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