mu-agent-bridge 1.0.71 → 1.0.78

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 (195) hide show
  1. package/README.md +17 -10
  2. package/dist/src-ts/agent.js +35 -0
  3. package/dist/src-ts/channel-sessions.js +3 -2
  4. package/dist/src-ts/cli.js +67 -37
  5. package/dist/src-ts/control-server.js +12 -30
  6. package/dist/src-ts/core-updater.js +141 -33
  7. package/dist/src-ts/daemon-process.js +2 -1
  8. package/dist/src-ts/dsh-agent.js +31 -13
  9. package/dist/src-ts/http-agent.js +36 -0
  10. package/dist/src-ts/plugin-catalog.js +11 -2
  11. package/dist/src-ts/plugin-manager.js +351 -50
  12. package/dist/src-ts/plugins/abweb/index.js +1 -1
  13. package/dist/src-ts/plugins/abweb/public/app.js +73 -5
  14. package/dist/src-ts/plugins/abweb/public/index.html +15 -0
  15. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  16. package/dist/src-ts/plugins/aun/attachments.js +3 -21
  17. package/dist/src-ts/plugins/aun/index.js +155 -21
  18. package/dist/src-ts/plugins/aun/messages.js +4 -1
  19. package/dist/src-ts/plugins/aun/public/app.js +2 -1
  20. package/dist/src-ts/plugins/aun/public/index.html +1 -1
  21. package/dist/src-ts/plugins/cdp-draft.js +5 -2
  22. package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  23. package/dist/src-ts/plugins/chatgpt/index.js +80 -19
  24. package/dist/src-ts/plugins/claude-cli/index.js +19 -2
  25. package/dist/src-ts/plugins/codex-cli/index.js +19 -2
  26. package/dist/src-ts/plugins/desktop-launch.js +44 -3
  27. package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  28. package/dist/src-ts/plugins/doubao/index.js +152 -8
  29. package/dist/src-ts/plugins/doubao/native-task.js +134 -0
  30. package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  31. package/dist/src-ts/plugins/dsh/index.js +172 -21
  32. package/dist/src-ts/plugins/dsh/session-history.js +31 -3
  33. package/dist/src-ts/plugins/feishu/index.js +1 -1
  34. package/dist/src-ts/plugins/local-cli/agent.js +74 -9
  35. package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  36. package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  37. package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  38. package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  39. package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  40. package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  41. package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  42. package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  43. package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  44. package/dist/src-ts/plugins/qwenwork/index.js +139 -24
  45. package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  46. package/dist/src-ts/plugins/workbuddy/index.js +124 -41
  47. package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  48. package/dist/src-ts/plugins/zcode/index.js +97 -0
  49. package/dist/src-ts/session-management.js +132 -14
  50. package/dist/src-ts/version-manager.js +6 -3
  51. package/dist/tests-ts/abweb-cli.test.js +10 -2
  52. package/dist/tests-ts/abweb-model-config.test.js +73 -0
  53. package/dist/tests-ts/agent.test.js +27 -0
  54. package/dist/tests-ts/aun-attachments.test.js +19 -17
  55. package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
  56. package/dist/tests-ts/aun-messages.test.js +20 -0
  57. package/dist/tests-ts/aun-plugin.test.js +292 -19
  58. package/dist/tests-ts/bundled-package.test.js +12 -2
  59. package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
  60. package/dist/tests-ts/channel-sessions.test.js +27 -0
  61. package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
  62. package/dist/tests-ts/control-server.test.js +56 -0
  63. package/dist/tests-ts/core-updater.test.js +215 -46
  64. package/dist/tests-ts/daemon-process.test.js +126 -1
  65. package/dist/tests-ts/desktop-launch.test.js +73 -0
  66. package/dist/tests-ts/doubao-native-task.test.js +237 -0
  67. package/dist/tests-ts/doubao-plugin.test.js +773 -12
  68. package/dist/tests-ts/dsh-agent.test.js +157 -8
  69. package/dist/tests-ts/dsh-cdp.test.js +148 -0
  70. package/dist/tests-ts/dsh-history.test.js +37 -0
  71. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  72. package/dist/tests-ts/http-agent.test.js +18 -0
  73. package/dist/tests-ts/installer-assets.test.js +1 -1
  74. package/dist/tests-ts/local-cli.test.js +146 -1
  75. package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
  76. package/dist/tests-ts/native-session-management.test.js +8 -7
  77. package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
  78. package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
  79. package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
  80. package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
  81. package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
  82. package/dist/tests-ts/personal-assistant.test.js +663 -0
  83. package/dist/tests-ts/platform-integration.test.js +183 -1
  84. package/dist/tests-ts/plugin-catalog.test.js +12 -2
  85. package/dist/tests-ts/plugin-manager.test.js +356 -4
  86. package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
  87. package/dist/tests-ts/publish-packages.test.js +42 -0
  88. package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
  89. package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
  90. package/dist/tests-ts/qwenwork-sse.test.js +33 -0
  91. package/dist/tests-ts/session-access-defaults.test.js +112 -0
  92. package/dist/tests-ts/session-management.test.js +159 -4
  93. package/dist/tests-ts/version-manager.test.js +40 -0
  94. package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
  95. package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
  96. package/dist/tests-ts/zcode-cdp.test.js +894 -0
  97. package/dist/tests-ts/zcode-plugin.test.js +396 -0
  98. package/package.json +1 -1
  99. package/plugins/abweb/dist/src-ts/agent.js +35 -0
  100. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  101. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
  102. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
  103. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
  104. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  105. package/plugins/abweb/package.json +1 -1
  106. package/plugins/abweb/plugin.json +1 -1
  107. package/plugins/aun/config.schema.json +1 -1
  108. package/plugins/aun/dist/src-ts/agent.js +35 -0
  109. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
  110. package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
  111. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
  112. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
  113. package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
  114. package/plugins/aun/package.json +1 -1
  115. package/plugins/aun/plugin.json +1 -1
  116. package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
  117. package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
  118. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  119. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
  120. package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
  121. package/plugins/chatgpt/package.json +1 -1
  122. package/plugins/chatgpt/plugin.json +1 -1
  123. package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
  124. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
  125. package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  126. package/plugins/claude-cli/package.json +1 -1
  127. package/plugins/claude-cli/plugin.json +3 -2
  128. package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
  129. package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
  130. package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  131. package/plugins/codex-cli/package.json +1 -1
  132. package/plugins/codex-cli/plugin.json +3 -2
  133. package/plugins/doubao/config.schema.json +2 -1
  134. package/plugins/doubao/dist/src-ts/agent.js +35 -0
  135. package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
  136. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
  137. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
  138. package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
  139. package/plugins/doubao/package.json +1 -1
  140. package/plugins/doubao/plugin.json +3 -2
  141. package/plugins/dsh/config.schema.json +5 -2
  142. package/plugins/dsh/dist/src-ts/agent.js +35 -0
  143. package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
  144. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
  145. package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
  146. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
  147. package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
  148. package/plugins/dsh/package.json +1 -1
  149. package/plugins/dsh/plugin.json +3 -2
  150. package/plugins/feishu/dist/src-ts/agent.js +35 -0
  151. package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
  152. package/plugins/feishu/package.json +1 -1
  153. package/plugins/feishu/plugin.json +1 -1
  154. package/plugins/personal-assistant/config.schema.json +23 -0
  155. package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
  156. package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
  157. package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
  158. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  159. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  160. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
  161. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
  162. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  163. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  164. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
  165. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
  166. package/plugins/personal-assistant/package.json +39 -0
  167. package/plugins/personal-assistant/plugin.json +15 -0
  168. package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
  169. package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
  170. package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
  171. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
  172. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
  173. package/plugins/qwenwork/package.json +1 -1
  174. package/plugins/qwenwork/plugin.json +3 -2
  175. package/plugins/workbuddy/config.schema.json +1 -1
  176. package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
  177. package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
  178. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
  179. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
  180. package/plugins/workbuddy/package.json +1 -1
  181. package/plugins/workbuddy/plugin.json +3 -2
  182. package/plugins/zcode/config.schema.json +13 -0
  183. package/plugins/zcode/dist/src-ts/agent.js +69 -0
  184. package/plugins/zcode/dist/src-ts/attachments.js +62 -0
  185. package/plugins/zcode/dist/src-ts/plugin.js +13 -0
  186. package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
  187. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
  188. package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
  189. package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
  190. package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
  191. package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
  192. package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
  193. package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
  194. package/plugins/zcode/package.json +16 -0
  195. package/plugins/zcode/plugin.json +11 -0
