mu-agent-bridge 1.0.4 → 1.0.12

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.
Files changed (41) hide show
  1. package/README.md +16 -11
  2. package/dist/src-ts/cli.js +60 -25
  3. package/dist/src-ts/control-server.js +73 -94
  4. package/dist/src-ts/local-aids.js +26 -0
  5. package/dist/src-ts/log-store.js +64 -0
  6. package/dist/src-ts/plugin-manager.js +178 -37
  7. package/dist/src-ts/plugin-manifest.js +5 -1
  8. package/dist/src-ts/plugins/abweb/index.js +1 -1
  9. package/dist/src-ts/plugins/abweb/public/app.js +165 -104
  10. package/dist/src-ts/plugins/abweb/public/index.html +6 -5
  11. package/dist/src-ts/plugins/abweb/public/style.css +12 -12
  12. package/dist/src-ts/plugins/aun/evolcore.js +54 -0
  13. package/dist/src-ts/plugins/aun/index.js +252 -50
  14. package/dist/src-ts/plugins/aun/public/app.js +66 -0
  15. package/dist/src-ts/plugins/aun/public/index.html +14 -0
  16. package/dist/src-ts/plugins/aun/public/style.css +21 -0
  17. package/dist/src-ts/plugins/doubao/cdp-client.js +1 -1
  18. package/dist/src-ts/plugins/doubao/index.js +2 -2
  19. package/dist/src-ts/plugins/feishu/client.js +133 -0
  20. package/dist/src-ts/plugins/feishu/index.js +214 -0
  21. package/dist/src-ts/plugins/feishu/public/app.js +126 -0
  22. package/dist/src-ts/plugins/feishu/public/index.html +16 -0
  23. package/dist/src-ts/plugins/feishu/public/style.css +25 -0
  24. package/dist/src-ts/plugins/workbuddy/cdp-client.js +118 -0
  25. package/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  26. package/dist/src-ts/plugins/workbuddy/index.js +139 -0
  27. package/dist/src-ts/version-manager.js +9 -1
  28. package/dist/tests-ts/abweb-plugin.test.js +19 -0
  29. package/dist/tests-ts/aun-plugin.test.js +108 -4
  30. package/dist/tests-ts/control-server.test.js +88 -6
  31. package/dist/tests-ts/doubao-plugin.test.js +14 -0
  32. package/dist/tests-ts/feishu-client.test.js +72 -0
  33. package/dist/tests-ts/feishu-page.test.js +47 -0
  34. package/dist/tests-ts/feishu-plugin.test.js +96 -0
  35. package/dist/tests-ts/log-store.test.js +27 -0
  36. package/dist/tests-ts/platform-integration.test.js +40 -1
  37. package/dist/tests-ts/plugin-catalog.test.js +8 -0
  38. package/dist/tests-ts/plugin-manager.test.js +62 -13
  39. package/dist/tests-ts/workbuddy-plugin.test.js +85 -0
  40. package/package.json +30 -6
  41. package/plugins/abweb/plugin.json +1 -1
@@ -1,4 +1,7 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.PluginManager = void 0;
4
7
  const promises_1 = require("node:fs/promises");
@@ -11,6 +14,8 @@ const agent_1 = require("./agent");
11
14
  const plugin_manifest_1 = require("./plugin-manifest");
12
15
  const version_manager_1 = require("./version-manager");
13
16
  const plugin_catalog_1 = require("./plugin-catalog");
17
+ const log_store_1 = require("./log-store");
18
+ const ajv_1 = __importDefault(require("ajv"));
14
19
  const logger = { info() { }, warn() { }, error() { } };
