mu-agent-bridge 1.0.27 → 1.0.29

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 (30) hide show
  1. package/dist/src-ts/plugin-manager.js +12 -6
  2. package/dist/src-ts/plugins/abweb/index.js +1 -1
  3. package/dist/src-ts/plugins/abweb/public/app.js +20 -1
  4. package/dist/src-ts/plugins/abweb/public/index.html +3 -0
  5. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  6. package/dist/src-ts/plugins/aun/index.js +5 -5
  7. package/dist/src-ts/plugins/aun/public/app.js +17 -4
  8. package/dist/tests-ts/abweb-login.test.js +1 -0
  9. package/dist/tests-ts/aun-plugin.test.js +6 -1
  10. package/dist/tests-ts/bundled-package.test.js +3 -1
  11. package/dist/tests-ts/channel-feedback.test.js +99 -0
  12. package/dist/tests-ts/plugin-manager.test.js +38 -0
  13. package/package.json +1 -1
  14. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  15. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +20 -1
  16. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +16 -1
  17. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -0
  18. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  19. package/plugins/abweb/package.json +32 -0
  20. package/plugins/abweb/plugin.json +1 -1
  21. package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -5
  22. package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +17 -4
  23. package/plugins/aun/package.json +35 -0
  24. package/plugins/aun/plugin.json +1 -1
  25. package/plugins/chatgpt/package.json +1 -0
  26. package/plugins/doubao/package.json +32 -0
  27. package/plugins/dsh/package.json +32 -0
  28. package/plugins/feishu/package.json +31 -0
  29. package/plugins/qwenwork/package.json +32 -0
  30. package/plugins/workbuddy/package.json +32 -0
