@timmy_hu/plugin-middleware 1.0.0 → 1.0.2
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/.xpertai-plugin/plugin.json +2 -2
- package/examples/request.example.json +91 -72
- package/index.js +325 -325
- package/package.json +1 -1
- package/src/middlewares/plugin-middleware.middleware.ts +278 -341
- package/src/schemas/config.schema.ts +13 -16
package/index.js
CHANGED
|
@@ -15,459 +15,458 @@ class PluginMiddleware {
|
|
|
15
15
|
zh_Hans: "插件中间件管理器"
|
|
16
16
|
},
|
|
17
17
|
description: {
|
|
18
|
-
en_US: "Discovers all middleware-type plugins
|
|
19
|
-
zh_Hans: "
|
|
18
|
+
en_US: "Discovers all middleware-type plugins installed on the XpertAI platform, shows their load status, and can install new middleware plugins from npm. Authenticates via /api/auth/login.",
|
|
19
|
+
zh_Hans: "自动发现 XpertAI 平台上所有已安装的中间件类型插件,展示加载状态,并支持从 npm 安装新的中间件插件。通过 /api/auth/login 登录获取 token。"
|
|
20
20
|
},
|
|
21
21
|
configSchema: {
|
|
22
22
|
type: "object",
|
|
23
23
|
properties: {
|
|
24
24
|
platformUrl: {
|
|
25
25
|
type: "string",
|
|
26
|
-
title: {
|
|
27
|
-
|
|
28
|
-
zh_Hans: "平台地址"
|
|
29
|
-
},
|
|
30
|
-
description: "XpertAI platform base URL for plugin discovery API",
|
|
26
|
+
title: { en_US: "Platform URL", zh_Hans: "平台地址" },
|
|
27
|
+
description: "XpertAI platform base URL",
|
|
31
28
|
default: "http://10.161.48.53:3300"
|
|
32
29
|
},
|
|
30
|
+
email: {
|
|
31
|
+
type: "string",
|
|
32
|
+
title: { en_US: "Email", zh_Hans: "登录邮箱" },
|
|
33
|
+
description: "Email address for platform login via /api/auth/login"
|
|
34
|
+
},
|
|
35
|
+
password: {
|
|
36
|
+
type: "string",
|
|
37
|
+
title: { en_US: "Password", zh_Hans: "登录密码" },
|
|
38
|
+
description: "Password for platform login via /api/auth/login"
|
|
39
|
+
},
|
|
33
40
|
organizationId: {
|
|
34
41
|
type: "string",
|
|
35
|
-
title: {
|
|
36
|
-
en_US: "Organization ID",
|
|
37
|
-
zh_Hans: "组织 ID"
|
|
38
|
-
},
|
|
42
|
+
title: { en_US: "Organization ID", zh_Hans: "组织 ID" },
|
|
39
43
|
description: "Organization ID for scoped plugin queries"
|
|
40
44
|
},
|
|
41
|
-
autoDiscover: {
|
|
42
|
-
type: "boolean",
|
|
43
|
-
title: {
|
|
44
|
-
en_US: "Auto Discover",
|
|
45
|
-
zh_Hans: "自动发现"
|
|
46
|
-
},
|
|
47
|
-
description: "Whether to automatically discover available middleware plugins on startup",
|
|
48
|
-
default: true
|
|
49
|
-
},
|
|
50
45
|
cacheTtlSeconds: {
|
|
51
46
|
type: "number",
|
|
52
|
-
title: {
|
|
53
|
-
en_US: "Cache TTL (seconds)",
|
|
54
|
-
zh_Hans: "缓存有效期(秒)"
|
|
55
|
-
},
|
|
47
|
+
title: { en_US: "Cache TTL (seconds)", zh_Hans: "缓存有效期(秒)" },
|
|
56
48
|
description: "How long to cache the discovered plugin list before refreshing",
|
|
57
49
|
default: 300
|
|
58
50
|
}
|
|
59
51
|
},
|
|
60
|
-
required: ["platformUrl"]
|
|
52
|
+
required: ["platformUrl", "email", "password"]
|
|
61
53
|
}
|
|
62
54
|
};
|
|
63
55
|
}
|
|
64
56
|
|
|
65
57
|
createMiddleware(options = {}, context = {}) {
|
|
66
|
-
const platformUrl = options.platformUrl || "http://10.161.48.53:3300";
|
|
58
|
+
const platformUrl = (options.platformUrl || "http://10.161.48.53:3300").replace(/\/+$/, "");
|
|
59
|
+
const email = options.email || "";
|
|
60
|
+
const password = options.password || "";
|
|
67
61
|
const organizationId = options.organizationId || "";
|
|
68
|
-
const autoDiscover = options.autoDiscover !== false;
|
|
69
62
|
const cacheTtlSeconds = options.cacheTtlSeconds || 300;
|
|
70
63
|
|
|
71
|
-
// In-memory
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
64
|
+
// In-memory state
|
|
65
|
+
const state = {
|
|
66
|
+
allPlugins: [], // 所有插件列表
|
|
67
|
+
middlewarePlugins: [], // 中间件类型插件列表
|
|
75
68
|
lastRefresh: null,
|
|
76
|
-
cacheTtl: cacheTtlSeconds * 1000
|
|
69
|
+
cacheTtl: cacheTtlSeconds * 1000,
|
|
70
|
+
token: null,
|
|
71
|
+
tokenExpiry: null
|
|
77
72
|
};
|
|
78
73
|
|
|
79
|
-
//
|
|
80
|
-
async function
|
|
74
|
+
// ======== 登录:调用 /api/auth/login 获取 token ========
|
|
75
|
+
async function loginAndGetToken() {
|
|
81
76
|
const now = Date.now();
|
|
82
|
-
if (
|
|
83
|
-
return
|
|
77
|
+
if (state.token && state.tokenExpiry && now < state.tokenExpiry) {
|
|
78
|
+
return state.token;
|
|
79
|
+
}
|
|
80
|
+
if (!email || !password) {
|
|
81
|
+
throw new Error("缺少登录凭据:请在插件配置中填写 email 和 password");
|
|
82
|
+
}
|
|
83
|
+
const fetch = (await import("node-fetch")).default;
|
|
84
|
+
const resp = await fetch(`${platformUrl}/api/auth/login`, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: { "Content-Type": "application/json" },
|
|
87
|
+
body: JSON.stringify({ email, password }),
|
|
88
|
+
timeout: 15000
|
|
89
|
+
});
|
|
90
|
+
if (!resp.ok) {
|
|
91
|
+
const errText = await resp.text();
|
|
92
|
+
throw new Error(`登录失败 (${resp.status}): ${errText}`);
|
|
93
|
+
}
|
|
94
|
+
const data = await resp.json();
|
|
95
|
+
const token = data.access_token || data.token || (data.data && data.data.access_token);
|
|
96
|
+
if (!token) {
|
|
97
|
+
throw new Error(`登录响应中未找到 token 字段`);
|
|
84
98
|
}
|
|
99
|
+
state.token = token;
|
|
100
|
+
state.tokenExpiry = now + 50 * 60 * 1000;
|
|
101
|
+
return token;
|
|
102
|
+
}
|
|
85
103
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
headers: {
|
|
92
|
-
"Content-Type": "application/json",
|
|
93
|
-
...(organizationId ? { "organization-id": organizationId } : {})
|
|
94
|
-
},
|
|
95
|
-
timeout: 10000
|
|
96
|
-
});
|
|
104
|
+
function authHeaders(token) {
|
|
105
|
+
const h = { "Content-Type": "application/json", "Authorization": `Bearer ${token}` };
|
|
106
|
+
if (organizationId) h["organization-id"] = organizationId;
|
|
107
|
+
return h;
|
|
108
|
+
}
|
|
97
109
|
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
// ======== 获取所有已安装插件(GET /api/plugin) ========
|
|
111
|
+
async function fetchAllPlugins() {
|
|
112
|
+
const now = Date.now();
|
|
113
|
+
if (state.lastRefresh && (now - state.lastRefresh) < state.cacheTtl && state.allPlugins.length > 0) {
|
|
114
|
+
return state.allPlugins;
|
|
115
|
+
}
|
|
116
|
+
const token = await loginAndGetToken();
|
|
117
|
+
const fetch = (await import("node-fetch")).default;
|
|
118
|
+
const resp = await fetch(`${platformUrl}/api/plugin`, {
|
|
119
|
+
method: "GET",
|
|
120
|
+
headers: authHeaders(token),
|
|
121
|
+
timeout: 15000
|
|
122
|
+
});
|
|
123
|
+
if (!resp.ok) {
|
|
124
|
+
if (resp.status === 401) {
|
|
125
|
+
state.token = null; state.tokenExpiry = null;
|
|
126
|
+
const newToken = await loginAndGetToken();
|
|
127
|
+
const retry = await fetch(`${platformUrl}/api/plugin`, { method: "GET", headers: authHeaders(newToken), timeout: 15000 });
|
|
128
|
+
if (!retry.ok) throw new Error(`Platform API returned ${retry.status}`);
|
|
129
|
+
const retryData = await retry.json();
|
|
130
|
+
return processPluginList(retryData);
|
|
100
131
|
}
|
|
132
|
+
throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
|
|
133
|
+
}
|
|
134
|
+
const data = await resp.json();
|
|
135
|
+
return processPluginList(data);
|
|
136
|
+
}
|
|
101
137
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
version: p.version || "unknown",
|
|
113
|
-
description: p.description || "",
|
|
114
|
-
status: p.status || "available",
|
|
115
|
-
category: p.category || "middleware",
|
|
116
|
-
loaded: registry.loadedPlugins.has(code)
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
138
|
+
function processPluginList(data) {
|
|
139
|
+
const list = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
|
|
140
|
+
state.allPlugins = list;
|
|
141
|
+
state.middlewarePlugins = list.filter(p => {
|
|
142
|
+
const meta = p.meta || {};
|
|
143
|
+
return meta.category === "middleware";
|
|
144
|
+
});
|
|
145
|
+
state.lastRefresh = Date.now();
|
|
146
|
+
return state.allPlugins;
|
|
147
|
+
}
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
149
|
+
// ======== 格式化中间件插件信息 ========
|
|
150
|
+
function formatMiddlewarePlugin(p) {
|
|
151
|
+
const meta = p.meta || {};
|
|
152
|
+
return {
|
|
153
|
+
name: p.name || meta.name,
|
|
154
|
+
packageName: p.packageName || "",
|
|
155
|
+
displayName: meta.displayName || p.name || meta.name,
|
|
156
|
+
version: meta.version || p.currentVersion || "unknown",
|
|
157
|
+
description: meta.description || "",
|
|
158
|
+
category: meta.category || "unknown",
|
|
159
|
+
author: meta.author || "",
|
|
160
|
+
keywords: meta.keywords || [],
|
|
161
|
+
source: p.source || "",
|
|
162
|
+
loadStatus: p.loadStatus || "unknown",
|
|
163
|
+
loadError: p.loadError || null,
|
|
164
|
+
configurationStatus: p.configurationStatus || null,
|
|
165
|
+
configurationError: p.configurationError || null,
|
|
166
|
+
canConfigure: p.canConfigure || false,
|
|
167
|
+
canUninstall: p.canUninstall || false,
|
|
168
|
+
canUpdate: p.canUpdate || false,
|
|
169
|
+
hasUpdate: p.hasUpdate || false,
|
|
170
|
+
effectiveInCurrentScope: p.effectiveInCurrentScope !== false,
|
|
171
|
+
componentSummary: p.componentSummary || { total: 0, skills: 0, mcpServers: 0, apps: 0, hooks: 0 }
|
|
172
|
+
};
|
|
130
173
|
}
|
|
131
174
|
|
|
132
|
-
// Tool 1:
|
|
175
|
+
// ======== Tool 1: list_middleware_plugins ========
|
|
133
176
|
const listMiddlewarePlugins = tool(
|
|
134
177
|
async (input) => {
|
|
135
178
|
try {
|
|
136
|
-
|
|
179
|
+
await fetchAllPlugins();
|
|
137
180
|
const filter = input.filter || "all";
|
|
181
|
+
let filtered = state.middlewarePlugins;
|
|
138
182
|
|
|
139
|
-
let filtered = plugins;
|
|
140
183
|
if (filter === "loaded") {
|
|
141
|
-
filtered =
|
|
142
|
-
} else if (filter === "
|
|
143
|
-
filtered =
|
|
184
|
+
filtered = filtered.filter(p => p.loadStatus === "loaded");
|
|
185
|
+
} else if (filter === "error") {
|
|
186
|
+
filtered = filtered.filter(p => p.loadStatus === "error" || p.loadError);
|
|
187
|
+
} else if (filter === "updatable") {
|
|
188
|
+
filtered = filtered.filter(p => p.hasUpdate === true);
|
|
144
189
|
}
|
|
145
190
|
|
|
146
191
|
return {
|
|
147
192
|
success: true,
|
|
148
193
|
total: filtered.length,
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
description: p.description,
|
|
154
|
-
status: registry.loadedPlugins.has(p.code) ? "loaded" : "available",
|
|
155
|
-
loadedAt: registry.loadedPlugins.has(p.code)
|
|
156
|
-
? registry.loadedPlugins.get(p.code).loadedAt
|
|
157
|
-
: null
|
|
158
|
-
}))
|
|
194
|
+
totalAllPlugins: state.allPlugins.length,
|
|
195
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
196
|
+
filter,
|
|
197
|
+
plugins: filtered.map(formatMiddlewarePlugin)
|
|
159
198
|
};
|
|
160
199
|
} catch (err) {
|
|
161
|
-
return {
|
|
162
|
-
success: false,
|
|
163
|
-
error: err.message,
|
|
164
|
-
plugins: []
|
|
165
|
-
};
|
|
200
|
+
return { success: false, error: err.message, plugins: [] };
|
|
166
201
|
}
|
|
167
202
|
},
|
|
168
203
|
{
|
|
169
204
|
name: "list_middleware_plugins",
|
|
170
|
-
description: "
|
|
205
|
+
description: "列出平台上所有已安装的中间件类型插件,可按状态过滤。返回插件名称、版本、描述、加载状态等详细信息。",
|
|
171
206
|
schema: z.object({
|
|
172
|
-
filter: z.enum(["all", "loaded", "
|
|
173
|
-
.describe("过滤条件:all=全部, loaded=已加载,
|
|
207
|
+
filter: z.enum(["all", "loaded", "error", "updatable"]).optional().default("all")
|
|
208
|
+
.describe("过滤条件:all=全部, loaded=已加载, error=加载出错, updatable=有可用更新")
|
|
174
209
|
})
|
|
175
210
|
}
|
|
176
211
|
);
|
|
177
212
|
|
|
178
|
-
// Tool 2:
|
|
179
|
-
const
|
|
213
|
+
// ======== Tool 2: get_middleware_plugin_detail ========
|
|
214
|
+
const getMiddlewarePluginDetail = tool(
|
|
180
215
|
async (input) => {
|
|
181
|
-
|
|
216
|
+
try {
|
|
217
|
+
await fetchAllPlugins();
|
|
218
|
+
const keyword = input.pluginNameOrPackage;
|
|
219
|
+
const found = state.middlewarePlugins.find(p => {
|
|
220
|
+
const meta = p.meta || {};
|
|
221
|
+
return p.name === keyword
|
|
222
|
+
|| meta.name === keyword
|
|
223
|
+
|| p.packageName === keyword
|
|
224
|
+
|| meta.displayName === keyword;
|
|
225
|
+
});
|
|
182
226
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
};
|
|
193
|
-
}
|
|
227
|
+
if (!found) {
|
|
228
|
+
return {
|
|
229
|
+
success: false,
|
|
230
|
+
error: `未找到名为 "${keyword}" 的中间件插件`,
|
|
231
|
+
availableMiddleware: state.middlewarePlugins.map(p => ({
|
|
232
|
+
name: p.name, packageName: p.packageName, displayName: (p.meta || {}).displayName
|
|
233
|
+
}))
|
|
234
|
+
};
|
|
235
|
+
}
|
|
194
236
|
|
|
195
|
-
|
|
196
|
-
const plugins = await fetchMiddlewarePlugins();
|
|
197
|
-
const target = plugins.find(p => p.code === pluginCode);
|
|
237
|
+
const detail = formatMiddlewarePlugin(found);
|
|
198
238
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
239
|
+
// 尝试获取组件信息
|
|
240
|
+
try {
|
|
241
|
+
const token = await loginAndGetToken();
|
|
242
|
+
const fetch = (await import("node-fetch")).default;
|
|
243
|
+
const compResp = await fetch(`${platformUrl}/api/plugin/${encodeURIComponent(found.name)}/components`, {
|
|
244
|
+
method: "GET", headers: authHeaders(token), timeout: 10000
|
|
245
|
+
});
|
|
246
|
+
if (compResp.ok) {
|
|
247
|
+
const compData = await compResp.json();
|
|
248
|
+
detail.components = compData.items || compData;
|
|
249
|
+
}
|
|
250
|
+
} catch (e) {
|
|
251
|
+
detail.components = [];
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { success: true, plugin: detail };
|
|
255
|
+
} catch (err) {
|
|
256
|
+
return { success: false, error: err.message };
|
|
205
257
|
}
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
name: "get_middleware_plugin_detail",
|
|
261
|
+
description: "查询指定中间件插件的详细信息,包括加载状态、配置状态、组件列表、版本信息等。",
|
|
262
|
+
schema: z.object({
|
|
263
|
+
pluginNameOrPackage: z.string().min(1)
|
|
264
|
+
.describe("插件名称、packageName 或 displayName,例如 'echarts-generator' 或 '@timmy_hu/bulk-email-sender'")
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
// ======== Tool 3: install_middleware_plugin ========
|
|
270
|
+
const installMiddlewarePlugin = tool(
|
|
271
|
+
async (input) => {
|
|
272
|
+
const packageName = input.packageName;
|
|
273
|
+
const version = input.version || "latest";
|
|
206
274
|
|
|
207
275
|
try {
|
|
208
|
-
|
|
276
|
+
const token = await loginAndGetToken();
|
|
209
277
|
const fetch = (await import("node-fetch")).default;
|
|
210
|
-
const
|
|
211
|
-
const resp = await fetch(loadUrl, {
|
|
278
|
+
const resp = await fetch(`${platformUrl}/api/plugin`, {
|
|
212
279
|
method: "POST",
|
|
213
|
-
headers:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
},
|
|
217
|
-
body: JSON.stringify({
|
|
218
|
-
pluginName: target.name || pluginCode,
|
|
219
|
-
version: target.version,
|
|
220
|
-
source: "npm"
|
|
221
|
-
}),
|
|
222
|
-
timeout: 30000
|
|
280
|
+
headers: authHeaders(token),
|
|
281
|
+
body: JSON.stringify({ pluginName: packageName, version, source: "npm" }),
|
|
282
|
+
timeout: 60000
|
|
223
283
|
});
|
|
224
284
|
|
|
225
285
|
if (!resp.ok) {
|
|
226
286
|
const errText = await resp.text();
|
|
227
|
-
throw new Error(
|
|
287
|
+
throw new Error(`安装失败 (${resp.status}): ${errText}`);
|
|
228
288
|
}
|
|
229
289
|
|
|
230
290
|
const result = await resp.json();
|
|
231
291
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
name: target.name,
|
|
235
|
-
version: target.version,
|
|
236
|
-
loadedAt: new Date().toISOString(),
|
|
237
|
-
loadResult: result
|
|
238
|
-
});
|
|
292
|
+
// 刷新缓存
|
|
293
|
+
state.lastRefresh = null;
|
|
239
294
|
|
|
240
295
|
return {
|
|
241
296
|
success: true,
|
|
242
|
-
message: `插件 ${
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
version:
|
|
247
|
-
|
|
248
|
-
status: "loaded"
|
|
297
|
+
message: `插件 ${packageName} 安装成功`,
|
|
298
|
+
installed: {
|
|
299
|
+
name: result.name || packageName,
|
|
300
|
+
packageName: result.packageName || packageName,
|
|
301
|
+
version: result.currentVersion || version,
|
|
302
|
+
organizationId: result.organizationId || organizationId
|
|
249
303
|
}
|
|
250
304
|
};
|
|
251
305
|
} catch (err) {
|
|
252
|
-
// Even if API call fails, mark as locally loaded for demo/offline scenarios
|
|
253
|
-
registry.loadedPlugins.set(pluginCode, {
|
|
254
|
-
code: pluginCode,
|
|
255
|
-
name: target.name,
|
|
256
|
-
version: target.version,
|
|
257
|
-
loadedAt: new Date().toISOString(),
|
|
258
|
-
loadResult: { offline: true, error: err.message }
|
|
259
|
-
});
|
|
260
|
-
|
|
261
306
|
return {
|
|
262
|
-
success:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
name: target.name,
|
|
267
|
-
version: target.version,
|
|
268
|
-
loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
|
|
269
|
-
status: "loaded_offline",
|
|
270
|
-
warning: err.message
|
|
271
|
-
}
|
|
307
|
+
success: false,
|
|
308
|
+
error: err.message,
|
|
309
|
+
packageName,
|
|
310
|
+
version
|
|
272
311
|
};
|
|
273
312
|
}
|
|
274
313
|
},
|
|
275
314
|
{
|
|
276
|
-
name: "
|
|
277
|
-
description: "
|
|
315
|
+
name: "install_middleware_plugin",
|
|
316
|
+
description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后插件将自动加载并可供智能体使用。",
|
|
278
317
|
schema: z.object({
|
|
279
|
-
|
|
318
|
+
packageName: z.string().min(1).describe("npm 包名,例如 '@timmy_hu/bulk-email-sender'"),
|
|
319
|
+
version: z.string().optional().default("latest").describe("版本号,默认为 latest")
|
|
280
320
|
})
|
|
281
321
|
}
|
|
282
322
|
);
|
|
283
323
|
|
|
284
|
-
// Tool
|
|
285
|
-
const
|
|
324
|
+
// ======== Tool 4: refresh_plugin_list ========
|
|
325
|
+
const refreshPluginList = tool(
|
|
286
326
|
async (input) => {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
327
|
+
state.lastRefresh = null;
|
|
328
|
+
state.token = null;
|
|
329
|
+
state.tokenExpiry = null;
|
|
330
|
+
try {
|
|
331
|
+
await fetchAllPlugins();
|
|
290
332
|
return {
|
|
291
|
-
success:
|
|
292
|
-
|
|
293
|
-
|
|
333
|
+
success: true,
|
|
334
|
+
message: `插件列表已刷新,共发现 ${state.allPlugins.length} 个插件,其中 ${state.middlewarePlugins.length} 个中间件类型`,
|
|
335
|
+
totalPlugins: state.allPlugins.length,
|
|
336
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
337
|
+
refreshedAt: new Date().toISOString()
|
|
294
338
|
};
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
try {
|
|
298
|
-
const fetch = (await import("node-fetch")).default;
|
|
299
|
-
const unloadUrl = `${platformUrl}/api/plugin/unload`;
|
|
300
|
-
const resp = await fetch(unloadUrl, {
|
|
301
|
-
method: "POST",
|
|
302
|
-
headers: {
|
|
303
|
-
"Content-Type": "application/json",
|
|
304
|
-
...(organizationId ? { "organization-id": organizationId } : {})
|
|
305
|
-
},
|
|
306
|
-
body: JSON.stringify({ pluginCode }),
|
|
307
|
-
timeout: 15000
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
if (!resp.ok) {
|
|
311
|
-
const errText = await resp.text();
|
|
312
|
-
throw new Error(`Unload API returned ${resp.status}: ${errText}`);
|
|
313
|
-
}
|
|
314
339
|
} catch (err) {
|
|
315
|
-
|
|
340
|
+
return { success: false, error: `刷新失败: ${err.message}` };
|
|
316
341
|
}
|
|
317
|
-
|
|
318
|
-
const removed = registry.loadedPlugins.get(pluginCode);
|
|
319
|
-
registry.loadedPlugins.delete(pluginCode);
|
|
320
|
-
|
|
321
|
-
return {
|
|
322
|
-
success: true,
|
|
323
|
-
message: `插件 ${pluginCode} 已卸载`,
|
|
324
|
-
plugin: {
|
|
325
|
-
code: pluginCode,
|
|
326
|
-
name: removed.name,
|
|
327
|
-
unloadedAt: new Date().toISOString(),
|
|
328
|
-
status: "unloaded"
|
|
329
|
-
}
|
|
330
|
-
};
|
|
331
342
|
},
|
|
332
343
|
{
|
|
333
|
-
name: "
|
|
334
|
-
description: "
|
|
335
|
-
schema: z.object({
|
|
336
|
-
pluginCode: z.string().min(1).describe("要卸载的中间件插件编码")
|
|
337
|
-
})
|
|
344
|
+
name: "refresh_plugin_list",
|
|
345
|
+
description: "刷新插件列表缓存,强制重新登录并从平台获取所有已安装插件的最新状态。",
|
|
346
|
+
schema: z.object({})
|
|
338
347
|
}
|
|
339
348
|
);
|
|
340
349
|
|
|
341
|
-
// Tool
|
|
342
|
-
const
|
|
350
|
+
// ======== Tool 5: check_login_status ========
|
|
351
|
+
const checkLoginStatus = tool(
|
|
343
352
|
async (input) => {
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
353
|
+
try {
|
|
354
|
+
const token = await loginAndGetToken();
|
|
355
|
+
return {
|
|
356
|
+
success: true,
|
|
357
|
+
loggedIn: true,
|
|
358
|
+
email,
|
|
359
|
+
platformUrl,
|
|
360
|
+
organizationId: organizationId || "(未设置)",
|
|
361
|
+
tokenExpiry: state.tokenExpiry ? new Date(state.tokenExpiry).toISOString() : null,
|
|
362
|
+
cachedPlugins: state.allPlugins.length,
|
|
363
|
+
cachedMiddleware: state.middlewarePlugins.length,
|
|
364
|
+
message: "登录状态正常,token 有效"
|
|
365
|
+
};
|
|
366
|
+
} catch (err) {
|
|
349
367
|
return {
|
|
350
368
|
success: false,
|
|
351
|
-
|
|
369
|
+
loggedIn: false,
|
|
370
|
+
email,
|
|
371
|
+
platformUrl,
|
|
372
|
+
error: err.message
|
|
352
373
|
};
|
|
353
374
|
}
|
|
354
|
-
|
|
355
|
-
const loaded = registry.loadedPlugins.has(pluginCode);
|
|
356
|
-
const loadInfo = loaded ? registry.loadedPlugins.get(pluginCode) : null;
|
|
357
|
-
|
|
358
|
-
return {
|
|
359
|
-
success: true,
|
|
360
|
-
plugin: {
|
|
361
|
-
code: target.code,
|
|
362
|
-
name: target.name,
|
|
363
|
-
version: target.version,
|
|
364
|
-
description: target.description,
|
|
365
|
-
category: target.category,
|
|
366
|
-
status: loaded ? "loaded" : "available",
|
|
367
|
-
loadedAt: loadInfo ? loadInfo.loadedAt : null,
|
|
368
|
-
tools: loaded ? (loadInfo.tools || []) : [],
|
|
369
|
-
configRequired: target.configRequired || []
|
|
370
|
-
}
|
|
371
|
-
};
|
|
372
375
|
},
|
|
373
376
|
{
|
|
374
|
-
name: "
|
|
375
|
-
description: "
|
|
376
|
-
schema: z.object({
|
|
377
|
-
pluginCode: z.string().min(1).describe("要查询的中间件插件编码")
|
|
378
|
-
})
|
|
377
|
+
name: "check_login_status",
|
|
378
|
+
description: "检查当前平台登录状态,验证 email/password 是否有效、token 是否过期,以及缓存中有多少插件。",
|
|
379
|
+
schema: z.object({})
|
|
379
380
|
}
|
|
380
381
|
);
|
|
381
382
|
|
|
382
|
-
// Tool
|
|
383
|
-
const
|
|
383
|
+
// ======== Tool 6: search_middleware_plugins ========
|
|
384
|
+
const searchMiddlewarePlugins = tool(
|
|
384
385
|
async (input) => {
|
|
385
|
-
registry.lastRefresh = null;
|
|
386
386
|
try {
|
|
387
|
-
|
|
387
|
+
await fetchAllPlugins();
|
|
388
|
+
const keyword = (input.keyword || "").toLowerCase();
|
|
389
|
+
const results = state.middlewarePlugins.filter(p => {
|
|
390
|
+
const meta = p.meta || {};
|
|
391
|
+
const searchFields = [
|
|
392
|
+
p.name, p.packageName, meta.name, meta.displayName,
|
|
393
|
+
meta.description, (meta.keywords || []).join(" ")
|
|
394
|
+
].map(s => (s || "").toLowerCase());
|
|
395
|
+
return searchFields.some(f => f.includes(keyword));
|
|
396
|
+
});
|
|
397
|
+
|
|
388
398
|
return {
|
|
389
399
|
success: true,
|
|
390
|
-
|
|
391
|
-
total:
|
|
392
|
-
|
|
400
|
+
keyword: input.keyword,
|
|
401
|
+
total: results.length,
|
|
402
|
+
plugins: results.map(formatMiddlewarePlugin)
|
|
393
403
|
};
|
|
394
404
|
} catch (err) {
|
|
395
|
-
return {
|
|
396
|
-
success: false,
|
|
397
|
-
error: `刷新缓存失败: ${err.message}`
|
|
398
|
-
};
|
|
405
|
+
return { success: false, error: err.message, plugins: [] };
|
|
399
406
|
}
|
|
400
407
|
},
|
|
401
408
|
{
|
|
402
|
-
name: "
|
|
403
|
-
description: "
|
|
404
|
-
schema: z.object({
|
|
409
|
+
name: "search_middleware_plugins",
|
|
410
|
+
description: "按关键词搜索中间件类型插件。搜索范围包括插件名称、包名、描述和关键词。",
|
|
411
|
+
schema: z.object({
|
|
412
|
+
keyword: z.string().min(1).describe("搜索关键词,例如 'echarts'、'email'、'retry'")
|
|
413
|
+
})
|
|
405
414
|
}
|
|
406
415
|
);
|
|
407
416
|
|
|
408
|
-
// Tool
|
|
409
|
-
const
|
|
417
|
+
// ======== Tool 7: get_plugin_statistics ========
|
|
418
|
+
const getPluginStatistics = tool(
|
|
410
419
|
async (input) => {
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const plugins = await fetchMiddlewarePlugins();
|
|
420
|
+
try {
|
|
421
|
+
await fetchAllPlugins();
|
|
415
422
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
success: true
|
|
422
|
-
});
|
|
423
|
-
continue;
|
|
423
|
+
// 按 category 统计
|
|
424
|
+
const categoryStats = {};
|
|
425
|
+
for (const p of state.allPlugins) {
|
|
426
|
+
const cat = (p.meta || {}).category || "unknown";
|
|
427
|
+
categoryStats[cat] = (categoryStats[cat] || 0) + 1;
|
|
424
428
|
}
|
|
425
429
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
success: false,
|
|
432
|
-
error: `未找到编码为 ${code} 的中间件插件`
|
|
433
|
-
});
|
|
434
|
-
continue;
|
|
430
|
+
// 按 loadStatus 统计中间件
|
|
431
|
+
const loadStatusStats = {};
|
|
432
|
+
for (const p of state.middlewarePlugins) {
|
|
433
|
+
const ls = p.loadStatus || "unknown";
|
|
434
|
+
loadStatusStats[ls] = (loadStatusStats[ls] || 0) + 1;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
name: target.name,
|
|
440
|
-
version: target.version,
|
|
441
|
-
loadedAt: new Date().toISOString(),
|
|
442
|
-
loadResult: { batch: true }
|
|
443
|
-
});
|
|
437
|
+
// 有更新的插件
|
|
438
|
+
const updatable = state.middlewarePlugins.filter(p => p.hasUpdate === true);
|
|
444
439
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
440
|
+
// 有配置错误的插件
|
|
441
|
+
const configErrors = state.middlewarePlugins.filter(p => p.configurationError);
|
|
442
|
+
|
|
443
|
+
// 有加载错误的插件
|
|
444
|
+
const loadErrors = state.middlewarePlugins.filter(p => p.loadError);
|
|
445
|
+
|
|
446
|
+
return {
|
|
450
447
|
success: true,
|
|
451
|
-
|
|
452
|
-
|
|
448
|
+
statistics: {
|
|
449
|
+
totalPlugins: state.allPlugins.length,
|
|
450
|
+
totalMiddleware: state.middlewarePlugins.length,
|
|
451
|
+
byCategory: categoryStats,
|
|
452
|
+
middlewareByLoadStatus: loadStatusStats,
|
|
453
|
+
updatableCount: updatable.length,
|
|
454
|
+
updatablePlugins: updatable.map(p => ({ name: p.name, packageName: p.packageName, version: (p.meta || {}).version })),
|
|
455
|
+
configErrorCount: configErrors.length,
|
|
456
|
+
configErrorPlugins: configErrors.map(p => ({ name: p.name, error: p.configurationError })),
|
|
457
|
+
loadErrorCount: loadErrors.length,
|
|
458
|
+
loadErrorPlugins: loadErrors.map(p => ({ name: p.name, error: p.loadError }))
|
|
459
|
+
},
|
|
460
|
+
generatedAt: new Date().toISOString()
|
|
461
|
+
};
|
|
462
|
+
} catch (err) {
|
|
463
|
+
return { success: false, error: err.message };
|
|
453
464
|
}
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
success: true,
|
|
457
|
-
total: pluginCodes.length,
|
|
458
|
-
loaded: results.filter(r => r.status === "loaded").length,
|
|
459
|
-
alreadyLoaded: results.filter(r => r.status === "already_loaded").length,
|
|
460
|
-
failed: results.filter(r => !r.success).length,
|
|
461
|
-
results
|
|
462
|
-
};
|
|
463
465
|
},
|
|
464
466
|
{
|
|
465
|
-
name: "
|
|
466
|
-
description: "
|
|
467
|
-
schema: z.object({
|
|
468
|
-
pluginCodes: z.array(z.string().min(1)).min(1)
|
|
469
|
-
.describe("要批量加载的中间件插件编码数组,例如 ['weather-query', 'pg-query-tool']")
|
|
470
|
-
})
|
|
467
|
+
name: "get_plugin_statistics",
|
|
468
|
+
description: "获取平台上所有插件的统计信息,包括按分类统计、按加载状态统计、有更新的插件、有错误的插件等。",
|
|
469
|
+
schema: z.object({})
|
|
471
470
|
}
|
|
472
471
|
);
|
|
473
472
|
|
|
@@ -475,11 +474,12 @@ class PluginMiddleware {
|
|
|
475
474
|
name: MIDDLEWARE_PROVIDER,
|
|
476
475
|
tools: [
|
|
477
476
|
listMiddlewarePlugins,
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
477
|
+
getMiddlewarePluginDetail,
|
|
478
|
+
installMiddlewarePlugin,
|
|
479
|
+
refreshPluginList,
|
|
480
|
+
checkLoginStatus,
|
|
481
|
+
searchMiddlewarePlugins,
|
|
482
|
+
getPluginStatistics
|
|
483
483
|
]
|
|
484
484
|
};
|
|
485
485
|
}
|
|
@@ -493,12 +493,12 @@ class PluginModule {}
|
|
|
493
493
|
const plugin = {
|
|
494
494
|
meta: {
|
|
495
495
|
name: "@timmy_hu/plugin-middleware",
|
|
496
|
-
version: "1.0.
|
|
496
|
+
version: "1.0.2",
|
|
497
497
|
level: "organization",
|
|
498
498
|
category: "middleware",
|
|
499
499
|
displayName: "插件中间件",
|
|
500
|
-
description: "
|
|
501
|
-
keywords: ["middleware", "plugin", "discovery", "
|
|
500
|
+
description: "自动发现 XpertAI 平台上所有已安装的中间件类型插件,展示加载状态,支持从 npm 安装新插件、搜索、统计。通过 /api/auth/login 登录获取 token。",
|
|
501
|
+
keywords: ["middleware", "plugin", "discovery", "install", "agent", "orchestration"],
|
|
502
502
|
author: "fpi"
|
|
503
503
|
},
|
|
504
504
|
register(ctx) {
|