15
20
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
16
21
  class PluginManager {
@@ -20,11 +25,55 @@ class PluginManager {
20
25
  root;
21
26
  messages = [];
22
27
  versions;
28
+ logs;
23
29
  constructor(options) {
24
30
  this.options = options;
25
31
  this.root = (0, node_path_1.resolve)(options.pluginsDir);
26
32
  this.versions = options.versions ?? new version_manager_1.VersionManager({ stateDir: options.stateDir });
33
+ this.logs = new log_store_1.LogStore((0, node_path_1.join)(options.stateDir, "logs"));
27
34
  }
35
+ log(level, message, fields, source = "mab") { this.logs.append({ time: new Date().toISOString(), level, source, message, ...(fields ? { fields } : {}) }); }
36
+ async updatePolicy() { try {
37
+ const value = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, "update-policy.json"), "utf8"));
38
+ return isUpdatePolicy(value) ? value : { mode: "auto-install" };
39
+ }
40
+ catch {
41
+ return { mode: "auto-install" };
42
+ } }
43
+ async saveUpdatePolicy(policy) { if (!isUpdatePolicy(policy))
44
+ throw new agent_1.AgentError("更新策略无效"); await (0, promises_1.mkdir)(this.options.stateDir, { recursive: true }); await (0, promises_1.writeFile)((0, node_path_1.join)(this.options.stateDir, "update-policy.json"), JSON.stringify(policy, null, 2)); this.log("info", `插件更新策略已设置:${policy.mode}`, undefined, "升级策略"); }
45
+ async downloadPackage(packageName, version) {
46
+ if (!/^agent-bridge-[a-z0-9-]+$/.test(packageName))
47
+ throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
48
+ if (version && !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version))
49
+ throw new agent_1.AgentError("插件版本号无效");
50
+ const target = (0, node_path_1.join)(this.options.stateDir, "downloads", packageName, version ?? "latest");
51
+ await (0, promises_1.rm)(target, { recursive: true, force: true });
52
+ await (0, promises_1.mkdir)(target, { recursive: true });
53
+ this.log("info", `开始下载插件:${packageName}`, { version }, "插件升级");
54
+ try {
55
+ const spec = version ? `${packageName}@${version}` : packageName;
56
+ if (process.platform === "win32")
57
+ await execFileAsync("npm.cmd", ["install", "--prefix", target, "--no-save", "--ignore-scripts", spec], { shell: true });
58
+ else
59
+ await execFileAsync("npm", ["install", "--prefix", target, "--no-save", "--ignore-scripts", spec]);
60
+ const candidate = (0, node_path_1.join)(target, "node_modules", packageName);
61
+ const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(candidate, "package.json"), "utf8"));
62
+ if (meta.name !== packageName || !meta.version || (version && meta.version !== version) || !meta.agentBridge?.plugin)
63
+ throw new agent_1.AgentError("npm 包声明无效");
64
+ const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(candidate, meta.agentBridge.plugin), "utf8")), candidate);
65
+ if (manifest.id !== packageName.slice("agent-bridge-".length) || manifest.version !== meta.version)
66
+ throw new agent_1.AgentError("npm 包与插件清单不匹配");
67
+ this.log("info", `插件下载完成:${manifest.id}`, { packageName, version: meta.version }, "插件升级");
68
+ return target;
69
+ }
70
+ catch (error) {
71
+ await (0, promises_1.rm)(target, { recursive: true, force: true });
72
+ this.log("error", `插件下载失败:${packageName}`, { error: error instanceof Error ? error.message : String(error) }, "插件升级");
73
+ throw error;
74
+ }
75
+ }
76
+ async cachedPackage(packageName, version) { const target = (0, node_path_1.join)(this.options.stateDir, "downloads", packageName, version ?? "latest"); return (0, node_fs_1.existsSync)((0, node_path_1.join)(target, "node_modules", packageName)) ? target : undefined; }
28
77
  async discover() {
29
78
  await (0, promises_1.mkdir)(this.root, { recursive: true });
30
79
  await this.discoverNpmPackages();
@@ -78,15 +127,15 @@ class PluginManager {
78
127
  if (!(0, node_path_1.resolve)(loaded.record.path).startsWith((0, node_path_1.resolve)(this.options.stateDir, "npm")))
79
128
  throw new agent_1.AgentError("内置插件不能通过 npm 升级");
80
129
  const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", packageName);
81
- const staging = (0, node_path_1.join)(this.options.stateDir, "npm", `.staging-${packageName}-${Date.now()}`);
130
+ const cached = await this.cachedPackage(packageName, version);
131
+ const staging = cached ?? (0, node_path_1.join)(this.options.stateDir, "npm", `.staging-${packageName}-${Date.now()}`);
82
132
  const backup = `${prefix}.backup-${Date.now()}`;
133
+ let stopped = false;
134
+ this.log("info", `开始升级插件:${id}`, { packageName, version }, "插件升级");
83
135
  await (0, promises_1.mkdir)(staging, { recursive: true });
84
136
  try {
85
- const spec = version ? `${packageName}@${version}` : packageName;
86
- if (process.platform === "win32")
87
- await execFileAsync("npm.cmd", ["install", "--prefix", staging, "--no-save", "--ignore-scripts", spec]);
88
- else
89
- await execFileAsync("npm", ["install", "--prefix", staging, "--no-save", "--ignore-scripts", spec]);
137
+ if (!cached)
138
+ await this.downloadPackage(packageName, version).then((downloaded) => (0, promises_1.rename)(downloaded, staging));
90
139
  const candidate = (0, node_path_1.join)(staging, "node_modules", packageName);
91
140
  const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(candidate, "package.json"), "utf8"));
92
141
  if (meta.name !== packageName || !meta.version || (version && meta.version !== version))
@@ -97,6 +146,7 @@ class PluginManager {
97
146
  if (manifest.id !== id || manifest.version !== meta.version)
98
147
  throw new agent_1.AgentError("npm 包插件 ID 或版本不匹配");
99
148
  await this.stop(id, "reload");
149
+ stopped = true;
100
150
  await (0, promises_1.rename)(prefix, backup);
101
151
  await (0, promises_1.rename)(staging, prefix);
102
152
  this.plugins.delete(id);
@@ -105,6 +155,7 @@ class PluginManager {
105
155
  const record = this.plugins.get(id).record;
106
156
  await this.versions.checkPackage(packageName, record.version);
107
157
  await (0, promises_1.rm)(backup, { recursive: true, force: true });
158
+ this.log("info", `插件升级成功:${id}`, { packageName, version: record.version }, "插件升级");
108
159
  return record;
109
160
  }
110
161
  catch (error) {
@@ -113,12 +164,15 @@ class PluginManager {
113
164
  await (0, promises_1.rm)(prefix, { recursive: true, force: true });
114
165
  await (0, promises_1.rename)(backup, prefix);
115
166
  }
116
- this.plugins.delete(id);
117
- try {
118
- await this.discoverManagedPackages();
119
- await this.start(id);
167
+ if (stopped) {
168
+ this.plugins.delete(id);
169
+ try {
170
+ await this.discoverManagedPackages();
171
+ await this.start(id);
172
+ }
173
+ catch { /* 回滚后仍无法启动时保留原错误 */ }
120
174
  }
121
- catch { /* 回滚后仍无法启动时保留原错误 */ }
175
+ this.log("error", `插件升级失败并已回滚:${id}`, { packageName, error: error instanceof Error ? error.message : String(error) }, "插件升级");
122
176
  throw error;
123
177
  }
124
178
  }
@@ -139,9 +193,10 @@ class PluginManager {
139
193
  if (await this.existsDir(prefix))
140
194
  throw new agent_1.AgentError("插件包已经安装");
141
195
  await (0, promises_1.mkdir)(prefix, { recursive: true });
196
+ this.log("info", `开始安装插件:${packageName}`, undefined, "插件安装");
142
197
  try {
143
198
  if (process.platform === "win32")
144
- await execFileAsync("npm.cmd", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName]);
199
+ await execFileAsync("npm.cmd", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName], { shell: true });
145
200
  else
146
201
  await execFileAsync("npm", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName]);
147
202
  const directory = (0, node_path_1.join)(prefix, "node_modules", name);
@@ -158,10 +213,12 @@ class PluginManager {
158
213
  const record = this.plugins.get(manifest.id)?.record;
159
214
  if (!record)
160
215
  throw new agent_1.AgentError("插件发现失败");
216
+ this.log("info", `插件安装成功:${manifest.id}`, { packageName, version: record.version }, "插件安装");
161
217
  return record;
162
218
  }
163
219
  catch (error) {
164
220
  await (0, promises_1.rm)(prefix, { recursive: true, force: true });
221
+ this.log("error", `插件安装失败:${packageName}`, { error: error instanceof Error ? error.message : String(error) }, "插件安装");
165
222
  throw error;
166
223
  }
167
224
  }
