mu-agent-bridge 1.0.70 → 1.0.73

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 (128) hide show
  1. package/README.md +9 -6
  2. package/dist/src-ts/agent.js +35 -0
  3. package/dist/src-ts/channel-sessions.js +3 -2
  4. package/dist/src-ts/control-server.js +5 -2
  5. package/dist/src-ts/dsh-agent.js +31 -15
  6. package/dist/src-ts/dsh-paths.js +7 -7
  7. package/dist/src-ts/plugin-catalog.js +11 -2
  8. package/dist/src-ts/plugin-manager.js +183 -9
  9. package/dist/src-ts/plugins/abweb/index.js +1 -1
  10. package/dist/src-ts/plugins/abweb/public/app.js +68 -2
  11. package/dist/src-ts/plugins/abweb/public/index.html +15 -0
  12. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  13. package/dist/src-ts/plugins/aun/evolcore.js +93 -17
  14. package/dist/src-ts/plugins/aun/index.js +77 -12
  15. package/dist/src-ts/plugins/aun/messages.js +3 -0
  16. package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  17. package/dist/src-ts/plugins/chatgpt/index.js +77 -17
  18. package/dist/src-ts/plugins/claude-cli/index.js +19 -2
  19. package/dist/src-ts/plugins/codex-cli/index.js +19 -2
  20. package/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
  21. package/dist/src-ts/plugins/doubao/index.js +36 -4
  22. package/dist/src-ts/plugins/dsh/index.js +82 -7
  23. package/dist/src-ts/plugins/local-cli/agent.js +74 -9
  24. package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  25. package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  26. package/dist/src-ts/plugins/personal-assistant/index.js +242 -0
  27. package/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
  28. package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  29. package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  30. package/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
  31. package/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
  32. package/dist/src-ts/plugins/qwenwork/index.js +89 -8
  33. package/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
  34. package/dist/src-ts/plugins/workbuddy/index.js +104 -33
  35. package/dist/src-ts/session-management.js +105 -6
  36. package/dist/tests-ts/abweb-cli.test.js +10 -2
  37. package/dist/tests-ts/abweb-model-config.test.js +70 -0
  38. package/dist/tests-ts/agent.test.js +27 -0
  39. package/dist/tests-ts/aun-messages.test.js +6 -0
  40. package/dist/tests-ts/aun-plugin.test.js +149 -0
  41. package/dist/tests-ts/bundled-package.test.js +15 -3
  42. package/dist/tests-ts/channel-sessions.test.js +27 -0
  43. package/dist/tests-ts/chatgpt-plugin.test.js +99 -0
  44. package/dist/tests-ts/control-server.test.js +88 -2
  45. package/dist/tests-ts/doubao-plugin.test.js +92 -0
  46. package/dist/tests-ts/dsh-agent.test.js +115 -8
  47. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  48. package/dist/tests-ts/installer-assets.test.js +2 -2
  49. package/dist/tests-ts/local-cli.test.js +119 -1
  50. package/dist/tests-ts/model-provider-cancellation.test.js +150 -0
  51. package/dist/tests-ts/personal-assistant-coordination.test.js +76 -0
  52. package/dist/tests-ts/personal-assistant-extensions.test.js +29 -0
  53. package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
  54. package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
  55. package/dist/tests-ts/personal-assistant.test.js +389 -0
  56. package/dist/tests-ts/platform-integration.test.js +124 -1
  57. package/dist/tests-ts/plugin-catalog.test.js +12 -2
  58. package/dist/tests-ts/plugin-manager.test.js +280 -0
  59. package/dist/tests-ts/publish-packages.test.js +42 -0
  60. package/dist/tests-ts/qwenwork-plugin.test.js +105 -1
  61. package/dist/tests-ts/session-management.test.js +155 -0
  62. package/dist/tests-ts/workbuddy-plugin.test.js +154 -2
  63. package/package.json +1 -1
  64. package/plugins/abweb/dist/src-ts/agent.js +35 -0
  65. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  66. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +68 -2
  67. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +53 -3
  68. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
  69. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  70. package/plugins/abweb/package.json +1 -1
  71. package/plugins/abweb/plugin.json +1 -1
  72. package/plugins/aun/dist/src-ts/agent.js +35 -0
  73. package/plugins/aun/dist/src-ts/plugins/aun/evolcore.js +93 -17
  74. package/plugins/aun/dist/src-ts/plugins/aun/index.js +77 -12
  75. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +3 -0
  76. package/plugins/aun/package.json +1 -1
  77. package/plugins/aun/plugin.json +1 -1
  78. package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
  79. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  80. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +77 -17
  81. package/plugins/chatgpt/package.json +1 -1
  82. package/plugins/chatgpt/plugin.json +1 -1
  83. package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
  84. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
  85. package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  86. package/plugins/claude-cli/package.json +1 -1
  87. package/plugins/claude-cli/plugin.json +3 -2
  88. package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
  89. package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
  90. package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  91. package/plugins/codex-cli/package.json +1 -1
  92. package/plugins/codex-cli/plugin.json +3 -2
  93. package/plugins/doubao/dist/src-ts/agent.js +35 -0
  94. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
  95. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +36 -4
  96. package/plugins/doubao/package.json +1 -1
  97. package/plugins/doubao/plugin.json +3 -2
  98. package/plugins/dsh/config.schema.json +1 -0
  99. package/plugins/dsh/dist/src-ts/agent.js +35 -0
  100. package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -15
  101. package/plugins/dsh/dist/src-ts/dsh-paths.js +7 -7
  102. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +197 -0
  103. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +82 -7
  104. package/plugins/dsh/package.json +1 -1
  105. package/plugins/dsh/plugin.json +3 -2
  106. package/plugins/feishu/dist/src-ts/agent.js +35 -0
  107. package/plugins/personal-assistant/config.schema.json +22 -0
  108. package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
  109. package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
  110. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  111. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  112. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +242 -0
  113. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
  114. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  115. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  116. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
  117. package/plugins/personal-assistant/package.json +39 -0
  118. package/plugins/personal-assistant/plugin.json +15 -0
  119. package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
  120. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
  121. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +89 -8
  122. package/plugins/qwenwork/package.json +1 -1
  123. package/plugins/qwenwork/plugin.json +3 -2
  124. package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
  125. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
  126. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +104 -33
  127. package/plugins/workbuddy/package.json +1 -1
  128. package/plugins/workbuddy/plugin.json +3 -2
