@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/dist/index.js
CHANGED
|
@@ -2,21 +2,36 @@
|
|
|
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
|
const [, , subcommand] = process.argv;
|
|
12
16
|
if (subcommand === "init") {
|
|
13
17
|
import("./cli.js")
|
|
14
|
-
.then(({ runInit }) =>
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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] === "--
|
|
70
|
-
|
|
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,
|
|
89
|
+
return { port, configDir, server };
|
|
73
90
|
}
|
|
74
|
-
const { port: CLI_PORT,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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
|
|
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:
|
|
122
|
-
unregister: unregister.length > 0 ? { tools: unregister } : undefined,
|
|
194
|
+
register: { mcpTools },
|
|
123
195
|
});
|
|
124
|
-
|
|
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
|
|
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;
|
|
209
|
+
return;
|
|
139
210
|
mcpSyncTimer = setTimeout(() => {
|
|
140
211
|
mcpSyncTimer = null;
|
|
141
|
-
console.log("[desktop] MCP
|
|
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
|
-
|
|
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
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
return [{ type: "text", text: JSON.stringify({ ok:
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
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
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
-
|
|
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
|
-
|
|
333
|
-
|
|
371
|
+
if (isDesktopMode)
|
|
372
|
+
await stopBrowser();
|
|
373
|
+
console.log("[desktop] 端点已停止");
|
|
334
374
|
},
|
|
335
375
|
},
|
|
336
376
|
caps: ["message", "tool_execute"],
|
|
337
|
-
|
|
338
|
-
|
|
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: () =>
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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,
|