@@ -182,6 +239,14 @@ class PluginManager {
182
239
  throw new agent_1.AgentError(`unknown plugin: ${id}`);
183
240
  if (loaded.instance && loaded.record.status === "ready")
184
241
  return;
242
+ const compatibility = this.checkCompatibility(loaded.record);
243
+ loaded.record.compatibility = compatibility;
244
+ if (!compatibility.ok) {
245
+ loaded.record.status = "failed";
246
+ loaded.record.error = compatibility.reason;
247
+ this.log("error", `插件兼容性检查失败:${id}`, { reason: compatibility.reason }, "插件生命周期");
248
+ throw new agent_1.AgentError(compatibility.reason);
249
+ }
185
250
  loaded.record.status = "starting";
186
251
  try {
187
252
  const instance = await this.load(loaded.record);
@@ -194,10 +259,10 @@ class PluginManager {
194
259
  }
195
260
  catch { /* defaults */ }
196
261
  const pluginLogger = {
197
- info: (message, fields) => { this.options.logger?.info(message, fields); if (fields?.direction && typeof fields.text === "string")
262
+ info: (message, fields) => { this.log("info", message, fields, id); this.options.logger?.info(message, fields); if (fields?.direction && typeof fields.text === "string")
198
263
  this.recordMessage(id, fields.direction, fields.text, typeof fields.peer === "string" ? fields.peer : undefined, typeof fields.agentAid === "string" ? fields.agentAid : undefined); },
199
- warn: (message, fields) => (this.options.logger ?? logger).warn(message, fields),
200
- error: (message, fields) => (this.options.logger ?? logger).error(message, fields),
264
+ warn: (message, fields) => { this.log("warn", message, fields, id); (this.options.logger ?? logger).warn(message, fields); },
265
+ error: (message, fields) => { this.log("error", message, fields, id); (this.options.logger ?? logger).error(message, fields); },
201
266
  };
202
267
  await instance.activate({
203
268
  dataDir,
@@ -213,17 +278,27 @@ class PluginManager {
213
278
  ingress: {
214
279
  publish: async (message) => {
215
280
  const target = message.agentPlugin || "qwenwork";
216
- const response = await this.handle(target, { task: message.text, sessionId: `${message.channel}:${message.peerId}` });
217
- await message.reply(response.text);
281
+ this.recordMessage(target, "request", message.text, message.peerId);
282
+ try {
283
+ const response = await this.handle(target, { task: message.text, sessionId: `${message.channel}:${message.peerId}` });
284
+ await message.reply(response.text);
285
+ this.recordMessage(target, "reply", response.text, message.peerId);
286
+ }
287
+ catch (error) {
288
+ this.recordMessage(target, "error", error instanceof Error ? error.message : String(error), message.peerId);
289
+ throw error;
290
+ }
218
291
  },
219
292
  },
220
293
  });
221
294
  loaded.instance = instance;
222
295
  loaded.record.status = "ready";
296
+ this.log("info", `插件已启动:${id}`, { version: loaded.record.version }, "插件生命周期");
223
297
  }
224
298
  catch (error) {
225
299
  loaded.record.status = "failed";
226
300
  loaded.record.error = error instanceof Error ? error.message : String(error);
301
+ this.log("error", `插件启动失败:${id}`, { error: loaded.record.error }, "插件生命周期");
227
302
  throw error;
228
303
  }
229
304
  }
@@ -244,6 +319,8 @@ class PluginManager {
244
319
  else
245
320
  this.unregisterHttpRoutes(id);
246
321
  loaded.record.status = "disabled";
322
+ this.log("info", `插件已停止:${id}`, { reason }, "插件生命周期");
323
+ await this.logs.flush();
247
324
  }
248
325
  async reload(id) { await this.stop(id, "reload"); await this.start(id); }
249
326
  async uninstall(id) {
@@ -263,29 +340,33 @@ class PluginManager {
263
340
  await (0, promises_1.rm)(isManaged ? managedPrefix : path, { recursive: true, force: true });
264
341
  this.plugins.delete(id);
265
342
  }
266
- async saveConfig(id, config) { if (!this.plugins.has(id))
267
- throw new agent_1.AgentError(`unknown plugin: ${id}`); const dir = (0, node_path_1.join)(this.options.stateDir, id); await (0, promises_1.mkdir)(dir, { recursive: true }); await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "config.json"), JSON.stringify(config, null, 2)); }
268
- async setAid(id, aid) { await this.saveConfig(id, { ...await this.config(id), aid }); }
269
- async recordCreatedAid(aid) {
270
- const file = (0, node_path_1.join)(this.options.stateDir, "created-aids.json");
271
- await (0, promises_1.mkdir)(this.options.stateDir, { recursive: true });
272
- let aids = [];
273
- try {
274
- aids = JSON.parse(await (0, promises_1.readFile)(file, "utf8"));
275
- }
276
- catch { /* 首次记录 */ }
277
- if (!aids.includes(aid))
278
- await (0, promises_1.writeFile)(file, JSON.stringify([...aids, aid].sort(), null, 2));
279
- }
280
- async createdAids() {
343
+ async saveConfig(id, config) {
344
+ const loaded = this.plugins.get(id);
345
+ if (!loaded)
346
+ throw new agent_1.AgentError(`unknown plugin: ${id}`);
347
+ await this.validateConfig(loaded.record, config);
348
+ const previous = await this.config(id);
349
+ const dir = (0, node_path_1.join)(this.options.stateDir, id);
350
+ await (0, promises_1.mkdir)(dir, { recursive: true });
351
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "config.json"), JSON.stringify(config, null, 2));
352
+ const action = await this.reloadStrategy(loaded.record);
281
353
  try {
282
- const aids = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, "created-aids.json"), "utf8"));
283
- return Array.isArray(aids) ? aids.filter((aid) => typeof aid === "string") : [];
354
+ if (action === "hot" && loaded.instance && typeof loaded.instance.reloadConfig === "function")
355
+ await loaded.instance.reloadConfig(config);
356
+ else if (action === "reload" && loaded.instance)
357
+ await this.reload(id);
358
+ else if (action === "hot" && loaded.instance)
359
+ throw new agent_1.AgentError("插件未提供 hot 配置重载接口");
360
+ this.log("info", `插件配置已保存:${id}`, { action }, "插件配置");
361
+ return { action, restartRequired: action === "restart" };
284
362
  }
