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