acp-ts 1.1.5 → 1.1.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
@@ -85,6 +85,11 @@ declare class AgentCP implements IAgentCP {
85
85
  * 设置群组事件处理器
86
86
  */
87
87
  setGroupEventHandler(handler: ACPGroupEventHandler): void;
88
+ /**
89
+ * 创建默认的群组事件处理器(防御性,仅打印日志)。
90
+ * 外部可通过 setGroupEventHandler 覆盖。
91
+ */
92
+ private _createDefaultGroupEventHandler;
88
93
  /**
89
94
  * 设置群组游标存储
90
95
  */
@@ -127,6 +132,10 @@ declare class AgentCP implements IAgentCP {
127
132
  * 添加群组到本地存储
128
133
  */
129
134
  addGroupToStore(groupId: string, name: string): void;
135
+ /**
136
+ * 将加入的群组注册到 Home AP(内部方法)
137
+ */
138
+ registerGroupToHomeAP(groupId: string, groupUrl: string, role?: string): Promise<void>;
130
139
  /**
131
140
  * 从本地存储删除群组
132
141
  */
package/dist/agentcp.js CHANGED
@@ -333,6 +333,8 @@ class AgentCP {
333
333
  };
334
334
  this.groupClient = new group_1.ACPGroupClient(this.activeAid, sendFunc);
335
335
  this.groupOps = new group_1.GroupOperations(this.groupClient);
336
+ // 设置默认 event handler,防止通知被静默丢弃
337
+ this.groupClient.setEventHandler(this._createDefaultGroupEventHandler());
336
338
  }
337
339
  /**
338
340
  * 初始化跨AP群组客户端。
@@ -349,6 +351,8 @@ class AgentCP {
349
351
  };
350
352
  this.groupClient = new group_1.ACPGroupClient(this.activeAid, sendFunc);
351
353
  this.groupOps = new group_1.GroupOperations(this.groupClient);
354
+ // 设置默认 event handler,防止通知被静默丢弃
355
+ this.groupClient.setEventHandler(this._createDefaultGroupEventHandler());
352
356
  }
353
357
  /**
354
358
  * 处理群组协议消息路由。
@@ -393,6 +397,58 @@ class AgentCP {
393
397
  this.groupClient.setEventHandler(handler);
394
398
  }
395
399
  }
400
+ /**
401
+ * 创建默认的群组事件处理器(防御性,仅打印日志)。
402
+ * 外部可通过 setGroupEventHandler 覆盖。
403
+ */
404
+ _createDefaultGroupEventHandler() {
405
+ return {
406
+ onNewMessage(groupId, latestMsgId, sender, preview) {
407
+ console.log(`[Group][DefaultHandler] onNewMessage: group=${groupId} msgId=${latestMsgId} sender=${sender} preview=${preview}`);
408
+ },
409
+ onNewEvent(groupId, latestEventId, eventType, summary) {
410
+ console.log(`[Group][DefaultHandler] onNewEvent: group=${groupId} eventId=${latestEventId} type=${eventType}`);
411
+ },
412
+ onGroupInvite(groupId, groupAddress, invitedBy) {
413
+ console.log(`[Group][DefaultHandler] onGroupInvite: group=${groupId} address=${groupAddress} invitedBy=${invitedBy}`);
414
+ },
415
+ onJoinApproved: (groupId, groupAddress) => {
416
+ console.log(`[Group][DefaultHandler] onJoinApproved: group=${groupId} address=${groupAddress}`);
417
+ (async () => {
418
+ try {
419
+ if (!this.groupOps || !this._groupTargetAid) {
420
+ console.warn(`[Group][DefaultHandler] onJoinApproved skipped: groupOps or targetAid not available`);
421
+ return;
422
+ }
423
+ let groupName = groupId;
424
+ try {
425
+ const info = await this.groupOps.getGroupInfo(this._groupTargetAid, groupId);
426
+ groupName = info.name || groupId;
427
+ }
428
+ catch (_) { }
429
+ this.addGroupToStore(groupId, groupName);
430
+ const groupUrl = groupAddress || `https://${this._groupTargetAid}/${groupId}`;
431
+ await this.registerGroupToHomeAP(groupId, groupUrl);
432
+ }
433
+ catch (e) {
434
+ console.error(`[Group][DefaultHandler] onJoinApproved processing failed: group=${groupId}`, e.message);
435
+ }
436
+ })();
437
+ },
438
+ onJoinRejected(groupId, reason) {
439
+ console.log(`[Group][DefaultHandler] onJoinRejected: group=${groupId} reason=${reason}`);
440
+ },
441
+ onJoinRequestReceived(groupId, agentId, message) {
442
+ console.log(`[Group][DefaultHandler] onJoinRequestReceived: group=${groupId} agent=${agentId}`);
443
+ },
444
+ onGroupMessage(groupId, msg) {
445
+ console.log(`[Group][DefaultHandler] onGroupMessage: group=${groupId} msgId=${msg.msg_id} sender=${msg.sender}`);
446
+ },
447
+ onGroupEvent(groupId, evt) {
448
+ console.log(`[Group][DefaultHandler] onGroupEvent: group=${groupId} event=${evt.event_type}`);
449
+ },
450
+ };
451
+ }
396
452
  /**
397
453
  * 设置群组游标存储
398
454
  */
@@ -497,6 +553,20 @@ class AgentCP {
497
553
  return;
498
554
  this.groupMessageStore.getOrCreateGroup(groupId, this._groupTargetAid, name);
499
555
  }
556
+ /**
557
+ * 将加入的群组注册到 Home AP(内部方法)
558
+ */
559
+ async registerGroupToHomeAP(groupId, groupUrl, role = 'member') {
560
+ if (!this.groupOps || !this._groupTargetAid)
561
+ return;
562
+ try {
563
+ await this.groupOps.registerMembership(this._groupTargetAid, groupId, groupUrl, this._groupTargetAid, this._groupSessionId, role);
564
+ console.log(`[Group] registerGroupToHomeAP success: group=${groupId}`);
565
+ }
566
+ catch (e) {
567
+ console.error(`[Group] registerGroupToHomeAP failed: group=${groupId}`, e.message);
568
+ }
569
+ }
500
570
  /**
501
571
  * 从本地存储删除群组
502
572
  */
@@ -74,7 +74,7 @@ class ACPGroupClient {
74
74
  * Called by the message dispatch chain in AgentCP.
75
75
  */
76
76
  handleIncoming(payload) {
77
- var _a, _b;
77
+ var _a, _b, _c;
78
78
  // console.log(`[GroupClient] <<< handleIncoming raw payload (first 500 chars): ${payload.substring(0, 500)}`);
79
79
  let data;
80
80
  try {
@@ -95,6 +95,12 @@ class ACPGroupClient {
95
95
  clearTimeout(pending.timer);
96
96
  this._pendingReqs.delete(requestId);
97
97
  pending.resolve(resp);
98
+ // 如果响应同时携带 event 字段,也要 dispatch 通知
99
+ const event = (_b = data.event) !== null && _b !== void 0 ? _b : "";
100
+ if (event && this._handler != null) {
101
+ const notify = (0, types_1.parseGroupNotify)(data);
102
+ (0, events_1.dispatchAcpNotify)(this._handler, notify);
103
+ }
98
104
  return;
99
105
  }
100
106
  else {
@@ -102,12 +108,15 @@ class ACPGroupClient {
102
108
  }
103
109
  }
104
110
  // Try as notification (has event field)
105
- const event = (_b = data.event) !== null && _b !== void 0 ? _b : "";
111
+ const event = (_c = data.event) !== null && _c !== void 0 ? _c : "";
106
112
  if (event) {
107
113
  const notify = (0, types_1.parseGroupNotify)(data);
108
114
  if (this._handler != null) {
109
115
  (0, events_1.dispatchAcpNotify)(this._handler, notify);
110
116
  }
117
+ else {
118
+ console.warn(`[GroupClient] !!! notification event="${event}" dropped: no event handler registered. Call setEventHandler() first.`);
119
+ }
111
120
  return;
112
121
  }
113
122
  console.warn(`[GroupClient] !!! unhandled incoming message: no request_id and no event field`, JSON.stringify(data).substring(0, 300));
package/dist/server.js CHANGED
@@ -84,7 +84,8 @@ async function doEnsureOnline(aid) {
84
84
  const cp = new agentcp_1.AgentCP(globalApiUrl, '', globalDataDir || undefined, { persistMessages: true, persistGroupMessages: true });
85
85
  await cp.loadAid(aid);
86
86
  cp.setAutoGenerateAgentMd(true);
87
- cp.setAgentMdOptions({ type: 'human', tags: ['human', 'acp'] });
87
+ const customOpts = getAidMdOptionsForAid(aid);
88
+ cp.setAgentMdOptions(Object.assign({ type: 'human', tags: ['human', 'acp'] }, customOpts));
88
89
  const connConfig = await cp.online();
89
90
  console.log(`[Server] 自动上线 AID: ${aid}`);
90
91
  const hb = new heartbeat_1.HeartbeatClient(aid, connConfig.heartbeatServer, '');
@@ -223,6 +224,58 @@ async function ensureGroupClient(instance) {
223
224
  instance.agentWS.onRawMessage((message) => {
224
225
  return instance.agentCP.handleGroupMessage(message);
225
226
  });
227
+ // 注册群组事件处理器,确保 SDK 通知回调可靠触发
228
+ instance.agentCP.setGroupEventHandler({
229
+ onNewMessage(groupId, latestMsgId, sender, preview) {
230
+ console.log(`[Group] onNewMessage: group=${groupId} msgId=${latestMsgId} sender=${sender} preview=${preview}`);
231
+ // 收到新消息通知时,主动拉取并存储到本地
232
+ instance.agentCP.pullAndStoreGroupMessages(groupId, 20).catch(e => {
233
+ console.error(`[Group] auto-pull messages failed for group=${groupId}:`, e);
234
+ });
235
+ },
236
+ onNewEvent(groupId, latestEventId, eventType, summary) {
237
+ console.log(`[Group] onNewEvent: group=${groupId} eventId=${latestEventId} type=${eventType} summary=${summary}`);
238
+ },
239
+ onGroupInvite(groupId, groupAddress, invitedBy) {
240
+ console.log(`[Group] onGroupInvite: group=${groupId} address=${groupAddress} invitedBy=${invitedBy}`);
241
+ },
242
+ onJoinApproved(groupId, groupAddress) {
243
+ console.log(`[Group] onJoinApproved: group=${groupId} address=${groupAddress}`);
244
+ // 审核通过:获取群信息、添加本地存储、注册到 Home AP
245
+ (async () => {
246
+ try {
247
+ if (!instance.agentCP.groupOps) {
248
+ console.warn(`[Group] onJoinApproved skipped: groupOps not available`);
249
+ return;
250
+ }
251
+ let groupName = groupId;
252
+ try {
253
+ const info = await instance.agentCP.groupOps.getGroupInfo(instance.groupTargetAid, groupId);
254
+ groupName = info.name || groupId;
255
+ }
256
+ catch (_) { }
257
+ instance.agentCP.addGroupToStore(groupId, groupName);
258
+ const groupUrl = groupAddress || `https://${instance.groupTargetAid}/${groupId}`;
259
+ await instance.agentCP.registerGroupToHomeAP(groupId, groupUrl);
260
+ }
261
+ catch (e) {
262
+ console.error(`[Group] onJoinApproved processing failed: group=${groupId}`, e.message);
263
+ }
264
+ })();
265
+ },
266
+ onJoinRejected(groupId, reason) {
267
+ console.log(`[Group] onJoinRejected: group=${groupId} reason=${reason}`);
268
+ },
269
+ onJoinRequestReceived(groupId, agentId, message) {
270
+ console.log(`[Group] onJoinRequestReceived: group=${groupId} agent=${agentId} msg=${message}`);
271
+ },
272
+ onGroupMessage(groupId, msg) {
273
+ console.log(`[Group] onGroupMessage: group=${groupId} msgId=${msg.msg_id} sender=${msg.sender}`);
274
+ },
275
+ onGroupEvent(groupId, evt) {
276
+ console.log(`[Group] onGroupEvent: group=${groupId} event=${evt.event_type}`);
277
+ },
278
+ });
226
279
  instance.groupInitialized = true;
227
280
  instance.groupSessionId = groupSessionId;
228
281
  instance.groupTargetAid = targetAid;
@@ -257,7 +310,7 @@ async function getAidStatusList() {
257
310
  }
258
311
  return result;
259
312
  }
260
- // agent.md 信息缓存 (aid -> { type, name, description, cachedAt })
313
+ // agent.md 信息缓存 (aid -> { type, name, description, tags, cachedAt })
261
314
  // 内存缓存 + 本地文件持久化,TTL 24 小时
262
315
  const agentInfoCache = new Map();
263
316
  const AGENT_INFO_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
@@ -274,7 +327,7 @@ function loadAgentInfoCacheFromDisk() {
274
327
  const now = Date.now();
275
328
  for (const [aid, info] of entries) {
276
329
  if (now - info.cachedAt < AGENT_INFO_CACHE_TTL) {
277
- agentInfoCache.set(aid, info);
330
+ agentInfoCache.set(aid, Object.assign(Object.assign({}, info), { tags: info.tags || [] }));
278
331
  }
279
332
  }
280
333
  console.log(`[Server] 已加载 agent info 缓存: ${agentInfoCache.size} 条`);
@@ -315,7 +368,7 @@ function fetchAgentMd(aid) {
315
368
  });
316
369
  }
317
370
  function parseAgentMdFrontmatter(content) {
318
- const result = { type: '', name: '', description: '' };
371
+ const result = { type: '', name: '', description: '', tags: [] };
319
372
  const match = content.match(/^---\s*\n([\s\S]*?)\n---/);
320
373
  if (!match)
321
374
  return result;
@@ -329,12 +382,20 @@ function parseAgentMdFrontmatter(content) {
329
382
  result.name = nameMatch[1].trim();
330
383
  if (descMatch)
331
384
  result.description = descMatch[1].trim();
385
+ // parse tags list
386
+ const tagsBlock = yaml.match(/^tags:\s*\n((?:\s+-\s+.*\n?)*)/m);
387
+ if (tagsBlock) {
388
+ const tagLines = tagsBlock[1].match(/^\s+-\s+(.+)$/gm);
389
+ if (tagLines) {
390
+ result.tags = tagLines.map(l => l.replace(/^\s+-\s+/, '').trim().replace(/^"(.*)"$/, '$1'));
391
+ }
392
+ }
332
393
  return result;
333
394
  }
334
395
  async function getAgentInfo(aid) {
335
396
  const cached = agentInfoCache.get(aid);
336
397
  if (cached && Date.now() - cached.cachedAt < AGENT_INFO_CACHE_TTL) {
337
- return { type: cached.type, name: cached.name, description: cached.description };
398
+ return { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] };
338
399
  }
339
400
  try {
340
401
  const md = await fetchAgentMd(aid);
@@ -346,10 +407,40 @@ async function getAgentInfo(aid) {
346
407
  catch (_a) {
347
408
  // 远程请求失败时,如果有过期缓存也先用着
348
409
  if (cached) {
349
- return { type: cached.type, name: cached.name, description: cached.description };
410
+ return { type: cached.type, name: cached.name, description: cached.description, tags: cached.tags || [] };
411
+ }
412
+ return { type: '', name: '', description: '', tags: [] };
413
+ }
414
+ }
415
+ // 每个 AID 的自定义 agent.md 选项 (昵称、描述)
416
+ function getAidMdOptionsPath() {
417
+ const dir = globalDataDir || process.cwd();
418
+ return path.join(dir, 'AIDs', '.aid-md-options.json');
419
+ }
420
+ function loadAidMdOptions() {
421
+ try {
422
+ const filePath = getAidMdOptionsPath();
423
+ if (fs.existsSync(filePath)) {
424
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
350
425
  }
351
- return { type: '', name: '', description: '' };
352
426
  }
427
+ catch (_a) { }
428
+ return {};
429
+ }
430
+ function saveAidMdOptions(aid, opts) {
431
+ try {
432
+ const filePath = getAidMdOptionsPath();
433
+ const dir = path.dirname(filePath);
434
+ if (!fs.existsSync(dir))
435
+ fs.mkdirSync(dir, { recursive: true });
436
+ const all = loadAidMdOptions();
437
+ all[aid] = opts;
438
+ fs.writeFileSync(filePath, JSON.stringify(all, null, 2), 'utf-8');
439
+ }
440
+ catch (_a) { }
441
+ }
442
+ function getAidMdOptionsForAid(aid) {
443
+ return loadAidMdOptions()[aid] || {};
353
444
  }
354
445
  // 消息与会话管理 — 每个 AID 独立 MessageStore
355
446
  let activeSessionId = null;
@@ -383,19 +474,19 @@ const indexHtml = `<!DOCTYPE html>
383
474
  .container { background: white; padding: 32px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.1); max-width: 560px; width: 100%; }
384
475
  h1 { color: #333; margin-bottom: 24px; text-align: center; font-size: 22px; }
385
476
  .hint { text-align: center; color: #999; font-size: 13px; margin-bottom: 20px; }
386
- .create-section { margin-bottom: 24px; }
387
- .create-section .aid-input-row { display: flex; gap: 8px; margin-bottom: 10px; align-items: center; }
477
+ .create-section { margin-bottom: 24px; display: flex; flex-direction: column; gap: 12px; }
478
+ .create-section .aid-input-row { display: flex; gap: 8px; align-items: center; }
388
479
  .create-section .aid-input-row input { flex: 1; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; min-width: 0; }
389
480
  .create-section .aid-input-row input:focus { outline: none; border-color: #007bff; }
390
481
  .create-section .aid-input-row .dot-separator { color: #999; font-size: 16px; flex-shrink: 0; }
391
- .create-section .aid-input-row select {
392
- padding: 10px 30px 10px 14px;
393
- border: 1px solid #ddd;
394
- border-radius: 8px;
395
- font-size: 14px;
396
- background: white;
397
- flex-shrink: 0;
398
- cursor: pointer;
482
+ .create-section .aid-input-row select {
483
+ padding: 10px 30px 10px 14px;
484
+ border: 1px solid #ddd;
485
+ border-radius: 8px;
486
+ font-size: 14px;
487
+ background: white;
488
+ flex-shrink: 0;
489
+ cursor: pointer;
399
490
  appearance: none;
400
491
  -webkit-appearance: none;
401
492
  -moz-appearance: none;
@@ -405,6 +496,9 @@ const indexHtml = `<!DOCTYPE html>
405
496
  background-size: 10px auto;
406
497
  }
407
498
  .create-section .aid-input-row select:focus { outline: none; border-color: #007bff; }
499
+ .create-section .extra-fields { display: flex; gap: 8px; }
500
+ .create-section .extra-fields input { flex: 1; padding: 10px 14px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; min-width: 0; }
501
+ .create-section .extra-fields input:focus { outline: none; border-color: #007bff; }
408
502
  .btn { display: block; width: 100%; padding: 12px; border: none; border-radius: 8px; font-size: 15px; cursor: pointer; transition: background 0.2s; }
409
503
  .btn-primary { background: #007bff; color: white; }
410
504
  .btn-primary:hover { background: #0056b3; }
@@ -450,6 +544,10 @@ const indexHtml = `<!DOCTYPE html>
450
544
  <span class="dot-separator">.</span>
451
545
  <select id="apSelect"></select>
452
546
  </div>
547
+ <div class="extra-fields">
548
+ <input type="text" id="aidNickname" placeholder="昵称(选填)">
549
+ <input type="text" id="aidDescription" placeholder="描述(选填)" style="flex:2;">
550
+ </div>
453
551
  <button class="btn btn-primary" onclick="createAid()">注册 AID</button>
454
552
  </div>
455
553
 
@@ -558,10 +656,12 @@ const indexHtml = `<!DOCTYPE html>
558
656
  var ap = document.getElementById('apSelect').value;
559
657
  if (!ap) { showStatus('请选择 AP', 'error'); return; }
560
658
  var fullPrefix = prefix + '.' + ap;
659
+ var nickname = document.getElementById('aidNickname').value.trim();
660
+ var description = document.getElementById('aidDescription').value.trim();
561
661
  try {
562
- var res = await fetch('/api/aid/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prefix: fullPrefix }) });
662
+ var res = await fetch('/api/aid/create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prefix: fullPrefix, nickname: nickname, description: description }) });
563
663
  var data = await res.json();
564
- if (data.success) { showStatus('AID 注册成功', 'success'); document.getElementById('newAid').value = ''; loadAidInfo(); }
664
+ if (data.success) { showStatus('AID 注册成功', 'success'); document.getElementById('newAid').value = ''; document.getElementById('aidNickname').value = ''; document.getElementById('aidDescription').value = ''; loadAidInfo(); }
565
665
  else { showStatus(data.error || '注册失败', 'error'); }
566
666
  } catch (e) { showStatus('注册失败: ' + e.message, 'error'); }
567
667
  }
@@ -659,12 +759,12 @@ const chatHtml = `<!DOCTYPE html>
659
759
 
660
760
  /* AID Group */
661
761
  .aid-group { border-bottom:1px solid var(--border); }
662
- .aid-group-header { padding:12px 14px; display:flex; align-items:center; cursor:pointer; background:linear-gradient(135deg,#f8fafc,#f1f5f9); user-select:none; border-left:3px solid var(--primary); }
663
- .aid-group-header:hover { background:linear-gradient(135deg,#eef2f7,#e8edf4); }
762
+ .aid-group-header { padding:12px 14px; display:flex; align-items:center; cursor:pointer; background:linear-gradient(135deg,#eef4ff,#e8f0fe); user-select:none; border-left:3px solid var(--primary); transition:all 0.2s; }
763
+ .aid-group-header:hover { background:linear-gradient(135deg,#dbeafe,#d0e4fd); }
664
764
  .aid-group-info { flex:1; min-width:0; margin-left:4px; }
665
- .aid-group-title { font-size:13px; font-weight:700; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:block; }
666
- .aid-group-desc { font-size:10px; color:var(--t2); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:2px; display:block; }
667
- .aid-group-arrow { font-size:10px; color:var(--t2); transition:transform 0.2s; flex-shrink:0; }
765
+ .aid-group-title { font-size:13px; font-weight:700; color:#1e40af; background:linear-gradient(135deg,#dbeafe,#c7d7fe); padding:2px 8px; border-radius:6px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; display:inline-block; max-width:100%; border:1px solid #bfdbfe; }
766
+ .aid-group-desc { font-size:10px; color:#6b7280; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; margin-top:3px; display:block; padding-left:2px; }
767
+ .aid-group-arrow { font-size:10px; color:var(--primary); transition:transform 0.2s; flex-shrink:0; }
668
768
  .aid-group-arrow.open { transform:rotate(90deg); }
669
769
  .aid-group-badge { font-size:10px; background:var(--primary); color:#fff; padding:1px 6px; border-radius:8px; margin-left:8px; flex-shrink:0; }
670
770
  .aid-group-add { background:none; border:1px solid var(--border); color:var(--t2); width:22px; height:22px; border-radius:4px; cursor:pointer; font-size:14px; line-height:20px; text-align:center; margin-left:6px; flex-shrink:0; }
@@ -672,24 +772,25 @@ const chatHtml = `<!DOCTYPE html>
672
772
  .aid-group-del { background:none; border:none; color:var(--t2); width:20px; height:20px; border-radius:4px; cursor:pointer; font-size:12px; line-height:20px; text-align:center; margin-left:4px; flex-shrink:0; display:none; }
673
773
  .aid-group-header:hover .aid-group-del { display:block; }
674
774
  .aid-group-del:hover { color:#dc3545; background:#ffebeb; }
675
- .session-del { position:absolute; right:8px; top:12px; background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; }
775
+ .session-del { position:absolute; right:8px; top:50%; transform:translateY(-50%); background:none; border:none; color:var(--t2); font-size:12px; cursor:pointer; display:none; padding:2px; }
676
776
  .session-item:hover .session-del { display:block; }
677
777
  .session-del:hover { color:#dc3545; }
678
- .aid-group-sessions { display:none; }
778
+ .aid-group-sessions { display:none; background:#fafbfc; }
679
779
  .aid-group-sessions.open { display:block; }
680
780
 
681
- .aid-group-avatar { width:34px; height:34px; border-radius:50%; object-fit:cover; flex-shrink:0; margin-right:8px; box-shadow:0 1px 3px rgba(0,0,0,0.12); }
781
+ .aid-group-avatar { width:36px; height:36px; border-radius:50%; object-fit:cover; flex-shrink:0; margin-right:8px; box-shadow:0 1px 4px rgba(37,99,235,0.18); border:2px solid #bfdbfe; }
682
782
 
683
- .session-item { padding:10px 14px 10px 32px; border-bottom:1px solid #f3f4f6; cursor:pointer; transition:background 0.15s; position:relative; }
684
- .session-item::before { content:''; position:absolute; left:18px; top:16px; width:6px; height:6px; border-radius:50%; background:var(--border); }
685
- .session-item:hover { background:#f5f7fa; }
783
+ .session-item { padding:10px 14px 10px 32px; border-bottom:1px solid #f0f1f3; cursor:pointer; transition:all 0.15s; position:relative; }
784
+ .session-item::before { content:''; position:absolute; left:18px; top:50%; transform:translateY(-50%); width:6px; height:6px; border-radius:50%; background:#d1d5db; }
785
+ .session-item:hover { background:#f0f5ff; }
686
786
  .session-item.active { background:#eff6ff; border-left:3px solid var(--primary); padding-left:29px; }
687
- .session-item.active::before { background:var(--primary); }
688
- .session-peer { font-weight:400; font-size:12px; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-left:10px; }
689
- .session-meta { font-size:10px; color:var(--t2); margin-top:2px; display:flex; align-items:center; gap:6px; padding-left:10px; }
690
- .tag { font-size:9px; padding:1px 5px; border-radius:3px; color:#fff; }
787
+ .session-item.active::before { background:var(--primary); box-shadow:0 0 0 2px rgba(37,99,235,0.2); }
788
+ .session-peer { font-weight:500; font-size:12px; color:var(--t1); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; padding-left:10px; background:#f1f5f9; border-radius:4px; padding:3px 8px 3px 10px; border:1px solid #e8ecf1; }
789
+ .session-item.active .session-peer { background:#dbeafe; border-color:#bfdbfe; color:#1e40af; }
790
+ .session-meta { font-size:10px; color:var(--t2); margin-top:4px; display:flex; align-items:center; gap:6px; padding-left:10px; }
791
+ .tag { font-size:9px; padding:1px 5px; border-radius:3px; color:#fff; font-weight:600; letter-spacing:0.3px; }
691
792
  .tag.outgoing { background:var(--ok); }
692
- .tag.incoming { background:var(--t2); }
793
+ .tag.incoming { background:#8b5cf6; }
693
794
 
694
795
  /* Chat Area */
695
796
  .chat-area { flex:1; display:flex; flex-direction:column; background:var(--chat-bg); min-width:0; }
@@ -755,7 +856,8 @@ const chatHtml = `<!DOCTYPE html>
755
856
  .bubble ul, .bubble ol { padding-left:1.5em; margin-bottom:0.5em; }
756
857
  .bubble li { margin-bottom:0.2em; }
757
858
  .bubble blockquote { margin:0.5em 0; padding-left:1em; border-left:4px solid rgba(0,0,0,0.1); color:var(--t2); }
758
- .bubble a { color:var(--primary); text-decoration:none; } .bubble a:hover { text-decoration:underline; }
859
+ .bubble a { color:var(--primary); text-decoration:underline; } .bubble a:hover { opacity:0.85; }
860
+ .message.sent .bubble a { color:#fff; } .message.sent .bubble a:hover { opacity:0.85; }
759
861
  .bubble img { max-width:100%; border-radius:4px; }
760
862
  .bubble code { background:rgba(0,0,0,0.1); padding:2px 4px; border-radius:3px; font-family:monospace; font-size:0.9em; }
761
863
  .bubble pre { background:#2d2d2d; color:#fff; padding:12px; border-radius:6px; overflow-x:auto; margin:8px 0; }
@@ -823,6 +925,7 @@ const chatHtml = `<!DOCTYPE html>
823
925
  <div class="group-actions">
824
926
  <div class="gbtn" onclick="showCreateGroupModal()">创建群组</div>
825
927
  <div class="gbtn" onclick="showJoinGroupModal()">加入群组</div>
928
+ <div class="gbtn" onclick="showMyGroups()">我的群</div>
826
929
  </div>
827
930
  <div class="group-list" id="groupList"><div style="padding:20px;text-align:center;color:#999;font-size:12px;">暂无群组</div></div>
828
931
  </div>
@@ -908,9 +1011,9 @@ const chatHtml = `<!DOCTYPE html>
908
1011
  </div>
909
1012
  </div>
910
1013
  <div class="modal-overlay" id="membersModal">
911
- <div class="modal">
1014
+ <div class="modal" style="max-width:520px;">
912
1015
  <h3>群组成员</h3>
913
- <div id="membersList" style="max-height:300px;overflow-y:auto;margin-bottom:16px;font-size:13px;"></div>
1016
+ <div id="membersList" style="max-height:400px;overflow-y:auto;margin-bottom:16px;font-size:13px;"></div>
914
1017
  <div class="modal-btns">
915
1018
  <button class="mbtn mbtn-cancel" onclick="hideMembersModal()">关闭</button>
916
1019
  </div>
@@ -925,6 +1028,15 @@ const chatHtml = `<!DOCTYPE html>
925
1028
  </div>
926
1029
  </div>
927
1030
  </div>
1031
+ <div class="modal-overlay" id="myGroupsModal">
1032
+ <div class="modal" style="max-width:560px;">
1033
+ <h3>我的群</h3>
1034
+ <div id="myGroupsContent" style="max-height:420px;overflow-y:auto;margin-bottom:16px;font-size:13px;"></div>
1035
+ <div class="modal-btns">
1036
+ <button class="mbtn mbtn-cancel" onclick="hideMyGroupsModal()">关闭</button>
1037
+ </div>
1038
+ </div>
1039
+ </div>
928
1040
  <script>
929
1041
  var S = { aid:'', sid:null, sessions:[], status:'disconnected', expanded:{}, sidebarOpen:true, aidList:[], closed:false, tab:'p2p', activeGroupId:null, groups:[], groupMsgs:[], groupTargetAid:'', isGroupCreator:false };
930
1042
  var D = {};
@@ -942,7 +1054,7 @@ const chatHtml = `<!DOCTYPE html>
942
1054
  var d = await r.json();
943
1055
  if (d.type || d.name) { agentInfoCache[aid] = d; }
944
1056
  return d;
945
- } catch(e) { return { type:'', name:'', description:'' }; }
1057
+ } catch(e) { return { type:'', name:'', description:'', tags:[] }; }
946
1058
  }
947
1059
  async function deleteSession(e, sessionId){
948
1060
  e.stopPropagation();
@@ -1040,12 +1152,17 @@ const chatHtml = `<!DOCTYPE html>
1040
1152
  var _pollCount=0;
1041
1153
  async function poll(){
1042
1154
  try {
1043
- var [sr,mr,wr] = await Promise.all([fetch('/api/sessions'),fetch('/api/messages'),fetch('/api/ws/status')]);
1044
- var sd=await sr.json(), md=await mr.json(), wd=await wr.json();
1045
- if(sd.sessions) updateSessions(sd.sessions, sd.activeSessionId);
1046
- S.closed=md.closed||false;
1047
- if(md.messages) renderMsgs(md.messages, S.closed);
1155
+ var wr=await fetch('/api/ws/status');
1156
+ var wd=await wr.json();
1048
1157
  updateDot(wd.status);
1158
+ // P2P会话和消息仅在P2P标签页时刷新
1159
+ if(S.tab==='p2p'){
1160
+ var [sr,mr] = await Promise.all([fetch('/api/sessions'),fetch('/api/messages')]);
1161
+ var sd=await sr.json(), md=await mr.json();
1162
+ if(sd.sessions) updateSessions(sd.sessions, sd.activeSessionId);
1163
+ S.closed=md.closed||false;
1164
+ if(md.messages) renderMsgs(md.messages, S.closed);
1165
+ }
1049
1166
  // 每5次轮询刷新一次AID在线状态
1050
1167
  if(++_pollCount%5===0){
1051
1168
  var ar=await fetch('/api/aid'); var ad=await ar.json();
@@ -1273,6 +1390,8 @@ const chatHtml = `<!DOCTYPE html>
1273
1390
  D.groupInfoBar.style.display=S.activeGroupId?'flex':'none';
1274
1391
  D.input.placeholder='输入群消息...';
1275
1392
  D.input.disabled=!S.activeGroupId;
1393
+ D.msgs.dataset.s='';
1394
+ _lastGroupMsgSig='';
1276
1395
  initGroupClient();
1277
1396
  pollGroupList();
1278
1397
  if(S.activeGroupId) pollGroupMessages();
@@ -1282,6 +1401,17 @@ const chatHtml = `<!DOCTYPE html>
1282
1401
  D.groupInfoBar.style.display='none';
1283
1402
  D.input.placeholder='输入消息...';
1284
1403
  D.input.disabled=false;
1404
+ _lastGroupMsgSig='';
1405
+ // 切回P2P时立即刷新消息
1406
+ D.msgs.dataset.s='';
1407
+ if(S.sid){
1408
+ fetch('/api/messages').then(function(r){ return r.json(); }).then(function(d){
1409
+ S.closed=d.closed||false;
1410
+ if(d.messages) renderMsgs(d.messages, S.closed);
1411
+ }).catch(function(){});
1412
+ } else {
1413
+ D.msgs.innerHTML='';
1414
+ }
1285
1415
  }
1286
1416
  }
1287
1417
 
@@ -1477,6 +1607,62 @@ const chatHtml = `<!DOCTYPE html>
1477
1607
  } catch(e){ alert('生成邀请码失败: '+e.message); }
1478
1608
  }
1479
1609
 
1610
+ function copyMemberAid(btn,aid){
1611
+ navigator.clipboard.writeText(aid).then(function(){
1612
+ btn.textContent='已复制';
1613
+ setTimeout(function(){ btn.textContent='复制'; },1200);
1614
+ });
1615
+ }
1616
+
1617
+ async function openAgentMdPage(aid){
1618
+ try {
1619
+ var r=await fetch('/api/agent-md-raw?aid='+encodeURIComponent(aid));
1620
+ var d=await r.json();
1621
+ if(!d.success||!d.content){ alert(d.error||'获取 agent.md 失败'); return; }
1622
+ var md=d.content;
1623
+ // 简单 markdown 渲染
1624
+ function renderMd(src){
1625
+ var h=escH(src);
1626
+ // headings
1627
+ h=h.replace(/^######\\s+(.+)$/gm,'<h6>$1</h6>');
1628
+ h=h.replace(/^#####\\s+(.+)$/gm,'<h5>$1</h5>');
1629
+ h=h.replace(/^####\\s+(.+)$/gm,'<h4>$1</h4>');
1630
+ h=h.replace(/^###\\s+(.+)$/gm,'<h3>$1</h3>');
1631
+ h=h.replace(/^##\\s+(.+)$/gm,'<h2>$1</h2>');
1632
+ h=h.replace(/^#\\s+(.+)$/gm,'<h1>$1</h1>');
1633
+ // bold & italic
1634
+ h=h.replace(/\\*\\*(.+?)\\*\\*/g,'<strong>$1</strong>');
1635
+ h=h.replace(/\\*(.+?)\\*/g,'<em>$1</em>');
1636
+ // blockquote
1637
+ h=h.replace(/^&gt;\\s?(.+)$/gm,'<blockquote style="border-left:3px solid #ddd;padding-left:12px;color:#666;margin:8px 0;">$1</blockquote>');
1638
+ // list items
1639
+ h=h.replace(/^-\\s+(.+)$/gm,'<li>$1</li>');
1640
+ // code inline
1641
+ var bt=String.fromCharCode(96);
1642
+ h=h.replace(new RegExp(bt+'([^'+bt+']+)'+bt,'g'),'<code style="background:#f5f5f5;padding:1px 4px;border-radius:3px;font-size:12px;">$1</code>');
1643
+ // frontmatter block: hide ---...---
1644
+ h=h.replace(/^---[\\s\\S]*?---\\s*/,'');
1645
+ // paragraphs
1646
+ h=h.replace(/\\n\\n/g,'</p><p>');
1647
+ h='<p>'+h+'</p>';
1648
+ return h;
1649
+ }
1650
+ var html='<!DOCTYPE html><html><head><meta charset="utf-8"><title>'+escH(aid)+' - Agent Profile</title>'
1651
+ +'<style>body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;max-width:720px;margin:40px auto;padding:0 20px;color:#333;line-height:1.6;}'
1652
+ +'h1{border-bottom:2px solid #eee;padding-bottom:8px;}h2{border-bottom:1px solid #eee;padding-bottom:6px;margin-top:24px;}'
1653
+ +'ul{padding-left:20px;}li{margin:4px 0;}blockquote{margin:12px 0;}'
1654
+ +'pre{background:#f5f5f5;padding:12px;border-radius:6px;overflow-x:auto;}'
1655
+ +'.aid-badge{display:inline-block;background:#e8f4fd;color:#0969da;padding:2px 8px;border-radius:10px;font-size:12px;font-family:monospace;margin-bottom:16px;}'
1656
+ +'</style></head><body>'
1657
+ +'<div class="aid-badge">'+escH(aid)+'</div>'
1658
+ +renderMd(md)
1659
+ +'</body></html>';
1660
+ var w=window.open('','_blank');
1661
+ if(w){ w.document.write(html); w.document.close(); }
1662
+ else { alert('弹窗被拦截,请允许弹窗后重试'); }
1663
+ } catch(e){ alert('获取 agent.md 失败: '+e.message); }
1664
+ }
1665
+
1480
1666
  async function showGroupMembers(){
1481
1667
  if(!S.activeGroupId) return;
1482
1668
  try {
@@ -1485,10 +1671,62 @@ const chatHtml = `<!DOCTYPE html>
1485
1671
  if(d.members){
1486
1672
  var html=d.members.map(function(m){
1487
1673
  var aid=m.agent_id||m;
1488
- var role=m.role?' ('+m.role+')':'';
1489
- return '<div style="padding:6px 0;border-bottom:1px solid #f3f4f6;font-family:monospace;font-size:12px;">'+escH(typeof aid==='string'?aid:JSON.stringify(aid))+role+'</div>';
1674
+ if(typeof aid!=='string') aid=JSON.stringify(aid);
1675
+ var role=m.role||'';
1676
+ var cachedInfo=agentInfoCache[aid];
1677
+ var avatarSrc=getAvatarSrc(cachedInfo?cachedInfo.type:'');
1678
+ var displayName=(cachedInfo&&cachedInfo.name)?cachedInfo.name:aid.split('.')[0];
1679
+ var typeTags='';
1680
+ if(cachedInfo&&cachedInfo.tags&&cachedInfo.tags.length){
1681
+ typeTags=cachedInfo.tags.map(function(t){ return '<span style="display:inline-block;background:#e8f4fd;color:#0969da;padding:1px 6px;border-radius:8px;font-size:10px;margin-right:4px;">'+escH(t)+'</span>'; }).join('');
1682
+ } else if(cachedInfo&&cachedInfo.type){
1683
+ typeTags='<span style="display:inline-block;background:#e8f4fd;color:#0969da;padding:1px 6px;border-radius:8px;font-size:10px;">'+escH(cachedInfo.type)+'</span>';
1684
+ }
1685
+ if(role){ typeTags+='<span style="display:inline-block;background:#fff3cd;color:#856404;padding:1px 6px;border-radius:8px;font-size:10px;margin-left:4px;">'+escH(role)+'</span>'; }
1686
+ var safeId='member-'+escH(aid).replace(/\\./g,'_');
1687
+ return '<div id="'+safeId+'" style="padding:10px 0;border-bottom:1px solid #f3f4f6;display:flex;align-items:center;gap:10px;">'
1688
+ +'<img src="'+avatarSrc+'" style="width:36px;height:36px;border-radius:50%;flex-shrink:0;" class="member-avatar" data-aid="'+escH(aid)+'">'
1689
+ +'<div style="flex:1;min-width:0;">'
1690
+ +'<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;">'
1691
+ +'<span style="font-size:13px;font-weight:500;" class="member-name" data-aid="'+escH(aid)+'">'+escH(displayName)+'</span>'
1692
+ +'<span class="member-tags" data-aid="'+escH(aid)+'">'+typeTags+'</span>'
1693
+ +'</div>'
1694
+ +'<div style="font-size:11px;color:var(--t2);font-family:monospace;margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">'+escH(aid)+'</div>'
1695
+ +'</div>'
1696
+ +'<div style="display:flex;gap:4px;flex-shrink:0;">'
1697
+ +'<button class="mbtn mbtn-ok" style="padding:4px 10px;font-size:11px;" onclick="copyMemberAid(this,\\''+escH(aid)+'\\')">复制</button>'
1698
+ +'<button class="mbtn mbtn-cancel" style="padding:4px 10px;font-size:11px;" onclick="openAgentMdPage(\\''+escH(aid)+'\\')">查看</button>'
1699
+ +'</div></div>';
1490
1700
  }).join('');
1491
1701
  $('membersList').innerHTML=html||'<div style="color:#999;">暂无成员</div>';
1702
+ // 异步加载未缓存的 agent info
1703
+ d.members.forEach(function(m){
1704
+ var aid=m.agent_id||m;
1705
+ if(typeof aid!=='string') aid=JSON.stringify(aid);
1706
+ if(!aid||agentInfoCache[aid]) return;
1707
+ fetchAgentInfo(aid).then(function(info){
1708
+ if(!info||(!info.name&&!info.type)) return;
1709
+ var safeId='member-'+aid.replace(/\\./g,'_');
1710
+ var el=document.getElementById(safeId);
1711
+ if(!el) return;
1712
+ var avatarEl=el.querySelector('.member-avatar[data-aid="'+aid+'"]');
1713
+ var nameEl=el.querySelector('.member-name[data-aid="'+aid+'"]');
1714
+ var tagsEl=el.querySelector('.member-tags[data-aid="'+aid+'"]');
1715
+ if(avatarEl) avatarEl.src=getAvatarSrc(info.type);
1716
+ if(nameEl) nameEl.textContent=info.name||aid.split('.')[0];
1717
+ if(tagsEl){
1718
+ var tags='';
1719
+ if(info.tags&&info.tags.length){
1720
+ tags=info.tags.map(function(t){ return '<span style="display:inline-block;background:#e8f4fd;color:#0969da;padding:1px 6px;border-radius:8px;font-size:10px;margin-right:4px;">'+escH(t)+'</span>'; }).join('');
1721
+ } else if(info.type){
1722
+ tags='<span style="display:inline-block;background:#e8f4fd;color:#0969da;padding:1px 6px;border-radius:8px;font-size:10px;">'+escH(info.type)+'</span>';
1723
+ }
1724
+ // 保留已有的 role tag
1725
+ var existingRole=tagsEl.querySelector('span[style*="fff3cd"]');
1726
+ tagsEl.innerHTML=tags+(existingRole?existingRole.outerHTML:'');
1727
+ }
1728
+ });
1729
+ });
1492
1730
  } else { $('membersList').innerHTML='<div style="color:#999;">获取失败</div>'; }
1493
1731
  $('membersModal').classList.add('show');
1494
1732
  } catch(e){ alert('获取成员失败: '+e.message); }
@@ -1578,6 +1816,39 @@ const chatHtml = `<!DOCTYPE html>
1578
1816
  } catch(e){ alert('退出失败: '+e.message); }
1579
1817
  }
1580
1818
 
1819
+ // ============================================================
1820
+ // 我的群 Functions
1821
+ // ============================================================
1822
+ function showMyGroupsModal(){ $('myGroupsModal').classList.add('show'); }
1823
+ function hideMyGroupsModal(){ $('myGroupsModal').classList.remove('show'); }
1824
+ async function showMyGroups(){
1825
+ showMyGroupsModal();
1826
+ $('myGroupsContent').innerHTML='<div style="text-align:center;padding:20px;color:#999;">加载中...</div>';
1827
+ try {
1828
+ var r=await fetch('/api/group/my-groups');
1829
+ var d=await r.json();
1830
+ if(!d.success){ $('myGroupsContent').innerHTML='<div style="text-align:center;padding:20px;color:#e74c3c;">'+escH(d.error||'获取失败')+'</div>'; return; }
1831
+ var groups=d.groups||[];
1832
+ if(!groups.length){ $('myGroupsContent').innerHTML='<div style="text-align:center;padding:20px;color:#999;">暂无群组</div>'; return; }
1833
+ var html='<table style="width:100%;border-collapse:collapse;font-size:12px;">';
1834
+ html+='<tr style="background:#f8fafc;"><th style="padding:8px 6px;text-align:left;border-bottom:1px solid #e2e8f0;">群名称</th><th style="padding:8px 6px;text-align:left;border-bottom:1px solid #e2e8f0;">群ID</th><th style="padding:8px 6px;text-align:center;border-bottom:1px solid #e2e8f0;">角色</th><th style="padding:8px 6px;text-align:center;border-bottom:1px solid #e2e8f0;">状态</th></tr>';
1835
+ groups.forEach(function(g){
1836
+ var statusText=g.status===1?'正常':g.status===0?'待审核':'未知('+g.status+')';
1837
+ var statusColor=g.status===1?'#10b981':g.status===0?'#f59e0b':'#94a3b8';
1838
+ var shortId=g.group_id.length>16?g.group_id.substring(0,16)+'...':g.group_id;
1839
+ html+='<tr style="border-bottom:1px solid #f1f5f9;cursor:pointer;" onmouseover="this.style.background=\\'#f0f9ff\\'" onmouseout="this.style.background=\\'\\'">';
1840
+ html+='<td style="padding:8px 6px;font-weight:500;">'+escH(g.name||g.group_id)+'</td>';
1841
+ html+='<td style="padding:8px 6px;color:#64748b;" title="'+escH(g.group_id)+'">'+escH(shortId)+'</td>';
1842
+ html+='<td style="padding:8px 6px;text-align:center;">'+escH(g.role||'-')+'</td>';
1843
+ html+='<td style="padding:8px 6px;text-align:center;"><span style="color:'+statusColor+';font-weight:500;">'+escH(statusText)+'</span></td>';
1844
+ html+='</tr>';
1845
+ });
1846
+ html+='</table>';
1847
+ html+='<div style="margin-top:8px;font-size:11px;color:#94a3b8;text-align:right;">共 '+d.total+' 个群组</div>';
1848
+ $('myGroupsContent').innerHTML=html;
1849
+ } catch(e){ $('myGroupsContent').innerHTML='<div style="text-align:center;padding:20px;color:#e74c3c;">请求失败: '+escH(e.message)+'</div>'; }
1850
+ }
1851
+
1581
1852
  // 扩展轮询:群组模式下也轮询群消息
1582
1853
  var _origPoll=poll;
1583
1854
  poll=async function(){
@@ -1676,6 +1947,22 @@ async function handleRequest(req, res) {
1676
1947
  sendJson(res, info);
1677
1948
  return;
1678
1949
  }
1950
+ // 获取远程 agent.md 原始内容
1951
+ if (pathname === '/api/agent-md-raw' && method === 'GET') {
1952
+ const aid = parsedUrl.query.aid;
1953
+ if (!aid) {
1954
+ sendJson(res, { success: false, error: '缺少 aid' });
1955
+ return;
1956
+ }
1957
+ try {
1958
+ const md = await fetchAgentMd(aid);
1959
+ sendJson(res, { success: true, content: md });
1960
+ }
1961
+ catch (e) {
1962
+ sendJson(res, { success: false, error: e.message || '获取失败' });
1963
+ }
1964
+ return;
1965
+ }
1679
1966
  if (pathname === '/api/aid' && method === 'GET') {
1680
1967
  try {
1681
1968
  const aidList = await datamanager_1.CertAndKeyStore.getAids();
@@ -1752,6 +2039,17 @@ async function handleRequest(req, res) {
1752
2039
  try {
1753
2040
  const created = await agentCP.createAid(aid);
1754
2041
  currentAid = created;
2042
+ // 保存自定义昵称和描述
2043
+ const nickname = (body.nickname || '').trim();
2044
+ const description = (body.description || '').trim();
2045
+ if (nickname || description) {
2046
+ const opts = {};
2047
+ if (nickname)
2048
+ opts.name = nickname;
2049
+ if (description)
2050
+ opts.description = description;
2051
+ saveAidMdOptions(created, opts);
2052
+ }
1755
2053
  sendJson(res, { success: true, aid: created });
1756
2054
  }
1757
2055
  catch (createErr) {
@@ -2158,6 +2456,9 @@ async function handleRequest(req, res) {
2158
2456
  }
2159
2457
  catch (_) { }
2160
2458
  instance.agentCP.addGroupToStore(groupId, groupName);
2459
+ // 注册到 Home AP
2460
+ const registrationUrl = `https://${targetAid}/${groupId}`;
2461
+ await instance.agentCP.registerGroupToHomeAP(groupId, registrationUrl);
2161
2462
  sendJson(res, { success: true, group_id: groupId });
2162
2463
  }
2163
2464
  else {
@@ -2223,6 +2524,31 @@ async function handleRequest(req, res) {
2223
2524
  }
2224
2525
  return;
2225
2526
  }
2527
+ if (pathname === '/api/group/my-groups' && method === 'GET') {
2528
+ try {
2529
+ const instance = await ensureOnline();
2530
+ await ensureGroupClient(instance);
2531
+ const ops = instance.agentCP.groupOps;
2532
+ const target = instance.groupTargetAid;
2533
+ const result = await ops.listMyGroups(target);
2534
+ // 尝试获取每个群的详细信息(名称等)
2535
+ const groups = [];
2536
+ for (const m of result.groups) {
2537
+ let name = m.group_id;
2538
+ try {
2539
+ const info = await ops.getGroupInfo(target, m.group_id);
2540
+ name = info.name || m.group_id;
2541
+ }
2542
+ catch (_) { }
2543
+ groups.push(Object.assign(Object.assign({}, m), { name }));
2544
+ }
2545
+ sendJson(res, { success: true, groups, total: result.total });
2546
+ }
2547
+ catch (e) {
2548
+ sendJson(res, { success: false, error: e.message, groups: [] });
2549
+ }
2550
+ return;
2551
+ }
2226
2552
  if (pathname === '/api/group/leave' && method === 'POST') {
2227
2553
  try {
2228
2554
  const body = await parseBody(req);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acp-ts",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "description": "基于 ACP智能体通信协议 的智能体通信库,提供智能体身份管理和实时通信功能",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",