mu-agent-bridge 1.0.31 → 1.0.32

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.
@@ -25,6 +25,7 @@ class PluginManager {
25
25
  options;
26
26
  controlUrl;
27
27
  plugins = new Map();
28
+ upgrades = new Map();
28
29
  httpRoutes = new Map();
29
30
  root;
30
31
  messages = [];
@@ -161,6 +162,22 @@ class PluginManager {
161
162
  })]);
162
163
  }
163
164
  async upgradePackage(id, packageName = `agent-bridge-${id}`, version) {
165
+ const pending = this.upgrades.get(id);
166
+ if (pending) {
167
+ if (pending.packageName === packageName && pending.version === version)
168
+ return pending.task;
169
+ throw new agent_1.AgentError("插件正在升级,请等待完成后再请求其他版本");
170
+ }
171
+ const task = this.performUpgrade(id, packageName, version);
172
+ this.upgrades.set(id, { packageName, version, task });
173
+ try {
174
+ return await task;
175
+ }
176
+ finally {
177
+ this.upgrades.delete(id);
178
+ }
179
+ }
180
+ async performUpgrade(id, packageName, version) {
164
181
  if (!/^agent-bridge-[a-z0-9-]+$/.test(packageName))
165
182
  throw new agent_1.AgentError("仅支持 agent-bridge-* npm 包名");
166
183
  if (version && !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(version))
@@ -233,6 +233,7 @@ class QwenworkPlugin {
233
233
  this.healthTimer = undefined;
234
234
  this.state = "stopped";
235
235
  this.stopped = true;
236
+ await this.requestQueue;
236
237
  if (this.recovery)
237
238
  await this.recovery;
238
239
  for (const dispose of this.disposers.splice(0))
@@ -72,6 +72,41 @@ const channel_sessions_1 = require("../src-ts/channel-sessions");
72
72
  await (0, promises_1.rm)(root, { recursive: true, force: true });
73
73
  }
74
74
  });
75
+ (0, node_test_1.default)("相同插件版本的并发升级只下载和重载一次", async () => {
76
+ const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-concurrent-update-"));
77
+ const name = "agent-bridge-demo";
78
+ const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state"),
79
+ versions: new version_manager_1.VersionManager({ stateDir: (0, node_path_1.join)(root, "state"), npm: async () => '"1.0.1"' }), publisherResolver: async () => "m2lihui" });
80
+ let release;
81
+ const gate = new Promise(resolve => { release = resolve; });
82
+ let downloads = 0;
83
+ let results;
84
+ try {
85
+ for (const [dir, version] of [[(0, node_path_1.join)(root, "plugins", "demo"), "1.0.0"], [(0, node_path_1.join)(root, "download", "node_modules", name), "1.0.1"]]) {
86
+ await (0, promises_1.mkdir)(dir, { recursive: true });
87
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name, version, agentBridge: { plugin: "./plugin.json" } }));
88
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id: "demo", version, entry: "index.js", apiVersion: "1", capabilities: [] }));
89
+ await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "module.exports={activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'})};");
90
+ }
91
+ await manager.discover();
92
+ await manager.start("demo");
93
+ manager.downloadPackage = async () => { downloads++; await gate; return (0, node_path_1.join)(root, "download"); };
94
+ results = Promise.allSettled([manager.upgradePackage("demo", name, "1.0.1"), manager.upgradePackage("demo", name, "1.0.1")]);
95
+ for (let i = 0; i < 100 && downloads === 0; i++)
96
+ await new Promise(resolve => setTimeout(resolve, 1));
97
+ await new Promise(resolve => setTimeout(resolve, 5));
98
+ strict_1.default.equal(downloads, 1);
99
+ release();
100
+ strict_1.default.ok((await results).every(result => result.status === "fulfilled"));
101
+ }
102
+ finally {
103
+ release();
104
+ await results;
105
+ if (manager.get("demo")?.instance)
106
+ await manager.stop("demo");
107
+ await (0, promises_1.rm)(root, { recursive: true, force: true });
108
+ }
109
+ });
75
110
  (0, node_test_1.default)("随包插件检查独立 npm 更新,升级后重启仍选新副本,失败恢复内置版本", async () => {
76
111
  const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-bundled-update-"));
77
112
  const stateDir = (0, node_path_1.join)(root, "state");
@@ -378,6 +378,26 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
378
378
  await plugin.deactivate("stop");
379
379
  }
380
380
  });
381
+ (0, node_test_1.default)("千问停用等待进行中的健康检查,避免重载重复发送心跳", async () => {
382
+ let release;
383
+ const plugin = new qwenwork_1.QwenworkPlugin({ httpPort: 0, client: {
384
+ connect: async () => undefined, close: () => undefined, connected: () => true,
385
+ send: async () => undefined, direct: async () => "回复",
386
+ healthCheck: async () => undefined, chatHeartbeat: async () => { await new Promise(resolve => { release = resolve; }); },
387
+ } });
388
+ await plugin.activate({ config: {}, dataDir: "." });
389
+ const internal = plugin;
390
+ for (let index = 0; index < 100 && !internal.checking; index++)
391
+ await new Promise(resolve => setTimeout(resolve, 1));
392
+ const stopping = plugin.deactivate("reload");
393
+ let finished = false;
394
+ void stopping.then(() => { finished = true; });
395
+ await new Promise(resolve => setTimeout(resolve, 5));
396
+ strict_1.default.equal(finished, false);
397
+ release();
398
+ await stopping;
399
+ strict_1.default.equal(finished, true);
400
+ });
381
401
  (0, node_test_1.default)("定时健康检查失败时只执行一次桌面恢复并重新连接", async () => {
382
402
  let healthy = false;
383
403
  let connected = true;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.31",
3
+ "version": "1.0.32",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {
@@ -233,6 +233,7 @@ class QwenworkPlugin {
233
233
  this.healthTimer = undefined;
234
234
  this.state = "stopped";
235
235
  this.stopped = true;
236
+ await this.requestQueue;
236
237
  if (this.recovery)
237
238
  await this.recovery;
238
239
  for (const dispose of this.disposers.splice(0))
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-qwenwork",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "agent-bridge 的千问办公插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "qwenwork",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "type": "agent",
5
5
  "displayName": "千问办公",
6
6
  "entry": "dist/src-ts/plugins/qwenwork/index.js",