@greatlhd/ailo-desktop 1.0.0 → 1.0.1

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/index.js CHANGED
@@ -2,21 +2,36 @@
2
2
  /**
3
3
  * Ailo Desktop — 超级端点
4
4
  *
5
- * 集成桌面能力 + 飞书 + 钉钉 + QQ + 邮件。
6
- * 各平台按需配置:有完整配置才启动对应 handler 并上报对应 blueprint。
5
+ * 集成桌面能力 + 飞书。
6
+ * 各平台按需配置:有完整配置才启动对应 handler 并上报对应工具。
7
7
  * 未配置的平台不会被 Ailo 感知,不影响其他能力正常工作。
8
8
  *
9
- * 子命令:ailo-desktop init [--defaults] — 初始化 config.json 与 Skills 目录(见 cli.ts)。
9
+ * 运行模式:
10
+ * - 桌面模式(有显示器):截图、浏览器、鼠标键盘 + 文件操作、代码执行、MCP
11
+ * - 服务器模式(无显示器):文件操作、代码执行、MCP
12
+ *
13
+ * 子命令:ailo-desktop init [--defaults] [--config-dir <path>] — 初始化 config.json 与 Skills 目录(见 cli.ts)。
10
14
  */
11
15
  const [, , subcommand] = process.argv;