@@ -115,8 +115,8 @@ class PluginManager {
115
115
  async discover() {
116
116
  await this.loadPluginTrust();
117
117
  await (0, promises_1.mkdir)(this.root, { recursive: true });
118
- await this.discoverNpmPackages();
119
118
  await this.discoverManagedPackages();
119
+ await this.discoverNpmPackages();
120
120
  for (const id of await (0, promises_1.readdir)(this.root, { withFileTypes: true })) {
121
121
  if (!id.isDirectory())
122
122
  continue;
@@ -145,7 +145,7 @@ class PluginManager {
145
145
  const root = (0, node_path_1.resolve)(this.options.stateDir, "npm");
146
146
  const path = (0, node_path_1.resolve)(record.path);
147
147
  const managed = path.startsWith(`${root}${node_path_1.sep}`);
148
- const packageName = managed ? (0, node_path_1.basename)((0, node_path_1.dirname)((0, node_path_1.dirname)(path))) : undefined;
148
+ const packageName = record.packageName;
149
149
  return { packageName: packageName && /^agent-bridge-[a-z0-9-]+$/.test(packageName) ? packageName : undefined, managed, version: record.version };
150
150
  }
151
151
  async checkPackageUpdates() {
@@ -165,12 +165,13 @@ class PluginManager {
165
165
  const loaded = this.plugins.get(id);
166
166
  if (!loaded)
167
167
  throw new agent_1.AgentError(`unknown plugin: ${id}`);
168
- if (!(0, node_path_1.resolve)(loaded.record.path).startsWith((0, node_path_1.resolve)(this.options.stateDir, "npm")))
169
- throw new agent_1.AgentError("内置插件不能通过 npm 升级");
168
+ if (loaded.record.packageName !== packageName)
169
+ throw new agent_1.AgentError("插件未声明对应的 npm 包,不能通过 npm 升级");
170
170
  const prefix = (0, node_path_1.join)(this.options.stateDir, "npm", packageName);
171
171
  const cached = await this.cachedPackage(packageName, version);
172
172
  const staging = cached ?? (0, node_path_1.join)(this.options.stateDir, "npm", `.staging-${packageName}-${Date.now()}`);
173
173
  const backup = `${prefix}.backup-${Date.now()}`;
174
+ const hadPrefix = await this.existsDir(prefix);
174
175
  let stopped = false;
175
176
  this.log("info", `开始升级插件:${id}`, { packageName, version }, "插件升级");
176
177
  await (0, promises_1.mkdir)(staging, { recursive: true });
@@ -192,7 +193,8 @@ class PluginManager {
192
193
  this.validateCandidate(manifest, meta.agentBridge);
193
194
  await this.stop(id, "reload");
194
195
  stopped = true;
195
- await (0, promises_1.rename)(prefix, backup);
196
+ if (hadPrefix)
197
+ await (0, promises_1.rename)(prefix, backup);
196
198
  await (0, promises_1.rename)(staging, prefix);
197
199
  this.plugins.delete(id);
198
200
  await this.discoverManagedPackages();
@@ -209,10 +211,14 @@ class PluginManager {
209
211
  await (0, promises_1.rm)(prefix, { recursive: true, force: true });
210
212
  await (0, promises_1.rename)(backup, prefix);
211
213
  }
214
+ else if (stopped && !hadPrefix)
215
+ await (0, promises_1.rm)(prefix, { recursive: true, force: true });
212
216
  if (stopped) {
213
217
  this.plugins.delete(id);
218
+ loaded.instance = undefined;
219
+ loaded.record.status = "discovered";
220
+ this.plugins.set(id, loaded);
214
221
  try {
215
- await this.discoverManagedPackages();
216
222
  await this.start(id);
217
223
  }
218
224
  catch { /* 回滚后仍无法启动时保留原错误 */ }
@@ -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.6",
8
+ version: "1.0.7",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",
@@ -35,6 +35,25 @@
35
35
  let loading = false;
36
36
  const installingPackages = new Set();
37
37
  function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
38
+ function showResult(title, message, failed) {
39
+ node("#operation-result-title").textContent = title;
40
+ const detail = node("#operation-result-message");
41
+ detail.textContent = message;
42
+ detail.className = failed ? "error" : "";
43
+ const dialog = node("#operation-result");
44
+ if (!dialog.open)
45
+ dialog.showModal();
46
+ }
47
+ node("#operation-result-close").onclick = () => node("#operation-result").close();
48
+ window.addEventListener("message", (event) => {
49
+ const frame = channelPanel.querySelector("iframe.channel-frame");
50
+ if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow)
51
+ return;
52
+ const result = event.data;
53
+ if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean")
54
+ return;
55
+ showResult(result.title, result.message, result.failed);
56
+ });
38
57
  async function loadCatalog() {
39
58
  const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
40
59
  const items = result.plugins ?? [];
@@ -113,7 +132,7 @@
113
132
  item.type = "button";
114
133
  item.textContent = label;
115
134
  item.title = title;
116
- item.onclick = () => { item.disabled = true; void run().catch((cause) => error(String(cause))).finally(() => { item.disabled = false; void load(false); }); };
135
+ item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); };
117
136
  return item;
118
137
  }
119
138
  async function load(refreshCatalog = true) {
@@ -21,6 +21,9 @@
21
21
  <div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
22
22
  <p id="error" class="error" role="alert" hidden></p>
23
23
  </main>
24
+ <dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
25
+ <h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
26
+ </dialog>
24
27
  <script src="/abweb/app.js"></script>
25
28
  </body>
26
29
  </html>
@@ -56,3 +56,7 @@ th { color: #566761; font-weight: 600; background: #edf1ef; }
56
56
  th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child(3), th:nth-child(4) { width: 145px; } th:nth-child(5) { width: 62px; } th:nth-child(7) { width: 140px; }
57
57
  .message-error td:last-child { color: #a83427; }
58
58
  @media (max-width: 620px) { .shell { padding: 16px; } .section-heading { align-items: flex-start; flex-direction: column; } form, form input { width: 100%; } }
59
+
60
+ #operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
61
+ #operation-result::backdrop { background: rgb(0 0 0 / 35%); }
62
+ #operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
@@ -10,7 +10,7 @@ const node_url_1 = require("node:url");
10
10
  const messages_1 = require("./messages");
11
11
  const evolcore_1 = require("./evolcore");
12
12
  const manifest = {
13
- id: "aun", version: "1.0.10", type: "channel", displayName: "AUN 通道",
13
+ id: "aun", version: "1.0.17", type: "channel", displayName: "AUN 通道",
14
14
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
15
15
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
16
16
  configSchema: "config.schema.json",
@@ -87,9 +87,8 @@ class AunPlugin {
87
87
  await this.persist();
88
88
  this.context?.logger.info("AUN 身份已创建", { aid, owner });
89
89
  }
90
- if (!this.clients.has(aid))
91
- await this.connectAid(aid);
92
- return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
90
+ const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
91
+ return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
93
92
  }
94
93
  finally {
95
94
  this.creating = false;
@@ -244,7 +243,8 @@ class AunPlugin {
244
243
  catch (error) {
245
244
  this.status = "degraded";
246
245
  this.healthMessage = safeError(error);
247
- this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
246
+ this.context?.logger.warn("AUN 通道连接失败", { aid, error: this.healthMessage });
247
+ return this.healthMessage;
248
248
  }
249
249
  }
250
250
  async deactivate(_reason) {
@@ -15,6 +15,12 @@
15
15
  return result;
16
16
  }
17
17
  function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
18
+ function notify(title, message, failed = false) {
19
+ show(message, failed);
20
+ if (window.parent !== window) window.parent.postMessage({ type: "mab:operation-result", title, message, failed }, window.location.origin);
21
+ else window.alert(`${title}\n${message}`);
22
+ }
23
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
18
24
  async function load(refreshForm = true) {
19
25
  if (loading) return;
20
26
  loading = true;
@@ -50,15 +56,22 @@
50
56
  async function bind(aid, pluginId) {
51
57
  try {
52
58
  await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
53
- await load(); show(`${aid} 已关联 ${pluginId}`);
54
- } catch (error) { show(`关联失败:${String(error)}`, true); }
59
+ await load(); notify("关联成功", `${aid} 已关联 ${pluginId}`);
60
+ } catch (error) { notify("关联失败", errorMessage(error), true); }
55
61
  }
56
62
  createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
57
- void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => load().then(() => show(result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建`))).catch((error) => show(String(error), true));
63
+ show("正在创建 AID 并连接,请稍候……");
64
+ void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
65
+ const failed = result.connection === "degraded";
66
+ const message = failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
67
+ : result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建并连接`;
68
+ notify(failed ? "AID 创建完成,连接失败" : "AID 创建结果", message, failed);
69
+ return load();
70
+ }).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
58
71
  };
59
72
  configForm.onsubmit = (event) => { event.preventDefault(); const changes = { agent_plugin: configForm.elements.agent_plugin.value, allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
60
73
  if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
61
- void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => show("配置已保存")).catch((error) => show(String(error), true));
74
+ void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
62
75
  };
63
76
  find("#refresh").onclick = () => void load();
64
77
  void load();
@@ -34,6 +34,7 @@ class Element {
34
34
  Date: class extends Date {
35
35
  static now() { return now; }
36
36
  },
37
+ window: { addEventListener() { } },
37
38
  setInterval() { }, confirm: () => false,
38
39
  fetch: async (path, options) => {
39
40
  calls.push({ path, token: options.headers.authorization });
@@ -137,6 +137,7 @@ const aun_1 = require("../src-ts/plugins/aun");
137
137
  const calls = [];
138
138
  let registration = { ok: true, data: {} };
139
139
  let loadOk = true;
140
+ let connectError = "";
140
141
  let loadFailAid = "";
141
142
  const clients = [];
142
143
  const sdk = {
@@ -150,7 +151,8 @@ const aun_1 = require("../src-ts/plugins/aun");
150
151
  AUNClient: class {
151
152
  constructor() { const current = this; current.emit = (payload) => current.handler?.(payload); clients.push(current); }
152
153
  on(_event, handler) { this.handler = handler; return { unsubscribe() { } }; }
153
- async connect() { calls.push("client.connect"); }
154
+ async connect() { calls.push("client.connect"); if (connectError)
155
+ throw new Error(connectError); }
154
156
  async close() { calls.push("client.close"); }
155
157
  async call(method, params) { calls.push(`${method}:${params.to}:${params.encrypt}`); }
156
158
  },
@@ -203,6 +205,9 @@ const aun_1 = require("../src-ts/plugins/aun");
203
205
  strict_1.default.deepEqual(await plugin.identity.create("pending.agentid.pub", "owner.agentid.pub"), { aid: "pending.agentid.pub", status: "已创建", connection: "ready" });
204
206
  strict_1.default.ok(calls.includes("register:pending.agentid.pub"));
205
207
  strict_1.default.equal((await plugin.health()).details?.aids instanceof Array, true);
208
+ connectError = "no trusted roots available for auth certificate verification";
209
+ strict_1.default.deepEqual(await plugin.identity.create("unconnected.agentid.pub", "owner.agentid.pub"), { aid: "unconnected.agentid.pub", status: "已创建", connection: "degraded", connectionError: connectError });
210
+ connectError = "";
206
211
  registration = { ok: false, error: { message: "注册失败" } };
207
212
  await strict_1.default.rejects(plugin.identity.create("failed.agentid.pub", "owner.agentid.pub"), /注册失败/);
208
213
  registration = { ok: true, data: {} };
@@ -30,6 +30,8 @@ const ids = ["abweb", "aun", "chatgpt", "doubao", "dsh", "feishu", "qwenwork", "
30
30
  strict_1.default.deepEqual(manifest, packageManifest);
31
31
  await (0, promises_1.access)((0, node_path_1.join)(directory, "config.schema.json"));
32
32
  await (0, promises_1.access)((0, node_path_1.join)(directory, manifest.entry));
33
- await strict_1.default.rejects((0, promises_1.access)((0, node_path_1.join)(directory, "package.json")));
33
+ const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
34
+ strict_1.default.equal(meta.name, `agent-bridge-${id}`);
35
+ strict_1.default.equal(meta.version, packageManifest.version);
34
36
  }
35
37
  });
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const strict_1 = __importDefault(require("node:assert/strict"));
7
+ const node_test_1 = __importDefault(require("node:test"));
8
+ const promises_1 = require("node:fs/promises");
9
+ const node_vm_1 = require("node:vm");
10
+ class Element {
11
+ hidden = true;
12
+ value = "";
13
+ textContent = "";
14
+ className = "";
15
+ open = false;
16
+ options = [];
17
+ selectedIndex = 0;
18
+ children = [];
19
+ elements = {};
20
+ onclick;
21
+ onsubmit;
22
+ replaceChildren(...items) { this.children = items; }
23
+ append(...items) { this.children.push(...items); }
24
+ setAttribute() { }
25
+ querySelector() { return this.children.find(item => item.className === "channel-frame"); }
26
+ showModal() { this.open = true; }
27
+ close() { this.open = false; }
28
+ }
29
+ const flush = () => new Promise(resolve => setImmediate(resolve));
30
+ (0, node_test_1.default)("ABWeb只接受当前同源通道页面的结果,刷新不关闭弹窗", async () => {
31
+ const nodes = new Map();
32
+ const node = (selector) => { if (!nodes.has(selector))
33
+ nodes.set(selector, new Element()); return nodes.get(selector); };
34
+ const source = {};
35
+ const frame = new Element();
36
+ frame.className = "channel-frame";
37
+ frame.contentWindow = source;
38
+ let receive;
39
+ (0, node_vm_1.runInNewContext)(await (0, promises_1.readFile)("src-ts/plugins/abweb/public/app.js", "utf8"), {
40
+ document: { querySelector: node, createElement: () => new Element(), hidden: false },
41
+ window: { location: { origin: "http://localhost:26107" }, addEventListener(name, handler) { if (name === "message")
42
+ receive = handler; } },
43
+ localStorage: { getItem: () => null, removeItem() { } }, setInterval() { }, confirm: () => false,
44
+ fetch: async () => ({ ok: true, status: 200, json: async () => ({ plugins: [], messages: [], sessions: [] }) }),
45
+ });
46
+ await flush();
47
+ node("#channel-panel").children = [frame];
48
+ const data = { type: "mab:operation-result", title: "创建 AID 失败", message: "owner AID has no active owner profile", failed: true };
49
+ strict_1.default.equal(typeof receive, "function");
50
+ receive({ origin: "http://other.invalid", source, data });
51
+ receive({ origin: "http://localhost:26107", source: {}, data });
52
+ strict_1.default.equal(node("#operation-result").open, false);
53
+ receive({ origin: "http://localhost:26107", source, data });
54
+ strict_1.default.equal(node("#operation-result").open, true);
55
+ strict_1.default.equal(node("#operation-result-title").textContent, data.title);
56
+ strict_1.default.equal(node("#operation-result-message").textContent, data.message);
57
+ node("#refresh").onclick();
58
+ await flush();
59
+ strict_1.default.equal(node("#operation-result").open, true);
60
+ node("#operation-result-close").onclick();
61
+ strict_1.default.equal(node("#operation-result").open, false);
62
+ });
63
+ for (const scenario of [
64
+ { ok: false, result: { error: "注册被拒绝" }, expected: "注册被拒绝", failed: true },
65
+ { ok: true, result: { status: "已创建", connection: "degraded", connectionError: "no trusted roots available" }, expected: "已创建,但连接失败:no trusted roots available", failed: true },
66
+ { ok: true, result: { status: "已创建", connection: "ready" }, expected: "已创建并连接", failed: false },
67
+ ])
68
+ (0, node_test_1.default)(`AUN创建结果通知ABWeb:${scenario.expected}`, async () => {
69
+ const nodes = new Map();
70
+ const node = (selector) => { if (!nodes.has(selector))
71
+ nodes.set(selector, new Element()); return nodes.get(selector); };
72
+ for (const [selector, keys] of [["#create-form", ["aid", "ownerAid"]], ["#config-form", ["agent_plugin", "allow_from", "aun_path", "encryption_seed"]]]) {
73
+ for (const key of keys)
74
+ node(selector).elements[key] = new Element();
75
+ }
76
+ const messages = [];
77
+ let refresh;
78
+ (0, node_vm_1.runInNewContext)(await (0, promises_1.readFile)("src-ts/plugins/aun/public/app.js", "utf8"), {
79
+ document: { querySelector: node, createElement: () => new Element(), hidden: false },
80
+ window: { parent: { postMessage(data, origin) { messages.push({ data, origin }); } }, location: { origin: "http://localhost:26107" } },
81
+ Option: function () { return new Element(); }, localStorage: { getItem: () => null }, setInterval(handler) { refresh = handler; },
82
+ fetch: async (path, options) => options.method === "POST"
83
+ ? { ok: scenario.ok, status: scenario.ok ? 201 : 502, json: async () => scenario.result }
84
+ : { ok: true, json: async () => path.endsWith("/identities") ? { groups: { muAgentBridge: [], evolcore: [] } } : { plugins: [] } },
85
+ });
86
+ await flush();
87
+ node("#create-form").elements.aid.value = "new.agentid.cn";
88
+ node("#create-form").elements.ownerAid.value = "owner.agentid.cn";
89
+ node("#create-form").onsubmit({ preventDefault() { } });
90
+ await flush();
91
+ strict_1.default.equal(messages.length, 1);
92
+ strict_1.default.equal(messages[0].data.type, "mab:operation-result");
93
+ strict_1.default.equal(messages[0].data.failed, scenario.failed);
94
+ strict_1.default.ok(messages[0].data.message.includes(scenario.expected));
95
+ strict_1.default.equal(messages[0].origin, "http://localhost:26107");
96
+ refresh();
97
+ await flush();
98
+ strict_1.default.equal(messages.length, 1, "自动刷新不反复弹窗");
99
+ });
@@ -44,6 +44,44 @@ const node_test_1 = __importDefault(require("node:test"));
44
44
  const plugin_manager_1 = require("../src-ts/plugin-manager");
45
45
  const version_manager_1 = require("../src-ts/version-manager");
46
46
  const channel_sessions_1 = require("../src-ts/channel-sessions");
47
+ (0, node_test_1.default)("随包插件检查独立 npm 更新,升级后重启仍选新副本,失败恢复内置版本", async () => {
48
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-bundled-update-"));
49
+ const stateDir = (0, node_path_1.join)(root, "state");
50
+ const pluginsDir = (0, node_path_1.join)(root, "plugins");
51
+ const packageName = "agent-bridge-demo";
52
+ const writePlugin = async (dir, version, failed = false) => {
53
+ await (0, promises_1.mkdir)(dir, { recursive: true });
54
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: packageName, version, agentBridge: { plugin: "./plugin.json" } }));
55
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id: "demo", version, entry: "index.js", apiVersion: "1", capabilities: [] }));
56
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `module.exports = { activate: async()=>{${failed ? 'throw new Error("升级测试失败")' : ''}}, deactivate: async()=>{}, health: async()=>({status:"ready"}) };`);
57
+ };
58
+ try {
59
+ await writePlugin((0, node_path_1.join)(pluginsDir, "demo"), "1.0.0");
60
+ const versions = new version_manager_1.VersionManager({ stateDir, npm: async () => JSON.stringify({ version: "1.0.1" }) });
61
+ const options = { pluginsDir, stateDir, versions, publisherResolver: async () => "m2lihui" };
62
+ const manager = new plugin_manager_1.PluginManager(options);
63
+ await manager.discover();
64
+ await manager.start("demo");
65
+ strict_1.default.equal((await manager.checkPackageUpdates()).find((item) => item.packageName === packageName)?.state, "update-available");
66
+ strict_1.default.equal(manager.list()[0].autoUpdate, true);
67
+ const downloaded = (0, node_path_1.join)(root, "downloaded");
68
+ await writePlugin((0, node_path_1.join)(downloaded, "node_modules", packageName), "1.0.1", true);
69
+ manager.downloadPackage = async () => downloaded;
70
+ await strict_1.default.rejects(manager.upgradePackage("demo", packageName, "1.0.1"), /升级测试失败/);
71
+ strict_1.default.equal(manager.list()[0].version, "1.0.0");
72
+ strict_1.default.equal(manager.list()[0].status, "ready");
73
+ await writePlugin((0, node_path_1.join)(downloaded, "node_modules", packageName), "1.0.1");
74
+ strict_1.default.equal((await manager.upgradePackage("demo", packageName, "1.0.1")).version, "1.0.1");
75
+ await manager.stop("demo");
76
+ const restarted = new plugin_manager_1.PluginManager(options);
77
+ await restarted.discover();
78
+ strict_1.default.equal(restarted.list()[0].version, "1.0.1");
79
+ strict_1.default.equal(restarted.packageInfo("demo").managed, true);
80
+ }
81
+ finally {
82
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
83
+ }
84
+ });
47
85
  (0, node_test_1.default)("通道会话隔离、续接和重启恢复", async () => {
48
86
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-sessions-"));
49
87
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {
@@ -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.6",
8
+ version: "1.0.7",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",
@@ -35,6 +35,25 @@
35
35
  let loading = false;
36
36
  const installingPackages = new Set();
37
37
  function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
38
+ function showResult(title, message, failed) {
39
+ node("#operation-result-title").textContent = title;
40
+ const detail = node("#operation-result-message");
41
+ detail.textContent = message;
42
+ detail.className = failed ? "error" : "";
43
+ const dialog = node("#operation-result");
44
+ if (!dialog.open)
45
+ dialog.showModal();
46
+ }
47
+ node("#operation-result-close").onclick = () => node("#operation-result").close();
48
+ window.addEventListener("message", (event) => {
49
+ const frame = channelPanel.querySelector("iframe.channel-frame");
50
+ if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow)
51
+ return;
52
+ const result = event.data;
53
+ if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean")
54
+ return;
55
+ showResult(result.title, result.message, result.failed);
56
+ });
38
57
  async function loadCatalog() {
39
58
  const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
40
59
  const items = result.plugins ?? [];
@@ -113,7 +132,7 @@
113
132
  item.type = "button";
114
133
  item.textContent = label;
115
134
  item.title = title;
116
- item.onclick = () => { item.disabled = true; void run().catch((cause) => error(String(cause))).finally(() => { item.disabled = false; void load(false); }); };
135
+ item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); };
117
136
  return item;
118
137
  }
119
138
  async function load(refreshCatalog = true) {
@@ -34,6 +34,21 @@
34
34
  const installingPackages = new Set<string>();
35
35
 
36
36
  function error(message: string): void { errorNode.textContent = message; errorNode.hidden = !message; }
37
+ function showResult(title: string, message: string, failed: boolean): void {
38
+ node<HTMLElement>("#operation-result-title").textContent = title;
39
+ const detail = node<HTMLElement>("#operation-result-message");
40
+ detail.textContent = message; detail.className = failed ? "error" : "";
41
+ const dialog = node<HTMLDialogElement>("#operation-result");
42
+ if (!dialog.open) dialog.showModal();
43
+ }
44
+ node<HTMLButtonElement>("#operation-result-close").onclick = () => node<HTMLDialogElement>("#operation-result").close();
45
+ window.addEventListener("message", (event: MessageEvent) => {
46
+ const frame = channelPanel.querySelector<HTMLIFrameElement>("iframe.channel-frame");
47
+ if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow) return;
48
+ const result = event.data;
49
+ if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean") return;
50
+ showResult(result.title, result.message, result.failed);
51
+ });
37
52
  async function loadCatalog(): Promise<void> {
38
53
  const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
39
54
  const items: Array<{ name: string; version: string; description: string; type: string }> = result.plugins ?? [];
@@ -80,7 +95,7 @@
80
95
  }
81
96
  function button(label: string, title: string, run: () => Promise<void>): HTMLButtonElement {
82
97
  const item = document.createElement("button"); item.type = "button"; item.textContent = label; item.title = title;
83
- item.onclick = () => { item.disabled = true; void run().catch((cause) => error(String(cause))).finally(() => { item.disabled = false; void load(false); }); }; return item;
98
+ item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); }; return item;
84
99
  }