@@ -25,6 +25,7 @@ const desktop_launch_1 = require("./plugins/desktop-launch");
25
25
  const workbuddy_1 = require("./plugins/workbuddy");
26
26
  const logger = { info() { }, warn() { }, error() { } };
27
27
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
28
+ const PLUGIN_UPGRADE_WAIT_MS = 2 * 60 * 60 * 1000;
28
29
  class PluginManager {
29
30
  options;
30
31
  controlUrl;
@@ -34,6 +35,7 @@ class PluginManager {
34
35
  root;
35
36
  messages = [];
36
37
  activeIngress = new Map();
38
+ issuedManagement = new WeakMap();
37
39
  trustConfig = defaultPluginTrustConfig();
38
40
  trustLoaded = false;
39
41
  publisherLookups = new Map();
@@ -110,7 +112,7 @@ class PluginManager {
110
112
  if (meta.name !== packageName || !meta.version || (version && meta.version !== version) || !meta.agentBridge?.plugin)
111
113
  throw new agent_1.AgentError("npm 包声明无效");
112
114
  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);
113
- if (manifest.id !== packageName.slice("agent-bridge-".length) || manifest.version !== meta.version)
115
+ if ((0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version)
114
116
  throw new agent_1.AgentError("npm 包与插件清单不匹配");
115
117
  this.validateCandidate(manifest, meta.agentBridge);
116
118
  this.log("info", `插件下载完成:${manifest.id}`, { packageName, version: meta.version }, "插件升级");
@@ -168,7 +170,8 @@ class PluginManager {
168
170
  return info.packageName ? this.versions.checkPackage(info.packageName, record.version) : Promise.resolve({ packageName: record.id, currentVersion: record.version, state: "latest", checkedAt: new Date().toISOString() });
169
171
  })]);
170
172
  }