package/README.md CHANGED
@@ -1,8 +1,7 @@
1
1
  # mu-agent-bridge 动态插件平台
2
2
 
3
3
  运行时使用 TypeScript/Node.js,实现一个可动态发现、启动、停止、重载和安装插件的 daemon。
4
- 当前插件包括千问办公 `qwenwork`、DeepSeek Harness `dsh`、豆包 `doubao`、基于 CodeBuddy CLI 的 `workbuddy`、远程 ChatGPT/Codex CDP 插件 `chatgpt`,以及插件化管理后台
5
- `abweb`。插件之间独立失败,管理 API 和 OpenAI 兼容接口由同一个本地控制服务提供。
4
+ 当前插件包括千问办公 `qwenwork`、DeepSeek Harness `dsh`、豆包 `doubao`、基于 CodeBuddy CLI 的 `workbuddy`、远程 ChatGPT/Codex CDP 插件 `chatgpt`、个人助理管理 Agent `personal-assistant`,以及插件化管理后台 `abweb`。插件之间独立失败,管理 API 和 OpenAI 兼容接口由同一个本地控制服务提供。
6
5
 
7
6
  ## 快速开始
8
7
 
@@ -48,14 +47,14 @@ abweb 的“配置码”栏;配对码 10 分钟有效,只能使用一次,
48
47
 
49
48
  ## npm 插件
50
49
 
51
- 默认包 `mu-agent-bridge` 已内置 abweb、AUN、千问办公、DSH、豆包、飞书、WorkBuddy 和 ChatGPT/Codex 八个插件,直接安装即可使用:
50
+ 默认包 `mu-agent-bridge` 已内置 abweb、AUN、千问办公、DSH、豆包、飞书、WorkBuddy、ChatGPT/Codex、Codex CLI、Claude CLI 和个人助理共十一个插件,直接安装即可使用:
52
51
 
53
52
  ```powershell
54
53
  npm install -g mu-agent-bridge@latest
55
54
  mab start
56
55
  ```
57
56
 
58
- 八个插件会从核心包的 `plugins/` 目录自动发现。插件仍分别发布为独立 npm 包,可用于单独测试、升级或替换版本。安装与发布说明见
57
+ 十一个插件会从核心包的 `plugins/` 目录自动发现。插件仍分别发布为独立 npm 包,可用于单独测试、升级或替换版本。安装与发布说明见
59
58
  [`docs/npm-publishing.md`](docs/npm-publishing.md)。
60
59
 
61
60
  `mab start` 启动时会查询核心包和托管插件的 npm 最新版本,检查状态保存于 mab 状态目录的
@@ -77,12 +76,16 @@ mab start
77
76
  abweb 插件目录从 npm 搜索带 `mu-agent-bridge-plugin` 关键词的包,按包元数据中的
78
77
  `agentBridge.type` 展示 Agent、Channel 和管理插件;安装后以 `plugin.json` 的类型和能力
79
78
  再次校验,不根据包名推断类型。搜索结果有收录延迟,可在页面直接输入 `agent-bridge-*`
80
- 包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后在 AUN 身份页手动关联每个 AID;未关联的 AID 不会自动投递,关联操作只修改指定 AID,不影响其它身份。`workbuddy` 插件包名为 `agent-bridge-workbuddy`,显式关联到 WorkBuddy 后即可按对端保持 CodeBuddy 会话。默认问答使用独立的 `@tencent-ai/codebuddy-code` CLI,须单独登录;没有 CLI 时使用 WorkBuddy 桌面端 CDP,并默认自动启动桌面应用。CLI 未登录时不会自动切换到桌面端账号;可设置 `transport` 为 `cdp` 显式使用桌面端。
79
+ 包名安装。已安装插件按清单能力兼容旧版分类。AUN 通道插件包名为 `agent-bridge-aun`,安装后在 AUN 身份页手动关联每个 AID;未关联的 AID 不会自动投递,关联操作只修改指定 AID。
80
+
81
+ 个人助理的独立 npm 包名为 `agent-bridge-pa`,运行时 ID、源码和数据目录仍用 `personal-assistant`。在 ABWeb 的“已安装插件 → 个人助理 → 配置模型”选择已安装来源:五个桌面 Agent、Codex CLI、Claude CLI,或 OpenAI 兼容接口;列表由后台动态发现。来源未就绪时显示原因并保留原配置。专用 AUN AID 仍关联到 `personal-assistant`,Peer 和可调用 Agent 仍受原白名单限制。详见[个人助理使用说明](packages/personal-assistant/README.md)。
82
+
83
+ `workbuddy` 插件包名为 `agent-bridge-workbuddy`,显式关联后按对端保持会话。默认问答使用独立的 `@tencent-ai/codebuddy-code` CLI,须单独登录;没有 CLI 时使用桌面端 CDP。CLI 未登录时不会自动切换到桌面账号。个人助理选择 WorkBuddy 桌面模型来源时请设置 `transport: "cdp"`。
81
84
 