285
- catch {
286
- return [];
363
+ catch (error) {
364
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "config.json"), JSON.stringify(previous, null, 2));
365
+ this.log("error", `插件配置重载失败:${id}`, { action, error: error instanceof Error ? error.message : String(error) }, "插件配置");
366
+ throw error;
287
367
  }
288
368
  }
369
+ async setAid(id, aid) { await this.saveConfig(id, { ...await this.config(id), aid }); }
289
370
  async config(id) { if (!this.plugins.has(id))
290
371
  throw new agent_1.AgentError(`unknown plugin: ${id}`); try {
291
372
  return JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, id, "config.json"), "utf8"));
@@ -293,6 +374,29 @@ class PluginManager {
293
374
  catch {
294
375
  return {};
295
376
  } }
377
+ async reloadStrategy(record) { if (!record.manifest.configSchema)
378
+ return "reload"; try {
379
+ const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(record.path, record.manifest.configSchema), "utf8"));
380
+ const value = schema["x-agent-bridge"]?.reload;
381
+ return value === "hot" || value === "restart" ? value : "reload";
382
+ }
383
+ catch {
384
+ return "reload";
385
+ } }
386
+ async validateConfig(record, config) { if (!config || typeof config !== "object" || Array.isArray(config))
387
+ throw new agent_1.AgentError("插件配置必须是对象"); if (!record.manifest.configSchema)
388
+ return; try {
389
+ const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(record.path, record.manifest.configSchema), "utf8"));
390
+ const { ["x-agent-bridge"]: _metadata, ...validationSchema } = schema;
391
+ const validator = new ajv_1.default({ allErrors: true }).compile(validationSchema);
392
+ if (!validator(config))
393
+ throw new agent_1.AgentError(`插件配置无效:${new ajv_1.default({ allErrors: true }).errorsText(validator.errors)}`);
394
+ }
395
+ catch (error) {
396
+ if (error instanceof agent_1.AgentError)
397
+ throw error;
398
+ throw new agent_1.AgentError(`插件配置校验失败:${error instanceof Error ? error.message : String(error)}`);
399
+ } }
296
400
  async health(id) { const loaded = this.plugins.get(id); if (!loaded)
297
401
  throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
298
402
  return { status: loaded.record.status }; return loaded.instance.health(); }