171
- async upgradePackage(id, packageName = `agent-bridge-${id}`, version) {
173
+ async upgradePackage(id, packageName, version) {
174
+ packageName ??= this.plugins.get(id)?.record.packageName ?? `agent-bridge-${id}`;
172
175
  const pending = this.upgrades.get(id);
173
176
  if (pending) {
174
177
  if (pending.packageName === packageName && pending.version === version)
@@ -215,11 +218,25 @@ class PluginManager {
215
218
  if (!meta.agentBridge?.plugin)
216
219
  throw new agent_1.AgentError("npm 包未声明 agentBridge.plugin");
217
220
  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);
218
- if (manifest.id !== id || manifest.version !== meta.version)
221
+ if (manifest.id !== id || (0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== id || manifest.version !== meta.version)
219
222
  throw new agent_1.AgentError("npm 包插件 ID 或版本不匹配");
220
223
  this.validateCandidate(manifest, meta.agentBridge);
221
- await this.stop(id, "reload");
222
- stopped = true;
224
+ const deadline = new AbortController();
225
+ const cancellation = loaded.cancellation;
226
+ const error = new agent_1.AgentOperationError("PLUGIN_UPGRADE_WAIT_TIMEOUT", "等待旧插件任务结束", new Error("升级已等待两小时,强制结束桥接任务并继续升级"));
227
+ this.log("info", `插件升级等待旧任务结束:${id}`, { waitMs: PLUGIN_UPGRADE_WAIT_MS, deadline: new Date(Date.now() + PLUGIN_UPGRADE_WAIT_MS).toISOString() }, "插件升级");
228
+ const timer = setTimeout(() => {
229
+ this.log("warn", `插件升级等待超时,强制结束桥接任务:${id}`, { code: error.code, waitMs: PLUGIN_UPGRADE_WAIT_MS }, "插件升级");
230
+ cancellation?.abort(error);
231
+ deadline.abort(error);
232
+ }, PLUGIN_UPGRADE_WAIT_MS);
233
+ try {
234
+ await this.stop(id, "reload", deadline.signal);
235
+ }
236
+ finally {
237
+ clearTimeout(timer);
238
+ stopped = !loaded.instance;
239
+ }
223
240
  if (hadPrefix)
224
241
  await (0, promises_1.rename)(prefix, backup);
225
242
  await (0, promises_1.rename)(staging, prefix);
@@ -255,6 +272,39 @@ class PluginManager {
255
272
  }
256
273
  }
257
274
  get(id) { return this.plugins.get(id); }
275
+ listModelProviders() {
276
+ return [...this.plugins.values()].filter(({ record }) => record.capabilities.includes("model-provider")).map(({ record, instance }) => {
277
+ const available = record.status === "ready" && typeof instance?.modelProvider?.complete === "function";
278
+ return { id: record.id, displayName: record.displayName, status: record.status, available, reason: available ? "" : record.error ?? (record.status === "ready" ? "插件未实现模型来源接口" : `插件当前状态:${record.status}`) };
279
+ });
280
+ }
281
+ async completeModelProvider(id, request) {
282
+ const loaded = this.plugins.get(id);
283
+ if (!loaded)
284
+ throw new agent_1.AgentError(`模型来源插件 ${id} 未安装`);
285
+ if (!loaded.record.capabilities.includes("model-provider"))
286
+ throw new agent_1.AgentError(`插件 ${id} 未声明模型来源能力`);
287
+ if (!loaded.instance || loaded.record.status !== "ready" || typeof loaded.instance.modelProvider?.complete !== "function")
288
+ throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”未就绪:${loaded.record.error ?? (loaded.record.status === "ready" ? "插件未实现模型来源接口" : loaded.record.status)}`);
289
+ if (!request || typeof request.prompt !== "string" || !request.prompt.trim() || Buffer.byteLength(request.prompt) > 1024 * 1024)
290
+ throw new agent_1.AgentError("模型来源请求内容为空或超过 1 MiB");
291
+ if (typeof request.sessionId !== "string" || !request.sessionId.trim() || request.sessionId.length > 512)
292
+ throw new agent_1.AgentError("模型来源会话标识无效");
293
+ if (!Number.isInteger(request.timeoutMs) || request.timeoutMs < 1000 || request.timeoutMs > 120_000)
294
+ throw new agent_1.AgentError("模型来源超时时间无效");
295
+ if (!(request.signal instanceof AbortSignal))
296
+ throw new agent_1.AgentError("模型来源请求缺少取消信号");
297
+ // 模型来源只接收最小调用契约,避免把未来扩展字段误传给桌面或 CLI 插件。
298
+ const result = await this.runPluginTask(loaded, request.signal, signal => loaded.instance.modelProvider.complete({
299
+ prompt: request.prompt,
300
+ sessionId: request.sessionId,
301
+ timeoutMs: request.timeoutMs,
302
+ signal,
303
+ }));
304
+ if (!result || typeof result.text !== "string" || !result.text.trim() || Buffer.byteLength(result.text) > 1024 * 1024)
305
+ throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”返回空内容或超大响应`);
306
+ return { text: result.text };
307
+ }
258
308
  recentMessages(id) { return this.messages.filter((item) => !id || item.pluginId === id).slice(-200).reverse(); }
259
309
  recordMessage(pluginId, direction, text, peer, agentAid) {
260
310
  if (direction === "error" && isNoisyAgentError(text))
@@ -267,8 +317,9 @@ class PluginManager {
267
317
  if (!/^agent-bridge-[a-z0-9-]+(?:@[0-9][a-zA-Z0-9.+-]*)?$/.test(packageName))
268
318
  throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
269
319
  const name = packageName.replace(/@.*$/, "");
270
- if (this.plugins.has(name.replace(/^agent-bridge-/, "")))
271
- throw new agent_1.AgentError("插件已经存在");
320
+ const version = packageName.slice(name.length + 1);
321
+ if (this.list().some((record) => record.packageName === name))
322
+ throw new agent_1.AgentError("插件已经存在,请使用升级操作");
272
323
  const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", name);
273
324
  if (await this.existsDir(prefix))
274
325
  throw new agent_1.AgentError("插件包已经安装");
@@ -283,12 +334,16 @@ class PluginManager {
283
334
  const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
284
335
  if (meta.name !== name || !meta.version || !meta.agentBridge?.plugin)
285
336
  throw new agent_1.AgentError("npm 包名称或插件声明无效");
337
+ if (version && meta.version !== version)
338
+ throw new agent_1.AgentError("npm 包版本与请求不匹配");
286
339
  const manifestFile = (0, node_path_1.resolve)(directory, meta.agentBridge.plugin);
287
340
  if (!manifestFile.startsWith(`${(0, node_path_1.resolve)(directory)}${node_path_1.sep}`))
288
341
  throw new agent_1.AgentError("插件清单路径越界");
289
342
  const manifest = (0, plugin_manifest_1.validateManifest)(JSON.parse(await (0, promises_1.readFile)(manifestFile, "utf8")), directory);
290
- if (manifest.id !== name.slice("agent-bridge-".length) || manifest.version !== meta.version || (meta.agentBridge.type && meta.agentBridge.type !== manifest.type))
343
+ if ((0, plugin_catalog_1.resolvePluginId)(name, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version || (meta.agentBridge.type && meta.agentBridge.type !== manifest.type))
291
344
  throw new agent_1.AgentError("npm 包与插件清单不匹配");
345
+ if (this.plugins.has(manifest.id))
346
+ throw new agent_1.AgentError("插件已经存在;不同 npm 来源不能覆盖,请先显式处理现有插件");
292
347
  this.validateCandidate(manifest, meta.agentBridge);
293
348
  await this.discoverDirectory(directory, name, manifestFile);
294
349
  await this.resolvePendingPublisherLookups();
@@ -326,7 +381,7 @@ class PluginManager {
326
381
  }
327
382
  if (loaded.instance) {
328
383
  try {
329
- await loaded.instance.deactivate("reload");
384
+ await loaded.instance.deactivate("reload", this.upgrades.has(id) ? { preserveDesktop: true } : undefined);
330
385
  }
331
386
  catch (error) {
332
387
  this.log("warn", `插件恢复前清理失败:${id}`, { error: error instanceof Error ? error.message : String(error) }, "插件生命周期");
@@ -356,14 +411,12 @@ class PluginManager {
356
411
  let instance;
357
412
  try {
358
413
  instance = await this.load(loaded.record);
414
+ const cancellation = loaded.cancellation = new AbortController();
415
+ loaded.tasks = new Set();
416
+ const http = loaded.http = this.createHttpRegistry(id);
359
417
  const dataDir = (0, node_path_1.join)(this.options.stateDir, id);
360
418
  await (0, promises_1.mkdir)(dataDir, { recursive: true });
361
- const configPath = (0, node_path_1.join)(dataDir, "config.json");
362
- let config = {};
363
- try {
364
- config = JSON.parse(await (0, promises_1.readFile)(configPath, "utf8"));
365
- }
366
- catch { /* defaults */ }
419
+ const config = await this.config(id);
367
420
  const pluginLogger = {
368
421
  info: (message, fields) => { this.log("info", message, fields, id); this.options.logger?.info(message, fields); if (fields?.direction && typeof fields.text === "string")
369
422
  this.recordMessage(id, fields.direction, fields.text, typeof fields.peer === "string" ? fields.peer : undefined, typeof fields.agentAid === "string" ? fields.agentAid : undefined); },
@@ -374,15 +427,33 @@ class PluginManager {
374
427
  dataDir,
375
428
  config,
376
429
  logger: pluginLogger,
377
- http: this.createHttpRegistry(id),
430
+ http,
431
+ modelInputGuard: true,
378
432
  callPlugin: async (targetId, request) => {
379
433
  if (targetId === id)
380
434
  throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
381
- return this.handle(targetId, request);
435
+ if (request.management)
436
+ throw new agent_1.AgentError("插件间调用不能传递管理能力");
437
+ cancellation.signal.throwIfAborted();
438
+ if (loaded.cancellation !== cancellation || !loaded.instance)
439
+ throw new agent_1.AgentError("插件实例已停止,旧调用已失效");
440
+ return this.handle(targetId, { ...request, signal: request.signal ? AbortSignal.any([request.signal, cancellation.signal]) : cancellation.signal });
441
+ },
442
+ callModelProvider: async (targetId, request) => {
443
+ if (targetId === id)
444
+ throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
445
+ cancellation.signal.throwIfAborted();
446
+ if (loaded.cancellation !== cancellation || !loaded.instance)
447
+ throw new agent_1.AgentError("插件实例已停止,旧调用已失效");
448
+ return this.completeModelProvider(targetId, { ...request, signal: AbortSignal.any([request.signal, cancellation.signal]) });
382
449
  },
383
450
  pluginStatus: (targetId) => this.plugins.get(targetId)?.record.status,
384
451
  ingress: {
385
- publish: async (message) => {
452
+ publish: message => this.runPluginTask(loaded, undefined, async (signal) => {
453
+ if (loaded.cancellation !== cancellation || loaded.instance !== instance)
454
+ throw new agent_1.AgentError("通道插件实例已停止,旧调用已失效");
455
+ if (loaded.record.status !== "ready")
456
+ throw new agent_1.AgentError("通道插件正在停止或尚未就绪,不再接收新任务");
386
457
  if (message.channel !== id)
387
458
  throw new agent_1.AgentError("消息来源与通道插件身份不一致");
388
459
  const trustedChannel = loaded.record.type === "channel" && (id === "aun" || id === "feishu");
@@ -396,7 +467,8 @@ class PluginManager {
396
467
  throw new agent_1.AgentError("当前通道不支持会话管理");
397
468
  const current = await this.sessions.current(message, target);
398
469
  const result = await this.sessionManagement.command(message.text, { source: current.source, sessionId: current.logicalSessionId });
399
- await message.reply(result);
470
+ signal.throwIfAborted();
471
+ await (0, agent_1.abortable)(message.reply(result, signal), signal);
400
472
  return;
401
473
  }
402
474
  if (trustedChannel)
@@ -407,30 +479,51 @@ class PluginManager {
407
479
  if (trustedChannel)
408
480
  this.activeIngress.set(source, (this.activeIngress.get(source) ?? 0) + 1);
409
481
  try {
410
- const instance = this.plugins.get(target)?.instance;
411
- const control = instance?.sessionOperations?.includes("new")
412
- ? (request) => this.controlSession(target, request)
482
+ const agentInstance = this.plugins.get(target)?.instance;
483
+ const control = agentInstance?.sessionOperations?.includes("new")
484
+ ? (request) => this.controlSession(target, request, signal)
413
485
  : undefined;
414
486
  const task = !message.text.trim() && message.attachments?.length ? "请处理收到的附件" : message.text;
415
487
  const forwarded = new Set();
416
488
  const sent = [];
417
489
  const onMessage = async (text) => {
490
+ signal.throwIfAborted();
418
491
  if (!text.trim() || forwarded.has(text))
419
492
  return;
420
- await message.reply(text);
493
+ await (0, agent_1.abortable)(message.reply(text, signal), signal);
494
+ signal.throwIfAborted();
421
495
  forwarded.add(text);
422
496
  sent.push({ text });
423
497
  this.recordMessage(target, "reply", text, message.peerId);
424
498
  };
425
- const response = await this.sessions.run(message, target, (sessionId, newSession) => this.handle(target, { task, sessionId, onMessage, ...(newSession ? { newSession: true } : {}), ...(message.attachments ? { attachments: message.attachments } : {}) }), control);
499
+ const response = await this.sessions.run(message, target, async (sessionId, newSession, logicalSessionId) => {
500
+ const targetRecord = this.plugins.get(target)?.record;
501
+ const config = targetRecord?.type === "agent" && targetRecord.capabilities.includes("management") ? await this.config(target) : undefined;
502
+ const ability = trustedChannel && id === "aun" && loaded.instance === instance && loaded.record.status === "ready" && Array.isArray(config?.allowedPeers) && (config.allowedPeers.length === 0 || config.allowedPeers.includes("*") || config.allowedPeers.includes(message.peerId))
503
+ ? this.createRequestManagement(target, message.peerId, source, logicalSessionId, { signal, onMessage, ...(message.replyAttachments ? { attachmentOutput: true } : {}) }) : undefined;
504
+ signal.throwIfAborted();
505
+ const request = { task, sessionId, signal, onMessage, ...(newSession ? { newSession: true } : {}), ...(message.attachments ? { attachments: message.attachments } : {}), ...(message.replyAttachments ? { attachmentOutput: true } : {}), ...(ability ? { management: ability } : {}) };
506
+ const issued = ability ? this.issuedManagement.get(ability) : undefined;
507
+ if (issued)
508
+ issued.active = true;
509
+ try {
510
+ return await this.handle(target, request);
511
+ }
512
+ finally {
513
+ if (issued)
514
+ issued.active = false;
515
+ }
516
+ }, control);
517
+ signal.throwIfAborted();
426
518
  const attachments = response.attachments?.length ? response.attachments : (0, attachments_1.asAttachments)(response.images);
427
519
  (0, attachments_1.validateAttachments)(attachments);
428
520
  if (attachments.length) {
429
521
  if (!message.replyAttachments) {
430
- await message.reply("当前通道不支持附件回传");
522
+ await (0, agent_1.abortable)(message.reply("当前通道不支持附件回传", signal), signal);
431
523
  throw new agent_1.AgentError("当前通道不支持附件回传");
432
524
  }
433
- await message.replyAttachments(attachments, response.text);
525
+ await (0, agent_1.abortable)(message.replyAttachments(attachments, response.text, signal), signal);
526
+ signal.throwIfAborted();
434
527
  sent.push({ text: response.text, attachments });
435
528
  this.recordMessage(target, "reply", response.text, message.peerId);
436
529
  }
@@ -462,7 +555,7 @@ class PluginManager {
462
555
  else
463
556
  this.activeIngress.delete(source);
464
557
  }
465
- },
558
+ }),
466
559
  },
467
560
  runtime: { controlUrl: this.controlUrl, ports: {
468
561
  qwenwork: Number(process.env.AGENT_BRIDGE_QWEN_CDP_PORT ?? process.env.QWEN_CDP_PORT ?? "9228"),
@@ -478,13 +571,15 @@ class PluginManager {
478
571
  catch (error) {
479
572
  if (instance) {
480
573
  try {
481
- await instance.deactivate("reload");
574
+ await instance.deactivate("reload", this.upgrades.has(id) ? { preserveDesktop: true } : undefined);
482
575
  }
483
576
  catch (cleanupError) {
484
577
  this.log("warn", `插件启动失败后的清理失败:${id}`, { error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError) }, "插件生命周期");
485
578
  }
486
579
  this.unregisterHttpRoutes(id);
487
580
  }
581
+ loaded.http?.unregister();
582
+ loaded.cancellation?.abort(new agent_1.AgentError("插件启动失败,调用已取消"));
488
583
  loaded.instance = undefined;
489
584
  loaded.record.status = "failed";
490
585
  loaded.record.error = error instanceof Error ? error.message : String(error);
@@ -492,17 +587,44 @@ class PluginManager {
492
587
  throw error;
493
588
  }
494
589
  }
495
- async stop(id, reason = "stop") {
590
+ async stop(id, reason = "stop", deadline) {
496
591
  const loaded = this.plugins.get(id);
497
592
  if (!loaded)
498
593
  throw new agent_1.AgentError(`unknown plugin: ${id}`);
499
594
  if (loaded.instance) {
500
595
  loaded.record.status = "stopping";
596
+ const instance = loaded.instance;
597
+ const stopping = Promise.resolve().then(async () => {
598
+ if (deadline)
599
+ await Promise.allSettled([...(loaded.tasks ?? [])]);
600
+ await instance.deactivate(reason, deadline ? { preserveDesktop: true } : undefined);
601
+ });
501
602
  try {
502
- await loaded.instance.deactivate(reason);
603
+ try {
604
+ await (0, agent_1.abortable)(stopping, deadline);
605
+ }
606
+ catch (error) {
607
+ if (!deadline?.aborted)
608
+ throw error;
609
+ let cleanupTimer;
610
+ try {
611
+ const cleaned = await Promise.race([
612
+ stopping.then(() => true, () => false),
613
+ new Promise(resolve => { cleanupTimer = setTimeout(() => resolve(false), 5_000); }),
614
+ ]);
615
+ if (!cleaned)
616
+ this.log("warn", `旧插件未完成清理,已隔离并继续升级:${id}`, { code: "PLUGIN_UPGRADE_CLEANUP_TIMEOUT" }, "插件升级");
617
+ }
618
+ finally {
619
+ clearTimeout(cleanupTimer);
620
+ }
621
+ }
503
622
  }
504
623
  finally {
624
+ loaded.http?.unregister();
505
625
  this.unregisterHttpRoutes(id);
626
+ if (deadline)
627
+ loaded.cancellation?.abort(new agent_1.AgentError("插件已停止,旧实例调用已取消"));
506
628
  loaded.instance = undefined;
507
629
  }
508
630
  }
@@ -583,13 +705,31 @@ class PluginManager {
583
705
  }
584
706
  }
585
707
  async setAid(id, aid) { await this.saveConfig(id, { ...await this.config(id), aid }); }
586
- async config(id) { if (!this.plugins.has(id))
587
- throw new agent_1.AgentError(`unknown plugin: ${id}`); try {
588
- return JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, id, "config.json"), "utf8"));
708
+ async config(id) {
709
+ const loaded = this.plugins.get(id);
710
+ if (!loaded)
711
+ throw new agent_1.AgentError(`unknown plugin: ${id}`);
712
+ let defaults = {};
713
+ if (loaded.record.manifest.configSchema) {
714
+ try {
715
+ const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(loaded.record.path, loaded.record.manifest.configSchema), "utf8"));
716
+ if (schema.default && typeof schema.default === "object" && !Array.isArray(schema.default))
717
+ defaults = schema.default;
718
+ }
719
+ catch { /* 默认声明不可读时保留已有配置 */ }
720
+ }
721
+ try {
722
+ const saved = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(this.options.stateDir, id, "config.json"), "utf8"));
723
+ if (!saved || typeof saved !== "object" || Array.isArray(saved))
724
+ throw new agent_1.AgentError("插件配置必须是对象");
725
+ return { ...defaults, ...saved };
726
+ }
727
+ catch (error) {
728
+ if (error.code === "ENOENT")
729
+ return defaults;
730
+ throw new agent_1.AgentError(`插件 ${id} 的配置无法读取或格式无效`);
731
+ }
589
732
  }
590
- catch {
591
- return {};
592
- } }
593
733
  async reloadStrategy(record) { if (!record.manifest.configSchema)
594
734
  return "reload"; try {
595
735
  const schema = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.resolve)(record.path, record.manifest.configSchema), "utf8"));
@@ -620,9 +760,143 @@ class PluginManager {
620
760
  ...await (0, agent_availability_1.agentAvailability)(plugin.id, await this.config(plugin.id), plugin.status),
621
761
  })));
622
762
  }
763
+ createRequestManagement(pluginId, peerId, source, logicalSessionId, output) {
764
+ const issued = { pluginId, instance: this.plugins.get(pluginId)?.instance, active: false };
765
+ const cancellation = this.plugins.get(pluginId)?.cancellation;
766
+ const checkActive = () => {
767
+ const current = this.plugins.get(pluginId);
768
+ if (!issued.active || !current?.instance || current.instance !== issued.instance)
769
+ throw new agent_1.AgentError("管理能力请求已结束或已撤销");
770
+ cancellation?.signal.throwIfAborted();
771
+ };
772
+ const allowed = async (agentId) => {
773
+ checkActive();
774
+ const loaded = this.plugins.get(pluginId);
775
+ if (!loaded?.instance || (loaded.record.status !== "ready" && !(loaded.record.status === "stopping" && this.upgrades.has(pluginId))) || !loaded.record.capabilities.includes("management"))
776
+ throw new agent_1.AgentError("当前插件未声明管理能力或未就绪");
777
+ const config = await this.config(pluginId);
778
+ checkActive();
779
+ const peers = config.allowedPeers;
780
+ if (!Array.isArray(peers) || (peers.length > 0 && !peers.includes("*") && !peers.includes(peerId)))
781
+ throw new agent_1.AgentError("当前 AUN 身份无权使用管理能力");
782
+ if (agentId !== undefined) {
783
+ const agents = config.allowedAgents;
784
+ if (!Array.isArray(agents) || (agents.length > 0 && !agents.includes("*") && !agents.includes(agentId)))
785
+ throw new agent_1.AgentError("目标 Agent 不在允许列表中");
786
+ }
787
+ return { loaded, config };
788
+ };
789
+ const management = {
790
+ listAgents: async () => {
791
+ const { config } = await allowed();
792
+ const aun = this.plugins.get("aun");
793
+ let bindings = {};
794
+ let identitiesAvailable = false;
795
+ try {
796
+ const health = aun?.record.status === "ready" ? await aun.instance?.health() : undefined;
797
+ const details = health?.details;
798
+ if (health?.status === "ready" && details && typeof details === "object" && !Array.isArray(details)) {
799
+ const raw = details.bindings;
800
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
801
+ bindings = raw;
802
+ identitiesAvailable = true;
803
+ }
804
+ }
805
+ }
806
+ catch { /* AUN 状态不可用时只返回 Agent 能力 */ }
807
+ const result = [];
808
+ const agents = this.list().filter(item => item.type === "agent");
809
+ const agentIds = new Set(agents.map(item => item.id));
810
+ for (const record of agents) {
811
+ const agentConfig = await this.config(record.id);
812
+ const current = this.plugins.get(record.id);
813
+ const currentRecord = current?.record ?? record;
814
+ const availability = await (0, agent_availability_1.agentAvailability)(record.id, agentConfig, currentRecord.status);
815
+ const latest = this.plugins.get(record.id);
816
+ const latestRecord = latest?.record ?? currentRecord;
817
+ const aids = identitiesAvailable
818
+ ? Object.entries(bindings).filter(([aid, bound]) => /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/.test(aid) && agentIds.has(bound) && bound === record.id).map(([aid]) => aid).sort()
819
+ : [];
820
+ const callable = Boolean(latest?.instance) && latestRecord.type === "agent" && latestRecord.id !== pluginId && latestRecord.status === "ready" && availability.available && !latestRecord.capabilities.includes("management") && (Array.isArray(config.allowedAgents) && (config.allowedAgents.length === 0 || config.allowedAgents.includes("*") || config.allowedAgents.includes(record.id)));
821
+ result.push({ id: latestRecord.id, displayName: latestRecord.displayName, status: latestRecord.status, ...availability, reason: !identitiesAvailable && !availability.reason ? "AUN 身份数据不可用" : availability.reason, capabilities: [...latestRecord.capabilities], aids, callable });
822
+ }
823
+ await allowed();
824
+ return result;
825
+ },
826
+ callAgent: async (id, task) => {
827
+ const { loaded } = await allowed(id);
828
+ if (typeof task !== "string" || !task.trim() || task.length > 20_000)
829
+ throw new agent_1.AgentError("Agent 调用内容无效");
830
+ const target = this.plugins.get(id);
831
+ if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready" || target.record.capabilities.includes("management") || id === pluginId)
832
+ throw new agent_1.AgentError("目标 Agent 不可调用");
833
+ if (!(await (0, agent_availability_1.agentAvailability)(id, await this.config(id), target.record.status)).available)
834
+ throw new agent_1.AgentError("目标 Agent 当前不可用");
835
+ await allowed(id);
836
+ // shortcut: 仅 WorkBuddy 已验证未知逻辑会话 ID 可安全建会话,其他插件验证后再扩展。
837
+ const sessionId = id === "workbuddy" && target.instance?.sessionOperations?.includes("new")
838
+ ? `management-${(0, node_crypto_1.createHash)("sha256").update(`${logicalSessionId}\0${id}`).digest("hex")}`
839
+ : undefined;
840
+ const signal = output?.signal ? AbortSignal.any([output.signal, cancellation.signal]) : cancellation.signal;
841
+ const response = await this.handle(id, { task, ...output, signal, ...(sessionId ? { sessionId } : {}) });
842
+ if (loaded.instance !== this.plugins.get(pluginId)?.instance)
843
+ throw new agent_1.AgentError("管理插件已重载");
844
+ await allowed(id);
845
+ return response;
846
+ },
847
+ listAunIdentities: async () => {
848
+ await allowed();
849
+ const aun = this.plugins.get("aun")?.instance;
850
+ let result;
851
+ if (aun?.aunManagement)
852
+ result = await aun.aunManagement.list();
853
+ else if (aun?.identity) {
854
+ // 旧 AUN 只有身份列表和健康绑定;兼容只读查询,不开放创建或绑定操作。
855
+ const groups = await aun.identity.list();
856
+ const health = await aun.health();
857
+ const aidPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{2,255}$/;
858
+ const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? [])])].filter(aid => aidPattern.test(aid)).sort();
859
+ const raw = health.details?.bindings;
860
+ const bindings = raw && typeof raw === "object" && !Array.isArray(raw)
861
+ ? Object.fromEntries(Object.entries(raw).filter(([aid, id]) => aidPattern.test(aid) && typeof id === "string" && /^[a-z0-9][a-z0-9-]{0,63}$/.test(id))) : {};
862
+ result = { aids, bindings };
863
+ }
864
+ else
865
+ throw new agent_1.AgentError("AUN 身份管理不可用");
866
+ await allowed();
867
+ return result;
868
+ },
869
+ createAunIdentity: async (input) => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
870
+ throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.create(input); await allowed(); return result; },
871
+ bindAunIdentity: async (input) => { await allowed(input.agentId); const target = this.plugins.get(input.agentId); if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready")
872
+ throw new agent_1.AgentError("只能关联已就绪的 Agent 插件"); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
873
+ throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.bind(input); await allowed(input.agentId); return result; },
874
+ runSessionCommand: async (command) => { await allowed(); if (typeof command !== "string" || command.length > 2_000 || !/^\/sessions(?:\s|$)/.test(command.trimStart()))
875
+ throw new agent_1.AgentError("仅支持 /sessions 只读命令"); const result = await this.sessionManagement.command(command, { source, sessionId: logicalSessionId }); await allowed(); if (result === undefined)
876
+ throw new agent_1.AgentError("不支持的 /sessions 命令"); return result; },
877
+ summarizeToday: async () => { await allowed(); const result = await this.sessionManagement.summarizeToday({ source, sessionId: logicalSessionId }); await allowed(); return result; },
878
+ };
879
+ Object.freeze(management);
880
+ this.issuedManagement.set(management, issued);
881
+ return management;
882
+ }
623
883
  async health(id) { const loaded = this.plugins.get(id); if (!loaded)
624
884
  throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
625
885
  return { status: loaded.record.status }; return loaded.instance.health(); }