12
16
  if (subcommand === "init") {
13
17
  import("./cli.js")
14
- .then(({ runInit }) => runInit(process.argv.includes("--defaults"))
15
- .then(() => process.exit(0))
16
- .catch((e) => {
17
- console.error(e);
18
- process.exit(1);
19
- }))
18
+ .then(({ runInit }) => {
19
+ const args = process.argv.slice(2);
20
+ let useDefaults = false;
21
+ let configDir;
22
+ for (let i = 0; i < args.length; i++) {
23
+ if (args[i] === "--defaults")
24
+ useDefaults = true;
25
+ else if ((args[i] === "--config-dir" || args[i] === "-c") && args[i + 1])
26
+ configDir = args[++i];
27
+ }
28
+ return runInit(useDefaults, configDir)
29
+ .then(() => process.exit(0))
30
+ .catch((e) => {
31
+ console.error(e);
32
+ process.exit(1);
33
+ });
34
+ })
20
35
  .catch((e) => {
21
36
  console.error("init 加载失败:", e);
22
37
  process.exit(1);
@@ -55,26 +70,34 @@ import { FeishuHandler } from "./feishu-handler.js";
55
70
  import { loadConnectionConfig, hasValidConfig, backoffDelayMs, readConfig, } from "./connection_util.js";
56
71
  import { CONFIG_FILENAME } from "./constants.js";
57
72
  import { errMsg } from "./utils.js";
58
- const BLUEPRINTS_DIR = join(__dirname, "..", "..", "..", "blueprints");
59
73
  function parseArgs() {
60
74
  const args = process.argv.slice(2);
61
75
  let port;
62
- let blueprintUrl;
76
+ let configDir;
77
+ let server;
63
78
  for (let i = 0; i < args.length; i++) {
64
79
  if (args[i] === "--port" && args[i + 1]) {
65
80
  const p = Number(args[++i]);
66
81
  if (!Number.isNaN(p) && p > 0)
67
82
  port = p;
68
83
  }
69
- else if (args[i] === "--blueprint-url" && args[i + 1])
70
- blueprintUrl = args[++i];
84
+ else if ((args[i] === "--config-dir" || args[i] === "-c") && args[i + 1])
85
+ configDir = args[++i];
86
+ else if (args[i] === "--server" || args[i] === "-s")
87
+ server = true;
71
88
  }
72
- return { port, blueprintUrl };
89
+ return { port, configDir, server };
73
90
  }
74
- const { port: CLI_PORT, blueprintUrl: CLI_BLUEPRINT_URL } = parseArgs();
75
- const BLUEPRINT_URL = CLI_BLUEPRINT_URL ?? join(BLUEPRINTS_DIR, "desktop-agent.blueprint.md");
76
- const BLUEPRINT_WEBCHAT = join(BLUEPRINTS_DIR, "webchat.blueprint.md");
77
- const BLUEPRINT_FEISHU = join(BLUEPRINTS_DIR, "feishu.blueprint.md");
91
+ const { port: CLI_PORT, configDir: CLI_CONFIG_DIR, server: CLI_SERVER } = parseArgs();
92
+ function hasDisplay() {
93
+ if (CLI_SERVER)
94
+ return false;
95
+ if (process.platform === "darwin" || process.platform === "win32") {
96
+ return true; // macOS/Windows 默认有显示器
97
+ }
98
+ return !!process.env.DISPLAY || !!process.env.WAYLAND_DISPLAY;
99
+ }
100
+ const isDesktopMode = hasDisplay();
78
101
  function loadSectionConfig(configPath, section, requiredKeys, mapper) {
79
102
  const raw = readConfig(configPath);
80
103
  const s = raw[section];
@@ -93,39 +116,87 @@ const mcpManager = new LocalMCPManager();
93
116
  let endpointCtx = null;
94
117
  let webchatApi = null;
95
118
  let feishuHandler = null;
96
- let lastMcpToolSnapshot = new Map();
97
119
  const desktopStateStore = new DesktopStateStore();
98
- function computeToolDiff(oldTools, newTools) {
99
- const newMap = new Map(newTools.map((t) => [t.name, t]));
100
- const register = [];
101
- const unregister = [];
102
- for (const [name] of oldTools) {
103
- if (!newMap.has(name))
104
- unregister.push(name);
120
+ // 已上报的工具和技能(供 config_server API 使用)
121
+ const reportedEndpointTools = [];
122
+ const reportedEndpointSkills = [];
123
+ // ──────────────────────────────────────────────────────────────
124
+ // 端点工具定义(上报给 Ailo)
125
+ // ──────────────────────────────────────────────────────────────
126
+ function buildEndpointTools() {
127
+ const tools = [];
128
+ // 文件操作工具
129
+ const fsToolDefs = [
130
+ ["read_file", "读取文件内容。传入文件路径,返回文件内容。"],
131
+ ["write_file", "创建或覆写文件。将内容写入指定路径的文件。"],
132
+ ["edit_file", "编辑文件。在指定位置插入内容。"],
133
+ ["append_file", "追加内容。将内容追加到文件末尾。"],
134
+ ["list_directory", "列出目录内容。返回指定目录下的文件和子目录列表。"],
135
+ ["find_files", "查找文件。根据模式匹配文件名。"],
136
+ ["search_content", "搜索内容。在文件中搜索匹配的文本行。"],
137
+ ["delete_file", "删除文件。删除指定路径的文件。"],
138
+ ["move_file", "移动文件。将文件从源路径移动到目标路径。"],
139
+ ["copy_file", "复制文件。将文件从源路径复制到目标路径。"],
140
+ ];
141
+ for (const [name, desc] of fsToolDefs) {
142
+ tools.push({ name, description: desc });
105
143
  }
106
- for (const [name, tool] of newMap) {
107
- if (!oldTools.has(name))
108
- register.push(tool);
144
+ // 代码执行工具
145
+ tools.push({ name: "execute_code", description: "执行代码。支持多种编程语言的代码执行。" }, { name: "exec", description: "执行 shell 命令。在服务器上执行 shell 命令并返回输出。" });
146
+ // MCP 管理工具
147
+ tools.push({
148
+ name: "mcp_manage",
149
+ description: "管理 MCP 服务器。列出、启动、停止 MCP 服务器,以及查看服务器提供的工具。",
150
+ });
151
+ // 飞书发送工具(飞书配置后可用)
152
+ tools.push({
153
+ name: "feishu_send",
154
+ description: "通过飞书发送消息。支持发送给指定 chat_id,可附带文本和文件附件。",
155
+ });
156
+ // 网页聊天发送工具
157
+ tools.push({
158
+ name: "webchat_send",
159
+ description: "通过网页聊天界面发送消息给用户。需要在浏览器中打开 Ailo Desktop 配置页。",
160
+ });
161
+ // 桌面模式工具
162
+ if (isDesktopMode) {
163
+ tools.push({ name: "screenshot", description: "截取屏幕截图。可指定窗口或全屏。" }, { name: "browser_use", description: "浏览器自动化。控制浏览器进行网页操作。" }, { name: "mouse_keyboard", description: "鼠标键盘控制。模拟鼠标点击、键盘输入等操作。" });
109
164
  }
110
- return { register, unregister };
165
+ return tools;
111
166
  }
167
+ // ──────────────────────────────────────────────────────────────
168
+ // 平台 handler 生命周期
169
+ // ──────────────────────────────────────────────────────────────
170
+ async function startPlatformHandlers(ctx, configPath) {
171
+ // 飞书
172
+ const feishuCfg = loadFeishuConfig(configPath);
173
+ if (feishuCfg) {
174
+ feishuHandler = new FeishuHandler(feishuCfg);
175
+ await feishuHandler.start(ctx);
176
+ console.log("[desktop] 飞书通道已启动");
177
+ }
178
+ }
179
+ async function stopPlatformHandlers() {
180
+ if (feishuHandler) {
181
+ await feishuHandler.stop();
182
+ feishuHandler = null;
183
+ }
184
+ }
185
+ // ──────────────────────────────────────────────────────────────
186
+ // 同步 MCP 工具到服务端
187
+ // ──────────────────────────────────────────────────────────────
112
188
  async function syncMcpToolsToServer() {
113
189
  if (!endpointCtx)
114
190
  return;
115
- const currentTools = mcpManager.getAllPrivateTools();
116
- const { register, unregister } = computeToolDiff(lastMcpToolSnapshot, currentTools);
117
- if (register.length === 0 && unregister.length === 0)
118
- return;
191
+ const mcpTools = mcpManager.getAllPrivateTools();
119
192
  try {
120
193
  await endpointCtx.update({
121
- register: register.length > 0 ? { tools: register } : undefined,
122
- unregister: unregister.length > 0 ? { tools: unregister } : undefined,
194
+ register: { mcpTools },
123
195
  });
124
- lastMcpToolSnapshot = new Map(currentTools.map((t) => [t.name, t]));
125
- console.log(`[desktop] MCP 工具增量同步: +${register.length} -${unregister.length}`);
196
+ console.log(`[desktop] MCP 工具同步: ${mcpTools.length} 个工具`);
126
197
  }
127
198
  catch (e) {
128
- console.error("[desktop] MCP 工具增量同步失败:", errMsg(e));
199
+ console.error("[desktop] MCP 工具同步失败:", errMsg(e));
129
200
  }
130
201
  }
131
202
  async function initSubsystems() {
@@ -135,10 +206,10 @@ async function initSubsystems() {
135
206
  let mcpSyncTimer = null;
136
207
  mcpManager.setOnToolsChanged(() => {
137
208
  if (mcpSyncTimer)
138
- return; // 防抖:500ms 内只同步一次
209
+ return;
139
210
  mcpSyncTimer = setTimeout(() => {
140
211
  mcpSyncTimer = null;
141
- console.log("[desktop] MCP 工具变更,增量同步到 Ailo");
212
+ console.log("[desktop] MCP 工具变更,同步到 Ailo");
142
213
  syncMcpToolsToServer();
143
214
  }, 500);
144
215
  });
@@ -148,33 +219,6 @@ async function initSubsystems() {
148
219
  }
149
220
  }
150
221
  // ──────────────────────────────────────────────────────────────
151
- // 平台 handler 生命周期
152
- // ──────────────────────────────────────────────────────────────
153
- async function startPlatformHandlers(ctx, configPath) {
154
- // 飞书
155
- const feishuCfg = loadFeishuConfig(configPath);
156
- if (feishuCfg) {
157
- feishuHandler = new FeishuHandler(feishuCfg);
158
- await feishuHandler.start(ctx);
159
- console.log("[desktop] 飞书通道已启动");
160
- }
161
- }
162
- async function stopPlatformHandlers() {
163
- if (feishuHandler) {
164
- await feishuHandler.stop();
165
- feishuHandler = null;
166
- }
167
- }
168
- // ──────────────────────────────────────────────────────────────
169
- // 动态计算 blueprints(只注册已配置的平台)
170
- // ──────────────────────────────────────────────────────────────
171
- function buildBlueprints(configPath) {
172
- const list = [BLUEPRINT_URL, BLUEPRINT_WEBCHAT];
173
- if (loadFeishuConfig(configPath))
174
- list.push(BLUEPRINT_FEISHU);
175
- return list;
176
- }
177
- // ──────────────────────────────────────────────────────────────
178
222
  // 工具 handlers
179
223
  // ──────────────────────────────────────────────────────────────
180
224
  const FS_TOOLS = [
@@ -188,8 +232,10 @@ function requireHandler(handler, platform) {
188
232
  }
189
233
  const requireFeishu = () => requireHandler(feishuHandler, "飞书");
190
234
  function buildToolHandlers() {
191
- const handlers = {
192
- screenshot: async (args) => {
235
+ const handlers = {};
236
+ // 桌面模式工具(需要显示器)
237
+ if (isDesktopMode) {
238
+ handlers.screenshot = async (args) => {
193
239
  const result = await captureDesktopObservation({
194
240
  capture_window: !!args.capture_window,
195
241
  screen: args.screen,
@@ -197,68 +243,68 @@ function buildToolHandlers() {
197
243
  if (result.observation)
198
244
  desktopStateStore.saveObservation(result.observation);
199
245
  return result.parts;
200
- },
201
- browser_use: async (args) => browserUse(args),
202
- execute_code: async (args) => {
203
- if (!endpointCtx)
204
- throw new Error("端点未就绪");
205
- return executeCode(endpointCtx, args);
206
- },
207
- exec: async (args) => {
246
+ };
247
+ handlers.browser_use = async (args) => browserUse(args);
248
+ handlers.mouse_keyboard = async (args) => mouseKeyboard(args, { stateStore: desktopStateStore });
249
+ }
250
+ // 通用工具(桌面和服务器都可用)
251
+ handlers.execute_code = async (args) => {
252
+ if (!endpointCtx)
253
+ throw new Error("端点未就绪");
254
+ return executeCode(endpointCtx, args);
255
+ };
256
+ handlers.exec = async (args) => {
257
+ if (!endpointCtx)
258
+ throw new Error("端点未就绪");
259
+ return execTool(endpointCtx, args);
260
+ };
261
+ handlers.mcp_manage = async (args) => {
262
+ const result = await mcpManager.handle(args);
263
+ if (result.toolsChanged)
264
+ syncMcpToolsToServer();
265
+ return result.text;
266
+ };
267
+ // 网页聊天发送
268
+ handlers.webchat_send = async (args) => {
269
+ const text = args.text;
270
+ const participantName = args.participantName;
271
+ const attachments = args.attachments ?? [];
272
+ if (!webchatApi && attachments.length === 0) {
273
+ return [{ type: "text", text: JSON.stringify({ ok: false, error: "webchat未就绪,请先连接Ailo并打开配置页" }) }];
274
+ }
275
+ const results = [];
276
+ if (text && webchatApi) {
277
+ const ok = webchatApi.recordAiloReply(text, participantName ?? "");
278
+ results.push(ok ? "文字已发送" : "文字发送失败,请确认participantName与网页聊天中的称呼一致且用户在线");
279
+ }
280
+ if (attachments.length > 0) {
208
281
  if (!endpointCtx)
209
- throw new Error("端点未就绪");
210
- return execTool(endpointCtx, args);
211
- },
212
- mouse_keyboard: async (args) => mouseKeyboard(args, { stateStore: desktopStateStore }),
213
- mcp_manage: async (args) => {
214
- const result = await mcpManager.handle(args);
215
- if (result.toolsChanged)
216
- syncMcpToolsToServer();
217
- return result.text;
218
- },
219
- // 网页聊天发送
220
- webchat_send: async (args) => {
221
- const text = args.text;
222
- const participantName = args.participantName;
223
- const attachments = args.attachments ?? [];
224
- if (!webchatApi && attachments.length === 0) {
225
- return [{ type: "text", text: JSON.stringify({ ok: false, error: "webchat未就绪,请先连接Ailo并打开配置页" }) }];
226
- }
227
- const results = [];
228
- // 发送文字
229
- if (text && webchatApi) {
230
- const ok = webchatApi.recordAiloReply(text, participantName ?? "");
231
- results.push(ok ? "文字已发送" : "文字发送失败,请确认participantName与网页聊天中的称呼一致且用户在线");
232
- }
233
- // 发送附件
234
- if (attachments.length > 0) {
235
- if (!endpointCtx)
236
- return [{ type: "text", text: JSON.stringify({ ok: false, error: "端点未就绪,无法发送附件" }) }];
237
- for (const att of attachments) {
238
- if (att.path) {
239
- await endpointCtx.sendFile(att.path);
240
- results.push(`文件已发送:${att.path}`);
241
- }
282
+ return [{ type: "text", text: JSON.stringify({ ok: false, error: "端点未就绪,无法发送附件" }) }];
283
+ for (const att of attachments) {
284
+ if (att.path) {
285
+ await endpointCtx.sendFile(att.path);
286
+ results.push(`文件已发送:${att.path}`);
242
287
  }
243
288
  }
244
- if (results.length === 0)
245
- return [{ type: "text", text: JSON.stringify({ ok: false, error: "请提供text或attachments" }) }];
246
- return [{ type: "text", text: JSON.stringify({ ok: true, results }) }];
247
- },
248
- // 飞书发送
249
- feishu_send: async (args) => {
250
- const h = requireFeishu();
251
- const atts = (args.attachments ?? []).map((a) => ({
252
- type: a.type,
253
- file_path: a.path,
254
- mime: a.mime,
255
- name: a.name,
256
- duration: a.duration,
257
- }));
258
- await h.sendText(args.chat_id, args.text ?? "", atts);
259
- return [{ type: "text", text: `已发送到 ${args.chat_id}` }];
260
- },
289
+ }
290
+ if (results.length === 0)
291
+ return [{ type: "text", text: JSON.stringify({ ok: false, error: "请提供text或attachments" }) }];
292
+ return [{ type: "text", text: JSON.stringify({ ok: true, results }) }];
293
+ };
294
+ // 飞书发送
295
+ handlers.feishu_send = async (args) => {
296
+ const h = requireFeishu();
297
+ const atts = (args.attachments ?? []).map((a) => ({
298
+ type: a.type,
299
+ file_path: a.path,
300
+ mime: a.mime,
301
+ name: a.name,
302
+ duration: a.duration,
303
+ }));
304
+ await h.sendText(args.chat_id, args.text ?? "", atts);
305
+ return [{ type: "text", text: `已发送到 ${args.chat_id}` }];
261
306
  };
307
+ // 文件系统工具
262
308
  for (const name of FS_TOOLS) {
263
309
  handlers[name] = async (args) => fsTool(name, args);
264
310
  }
@@ -269,31 +315,18 @@ function buildToolHandlers() {
269
315
  // ──────────────────────────────────────────────────────────────
270
316
  async function main() {
271
317
  const port = CLI_PORT ?? (await promptPort());
272
- const configPath = join(process.cwd(), CONFIG_FILENAME);
318
+ const configDir = CLI_CONFIG_DIR ?? process.cwd();
319
+ const configPath = join(configDir, CONFIG_FILENAME);
320
+ console.log(`[desktop] 配置目录: ${configDir}`);
321
+ console.log(`[desktop] 运行模式: ${isDesktopMode ? "桌面模式" : "服务器模式"}`);
273
322
  const connectionState = { connected: false, endpointId: "" };
274
323
  let webchatCtxRef = null;
275
324
  let connectAttempt = 0;
276
325
  let endpointConnecting = false;
277
- async function reloadPlatformHandler(opts) {
278
- if (!endpointCtx)
279
- return;
280
- const wasRunning = !!opts.getHandler();
281
- if (opts.getHandler()) {
282
- await opts.getHandler().stop();
283
- opts.setHandler(null);
284
- }
285
- const cfg = opts.loadConfig();
286
- if (cfg) {
287
- const h = opts.createHandler(cfg);
288
- opts.setHandler(h);
289
- await h.start(endpointCtx);
290
- if (!wasRunning)
291
- await endpointCtx.update({ register: { blueprints: [opts.blueprint] } });
292
- }
293
- else if (wasRunning) {
294
- await endpointCtx.update({ unregister: { blueprints: [opts.blueprint] } });
295
- }
296
- }
326
+ // 端点描述
327
+ const endpointInstructions = isDesktopMode
328
+ ? "Ailo Desktop 端点 - 具备文件操作、代码执行、截图、浏览器自动化、鼠标键盘控制等能力"
329
+ : "Ailo Server 端点 - 具备文件操作、代码执行等服务器能力";
297
330
  async function applyConnectionConfig(overrides) {
298
331
  const cfg = overrides ?? loadConnectionConfig(configPath);
299
332
  if (!hasValidConfig(cfg))
@@ -302,8 +335,11 @@ async function main() {
302
335
  return;
303
336
  endpointConnecting = true;
304
337
  connectAttempt = 0;
338
+ // 填充已上报的工具列表
339
+ const builtInTools = buildEndpointTools();
340
+ reportedEndpointTools.length = 0;
341
+ reportedEndpointTools.push(...builtInTools);
305
342
  try {
306
- const blueprints = buildBlueprints(configPath);
307
343
  runEndpoint({
308
344
  ailoWsUrl: cfg.url,
309
345
  ailoApiKey: cfg.apiKey,
@@ -316,10 +352,11 @@ async function main() {
316
352
  connectionState.connected = true;
317
353
  connectionState.endpointId = cfg.endpointId;
318
354
  webchatCtxRef = ctx;
319
- lastMcpToolSnapshot = new Map(mcpManager.getAllPrivateTools().map((t) => [t.name, t]));
320
355
  serverRef.notifyContextAttached();
321
356
  await startPlatformHandlers(ctx, configPath);
322
- console.log("[desktop] 桌面端点已启动");
357
+ // 同步 MCP 工具
358
+ await syncMcpToolsToServer();
359
+ console.log("[desktop] 端点已启动");
323
360
  },
324
361
  stop: async () => {
325
362
  await stopPlatformHandlers();
@@ -328,14 +365,18 @@ async function main() {
328
365
  connectionState.connected = false;
329
366
  connectionState.endpointId = "";
330
367
  webchatCtxRef = null;
368
+ reportedEndpointTools.length = 0;
369
+ reportedEndpointSkills.length = 0;
331
370
  await mcpManager.shutdown();
332
- await stopBrowser();
333
- console.log("[desktop] 桌面端点已停止");
371
+ if (isDesktopMode)
372
+ await stopBrowser();
373
+ console.log("[desktop] 端点已停止");
334
374
  },
335
375
  },
336
376
  caps: ["message", "tool_execute"],
337
- blueprints,
338
- tools: mcpManager.getAllPrivateTools(),
377
+ tools: builtInTools,
378
+ mcpTools: mcpManager.getAllPrivateTools(),
379
+ instructions: endpointInstructions,
339
380
  toolHandlers: buildToolHandlers(),
340
381
  onUnknownTool: async (name, args) => {
341
382
  const idx = name.indexOf(":");
@@ -358,11 +399,7 @@ async function main() {
358
399
  return;
359
400
  }
360
401
  try {
361
- await client.reconnect(undefined, {
362
- url: latest.url,
363
- apiKey: latest.apiKey,
364
- endpointId: latest.endpointId,
365
- });
402
+ await client.reconnect(undefined, undefined, undefined, mcpManager.getAllPrivateTools());
366
403
  }
367
404
  catch (e) {
368
405
  console.error("[desktop] 重试连接失败:", errMsg(e));
@@ -380,16 +417,16 @@ async function main() {
380
417
  mcpManager,
381
418
  getConnectionStatus: () => connectionState,
382
419
  getWebchatCtx: () => webchatCtxRef,
420
+ getEndpointCtx: () => endpointCtx,
421
+ getEndpointTools: () => reportedEndpointTools.map(t => ({ name: t.name, description: t.description ?? "" })),
422
+ getEndpointSkills: () => reportedEndpointSkills.map(s => ({ name: s.name, description: s.description ?? "" })),
383
423
  port,
384
424
  configPath,
385
- blueprintUrl: BLUEPRINT_URL,
386
- blueprintLocalPath: join(BLUEPRINTS_DIR, "desktop-agent.blueprint.md"),
387
- getBlueprintPaths: () => buildBlueprints(configPath),
388
425
  onWebchatReady: (api) => { webchatApi = api; },
389
426
  onRequestReconnect: async () => {
390
427
  if (!endpointCtx)
391
428
  return;
392
- await endpointCtx.client.reconnect([]);
429
+ await endpointCtx.client.reconnect(undefined, undefined, undefined, mcpManager.getAllPrivateTools());
393
430
  },
394
431
  onConnectionConfigSaved: async (config) => {
395
432
  const cfg = {
@@ -398,19 +435,16 @@ async function main() {
398
435
  endpointId: config.endpointId,
399
436
  };
400
437
  if (endpointCtx) {
401
- await endpointCtx.client.reconnect(undefined, cfg);
438
+ await endpointCtx.client.reconnect(undefined, cfg, undefined, mcpManager.getAllPrivateTools());
402
439
  }
403
440
  else {
404
441
  await applyConnectionConfig(cfg);
405
442
  }
406
443
  },
407
- onFeishuConfigSaved: () => reloadPlatformHandler({
408
- getHandler: () => feishuHandler,
409
- setHandler: (h) => { feishuHandler = h; },
410
- loadConfig: () => loadFeishuConfig(configPath),
411
- createHandler: (cfg) => new FeishuHandler(cfg),
412
- blueprint: BLUEPRINT_FEISHU,
413
- }),
444
+ onFeishuConfigSaved: async () => {
445
+ // 飞书配置变更时,重新同步 MCP 工具(可能包含新的飞书工具)
446
+ await syncMcpToolsToServer();
447
+ },
414
448
  getFeishuStatus: () => ({
415
449
  configured: !!loadFeishuConfig(configPath),
416
450
  running: !!feishuHandler,