@@ -450,12 +554,48 @@ class PluginManager {
450
554
  const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)(manifestFile, "utf8")), directory);
451
555
  if (this.plugins.has(manifest.id))
452
556
  return;
453
- this.plugins.set(manifest.id, { record: { id: manifest.id, version: manifest.version, type: manifest.type ?? "other", displayName: manifest.displayName ?? manifest.id, status: "discovered", capabilities: manifest.capabilities, manifest, path: directory } });
557
+ let packageName;
558
+ let publisher;
559
+ let coreConstraint;
560
+ try {
561
+ const packageJson = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
562
+ packageName = packageJson.name;
563
+ publisher = packageJson._npmUser?.name ?? packageJson.maintainers?.[0]?.name;
564
+ coreConstraint = packageJson.agentBridge?.requires?.["mu-agent-bridge"];
565
+ const declared = packageJson.agentBridge?.requires?.plugins;
566
+ if (declared && JSON.stringify(declared) !== JSON.stringify(manifest.dependencies ?? {}))
567
+ throw new agent_1.AgentError("npm 包与插件清单的依赖声明冲突");
568
+ }
569
+ catch (error) {
570
+ if (error instanceof agent_1.AgentError)
571
+ throw error; /* 内置插件没有 npm 元数据 */
572
+ }
573
+ if (coreConstraint) {
574
+ const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
575
+ if (!(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint))
576
+ throw new agent_1.AgentError(`核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}`);
577
+ }
578
+ const trustedNames = new Set((process.env.AGENT_BRIDGE_TRUSTED_PACKAGES ?? "").split(",").map((name) => name.trim()).filter(Boolean));
579
+ const trusted = Boolean(packageName && trustedNames.has(packageName));
580
+ this.plugins.set(manifest.id, { record: { id: manifest.id, version: manifest.version, type: manifest.type ?? "other", displayName: manifest.displayName ?? manifest.id, status: "discovered", capabilities: manifest.capabilities, manifest, path: directory, ...(packageName ? { packageName, publisher, trusted, autoUpdate: trusted } : {}), ...(coreConstraint ? { coreConstraint } : {}) } });
454
581
  }
