mu-agent-bridge 1.0.73 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +7 -28
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +7 -2
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-manager.js +185 -58
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +89 -11
- package/dist/src-ts/plugins/aun/messages.js +1 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/dist/src-ts/plugins/doubao/index.js +119 -7
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +102 -23
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +27 -8
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-model-config.test.js +44 -41
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +14 -0
- package/dist/tests-ts/aun-plugin.test.js +207 -19
- package/dist/tests-ts/bundled-package.test.js +1 -1
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
- package/dist/tests-ts/control-server.test.js +20 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +688 -19
- package/dist/tests-ts/dsh-agent.test.js +91 -19
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/local-cli.test.js +29 -2
- package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant.test.js +277 -3
- package/dist/tests-ts/platform-integration.test.js +60 -1
- package/dist/tests-ts/plugin-manager.test.js +78 -6
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +1 -1
- package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +8 -8
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +1 -1
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +3 -2
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/plugins/personal-assistant/package.json +2 -2
- package/plugins/personal-assistant/plugin.json +1 -1
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -615,6 +615,41 @@ async function withMappedNpmPackage(run) {
|
|
|
615
615
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
616
616
|
}
|
|
617
617
|
});
|
|
618
|
+
(0, node_test_1.default)("插件整体默认配置用于启动并保留用户显式限制", async () => {
|
|
619
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-config-defaults-"));
|
|
620
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
|
|
621
|
+
try {
|
|
622
|
+
for (const id of ["with-defaults", "without-defaults"]) {
|
|
623
|
+
const dir = (0, node_path_1.join)(root, "plugins", id);
|
|
624
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
625
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, version: "1.0.0", entry: "index.js", configSchema: "config.schema.json", apiVersion: "1", capabilities: ["chat"] }));
|
|
626
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "config.schema.json"), JSON.stringify({ type: "object", properties: { allowedPeers: { type: "array", items: { type: "string" }, default: ["*"] }, allowedAgents: { type: "array", items: { type: "string" } }, aid: { type: "string" } }, ...(id === "with-defaults" ? { default: { allowedPeers: ["*"], allowedAgents: ["workbuddy"] } } : {}) }));
|
|
627
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), "module.exports=class{async activate(c){this.config=c.config}async deactivate(){}async health(){return {status:'ready'}}async handle(){return {text:JSON.stringify(this.config)}}};");
|
|
628
|
+
}
|
|
629
|
+
await manager.discover();
|
|
630
|
+
const defaults = { allowedPeers: ["*"], allowedAgents: ["workbuddy"] };
|
|
631
|
+
strict_1.default.deepEqual(await manager.config("with-defaults"), defaults);
|
|
632
|
+
await manager.start("with-defaults");
|
|
633
|
+
strict_1.default.deepEqual(JSON.parse((await manager.handle("with-defaults", { task: "查看配置" })).text), defaults);
|
|
634
|
+
await manager.saveConfig("with-defaults", { aid: "assistant.agentid.cn" });
|
|
635
|
+
strict_1.default.deepEqual(await manager.config("with-defaults"), { ...defaults, aid: "assistant.agentid.cn" });
|
|
636
|
+
await manager.saveConfig("with-defaults", { allowedPeers: [], allowedAgents: [] });
|
|
637
|
+
strict_1.default.deepEqual(await manager.config("with-defaults"), { allowedPeers: [], allowedAgents: [], aid: "assistant.agentid.cn" });
|
|
638
|
+
await manager.reload("with-defaults");
|
|
639
|
+
strict_1.default.deepEqual(JSON.parse((await manager.handle("with-defaults", { task: "查看限制" })).text).allowedPeers, []);
|
|
640
|
+
strict_1.default.deepEqual(await manager.config("without-defaults"), {});
|
|
641
|
+
for (const value of ["{损坏", "null", "[]", '"无效配置"']) {
|
|
642
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "with-defaults", "config.json"), value);
|
|
643
|
+
await strict_1.default.rejects(manager.config("with-defaults"), /配置.*无法读取|配置.*无效/);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
if (manager.get("with-defaults")?.instance)
|
|
648
|
+
await manager.stop("with-defaults");
|
|
649
|
+
await manager.logs.flush();
|
|
650
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
651
|
+
}
|
|
652
|
+
});
|
|
618
653
|
(0, node_test_1.default)("插件配置按 schema 重载并拒绝无效值", async () => {
|
|
619
654
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-config-"));
|
|
620
655
|
try {
|
|
@@ -857,13 +892,13 @@ async function withMappedNpmPackage(run) {
|
|
|
857
892
|
const pendingRequest = ingress.publish({ channel: "aun", peerId: "admin.agentid.cn", text: "撤权竞态", agentPlugin: "personal-assistant", reply: async () => undefined });
|
|
858
893
|
await managementReady;
|
|
859
894
|
// 直接更新配置以验证逐次授权检查,不依赖配置保存触发的插件重载。
|
|
860
|
-
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "personal-assistant", "config.json"), JSON.stringify({ allowedPeers: [], allowedAgents: ["target"] }));
|
|
895
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "state", "personal-assistant", "config.json"), JSON.stringify({ allowedPeers: ["other.agentid.cn"], allowedAgents: ["target"] }));
|
|
861
896
|
globalThis.managementContinue();
|
|
862
897
|
globalThis.managementContinue();
|
|
863
898
|
await pendingRequest;
|
|
864
899
|
strict_1.default.match(globalThis.managementDenied, /无权/);
|
|
865
900
|
delete globalThis.managementPause;
|
|
866
|
-
await manager.saveConfig("personal-assistant", { allowedPeers: [], allowedAgents: ["target"] });
|
|
901
|
+
await manager.saveConfig("personal-assistant", { allowedPeers: ["other.agentid.cn"], allowedAgents: ["target"] });
|
|
867
902
|
let unauthorizedReply = "";
|
|
868
903
|
await ingress.publish({ channel: "aun", peerId: "admin.agentid.cn", text: "再次请求", agentPlugin: "personal-assistant", reply: async (text) => { unauthorizedReply = text; } });
|
|
869
904
|
strict_1.default.equal(unauthorizedReply, "untrusted");
|
|
@@ -1009,9 +1044,11 @@ async function withMappedNpmPackage(run) {
|
|
|
1009
1044
|
await globalThis.attachmentIngress.publish(message);
|
|
1010
1045
|
strict_1.default.equal(globalThis.attachmentRequest.task, "原文");
|
|
1011
1046
|
strict_1.default.deepEqual(globalThis.attachmentRequest.attachments, [item]);
|
|
1047
|
+
strict_1.default.equal(globalThis.attachmentRequest.attachmentOutput, true);
|
|
1012
1048
|
strict_1.default.deepEqual(received, { items: [{ ...png, kind: "image" }], text: "生成结果" });
|
|
1013
1049
|
let failureText = "";
|
|
1014
1050
|
await strict_1.default.rejects(() => globalThis.attachmentIngress.publish({ ...message, replyAttachments: undefined, reply: async (text) => { failureText = text; } }), /不支持附件回传/);
|
|
1051
|
+
strict_1.default.equal(globalThis.attachmentRequest.attachmentOutput, undefined);
|
|
1015
1052
|
strict_1.default.match(failureText, /不支持附件回传/);
|
|
1016
1053
|
}
|
|
1017
1054
|
finally {
|
|
@@ -1248,7 +1285,7 @@ async function withMappedNpmPackage(run) {
|
|
|
1248
1285
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1249
1286
|
}
|
|
1250
1287
|
});
|
|
1251
|
-
(0, node_test_1.default)("
|
|
1288
|
+
(0, node_test_1.default)("限制模式下归档只信任真实通道,管理命令不会执行 Agent 或归档产物", async () => {
|
|
1252
1289
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "manager-session-history-"));
|
|
1253
1290
|
const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
|
|
1254
1291
|
let summaryRequest = "";
|
|
@@ -1279,7 +1316,7 @@ async function withMappedNpmPackage(run) {
|
|
|
1279
1316
|
await strict_1.default.rejects(globalThis.historyIngress_fake.publish(message("伪造")), /通道|来源/);
|
|
1280
1317
|
await ingress.publish(message("默认不归档"));
|
|
1281
1318
|
strict_1.default.equal((await manager.sessionManagement.archive.listSessions()).sessions.length, 0);
|
|
1282
|
-
await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
|
|
1319
|
+
await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [] });
|
|
1283
1320
|
await ingress.publish(message("正文", "peer", "same-message"));
|
|
1284
1321
|
await ingress.publish(message("正文", "peer", "same-message"));
|
|
1285
1322
|
const archived = (await manager.sessionManagement.archive.listSessions()).sessions;
|
|
@@ -1294,7 +1331,7 @@ async function withMappedNpmPackage(run) {
|
|
|
1294
1331
|
await ingress.publish(message("最近 4 小时有哪些话题", "admin"));
|
|
1295
1332
|
strict_1.default.match(replies.at(-1), /无权/);
|
|
1296
1333
|
strict_1.default.equal(globalThis.historyCalls, before);
|
|
1297
|
-
await manager.sessionManagement.saveConfig({ enabled: true, grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources: [archived[0].source] }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
|
|
1334
|
+
await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources: [archived[0].source] }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
|
|
1298
1335
|
await ingress.publish(message("/sessions list", "admin"));
|
|
1299
1336
|
const ref = replies.at(-1).split("\n")[0].split(" ")[0];
|
|
1300
1337
|
await ingress.publish(message(`/sessions summary --from ${ref} 比较内容`, "admin"));
|
|
@@ -1308,7 +1345,7 @@ async function withMappedNpmPackage(run) {
|
|
|
1308
1345
|
await ingress.publish(message("/sessions list", "admin"));
|
|
1309
1346
|
strict_1.default.ok(!replies.at(-1).includes("agent2"));
|
|
1310
1347
|
const sources = (await manager.sessionManagement.archive.listSessions()).sessions.map(item => item.source);
|
|
1311
|
-
await manager.sessionManagement.saveConfig({ enabled: true, grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
|
|
1348
|
+
await manager.sessionManagement.saveConfig({ enabled: true, allowFrom: ["history-admin.agentid.cn"], grants: [{ managerSource: admin.source, managerSessionId: admin.logicalSessionId, sources }], summarizer: { endpoint: `http://127.0.0.1:${summaryServer.address().port}/v1/chat/completions`, model: "read-only" } });
|
|
1312
1349
|
await ingress.publish(message("/sessions list", "admin"));
|
|
1313
1350
|
const refs = replies.at(-1).split("\n").filter(line => /^[a-f0-9]{12}\./.test(line)).map(line => line.split(" ")[0]);
|
|
1314
1351
|
strict_1.default.equal(refs.length, 2);
|
|
@@ -1523,3 +1560,38 @@ async function withMappedNpmPackage(run) {
|
|
|
1523
1560
|
}
|
|
1524
1561
|
}
|
|
1525
1562
|
});
|
|
1563
|
+
(0, node_test_1.default)("个人助理兼容旧 AUN 的只读身份查询,保留绑定并转交附件回传能力", async () => {
|
|
1564
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-pa-legacy-aun-"));
|
|
1565
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: root, stateDir: (0, node_path_1.join)(root, "state") });
|
|
1566
|
+
try {
|
|
1567
|
+
for (const [id, type, capabilities, code] of [
|
|
1568
|
+
["aun", "channel", ["channel"], "activate:async(c)=>{globalThis.legacyPaIngress=c.ingress},identity:{list:async()=>({evolcore:['worker.agentid.cn'],muAgentBridge:['pa.agentid.cn'],localMuAgentBridge:['local.agentid.cn']})},health:async()=>({status:'ready',details:{bindings:{'worker.agentid.cn':'workbuddy'}}})"],
|
|
1569
|
+
["personal-assistant", "agent", ["chat", "management"], "handle:async(r)=>{globalThis.legacyPaIdentities=await r.management.listAunIdentities();return r.management.callAgent('workbuddy',r.task)}"],
|
|
1570
|
+
["workbuddy", "agent", ["chat"], "sessionOperations:['new'],handle:async(r)=>{globalThis.legacyPaTarget=r;await r.onMessage('执行中');return {text:'完成'}}"],
|
|
1571
|
+
]) {
|
|
1572
|
+
const dir = (0, node_path_1.join)(root, id);
|
|
1573
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
1574
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, type, capabilities, version: "1.0.0", entry: "index.js", apiVersion: "1" }));
|
|
1575
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `module.exports={manifest:require('./plugin.json'),activate:async()=>{},deactivate:async()=>{},health:async()=>({status:'ready'}),${code}};`);
|
|
1576
|
+
}
|
|
1577
|
+
await manager.discover();
|
|
1578
|
+
await manager.saveConfig("workbuddy", { executablePath: process.execPath });
|
|
1579
|
+
await manager.saveConfig("personal-assistant", { allowedPeers: ["*"], allowedAgents: ["workbuddy"] });
|
|
1580
|
+
for (const id of ["workbuddy", "personal-assistant", "aun"])
|
|
1581
|
+
await manager.start(id);
|
|
1582
|
+
const replies = [];
|
|
1583
|
+
await globalThis.legacyPaIngress.publish({ channel: "aun", peerId: "owner.agentid.cn", agentPlugin: "personal-assistant", text: "生成图片", reply: async (s) => { replies.push(s); }, replyAttachments: async () => { } });
|
|
1584
|
+
strict_1.default.deepEqual(globalThis.legacyPaIdentities, { aids: ["local.agentid.cn", "pa.agentid.cn", "worker.agentid.cn"], bindings: { "worker.agentid.cn": "workbuddy" } });
|
|
1585
|
+
strict_1.default.equal(globalThis.legacyPaTarget.attachmentOutput, true);
|
|
1586
|
+
strict_1.default.equal(globalThis.legacyPaTarget.management, undefined);
|
|
1587
|
+
strict_1.default.deepEqual(replies, ["执行中", "完成"]);
|
|
1588
|
+
}
|
|
1589
|
+
finally {
|
|
1590
|
+
for (const id of ["aun", "personal-assistant", "workbuddy"])
|
|
1591
|
+
if (manager.get(id)?.instance)
|
|
1592
|
+
await manager.stop(id);
|
|
1593
|
+
for (const key of ["legacyPaIngress", "legacyPaIdentities", "legacyPaTarget"])
|
|
1594
|
+
delete globalThis[key];
|
|
1595
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
@@ -0,0 +1,354 @@
|
|
|
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_crypto_1 = require("node:crypto");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
12
|
+
const agent_1 = require("../src-ts/agent");
|
|
13
|
+
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
14
|
+
const version_manager_1 = require("../src-ts/version-manager");
|
|
15
|
+
const TWO_HOURS = 2 * 60 * 60 * 1000;
|
|
16
|
+
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
17
|
+
async function until(check) {
|
|
18
|
+
for (let i = 0; i < 2_000; i++) {
|
|
19
|
+
if (check())
|
|
20
|
+
return;
|
|
21
|
+
await flush();
|
|
22
|
+
}
|
|
23
|
+
strict_1.default.ok(check(), "等待预期状态超时");
|
|
24
|
+
}
|
|
25
|
+
function deferred() {
|
|
26
|
+
let resolve;
|
|
27
|
+
const promise = new Promise(done => { resolve = done; });
|
|
28
|
+
return { promise, resolve };
|
|
29
|
+
}
|
|
30
|
+
function cancelled(task, pattern = /PLUGIN_UPGRADE_WAIT_TIMEOUT/) {
|
|
31
|
+
const checked = strict_1.default.rejects(task, pattern);
|
|
32
|
+
void checked.catch(() => undefined);
|
|
33
|
+
return checked;
|
|
34
|
+
}
|
|
35
|
+
async function fixture(t, run) {
|
|
36
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-upgrade-deadline-"));
|
|
37
|
+
const key = `upgrade-${(0, node_crypto_1.randomUUID)()}`;
|
|
38
|
+
const gate = deferred();
|
|
39
|
+
const cleanup = deferred();
|
|
40
|
+
const state = { contexts: {}, signals: [], stopping: [], cleanupOptions: [], fail: false, holdCleanup: false, ignoreSignal: false };
|
|
41
|
+
state.run = async (request) => {
|
|
42
|
+
state.signals.push(request.signal);
|
|
43
|
+
state.request = request;
|
|
44
|
+
await (state.ignoreSignal ? gate.promise : (0, agent_1.abortable)(gate.promise, request.signal));
|
|
45
|
+
await request.onMessage?.("旧任务的迟到回复");
|
|
46
|
+
return { text: "旧任务完成" };
|
|
47
|
+
};
|
|
48
|
+
globalThis[key] = state;
|
|
49
|
+
const stateDir = (0, node_path_1.join)(root, "state");
|
|
50
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(stateDir, "npm"), { recursive: true });
|
|
51
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir,
|
|
52
|
+
versions: new version_manager_1.VersionManager({ stateDir, npm: async () => '"1.0.1"' }), publisherResolver: async () => "m2lihui" });
|
|
53
|
+
const pending = [];
|
|
54
|
+
const add = async (id, type = "agent") => {
|
|
55
|
+
for (const [dir, version] of [[(0, node_path_1.join)(root, "plugins", id), "1.0.0"], [(0, node_path_1.join)(stateDir, "downloads", `agent-bridge-${id}`, "1.0.1", "node_modules", `agent-bridge-${id}`), "1.0.1"]]) {
|
|
56
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
57
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: `agent-bridge-${id}`, version, agentBridge: { plugin: "./plugin.json" } }));
|
|
58
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, type, version, entry: "index.js", apiVersion: "1", capabilities: type === "channel" ? ["channel"] : id === "personal-assistant" ? ["chat", "management"] : ["chat", "model-provider"] }));
|
|
59
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `
|
|
60
|
+
const state=globalThis[${JSON.stringify(key)}];
|
|
61
|
+
module.exports=class {
|
|
62
|
+
constructor(){this.pending=new Set();this.modelProvider={complete:r=>this.handle({task:r.prompt,...r})}}
|
|
63
|
+
get sessionOperations(){return ['new']}
|
|
64
|
+
async controlSession(r){return {...await this.handle({task:'/new'}),sessionId:r.sessionId}}
|
|
65
|
+
async activate(c){this.context=c;state.contexts[${JSON.stringify(id)}]=${JSON.stringify(type)}==='channel'?{...c,ingress:{publish:async m=>{const task=c.ingress.publish(m);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}}}:c;this.dispose=c.http.register('GET','/${id}/deadline',(_q,r)=>r.end('${version}'));if('${version}'==='1.0.1'&&state.fail)throw Error('新版启动失败')}
|
|
66
|
+
async deactivate(reason,options){state.stopping.push('${id}@${version}');state.cleanupOptions.push(options);await Promise.allSettled([...this.pending]);if('${version}'==='1.0.0'&&state.failCleanup){state.failCleanup=false;throw Error('旧插件清理失败')}if('${version}'==='1.0.0'&&state.holdCleanup)await state.cleanup;this.dispose();this.context.http.unregister()}
|
|
67
|
+
async health(){return {status:'ready'}}
|
|
68
|
+
async handle(r){if('${version}'!=='1.0.0')return {text:'新版完成'};const task=state.run(r);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}
|
|
69
|
+
};`);
|
|
70
|
+
}
|
|
71
|
+
await manager.discover();
|
|
72
|
+
await manager.start(id);
|
|
73
|
+
};
|
|
74
|
+
state.cleanup = cleanup.promise;
|
|
75
|
+
const upgrade = (id = "demo") => {
|
|
76
|
+
const promise = manager.upgradePackage(id, `agent-bridge-${id}`, "1.0.1");
|
|
77
|
+
pending.push(promise);
|
|
78
|
+
void promise.catch(() => undefined);
|
|
79
|
+
return promise;
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await add("demo");
|
|
83
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
84
|
+
await run({ manager, gate, cleanup, state, upgrade, add });
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
gate.resolve();
|
|
88
|
+
cleanup.resolve();
|
|
89
|
+
t.mock.timers.reset();
|
|
90
|
+
await Promise.allSettled(pending);
|
|
91
|
+
for (const plugin of manager.list())
|
|
92
|
+
if (manager.get(plugin.id)?.instance)
|
|
93
|
+
await manager.stop(plugin.id);
|
|
94
|
+
await manager.logs.flush();
|
|
95
|
+
delete globalThis[key];
|
|
96
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 10 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
(0, node_test_1.default)("插件升级等待满两小时才取消旧任务并继续安装", async (t) => {
|
|
100
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
101
|
+
const task = manager.handle("demo", { task: "长任务" });
|
|
102
|
+
const failure = cancelled(task, /PLUGIN_UPGRADE_WAIT_TIMEOUT.*两小时/);
|
|
103
|
+
await until(() => state.signals.length === 1);
|
|
104
|
+
const updated = upgrade();
|
|
105
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
106
|
+
t.mock.timers.tick(TWO_HOURS - 1);
|
|
107
|
+
await flush();
|
|
108
|
+
strict_1.default.equal(Boolean(state.signals[0]?.aborted), false);
|
|
109
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
110
|
+
strict_1.default.deepEqual(state.stopping, [], "两小时内不能提前调用可能取消任务的 deactivate");
|
|
111
|
+
t.mock.timers.tick(1);
|
|
112
|
+
await flush();
|
|
113
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
114
|
+
await failure;
|
|
115
|
+
await updated;
|
|
116
|
+
strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }]);
|
|
117
|
+
strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
|
|
118
|
+
await manager.logs.flush();
|
|
119
|
+
const logs = (await manager.logs.page()).logs;
|
|
120
|
+
strict_1.default.ok(logs.some(log => log.fields?.waitMs === TWO_HOURS && typeof log.fields?.deadline === "string"));
|
|
121
|
+
strict_1.default.ok(logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
(0, node_test_1.default)("旧任务及时完成则正常升级且清除强制取消定时器", async (t) => {
|
|
125
|
+
await fixture(t, async ({ manager, state, gate, upgrade }) => {
|
|
126
|
+
const task = manager.handle("demo", { task: "短任务" });
|
|
127
|
+
await until(() => state.signals.length === 1);
|
|
128
|
+
const updated = upgrade();
|
|
129
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
130
|
+
gate.resolve();
|
|
131
|
+
strict_1.default.equal((await task).text, "旧任务完成");
|
|
132
|
+
await updated;
|
|
133
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
134
|
+
await flush();
|
|
135
|
+
await manager.logs.flush();
|
|
136
|
+
strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
137
|
+
strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
(0, node_test_1.default)("不响应取消的旧任务不能继续阻塞升级、发送迟到回复或注销新版路由", async (t) => {
|
|
141
|
+
await fixture(t, async ({ manager, state, cleanup, gate, upgrade, add }) => {
|
|
142
|
+
await add("other");
|
|
143
|
+
state.ignoreSignal = true;
|
|
144
|
+
state.holdCleanup = true;
|
|
145
|
+
const replies = [];
|
|
146
|
+
const task = manager.handle("demo", { task: "挂起任务", onMessage: async (text) => { replies.push(text); } });
|
|
147
|
+
const failure = cancelled(task);
|
|
148
|
+
await until(() => state.signals.length === 1);
|
|
149
|
+
const oldContext = state.contexts.demo;
|
|
150
|
+
const updated = upgrade();
|
|
151
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
152
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
153
|
+
await flush();
|
|
154
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
155
|
+
t.mock.timers.tick(5_000);
|
|
156
|
+
await flush();
|
|
157
|
+
await failure;
|
|
158
|
+
await updated;
|
|
159
|
+
let reply = "";
|
|
160
|
+
const readRoute = () => manager.dispatchHttp({ method: "GET", url: "/demo/deadline" }, { end: (text) => { reply = text; } });
|
|
161
|
+
strict_1.default.equal(await readRoute(), true);
|
|
162
|
+
strict_1.default.equal(reply, "1.0.1");
|
|
163
|
+
gate.resolve();
|
|
164
|
+
cleanup.resolve();
|
|
165
|
+
await flush();
|
|
166
|
+
await flush();
|
|
167
|
+
strict_1.default.deepEqual(replies, []);
|
|
168
|
+
strict_1.default.equal(await readRoute(), true);
|
|
169
|
+
strict_1.default.equal(reply, "1.0.1");
|
|
170
|
+
strict_1.default.throws(() => oldContext.http.register("GET", "/stale", () => undefined), /停止|取消|结束/);
|
|
171
|
+
await strict_1.default.rejects(oldContext.callPlugin("other", { task: "过期调用" }), /停止|取消|结束|PLUGIN_UPGRADE_WAIT_TIMEOUT/);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
(0, node_test_1.default)("模型来源任务也在插件升级等待两小时后取消", async (t) => {
|
|
175
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
176
|
+
const task = manager.completeModelProvider("demo", { prompt: "模型任务", sessionId: "model-session", timeoutMs: 120_000, signal: new AbortController().signal });
|
|
177
|
+
const failure = cancelled(task);
|
|
178
|
+
await until(() => state.signals.length === 1);
|
|
179
|
+
const updated = upgrade();
|
|
180
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
181
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
182
|
+
await flush();
|
|
183
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
184
|
+
await failure;
|
|
185
|
+
await updated;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
(0, node_test_1.default)("通道升级期限取消来源任务并传递到目标 Agent,保留原会话记录", async (t) => {
|
|
189
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
190
|
+
await add("aun", "channel");
|
|
191
|
+
const source = state.contexts.aun;
|
|
192
|
+
const task = source.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "长任务", reply: async () => undefined });
|
|
193
|
+
const failure = cancelled(task);
|
|
194
|
+
await until(() => state.signals.length === 1);
|
|
195
|
+
const updated = upgrade("aun");
|
|
196
|
+
await until(() => manager.get("aun").record.status === "stopping");
|
|
197
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
198
|
+
await flush();
|
|
199
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
200
|
+
await failure;
|
|
201
|
+
await updated;
|
|
202
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
203
|
+
strict_1.default.equal(manager.sessions.isBusy('["aun","","","peer.agentid.cn",""]'), false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
(0, node_test_1.default)("插件间调用随来源插件的升级期限取消,不取消无关插件实例", async (t) => {
|
|
207
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
208
|
+
await add("parent");
|
|
209
|
+
const original = state.run;
|
|
210
|
+
state.run = (request) => request.task === "父任务"
|
|
211
|
+
? state.contexts.parent.callPlugin("demo", { task: "子任务" })
|
|
212
|
+
: original(request);
|
|
213
|
+
const task = manager.handle("parent", { task: "父任务" });
|
|
214
|
+
const failure = cancelled(task);
|
|
215
|
+
await until(() => state.signals.length === 1);
|
|
216
|
+
const updated = upgrade("parent");
|
|
217
|
+
await until(() => manager.get("parent").record.status === "stopping");
|
|
218
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
219
|
+
await flush();
|
|
220
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
221
|
+
await failure;
|
|
222
|
+
await updated;
|
|
223
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
(0, node_test_1.default)("强制取消后新版启动失败能回滚,下一次升级重新等待两小时", async (t) => {
|
|
227
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
228
|
+
state.fail = true;
|
|
229
|
+
const first = cancelled(manager.handle("demo", { task: "第一项任务" }));
|
|
230
|
+
await until(() => state.signals.length === 1);
|
|
231
|
+
const updated = upgrade();
|
|
232
|
+
const rollback = strict_1.default.rejects(updated, /新版启动失败/);
|
|
233
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
234
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
235
|
+
await flush();
|
|
236
|
+
await first;
|
|
237
|
+
await rollback;
|
|
238
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
239
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
240
|
+
strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }, { preserveDesktop: true }]);
|
|
241
|
+
state.fail = false;
|
|
242
|
+
await add("demo");
|
|
243
|
+
const second = cancelled(manager.handle("demo", { task: "第二项任务" }));
|
|
244
|
+
await until(() => state.signals.length === 2);
|
|
245
|
+
strict_1.default.equal(state.signals[1].aborted, false);
|
|
246
|
+
const retried = upgrade();
|
|
247
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
248
|
+
t.mock.timers.tick(TWO_HOURS - 1);
|
|
249
|
+
await flush();
|
|
250
|
+
strict_1.default.equal(state.signals[1].aborted, false);
|
|
251
|
+
t.mock.timers.tick(1);
|
|
252
|
+
await flush();
|
|
253
|
+
await second;
|
|
254
|
+
await retried;
|
|
255
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
(0, node_test_1.default)("旧插件清理失败时恢复旧版本并清除升级期限", async (t) => {
|
|
259
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
260
|
+
state.failCleanup = true;
|
|
261
|
+
await strict_1.default.rejects(upgrade(), /旧插件清理失败/);
|
|
262
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
263
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
264
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
265
|
+
await flush();
|
|
266
|
+
await manager.logs.flush();
|
|
267
|
+
strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
(0, node_test_1.default)("普通停止不套用升级的两小时强制取消期限", async (t) => {
|
|
271
|
+
await fixture(t, async ({ manager, state, gate }) => {
|
|
272
|
+
const task = manager.handle("demo", { task: "普通任务" });
|
|
273
|
+
await until(() => state.signals.length === 1);
|
|
274
|
+
const stopping = manager.stop("demo");
|
|
275
|
+
await until(() => state.stopping.length === 1);
|
|
276
|
+
t.mock.timers.tick(TWO_HOURS + 5_000);
|
|
277
|
+
await flush();
|
|
278
|
+
strict_1.default.equal(state.signals[0].aborted, false);
|
|
279
|
+
gate.resolve();
|
|
280
|
+
await task;
|
|
281
|
+
await stopping;
|
|
282
|
+
strict_1.default.deepEqual(state.cleanupOptions, [undefined]);
|
|
283
|
+
strict_1.default.equal(manager.get("demo").record.status, "disabled");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
(0, node_test_1.default)("个人助理升级期限传递到管理能力启动的子 Agent 任务", async (t) => {
|
|
287
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
288
|
+
await add("personal-assistant");
|
|
289
|
+
await add("aun", "channel");
|
|
290
|
+
await manager.saveConfig("personal-assistant", { allowedPeers: ["*"], allowedAgents: ["demo"] });
|
|
291
|
+
const original = state.run;
|
|
292
|
+
state.run = (request) => request.task === "助理任务"
|
|
293
|
+
? request.management.callAgent("demo", "子任务")
|
|
294
|
+
: original(request);
|
|
295
|
+
const failure = cancelled(state.contexts.aun.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "personal-assistant", text: "助理任务", reply: async () => undefined }));
|
|
296
|
+
await until(() => state.signals.length === 1);
|
|
297
|
+
const updated = upgrade("personal-assistant");
|
|
298
|
+
await until(() => manager.get("personal-assistant").record.status === "stopping");
|
|
299
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
300
|
+
await flush();
|
|
301
|
+
strict_1.default.equal(state.signals[0]?.aborted, true, "助理取消不能留下仍在执行的桥接子任务");
|
|
302
|
+
await failure;
|
|
303
|
+
await updated;
|
|
304
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
(0, node_test_1.default)("挂起的新建会话操作同样受插件升级期限约束", async (t) => {
|
|
308
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
309
|
+
let rejected = false;
|
|
310
|
+
const task = manager.controlSession("demo", { operation: "new", sessionId: "new-session" }).catch(error => { rejected = true; throw error; });
|
|
311
|
+
const failure = cancelled(task);
|
|
312
|
+
await until(() => state.signals.length === 1);
|
|
313
|
+
const updated = upgrade();
|
|
314
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
315
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
316
|
+
await flush();
|
|
317
|
+
strict_1.default.equal(rejected, true);
|
|
318
|
+
t.mock.timers.tick(5_000);
|
|
319
|
+
await flush();
|
|
320
|
+
await failure;
|
|
321
|
+
await updated;
|
|
322
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
for (const delivery of ["文本", "附件"])
|
|
326
|
+
(0, node_test_1.default)(`升级取消挂起的${delivery}回传后释放来源忙碌状态并记录具体错误`, async (t) => {
|
|
327
|
+
await fixture(t, async ({ manager, state, add, gate, upgrade }) => {
|
|
328
|
+
await add("aun", "channel");
|
|
329
|
+
await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
|
|
330
|
+
state.run = async () => ({ text: "回传结果", ...(delivery === "附件" ? { attachments: [{ kind: "file", data: new Uint8Array([42]), filename: "结果.txt", contentType: "text/plain" }] } : {}) });
|
|
331
|
+
let delivering = false;
|
|
332
|
+
const deliver = async () => { delivering = true; await gate.promise; };
|
|
333
|
+
const message = { channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "生成文件", reply: deliver, replyAttachments: deliver };
|
|
334
|
+
const failure = cancelled(state.contexts.aun.ingress.publish(message));
|
|
335
|
+
await until(() => delivering);
|
|
336
|
+
const binding = await manager.sessions.current(message, "demo");
|
|
337
|
+
const updated = upgrade("aun");
|
|
338
|
+
await until(() => manager.get("aun").record.status === "stopping");
|
|
339
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
340
|
+
await flush();
|
|
341
|
+
t.mock.timers.tick(5_000);
|
|
342
|
+
await flush();
|
|
343
|
+
await failure;
|
|
344
|
+
await updated;
|
|
345
|
+
const sessions = await manager.sessionManagement.command("/sessions list", { source: binding.source, sessionId: "另一个管理会话" });
|
|
346
|
+
strict_1.default.match(sessions, new RegExp(binding.logicalSessionId));
|
|
347
|
+
strict_1.default.doesNotMatch(sessions, /正在运行/);
|
|
348
|
+
strict_1.default.ok(manager.recentMessages().some(item => item.direction === "error" && item.text.includes("PLUGIN_UPGRADE_WAIT_TIMEOUT")));
|
|
349
|
+
gate.resolve();
|
|
350
|
+
await flush();
|
|
351
|
+
strict_1.default.ok(!manager.recentMessages().some(item => item.direction === "reply"));
|
|
352
|
+
strict_1.default.equal((await manager.sessionManagement.archive.snapshotSessions()).sessions.length, 0);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -16,7 +16,7 @@ const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
|
16
16
|
const bin = (0, node_path_1.join)(root, "bin");
|
|
17
17
|
const packageRoot = (0, node_path_1.join)(root, "packages", "personal-assistant");
|
|
18
18
|
try {
|
|
19
|
-
const ids = ["qwenwork", "abweb", "dsh", "doubao", "aun", "workbuddy", "feishu", "chatgpt", "codex-cli", "claude-cli", "personal-assistant"];
|
|
19
|
+
const ids = ["qwenwork", "abweb", "dsh", "doubao", "aun", "workbuddy", "zcode", "feishu", "chatgpt", "codex-cli", "claude-cli", "personal-assistant"];
|
|
20
20
|
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.0.0" }));
|
|
21
21
|
for (const id of ids) {
|
|
22
22
|
const directory = (0, node_path_1.join)(root, "packages", id);
|
|
@@ -251,6 +251,36 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
251
251
|
await plugin.deactivate("stop");
|
|
252
252
|
strict_1.default.equal((await plugin.health()).status, "stopped");
|
|
253
253
|
});
|
|
254
|
+
(0, node_test_1.default)("千问办公仅在重载时请求结束桌面进程", async () => {
|
|
255
|
+
let restarts = 0;
|
|
256
|
+
const client = {
|
|
257
|
+
connect: async () => undefined,
|
|
258
|
+
close: () => undefined,
|
|
259
|
+
connected: () => true,
|
|
260
|
+
send: async () => undefined,
|
|
261
|
+
direct: async () => "回复",
|
|
262
|
+
};
|
|
263
|
+
const plugin = new qwenwork_1.QwenworkPlugin({ client, httpPort: 0, restartDesktop: async () => { restarts++; } });
|
|
264
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
265
|
+
await plugin.deactivate("reload");
|
|
266
|
+
strict_1.default.equal(restarts, 1);
|
|
267
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
268
|
+
await plugin.deactivate("stop");
|
|
269
|
+
strict_1.default.equal(restarts, 1);
|
|
270
|
+
});
|
|
271
|
+
(0, node_test_1.default)("千问办公升级清理保留桌面进程", async () => {
|
|
272
|
+
let restarts = 0;
|
|
273
|
+
let closed = 0;
|
|
274
|
+
const plugin = new qwenwork_1.QwenworkPlugin({
|
|
275
|
+
httpPort: 0, restartDesktop: async () => { restarts++; },
|
|
276
|
+
client: { connect: async () => undefined, close: () => { closed++; }, connected: () => true, send: async () => undefined, direct: async () => "回复" },
|
|
277
|
+
});
|
|
278
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
279
|
+
await plugin.deactivate("reload", { preserveDesktop: true });
|
|
280
|
+
strict_1.default.equal(restarts, 0);
|
|
281
|
+
strict_1.default.equal(closed, 1);
|
|
282
|
+
strict_1.default.equal((await plugin.health()).status, "stopped");
|
|
283
|
+
});
|
|
254
284
|
(0, node_test_1.default)("千问办公 /new 调用真实新任务并返回逻辑会话标识", async () => {
|
|
255
285
|
let created = 0;
|
|
256
286
|
const plugin = new qwenwork_1.QwenworkPlugin({
|