@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/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 and provides dynamic on-demand loading, unloading, and status query tools for agent orchestration.",
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
- en_US: "Platform URL",
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 registry for discovered middleware plugins
72
- const registry = {
73
- plugins: new Map(),
74
- loadedPlugins: new Map(),
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
- // Helper: fetch middleware plugins from platform API
80
- async function fetchMiddlewarePlugins() {
74
+ // ======== 登录:调用 /api/auth/login 获取 token ========
75
+ async function loginAndGetToken() {
81
76
  const now = Date.now();
82
- if (registry.lastRefresh && (now - registry.lastRefresh) < registry.cacheTtl) {
83
- return Array.from(registry.plugins.values());
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
- try {
87
- const fetch = (await import("node-fetch")).default;
88
- const url = `${platformUrl}/api/plugin?category=middleware&organization-id=${encodeURIComponent(organizationId)}`;
89
- const resp = await fetch(url, {
90
- method: "GET",
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
- if (!resp.ok) {
99
- throw new Error(`Platform API returned ${resp.status}: ${resp.statusText}`);
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
- const data = await resp.json();
103
- const pluginList = Array.isArray(data) ? data : (data.data || data.items || data.plugins || []);
104
-
105
- registry.plugins.clear();
106
- for (const p of pluginList) {
107
- const code = p.code || p.name || p.pluginName;
108
- if (code) {
109
- registry.plugins.set(code, {
110
- code,
111
- name: p.name || p.displayName || code,
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
- registry.lastRefresh = now;
122
- return Array.from(registry.plugins.values());
123
- } catch (err) {
124
- // If API call fails, return cached data or empty list
125
- if (registry.plugins.size > 0) {
126
- return Array.from(registry.plugins.values());
127
- }
128
- throw new Error(`Failed to discover middleware plugins: ${err.message}`);
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: List all discovered middleware plugins
175
+ // ======== Tool 1: list_middleware_plugins ========
133
176
  const listMiddlewarePlugins = tool(
134
177
  async (input) => {
135
178
  try {
136
- const plugins = await fetchMiddlewarePlugins();
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 = plugins.filter(p => p.loaded || registry.loadedPlugins.has(p.code));
142
- } else if (filter === "available") {
143
- filtered = plugins.filter(p => !p.loaded && !registry.loadedPlugins.has(p.code));
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
- plugins: filtered.map(p => ({
150
- code: p.code,
151
- name: p.name,
152
- version: p.version,
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", "available"]).optional().default("all")
173
- .describe("过滤条件:all=全部, loaded=已加载, available=可用未加载")
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: Load a specific middleware plugin by code
179
- const loadMiddlewarePlugin = tool(
213
+ // ======== Tool 2: get_middleware_plugin_detail ========
214
+ const getMiddlewarePluginDetail = tool(
180
215
  async (input) => {
181
- const pluginCode = input.pluginCode;
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
- if (registry.loadedPlugins.has(pluginCode)) {
184
- return {
185
- success: true,
186
- message: `插件 ${pluginCode} 已经处于加载状态`,
187
- plugin: {
188
- code: pluginCode,
189
- loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
190
- status: "already_loaded"
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
- // Discover if not yet known
196
- const plugins = await fetchMiddlewarePlugins();
197
- const target = plugins.find(p => p.code === pluginCode);
237
+ const detail = formatMiddlewarePlugin(found);
198
238
 
199
- if (!target) {
200
- return {
201
- success: false,
202
- error: `未找到编码为 ${pluginCode} 的中间件插件`,
203
- availablePlugins: plugins.map(p => p.code)
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
- // Attempt to load via platform API
276
+ const token = await loginAndGetToken();
209
277
  const fetch = (await import("node-fetch")).default;
210
- const loadUrl = `${platformUrl}/api/plugin/load`;
211
- const resp = await fetch(loadUrl, {
278
+ const resp = await fetch(`${platformUrl}/api/plugin`, {
212
279
  method: "POST",
213
- headers: {
214
- "Content-Type": "application/json",
215
- ...(organizationId ? { "organization-id": organizationId } : {})
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(`Load API returned ${resp.status}: ${errText}`);
287
+ throw new Error(`安装失败 (${resp.status}): ${errText}`);
228
288
  }
229
289
 
230
290
  const result = await resp.json();
231
291
 
232
- registry.loadedPlugins.set(pluginCode, {
233
- code: pluginCode,
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: `插件 ${pluginCode} 加载成功`,
243
- plugin: {
244
- code: pluginCode,
245
- name: target.name,
246
- version: target.version,
247
- loadedAt: registry.loadedPlugins.get(pluginCode).loadedAt,
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: true,
263
- message: `插件 ${pluginCode} 已标记为加载(离线模式)`,
264
- plugin: {
265
- code: pluginCode,
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: "load_middleware_plugin",
277
- description: "按插件编码动态加载指定的中间件插件。加载后该插件的工具和能力将对智能体可用。",
315
+ name: "install_middleware_plugin",
316
+ description: "从 npm 安装一个新的中间件插件到 XpertAI 平台。安装后插件将自动加载并可供智能体使用。",
278
317
  schema: z.object({
279
- pluginCode: z.string().min(1).describe("要加载的中间件插件编码,例如 'weather-query' 'pg-query-tool'")
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 3: Unload a specific middleware plugin
285
- const unloadMiddlewarePlugin = tool(
324
+ // ======== Tool 4: refresh_plugin_list ========
325
+ const refreshPluginList = tool(
286
326
  async (input) => {
287
- const pluginCode = input.pluginCode;
288
-
289
- if (!registry.loadedPlugins.has(pluginCode)) {
327
+ state.lastRefresh = null;
328
+ state.token = null;
329
+ state.tokenExpiry = null;
330
+ try {
331
+ await fetchAllPlugins();
290
332
  return {
291
- success: false,
292
- error: `插件 ${pluginCode} 当前未加载,无需卸载`,
293
- loadedPlugins: Array.from(registry.loadedPlugins.keys())
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
- // Continue with local unload even if API fails
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: "unload_middleware_plugin",
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 4: Query detailed status of a specific middleware plugin
342
- const getMiddlewarePluginStatus = tool(
350
+ // ======== Tool 5: check_login_status ========
351
+ const checkLoginStatus = tool(
343
352
  async (input) => {
344
- const pluginCode = input.pluginCode;
345
- const plugins = await fetchMiddlewarePlugins();
346
- const target = plugins.find(p => p.code === pluginCode);
347
-
348
- if (!target) {
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
- error: `未找到编码为 ${pluginCode} 的中间件插件`
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: "get_middleware_plugin_status",
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 5: Refresh the plugin discovery cache
383
- const refreshPluginCache = tool(
383
+ // ======== Tool 6: search_middleware_plugins ========
384
+ const searchMiddlewarePlugins = tool(
384
385
  async (input) => {
385
- registry.lastRefresh = null;
386
386
  try {
387
- const plugins = await fetchMiddlewarePlugins();
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
- message: `缓存已刷新,发现 ${plugins.length} 个中间件插件`,
391
- total: plugins.length,
392
- refreshedAt: new Date().toISOString()
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: "refresh_plugin_cache",
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 6: Batch load multiple middleware plugins
409
- const batchLoadMiddlewarePlugins = tool(
417
+ // ======== Tool 7: get_plugin_statistics ========
418
+ const getPluginStatistics = tool(
410
419
  async (input) => {
411
- const pluginCodes = input.pluginCodes;
412
- const results = [];
413
-
414
- const plugins = await fetchMiddlewarePlugins();
420
+ try {
421
+ await fetchAllPlugins();
415
422
 
416
- for (const code of pluginCodes) {
417
- if (registry.loadedPlugins.has(code)) {
418
- results.push({
419
- code,
420
- status: "already_loaded",
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
- const target = plugins.find(p => p.code === code);
427
- if (!target) {
428
- results.push({
429
- code,
430
- status: "not_found",
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
- registry.loadedPlugins.set(code, {
438
- code,
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
- results.push({
446
- code,
447
- name: target.name,
448
- version: target.version,
449
- status: "loaded",
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
- loadedAt: registry.loadedPlugins.get(code).loadedAt
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: "batch_load_middleware_plugins",
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
- loadMiddlewarePlugin,
479
- unloadMiddlewarePlugin,
480
- getMiddlewarePluginStatus,
481
- refreshPluginCache,
482
- batchLoadMiddlewarePlugins
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.0",
496
+ version: "1.0.2",
497
497
  level: "organization",
498
498
  category: "middleware",
499
499
  displayName: "插件中间件",
500
- description: "自动发现所有中间件类型插件,支持动态按需加载、卸载和状态查询,为智能体编排提供灵活的中间件管理能力。",
501
- keywords: ["middleware", "plugin", "discovery", "dynamic-loading", "agent", "orchestration"],
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) {