455
582
  catch (error) {
456
583
  throw new agent_1.AgentError(`invalid plugin ${label}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
457
584
  }
458
585
  }
586
+ checkCompatibility(record) {
587
+ const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
588
+ if (record.coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, record.coreConstraint))
589
+ return { ok: false, reason: `核心版本 ${coreVersion} 不满足插件要求 ${record.coreConstraint}` };
590
+ for (const [id, range] of Object.entries(record.manifest.dependencies ?? {})) {
591
+ const dependency = this.plugins.get(id)?.record;
592
+ if (!dependency)
593
+ return { ok: false, reason: `缺少插件依赖:${id}` };
594
+ if (!(0, version_manager_1.satisfiesVersion)(dependency.version, range))
595
+ return { ok: false, reason: `插件依赖 ${id}@${dependency.version} 不满足 ${range}` };
596
+ }
597
+ return { ok: true };
598
+ }
459
599
  async exists(path) { try {
460
600
  await (0, promises_1.readFile)((0, node_path_1.join)(path, "plugin.json"));
461
601
  return true;
@@ -472,3 +612,4 @@ class PluginManager {
472
612
  } }
473
613
  }
474
614
  exports.PluginManager = PluginManager;
615
+ function isUpdatePolicy(value) { return Boolean(value && typeof value === "object" && ["auto-install", "download-only", "manual"].includes(value.mode)); }
@@ -25,6 +25,10 @@ function validateManifest(value, baseDir) {
25
25
  throw new agent_1.AgentError("插件类型与能力不匹配");
26
26
  if (m.process && m.process !== "in-process" && m.process !== "isolated")
27
27
  throw new agent_1.AgentError("invalid plugin process");
28
+ if (m.managementPage !== undefined && ((m.type ?? inferredType) !== "channel" || m.managementPage !== `/abweb/channels/${m.id}`))
29
+ throw new agent_1.AgentError("插件管理页面路径无效");
30
+ if (m.dependencies && (!m.dependencies || typeof m.dependencies !== "object" || Array.isArray(m.dependencies) || Object.entries(m.dependencies).some(([id, range]) => !/^[a-z][a-z0-9-]{1,63}$/.test(id) || typeof range !== "string" || !range.trim())))
31
+ throw new agent_1.AgentError("插件依赖声明无效");
28
32
  if (baseDir) {
29
33
  const root = (0, node_path_1.resolve)(baseDir);
30
34
  const target = (0, node_path_1.resolve)(root, m.entry);
@@ -32,5 +36,5 @@ function validateManifest(value, baseDir) {
32
36
  if ((0, node_path_1.isAbsolute)(m.entry) || rel.startsWith(`..${node_path_1.sep}`) || rel === "..")
33
37
  throw new agent_1.AgentError("plugin entry path escapes plugin directory");
34
38
  }
35
- return { id: m.id, version: m.version, displayName: m.displayName ?? m.id, type: m.type ?? inferredType, entry: m.entry, apiVersion: m.apiVersion, process: m.process ?? "in-process", capabilities: [...m.capabilities], configSchema: m.configSchema, routes: m.routes };
39
+ return { id: m.id, version: m.version, displayName: m.displayName ?? m.id, type: m.type ?? inferredType, entry: m.entry, apiVersion: m.apiVersion, process: m.process ?? "in-process", capabilities: [...m.capabilities], configSchema: m.configSchema, routes: m.routes, ...(m.dependencies ? { dependencies: { ...m.dependencies } } : {}), ...(m.managementPage ? { managementPage: m.managementPage } : {}) };
36
40
  }
@@ -5,7 +5,7 @@ const node_fs_1 = require("node:fs");
5
5
  const node_path_1 = require("node:path");
6
6
  const MANIFEST = {
7
7
  id: "abweb",
8
- version: "1.0.1",
8
+ version: "1.0.3",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",