@wings006/agent-link 0.1.3 → 0.1.5

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.
@@ -2,12 +2,19 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { z } from "zod";
4
4
  import http from "node:http";
5
+ import { readFileSync } from "node:fs";
6
+ import { fileURLToPath } from "node:url";
7
+ import path from "node:path";
5
8
  import { sendNotification } from "./notify.js";
9
+ import { loadConfig, saveConfig, CONFIG_PATH } from "./config.js";
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const pkg = JSON.parse(readFileSync(path.join(__dirname, "..", "package.json"), "utf-8"));
6
12
  export class AgentLinkMcpServer {
7
13
  mcp;
8
14
  daemonUrl;
9
15
  agentName;
10
16
  pendingTasks = new Map();
17
+ hasShownPollTip = false;
11
18
  constructor(agentName, daemonApiPort) {
12
19
  this.agentName = agentName;
13
20
  this.daemonUrl = `http://127.0.0.1:${daemonApiPort}`;
@@ -15,6 +22,7 @@ export class AgentLinkMcpServer {
15
22
  name: "agent-link",
16
23
  version: "0.1.0",
17
24
  });
25
+ this.registerPrompts();
18
26
  this.registerTools();
19
27
  }
20
28
  async daemonFetch(path, options) {
@@ -25,9 +33,486 @@ export class AgentLinkMcpServer {
25
33
  }
26
34
  return res;
27
35
  }
