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 +9 -0
- package/dist/agentcp.js +70 -0
- package/dist/group/client.js +11 -2
- package/dist/server.js +372 -46
- package/package.json +1 -1
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
|
*/
|
package/dist/group/client.js
CHANGED
|
@@ -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 = (
|
|
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
|
-
|
|
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;
|
|
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,#
|
|
663
|
-
.aid-group-header:hover { background:linear-gradient(135deg,#
|
|
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:
|
|
666
|
-
.aid-group-desc { font-size:10px; color
|
|
667
|
-
.aid-group-arrow { font-size:10px; color:var(--
|
|
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:
|
|
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:
|
|
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 #
|
|
684
|
-
.session-item::before { content:''; position:absolute; left:18px; top:
|
|
685
|
-
.session-item:hover { background:#
|
|
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:
|
|
689
|
-
.session-
|
|
690
|
-
.
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
1044
|
-
var
|
|
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(/^>\\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
|
-
|
|
1489
|
-
|
|
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);
|