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
|
@@ -37,17 +37,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
39
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
40
|
-
const node_test_1 =
|
|
40
|
+
const node_test_1 = __importStar(require("node:test"));
|
|
41
41
|
const node_vm_1 = require("node:vm");
|
|
42
|
+
const childProcess = require("node:child_process");
|
|
42
43
|
const promises_1 = require("node:fs/promises");
|
|
44
|
+
const node_fs_1 = require("node:fs");
|
|
43
45
|
const node_os_1 = require("node:os");
|
|
44
46
|
const node_path_1 = require("node:path");
|
|
47
|
+
const fsPromises = require("node:fs/promises");
|
|
45
48
|
const doubao = __importStar(require("../src-ts/plugins/doubao/cdp-client"));
|
|
46
49
|
const doubao_1 = require("../src-ts/plugins/doubao");
|
|
47
50
|
const cdp_client_1 = require("../src-ts/plugins/doubao/cdp-client");
|
|
48
51
|
const attachments_1 = require("../src-ts/plugins/doubao/attachments");
|
|
52
|
+
const desktopLaunch = __importStar(require("../src-ts/plugins/desktop-launch"));
|
|
49
53
|
const imageUrl = "https://p3-flow-imagex-sign.byteimg.com/tos-cn-i-a9rns2rl98/rc_gen_image/test.png?signature=test";
|
|
50
54
|
const png = Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+jRZkAAAAASUVORK5CYII=", "base64");
|
|
55
|
+
(0, node_test_1.default)("豆包升级清理保留桌面,普通停止仍关闭桥接持有的进程", async (t) => {
|
|
56
|
+
const terminated = [];
|
|
57
|
+
t.mock.method(desktopLaunch, "terminateOwnedProcess", async (child) => { if (child)
|
|
58
|
+
terminated.push(child); });
|
|
59
|
+
let closed = 0;
|
|
60
|
+
const plugin = new doubao_1.DoubaoPlugin({ connect: async () => undefined, close: async () => { closed++; }, connected: () => true, direct: async () => "回复" });
|
|
61
|
+
const owned = { pid: 12345 };
|
|
62
|
+
await plugin.activate({ config: {} });
|
|
63
|
+
plugin.ownedProcess = owned;
|
|
64
|
+
await plugin.deactivate("reload", { preserveDesktop: true });
|
|
65
|
+
strict_1.default.equal(closed, 1);
|
|
66
|
+
strict_1.default.deepEqual(terminated, []);
|
|
67
|
+
await plugin.activate({ config: {} });
|
|
68
|
+
plugin.ownedProcess = owned;
|
|
69
|
+
await plugin.deactivate();
|
|
70
|
+
strict_1.default.deepEqual(terminated, [owned]);
|
|
71
|
+
});
|
|
51
72
|
function deferred() {
|
|
52
73
|
let resolve;
|
|
53
74
|
let reject;
|
|
@@ -123,14 +144,35 @@ function deferred() {
|
|
|
123
144
|
await legacy.deactivate();
|
|
124
145
|
}
|
|
125
146
|
});
|
|
147
|
+
(0, node_test_1.default)("豆包模型来源由服务端创建独立会话,不切换用户桌面", async () => {
|
|
148
|
+
const controller = new AbortController();
|
|
149
|
+
const calls = [];
|
|
150
|
+
const client = {
|
|
151
|
+
async connect() { }, async close() { }, connected: () => true, direct: async () => "不应使用普通入口",
|
|
152
|
+
createSession: async () => { calls.push("create"); },
|
|
153
|
+
complete: async (text, signal, _items, conversationId) => {
|
|
154
|
+
calls.push([text, signal, conversationId]);
|
|
155
|
+
return { text: "模型 JSON", sessionId: "314159" };
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
const plugin = new doubao_1.DoubaoPlugin(client);
|
|
159
|
+
try {
|
|
160
|
+
await plugin.activate({ config: {} });
|
|
161
|
+
strict_1.default.deepEqual(await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-thread", timeoutMs: 5000, signal: controller.signal }), { text: "模型 JSON" });
|
|
162
|
+
strict_1.default.deepEqual(calls, [["模型提示", controller.signal, ""]]);
|
|
163
|
+
}
|
|
164
|
+
finally {
|
|
165
|
+
await plugin.deactivate();
|
|
166
|
+
}
|
|
167
|
+
});
|
|
126
168
|
(0, node_test_1.default)("豆包在访问页面前明确拒绝视频与不支持的文件类型", async () => {
|
|
127
|
-
const client = new cdp_client_1.DoubaoCdpClient();
|
|
169
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat" });
|
|
128
170
|
for (const [filename, contentType, error] of [["clip.mp4", "video/mp4", /不支持视频/], ["archive.zip", "application/zip", /不支持.*文件类型/]]) {
|
|
129
171
|
await strict_1.default.rejects(client.complete("读取附件", undefined, [{ kind: "file", data: Buffer.from("data"), filename, contentType }]), error);
|
|
130
172
|
}
|
|
131
173
|
});
|
|
132
174
|
(0, node_test_1.default)("豆包上传和清理同时失败时保留两个错误", async () => {
|
|
133
|
-
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 3000 });
|
|
175
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 3000 });
|
|
134
176
|
let uploadName = "";
|
|
135
177
|
client.evaluate = async (expression) => {
|
|
136
178
|
if (expression.includes("const transfer = new DataTransfer")) {
|
|
@@ -149,7 +191,7 @@ function deferred() {
|
|
|
149
191
|
});
|
|
150
192
|
});
|
|
151
193
|
(0, node_test_1.default)("豆包上传启动阶段异常也清理本次附件", async () => {
|
|
152
|
-
const client = new cdp_client_1.DoubaoCdpClient();
|
|
194
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat" });
|
|
153
195
|
let cleaned = false;
|
|
154
196
|
client.evaluate = async (expression) => {
|
|
155
197
|
if (expression.includes("names.includes")) {
|
|
@@ -185,6 +227,39 @@ function deferred() {
|
|
|
185
227
|
strict_1.default.deepEqual(extract({ block_type: 2074, is_finish: true, content_obj: { creations: [{ type: 2, video: { status: 4, vid: "failed" } }] } }), { artifacts: [], pending: false, failed: true });
|
|
186
228
|
strict_1.default.deepEqual(extract(null), { artifacts: [], pending: false });
|
|
187
229
|
});
|
|
230
|
+
(0, node_test_1.default)("豆包本地电脑文件操作块只回传工作区内的真实文件", async (t) => {
|
|
231
|
+
const workspace = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-doubao-files-"));
|
|
232
|
+
const filename = "倍距点数轴题.docx";
|
|
233
|
+
const filePath = (0, node_path_1.join)(workspace, filename);
|
|
234
|
+
const data = Buffer.from("word-file-content");
|
|
235
|
+
await (0, promises_1.writeFile)(filePath, data);
|
|
236
|
+
t.after(() => (0, promises_1.rm)(workspace, { recursive: true, force: true }));
|
|
237
|
+
const extract = new Function(`return (${doubao.responseArtifactsScript})`)();
|
|
238
|
+
const result = extract([
|
|
239
|
+
{ block_type: 10019, is_finish: true, content: { file_operation_block: { file_type: "diff", path: filePath } } },
|
|
240
|
+
{ block_type: 10019, is_finish: true, content: { file_operation_block: { file_type: "shell", path: filePath } } },
|
|
241
|
+
], workspace);
|
|
242
|
+
strict_1.default.deepEqual(result, { artifacts: [{ kind: "file", filename, path: filePath, workspace }], pending: false });
|
|
243
|
+
const attachment = await doubao.downloadDoubaoArtifact(result.artifacts[0]);
|
|
244
|
+
strict_1.default.equal(attachment.kind, "file");
|
|
245
|
+
strict_1.default.equal(attachment.filename, filename);
|
|
246
|
+
strict_1.default.deepEqual(attachment.data, data);
|
|
247
|
+
const outside = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-doubao-outside-"));
|
|
248
|
+
const outsidePath = (0, node_path_1.join)(outside, "outside.docx");
|
|
249
|
+
await (0, promises_1.writeFile)(outsidePath, data);
|
|
250
|
+
t.after(() => (0, promises_1.rm)(outside, { recursive: true, force: true }));
|
|
251
|
+
await strict_1.default.rejects(doubao.downloadDoubaoArtifact({ ...result.artifacts[0], path: outsidePath }), /工作区/);
|
|
252
|
+
});
|
|
253
|
+
(0, node_test_1.default)("豆包本地电脑富媒体卡片采集完成原图,忽略工具预览和未完成卡片", () => {
|
|
254
|
+
const extract = new Function(`return (${doubao.responseArtifactsScript})`)();
|
|
255
|
+
const image = { status: 2, image_ori: { url: imageUrl }, image_thumb: { url: "https://example.com/thumb.png" } };
|
|
256
|
+
const card = { block_type: 10084, is_finish: true, content: { rich_media_layout_block: { media: [{ type: 6, creation: { type: 1, image } }, { type: 0 }] } } };
|
|
257
|
+
strict_1.default.deepEqual(extract([
|
|
258
|
+
{ block_type: 10010, is_finish: true, content: { gen_image_block: { images: [{ uri: "https://example.com/preview", full_size_url: ["https://example.com/preview"] }] } } },
|
|
259
|
+
card, card,
|
|
260
|
+
]), { artifacts: [{ kind: "image", url: imageUrl }], pending: false });
|
|
261
|
+
strict_1.default.deepEqual(extract({ ...card, is_finish: false }), { artifacts: [], pending: true });
|
|
262
|
+
});
|
|
188
263
|
(0, node_test_1.default)("豆包异步产物只匹配同会话、本轮回复或原始问题引用", () => {
|
|
189
264
|
const file = { block_type: 10020, is_finish: true, content_obj: { name: "result.txt", uri: "own-file" } };
|
|
190
265
|
const question = "59158521582734851";
|
|
@@ -252,7 +327,7 @@ function deferred() {
|
|
|
252
327
|
return new Response(JSON.stringify({ code: 0, data: { play_infos: [{ main: videoUrl }] } }));
|
|
253
328
|
},
|
|
254
329
|
};
|
|
255
|
-
const client = new cdp_client_1.DoubaoCdpClient({ conversationId: "own", timeoutMs: 5000, fetchImpl: async (input) => new Response(String(input) === imageUrl ? png : String(input) === videoUrl ? mp4 : "marker", { headers: { "content-type": String(input) === imageUrl ? "image/png" : String(input) === videoUrl ? "video/mp4" : "text/plain" } }) });
|
|
330
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", conversationId: "own", timeoutMs: 5000, fetchImpl: async (input) => new Response(String(input) === imageUrl ? png : String(input) === videoUrl ? mp4 : "marker", { headers: { "content-type": String(input) === imageUrl ? "image/png" : String(input) === videoUrl ? "video/mp4" : "text/plain" } }) });
|
|
256
331
|
client.connect = async () => { };
|
|
257
332
|
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
258
333
|
const result = await client.complete("生成产物");
|
|
@@ -279,7 +354,7 @@ function deferred() {
|
|
|
279
354
|
return new Response(`event: STREAM_MSG_NOTIFY\ndata: ${JSON.stringify({ content: { content_block: text === "请求甲" ? [block] : [{ content: { text_block: { text: "第二轮回复" } } }] } })}\n\nevent: CHUNK_DELTA\ndata: ${JSON.stringify({ text: text === "请求甲" ? "第一轮回复" : "" })}\n\n`);
|
|
280
355
|
},
|
|
281
356
|
};
|
|
282
|
-
const client = new cdp_client_1.DoubaoCdpClient({ conversationId: "test", timeoutMs: 5000, fetchImpl: async () => {
|
|
357
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", conversationId: "test", timeoutMs: 5000, fetchImpl: async () => {
|
|
283
358
|
downloadStarted.resolve();
|
|
284
359
|
await downloadGate.promise;
|
|
285
360
|
return new Response(png, { headers: { "content-type": "image/png" } });
|
|
@@ -313,6 +388,58 @@ function deferred() {
|
|
|
313
388
|
strict_1.default.equal(results[0].attachments.length, 1);
|
|
314
389
|
strict_1.default.equal(results[1].attachments, undefined);
|
|
315
390
|
});
|
|
391
|
+
(0, node_test_1.default)("豆包本地电脑任务持续产生流式进度时超过静默超时仍等待最终回复", { timeout: 10_000 }, async () => {
|
|
392
|
+
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 1_100 });
|
|
393
|
+
let requestId = "", polls = 0, progressSequence = 0, completed = false;
|
|
394
|
+
client.connect = async () => { };
|
|
395
|
+
client.evaluate = async (expression) => {
|
|
396
|
+
if (expression.includes("buildGeneralAgentCompletionTaskParam")) {
|
|
397
|
+
requestId = expression.match(/"unique_key":"([^"]+)"/)?.[1] ?? "";
|
|
398
|
+
strict_1.default.ok(requestId);
|
|
399
|
+
setTimeout(() => { progressSequence = 1; }, 600);
|
|
400
|
+
setTimeout(() => { progressSequence = 2; }, 1_300);
|
|
401
|
+
setTimeout(() => { completed = true; }, 2_300);
|
|
402
|
+
return "started";
|
|
403
|
+
}
|
|
404
|
+
if (expression === "window.__mab_doubao_result") {
|
|
405
|
+
polls++;
|
|
406
|
+
return completed ? JSON.stringify({ requestId, ok: true, reply: "图片已生成", conversationId: "123" }) : null;
|
|
407
|
+
}
|
|
408
|
+
if (expression === "window.__mab_doubao_progress")
|
|
409
|
+
return { requestId, sequence: progressSequence };
|
|
410
|
+
};
|
|
411
|
+
const result = await client.complete("生成图片");
|
|
412
|
+
strict_1.default.equal(result.text, "图片已生成");
|
|
413
|
+
strict_1.default.equal(polls, 3);
|
|
414
|
+
});
|
|
415
|
+
for (const state of ["无输出", "进度不再变化", "其他请求的进度"]) {
|
|
416
|
+
(0, node_test_1.default)(`豆包本地电脑${state}不能持续延长等待`, async (t) => {
|
|
417
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
418
|
+
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 2_000 });
|
|
419
|
+
let requestId = "", polls = 0;
|
|
420
|
+
client.connect = async () => { };
|
|
421
|
+
client.evaluate = async (expression) => {
|
|
422
|
+
if (expression.includes("buildGeneralAgentCompletionTaskParam")) {
|
|
423
|
+
requestId = expression.match(/"unique_key":"([^"]+)"/)?.[1] ?? "";
|
|
424
|
+
strict_1.default.ok(requestId);
|
|
425
|
+
return "started";
|
|
426
|
+
}
|
|
427
|
+
if (expression === "window.__mab_doubao_result") {
|
|
428
|
+
polls++;
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
431
|
+
if (expression === "window.__mab_doubao_progress")
|
|
432
|
+
return { requestId: state === "其他请求的进度" ? "other-request" : requestId, sequence: state === "无输出" ? 0 : state === "进度不再变化" ? 1 : polls };
|
|
433
|
+
};
|
|
434
|
+
const result = strict_1.default.rejects(client.complete("等待本轮回复"), (error) => error.code === "DOUBAO_REPLY_TIMEOUT");
|
|
435
|
+
for (let i = 0; i < 5; i++) {
|
|
436
|
+
await new Promise(setImmediate);
|
|
437
|
+
t.mock.timers.tick(1_000);
|
|
438
|
+
}
|
|
439
|
+
await result;
|
|
440
|
+
strict_1.default.equal(polls, state === "进度不再变化" ? 3 : 2);
|
|
441
|
+
});
|
|
442
|
+
}
|
|
316
443
|
for (const late of ["成功", "失败"]) {
|
|
317
444
|
(0, node_test_1.default)(`豆包前轮超时后的晚${late}回调不覆盖下一轮结果`, { timeout: 10_000 }, async () => {
|
|
318
445
|
const firstResponse = deferred(), nextResponse = deferred(), nextStarted = deferred();
|
|
@@ -323,7 +450,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
323
450
|
fetch: () => { if (++requests === 1)
|
|
324
451
|
return firstResponse.promise; nextStarted.resolve(); return nextResponse.promise; },
|
|
325
452
|
};
|
|
326
|
-
const client = new cdp_client_1.DoubaoCdpClient({ conversationId: "test", timeoutMs: 20 });
|
|
453
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", conversationId: "test", timeoutMs: 20 });
|
|
327
454
|
client.connect = async () => { };
|
|
328
455
|
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
329
456
|
await strict_1.default.rejects(client.complete("请求甲"), /等待豆包回复超时/);
|
|
@@ -348,6 +475,304 @@ for (const late of ["成功", "失败"]) {
|
|
|
348
475
|
(0, node_test_1.default)("豆包流式合并不吞掉正文中重复出现的词", () => {
|
|
349
476
|
strict_1.default.equal(doubao.mergeDoubaoReply("天空是蓝色。海水也是", "蓝色"), "天空是蓝色。海水也是蓝色");
|
|
350
477
|
});
|
|
478
|
+
(0, node_test_1.default)("豆包预分配失败且未确认时复用原请求有限重试", { timeout: 10_000 }, async () => {
|
|
479
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
480
|
+
const bodies = [];
|
|
481
|
+
let requests = 0;
|
|
482
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async (_url, init) => {
|
|
483
|
+
bodies.push(String(init.body));
|
|
484
|
+
if (++requests === 1)
|
|
485
|
+
return new Response('event: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n');
|
|
486
|
+
return new Response('event: SSE_ACK\ndata: {"ack_client_meta":{"conversation_id":"123"}}\n\nevent: CHUNK_DELTA\ndata: {"text":"重试后回复"}\n\n');
|
|
487
|
+
} };
|
|
488
|
+
client.connect = async () => { };
|
|
489
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
490
|
+
const result = await client.complete("重试请求");
|
|
491
|
+
strict_1.default.equal(result.text, "重试后回复");
|
|
492
|
+
strict_1.default.equal(requests, 2);
|
|
493
|
+
strict_1.default.equal(bodies[0], bodies[1]);
|
|
494
|
+
});
|
|
495
|
+
(0, node_test_1.default)("豆包预分配失败重试耗尽时保留平台错误码", { timeout: 10_000 }, async () => {
|
|
496
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
497
|
+
let requests = 0;
|
|
498
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => {
|
|
499
|
+
requests++;
|
|
500
|
+
return new Response('event: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n');
|
|
501
|
+
} };
|
|
502
|
+
client.connect = async () => { };
|
|
503
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
504
|
+
await strict_1.default.rejects(client.complete("重试请求"), /718013001.*尝试 3 次/);
|
|
505
|
+
strict_1.default.equal(requests, 3);
|
|
506
|
+
});
|
|
507
|
+
(0, node_test_1.default)("豆包已确认或已有进展的流错误不自动重发", { timeout: 10_000 }, async () => {
|
|
508
|
+
for (const stream of [
|
|
509
|
+
'event: SSE_ACK\ndata: {"ack_client_meta":{"conversation_id":"123"}}\n\nevent: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n',
|
|
510
|
+
'event: CHUNK_DELTA\ndata: {"text":"已开始"}\n\nevent: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n',
|
|
511
|
+
'event: STREAM_CHUNK\ndata: {"patch_op":[{"patch_value":{"content_block":[{"block_type":10020,"is_finish":false,"content":{"file_block":{"name":"生成中.pdf","uri":"pending"}}}]}}]}\n\nevent: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n',
|
|
512
|
+
]) {
|
|
513
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 2_000 });
|
|
514
|
+
let requests = 0;
|
|
515
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => { requests++; return new Response(stream); } };
|
|
516
|
+
client.connect = async () => { };
|
|
517
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
518
|
+
await strict_1.default.rejects(client.complete("不要重复发送"), /718013001/);
|
|
519
|
+
strict_1.default.equal(requests, 1);
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
(0, node_test_1.default)("豆包轮询期间 CDP 断开时重连后继续读取原结果且不重发", async () => {
|
|
523
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
524
|
+
let connected = 0;
|
|
525
|
+
let disconnected = false;
|
|
526
|
+
let submissions = 0;
|
|
527
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => {
|
|
528
|
+
submissions++;
|
|
529
|
+
return new Response('event: SSE_ACK\ndata: {"ack_client_meta":{"conversation_id":"456"}}\n\nevent: CHUNK_DELTA\ndata: {"text":"断线后继续回复"}\n\n');
|
|
530
|
+
} };
|
|
531
|
+
client.connect = async () => { connected++; };
|
|
532
|
+
client.evaluate = async (expression) => {
|
|
533
|
+
if (expression.includes("new AbortController()"))
|
|
534
|
+
disconnected = true;
|
|
535
|
+
if (expression === "window.__mab_doubao_result" && disconnected) {
|
|
536
|
+
disconnected = false;
|
|
537
|
+
throw new Error("豆包 CDP 已断开");
|
|
538
|
+
}
|
|
539
|
+
return (0, node_vm_1.runInNewContext)(expression, page);
|
|
540
|
+
};
|
|
541
|
+
const result = await client.complete("断线恢复");
|
|
542
|
+
strict_1.default.equal(result.text, "断线后继续回复");
|
|
543
|
+
strict_1.default.equal(connected, 2);
|
|
544
|
+
strict_1.default.equal(submissions, 1);
|
|
545
|
+
});
|
|
546
|
+
for (const recoveredState of ["已完成", "有新进度", "无新进度", "仍然断开"]) {
|
|
547
|
+
(0, node_test_1.default)(`豆包本地电脑流恢复跨过截止时间后核对原任务:${recoveredState}`, async (t) => {
|
|
548
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
549
|
+
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 2_000 });
|
|
550
|
+
let requestId = "", sequence = 0, submissions = 0, disconnected = false, reads = 0, connections = 0;
|
|
551
|
+
client.connect = async () => {
|
|
552
|
+
if (++connections > 1)
|
|
553
|
+
t.mock.timers.tick(2_500);
|
|
554
|
+
};
|
|
555
|
+
client.evaluate = async (expression) => {
|
|
556
|
+
if (expression.includes("buildGeneralAgentCompletionTaskParam")) {
|
|
557
|
+
submissions++;
|
|
558
|
+
requestId = expression.match(/"unique_key":"([^"]+)"/)?.[1] ?? "";
|
|
559
|
+
return "started";
|
|
560
|
+
}
|
|
561
|
+
if (expression === "window.__mab_doubao_request_id")
|
|
562
|
+
return requestId;
|
|
563
|
+
if (expression === "window.__mab_doubao_result") {
|
|
564
|
+
reads++;
|
|
565
|
+
if (!disconnected && reads === 2) {
|
|
566
|
+
disconnected = true;
|
|
567
|
+
throw new Error("豆包 CDP 已断开");
|
|
568
|
+
}
|
|
569
|
+
strict_1.default.ok(reads <= 4, "没有新进度时不得无限重连");
|
|
570
|
+
if (reads > 2 && recoveredState === "仍然断开")
|
|
571
|
+
throw new Error("豆包 CDP 已断开");
|
|
572
|
+
if (reads > 2 && recoveredState === "有新进度")
|
|
573
|
+
sequence = 1;
|
|
574
|
+
return reads > (recoveredState === "有新进度" ? 3 : 2) && (recoveredState === "已完成" || recoveredState === "有新进度")
|
|
575
|
+
? JSON.stringify({ requestId, ok: true, reply: "断线期间完成的回复", conversationId: "123" }) : null;
|
|
576
|
+
}
|
|
577
|
+
if (expression === "window.__mab_doubao_progress")
|
|
578
|
+
return { requestId, sequence };
|
|
579
|
+
};
|
|
580
|
+
const running = client.complete("断线期间持续生成", undefined, undefined, "");
|
|
581
|
+
void running.catch(() => undefined);
|
|
582
|
+
for (let i = 0; i < 5; i++) {
|
|
583
|
+
await new Promise(setImmediate);
|
|
584
|
+
t.mock.timers.tick(1_000);
|
|
585
|
+
}
|
|
586
|
+
if (recoveredState === "已完成" || recoveredState === "有新进度")
|
|
587
|
+
strict_1.default.equal((await running).text, "断线期间完成的回复");
|
|
588
|
+
else
|
|
589
|
+
await strict_1.default.rejects(running, (error) => error.code === "DOUBAO_REPLY_TIMEOUT");
|
|
590
|
+
strict_1.default.equal(reads, recoveredState === "有新进度" ? 4 : 3, "恢复后应重新读取原请求结果");
|
|
591
|
+
strict_1.default.equal(connections, 2);
|
|
592
|
+
strict_1.default.equal(submissions, 1, "恢复不应重复提交任务");
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
(0, node_test_1.default)("豆包任务执行中 CDP 端口拒绝连接时恢复桌面并读取原任务,不重复提交", async () => {
|
|
596
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000, recoverCdp: async () => { recoveries++; connected = true; } });
|
|
597
|
+
let connected = false, connections = 0, recoveries = 0, disconnected = false, submissions = 0;
|
|
598
|
+
let requestId = "";
|
|
599
|
+
client.connected = () => connected;
|
|
600
|
+
client.connect = async () => {
|
|
601
|
+
connections++;
|
|
602
|
+
if (connections === 2)
|
|
603
|
+
throw Object.assign(new Error("fetch failed"), { cause: { code: "ECONNREFUSED" } });
|
|
604
|
+
connected = true;
|
|
605
|
+
};
|
|
606
|
+
client.evaluate = async (expression) => {
|
|
607
|
+
if (expression.includes("new AbortController()")) {
|
|
608
|
+
submissions++;
|
|
609
|
+
requestId = JSON.parse(expression.match(/window\.__mab_doubao_request_id = (.*?);/)?.[1] ?? '""');
|
|
610
|
+
disconnected = true;
|
|
611
|
+
return "started";
|
|
612
|
+
}
|
|
613
|
+
if (expression === "window.__mab_doubao_result" && disconnected) {
|
|
614
|
+
disconnected = false;
|
|
615
|
+
connected = false;
|
|
616
|
+
throw new Error("豆包 CDP 已断开");
|
|
617
|
+
}
|
|
618
|
+
if (expression === "window.__mab_doubao_request_id")
|
|
619
|
+
return requestId;
|
|
620
|
+
if (expression === "window.__mab_doubao_result")
|
|
621
|
+
return JSON.stringify({ ok: true, reply: "恢复后的原任务回复", conversationId: "123" });
|
|
622
|
+
};
|
|
623
|
+
strict_1.default.equal((await client.complete("执行中的任务")).text, "恢复后的原任务回复");
|
|
624
|
+
strict_1.default.equal(recoveries, 1);
|
|
625
|
+
strict_1.default.equal(submissions, 1);
|
|
626
|
+
});
|
|
627
|
+
(0, node_test_1.default)("豆包 CDP 命令超时但旧 socket 仍打开时重建连接并继续读取原任务", { timeout: 10_000 }, async () => {
|
|
628
|
+
const original = globalThis.WebSocket;
|
|
629
|
+
const sockets = [];
|
|
630
|
+
let discoveries = 0;
|
|
631
|
+
let submissions = 0;
|
|
632
|
+
let requestId = "";
|
|
633
|
+
class TestWebSocket {
|
|
634
|
+
url;
|
|
635
|
+
readyState = 0;
|
|
636
|
+
onopen;
|
|
637
|
+
onclose;
|
|
638
|
+
onmessage;
|
|
639
|
+
constructor(url) {
|
|
640
|
+
this.url = url;
|
|
641
|
+
sockets.push(this);
|
|
642
|
+
queueMicrotask(() => { this.readyState = 1; this.onopen?.(); });
|
|
643
|
+
}
|
|
644
|
+
send(raw) {
|
|
645
|
+
const request = JSON.parse(raw);
|
|
646
|
+
const expression = request.params?.expression ?? "";
|
|
647
|
+
if (expression.includes("new AbortController()")) {
|
|
648
|
+
submissions++;
|
|
649
|
+
requestId = JSON.parse(expression.match(/window\.__mab_doubao_request_id = (.*?);/)?.[1] ?? '""');
|
|
650
|
+
}
|
|
651
|
+
if (sockets[0] === this && expression === "window.__mab_doubao_result")
|
|
652
|
+
return;
|
|
653
|
+
const value = expression === "window.__mab_doubao_request_id"
|
|
654
|
+
? requestId
|
|
655
|
+
: expression === "window.__mab_doubao_result"
|
|
656
|
+
? JSON.stringify({ ok: true, reply: "超时后恢复的回复", conversationId: "456" })
|
|
657
|
+
: "started";
|
|
658
|
+
queueMicrotask(() => this.onmessage?.({ data: JSON.stringify({ id: request.id, result: { result: { value } } }) }));
|
|
659
|
+
}
|
|
660
|
+
close() { if (this.readyState === 3)
|
|
661
|
+
return; this.readyState = 3; this.onclose?.(); }
|
|
662
|
+
}
|
|
663
|
+
globalThis.WebSocket = TestWebSocket;
|
|
664
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 2_500, fetchImpl: async () => new Response(JSON.stringify([
|
|
665
|
+
{ type: "page", url: "doubao://doubao-chat/chat/test", webSocketDebuggerUrl: `ws://page-${++discoveries}` },
|
|
666
|
+
])) });
|
|
667
|
+
const cdp = client.cdp.bind(client);
|
|
668
|
+
client.cdp = (method, params, timeoutMs = 50) => cdp(method, params, timeoutMs);
|
|
669
|
+
try {
|
|
670
|
+
strict_1.default.equal((await client.complete("超时后继续")).text, "超时后恢复的回复");
|
|
671
|
+
strict_1.default.equal(discoveries, 2);
|
|
672
|
+
strict_1.default.equal(sockets.length, 2);
|
|
673
|
+
strict_1.default.equal(submissions, 1);
|
|
674
|
+
}
|
|
675
|
+
finally {
|
|
676
|
+
await client.close();
|
|
677
|
+
globalThis.WebSocket = original;
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
(0, node_test_1.default)("豆包等待流结束时不重复注入请求", async () => {
|
|
681
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
682
|
+
let submissions = 0, polls = 0;
|
|
683
|
+
client.connect = async () => { };
|
|
684
|
+
client.evaluate = async (expression) => {
|
|
685
|
+
if (expression.includes("new AbortController()")) {
|
|
686
|
+
submissions++;
|
|
687
|
+
return "started";
|
|
688
|
+
}
|
|
689
|
+
if (expression === "window.__mab_doubao_result" && ++polls >= 2)
|
|
690
|
+
return JSON.stringify({ ok: true, reply: "原任务回复", conversationId: "456" });
|
|
691
|
+
};
|
|
692
|
+
strict_1.default.equal((await client.complete("只提交一次")).text, "原任务回复");
|
|
693
|
+
strict_1.default.equal(submissions, 1);
|
|
694
|
+
});
|
|
695
|
+
(0, node_test_1.default)("豆包重连后原页面任务丢失时明确报告结果未知,不重发", async () => {
|
|
696
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
697
|
+
let submissions = 0;
|
|
698
|
+
client.connect = async () => { };
|
|
699
|
+
client.evaluate = async (expression) => {
|
|
700
|
+
if (expression.includes("new AbortController()")) {
|
|
701
|
+
submissions++;
|
|
702
|
+
return "started";
|
|
703
|
+
}
|
|
704
|
+
if (expression === "window.__mab_doubao_result")
|
|
705
|
+
throw new Error("豆包 CDP 已断开");
|
|
706
|
+
};
|
|
707
|
+
await strict_1.default.rejects(client.complete("不能重复执行"), (error) => error.code === "DOUBAO_RESULT_UNKNOWN" && /原任务|页面/.test(error.message));
|
|
708
|
+
strict_1.default.equal(submissions, 1);
|
|
709
|
+
});
|
|
710
|
+
(0, node_test_1.default)("豆包 CDP 连接暂时失败会重试并记录恢复,耗尽时保留端口和原因", async () => {
|
|
711
|
+
const logs = [];
|
|
712
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", port: 19227, timeoutMs: 4_000, logger: { warn: (message) => logs.push(message) } });
|
|
713
|
+
let attempts = 0;
|
|
714
|
+
client.openConnection = async () => { if (++attempts < 3)
|
|
715
|
+
throw Object.assign(new Error("fetch failed"), { cause: { code: "ECONNREFUSED" } }); };
|
|
716
|
+
await client.connect();
|
|
717
|
+
strict_1.default.equal(attempts, 3);
|
|
718
|
+
strict_1.default.ok(logs.some(message => /19227.*重试/.test(message)));
|
|
719
|
+
attempts = 0;
|
|
720
|
+
client.openConnection = async () => { attempts++; throw Object.assign(new Error("fetch failed"), { cause: { code: "ECONNREFUSED" } }); };
|
|
721
|
+
await strict_1.default.rejects(client.complete("未提交的任务"), (error) => error.code === "DOUBAO_CDP_UNAVAILABLE" && /19227.*ECONNREFUSED/.test(error.message));
|
|
722
|
+
strict_1.default.equal(attempts, 3);
|
|
723
|
+
});
|
|
724
|
+
(0, node_test_1.default)("豆包预分配失败遇到取消不再重试", async () => {
|
|
725
|
+
const controller = new AbortController();
|
|
726
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
727
|
+
let submissions = 0;
|
|
728
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => {
|
|
729
|
+
submissions++;
|
|
730
|
+
controller.abort();
|
|
731
|
+
return new Response('event: STREAM_ERROR\ndata: {"error_code":718013001,"error_msg":"预分配会话位置信息失败"}\n\n');
|
|
732
|
+
} };
|
|
733
|
+
client.connect = async () => { };
|
|
734
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
735
|
+
await strict_1.default.rejects(client.complete("取消测试", controller.signal), /取消/);
|
|
736
|
+
strict_1.default.equal(submissions, 1);
|
|
737
|
+
});
|
|
738
|
+
(0, node_test_1.default)("豆包提交后 CDP 回执丢失时恢复原任务,不重复执行", async () => {
|
|
739
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
740
|
+
let submissions = 0, connections = 0;
|
|
741
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => {
|
|
742
|
+
submissions++;
|
|
743
|
+
return new Response('event: SSE_ACK\ndata: {"ack_client_meta":{"conversation_id":"456"}}\n\nevent: CHUNK_DELTA\ndata: {"text":"提交回执丢失后仍收到回复"}\n\n');
|
|
744
|
+
} };
|
|
745
|
+
client.connect = async () => { connections++; };
|
|
746
|
+
client.evaluate = async (expression) => {
|
|
747
|
+
const result = (0, node_vm_1.runInNewContext)(expression, page);
|
|
748
|
+
if (expression.includes("new AbortController()"))
|
|
749
|
+
throw new Error("豆包 CDP 已断开");
|
|
750
|
+
return result;
|
|
751
|
+
};
|
|
752
|
+
strict_1.default.equal((await client.complete("提交一次")).text, "提交回执丢失后仍收到回复");
|
|
753
|
+
strict_1.default.equal(submissions, 1);
|
|
754
|
+
strict_1.default.equal(connections, 2);
|
|
755
|
+
});
|
|
756
|
+
(0, node_test_1.default)("豆包风控和未知网络错误不重发任务,保留具体平台原因", async () => {
|
|
757
|
+
for (const failure of [
|
|
758
|
+
{ error_code: 718013001, error_msg: "需要验证", extra: { decision: { type: "verify", subtype: "slide" } } },
|
|
759
|
+
{ error_code: 12345, error_msg: "平台临时拒绝" },
|
|
760
|
+
undefined,
|
|
761
|
+
]) {
|
|
762
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 4_000 });
|
|
763
|
+
let submissions = 0;
|
|
764
|
+
const page = { window: {}, URL, AbortController, TextDecoder, navigator: { userAgent: "Chrome/147.0.0.0 SamanthaDoubao/2.32.6" }, performance: { getEntriesByType: () => [] }, fetch: async () => {
|
|
765
|
+
submissions++;
|
|
766
|
+
if (!failure)
|
|
767
|
+
throw new Error("fetch failed");
|
|
768
|
+
return new Response(`event: STREAM_ERROR\ndata: ${JSON.stringify(failure)}\n\n`);
|
|
769
|
+
} };
|
|
770
|
+
client.connect = async () => { };
|
|
771
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, page);
|
|
772
|
+
await strict_1.default.rejects(client.complete("不要重复执行"), (error) => /DOUBAO_(PLATFORM_ERROR|STREAM_FAILED)/.test(error.code) && /豆包平台流式响应/.test(error.stage));
|
|
773
|
+
strict_1.default.equal(submissions, 1);
|
|
774
|
+
}
|
|
775
|
+
});
|
|
351
776
|
(0, node_test_1.default)("豆包完整回复解析 SSE 图片并保留增量正文中的重复词", async () => {
|
|
352
777
|
const original = globalThis.WebSocket;
|
|
353
778
|
const block = { block_type: 2074, is_finish: true, content: { creation_block: { creations: [{ image: { status: 2, image_ori: { url: imageUrl } } }] } } };
|
|
@@ -392,7 +817,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
392
817
|
}
|
|
393
818
|
globalThis.WebSocket = TestWebSocket;
|
|
394
819
|
let downloads = 0;
|
|
395
|
-
const client = new cdp_client_1.DoubaoCdpClient({ fetchImpl: async (input) => {
|
|
820
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", fetchImpl: async (input) => {
|
|
396
821
|
if (String(input).endsWith("/json"))
|
|
397
822
|
return new Response(JSON.stringify([{ type: "page", url: "doubao://doubao-chat/chat/test", webSocketDebuggerUrl: "ws://test" }]));
|
|
398
823
|
strict_1.default.equal(String(input), imageUrl);
|
|
@@ -481,7 +906,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
481
906
|
});
|
|
482
907
|
(0, node_test_1.default)("未配置豆包参数时保留默认 CDP 端口", async () => {
|
|
483
908
|
let url = "";
|
|
484
|
-
const client = new cdp_client_1.DoubaoCdpClient({ port: undefined, conversationId: undefined, botId: undefined, timeoutMs: undefined, fetchImpl: async (input) => { url = String(input); return new Response("[]"); } });
|
|
909
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", port: undefined, conversationId: undefined, botId: undefined, timeoutMs: undefined, fetchImpl: async (input) => { url = String(input); return new Response("[]"); } });
|
|
485
910
|
await strict_1.default.rejects(client.connect(), /未找到豆包/);
|
|
486
911
|
strict_1.default.match(url, /:\d+\/json$/);
|
|
487
912
|
});
|
|
@@ -531,6 +956,10 @@ for (const late of ["成功", "失败"]) {
|
|
|
531
956
|
const existing = JSON.parse((0, cdp_client_1.requestBody)("继续", "123", "bot"));
|
|
532
957
|
strict_1.default.equal(existing.option.need_create_conversation, false);
|
|
533
958
|
strict_1.default.equal(existing.client_meta.local_conversation_id, undefined);
|
|
959
|
+
const localComputer = JSON.parse((0, cdp_client_1.requestBody)("执行", "123", "bot", [], [], "local-computer"));
|
|
960
|
+
strict_1.default.equal(localComputer.option.agent_mode, 1);
|
|
961
|
+
strict_1.default.equal(localComputer.option.aggregate_params.agent_mode, "1");
|
|
962
|
+
strict_1.default.equal(localComputer.ext.agent_mode, "1");
|
|
534
963
|
});
|
|
535
964
|
(0, node_test_1.default)("停止豆包客户端时向页面发送请求取消指令", async () => {
|
|
536
965
|
const original = globalThis.WebSocket;
|
|
@@ -549,7 +978,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
549
978
|
}
|
|
550
979
|
globalThis.WebSocket = TestWebSocket;
|
|
551
980
|
try {
|
|
552
|
-
const client = new cdp_client_1.DoubaoCdpClient({ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "https://www.doubao.com/chat/test", webSocketDebuggerUrl: "ws://test" }])) });
|
|
981
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "https://www.doubao.com/chat/test", webSocketDebuggerUrl: "ws://test" }])) });
|
|
553
982
|
await client.connect();
|
|
554
983
|
const pending = client.direct("停止测试").catch((error) => String(error));
|
|
555
984
|
await new Promise((resolve) => setImmediate(resolve));
|
|
@@ -586,7 +1015,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
586
1015
|
close() { this.readyState = 3; this.onclose?.(); }
|
|
587
1016
|
}
|
|
588
1017
|
globalThis.WebSocket = TestWebSocket;
|
|
589
|
-
const client = new cdp_client_1.DoubaoCdpClient({ fetchImpl: async () => {
|
|
1018
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", fetchImpl: async () => {
|
|
590
1019
|
if (++discoveries === 2)
|
|
591
1020
|
await secondDiscovery.promise;
|
|
592
1021
|
return new Response(JSON.stringify([{ type: "page", url: "doubao://doubao-chat/chat/test", webSocketDebuggerUrl: "ws://test" }]));
|
|
@@ -642,7 +1071,7 @@ for (const late of ["成功", "失败"]) {
|
|
|
642
1071
|
}
|
|
643
1072
|
globalThis.WebSocket = TestWebSocket;
|
|
644
1073
|
let discoveries = 0;
|
|
645
|
-
const client = new cdp_client_1.DoubaoCdpClient({ timeoutMs: 2000, fetchImpl: async () => new Response(JSON.stringify([
|
|
1074
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 2000, fetchImpl: async () => new Response(JSON.stringify([
|
|
646
1075
|
{ type: "page", url: "doubao://doubao-chat/chat/test", webSocketDebuggerUrl: `ws://page-${++discoveries}` },
|
|
647
1076
|
])) });
|
|
648
1077
|
try {
|
|
@@ -682,6 +1111,264 @@ for (const late of ["成功", "失败"]) {
|
|
|
682
1111
|
await plugin.deactivate();
|
|
683
1112
|
}
|
|
684
1113
|
});
|
|
1114
|
+
(0, node_test_1.default)("豆包自动启动复用默认用户目录,但不结束人工进程", async () => {
|
|
1115
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-default-profile-"));
|
|
1116
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1117
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1118
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1119
|
+
let connections = 0;
|
|
1120
|
+
let profile = "未捕获";
|
|
1121
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => {
|
|
1122
|
+
connections++;
|
|
1123
|
+
if (connections === 1)
|
|
1124
|
+
throw Object.assign(new TypeError("fetch failed"), { cause: { code: "ECONNREFUSED" } });
|
|
1125
|
+
});
|
|
1126
|
+
let terminated = false;
|
|
1127
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => { terminated = true; return []; });
|
|
1128
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async (...args) => { profile = args[3]; });
|
|
1129
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1130
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1131
|
+
try {
|
|
1132
|
+
await plugin.activate({ config: { executablePath: executable } });
|
|
1133
|
+
strict_1.default.equal(terminated, false);
|
|
1134
|
+
strict_1.default.equal(profile, undefined);
|
|
1135
|
+
}
|
|
1136
|
+
finally {
|
|
1137
|
+
await plugin.deactivate();
|
|
1138
|
+
restart.mock.restore();
|
|
1139
|
+
terminate.mock.restore();
|
|
1140
|
+
connect.mock.restore();
|
|
1141
|
+
Object.defineProperty(process, "platform", platform);
|
|
1142
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
(0, node_test_1.default)("豆包非端口连接错误不结束人工进程", async () => {
|
|
1146
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-connect-error-"));
|
|
1147
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1148
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1149
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1150
|
+
let terminated = false;
|
|
1151
|
+
let restarted = false;
|
|
1152
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => { throw new Error("豆包页面协议错误"); });
|
|
1153
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => { terminated = true; return []; });
|
|
1154
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async () => { restarted = true; });
|
|
1155
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1156
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1157
|
+
try {
|
|
1158
|
+
await strict_1.default.rejects(plugin.activate({ config: { executablePath: executable } }), /豆包页面协议错误/);
|
|
1159
|
+
strict_1.default.equal(terminated, false);
|
|
1160
|
+
strict_1.default.equal(restarted, false);
|
|
1161
|
+
}
|
|
1162
|
+
finally {
|
|
1163
|
+
await plugin.deactivate();
|
|
1164
|
+
restart.mock.restore();
|
|
1165
|
+
terminate.mock.restore();
|
|
1166
|
+
connect.mock.restore();
|
|
1167
|
+
Object.defineProperty(process, "platform", platform);
|
|
1168
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
(0, node_test_1.default)("豆包普通任务 CDP 端口拒绝连接时恢复桥接实例并继续原会话", async () => {
|
|
1172
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-cleanup-error-"));
|
|
1173
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1174
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1175
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1176
|
+
let connected = true, connections = 0, restarted = false;
|
|
1177
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => { connections++; if (!connected)
|
|
1178
|
+
throw Object.assign(new Error("CDP 未连接"), { code: "ECONNREFUSED" }); });
|
|
1179
|
+
const connectionState = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connected", () => connected);
|
|
1180
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => { strict_1.default.fail("不能结束人工桌面"); return []; });
|
|
1181
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async (...args) => {
|
|
1182
|
+
strict_1.default.equal(args[0], undefined, "不得接管人工进程");
|
|
1183
|
+
restarted = true;
|
|
1184
|
+
connected = true;
|
|
1185
|
+
await args[4]();
|
|
1186
|
+
});
|
|
1187
|
+
const complete = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "complete", async (_text, _signal, _attachments, conversationId) => {
|
|
1188
|
+
strict_1.default.ok(connected);
|
|
1189
|
+
strict_1.default.equal(conversationId, "123");
|
|
1190
|
+
return { text: "原会话已恢复", sessionId: "123" };
|
|
1191
|
+
});
|
|
1192
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1193
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1194
|
+
try {
|
|
1195
|
+
await plugin.activate({ config: { executablePath: executable } });
|
|
1196
|
+
plugin.bindings.set("peer-a", "123");
|
|
1197
|
+
connected = false;
|
|
1198
|
+
strict_1.default.deepEqual(await plugin.handle({ task: "恢复原任务", sessionId: "peer-a" }), { text: "原会话已恢复", sessionId: "peer-a" });
|
|
1199
|
+
strict_1.default.equal(restarted, true);
|
|
1200
|
+
strict_1.default.equal(connections, 3);
|
|
1201
|
+
}
|
|
1202
|
+
finally {
|
|
1203
|
+
await plugin.deactivate();
|
|
1204
|
+
restart.mock.restore();
|
|
1205
|
+
terminate.mock.restore();
|
|
1206
|
+
connect.mock.restore();
|
|
1207
|
+
connectionState.mock.restore();
|
|
1208
|
+
complete.mock.restore();
|
|
1209
|
+
Object.defineProperty(process, "platform", platform);
|
|
1210
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
(0, node_test_1.default)("豆包默认用户目录被占用时复制登录状态到桥接目录", async () => {
|
|
1214
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-profile-fallback-"));
|
|
1215
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1216
|
+
const localAppData = (0, node_path_1.join)(root, "local-app-data");
|
|
1217
|
+
const dataDir = (0, node_path_1.join)(root, "state");
|
|
1218
|
+
const defaultProfile = (0, node_path_1.join)(localAppData, "Doubao", "User Data");
|
|
1219
|
+
const cookieFile = (0, node_path_1.join)(defaultProfile, "Default", "Network", "Cookies");
|
|
1220
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1221
|
+
const previousLocalAppData = process.env.LOCALAPPDATA;
|
|
1222
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(defaultProfile, "Default", "Network"), { recursive: true });
|
|
1223
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1224
|
+
await (0, promises_1.writeFile)(cookieFile, "logged-in-cookie");
|
|
1225
|
+
process.env.LOCALAPPDATA = localAppData;
|
|
1226
|
+
let connections = 0;
|
|
1227
|
+
const profiles = [];
|
|
1228
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => {
|
|
1229
|
+
connections++;
|
|
1230
|
+
if (connections === 1)
|
|
1231
|
+
throw Object.assign(new Error("CDP 未连接"), { code: "ECONNREFUSED" });
|
|
1232
|
+
});
|
|
1233
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async (...args) => {
|
|
1234
|
+
profiles.push(args[3]);
|
|
1235
|
+
if (profiles.length === 1)
|
|
1236
|
+
throw new Error("默认用户目录已被占用");
|
|
1237
|
+
});
|
|
1238
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => []);
|
|
1239
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1240
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1241
|
+
try {
|
|
1242
|
+
await plugin.activate({ config: { executablePath: executable }, dataDir });
|
|
1243
|
+
strict_1.default.equal(profiles[0], undefined);
|
|
1244
|
+
strict_1.default.equal(profiles.length, 2);
|
|
1245
|
+
strict_1.default.equal(typeof profiles[1], "string");
|
|
1246
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(String(profiles[1]), "Default", "Network", "Cookies"), "utf8"), "logged-in-cookie");
|
|
1247
|
+
}
|
|
1248
|
+
finally {
|
|
1249
|
+
await plugin.deactivate();
|
|
1250
|
+
terminate.mock.restore();
|
|
1251
|
+
restart.mock.restore();
|
|
1252
|
+
connect.mock.restore();
|
|
1253
|
+
Object.defineProperty(process, "platform", platform);
|
|
1254
|
+
if (previousLocalAppData === undefined)
|
|
1255
|
+
delete process.env.LOCALAPPDATA;
|
|
1256
|
+
else
|
|
1257
|
+
process.env.LOCALAPPDATA = previousLocalAppData;
|
|
1258
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1259
|
+
}
|
|
1260
|
+
});
|
|
1261
|
+
(0, node_test_1.default)("豆包登录文件被人工进程锁定时仍完成桥接启动", async () => {
|
|
1262
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-locked-profile-"));
|
|
1263
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1264
|
+
const localAppData = (0, node_path_1.join)(root, "local-app-data");
|
|
1265
|
+
const dataDir = (0, node_path_1.join)(root, "state");
|
|
1266
|
+
const defaultProfile = (0, node_path_1.join)(localAppData, "Doubao", "User Data");
|
|
1267
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1268
|
+
const previousLocalAppData = process.env.LOCALAPPDATA;
|
|
1269
|
+
await (0, promises_1.mkdir)(defaultProfile, { recursive: true });
|
|
1270
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(dataDir, "desktop-profile"), { recursive: true });
|
|
1271
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1272
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(defaultProfile, "Local State"), "logged-in-state");
|
|
1273
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dataDir, "desktop-profile", "Local State"), "existing-login-state");
|
|
1274
|
+
process.env.LOCALAPPDATA = localAppData;
|
|
1275
|
+
let connections = 0;
|
|
1276
|
+
const profiles = [];
|
|
1277
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => {
|
|
1278
|
+
connections++;
|
|
1279
|
+
if (connections === 1)
|
|
1280
|
+
throw Object.assign(new Error("CDP 未连接"), { code: "ECONNREFUSED" });
|
|
1281
|
+
});
|
|
1282
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async (...args) => {
|
|
1283
|
+
profiles.push(args[3]);
|
|
1284
|
+
if (profiles.length === 1)
|
|
1285
|
+
throw new Error("默认用户目录已被占用");
|
|
1286
|
+
});
|
|
1287
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => []);
|
|
1288
|
+
const copy = node_test_1.mock.method(fsPromises, "cp", async () => {
|
|
1289
|
+
throw Object.assign(new Error("文件被占用"), { code: "EBUSY" });
|
|
1290
|
+
});
|
|
1291
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1292
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1293
|
+
try {
|
|
1294
|
+
await plugin.activate({ config: { executablePath: executable }, dataDir });
|
|
1295
|
+
strict_1.default.equal(profiles.length, 2);
|
|
1296
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(dataDir, "desktop-profile", "Local State"), "utf8"), "existing-login-state");
|
|
1297
|
+
}
|
|
1298
|
+
finally {
|
|
1299
|
+
await plugin.deactivate();
|
|
1300
|
+
copy.mock.restore();
|
|
1301
|
+
terminate.mock.restore();
|
|
1302
|
+
restart.mock.restore();
|
|
1303
|
+
connect.mock.restore();
|
|
1304
|
+
Object.defineProperty(process, "platform", platform);
|
|
1305
|
+
if (previousLocalAppData === undefined)
|
|
1306
|
+
delete process.env.LOCALAPPDATA;
|
|
1307
|
+
else
|
|
1308
|
+
process.env.LOCALAPPDATA = previousLocalAppData;
|
|
1309
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1310
|
+
}
|
|
1311
|
+
});
|
|
1312
|
+
(0, node_test_1.default)("豆包登录文件可用备份读取时复制完整状态", async () => {
|
|
1313
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-backup-profile-"));
|
|
1314
|
+
const executable = (0, node_path_1.join)(root, "Doubao.exe");
|
|
1315
|
+
const localAppData = (0, node_path_1.join)(root, "local-app-data");
|
|
1316
|
+
const dataDir = (0, node_path_1.join)(root, "state");
|
|
1317
|
+
const defaultProfile = (0, node_path_1.join)(localAppData, "Doubao", "User Data");
|
|
1318
|
+
const platform = Object.getOwnPropertyDescriptor(process, "platform");
|
|
1319
|
+
const previousLocalAppData = process.env.LOCALAPPDATA;
|
|
1320
|
+
await (0, promises_1.mkdir)(defaultProfile, { recursive: true });
|
|
1321
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
1322
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(defaultProfile, "Local State"), "logged-in-state");
|
|
1323
|
+
process.env.LOCALAPPDATA = localAppData;
|
|
1324
|
+
let connections = 0;
|
|
1325
|
+
const profiles = [];
|
|
1326
|
+
const connect = node_test_1.mock.method(cdp_client_1.DoubaoCdpClient.prototype, "connect", async () => {
|
|
1327
|
+
connections++;
|
|
1328
|
+
if (connections === 1)
|
|
1329
|
+
throw Object.assign(new Error("CDP 未连接"), { code: "ECONNREFUSED" });
|
|
1330
|
+
});
|
|
1331
|
+
const restart = node_test_1.mock.method(desktopLaunch, "restartOwnedAndWait", async (...args) => {
|
|
1332
|
+
profiles.push(args[3]);
|
|
1333
|
+
if (profiles.length === 1)
|
|
1334
|
+
throw new Error("默认用户目录已被占用");
|
|
1335
|
+
});
|
|
1336
|
+
const terminate = node_test_1.mock.method(desktopLaunch, "terminateWindowsDesktop", async () => []);
|
|
1337
|
+
const copy = node_test_1.mock.method(fsPromises, "cp", async () => {
|
|
1338
|
+
throw Object.assign(new Error("文件被占用"), { code: "EBUSY" });
|
|
1339
|
+
});
|
|
1340
|
+
const robocopy = node_test_1.mock.method(childProcess, "execFile", (file, args, _options, callback) => {
|
|
1341
|
+
strict_1.default.equal(file, "robocopy.exe");
|
|
1342
|
+
const source = args[0];
|
|
1343
|
+
const target = args[1];
|
|
1344
|
+
const name = args[2];
|
|
1345
|
+
(0, node_fs_1.mkdirSync)(target, { recursive: true });
|
|
1346
|
+
(0, node_fs_1.writeFileSync)((0, node_path_1.join)(target, name), (0, node_fs_1.readFileSync)((0, node_path_1.join)(source, name)));
|
|
1347
|
+
callback(null);
|
|
1348
|
+
return {};
|
|
1349
|
+
});
|
|
1350
|
+
Object.defineProperty(process, "platform", { value: "win32" });
|
|
1351
|
+
const plugin = new doubao_1.DoubaoPlugin();
|
|
1352
|
+
try {
|
|
1353
|
+
await plugin.activate({ config: { executablePath: executable }, dataDir });
|
|
1354
|
+
strict_1.default.equal(profiles.length, 2);
|
|
1355
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(dataDir, "desktop-profile", "Local State"), "utf8"), "logged-in-state");
|
|
1356
|
+
}
|
|
1357
|
+
finally {
|
|
1358
|
+
await plugin.deactivate();
|
|
1359
|
+
robocopy.mock.restore();
|
|
1360
|
+
copy.mock.restore();
|
|
1361
|
+
terminate.mock.restore();
|
|
1362
|
+
restart.mock.restore();
|
|
1363
|
+
connect.mock.restore();
|
|
1364
|
+
Object.defineProperty(process, "platform", platform);
|
|
1365
|
+
if (previousLocalAppData === undefined)
|
|
1366
|
+
delete process.env.LOCALAPPDATA;
|
|
1367
|
+
else
|
|
1368
|
+
process.env.LOCALAPPDATA = previousLocalAppData;
|
|
1369
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
685
1372
|
(0, node_test_1.default)("豆包 /new 切换独立会话,重载后仍使用服务端会话标识", async () => {
|
|
686
1373
|
const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-sessions-"));
|
|
687
1374
|
const targets = [];
|
|
@@ -754,3 +1441,77 @@ for (const late of ["成功", "失败"]) {
|
|
|
754
1441
|
await plugin.deactivate();
|
|
755
1442
|
}
|
|
756
1443
|
});
|
|
1444
|
+
(0, node_test_1.default)("豆包模型排队取消不新建会话,生成取消后仍等待底层结束", async () => {
|
|
1445
|
+
let release;
|
|
1446
|
+
const calls = [];
|
|
1447
|
+
const plugin = new doubao_1.DoubaoPlugin({
|
|
1448
|
+
connect: async () => undefined, close: async () => undefined, connected: () => true,
|
|
1449
|
+
direct: async () => "回复", createSession: async () => { calls.push("create"); },
|
|
1450
|
+
complete: async (text) => { calls.push(text); if (text === "模型提示")
|
|
1451
|
+
await new Promise(resolve => { release = resolve; }); return { text: "回复", sessionId: "123" }; },
|
|
1452
|
+
});
|
|
1453
|
+
await plugin.activate({ config: {} });
|
|
1454
|
+
const activeController = new AbortController();
|
|
1455
|
+
let error;
|
|
1456
|
+
const generating = plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-active", timeoutMs: 5000, signal: activeController.signal }).catch((value) => { error = value; });
|
|
1457
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1458
|
+
const queuedController = new AbortController();
|
|
1459
|
+
let queuedError;
|
|
1460
|
+
const queued = plugin.modelProvider.complete({ prompt: "不应发送", sessionId: "pa-queued", timeoutMs: 5000, signal: queuedController.signal }).catch((value) => { queuedError = value; });
|
|
1461
|
+
activeController.abort(new Error("取消生成等待"));
|
|
1462
|
+
queuedController.abort(new Error("取消排队等待"));
|
|
1463
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1464
|
+
const rejectedImmediately = Boolean(error && queuedError);
|
|
1465
|
+
const before = [...calls];
|
|
1466
|
+
release();
|
|
1467
|
+
await Promise.all([generating, queued]);
|
|
1468
|
+
await plugin.handle({ task: "普通任务" });
|
|
1469
|
+
await plugin.deactivate();
|
|
1470
|
+
strict_1.default.equal(rejectedImmediately, true);
|
|
1471
|
+
strict_1.default.match(error.stage, /generate/);
|
|
1472
|
+
strict_1.default.match(queuedError.stage, /queue/);
|
|
1473
|
+
strict_1.default.deepEqual(before, ["模型提示"]);
|
|
1474
|
+
strict_1.default.deepEqual(calls, ["模型提示", "普通任务"]);
|
|
1475
|
+
});
|
|
1476
|
+
(0, node_test_1.default)("豆包模型断线时优先恢复连接且不新建会话", async () => {
|
|
1477
|
+
let connected = true;
|
|
1478
|
+
let reconnects = 0;
|
|
1479
|
+
let creates = 0;
|
|
1480
|
+
let connections = 0;
|
|
1481
|
+
const plugin = new doubao_1.DoubaoPlugin({
|
|
1482
|
+
connect: async () => { connections++; connected = true; }, close: async () => undefined, connected: () => connected,
|
|
1483
|
+
direct: async () => "回复", createSession: async () => { creates++; }, complete: async () => ({ text: "回复", sessionId: "123" }),
|
|
1484
|
+
});
|
|
1485
|
+
await plugin.activate({ config: {} });
|
|
1486
|
+
const reconnect = plugin.reconnect;
|
|
1487
|
+
plugin.reconnect = async () => { reconnects++; await reconnect(); };
|
|
1488
|
+
connected = false;
|
|
1489
|
+
const result = await plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-offline", timeoutMs: 5000, signal: new AbortController().signal });
|
|
1490
|
+
await plugin.deactivate();
|
|
1491
|
+
strict_1.default.equal(result.text, "回复");
|
|
1492
|
+
strict_1.default.equal(connections, 2);
|
|
1493
|
+
strict_1.default.equal(reconnects, 1);
|
|
1494
|
+
strict_1.default.equal(creates, 0);
|
|
1495
|
+
});
|
|
1496
|
+
(0, node_test_1.default)("豆包取消后仍读取真实页面请求的迟到结果,不提前释放内部队列", async (t) => {
|
|
1497
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
1498
|
+
const client = new cdp_client_1.DoubaoCdpClient({ mode: "chat", timeoutMs: 10 });
|
|
1499
|
+
const controller = new AbortController();
|
|
1500
|
+
let polled = false;
|
|
1501
|
+
let settled = false;
|
|
1502
|
+
client.connect = async () => undefined;
|
|
1503
|
+
client.evaluate = async (expression) => {
|
|
1504
|
+
if (expression === "window.__mab_doubao_result") {
|
|
1505
|
+
polled = true;
|
|
1506
|
+
return JSON.stringify({ ok: true, reply: "迟到回复", conversationId: "123" });
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
const running = client.complete("模型提示", controller.signal, undefined, "").then((value) => { settled = true; return value; });
|
|
1510
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1511
|
+
controller.abort(new Error("停止等待"));
|
|
1512
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
1513
|
+
strict_1.default.equal(settled, false);
|
|
1514
|
+
t.mock.timers.tick(1000);
|
|
1515
|
+
strict_1.default.deepEqual(await running, { text: "迟到回复", sessionId: "123" });
|
|
1516
|
+
strict_1.default.equal(polled, true);
|
|
1517
|
+
});
|