82
85
  插件仍在 daemon 的同一个 Node.js 进程内运行。修改插件入口依赖后应重启 daemon,避免模块缓存
83
86
  影响新代码。
84
87
 
85
- npm 尚未发布时,核心包内置的八个默认插件仍会出现在插件列表。abweb 的“安装本地插件”
88
+ npm 尚未发布时,核心包内置的十一个默认插件仍会出现在插件列表。abweb 的“安装本地插件”
86
89
  可填写 **Windows 主机上**包含 `plugin.json` 的目录路径,例如编译后的
87
90
  `C:\path\to\agent_bridge\packages\dsh`;安装后可在下拉框中启动。已内置的千问办公无需再次安装。
88
91
  “安装 npm 插件”只适用于包已经公开发布的情况。npm 发布令牌仅供发布脚本向 npm 官网认证,
@@ -1,7 +1,42 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AgentOperationError = exports.AgentError = void 0;
4
+ exports.abortable = abortable;
4
5
  exports.errorDetails = errorDetails;
6
+ async function abortable(promise, signal) {
7
+ if (!signal)
8
+ return promise;
9
+ if (signal.aborted) {
10
+ // promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
11
+ void promise.catch(() => undefined);
12
+ signal.throwIfAborted();
13
+ }
14
+ return new Promise((resolve, reject) => {
15
+ let settled = false;
16
+ const cleanup = () => signal.removeEventListener("abort", abort);
17
+ const abort = () => {
18
+ if (settled)
19
+ return;
20
+ settled = true;
21
+ cleanup();
22
+ reject(signal.reason);
23
+ };
24
+ signal.addEventListener("abort", abort, { once: true });
25
+ promise.then((value) => {
26
+ if (settled)
27
+ return;
28
+ settled = true;
29
+ cleanup();
30
+ resolve(value);
31
+ }, (error) => {
32
+ if (settled)
33
+ return;
34
+ settled = true;
35
+ cleanup();
36
+ reject(error);
37
+ });
38
+ });
39
+ }
5
40
  class AgentError extends Error {
6
41
  constructor(message, options) {
7
42
  super(message, options);
@@ -136,12 +136,13 @@ class ChannelSessions {
136
136
  const active = this.sessions[this.active[source]];
137
137
  const entry = !command && active?.agentPlugin === agentPlugin ? active : undefined;
138
138
  const sessionId = entry?.sessionId ?? (0, node_crypto_1.randomUUID)();
139
+ const logicalSessionId = entry?.logicalSessionId ?? (0, node_crypto_1.randomUUID)();
139
140
  const response = command
140
141
  ? await control({ operation: "new", sessionId, ...(command[1]?.trim() ? { name: command[1].trim() } : {}) })
141
- : await call(sessionId, !entry || entry.newSession === true);
142
+ : await call(sessionId, !entry || entry.newSession === true, logicalSessionId);
142
143
  if (command && (typeof response.sessionId !== "string" || !response.sessionId.trim()))
143
144
  throw new Error("插件未返回有效的新会话标识,原会话绑定保持不变");
144
- const next = { source, logicalSessionId: entry?.logicalSessionId ?? (0, node_crypto_1.randomUUID)(), agentPlugin, sessionId: response.sessionId || sessionId, updatedAt: new Date().toISOString(), ...(command ? { newSession: true } : {}) };
145
+ const next = { source, logicalSessionId, agentPlugin, sessionId: response.sessionId || sessionId, updatedAt: new Date().toISOString(), ...(command ? { newSession: true } : {}) };
145
146
  await this.save(next);
146
147
  return command ? response : { ...response, source, logicalSessionId: next.logicalSessionId, agentPlugin, request: message, response };
147
148
  });
@@ -183,7 +183,9 @@ class ControlServer {
183
183
  if (method === "GET" && url.pathname === "/api/health")
184
184
  return this.json(res, 200, { status: "ok", plugins: this.options.manager.list().length });
185
185
  if (method === "GET" && url.pathname === "/api/versions")
186
- return this.json(res, 200, { core: this.options.manager.versions.get("mu-agent-bridge") ?? null, plugins: this.options.manager.list().map((plugin) => ({ ...plugin, versionStatus: this.options.manager.versions.get(`agent-bridge-${plugin.id}`) ?? null })) });
186
+ return this.json(res, 200, { core: this.options.manager.versions.get("mu-agent-bridge") ?? null, plugins: this.options.manager.list().map((plugin) => ({ ...plugin, versionStatus: this.options.manager.versions.get(plugin.packageName ?? `agent-bridge-${plugin.id}`) ?? null })) });
187
+ if (method === "GET" && url.pathname === "/api/model-providers")
188
+ return this.json(res, 200, { providers: this.options.manager.listModelProviders() });
187
189
  if (method === "GET" && url.pathname === "/api/update-policy")
188
190
  return this.json(res, 200, await this.options.manager.updatePolicy());
189
191
  if (method === "PATCH" && url.pathname === "/api/update-policy")
@@ -283,7 +285,8 @@ class ControlServer {
283
285
  }
284
286
  if (method === "POST" && parts[3] === "download") {
285
287
  const body = await this.body(req);
286
- return this.json(res, 200, { ok: true, path: await this.options.manager.downloadPackage(body.packageName ?? `agent-bridge-${id}`, body.version) });
288
+ const packageName = body.packageName ?? this.options.manager.packageInfo(id).packageName ?? `agent-bridge-${id}`;
289
+ return this.json(res, 200, { ok: true, path: await this.options.manager.downloadPackage(packageName, body.version) });
287
290
  }
288
291
  if (method === "DELETE" && parts.length === 3) {
289
292
  await this.options.manager.uninstall(id);
@@ -61,19 +61,23 @@ async function readPresentedFiles(directory, paths, cwd, inputs, returnPaths) {
61
61
  return result;
62
62
  }
63
63
  class DshAgent {
64
+ options;
64
65
  name = "dsh";
65
66
  paths;
66
67
  spawnProcess;
67
68
  defaultTimeoutMs;
68
69
  constructor(options) {
69
- this.paths = options.paths ?? (0, dsh_paths_1.resolveDshPaths)(options.patchPath);
70
+ this.options = options;
71
+ this.paths = options.paths;
70
72
  this.spawnProcess = options.spawn ?? node_child_process_1.spawn;
71
73
  this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300_000;
72
74
  }
73
- async send(request) {
75
+ async send(request, modelRequest = false) {
74
76
  if (!request.task.trim())
75
77
  throw new agent_1.AgentError("DSH task must not be empty");
78
+ request.signal?.throwIfAborted();
76
79
  (0, attachments_1.validateAttachments)(request.attachments);
80
+ const paths = this.paths ?? await (0, dsh_paths_1.resolveDshPaths)(this.options.patchPath);
77
81
  const workDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-dsh-"));
78
82
  let failed = false;
79
83
  try {
@@ -84,12 +88,12 @@ class DshAgent {
84
88
  const inputPaths = (request.attachments ?? []).map((item, index) => ({ path: (0, node_path_1.join)(workDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), returnPath: (0, node_path_1.join)(outputDirectory, `${index}-${(0, attachments_1.attachmentFilename)(item.filename)}`), filename: item.filename, contentType: item.contentType }));
85
89
  for (const [index, item] of (request.attachments ?? []).entries())
86
90
  await (0, promises_1.writeFile)(inputPaths[index].returnPath, item.data, { mode: 0o600 });
87
- const task = `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
88
- const args = ["--expose-internals", this.paths.cli, "--profile", "headless", "--patch", this.paths.patch, "--json"];
91
+ const task = modelRequest ? request.task : `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
92
+ const args = ["--expose-internals", paths.cli, "--profile", "headless", "--patch", paths.patch, "--json"];
89
93
  if (request.sessionId && !request.newSession)
90
94
  args.push("--session-id", request.sessionId);
91
95
  args.push("-");
92
- const child = this.spawnProcess(this.paths.executable, args, {
96
+ const child = this.spawnProcess(paths.executable, args, {
93
97
  cwd: request.cwd,
94
98
  env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
95
99
  stdio: ["pipe", "pipe", "pipe"],
@@ -126,18 +130,30 @@ class DshAgent {
126
130
  });
127
131
  child.stderr.on("data", (chunk) => { errors += chunk; });
128
132
  await new Promise((resolve, reject) => {
129
- const timer = setTimeout(() => {
130
- child.kill();
131
- reject(new agent_1.AgentError(`DSH task timed out after ${request.timeoutMs ?? this.defaultTimeoutMs}ms`));
132
- }, request.timeoutMs ?? this.defaultTimeoutMs);
133
- child.once("error", (error) => { clearTimeout(timer); reject(new agent_1.AgentError("failed to start DSH", { cause: error })); });
134
- child.once("close", (code) => {
133
+ let timedOut = false;
134
+ const finish = (action) => {
135
135
  clearTimeout(timer);
136
- if (code !== 0 && !finalText)
137
- reject(new agent_1.AgentError(errors.trim() || `DSH exited with code ${code}`));
138
- else
139
- resolve();
136
+ request.signal?.removeEventListener("abort", abort);
137
+ action();
138
+ };
139
+ const abort = () => { child.kill(); };
140
+ const timer = setTimeout(() => { timedOut = true; child.kill(); }, request.timeoutMs ?? this.defaultTimeoutMs);
141
+ child.once("error", (error) => finish(() => reject(new agent_1.AgentError("failed to start DSH", { cause: error }))));
142
+ child.once("close", (code) => {
143
+ finish(() => {
144
+ if (request.signal?.aborted)
145
+ reject(request.signal.reason instanceof Error ? request.signal.reason : new agent_1.AgentError("DSH task canceled"));
146
+ else if (timedOut)
147
+ reject(new agent_1.AgentError(`DSH task timed out after ${request.timeoutMs ?? this.defaultTimeoutMs}ms`));
148
+ else if (code !== 0 && !finalText)
149
+ reject(new agent_1.AgentError(errors.trim() || `DSH exited with code ${code}`));
150
+ else
151
+ resolve();
152
+ });
140
153
  });
154
+ request.signal?.addEventListener("abort", abort, { once: true });
155
+ if (request.signal?.aborted)
156
+ abort();
141
157
  child.stdin.end(task, "utf8");
142
158
  });
143
159
  if (request.newSession && !sessionId)
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveDshPaths = resolveDshPaths;
4
- const node_fs_1 = require("node:fs");
5
4
  const node_path_1 = require("node:path");
6
- function resolveDshPaths(patch) {
7
- const override = process.env.DSH_INSTALL_DIR;
8
- const candidates = [override, "D:\\Programs\\DeepSeek Harness", "D:\\DSH"].filter((value) => Boolean(value));
9
- const root = candidates.find((candidate) => (0, node_fs_1.existsSync)((0, node_path_1.join)(candidate, "DeepSeek Harness.exe"))) ?? candidates[0];
5
+ const desktop_launch_1 = require("./plugins/desktop-launch");
6
+ async function resolveDshPaths(patch, configured) {
7
+ const executable = await (0, desktop_launch_1.findWindowsExecutable)("DeepSeek Harness.exe", configured ?? (process.env.DSH_INSTALL_DIR ? (0, node_path_1.join)(process.env.DSH_INSTALL_DIR, "DeepSeek Harness.exe") : undefined));
8
+ if (!executable)
9
+ throw new Error("未找到 DeepSeek Harness 可执行文件,请先安装 DSH 或重新检测客户端路径");
10
10
  return {
11
- executable: (0, node_path_1.join)(root, "DeepSeek Harness.exe"),
12
- cli: (0, node_path_1.join)(root, "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"),
11
+ executable,
12
+ cli: (0, node_path_1.join)((0, node_path_1.dirname)(executable), "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"),
13
13
  patch,
14
14
  };
15
15
  }
@@ -1,8 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolvePluginId = resolvePluginId;
3
4
  exports.searchPluginCatalog = searchPluginCatalog;
4
5
  const registry = "https://registry.npmjs.org";
5
6
  const pluginName = /^agent-bridge-[a-z0-9-]+$/;
7
+ const pluginId = /^[a-z][a-z0-9-]{1,63}$/;
8
+ function resolvePluginId(packageName, declaredId) {
9
+ if (!pluginName.test(packageName))
10
+ return;
11
+ const id = declaredId === undefined ? packageName.slice("agent-bridge-".length) : declaredId;
12
+ return typeof id === "string" && pluginId.test(id) ? id : undefined;
13
+ }
6
14
  async function getJson(url) {
7
15
  const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
8
16
  if (!response.ok)
@@ -24,9 +32,10 @@ async function searchPluginCatalog(query = "", get = getJson) {
24
32
  const version = metadata?.["dist-tags"]?.latest;
25
33
  const pkg = metadata?.versions?.[version];
26
34
  const type = pkg?.agentBridge?.type;
27
- if (!pkg?.agentBridge?.plugin || !["agent", "channel", "management"].includes(type))
35
+ const id = resolvePluginId(name, pkg?.agentBridge?.id);
36
+ if (!id || !pkg?.agentBridge?.plugin || !["agent", "channel", "management"].includes(type))
28
37
  return;
29
- return { name, version, description: typeof entry.package.description === "string" ? entry.package.description : "", type };
38
+ return { name, id, version, description: typeof entry.package.description === "string" ? entry.package.description : "", type };
30
39
  }
31
40
  catch {
32
41
  return;
@@ -34,6 +34,7 @@ class PluginManager {
34
34
  root;
35
35
  messages = [];
36
36
  activeIngress = new Map();
37
+ issuedManagement = new WeakMap();
37
38
  trustConfig = defaultPluginTrustConfig();
38
39
  trustLoaded = false;
39
40
  publisherLookups = new Map();
@@ -110,7 +111,7 @@ class PluginManager {
110
111
  if (meta.name !== packageName || !meta.version || (version && meta.version !== version) || !meta.agentBridge?.plugin)
111
112
  throw new agent_1.AgentError("npm 包声明无效");
112
113
  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)
114
+ if ((0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== manifest.id || manifest.version !== meta.version)
114
115
  throw new agent_1.AgentError("npm 包与插件清单不匹配");
115
116
  this.validateCandidate(manifest, meta.agentBridge);
116
117
  this.log("info", `插件下载完成:${manifest.id}`, { packageName, version: meta.version }, "插件升级");
@@ -168,7 +169,8 @@ class PluginManager {
168
169
  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
170
  })]);
170
171
  }
171
- async upgradePackage(id, packageName = `agent-bridge-${id}`, version) {
172
+ async upgradePackage(id, packageName, version) {
173
+ packageName ??= this.plugins.get(id)?.record.packageName ?? `agent-bridge-${id}`;
172
174
  const pending = this.upgrades.get(id);
173
175
  if (pending) {
174
176
  if (pending.packageName === packageName && pending.version === version)
@@ -215,7 +217,7 @@ class PluginManager {
215
217
  if (!meta.agentBridge?.plugin)
216
218
  throw new agent_1.AgentError("npm 包未声明 agentBridge.plugin");
217
219
  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)
220
+ if (manifest.id !== id || (0, plugin_catalog_1.resolvePluginId)(packageName, meta.agentBridge.id) !== id || manifest.version !== meta.version)
219
221
  throw new agent_1.AgentError("npm 包插件 ID 或版本不匹配");
220
222
  this.validateCandidate(manifest, meta.agentBridge);
221
223
  await this.stop(id, "reload");
@@ -255,6 +257,39 @@ class PluginManager {
255
257
  }
256
258
  }
257
259
  get(id) { return this.plugins.get(id); }
260
+ listModelProviders() {
261
+ return [...this.plugins.values()].filter(({ record }) => record.capabilities.includes("model-provider")).map(({ record, instance }) => {
262
+ const available = record.status === "ready" && typeof instance?.modelProvider?.complete === "function";
263
+ return { id: record.id, displayName: record.displayName, status: record.status, available, reason: available ? "" : record.error ?? (record.status === "ready" ? "插件未实现模型来源接口" : `插件当前状态:${record.status}`) };
264
+ });
265
+ }
266
+ async completeModelProvider(id, request) {
267
+ const loaded = this.plugins.get(id);
268
+ if (!loaded)
269
+ throw new agent_1.AgentError(`模型来源插件 ${id} 未安装`);
270
+ if (!loaded.record.capabilities.includes("model-provider"))
271
+ throw new agent_1.AgentError(`插件 ${id} 未声明模型来源能力`);
272
+ if (!loaded.instance || loaded.record.status !== "ready" || typeof loaded.instance.modelProvider?.complete !== "function")
273
+ throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”未就绪:${loaded.record.error ?? (loaded.record.status === "ready" ? "插件未实现模型来源接口" : loaded.record.status)}`);
274
+ if (!request || typeof request.prompt !== "string" || !request.prompt.trim() || Buffer.byteLength(request.prompt) > 1024 * 1024)
275
+ throw new agent_1.AgentError("模型来源请求内容为空或超过 1 MiB");
276
+ if (typeof request.sessionId !== "string" || !request.sessionId.trim() || request.sessionId.length > 512)
277
+ throw new agent_1.AgentError("模型来源会话标识无效");
278
+ if (!Number.isInteger(request.timeoutMs) || request.timeoutMs < 1000 || request.timeoutMs > 120_000)
279
+ throw new agent_1.AgentError("模型来源超时时间无效");
280
+ if (!(request.signal instanceof AbortSignal))
281
+ throw new agent_1.AgentError("模型来源请求缺少取消信号");
282
+ // 模型来源只接收最小调用契约,避免把未来扩展字段误传给桌面或 CLI 插件。
283
+ const result = await loaded.instance.modelProvider.complete({
284
+ prompt: request.prompt,
285
+ sessionId: request.sessionId,
286
+ timeoutMs: request.timeoutMs,
287
+ signal: request.signal,
288
+ });
289
+ if (!result || typeof result.text !== "string" || !result.text.trim() || Buffer.byteLength(result.text) > 1024 * 1024)
290
+ throw new agent_1.AgentError(`模型来源插件“${loaded.record.displayName}”返回空内容或超大响应`);
291
+ return { text: result.text };
292
+ }
258
293
  recentMessages(id) { return this.messages.filter((item) => !id || item.pluginId === id).slice(-200).reverse(); }
259
294
  recordMessage(pluginId, direction, text, peer, agentAid) {
260
295
  if (direction === "error" && isNoisyAgentError(text))
@@ -267,8 +302,9 @@ class PluginManager {
267
302
  if (!/^agent-bridge-[a-z0-9-]+(?:@[0-9][a-zA-Z0-9.+-]*)?$/.test(packageName))
268
303
  throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
269
304
  const name = packageName.replace(/@.*$/, "");
270
- if (this.plugins.has(name.replace(/^agent-bridge-/, "")))
271
- throw new agent_1.AgentError("插件已经存在");
305
+ const version = packageName.slice(name.length + 1);
306
+ if (this.list().some((record) => record.packageName === name))
307
+ throw new agent_1.AgentError("插件已经存在,请使用升级操作");
272
308
  const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", name);
273
309
  if (await this.existsDir(prefix))
274
310
  throw new agent_1.AgentError("插件包已经安装");
@@ -283,12 +319,16 @@ class PluginManager {
283
319
  const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
284
320
  if (meta.name !== name || !meta.version || !meta.agentBridge?.plugin)
285
321
  throw new agent_1.AgentError("npm 包名称或插件声明无效");
322
+ if (version && meta.version !== version)
323
+ throw new agent_1.AgentError("npm 包版本与请求不匹配");
286
324
  const manifestFile = (0, node_path_1.resolve)(directory, meta.agentBridge.plugin);
287
325
  if (!manifestFile.startsWith(`${(0, node_path_1.resolve)(directory)}${node_path_1.sep}`))
288
326
  throw new agent_1.AgentError("插件清单路径越界");
289
327
  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))
328
+ 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
329
  throw new agent_1.AgentError("npm 包与插件清单不匹配");
330
+ if (this.plugins.has(manifest.id))
331
+ throw new agent_1.AgentError("插件已经存在;不同 npm 来源不能覆盖,请先显式处理现有插件");
292
332
  this.validateCandidate(manifest, meta.agentBridge);
293
333
  await this.discoverDirectory(directory, name, manifestFile);
294
334
  await this.resolvePendingPublisherLookups();
@@ -375,11 +415,19 @@ class PluginManager {
375
415
  config,
376
416
  logger: pluginLogger,
377
417
  http: this.createHttpRegistry(id),
418
+ modelInputGuard: true,
378
419
  callPlugin: async (targetId, request) => {
379
420
  if (targetId === id)
380
421
  throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
422
+ if (request.management)
423
+ throw new agent_1.AgentError("插件间调用不能传递管理能力");
381
424
  return this.handle(targetId, request);
382
425
  },
426
+ callModelProvider: async (targetId, request) => {
427
+ if (targetId === id)
428
+ throw new agent_1.AgentError(`插件不能调用自身: ${id}`);
429
+ return this.completeModelProvider(targetId, request);
430
+ },
383
431
  pluginStatus: (targetId) => this.plugins.get(targetId)?.record.status,
384
432
  ingress: {
385
433
  publish: async (message) => {
@@ -407,8 +455,8 @@ class PluginManager {
407
455
  if (trustedChannel)
408
456
  this.activeIngress.set(source, (this.activeIngress.get(source) ?? 0) + 1);
409
457
  try {
410
- const instance = this.plugins.get(target)?.instance;
411
- const control = instance?.sessionOperations?.includes("new")
458
+ const agentInstance = this.plugins.get(target)?.instance;
459
+ const control = agentInstance?.sessionOperations?.includes("new")
412
460
  ? (request) => this.controlSession(target, request)
413
461
  : undefined;
414
462
  const task = !message.text.trim() && message.attachments?.length ? "请处理收到的附件" : message.text;
@@ -422,7 +470,23 @@ class PluginManager {
422
470
  sent.push({ text });
423
471
  this.recordMessage(target, "reply", text, message.peerId);
424
472
  };
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);
473
+ const response = await this.sessions.run(message, target, async (sessionId, newSession, logicalSessionId) => {
474
+ const targetRecord = this.plugins.get(target)?.record;
475
+ const config = targetRecord?.type === "agent" && targetRecord.capabilities.includes("management") ? await this.config(target) : undefined;
476
+ const ability = trustedChannel && id === "aun" && loaded.instance === instance && loaded.record.status === "ready" && Array.isArray(config?.allowedPeers) && (config.allowedPeers.includes("*") || config.allowedPeers.includes(message.peerId))
477
+ ? this.createRequestManagement(target, message.peerId, source, logicalSessionId) : undefined;
478
+ const request = { task, sessionId, onMessage, ...(newSession ? { newSession: true } : {}), ...(message.attachments ? { attachments: message.attachments } : {}), ...(ability ? { management: ability } : {}) };
479
+ const issued = ability ? this.issuedManagement.get(ability) : undefined;
480
+ if (issued)
481
+ issued.active = true;
482
+ try {
483
+ return await this.handle(target, request);
484
+ }
485
+ finally {
486
+ if (issued)
487
+ issued.active = false;
488
+ }
489
+ }, control);
426
490
  const attachments = response.attachments?.length ? response.attachments : (0, attachments_1.asAttachments)(response.images);
427
491
  (0, attachments_1.validateAttachments)(attachments);
428
492
  if (attachments.length) {
@@ -620,6 +684,103 @@ class PluginManager {
620
684
  ...await (0, agent_availability_1.agentAvailability)(plugin.id, await this.config(plugin.id), plugin.status),
621
685
  })));
622
686
  }
687
+ createRequestManagement(pluginId, peerId, source, logicalSessionId) {
688
+ const issued = { pluginId, instance: this.plugins.get(pluginId)?.instance, active: false };
689
+ const checkActive = () => {
690
+ const current = this.plugins.get(pluginId);
691
+ if (!issued.active || !current?.instance || current.instance !== issued.instance)
692
+ throw new agent_1.AgentError("管理能力请求已结束或已撤销");
693
+ };
694
+ const allowed = async (agentId) => {
695
+ checkActive();
696
+ const loaded = this.plugins.get(pluginId);
697
+ if (!loaded?.instance || loaded.record.status !== "ready" || !loaded.record.capabilities.includes("management"))
698
+ throw new agent_1.AgentError("当前插件未声明管理能力或未就绪");
699
+ const config = await this.config(pluginId);
700
+ checkActive();
701
+ const peers = Array.isArray(config.allowedPeers) ? config.allowedPeers : [];
702
+ if (!peers.includes("*") && !peers.includes(peerId))
703
+ throw new agent_1.AgentError("当前 AUN 身份无权使用管理能力");
704
+ if (agentId !== undefined) {
705
+ const agents = Array.isArray(config.allowedAgents) ? config.allowedAgents : [];
706
+ if (!agents.includes(agentId))
707
+ throw new agent_1.AgentError("目标 Agent 不在允许列表中");
708
+ }
709
+ return { loaded, config };
710
+ };
711
+ const management = {
712
+ listAgents: async () => {
713
+ const { config } = await allowed();
714
+ const aun = this.plugins.get("aun");
715
+ let bindings = {};
716
+ let identitiesAvailable = false;
717
+ try {
718
+ const health = aun?.record.status === "ready" ? await aun.instance?.health() : undefined;
719
+ const details = health?.details;
720
+ if (health?.status === "ready" && details && typeof details === "object" && !Array.isArray(details)) {
721
+ const raw = details.bindings;
722
+ if (raw && typeof raw === "object" && !Array.isArray(raw)) {
723
+ bindings = raw;
724
+ identitiesAvailable = true;
725
+ }
726
+ }
727
+ }
728
+ catch { /* AUN 状态不可用时只返回 Agent 能力 */ }
729
+ const result = [];
730
+ const agents = this.list().filter(item => item.type === "agent");
731
+ const agentIds = new Set(agents.map(item => item.id));
732
+ for (const record of agents) {
733
+ const agentConfig = await this.config(record.id);
734
+ const current = this.plugins.get(record.id);
735
+ const currentRecord = current?.record ?? record;
736
+ const availability = await (0, agent_availability_1.agentAvailability)(record.id, agentConfig, currentRecord.status);
737
+ const latest = this.plugins.get(record.id);
738
+ const latestRecord = latest?.record ?? currentRecord;
739
+ const aids = identitiesAvailable
740
+ ? 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()
741
+ : [];
742
+ 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.includes(record.id));
743
+ result.push({ id: latestRecord.id, displayName: latestRecord.displayName, status: latestRecord.status, ...availability, reason: !identitiesAvailable && !availability.reason ? "AUN 身份数据不可用" : availability.reason, capabilities: [...latestRecord.capabilities], aids, callable });
744
+ }
745
+ await allowed();
746
+ return result;
747
+ },
748
+ callAgent: async (id, task) => {
749
+ const { loaded } = await allowed(id);
750
+ if (typeof task !== "string" || !task.trim() || task.length > 20_000)
751
+ throw new agent_1.AgentError("Agent 调用内容无效");
752
+ const target = this.plugins.get(id);
753
+ if (!target?.instance || target.record.type !== "agent" || target.record.status !== "ready" || target.record.capabilities.includes("management") || id === pluginId)
754
+ throw new agent_1.AgentError("目标 Agent 不可调用");
755
+ if (!(await (0, agent_availability_1.agentAvailability)(id, await this.config(id), target.record.status)).available)
756
+ throw new agent_1.AgentError("目标 Agent 当前不可用");
757
+ await allowed(id);
758
+ // shortcut: 仅 WorkBuddy 已验证未知逻辑会话 ID 可安全建会话,其他插件验证后再扩展。
759
+ const sessionId = id === "workbuddy" && target.instance?.sessionOperations?.includes("new")
760
+ ? `management-${(0, node_crypto_1.createHash)("sha256").update(`${logicalSessionId}\0${id}`).digest("hex")}`
761
+ : undefined;
762
+ const response = await this.handle(id, { task, ...(sessionId ? { sessionId } : {}) });
763
+ if (loaded.instance !== this.plugins.get(pluginId)?.instance)
764
+ throw new agent_1.AgentError("管理插件已重载");
765
+ await allowed(id);
766
+ return response;
767
+ },
768
+ listAunIdentities: async () => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
769
+ throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.list(); await allowed(); return result; },
770
+ createAunIdentity: async (input) => { await allowed(); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
771
+ throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.create(input); await allowed(); return result; },
772
+ 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")
773
+ throw new agent_1.AgentError("只能关联已就绪的 Agent 插件"); const aun = this.plugins.get("aun")?.instance; if (!aun?.aunManagement)
774
+ throw new agent_1.AgentError("AUN 身份管理不可用"); const result = await aun.aunManagement.bind(input); await allowed(input.agentId); return result; },
775
+ runSessionCommand: async (command) => { await allowed(); if (typeof command !== "string" || command.length > 2_000 || !/^\/sessions(?:\s|$)/.test(command.trimStart()))
776
+ throw new agent_1.AgentError("仅支持 /sessions 只读命令"); const result = await this.sessionManagement.command(command, { source, sessionId: logicalSessionId }); await allowed(); if (result === undefined)
777
+ throw new agent_1.AgentError("不支持的 /sessions 命令"); return result; },
778
+ summarizeToday: async () => { await allowed(); const result = await this.sessionManagement.summarizeToday({ source, sessionId: logicalSessionId }); await allowed(); return result; },
779
+ };
780
+ Object.freeze(management);
781
+ this.issuedManagement.set(management, issued);
782
+ return management;
783
+ }
623
784
  async health(id) { const loaded = this.plugins.get(id); if (!loaded)
624
785
  throw new agent_1.AgentError(`unknown plugin: ${id}`); if (!loaded.instance)
625
786
  return { status: loaded.record.status }; return loaded.instance.health(); }
@@ -632,6 +793,13 @@ class PluginManager {
632
793
  const reason = loaded?.record.error ? `启动错误:${loaded.record.error}` : `当前状态:${loaded?.record.status ?? "未发现"}`;
633
794
  throw new agent_1.AgentError(`插件“${name}”无法调用,插件未就绪:${processText}${reason}`);
634
795
  }
796
+ if (request.modelResponseSchema !== undefined && !loaded.instance.modelInput)
797
+ throw new agent_1.AgentError("目标插件不支持无工具模型请求");
798
+ if (request.management) {
799
+ const issued = this.issuedManagement.get(request.management);
800
+ if (!issued || !issued.active || issued.pluginId !== id || issued.instance !== loaded.instance)
801
+ throw new agent_1.AgentError("管理能力无效或已撤销");
802
+ }
635
803
  (0, attachments_1.validateAttachments)(request.attachments);
636
804
  if (request.attachments?.length && !loaded.instance.attachmentInput)
637
805
  throw new agent_1.AgentError(`插件 ${id} 不支持接收附件`);
@@ -812,6 +980,8 @@ class PluginManager {
812
980
  try {
813
981
  const packageJson = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
814
982
  packageName = packageJson.name;
983
+ if (packageName && (packageName.startsWith("agent-bridge-") || packageJson.agentBridge?.id !== undefined) && (0, plugin_catalog_1.resolvePluginId)(packageName, packageJson.agentBridge?.id) !== manifest.id)
984
+ throw new agent_1.AgentError("npm 包声明的插件 ID 与清单不一致");
815
985
  coreConstraint = packageJson.agentBridge?.requires?.["mu-agent-bridge"];
816
986
  const declared = packageJson.agentBridge?.requires?.plugins;
817
987
  if (declared && JSON.stringify(declared) !== JSON.stringify(manifest.dependencies ?? {}))
@@ -824,6 +994,10 @@ class PluginManager {
824
994
  const coreVersion = require((0, node_path_1.resolve)(__dirname, "../../package.json")).version;
825
995
  const error = coreConstraint && !(0, version_manager_1.satisfiesVersion)(coreVersion, coreConstraint) ? `核心版本 ${coreVersion} 不满足插件要求 ${coreConstraint}` : undefined;
826
996
  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 } : {}) };
997
+ if (existing && existing.record.packageName && packageName && existing.record.packageName !== packageName) {
998
+ this.log("warn", "插件 npm 来源冲突,保留现有来源", { id: manifest.id, existingPackageName: existing.record.packageName, ignoredPackageName: packageName }, "插件发现");
999
+ return;
1000
+ }
827
1001
  if (existing) {
828
1002
  const versionOrder = (0, version_manager_1.compareVersions)(record.version, existing.record.version);
829
1003
  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.9",
8
+ version: "1.0.13",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",