886
+ async runPluginTask(loaded, upstream, work) {
887
+ const local = loaded.cancellation.signal;
888
+ const signal = upstream ? AbortSignal.any([upstream, local]) : local;
889
+ signal.throwIfAborted();
890
+ const task = (0, agent_1.abortable)(work(signal), signal);
891
+ const tasks = loaded.tasks;
892
+ tasks.add(task);
893
+ try {
894
+ return await task;
895
+ }
896
+ finally {
897
+ tasks.delete(task);
898
+ }
899
+ }
626
900
  async handle(id, request) {
627
901
  const loaded = this.plugins.get(id);
628
902
  if (!loaded?.instance || loaded.record.status !== "ready") {
@@ -632,20 +906,32 @@ class PluginManager {
632
906
  const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
633
907
  throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
634
908
  }
909
+ if (request.modelResponseSchema !== undefined && !loaded.instance.modelInput)
910
+ throw new agent_1.AgentError("目标插件不支持无工具模型请求");
911
+ if (request.management) {
912
+ const issued = this.issuedManagement.get(request.management);
913
+ if (!issued || !issued.active || issued.pluginId !== id || issued.instance !== loaded.instance)
914
+ throw new agent_1.AgentError("管理能力无效或已撤销");
915
+ }
635
916
  (0, attachments_1.validateAttachments)(request.attachments);
636
917
  if (request.attachments?.length && !loaded.instance.attachmentInput)
637
918
  throw new agent_1.AgentError(`插件 ${id} 不支持接收附件`);
638
- return loaded.instance.handle(request);
919
+ const instance = loaded.instance;
920
+ return this.runPluginTask(loaded, request.signal, signal => instance.handle({
921
+ ...request, signal,
922
+ ...(request.onMessage ? { onMessage: async (text) => { signal.throwIfAborted(); await request.onMessage(text); } } : {}),
923
+ }));
639
924
  }
640
- async controlSession(id, request) {
925
+ async controlSession(id, request, signal) {
641
926
  const loaded = this.plugins.get(id);
642
927
  if (!loaded?.instance || loaded.record.status !== "ready")
643
928
  throw new agent_1.AgentError(`插件 ${id} 未就绪,无法控制会话`);
644
929
  if (!loaded.instance.sessionOperations?.includes(request.operation))
645
930
  throw new agent_1.AgentError(`插件 ${id} 不支持新建会话`);
646
- const response = loaded.instance.controlSession
647
- ? await loaded.instance.controlSession(request)
648
- : { text: `${loaded.record.displayName} 已创建新会话`, sessionId: request.sessionId };
931
+ const instance = loaded.instance;
932
+ const response = await this.runPluginTask(loaded, signal, async () => instance.controlSession
933
+ ? instance.controlSession(request)
934
+ : { text: `${loaded.record.displayName} 已创建新会话`, sessionId: request.sessionId });
649
935
  if (typeof response.sessionId !== "string" || !response.sessionId.trim())
650
936
  throw new agent_1.AgentError("插件未返回有效的新会话标识,原会话绑定保持不变");
651
937
  return response;
@@ -663,18 +949,25 @@ class PluginManager {
663
949
  return true;
664
950
  }
665
951
  createHttpRegistry(pluginId) {
952
+ const disposers = new Set();
953
+ let closed = false;
954
+ const check = () => { if (closed)
955
+ throw new agent_1.AgentError("插件 HTTP 注册表已停止"); };
956
+ const remember = (dispose) => { disposers.add(dispose); return () => { dispose(); disposers.delete(dispose); }; };
666
957
  return {
667
958
  register: (routeOrMethod, pathOrHandler, maybeHandler) => {
959
+ check();
668
960
  if (typeof routeOrMethod === "object")
669
- return this.registerHttpRoute(pluginId, routeOrMethod.method, routeOrMethod.path, routeOrMethod.handler);
961
+ return remember(this.registerHttpRoute(pluginId, routeOrMethod.method, routeOrMethod.path, routeOrMethod.handler));
670
962
  if (typeof pathOrHandler === "string" && maybeHandler)
671
- return this.registerHttpRoute(pluginId, routeOrMethod, pathOrHandler, maybeHandler);
963
+ return remember(this.registerHttpRoute(pluginId, routeOrMethod, pathOrHandler, maybeHandler));
672
964
  if (typeof pathOrHandler === "function")
673
- return this.registerLegacyRoute(pluginId, routeOrMethod, pathOrHandler);
965
+ return remember(this.registerLegacyRoute(pluginId, routeOrMethod, pathOrHandler));
674
966
  throw new agent_1.AgentError("invalid plugin HTTP route");
675
967
  },
676
- registerRoute: (path, handler) => this.registerLegacyRoute(pluginId, path, handler),
677
- unregister: () => this.unregisterHttpRoutes(pluginId),
968
+ registerRoute: (path, handler) => { check(); return remember(this.registerLegacyRoute(pluginId, path, handler)); },
969
+ unregister: () => { closed = true; for (const dispose of disposers)
970
+ dispose(); disposers.clear(); },
678
971
  };
679
972
  }
680
973
  registerHttpRoute(pluginId, method, path, handler) {
@@ -686,8 +979,9 @@ class PluginManager {
686
979
  const existing = this.httpRoutes.get(key);
687
980
  if (existing)
688
981
  throw new agent_1.AgentError(`HTTP route already registered: ${normalizedMethod} ${normalizedPath}`);
689
- this.httpRoutes.set(key, { pluginId, method: normalizedMethod, path: normalizedPath, handler });
690
- return () => { if (this.httpRoutes.get(key)?.pluginId === pluginId)
982
+ const route = { pluginId, method: normalizedMethod, path: normalizedPath, handler };
983
+ this.httpRoutes.set(key, route);
984
+ return () => { if (this.httpRoutes.get(key) === route)
691
985
  this.httpRoutes.delete(key); };
692
986
  }
693
987
  registerLegacyRoute(pluginId, path, handler) {
@@ -708,8 +1002,9 @@ class PluginManager {
708
1002
  response.setHeader(name, value);
709
1003
  response.end(result?.body ?? "");
710
1004
  };
711
- this.httpRoutes.set(key, { pluginId, method: "*", path: normalizedPath, handler: nativeHandler });
712
- return () => { if (this.httpRoutes.get(key)?.pluginId === pluginId)
1005
+ const route = { pluginId, method: "*", path: normalizedPath, handler: nativeHandler };
1006
+ this.httpRoutes.set(key, route);
1007
+ return () => { if (this.httpRoutes.get(key) === route)
713
1008
  this.httpRoutes.delete(key); };
714
1009
  }
715
1010
  unregisterHttpRoutes(pluginId) { for (const [key, route] of this.httpRoutes)
@@ -812,6 +1107,8 @@ class PluginManager {
812
1107
  try {
813
1108
  const packageJson = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
814
1109
  packageName = packageJson.name;
1110
+ if (packageName && (packageName.startsWith("agent-bridge-") || packageJson.agentBridge?.id !== undefined) && (0, plugin_catalog_1.resolvePluginId)(packageName, packageJson.agentBridge?.id) !== manifest.id)
1111
+ throw new agent_1.AgentError("npm 包声明的插件 ID 与清单不一致");
815
1112
  coreConstraint = packageJson.agentBridge?.requires?.["mu-agent-bridge"];
816
1113
  const declared = packageJson.agentBridge?.requires?.plugins;
817
1114
  if (declared && JSON.stringify(declared) !== JSON.stringify(manifest.dependencies ?? {}))
@@ -824,6 +1121,10 @@ class PluginManager {
824
1121
  const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
825
1122
  const error = coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint) ? `核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}` : undefined;
826
1123
  const record = { id: manifest.id, version: manifest.version, type: manifest.type ?? "other", displayName: manifest.displayName ?? manifest.id, status: error ? "failed" : "discovered", capabilities: manifest.capabilities, manifest, path: directory, ...(packageName ? { packageName } : {}), ...(coreConstraint ? { coreConstraint } : {}), ...(error ? { error } : {}) };
1124
+ if (existing && existing.record.packageName && packageName && existing.record.packageName !== packageName) {
1125
+ this.log("warn", "插件 npm 来源冲突,保留现有来源", { id: manifest.id, existingPackageName: existing.record.packageName, ignoredPackageName: packageName }, "插件发现");
1126
+ return;
1127
+ }
827
1128
  if (existing) {
828
1129
  const versionOrder = (0, version_manager_1.compareVersions)(record.version, existing.record.version);
829
1130
  const candidateManaged = (0, node_path_1.resolve)(directory).startsWith(`${(0, node_path_1.resolve)(this.options.stateDir, "npm")}${node_path_1.sep}`);
@@ -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.11",
8
+ version: "1.0.15",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",