36
+ registerPrompts() {
37
+ this.mcp.prompt("check-messages", "检查收件箱,播报新消息并询问是否回复", async () => {
38
+ try {
39
+ const res = await this.daemonFetch("/api/tasks/inbox?unread_only=true");
40
+ const tasks = (await res.json());
41
+ if (tasks.length === 0) {
42
+ return { messages: [{ role: "user", content: { type: "text", text: "[Agent Link] 没有新消息。" } }] };
43
+ }
44
+ const summary = tasks.map((t) => `- 来自 ${t.from}(${t.createdAt}): ${t.message}(ID: ${t.id})`).join("\n");
45
+ return {
46
+ messages: [{
47
+ role: "user",
48
+ content: {
49
+ type: "text",
50
+ text: `[Agent Link] 收到 ${tasks.length} 条未读消息:\n\n${summary}\n\n请简洁地播报这些消息给用户,并询问是否需要回复。如需回复,使用 agent_reply 工具。`,
51
+ },
52
+ }],
53
+ };
54
+ }
55
+ catch {
56
+ return { messages: [{ role: "user", content: { type: "text", text: "[Agent Link] 无法连接 daemon,跳过本次检查。" } }] };
57
+ }
58
+ });
59
+ }
60
+ async checkMuted(type, name) {
61
+ try {
62
+ const res = await this.daemonFetch(`/api/mute/check?type=${encodeURIComponent(type)}&name=${encodeURIComponent(name)}`);
63
+ const data = (await res.json());
64
+ return data.muted;
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
28
70
  registerTools() {
71
+ // 发送好友请求
72
+ this.mcp.tool("agent_friend_request", "向目标 Agent 发送好友请求", {
73
+ to: z.string().describe("目标 Agent 名称"),
74
+ message: z.string().optional().describe("附言"),
75
+ }, async ({ to, message }) => {
76
+ try {
77
+ const res = await this.daemonFetch("/api/friends/request", {
78
+ method: "POST", headers: { "Content-Type": "application/json" },
79
+ body: JSON.stringify({ to, message }),
80
+ });
81
+ const data = (await res.json());
82
+ return { content: [{ type: "text", text: `好友请求已发送给 ${to}(请求ID: ${data.requestId})` }] };
83
+ }
84
+ catch (err) {
85
+ return { content: [{ type: "text", text: `发送失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
86
+ }
87
+ });
88
+ // 查看好友请求
89
+ this.mcp.tool("agent_friend_requests", "查看收到的好友请求", {}, async () => {
90
+ try {
91
+ const res = await this.daemonFetch("/api/friends/requests");
92
+ const reqs = (await res.json());
93
+ return {
94
+ content: [{
95
+ type: "text",
96
+ text: reqs.length === 0 ? "没有待处理的好友请求" : JSON.stringify(reqs, null, 2),
97
+ }],
98
+ };
99
+ }
100
+ catch (err) {
101
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
102
+ }
103
+ });
104
+ // 回复好友请求
105
+ this.mcp.tool("agent_friend_respond", "接受或拒绝好友请求", {
106
+ request_id: z.string().describe("好友请求 ID"),
107
+ accept: z.boolean().describe("true=接受, false=拒绝"),
108
+ }, async ({ request_id, accept }) => {
109
+ try {
110
+ await this.daemonFetch("/api/friends/respond", {
111
+ method: "POST", headers: { "Content-Type": "application/json" },
112
+ body: JSON.stringify({ request_id, accept }),
113
+ });
114
+ return { content: [{ type: "text", text: accept ? "已接受好友请求" : "已拒绝好友请求" }] };
115
+ }
116
+ catch (err) {
117
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
118
+ }
119
+ });
120
+ // 查看好友列表
121
+ this.mcp.tool("agent_friends", "查看好友列表(含在线状态)", {
122
+ category: z.string().optional().describe("按分组筛选"),
123
+ }, async ({ category }) => {
124
+ try {
125
+ const url = category ? `/api/friends?category=${encodeURIComponent(category)}` : "/api/friends";
126
+ const res = await this.daemonFetch(url);
127
+ const friends = (await res.json());
128
+ return {
129
+ content: [{
130
+ type: "text",
131
+ text: friends.length === 0 ? "好友列表为空" : JSON.stringify(friends, null, 2),
132
+ }],
133
+ };
134
+ }
135
+ catch (err) {
136
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
137
+ }
138
+ });
139
+ // 删除好友
140
+ this.mcp.tool("agent_friend_remove", "删除好友", {
141
+ name: z.string().describe("好友名称"),
142
+ }, async ({ name }) => {
143
+ try {
144
+ await this.daemonFetch("/api/friends/remove", {
145
+ method: "POST", headers: { "Content-Type": "application/json" },
146
+ body: JSON.stringify({ name }),
147
+ });
148
+ return { content: [{ type: "text", text: `已删除好友 ${name}` }] };
149
+ }
150
+ catch (err) {
151
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
152
+ }
153
+ });
154
+ // 设置好友分组
155
+ this.mcp.tool("agent_friend_category", "设置好友分组", {
156
+ friend: z.string().describe("好友名称"),
157
+ category: z.string().describe("分组名称"),
158
+ }, async ({ friend, category }) => {
159
+ try {
160
+ await this.daemonFetch("/api/friends/category", {
161
+ method: "POST", headers: { "Content-Type": "application/json" },
162
+ body: JSON.stringify({ friend, category }),
163
+ });
164
+ return { content: [{ type: "text", text: `已将 ${friend} 移至分组「${category}」` }] };
165
+ }
166
+ catch (err) {
167
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
168
+ }
169
+ });
170
+ // 静音/取消静音
171
+ this.mcp.tool("agent_mute", "静音指定 Agent 或群组,静音后不弹出系统通知", {
172
+ type: z.enum(["agent", "group"]).describe("类型:agent 或 group"),
173
+ name: z.string().describe("Agent 名称或群组 ID"),
174
+ }, async ({ type, name }) => {
175
+ try {
176
+ await this.daemonFetch("/api/mute", {
177
+ method: "POST",
178
+ headers: { "Content-Type": "application/json" },
179
+ body: JSON.stringify({ type, name }),
180
+ });
181
+ return { content: [{ type: "text", text: `已静音 ${type === "agent" ? "Agent" : "群组"} "${name}"` }] };
182
+ }
183
+ catch (err) {
184
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
185
+ }
186
+ });
187
+ this.mcp.tool("agent_unmute", "取消静音指定 Agent 或群组", {
188
+ type: z.enum(["agent", "group"]).describe("类型:agent 或 group"),
189
+ name: z.string().describe("Agent 名称或群组 ID"),
190
+ }, async ({ type, name }) => {
191
+ try {
192
+ await this.daemonFetch("/api/unmute", {
193
+ method: "POST",
194
+ headers: { "Content-Type": "application/json" },
195
+ body: JSON.stringify({ type, name }),
196
+ });
197
+ return { content: [{ type: "text", text: `已取消静音 ${type === "agent" ? "Agent" : "群组"} "${name}"` }] };
198
+ }
199
+ catch (err) {
200
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
201
+ }
202
+ });
203
+ // 群组功能
204
+ this.mcp.tool("agent_group_create", "创建群组", {
205
+ name: z.string().describe("群组名称"),
206
+ }, async ({ name }) => {
207
+ try {
208
+ const res = await this.daemonFetch("/api/groups", {
209
+ method: "POST", headers: { "Content-Type": "application/json" },
210
+ body: JSON.stringify({ name }),
211
+ });
212
+ const data = (await res.json());
213
+ return { content: [{ type: "text", text: `群组「${name}」已创建(ID: ${data.id})` }] };
214
+ }
215
+ catch (err) {
216
+ return { content: [{ type: "text", text: `创建失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
217
+ }
218
+ });
219
+ this.mcp.tool("agent_group_list", "获取我加入的群组列表", {}, async () => {
220
+ try {
221
+ const res = await this.daemonFetch("/api/groups");
222
+ const groups = (await res.json());
223
+ return {
224
+ content: [{
225
+ type: "text",
226
+ text: groups.length === 0 ? "没有加入任何群组" : JSON.stringify(groups, null, 2),
227
+ }],
228
+ };
229
+ }
230
+ catch (err) {
231
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
232
+ }
233
+ });
234
+ this.mcp.tool("agent_group_members", "获取群组成员列表", {
235
+ group_id: z.string().describe("群组 ID"),
236
+ }, async ({ group_id }) => {
237
+ try {
238
+ const res = await this.daemonFetch(`/api/groups/${group_id}/members`);
239
+ const members = (await res.json());
240
+ return {
241
+ content: [{
242
+ type: "text",
243
+ text: members.length === 0 ? "群组无成员" : JSON.stringify(members, null, 2),
244
+ }],
245
+ };
246
+ }
247
+ catch (err) {
248
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
249
+ }
250
+ });
251
+ this.mcp.tool("agent_group_invite", "邀请 Agent 加入群组", {
252
+ group_id: z.string().describe("群组 ID"),
253
+ to: z.string().describe("目标 Agent 名称"),
254
+ }, async ({ group_id, to }) => {
255
+ try {
256
+ const res = await this.daemonFetch("/api/groups/invite", {
257
+ method: "POST", headers: { "Content-Type": "application/json" },
258
+ body: JSON.stringify({ groupId: group_id, to }),
259
+ });
260
+ const data = (await res.json());
261
+ if (data.error) {
262
+ return { content: [{ type: "text", text: data.error }], isError: true };
263
+ }
264
+ return { content: [{ type: "text", text: `已邀请 ${to} 加入群组(邀请ID: ${data.id})` }] };
265
+ }
266
+ catch (err) {
267
+ return { content: [{ type: "text", text: `邀请失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
268
+ }
269
+ });
270
+ this.mcp.tool("agent_group_invites", "查看收到的群组邀请", {}, async () => {
271
+ try {
272
+ const res = await this.daemonFetch("/api/groups/invites");
273
+ const invites = (await res.json());
274
+ return {
275
+ content: [{
276
+ type: "text",
277
+ text: invites.length === 0 ? "没有待处理的群组邀请" : JSON.stringify(invites, null, 2),
278
+ }],
279
+ };
280
+ }
281
+ catch (err) {
282
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
283
+ }
284
+ });
285
+ this.mcp.tool("agent_group_invite_respond", "接受或拒绝群组邀请", {
286
+ invite_id: z.string().describe("邀请 ID"),
287
+ accept: z.boolean().describe("true=接受, false=拒绝"),
288
+ }, async ({ invite_id, accept }) => {
289
+ try {
290
+ await this.daemonFetch("/api/groups/invites/respond", {
291
+ method: "POST", headers: { "Content-Type": "application/json" },
292
+ body: JSON.stringify({ inviteId: invite_id, accept }),
293
+ });
294
+ return { content: [{ type: "text", text: accept ? "已接受群组邀请" : "已拒绝群组邀请" }] };
295
+ }
296
+ catch (err) {
297
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
298
+ }
299
+ });
300
+ this.mcp.tool("agent_group_message", "发送群组消息", {
301
+ group_id: z.string().describe("群组 ID"),
302
+ message: z.string().describe("消息内容"),
303
+ }, async ({ group_id, message }) => {
304
+ try {
305
+ const res = await this.daemonFetch("/api/groups/message", {
306
+ method: "POST", headers: { "Content-Type": "application/json" },
307
+ body: JSON.stringify({ groupId: group_id, message }),
308
+ });
309
+ const data = (await res.json());
310
+ if (data.error) {
311
+ return { content: [{ type: "text", text: data.error }], isError: true };
312
+ }
313
+ return { content: [{ type: "text", text: `消息已发送到群组(消息ID: ${data.id})` }] };
314
+ }
315
+ catch (err) {
316
+ return { content: [{ type: "text", text: `发送失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
317
+ }
318
+ });
319
+ this.mcp.tool("agent_group_messages", "获取群组消息列表", {
320
+ group_id: z.string().describe("群组 ID"),
321
+ limit: z.number().optional().describe("获取消息数量(默认 50)"),
322
+ }, async ({ group_id, limit }) => {
323
+ try {
324
+ const url = `/api/groups/${group_id}/messages${limit ? `?limit=${limit}` : ""}`;
325
+ const res = await this.daemonFetch(url);
326
+ const messages = (await res.json());
327
+ return {
328
+ content: [{
329
+ type: "text",
330
+ text: messages.length === 0 ? "群组暂无消息" : JSON.stringify(messages, null, 2),
331
+ }],
332
+ };
333
+ }
334
+ catch (err) {
335
+ return { content: [{ type: "text", text: `查询失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
336
+ }
337
+ });
338
+ this.mcp.tool("agent_group_mark_read", "标记群组消息已读", {
339
+ group_id: z.string().describe("群组 ID"),
340
+ }, async ({ group_id }) => {
341
+ try {
342
+ await this.daemonFetch(`/api/groups/${group_id}/read`, { method: "POST" });
343
+ return { content: [{ type: "text", text: `群组 ${group_id} 的消息已标记为已读` }] };
344
+ }
345
+ catch (err) {
346
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
347
+ }
348
+ });
349
+ this.mcp.tool("agent_group_leave", "离开群组", {
350
+ group_id: z.string().describe("群组 ID"),
351
+ }, async ({ group_id }) => {
352
+ try {
353
+ const res = await this.daemonFetch(`/api/groups/${group_id}/leave`, { method: "POST" });
354
+ const data = (await res.json());
355
+ return { content: [{ type: "text", text: data.ok ? `已离开群组 ${group_id}` : `不在该群组中` }] };
356
+ }
357
+ catch (err) {
358
+ return { content: [{ type: "text", text: `操作失败: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
359
+ }
360
+ });
361
+ // 设置在线状态
362
+ this.mcp.tool("agent_set_status", "设置本 Agent 的在线状态。offline 时其他人无法发送消息给你。", {
363
+ status: z.enum(["online", "offline", "busy"]).describe("在线状态"),
364
+ }, async ({ status }) => {
365
+ try {
366
+ await this.daemonFetch("/api/status", {
367
+ method: "POST",
368
+ headers: { "Content-Type": "application/json" },
369
+ body: JSON.stringify({ status }),
370
+ });
371
+ const desc = { online: "在线", offline: "离线", busy: "忙碌" };
372
+ return {
373
+ content: [
374
+ { type: "text", text: `状态已设为「${desc[status]}」` },
375
+ ],
376
+ };
377
+ }
378
+ catch (err) {
379
+ return {
380
+ content: [
381
+ { type: "text", text: `设置失败: ${err instanceof Error ? err.message : String(err)}` },
382
+ ],
383
+ isError: true,
384
+ };
385
+ }
386
+ });
387
+ // 配置管理
388
+ this.mcp.tool("agent_config", "查看或修改 agent-link 配置(名称、中继服务器等)。修改后需重启 Claude Code 生效。", {
389
+ name: z.string().optional().describe("设置 Agent 名称"),
390
+ relay_url: z.string().optional().describe("设置中继服务器地址,如 ws://IP:3458。设为空字符串则清除"),
391
+ }, async ({ name, relay_url }) => {
392
+ // 无参数时显示当前配置和引导
393
+ if (name === undefined && relay_url === undefined) {
394
+ const fileConfig = loadConfig();
395
+ let health = null;
396
+ try {
397
+ const res = await this.daemonFetch("/api/health");
398
+ health = (await res.json());
399
+ }
400
+ catch { }
401
+ const activeName = health?.name || fileConfig.name || "(未知)";
402
+ const activeRelay = health?.relayUrl || "(未配置)";
403
+ const lines = [
404
+ `== 当前生效配置 ==`,
405
+ ` 名称: ${activeName}`,
406
+ ` 中继: ${activeRelay}`,
407
+ ` 本机发现: ${health?.noLocal ? "禁用" : "启用"}`,
408
+ ` mDNS: ${health?.noMdns ? "禁用" : "启用"}`,
409
+ ``,
410
+ `== 配置文件 (${CONFIG_PATH}) ==`,
411
+ ` 名称: ${fileConfig.name || "(未设置)"}`,
412
+ ` 中继: ${fileConfig.relayUrl || "(未设置)"}`,
413
+ ``,
414
+ `== 配置方式(任选其一) ==`,
415
+ `方式1: 通过本工具修改配置文件`,
416
+ ` agent_config(name: "你的名称")`,
417
+ ` agent_config(relay_url: "ws://中继服务器IP:3458")`,
418
+ ``,
419
+ `方式2: 通过 MCP 启动参数(优先级更高)`,
420
+ ` "args": ["--name", "名称", "--relay", "ws://IP:3458"]`,
421
+ ``,
422
+ `配置优先级: MCP 启动参数 > 配置文件 > 环境变量 > 默认值`,
423
+ `修改后需重启 Claude Code 生效。`,
424
+ ];
425
+ return { content: [{ type: "text", text: lines.join("\n") }] };
426
+ }
427
+ // 修改配置
428
+ const updates = {};
429
+ if (name !== undefined)
430
+ updates.name = name;
431
+ if (relay_url !== undefined)
432
+ updates.relayUrl = relay_url || undefined;
433
+ const saved = saveConfig(updates);
434
+ const changes = [];
435
+ if (name !== undefined)
436
+ changes.push(`名称 → ${name}`);
437
+ if (relay_url !== undefined)
438
+ changes.push(relay_url ? `中继 → ${relay_url}` : `中继 → 已清除`);
439
+ return {
440
+ content: [{
441
+ type: "text",
442
+ text: `配置已保存到 ${CONFIG_PATH}\n${changes.join("\n")}\n\n⚠ 需要重启 Claude Code 使配置生效。`,
443
+ }],
444
+ };
445
+ });
446
+ // 状态总览
447
+ this.mcp.tool("agent_status", "查看当前状态总览:Daemon 信息、在线 Agent、好友、群组等", {}, async () => {
448
+ try {
449
+ const [healthRes, agentsRes, friendsRes, groupsRes, muteRes] = await Promise.all([
450
+ this.daemonFetch("/api/health"),
451
+ this.daemonFetch("/api/agents"),
452
+ this.daemonFetch("/api/friends"),
453
+ this.daemonFetch("/api/groups"),
454
+ this.daemonFetch("/api/mute"),
455
+ ]);
456
+ const health = (await healthRes.json());
457
+ const agents = (await agentsRes.json());
458
+ const friends = (await friendsRes.json());
459
+ const groups = (await groupsRes.json());
460
+ const muted = (await muteRes.json());
461
+ const uptimeMin = Math.floor(health.uptime / 60000);
462
+ const uptimeStr = uptimeMin < 60
463
+ ? `${uptimeMin} 分钟`
464
+ : `${Math.floor(uptimeMin / 60)} 小时 ${uptimeMin % 60} 分钟`;
465
+ const discoveryModes = [
466
+ !health.noLocal ? "本机" : null,
467
+ !health.noMdns ? "mDNS" : null,
468
+ health.relayUrl ? `中继(${health.relayUrl})` : null,
469
+ ].filter(Boolean).join("、") || "无";
470
+ const lines = [
471
+ `== Daemon ==`,
472
+ ` 名称: ${health.name}`,
473
+ ` 版本: ${pkg.version}`,
474
+ ` PID: ${health.pid}`,
475
+ ` 运行时间: ${uptimeStr}`,
476
+ ` A2A 端口: ${health.port} | API 端口: ${health.apiPort}`,
477
+ ` 发现模式: ${discoveryModes}`,
478
+ ``,
479
+ `== 在线 Agent (${agents.length}) ==`,
480
+ ...(agents.length === 0
481
+ ? [" (无)"]
482
+ : agents.map((a) => ` ${a.name} — ${a.status}${a.skills?.length ? ` [${a.skills.join(",")}]` : ""}`)),
483
+ ``,
484
+ `== 好友 (${friends.length}) ==`,
485
+ ...(friends.length === 0
486
+ ? [" (无)"]
487
+ : friends.map((f) => ` ${f.friendName} — ${f.status}${f.category !== "default" ? ` (${f.category})` : ""}`)),
488
+ ``,
489
+ `== 群组 (${groups.length}) ==`,
490
+ ...(groups.length === 0
491
+ ? [" (无)"]
492
+ : groups.map((g) => ` ${g.name} (${g.id.slice(0, 8)}...) — 创建者: ${g.creator}`)),
493
+ ];
494
+ if (muted.length > 0) {
495
+ lines.push(``, `== 静音 (${muted.length}) ==`);
496
+ for (const m of muted) {
497
+ lines.push(` ${m.targetType}: ${m.targetName}`);
498
+ }
499
+ }
500
+ lines.push(``, `== 提示 ==`);
501
+ if (!health.relayUrl) {
502
+ lines.push(` ⚠ 未配置中继服务器,仅限局域网/本机通信。使用 agent_config 配置中继以支持跨网络通信。`);
503
+ }
504
+ lines.push(` 开启自动查收: /loop 1m 使用 agent_inbox(unread_only=true) 检查未读消息,有新消息则播报并询问是否回复,没有则不输出`);
505
+ return { content: [{ type: "text", text: lines.join("\n") }] };
506
+ }
507
+ catch (err) {
508
+ return {
509
+ content: [{ type: "text", text: `无法连接 daemon: ${err instanceof Error ? err.message : String(err)}` }],
510
+ isError: true,
511
+ };
512
+ }
513
+ });
29
514
  // 列出在线 Agent
30
- this.mcp.tool("agent_list", "列出局域网内所有在线的 Agent", {}, async () => {
515
+ this.mcp.tool("agent_list", "列出所有在线的 Agent", {}, async () => {
31
516
  try {
32
517
  const res = await this.daemonFetch("/api/agents");
33
518
  const agents = (await res.json());
@@ -317,11 +802,16 @@ export class AgentLinkMcpServer {
317
802
  reconnect(5000);
318
803
  });
319
804
  }
320
- handleSSEEvent(event) {
805
+ async handleSSEEvent(event) {
321
806
  const task = event.task;
322
807
  if (event.type === "task:created" && task.to === this.agentName) {
323
- // 收到新消息通知
324
- sendNotification(`Agent Link: 来自 ${task.from}`, task.message);
808
+ // 收到新消息,检查是否静音
809
+ const muted = await this.checkMuted("agent", task.from);
810
+ if (!muted) {
811
+ const tip = this.hasShownPollTip ? "" : "\n\n提示: 执行 /loop 1m /agent-link:check-messages 开启自动查收";
812
+ sendNotification(`Agent Link: 来自 ${task.from}`, task.message + tip);
813
+ this.hasShownPollTip = true;
814
+ }
325
815
  return;
326
816
  }
327
817
  if (event.type === "task:updated") {
@@ -329,10 +819,13 @@ export class AgentLinkMcpServer {
329
819
  const pending = this.pendingTasks.get(task.id);
330
820
  if (pending && (task.status === "completed" || task.status === "rejected" || task.status === "canceled")) {
331
821
  pending.task = task;
332
- const title = task.status === "completed"
333
- ? `${pending.to} 回复了`
334
- : `${pending.to} ${task.status === "rejected" ? "拒绝了任务" : "取消了任务"}`;
335
- sendNotification(title, task.result || "(无内容)");
822
+ const muted = await this.checkMuted("agent", pending.to);
823
+ if (!muted) {
824
+ const title = task.status === "completed"
825
+ ? `${pending.to} 回复了`
826
+ : `${pending.to} ${task.status === "rejected" ? "拒绝了任务" : "取消了任务"}`;
827
+ sendNotification(title, task.result || "(无内容)");
828
+ }
336
829
  }
337
830
  }
338
831
  }