85
100
  async function load(refreshCatalog = true): Promise<void> {
86
101
  if (loading) return;
@@ -21,6 +21,9 @@
21
21
  <div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
22
22
  <p id="error" class="error" role="alert" hidden></p>
23
23
  </main>
24
+ <dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
25
+ <h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
26
+ </dialog>
24
27
  <script src="/abweb/app.js"></script>
25
28
  </body>
26
29
  </html>
@@ -56,3 +56,7 @@ th { color: #566761; font-weight: 600; background: #edf1ef; }
56
56
  th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child(3), th:nth-child(4) { width: 145px; } th:nth-child(5) { width: 62px; } th:nth-child(7) { width: 140px; }
57
57
  .message-error td:last-child { color: #a83427; }
58
58
  @media (max-width: 620px) { .shell { padding: 16px; } .section-heading { align-items: flex-start; flex-direction: column; } form, form input { width: 100%; } }
59
+
60
+ #operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
61
+ #operation-result::backdrop { background: rgb(0 0 0 / 35%); }
62
+ #operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-bridge-abweb",
3
+ "version": "1.0.7",
4
+ "description": "agent-bridge 的网页管理插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-management"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/abweb"
13
+ },
14
+ "main": "dist/src-ts/plugins/abweb/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "management"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ }
32
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "abweb",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "type": "management",
5
5
  "displayName": "Agent Bridge Web",
6
6
  "entry": "dist/src-ts/plugins/abweb/index.js",
@@ -10,7 +10,7 @@ const node_url_1 = require("node:url");
10
10
  const messages_1 = require("./messages");
11
11
  const evolcore_1 = require("./evolcore");
12
12
  const manifest = {
13
- id: "aun", version: "1.0.10", type: "channel", displayName: "AUN 通道",
13
+ id: "aun", version: "1.0.17", type: "channel", displayName: "AUN 通道",
14
14
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
15
15
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
16
16
  configSchema: "config.schema.json",
@@ -87,9 +87,8 @@ class AunPlugin {
87
87
  await this.persist();
88
88
  this.context?.logger.info("AUN 身份已创建", { aid, owner });
89
89
  }
90
- if (!this.clients.has(aid))
91
- await this.connectAid(aid);
92
- return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
90
+ const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
91
+ return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
93
92
  }
94
93
  finally {
95
94
  this.creating = false;
@@ -244,7 +243,8 @@ class AunPlugin {
244
243
  catch (error) {
245
244
  this.status = "degraded";
246
245
  this.healthMessage = safeError(error);
247
- this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
246
+ this.context?.logger.warn("AUN 通道连接失败", { aid, error: this.healthMessage });
247
+ return this.healthMessage;
248
248
  }
249
249
  }
250
250
  async deactivate(_reason) {
@@ -15,6 +15,12 @@
15
15
  return result;
16
16
  }
17
17
  function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
18
+ function notify(title, message, failed = false) {
19
+ show(message, failed);
20
+ if (window.parent !== window) window.parent.postMessage({ type: "mab:operation-result", title, message, failed }, window.location.origin);
21
+ else window.alert(`${title}\n${message}`);
22
+ }
23
+ const errorMessage = (error) => error instanceof Error ? error.message : String(error);
18
24
  async function load(refreshForm = true) {
19
25
  if (loading) return;
20
26
  loading = true;
@@ -50,15 +56,22 @@
50
56
  async function bind(aid, pluginId) {
51
57
  try {
52
58
  await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
53
- await load(); show(`${aid} 已关联 ${pluginId}`);
54
- } catch (error) { show(`关联失败:${String(error)}`, true); }
59
+ await load(); notify("关联成功", `${aid} 已关联 ${pluginId}`);
60
+ } catch (error) { notify("关联失败", errorMessage(error), true); }
55
61
  }
56
62
  createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
57
- void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => load().then(() => show(result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建`))).catch((error) => show(String(error), true));
63
+ show("正在创建 AID 并连接,请稍候……");
64
+ void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
65
+ const failed = result.connection === "degraded";
66
+ const message = failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
67
+ : result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建并连接`;
68
+ notify(failed ? "AID 创建完成,连接失败" : "AID 创建结果", message, failed);
69
+ return load();
70
+ }).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
58
71
  };
59
72
  configForm.onsubmit = (event) => { event.preventDefault(); const changes = { agent_plugin: configForm.elements.agent_plugin.value, allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
60
73
  if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
61
- void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => show("配置已保存")).catch((error) => show(String(error), true));
74
+ void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
62
75
  };
63
76
  find("#refresh").onclick = () => void load();
64
77
  void load();
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "agent-bridge-aun",
3
+ "version": "1.0.17",
4
+ "description": "mu-agent-bridge 的 AUN 通道插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-channel"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/aun"
13
+ },
14
+ "main": "dist/src-ts/plugins/aun/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "channel"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22.16.0"
31
+ },
32
+ "dependencies": {
33
+ "@agentunion/fastaun": "0.5.18"
34
+ }
35
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.14",
3
+ "version": "1.0.17",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",
@@ -0,0 +1 @@
1
+ {"name":"agent-bridge-chatgpt","version":"1.0.1","description":"agent-bridge 的 ChatGPT Codex CDP 插件。","keywords":["mu-agent-bridge-plugin","mu-agent-bridge-agent"],"main":"dist/src-ts/plugins/chatgpt/index.js","files":["dist/","plugin.json","config.schema.json","README.md"],"agentBridge":{"plugin":"./plugin.json","type":"agent"},"engines":{"node":">=22"}}
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-bridge-doubao",
3
+ "version": "1.0.5",
4
+ "description": "agent-bridge 的豆包插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-agent"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/doubao"
13
+ },
14
+ "main": "dist/src-ts/plugins/doubao/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "agent"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-bridge-dsh",
3
+ "version": "1.0.4",
4
+ "description": "agent-bridge 的 DeepSeek Harness 插件,支持后台定时自动升级验证。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-agent"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/dsh"
13
+ },
14
+ "main": "dist/src-ts/plugins/dsh/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "agent"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ }
32
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "agent-bridge-feishu",
3
+ "version": "1.0.4",
4
+ "description": "agent-bridge 的飞书通道插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-channel"
8
+ ],
9
+ "main": "dist/src-ts/plugins/feishu/index.js",
10
+ "files": [
11
+ "dist/",
12
+ "plugin.json",
13
+ "config.schema.json",
14
+ "README.md"
15
+ ],
16
+ "agentBridge": {
17
+ "plugin": "./plugin.json",
18
+ "type": "channel"
19
+ },
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "dependencies": {
24
+ "qrcode": "^1.5.4",
25
+ "@larksuiteoapi/node-sdk": "1.74.0"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "registry": "https://registry.npmjs.org"
30
+ }
31
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-bridge-qwenwork",
3
+ "version": "1.0.3",
4
+ "description": "agent-bridge 的千问办公插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-agent"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/qwenwork"
13
+ },
14
+ "main": "dist/src-ts/plugins/qwenwork/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "agent"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ }
32
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "agent-bridge-workbuddy",
3
+ "version": "1.1.2",
4
+ "description": "agent-bridge 的 CodeBuddy CLI 插件。",
5
+ "keywords": [
6
+ "mu-agent-bridge-plugin",
7
+ "mu-agent-bridge-agent"
8
+ ],
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
12
+ "directory": "packages/workbuddy"
13
+ },
14
+ "main": "dist/src-ts/plugins/workbuddy/index.js",
15
+ "files": [
16
+ "dist/",
17
+ "plugin.json",
18
+ "config.schema.json",
19
+ "README.md"
20
+ ],
21
+ "agentBridge": {
22
+ "plugin": "./plugin.json",
23
+ "type": "agent"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public",
27
+ "registry": "https://registry.npmjs.org"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ }
32
+ }