mu-agent-bridge 1.0.71 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +12 -30
- 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 +31 -13
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +351 -50
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +155 -21
- package/dist/src-ts/plugins/aun/messages.js +4 -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/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/dist/src-ts/plugins/doubao/index.js +152 -8
- 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 +172 -21
- 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/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/dist/src-ts/plugins/workbuddy/index.js +124 -41
- 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 +132 -14
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +73 -0
- package/dist/tests-ts/agent.test.js +27 -0
- 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 +20 -0
- package/dist/tests-ts/aun-plugin.test.js +292 -19
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
- package/dist/tests-ts/control-server.test.js +56 -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 +773 -12
- package/dist/tests-ts/dsh-agent.test.js +157 -8
- 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/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +146 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +663 -0
- package/dist/tests-ts/platform-integration.test.js +183 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +356 -4
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
- 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 +159 -4
- 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 +499 -4
- 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/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- 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/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -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/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
- 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 +3 -2
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
- 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 +172 -21
- 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 +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- 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 +23 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- 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 +167 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- 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 +90 -18
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
- 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
|
@@ -12,28 +12,112 @@ const node_test_1 = __importDefault(require("node:test"));
|
|
|
12
12
|
const node_vm_1 = require("node:vm");
|
|
13
13
|
const aun_1 = require("../src-ts/plugins/aun");
|
|
14
14
|
const control_server_1 = require("../src-ts/control-server");
|
|
15
|
+
const zcode_1 = require("../src-ts/plugins/zcode");
|
|
15
16
|
const commandPath = "/api/plugins/aun/commands";
|
|
16
|
-
(0, node_test_1.default)("AUN
|
|
17
|
+
(0, node_test_1.default)("AUN 图片与说明分开发送,避免网页因普通正文隐藏图片卡片", async () => {
|
|
17
18
|
const image = { kind: "image", data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "生成图.png", contentType: "image/png" };
|
|
18
19
|
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
20
|
+
storage.sendResult = { status: "delivered" };
|
|
19
21
|
await receive({ from: "peer.agentid.cn", message_id: "pic-1", payload: { text: "画猫", thread_id: "topic-1" } });
|
|
20
22
|
strict_1.default.equal(storage.writes.length, 1);
|
|
21
23
|
strict_1.default.deepEqual(storage.writes[0].data, image.data);
|
|
22
|
-
strict_1.default.equal(storage.writes[0].options.public,
|
|
23
|
-
strict_1.default.equal(storage.writes[0].options.overwrite,
|
|
24
|
-
strict_1.default.
|
|
25
|
-
strict_1.default.equal(sent.length,
|
|
24
|
+
strict_1.default.equal(storage.writes[0].options.public, true);
|
|
25
|
+
strict_1.default.equal(storage.writes[0].options.overwrite, true);
|
|
26
|
+
strict_1.default.equal(storage.shares.length, 0);
|
|
27
|
+
strict_1.default.equal(sent.length, 2);
|
|
26
28
|
strict_1.default.equal(sent[0].to, "peer.agentid.cn");
|
|
27
29
|
strict_1.default.equal(sent[0].encrypt, true);
|
|
28
30
|
strict_1.default.deepEqual(sent[0].payload, {
|
|
29
|
-
type: "image",
|
|
31
|
+
type: "image", thread_id: "topic-1", ref_message_id: "pic-1",
|
|
30
32
|
attachments: [{
|
|
31
|
-
owner_aid: "local.agentid.cn",
|
|
33
|
+
owner_aid: "local.agentid.cn", object_key: storage.writes[0].path.slice(1), filename: "生成图.png", content_type: "image/png",
|
|
32
34
|
size_bytes: image.data.length, sha256: (0, node_crypto_1.createHash)("sha256").update(image.data).digest("hex"),
|
|
33
35
|
}],
|
|
34
36
|
});
|
|
37
|
+
strict_1.default.deepEqual(sent[1], {
|
|
38
|
+
to: "peer.agentid.cn", encrypt: true,
|
|
39
|
+
payload: { type: "text", text: `生成完成\n附件下载链接:\nhttps://local.agentid.cn/${storage.writes[0].path.slice(1)}`, thread_id: "topic-1", ref_message_id: "pic-1" },
|
|
40
|
+
});
|
|
35
41
|
}, async (message) => { await message.replyAttachments?.([image], "生成完成"); });
|
|
36
42
|
});
|
|
43
|
+
(0, node_test_1.default)("AUN 图片已送达但说明发送失败时明确阶段,不重发图片或降级明文", async () => {
|
|
44
|
+
const image = { kind: "image", data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "图.png", contentType: "image/png" };
|
|
45
|
+
await withAunAttachmentPlugin(async (receive, sent, storage, logs) => {
|
|
46
|
+
storage.sendFailure = (params) => params.payload.type === "text" && !params.payload.text.startsWith("附件回传失败");
|
|
47
|
+
await receive({ from: "peer.agentid.cn", message_id: "caption-1", payload: { text: "画图" } });
|
|
48
|
+
strict_1.default.equal(sent.filter(item => item.payload.type === "image").length, 1);
|
|
49
|
+
strict_1.default.equal(storage.writes.length, 1);
|
|
50
|
+
strict_1.default.ok(sent.every(item => item.encrypt === true));
|
|
51
|
+
const failure = sent.find(item => item.payload.text?.startsWith("附件回传失败"));
|
|
52
|
+
strict_1.default.match(failure?.payload.text, /AUN_ATTACHMENT_CAPTION_SEND_FAILED.*图片已发送.*说明/);
|
|
53
|
+
strict_1.default.match(failure?.payload.text, /https:\/\/local\.agentid\.cn\/shared\//);
|
|
54
|
+
strict_1.default.doesNotMatch(JSON.stringify(logs), /private-token/);
|
|
55
|
+
}, async (message) => { await message.replyAttachments([image], "生成完成"); });
|
|
56
|
+
});
|
|
57
|
+
(0, node_test_1.default)("AUN 图片发送失败时向原 Peer 回传可直接读取的下载链接", async () => {
|
|
58
|
+
const image = { kind: "image", data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "生成图.png", contentType: "image/png" };
|
|
59
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
60
|
+
storage.sendResult = { status: "failed" };
|
|
61
|
+
await receive({ from: "peer.agentid.cn", message_id: "pic-failed-1", payload: { text: "画猫", thread_id: "topic-1" } });
|
|
62
|
+
const fallback = sent.find((item) => item.payload.type === "text");
|
|
63
|
+
strict_1.default.ok(fallback);
|
|
64
|
+
strict_1.default.match(fallback.payload.text, /附件下载链接/);
|
|
65
|
+
strict_1.default.match(fallback.payload.text, /https:\/\/local\.agentid\.cn\/shared\//);
|
|
66
|
+
strict_1.default.doesNotMatch(fallback.payload.text, /\?t=/);
|
|
67
|
+
strict_1.default.equal(fallback.payload.thread_id, "topic-1");
|
|
68
|
+
strict_1.default.match(fallback.payload.text, /failed/);
|
|
69
|
+
}, async (message) => { await message.replyAttachments([image]); });
|
|
70
|
+
});
|
|
71
|
+
(0, node_test_1.default)("AUN 未返回附件发送状态时报告未确认并附下载链接", async () => {
|
|
72
|
+
const image = { kind: "image", data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "生成图.png", contentType: "image/png" };
|
|
73
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
74
|
+
storage.sendResult = undefined;
|
|
75
|
+
await receive({ from: "peer.agentid.cn", message_id: "pic-unknown-1", payload: { text: "画猫" } });
|
|
76
|
+
const fallback = sent.find((item) => item.payload.type === "text");
|
|
77
|
+
strict_1.default.ok(fallback);
|
|
78
|
+
strict_1.default.match(fallback.payload.text, /未返回状态/);
|
|
79
|
+
strict_1.default.match(fallback.payload.text, /附件下载链接/);
|
|
80
|
+
strict_1.default.doesNotMatch(fallback.payload.text, /\?t=/);
|
|
81
|
+
}, async (message) => { await message.replyAttachments([image]); });
|
|
82
|
+
});
|
|
83
|
+
(0, node_test_1.default)("AUN 含下载链接的失败回信加密失败时不降级明文", async () => {
|
|
84
|
+
const image = { kind: "image", data: new Uint8Array([137, 80, 78, 71, 13, 10, 26, 10]), filename: "生成图.png", contentType: "image/png" };
|
|
85
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
86
|
+
storage.sendFailure = () => true;
|
|
87
|
+
await receive({ from: "peer.agentid.cn", message_id: "pic-e2ee-1", payload: { text: "画猫", thread_id: "topic-1" } });
|
|
88
|
+
strict_1.default.equal(sent.length, 2);
|
|
89
|
+
strict_1.default.ok(sent.every((item) => item.encrypt === true));
|
|
90
|
+
strict_1.default.match(sent[1].payload.text, /附件下载链接/);
|
|
91
|
+
}, async (message) => { await message.replyAttachments([image]); });
|
|
92
|
+
});
|
|
93
|
+
(0, node_test_1.default)("AUN 普通文件发送失败时也提供原文件下载链接", async () => {
|
|
94
|
+
const file = { kind: "file", data: new Uint8Array([1, 2, 3]), filename: "结果.txt", contentType: "text/plain" };
|
|
95
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
96
|
+
storage.sendResult = { status: "failed" };
|
|
97
|
+
await receive({ from: "peer.agentid.cn", message_id: "file-failed-1", payload: { text: "生成文件", thread_id: "topic-1" } });
|
|
98
|
+
const fallback = sent.find((item) => item.payload.type === "text");
|
|
99
|
+
strict_1.default.ok(fallback);
|
|
100
|
+
strict_1.default.match(fallback.payload.text, /https:\/\/local\.agentid\.cn\/shared\//);
|
|
101
|
+
strict_1.default.doesNotMatch(fallback.payload.text, /\?t=/);
|
|
102
|
+
strict_1.default.equal(fallback.encrypt, true);
|
|
103
|
+
strict_1.default.equal(fallback.payload.thread_id, "topic-1");
|
|
104
|
+
}, async (message) => { await message.replyAttachments([file]); });
|
|
105
|
+
});
|
|
106
|
+
(0, node_test_1.default)("AUN 普通文件成功回传时提供下载链接和标准文件附件", async () => {
|
|
107
|
+
const file = { kind: "file", data: new Uint8Array([1, 2, 3]), filename: "结果.txt", contentType: "text/plain" };
|
|
108
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
109
|
+
await receive({ from: "peer.agentid.cn", message_id: "file-link-1", payload: { text: "生成文件", thread_id: "topic-file" } });
|
|
110
|
+
const url = `https://local.agentid.cn/${storage.writes[0].path.slice(1)}`;
|
|
111
|
+
strict_1.default.equal(sent.length, 1);
|
|
112
|
+
strict_1.default.equal(sent[0].payload.type, "file");
|
|
113
|
+
strict_1.default.equal(sent[0].payload.text, `文件已生成。\n文件下载链接:\n${url}`);
|
|
114
|
+
strict_1.default.equal(sent[0].payload.thread_id, "topic-file");
|
|
115
|
+
strict_1.default.equal(sent[0].payload.ref_message_id, "file-link-1");
|
|
116
|
+
strict_1.default.equal(sent[0].payload.attachments[0].filename, file.filename);
|
|
117
|
+
strict_1.default.equal(storage.writes[0].options.public, true);
|
|
118
|
+
strict_1.default.equal(storage.shares.length, 0);
|
|
119
|
+
}, async (message) => { await message.replyAttachments([file]); });
|
|
120
|
+
});
|
|
37
121
|
const commandRequest = (body) => (async function* () { yield Buffer.from(JSON.stringify(body)); })();
|
|
38
122
|
const rawCommandRequest = (body) => (async function* () { yield Buffer.from(body); })();
|
|
39
123
|
const commandResponse = () => ({ statusCode: 0, body: undefined, setHeader() { }, end(body) { this.body = JSON.parse(body); } });
|
|
@@ -74,6 +158,70 @@ async function runAunCommand(routes, response, argv) {
|
|
|
74
158
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
75
159
|
}
|
|
76
160
|
});
|
|
161
|
+
(0, node_test_1.default)("AUN 回传遇到传输断开时重连并重试一次", async () => {
|
|
162
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-reply-retry-"));
|
|
163
|
+
let receive;
|
|
164
|
+
let connects = 0;
|
|
165
|
+
let sends = 0;
|
|
166
|
+
const sdk = {
|
|
167
|
+
AIDStore: class {
|
|
168
|
+
list() { return { ok: true, data: { identities: [{ aid: "local.agentid.cn" }] } }; }
|
|
169
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
170
|
+
close() { }
|
|
171
|
+
},
|
|
172
|
+
AUNClient: class {
|
|
173
|
+
on(_event, handler) { receive = handler; return { unsubscribe() { } }; }
|
|
174
|
+
async connect() { connects++; }
|
|
175
|
+
async close() { }
|
|
176
|
+
async call(_method, _params) {
|
|
177
|
+
sends++;
|
|
178
|
+
if (sends === 1)
|
|
179
|
+
throw Object.assign(new Error("client is not connected"), { name: "ConnectionError" });
|
|
180
|
+
return { status: "sent" };
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async (message) => { await message.reply("恢复后的回复"); } } });
|
|
185
|
+
try {
|
|
186
|
+
await plugin.activate({ dataDir: root, config: { bindings: { "local.agentid.cn": "qwenwork" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
187
|
+
await receive({ from: "peer.agentid.cn", message_id: "retry-1", payload: { text: "请回复" } });
|
|
188
|
+
strict_1.default.equal(connects, 2);
|
|
189
|
+
strict_1.default.equal(sends, 2);
|
|
190
|
+
}
|
|
191
|
+
finally {
|
|
192
|
+
await plugin.deactivate("stop");
|
|
193
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
(0, node_test_1.default)("AUN 平台错误不触发连接重试", async () => {
|
|
197
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-no-retry-business-error-"));
|
|
198
|
+
let connects = 0;
|
|
199
|
+
let sends = 0;
|
|
200
|
+
const sdk = {
|
|
201
|
+
AIDStore: class {
|
|
202
|
+
list() { return { ok: true, data: { identities: [{ aid: "local.agentid.cn" }] } }; }
|
|
203
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
204
|
+
close() { }
|
|
205
|
+
},
|
|
206
|
+
AUNClient: class {
|
|
207
|
+
on() { return { unsubscribe() { } }; }
|
|
208
|
+
async connect() { connects++; }
|
|
209
|
+
async close() { }
|
|
210
|
+
async call() { sends++; throw Object.assign(new Error("Gateway HTTP 404: not found"), { name: "NotFoundError" }); }
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
214
|
+
try {
|
|
215
|
+
await plugin.activate({ dataDir: root, config: { bindings: { "local.agentid.cn": "qwenwork" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
216
|
+
await strict_1.default.rejects(plugin.callAunWithRecovery("local.agentid.cn", { to: "peer.agentid.cn", payload: { type: "text", text: "回复" } }), /404/);
|
|
217
|
+
strict_1.default.equal(connects, 1);
|
|
218
|
+
strict_1.default.equal(sends, 1);
|
|
219
|
+
}
|
|
220
|
+
finally {
|
|
221
|
+
await plugin.deactivate("stop");
|
|
222
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
223
|
+
}
|
|
224
|
+
});
|
|
77
225
|
(0, node_test_1.default)("AUN 绑定目标未就绪时不调用插件且不改动其它 AID", async () => {
|
|
78
226
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-unready-binding-"));
|
|
79
227
|
let receive;
|
|
@@ -116,7 +264,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
116
264
|
const sent = [];
|
|
117
265
|
const logs = [];
|
|
118
266
|
const storage = {
|
|
119
|
-
writes: [], shares: [],
|
|
267
|
+
writes: [], shares: [], sendResult: { status: "delivered" },
|
|
120
268
|
async writeBytes(path, data, options) { this.writes.push({ path, data, options }); return { size: data.byteLength, contentType: options.contentType, sha256: (0, node_crypto_1.createHash)("sha256").update(data).digest("hex") }; },
|
|
121
269
|
async stat() { throw new Error("不应读取未配置的公开对象"); },
|
|
122
270
|
async readBytes() { throw new Error("不应读取未配置的公开对象"); },
|
|
@@ -140,7 +288,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
140
288
|
const error = new Error("不能发送 https://local.agentid.cn/a?t=private-token");
|
|
141
289
|
error.name = "E2EEError";
|
|
142
290
|
throw error;
|
|
143
|
-
} }
|
|
291
|
+
} return storage.sendResult; }
|
|
144
292
|
},
|
|
145
293
|
};
|
|
146
294
|
const logger = { info() { }, warn(message, fields) { logs.push(JSON.stringify([message, fields])); }, error() { } };
|
|
@@ -157,6 +305,24 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
157
305
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
158
306
|
}
|
|
159
307
|
}
|
|
308
|
+
(0, node_test_1.default)("AUN 升级取消发生在 FS 上传结束时不再发送迟到附件或下载链接", async () => {
|
|
309
|
+
const controller = new AbortController();
|
|
310
|
+
const item = { kind: "file", data: new Uint8Array([42]), filename: "结果.txt", contentType: "text/plain" };
|
|
311
|
+
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
312
|
+
const write = storage.writeBytes.bind(storage);
|
|
313
|
+
storage.writeBytes = async (...args) => {
|
|
314
|
+
const result = await write(...args);
|
|
315
|
+
controller.abort(new Error("PLUGIN_UPGRADE_WAIT_TIMEOUT:升级等待两小时,桥接任务已取消"));
|
|
316
|
+
return result;
|
|
317
|
+
};
|
|
318
|
+
await receive({ from: "peer.agentid.cn", message_id: "upgrade-cancel-file", payload: { text: "生成文件" } });
|
|
319
|
+
strict_1.default.equal(storage.writes.length, 1);
|
|
320
|
+
strict_1.default.equal(sent.filter(item => item.payload.type === "file").length, 0);
|
|
321
|
+
strict_1.default.equal(sent.length, 1);
|
|
322
|
+
strict_1.default.match(sent[0].payload.text, /PLUGIN_UPGRADE_WAIT_TIMEOUT/);
|
|
323
|
+
strict_1.default.doesNotMatch(sent[0].payload.text, /https:\/\/|附件下载链接/);
|
|
324
|
+
}, async (message) => { await message.replyAttachments([item], "迟到文件", controller.signal); });
|
|
325
|
+
});
|
|
160
326
|
(0, node_test_1.default)("AUN 未手动关联的 AID 忽略消息、附件和旧默认 Agent 配置", async () => {
|
|
161
327
|
let published = false;
|
|
162
328
|
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
@@ -174,11 +340,11 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
174
340
|
];
|
|
175
341
|
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
176
342
|
await receive({ from: "peer.agentid.cn", message_id: "request-1", payload: { text: "生成产物", thread_id: "topic-1" } });
|
|
177
|
-
strict_1.default.deepEqual(sent.map((item) => item.payload.type), ["image", "file"]);
|
|
178
|
-
strict_1.default.
|
|
179
|
-
strict_1.default.deepEqual(sent.map((item) => item.payload.attachments[0].filename), ["图片.png", "报告.txt"]);
|
|
180
|
-
strict_1.default.ok(storage.writes.every((item) => /^[A-Za-z0-9._/-]+$/.test(item.path) && item.options.public ===
|
|
181
|
-
strict_1.default.
|
|
343
|
+
strict_1.default.deepEqual(sent.map((item) => item.payload.type), ["image", "text", "file"]);
|
|
344
|
+
strict_1.default.ok(sent.every(item => item.to === "peer.agentid.cn" && item.encrypt === true && item.payload.thread_id === "topic-1" && item.payload.ref_message_id === "request-1"));
|
|
345
|
+
strict_1.default.deepEqual(sent.filter(item => item.payload.attachments).map((item) => item.payload.attachments[0].filename), ["图片.png", "报告.txt"]);
|
|
346
|
+
strict_1.default.ok(storage.writes.every((item) => /^[A-Za-z0-9._/-]+$/.test(item.path) && item.options.public === true && item.options.overwrite === true));
|
|
347
|
+
strict_1.default.equal(storage.shares.length, 0);
|
|
182
348
|
}, async (message) => { await message.replyAttachments(items, "结果"); });
|
|
183
349
|
});
|
|
184
350
|
(0, node_test_1.default)("AUN 视频按可预览消息发送,与普通文件分组并保留原会话", async () => {
|
|
@@ -190,7 +356,8 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
190
356
|
await withAunAttachmentPlugin(async (receive, sent, storage) => {
|
|
191
357
|
await receive({ from: "peer.agentid.cn", message_id: "video-1", payload: { text: "返回产物", thread_id: "topic-video" } });
|
|
192
358
|
strict_1.default.deepEqual(sent.map((item) => item.payload.type), ["video", "file"]);
|
|
193
|
-
strict_1.default.
|
|
359
|
+
strict_1.default.equal(sent[0].payload.text, `视频已生成。\n附件下载链接:\nhttps://local.agentid.cn/${storage.writes[0].path.slice(1)}\nhttps://local.agentid.cn/${storage.writes[2].path.slice(1)}`);
|
|
360
|
+
strict_1.default.equal(sent[1].payload.text, `文件已生成。\n文件下载链接:\nhttps://local.agentid.cn/${storage.writes[1].path.slice(1)}`);
|
|
194
361
|
strict_1.default.deepEqual(sent.map((item) => item.payload.attachments.map((ref) => ref.filename)), [["视频.mp4", "视频.webm"], ["报告.txt"]]);
|
|
195
362
|
for (const item of sent) {
|
|
196
363
|
strict_1.default.equal(item.to, "peer.agentid.cn");
|
|
@@ -199,9 +366,8 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
199
366
|
strict_1.default.equal(item.payload.ref_message_id, "video-1");
|
|
200
367
|
}
|
|
201
368
|
strict_1.default.equal(storage.writes.length, 3);
|
|
202
|
-
strict_1.default.deepEqual(storage.writes.map((item) => item.options.public), [true,
|
|
203
|
-
strict_1.default.equal(storage.shares.length,
|
|
204
|
-
strict_1.default.ok(storage.shares.every((item) => item.allowedAids.length === 1 && item.allowedAids[0] === "peer.agentid.cn"));
|
|
369
|
+
strict_1.default.deepEqual(storage.writes.map((item) => item.options.public), [true, true, true]);
|
|
370
|
+
strict_1.default.equal(storage.shares.length, 0);
|
|
205
371
|
}, async (message) => { await message.replyAttachments(items); });
|
|
206
372
|
});
|
|
207
373
|
(0, node_test_1.default)("AUN 纯分享附件下载后发布,白名单外消息不触碰附件", async () => {
|
|
@@ -333,7 +499,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
333
499
|
await receive({ from: "peer.agentid.cn", message_id: "partial-1", payload: { text: "生成", thread_id: "topic-1" } });
|
|
334
500
|
strict_1.default.equal(storage.writes.length, 2);
|
|
335
501
|
strict_1.default.ok(sent.filter((item) => item.payload.type === "file").every((item) => item.encrypt === true));
|
|
336
|
-
const errorReply = sent.find((item) => item.payload.
|
|
502
|
+
const errorReply = sent.find((item) => item.payload.text?.startsWith("附件回传失败"));
|
|
337
503
|
strict_1.default.match(errorReply.payload.text, /1\/2.*未确认/);
|
|
338
504
|
strict_1.default.doesNotMatch(JSON.stringify([sent.filter((item) => item.payload.type === "text"), logs]), /private-token/);
|
|
339
505
|
}, async (message) => {
|
|
@@ -385,6 +551,91 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
385
551
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
386
552
|
}
|
|
387
553
|
});
|
|
554
|
+
(0, node_test_1.default)("AUN 管理接口列出安全绑定、使用默认 Owner 创建并要求显式允许替换", async () => {
|
|
555
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-management-api-"));
|
|
556
|
+
const registered = [];
|
|
557
|
+
const sdk = {
|
|
558
|
+
AIDStore: class {
|
|
559
|
+
async register(aid) { registered.push(aid); return { ok: true, data: {} }; }
|
|
560
|
+
list() { return { ok: true, data: { identities: ["assistant.agentid.cn", ...registered].map((aid) => ({ aid })) } }; }
|
|
561
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
562
|
+
close() { }
|
|
563
|
+
},
|
|
564
|
+
AUNClient: class {
|
|
565
|
+
on() { return { unsubscribe() { } }; }
|
|
566
|
+
async connect() { }
|
|
567
|
+
async close() { }
|
|
568
|
+
},
|
|
569
|
+
};
|
|
570
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
571
|
+
const fetchCalls = [];
|
|
572
|
+
let failBinding = false;
|
|
573
|
+
const originalFetch = globalThis.fetch;
|
|
574
|
+
globalThis.fetch = async (url, init) => {
|
|
575
|
+
fetchCalls.push({ path: String(url), body: init?.body ? JSON.parse(String(init.body)) : undefined });
|
|
576
|
+
if (failBinding)
|
|
577
|
+
return Response.json({ error: "配置保存失败" }, { status: 400 });
|
|
578
|
+
return Response.json({ ok: true });
|
|
579
|
+
};
|
|
580
|
+
try {
|
|
581
|
+
await plugin.activate({ dataDir: root, config: { aid: "assistant.agentid.cn", owner_aid: "owner.agentid.cn", encryption_seed: "private-seed", bindings: { "assistant.agentid.cn": "personal-assistant", "old.agentid.cn": "qwenwork" } }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } }, runtime: { controlUrl: "http://127.0.0.1:3000" } });
|
|
582
|
+
strict_1.default.deepEqual(await plugin.aunManagement.list(), {
|
|
583
|
+
aids: ["assistant.agentid.cn", "old.agentid.cn"],
|
|
584
|
+
bindings: { "assistant.agentid.cn": "personal-assistant", "old.agentid.cn": "qwenwork" },
|
|
585
|
+
});
|
|
586
|
+
await strict_1.default.rejects(plugin.aunManagement.create({ aid: "invalid" }), /AID 格式无效/);
|
|
587
|
+
await strict_1.default.rejects(plugin.aunManagement.bind({ aid: "new.agentid.cn", agentId: "qwenwork", replace: false }), /其他 AID 绑定.*replace/);
|
|
588
|
+
await strict_1.default.rejects(plugin.aunManagement.bind({ aid: "old.agentid.cn", agentId: "personal-assistant", replace: true }), /已绑定到其他 Agent/);
|
|
589
|
+
const created = await plugin.aunManagement.create({ aid: "new.agentid.cn" });
|
|
590
|
+
strict_1.default.deepEqual(created, { aid: "new.agentid.cn", status: "已创建", connection: "ready" });
|
|
591
|
+
strict_1.default.deepEqual(registered, ["new.agentid.cn"]);
|
|
592
|
+
strict_1.default.doesNotMatch(JSON.stringify(created), /owner|private-seed|token|path/i);
|
|
593
|
+
failBinding = true;
|
|
594
|
+
await strict_1.default.rejects(plugin.aunManagement.bind({ aid: "new.agentid.cn", agentId: "qwenwork", replace: true }), /身份关联失败/);
|
|
595
|
+
strict_1.default.equal(fetchCalls.length, 1);
|
|
596
|
+
failBinding = false;
|
|
597
|
+
strict_1.default.deepEqual(await plugin.aunManagement.bind({ aid: "new.agentid.cn", agentId: "qwenwork", replace: true }), { aid: "new.agentid.cn", agent: "qwenwork", replacedAids: ["old.agentid.cn"] });
|
|
598
|
+
strict_1.default.deepEqual(fetchCalls, [
|
|
599
|
+
{ path: "http://127.0.0.1:3000/api/plugins/qwenwork/aid", body: { aid: "new.agentid.cn" } },
|
|
600
|
+
{ path: "http://127.0.0.1:3000/api/plugins/qwenwork/aid", body: { aid: "new.agentid.cn" } },
|
|
601
|
+
]);
|
|
602
|
+
}
|
|
603
|
+
finally {
|
|
604
|
+
globalThis.fetch = originalFetch;
|
|
605
|
+
await plugin.deactivate("stop");
|
|
606
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
(0, node_test_1.default)("AUN 管理创建拒绝缺失 Owner,并在注册后连接失败时禁止盲目重试", async () => {
|
|
610
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-management-create-partial-"));
|
|
611
|
+
const registered = [];
|
|
612
|
+
const sdk = {
|
|
613
|
+
AIDStore: class {
|
|
614
|
+
async register(aid) { registered.push(aid); return { ok: true, data: {} }; }
|
|
615
|
+
list() { return { ok: true, data: { identities: registered.map((aid) => ({ aid })) } }; }
|
|
616
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
617
|
+
close() { }
|
|
618
|
+
},
|
|
619
|
+
AUNClient: class {
|
|
620
|
+
on() { return { unsubscribe() { } }; }
|
|
621
|
+
async connect() { throw new Error("连接失败"); }
|
|
622
|
+
async close() { }
|
|
623
|
+
},
|
|
624
|
+
};
|
|
625
|
+
const plugin = new aun_1.AunPlugin({ sdk, ingress: { publish: async () => undefined } });
|
|
626
|
+
try {
|
|
627
|
+
await plugin.activate({ dataDir: root, config: {}, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
628
|
+
await strict_1.default.rejects(plugin.aunManagement.create({ aid: "missing-owner.agentid.cn" }), /未提供 Owner AID/);
|
|
629
|
+
strict_1.default.deepEqual(registered, []);
|
|
630
|
+
await strict_1.default.rejects(plugin.aunManagement.create({ aid: "partial.agentid.cn", ownerAid: "owner.agentid.cn" }), /已创建但连接失败.*不要重试创建/);
|
|
631
|
+
strict_1.default.deepEqual(registered, ["partial.agentid.cn"]);
|
|
632
|
+
strict_1.default.deepEqual((await plugin.aunManagement.list()).aids, ["partial.agentid.cn"]);
|
|
633
|
+
}
|
|
634
|
+
finally {
|
|
635
|
+
await plugin.deactivate("stop");
|
|
636
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
637
|
+
}
|
|
638
|
+
});
|
|
388
639
|
(0, node_test_1.default)("AUN 首次加载失败后仍列出已注册身份且重试不重复注册", async () => {
|
|
389
640
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-cli-load-retry-"));
|
|
390
641
|
const routes = new Map();
|
|
@@ -1561,3 +1812,25 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1561
1812
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1562
1813
|
}
|
|
1563
1814
|
});
|
|
1815
|
+
(0, node_test_1.default)("ZCode 关联 AID 后接收 Peer 消息并把回复送回原话题", async () => {
|
|
1816
|
+
const zcode = new zcode_1.ZcodePlugin({ client: {
|
|
1817
|
+
connect: async () => undefined, close() { }, connected: () => true,
|
|
1818
|
+
createTask: async () => undefined,
|
|
1819
|
+
direct: async (text) => `ZCode 回复:${text}`,
|
|
1820
|
+
} });
|
|
1821
|
+
await zcode.activate({ config: { aid: "local.agentid.cn" }, logger: { info() { }, warn() { }, error() { } } });
|
|
1822
|
+
try {
|
|
1823
|
+
await withAunAttachmentPlugin(async (receive, sent, _storage, _logs, aun) => {
|
|
1824
|
+
strict_1.default.equal((await aun.health()).details?.bindings && (await aun.health()).details.bindings["local.agentid.cn"], "zcode");
|
|
1825
|
+
await receive({ from: "peer.agentid.cn", message_id: "zcode-aun-1", payload: { text: "验证 ZCode", thread_id: "zcode-topic" } });
|
|
1826
|
+
strict_1.default.deepEqual(sent, [{ to: "peer.agentid.cn", payload: { type: "text", text: "ZCode 回复:验证 ZCode", thread_id: "zcode-topic" }, encrypt: true }]);
|
|
1827
|
+
}, async (message) => {
|
|
1828
|
+
strict_1.default.equal(message.agentPlugin, "zcode");
|
|
1829
|
+
const response = await zcode.handle({ task: message.text, sessionId: "zcode-peer" });
|
|
1830
|
+
await message.reply(response.text);
|
|
1831
|
+
}, { allow_from: "peer.agentid.cn", bindings: { "local.agentid.cn": "zcode" } });
|
|
1832
|
+
}
|
|
1833
|
+
finally {
|
|
1834
|
+
await zcode.deactivate("stop");
|
|
1835
|
+
}
|
|
1836
|
+
});
|
|
@@ -8,7 +8,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
9
|
const node_module_1 = require("node:module");
|
|
10
10
|
const node_test_1 = __importDefault(require("node:test"));
|
|
11
|
-
const ids = ["abweb", "aun", "chatgpt", "claude-cli", "codex-cli", "doubao", "dsh", "feishu", "qwenwork", "workbuddy"];
|
|
11
|
+
const ids = ["abweb", "aun", "chatgpt", "claude-cli", "codex-cli", "doubao", "dsh", "feishu", "personal-assistant", "qwenwork", "workbuddy", "zcode"];
|
|
12
12
|
(0, node_test_1.default)("独立包和默认插件的运行时依赖可加载且代码版本一致", async () => {
|
|
13
13
|
const load = (0, node_module_1.createRequire)(__filename);
|
|
14
14
|
for (const id of ids) {
|
|
@@ -35,7 +35,17 @@ const ids = ["abweb", "aun", "chatgpt", "claude-cli", "codex-cli", "doubao", "ds
|
|
|
35
35
|
strict_1.default.deepEqual(packageSchema, schema, `${id} 独立包与内置插件的配置声明必须一致`);
|
|
36
36
|
await (0, promises_1.access)((0, node_path_1.join)(directory, manifest.entry));
|
|
37
37
|
const meta = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "package.json"), "utf8"));
|
|
38
|
-
strict_1.default.equal(meta.name, `agent-bridge-${id}`);
|
|
38
|
+
strict_1.default.equal(meta.name, id === "personal-assistant" ? "agent-bridge-pa" : `agent-bridge-${id}`);
|
|
39
|
+
if (id === "personal-assistant")
|
|
40
|
+
strict_1.default.equal(meta.agentBridge.id, id);
|
|
39
41
|
strict_1.default.equal(meta.version, packageManifest.version);
|
|
40
42
|
}
|
|
41
43
|
});
|
|
44
|
+
(0, node_test_1.default)("五个桌面插件的安装清单声明模型来源能力", async () => {
|
|
45
|
+
for (const id of ["chatgpt", "doubao", "dsh", "qwenwork", "workbuddy"]) {
|
|
46
|
+
for (const root of ["packages", "plugins"]) {
|
|
47
|
+
const manifest = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(process.cwd(), root, id, "plugin.json"), "utf8"));
|
|
48
|
+
strict_1.default.ok(manifest.capabilities?.includes("model-provider"), `${root}/${id} 必须声明模型来源能力`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -17,11 +17,15 @@ function desktop(text) {
|
|
|
17
17
|
let generating = false;
|
|
18
18
|
let newChat = false;
|
|
19
19
|
let sends = 0;
|
|
20
|
+
let hidden = false;
|
|
21
|
+
let restored = 0;
|
|
20
22
|
const editor = { innerText: text, focus() { active = true; }, querySelectorAll: () => [send] };
|
|
21
|
-
const send = { disabled: false, getAttribute: () => null, click() {
|
|
23
|
+
const send = { disabled: false, getAttribute: () => null, click() { if (hidden)
|
|
24
|
+
return; sends++; editor.innerText = ""; } };
|
|
22
25
|
const create = { disabled: false, textContent: "New chat", getAttribute: () => null, click() { newChat = true; } };
|
|
23
26
|
const target = { getAttribute: (key) => key.endsWith("host-id") ? "h" : key.endsWith("thread-id") ? "target" : String(newChat), click() { newChat = true; } };
|
|
24
27
|
const document = {
|
|
28
|
+
get hidden() { return hidden; },
|
|
25
29
|
get activeElement() { return active ? editor : null; },
|
|
26
30
|
get URL() { return `file:///index.html?chat=${newChat ? "new" : "old"}`; },
|
|
27
31
|
querySelector: (selector) => selector.includes("contenteditable") ? editor
|
|
@@ -33,9 +37,168 @@ function desktop(text) {
|
|
|
33
37
|
createRange: () => ({ selectNodeContents() { } }),
|
|
34
38
|
execCommand(command) { strict_1.default.equal(command, "delete"); editor.innerText = ""; return true; },
|
|
35
39
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
40
|
+
const window = { getSelection: () => ({ removeAllRanges() { }, addRange() { } }), desktopApi: { async focusMainWindow() { restored++; hidden = false; } } };
|
|
41
|
+
const context = { document, location: { get href() { return document.URL; } }, window };
|
|
42
|
+
return { editor, document, window, send, get sends() { return sends; }, get restored() { return restored; }, get hidden() { return hidden; }, set hidden(value) { hidden = value; }, get newChat() { return newChat; }, set generating(value) { generating = value; }, evaluate: async (expression) => (0, node_vm_1.runInNewContext)(expression, context) };
|
|
43
|
+
}
|
|
44
|
+
for (const moment of ["插入后", "点击前复查"]) {
|
|
45
|
+
(0, node_test_1.default)(`千问${moment}最小化后恢复窗口,文字只插入一次、任务只发送一次`, async () => {
|
|
46
|
+
const page = desktop("");
|
|
47
|
+
const client = new cdp_client_1.QwenCdpClient({ timeoutMs: 10 });
|
|
48
|
+
let inserts = 0;
|
|
49
|
+
let minimized = false;
|
|
50
|
+
client.evaluate = async (expression) => {
|
|
51
|
+
if (moment === "点击前复查" && !minimized && expression.includes('.click()')) {
|
|
52
|
+
minimized = true;
|
|
53
|
+
page.hidden = true;
|
|
54
|
+
}
|
|
55
|
+
return page.evaluate(expression);
|
|
56
|
+
};
|
|
57
|
+
client.cdp = async (_method, params) => {
|
|
58
|
+
inserts++;
|
|
59
|
+
page.editor.innerText += params.text;
|
|
60
|
+
if (moment === "插入后")
|
|
61
|
+
page.hidden = true;
|
|
62
|
+
};
|
|
63
|
+
await strict_1.default.rejects(client.complete("自动任务", 0), /QWEN_REPLY_TIMEOUT/);
|
|
64
|
+
strict_1.default.equal(inserts, 1);
|
|
65
|
+
strict_1.default.equal(page.sends, 1);
|
|
66
|
+
strict_1.default.equal(page.restored, 1);
|
|
67
|
+
strict_1.default.equal(page.editor.innerText, "");
|
|
68
|
+
});
|
|
38
69
|
}
|
|
70
|
+
(0, node_test_1.default)("千问确认首次点击尚未发生时只再次检查提交,不重复插入文字", async () => {
|
|
71
|
+
const page = desktop("");
|
|
72
|
+
const client = new cdp_client_1.QwenCdpClient({ timeoutMs: 100 });
|
|
73
|
+
let inserts = 0;
|
|
74
|
+
let clickChecks = 0;
|
|
75
|
+
client.evaluate = async (expression) => {
|
|
76
|
+
if (expression.includes("document.querySelector('button[aria-label=\"发送消息\"]').click()") && ++clickChecks === 1)
|
|
77
|
+
page.hidden = true;
|
|
78
|
+
return page.evaluate(expression);
|
|
79
|
+
};
|
|
80
|
+
client.cdp = async (_method, params) => { inserts++; page.editor.innerText += params.text; };
|
|
81
|
+
await strict_1.default.rejects(client.complete("自动任务", 0), /QWEN_REPLY_TIMEOUT/);
|
|
82
|
+
strict_1.default.equal(inserts, 1);
|
|
83
|
+
strict_1.default.equal(clickChecks, 2);
|
|
84
|
+
strict_1.default.equal(page.sends, 1);
|
|
85
|
+
strict_1.default.equal(page.restored, 1);
|
|
86
|
+
});
|
|
87
|
+
(0, node_test_1.default)("千问点击后最小化且正在生成时恢复确认,不再次点击", async () => {
|
|
88
|
+
const page = desktop("");
|
|
89
|
+
const client = new cdp_client_1.QwenCdpClient({ timeoutMs: 10 });
|
|
90
|
+
let clicks = 0;
|
|
91
|
+
page.send.click = () => { clicks++; page.hidden = true; page.generating = true; };
|
|
92
|
+
const restore = page.window.desktopApi.focusMainWindow;
|
|
93
|
+
page.window.desktopApi.focusMainWindow = async () => { await restore(); page.editor.innerText = ""; page.generating = false; };
|
|
94
|
+
client.evaluate = page.evaluate;
|
|
95
|
+
client.cdp = async (_method, params) => { page.editor.innerText += params.text; };
|
|
96
|
+
await strict_1.default.rejects(client.complete("自动任务", 0), /QWEN_REPLY_TIMEOUT/);
|
|
97
|
+
strict_1.default.equal(clicks, 1);
|
|
98
|
+
strict_1.default.equal(page.restored, 1);
|
|
99
|
+
strict_1.default.equal(page.editor.innerText, "");
|
|
100
|
+
});
|
|
101
|
+
(0, node_test_1.default)("千问等待回复期间最小化,恢复渲染后取得本轮回复且不重发", async (t) => {
|
|
102
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
103
|
+
const page = desktop("");
|
|
104
|
+
const client = new cdp_client_1.QwenCdpClient({ timeoutMs: 1000 });
|
|
105
|
+
let minimized = false;
|
|
106
|
+
const reply = { innerText: "本轮回复", getAttribute: () => "new-reply" };
|
|
107
|
+
page.document.querySelectorAll = (() => page.sends && !page.hidden ? [reply] : []);
|
|
108
|
+
const restore = page.window.desktopApi.focusMainWindow;
|
|
109
|
+
page.window.desktopApi.focusMainWindow = async () => { await restore(); page.generating = false; };
|
|
110
|
+
client.evaluate = async (expression) => {
|
|
111
|
+
const result = await page.evaluate(expression);
|
|
112
|
+
if (!minimized && page.sends && expression.includes('return Boolean(input && !input.innerText.trim())')) {
|
|
113
|
+
minimized = true;
|
|
114
|
+
page.hidden = true;
|
|
115
|
+
page.generating = true;
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
};
|
|
119
|
+
client.cdp = async (_method, params) => { page.editor.innerText += params.text; };
|
|
120
|
+
let settled = false;
|
|
121
|
+
const running = client.complete("自动任务", 20_000, undefined, "本轮回复").finally(() => { settled = true; });
|
|
122
|
+
for (let index = 0; index < 4 && !settled; index++) {
|
|
123
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
124
|
+
t.mock.timers.tick(2000);
|
|
125
|
+
}
|
|
126
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
127
|
+
if (!settled) {
|
|
128
|
+
t.mock.timers.reset();
|
|
129
|
+
strict_1.default.fail("窗口未恢复,未取得本轮回复");
|
|
130
|
+
}
|
|
131
|
+
strict_1.default.equal((await running).text, "本轮回复");
|
|
132
|
+
strict_1.default.equal(page.sends, 1);
|
|
133
|
+
strict_1.default.equal(page.restored, 1);
|
|
134
|
+
});
|
|
135
|
+
for (const mode of ["按钮未就绪", "点击前按钮失效", "用户修改文字", "用户切换会话", "开始生成", "点击结果未知", "未确认提交", "插入响应断线"]) {
|
|
136
|
+
(0, node_test_1.default)(`千问提交失败:${mode}时只清理可确认属于本次请求的输入`, async () => {
|
|
137
|
+
const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-submit-"));
|
|
138
|
+
const prompt = "自动任务\n[[NOOP]]";
|
|
139
|
+
const page = desktop("");
|
|
140
|
+
const client = new cdp_client_1.QwenCdpClient({ dataDir, timeoutMs: 10 });
|
|
141
|
+
const canClear = mode === "按钮未就绪" || mode === "点击前按钮失效";
|
|
142
|
+
let clicks = 0;
|
|
143
|
+
page.send.disabled = mode !== "点击前按钮失效" && mode !== "点击结果未知" && mode !== "未确认提交";
|
|
144
|
+
page.send.click = () => {
|
|
145
|
+
clicks++;
|
|
146
|
+
if (mode === "点击结果未知")
|
|
147
|
+
throw new Error("点击响应断线");
|
|
148
|
+
};
|
|
149
|
+
client.evaluate = async (expression) => {
|
|
150
|
+
if (mode === "点击前按钮失效" && expression.includes('.click()'))
|
|
151
|
+
page.send.disabled = true;
|
|
152
|
+
return page.evaluate(expression);
|
|
153
|
+
};
|
|
154
|
+
client.cdp = async (method, params) => {
|
|
155
|
+
strict_1.default.equal(method, "Input.insertText");
|
|
156
|
+
page.editor.innerText += params.text;
|
|
157
|
+
if (mode === "用户修改文字")
|
|
158
|
+
page.editor.innerText += "用户补充";
|
|
159
|
+
if (mode === "用户切换会话")
|
|
160
|
+
Object.defineProperty(page.document, "URL", { value: "file:///index.html?chat=other" });
|
|
161
|
+
if (mode === "开始生成")
|
|
162
|
+
page.generating = true;
|
|
163
|
+
if (mode === "插入响应断线")
|
|
164
|
+
throw Object.assign(new Error("connection reset"), { code: "ECONNRESET" });
|
|
165
|
+
};
|
|
166
|
+
try {
|
|
167
|
+
await strict_1.default.rejects(client.complete(prompt, 0), mode === "插入响应断线" ? /QWEN_SUBMIT_FAILED.*ECONNRESET/ : /QWEN_SUBMIT_FAILED/);
|
|
168
|
+
strict_1.default.equal(page.editor.innerText, canClear ? "" : prompt + (mode === "用户修改文字" ? "用户补充" : ""));
|
|
169
|
+
strict_1.default.equal(clicks, mode === "点击结果未知" || mode === "未确认提交" ? 1 : 0);
|
|
170
|
+
const files = await (0, promises_1.readdir)(dataDir);
|
|
171
|
+
strict_1.default.deepEqual(files, canClear ? ["draft-recovery.jsonl"] : []);
|
|
172
|
+
if (canClear) {
|
|
173
|
+
const backup = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(dataDir, "draft-recovery.jsonl"), "utf8"));
|
|
174
|
+
strict_1.default.equal(backup.text, prompt);
|
|
175
|
+
strict_1.default.equal(backup.url, "file:///index.html?chat=old");
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
(0, node_test_1.default)("千问等待发送按钮延迟就绪后只提交一次", async () => {
|
|
184
|
+
const page = desktop("");
|
|
185
|
+
const client = new cdp_client_1.QwenCdpClient({ timeoutMs: 1000 });
|
|
186
|
+
page.send.disabled = true;
|
|
187
|
+
client.evaluate = page.evaluate;
|
|
188
|
+
let timer;
|
|
189
|
+
client.cdp = async (_method, params) => {
|
|
190
|
+
page.editor.innerText += params.text;
|
|
191
|
+
timer = setTimeout(() => { page.send.disabled = false; }, 50);
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
await strict_1.default.rejects(client.complete("自动任务", 0), /QWEN_REPLY_TIMEOUT/);
|
|
195
|
+
strict_1.default.equal(page.sends, 1);
|
|
196
|
+
strict_1.default.equal(page.editor.innerText, "");
|
|
197
|
+
}
|
|
198
|
+
finally {
|
|
199
|
+
clearTimeout(timer);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
39
202
|
for (const kind of ["qwenwork", "chatgpt"]) {
|
|
40
203
|
for (const operation of ["send", "new", ...(kind === "chatgpt" ? ["switch"] : [])]) {
|
|
41
204
|
(0, node_test_1.default)(`${kind} ${operation} 先备份并清理残留,不再阻塞远程请求`, async () => {
|
|
@@ -107,6 +270,22 @@ for (const kind of ["qwenwork", "chatgpt"]) {
|
|
|
107
270
|
await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
|
|
108
271
|
}
|
|
109
272
|
});
|
|
273
|
+
(0, node_test_1.default)("备份期间切换到相同草稿的另一会话时不能清理", async () => {
|
|
274
|
+
const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-draft-"));
|
|
275
|
+
const page = desktop("相同的草稿");
|
|
276
|
+
let calls = 0;
|
|
277
|
+
try {
|
|
278
|
+
await strict_1.default.rejects((0, cdp_draft_1.recoverCdpDraft)(async (expression) => {
|
|
279
|
+
if (++calls === 2)
|
|
280
|
+
Object.defineProperty(page.document, "URL", { value: "file:///index.html?chat=other" });
|
|
281
|
+
return page.evaluate(expression);
|
|
282
|
+
}, "[contenteditable]", "[stop]", dataDir), /内容发生变化/);
|
|
283
|
+
strict_1.default.equal(page.editor.innerText, "相同的草稿");
|
|
284
|
+
}
|
|
285
|
+
finally {
|
|
286
|
+
await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
|
|
287
|
+
}
|
|
288
|
+
});
|
|
110
289
|
(0, node_test_1.default)("优先调用 React 编辑器控制器清理内部草稿状态", async () => {
|
|
111
290
|
const page = desktop("React 内部残留");
|
|
112
291
|
let cleared = 0;
|