mu-agent-bridge 1.0.73 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +7 -28
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +7 -2
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-manager.js +185 -58
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +89 -11
- package/dist/src-ts/plugins/aun/messages.js +1 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/dist/src-ts/plugins/doubao/index.js +119 -7
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +102 -23
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +27 -8
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-model-config.test.js +44 -41
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +14 -0
- package/dist/tests-ts/aun-plugin.test.js +207 -19
- package/dist/tests-ts/bundled-package.test.js +1 -1
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
- package/dist/tests-ts/control-server.test.js +20 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +688 -19
- package/dist/tests-ts/dsh-agent.test.js +91 -19
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/local-cli.test.js +29 -2
- package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant.test.js +277 -3
- package/dist/tests-ts/platform-integration.test.js +60 -1
- package/dist/tests-ts/plugin-manager.test.js +78 -6
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +1 -1
- package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +8 -8
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +1 -1
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +3 -2
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/plugins/personal-assistant/package.json +2 -2
- package/plugins/personal-assistant/plugin.json +1 -1
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -0,0 +1,894 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_vm_1 = require("node:vm");
|
|
12
|
+
const node_crypto_1 = require("node:crypto");
|
|
13
|
+
const cdp_client_1 = require("../src-ts/plugins/zcode/cdp-client");
|
|
14
|
+
const zcode_1 = require("../src-ts/plugins/zcode");
|
|
15
|
+
const attachments_1 = require("../src-ts/attachments");
|
|
16
|
+
function fixture(timeoutMs = 50) {
|
|
17
|
+
const page = { renderedUser: undefined, draft: "", editable: true, sessionId: "session_test", running: false, hostState: "false", completed: true, sent: 0, inserted: 0, errors: "", quotaError: "", chatError: "", rows: [{ id: "1", role: "assistant", text: "旧回复" }], reply: "本轮回复", images: {}, files: {}, permission: undefined, clicked: [], nextPermission: undefined, taskPages: [["session_test"]], expanded: 0 };
|
|
18
|
+
const lists = new WeakMap();
|
|
19
|
+
function permissionList() {
|
|
20
|
+
if (!page.permission)
|
|
21
|
+
return null;
|
|
22
|
+
const permission = page.permission;
|
|
23
|
+
if (lists.has(permission))
|
|
24
|
+
return lists.get(permission);
|
|
25
|
+
let selected = 0;
|
|
26
|
+
const nodes = permission.labels.map((label, index) => ({
|
|
27
|
+
innerText: `${index + 1}. ${label}`,
|
|
28
|
+
getAttribute: (name) => name === "aria-label" ? label : name === "aria-selected" ? String(index === selected) : name === "aria-disabled" ? String(!!permission.disabled) : name === "data-permission-option-kind" ? ["allowOnce", "allowAlways", "rejectOnce"][index] : null,
|
|
29
|
+
get disabled() { return !!permission.disabled; },
|
|
30
|
+
focus() { selected = index; },
|
|
31
|
+
click() {
|
|
32
|
+
strict_1.default.equal(selected, index, "必须先选中再确认权限");
|
|
33
|
+
page.clicked.push(label);
|
|
34
|
+
page.permission = page.nextPermission;
|
|
35
|
+
page.nextPermission = undefined;
|
|
36
|
+
page.running = !!page.permission;
|
|
37
|
+
},
|
|
38
|
+
closest: () => list,
|
|
39
|
+
}));
|
|
40
|
+
const list = { parentElement: { previousElementSibling: { innerText: permission.prompt } }, querySelectorAll: () => nodes };
|
|
41
|
+
lists.set(permission, list);
|
|
42
|
+
return list;
|
|
43
|
+
}
|
|
44
|
+
const content = (text, rowId, user = false) => ({
|
|
45
|
+
innerText: user && page.renderedUser !== undefined ? page.renderedUser : text,
|
|
46
|
+
...(user ? { __reactFiber_test: { memoizedProps: { rowId, contentText: text } } } : {}),
|
|
47
|
+
querySelectorAll: (query) => query.includes("data-markdown-image") ? (page.images[rowId ?? ""] ?? []).map(image => ({
|
|
48
|
+
getAttribute: (name) => name === "src" ? image.src : null,
|
|
49
|
+
closest: () => ({ getAttribute: (name) => name === "title" ? image.title ?? null : null }),
|
|
50
|
+
})) : query === "button[title]" ? (page.files[rowId ?? ""] ?? []).map(file => ({
|
|
51
|
+
getAttribute: (name) => name === "title" ? file.path : null,
|
|
52
|
+
__reactFiber_test: { memoizedProps: file.plain ? {} : { fileLink: { path: file.path, workspaceRemoteSessionId: file.remote } } },
|
|
53
|
+
})) : [],
|
|
54
|
+
});
|
|
55
|
+
const editor = { get innerText() { return page.draft; }, focus() { }, getAttribute: () => String(page.editable) };
|
|
56
|
+
const errorNodes = {
|
|
57
|
+
errors: { get innerText() { return page.errors; } },
|
|
58
|
+
quotaError: { get innerText() { return page.quotaError; } },
|
|
59
|
+
chatError: { get innerText() { return page.chatError; } },
|
|
60
|
+
};
|
|
61
|
+
const send = { get disabled() { return !page.draft; }, getAttribute: () => null, click() {
|
|
62
|
+
page.sent++;
|
|
63
|
+
page.errors = page.quotaError = page.chatError = "";
|
|
64
|
+
if (page.sessionId === "draft")
|
|
65
|
+
page.sessionId = "session_test";
|
|
66
|
+
page.rows.push({ id: "2", role: "user", text: page.draft }, { id: "3", role: "assistant", text: page.reply });
|
|
67
|
+
page.draft = "";
|
|
68
|
+
} };
|
|
69
|
+
const root = {
|
|
70
|
+
closest: () => ({ getAttribute: () => page.running ? "true" : page.hostState }),
|
|
71
|
+
querySelector(selector) {
|
|
72
|
+
if (selector.includes('[data-task-list-show-more="true"]'))
|
|
73
|
+
return page.expanded + 1 < page.taskPages.length ? { getAttribute: () => null, click() { page.expanded++; } } : null;
|
|
74
|
+
if (selector.startsWith('[data-testid="task-item-')) {
|
|
75
|
+
const id = selector.slice('[data-testid="task-item-'.length, -2);
|
|
76
|
+
return page.taskPages.slice(0, page.expanded + 1).flat().includes(id) ? { getAttribute: () => null, click() { page.sessionId = id; page.errors = page.quotaError = page.chatError = ""; } } : null;
|
|
77
|
+
}
|
|
78
|
+
if (selector.includes("data-permission-option-kind"))
|
|
79
|
+
return permissionList()?.querySelectorAll()[0] ?? null;
|
|
80
|
+
if (selector === '[data-testid="v4-composer-input"]')
|
|
81
|
+
return editor;
|
|
82
|
+
if (selector === '[data-testid="v4-session-pane-workspace-main"]')
|
|
83
|
+
return { getAttribute: () => page.sessionId };
|
|
84
|
+
if (selector === '[data-testid="v4-composer-send"]')
|
|
85
|
+
return send;
|
|
86
|
+
if (selector === '[data-testid="task-new-button"]')
|
|
87
|
+
return { getAttribute: () => null, click() { page.draft = ""; page.sessionId = "draft"; page.rows = []; page.errors = page.quotaError = page.chatError = ""; } };
|
|
88
|
+
if (selector.startsWith('[data-testid="v4-feedback-like-'))
|
|
89
|
+
return page.completed ? {} : null;
|
|
90
|
+
if (/停止|Stop|chat-loading/.test(selector))
|
|
91
|
+
return page.running ? {} : null;
|
|
92
|
+
if (selector.includes('v4-session-quota-banner') && page.quotaError)
|
|
93
|
+
return errorNodes.quotaError;
|
|
94
|
+
if (selector.includes('chat-error-banner') && page.chatError)
|
|
95
|
+
return errorNodes.chatError;
|
|
96
|
+
if (/role="alert"|error/.test(selector))
|
|
97
|
+
return page.errors ? errorNodes.errors : null;
|
|
98
|
+
return null;
|
|
99
|
+
},
|
|
100
|
+
querySelectorAll(selector) {
|
|
101
|
+
if (selector.includes("data-permission-option-kind"))
|
|
102
|
+
return permissionList()?.querySelectorAll() ?? [];
|
|
103
|
+
if (selector !== '[data-row-id]')
|
|
104
|
+
return [];
|
|
105
|
+
return page.rows.map(row => ({
|
|
106
|
+
getAttribute: (name) => name === "data-row-id" ? row.id : null,
|
|
107
|
+
classList: { contains: (name) => name === "group/assistant-row" && row.role === "assistant" },
|
|
108
|
+
querySelector: (query) => query.includes("data-v4-user-input") ? row.role === "user" ? content(row.text, row.id, true) : null : row.role === "assistant" ? content(row.text, row.id) : null,
|
|
109
|
+
}));
|
|
110
|
+
},
|
|
111
|
+
};
|
|
112
|
+
const document = { querySelector: (selector) => selector === '[data-testid="v4-pane-shell-workspace-main"]' ? root : root.querySelector(selector) };
|
|
113
|
+
const client = new cdp_client_1.ZcodeCdpClient({ timeoutMs });
|
|
114
|
+
const internal = client;
|
|
115
|
+
internal.evaluate = async (expression) => {
|
|
116
|
+
const value = (0, node_vm_1.runInNewContext)(expression, { document, crypto: { randomUUID: node_crypto_1.randomUUID } });
|
|
117
|
+
return value === undefined ? undefined : JSON.parse(JSON.stringify(value));
|
|
118
|
+
};
|
|
119
|
+
internal.cdp = async (method, params) => {
|
|
120
|
+
strict_1.default.equal(method, "Input.insertText");
|
|
121
|
+
page.inserted++;
|
|
122
|
+
page.draft += params.text;
|
|
123
|
+
};
|
|
124
|
+
return { page, client, document, internal, errorNodes };
|
|
125
|
+
}
|
|
126
|
+
(0, node_test_1.default)("ZCode 快照按真实 v4 DOM 区分用户、助手与工具行", () => {
|
|
127
|
+
const { page, document } = fixture();
|
|
128
|
+
page.rows.push({ id: "2", role: "user", text: "请回复" }, { id: "3", role: "tool", text: "思考与工具输出" });
|
|
129
|
+
const state = (0, node_vm_1.runInNewContext)(cdp_client_1.zcodeSnapshotScript, { document });
|
|
130
|
+
strict_1.default.deepEqual(JSON.parse(JSON.stringify(state.rows)), [{ id: "1", role: "assistant", text: "旧回复", finished: true }, { id: "2", role: "user", text: "请回复" }]);
|
|
131
|
+
});
|
|
132
|
+
(0, node_test_1.default)("ZCode 本轮匹配使用 React 原始用户文本,不使用格式化后的渲染文字", async () => {
|
|
133
|
+
const { page, client, document } = fixture();
|
|
134
|
+
const task = "运行 $d 中的命令并回复本轮结果";
|
|
135
|
+
page.renderedUser = "运行 D 中的命令并回复本轮结果";
|
|
136
|
+
const result = await client.complete({ task, timeoutMs: 2000 });
|
|
137
|
+
strict_1.default.equal(result.text, "本轮回复");
|
|
138
|
+
strict_1.default.equal(page.sent, 1);
|
|
139
|
+
const state = (0, node_vm_1.runInNewContext)(cdp_client_1.zcodeSnapshotScript, { document });
|
|
140
|
+
strict_1.default.equal(state.rows.find((row) => row.role === "user")?.text, task);
|
|
141
|
+
});
|
|
142
|
+
(0, node_test_1.default)("ZCode 重载任务的输入框只读时等待可编辑再输入,持续只读时不发送", async () => {
|
|
143
|
+
for (const loads of [true, false]) {
|
|
144
|
+
const { page, client, internal, document } = fixture(loads ? 1000 : 20);
|
|
145
|
+
page.editable = false;
|
|
146
|
+
strict_1.default.equal((0, node_vm_1.runInNewContext)(cdp_client_1.zcodeSnapshotScript, { document }).editor, false);
|
|
147
|
+
const evaluate = internal.evaluate;
|
|
148
|
+
let polls = 0;
|
|
149
|
+
internal.evaluate = async (expression) => {
|
|
150
|
+
if (expression === cdp_client_1.zcodeSnapshotScript && ++polls === 3 && loads)
|
|
151
|
+
page.editable = true;
|
|
152
|
+
return evaluate(expression);
|
|
153
|
+
};
|
|
154
|
+
const cdp = internal.cdp;
|
|
155
|
+
internal.cdp = async (...args) => { strict_1.default.equal(page.editable, true); return cdp(...args); };
|
|
156
|
+
if (loads)
|
|
157
|
+
strict_1.default.equal((await client.complete({ task: "继续原任务" })).text, "本轮回复");
|
|
158
|
+
else
|
|
159
|
+
await strict_1.default.rejects(client.complete({ task: "继续原任务" }), /输入框未就绪/);
|
|
160
|
+
strict_1.default.equal(page.sent, loads ? 1 : 0);
|
|
161
|
+
strict_1.default.equal(page.inserted, loads ? 1 : 0);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
(0, node_test_1.default)("ZCode 冷启动后新建或恢复任务前等待编辑器解除只读", async () => {
|
|
165
|
+
for (const operation of ["new", "restore"]) {
|
|
166
|
+
const { page, client, internal } = fixture(1000);
|
|
167
|
+
page.editable = false;
|
|
168
|
+
page.taskPages.push(["session_original"]);
|
|
169
|
+
const evaluate = internal.evaluate;
|
|
170
|
+
let polls = 0;
|
|
171
|
+
internal.evaluate = async (expression) => {
|
|
172
|
+
if (++polls === 3)
|
|
173
|
+
page.editable = true;
|
|
174
|
+
return evaluate(expression);
|
|
175
|
+
};
|
|
176
|
+
if (operation === "new")
|
|
177
|
+
await client.createTask();
|
|
178
|
+
else
|
|
179
|
+
await client.activateConversation("session_original");
|
|
180
|
+
strict_1.default.equal(page.sessionId, operation === "new" ? "draft" : "session_original");
|
|
181
|
+
strict_1.default.equal(page.inserted, 0);
|
|
182
|
+
strict_1.default.equal(page.sent, 0);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
(0, node_test_1.default)("ZCode 恢复被显示更多折叠的原任务,不新建或发送消息", async () => {
|
|
186
|
+
const { page, client } = fixture(1000);
|
|
187
|
+
page.taskPages = [["session_test"], ["session_other"], ["session_original"]];
|
|
188
|
+
await client.activateConversation("session_original");
|
|
189
|
+
strict_1.default.equal(page.sessionId, "session_original");
|
|
190
|
+
strict_1.default.equal(page.expanded, 2);
|
|
191
|
+
strict_1.default.equal(page.sent, 0);
|
|
192
|
+
strict_1.default.equal(page.inserted, 0);
|
|
193
|
+
});
|
|
194
|
+
(0, node_test_1.default)("ZCode 旧会话错误横幅不阻止新建或恢复其他任务", async () => {
|
|
195
|
+
for (const operation of ["new", "restore"]) {
|
|
196
|
+
const { page, client } = fixture(1000);
|
|
197
|
+
page.chatError = "[1313][请求频率已受到限制。]";
|
|
198
|
+
page.taskPages.push(["session_original"]);
|
|
199
|
+
if (operation === "new")
|
|
200
|
+
await client.createTask();
|
|
201
|
+
else
|
|
202
|
+
await client.activateConversation("session_original");
|
|
203
|
+
strict_1.default.equal(page.sessionId, operation === "new" ? "draft" : "session_original");
|
|
204
|
+
strict_1.default.equal(page.chatError, "");
|
|
205
|
+
strict_1.default.equal(page.inserted, 0);
|
|
206
|
+
strict_1.default.equal(page.sent, 0);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
(0, node_test_1.default)("ZCode 同一会话有旧错误横幅时仍可提交新请求", async () => {
|
|
210
|
+
const { page, client } = fixture();
|
|
211
|
+
page.chatError = "[1313][上一轮请求频率已受到限制。]";
|
|
212
|
+
strict_1.default.equal((await client.complete({ task: "用户重新发起请求" }, undefined, false, "session_test")).text, "本轮回复");
|
|
213
|
+
strict_1.default.equal(page.sessionId, "session_test");
|
|
214
|
+
strict_1.default.equal(page.inserted, 1);
|
|
215
|
+
strict_1.default.equal(page.sent, 1);
|
|
216
|
+
});
|
|
217
|
+
(0, node_test_1.default)("ZCode 提交后旧错误横幅延迟消失时继续等本轮回复", async () => {
|
|
218
|
+
const { page, client, internal } = fixture();
|
|
219
|
+
const previousError = "[1313][上一轮错误]";
|
|
220
|
+
page.chatError = previousError;
|
|
221
|
+
const evaluate = internal.evaluate;
|
|
222
|
+
let polls = 0;
|
|
223
|
+
internal.evaluate = async (expression) => {
|
|
224
|
+
if (page.sent && expression === cdp_client_1.zcodeSnapshotScript)
|
|
225
|
+
page.chatError = ++polls < 4 ? previousError : "";
|
|
226
|
+
return evaluate(expression);
|
|
227
|
+
};
|
|
228
|
+
strict_1.default.equal((await client.complete({ task: "用户主动重试", timeoutMs: 3000 })).text, "本轮回复");
|
|
229
|
+
strict_1.default.ok(polls >= 4);
|
|
230
|
+
strict_1.default.equal(page.sent, 1);
|
|
231
|
+
});
|
|
232
|
+
(0, node_test_1.default)("ZCode 重试后文案相同的新错误和同一横幅的新文案都及时回传", async () => {
|
|
233
|
+
for (const remount of [true, false]) {
|
|
234
|
+
const { page, client, internal, errorNodes } = fixture();
|
|
235
|
+
page.chatError = "[1313][请求频率限制]";
|
|
236
|
+
const evaluate = internal.evaluate;
|
|
237
|
+
internal.evaluate = async (expression) => {
|
|
238
|
+
if (page.sent && expression === cdp_client_1.zcodeSnapshotScript) {
|
|
239
|
+
page.chatError = remount ? "[1313][请求频率限制]" : "[1313][本轮请求频率限制]";
|
|
240
|
+
if (remount)
|
|
241
|
+
errorNodes.chatError = { get innerText() { return page.chatError; } };
|
|
242
|
+
}
|
|
243
|
+
return evaluate(expression);
|
|
244
|
+
};
|
|
245
|
+
await strict_1.default.rejects(client.complete({ task: "用户主动重试", timeoutMs: 1000 }), /1313/);
|
|
246
|
+
strict_1.default.equal(page.sent, 1);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
(0, node_test_1.default)("ZCode 展开后仍无原任务时保留当前任务并报错", async () => {
|
|
250
|
+
const { page, client } = fixture(1000);
|
|
251
|
+
page.taskPages.push(["session_other"]);
|
|
252
|
+
await strict_1.default.rejects(client.activateConversation("session_missing"), /未找到原任务/);
|
|
253
|
+
strict_1.default.equal(page.expanded, 1);
|
|
254
|
+
strict_1.default.equal(page.sessionId, "session_test");
|
|
255
|
+
strict_1.default.equal(page.sent, 0);
|
|
256
|
+
});
|
|
257
|
+
(0, node_test_1.default)("ZCode 旧错误不影响草稿、生成和权限等待的任务切换保护", async () => {
|
|
258
|
+
for (const operation of ["new", "restore"])
|
|
259
|
+
for (const kind of ["draft", "running", "permission"]) {
|
|
260
|
+
const { page, client } = fixture();
|
|
261
|
+
page.chatError = "[1313][上一轮请求频率已受到限制。]";
|
|
262
|
+
page.taskPages.push(["session_original"]);
|
|
263
|
+
if (kind === "draft")
|
|
264
|
+
page.draft = "用户草稿";
|
|
265
|
+
if (kind === "running")
|
|
266
|
+
page.running = true;
|
|
267
|
+
if (kind === "permission")
|
|
268
|
+
page.permission = { prompt: "确认权限", labels: ["允许", "拒绝"] };
|
|
269
|
+
await strict_1.default.rejects(operation === "new" ? client.createTask() : client.activateConversation("session_original"), /草稿|生成|权限/);
|
|
270
|
+
strict_1.default.equal(page.expanded, 0);
|
|
271
|
+
strict_1.default.equal(page.sessionId, "session_test");
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
(0, node_test_1.default)("ZCode 填写 Lexical 编辑器并只返回本轮助手文本", async () => {
|
|
275
|
+
const { page, client } = fixture();
|
|
276
|
+
strict_1.default.equal(await client.direct("请回复本轮文本", 2000), "本轮回复");
|
|
277
|
+
strict_1.default.equal(page.inserted, 1);
|
|
278
|
+
strict_1.default.equal(page.sent, 1);
|
|
279
|
+
});
|
|
280
|
+
(0, node_test_1.default)("ZCode 最终助手行中的 data URI 图片作为附件返回", async () => {
|
|
281
|
+
const { page, client } = fixture();
|
|
282
|
+
const data = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 1, 2, 3]);
|
|
283
|
+
const image = { src: `data:image/png;base64,${data.toString("base64")}`, title: "C:/Users/M2LIH/.zcode/visualizations/sales-bar-chart.png" };
|
|
284
|
+
page.images = { "1": [image], "2": [image], "3": [image], "tool": [image] };
|
|
285
|
+
page.rows.push({ id: "tool", role: "tool", text: "工具预览" });
|
|
286
|
+
const result = await client.complete({ task: "生成销售图表", timeoutMs: 2000 });
|
|
287
|
+
strict_1.default.equal(result.attachments?.length, 1);
|
|
288
|
+
strict_1.default.equal(result.attachments?.[0].filename, "sales-bar-chart.png");
|
|
289
|
+
strict_1.default.equal(result.attachments?.[0].kind, "image");
|
|
290
|
+
strict_1.default.equal(result.attachments?.[0].contentType, "image/png");
|
|
291
|
+
strict_1.default.deepEqual(Buffer.from(result.attachments?.[0].data ?? []), data);
|
|
292
|
+
});
|
|
293
|
+
(0, node_test_1.default)("ZCode 支持仅含图片的回复、多张图片及无路径时的文件名", async () => {
|
|
294
|
+
const { page, client } = fixture();
|
|
295
|
+
page.reply = "";
|
|
296
|
+
page.images["3"] = [
|
|
297
|
+
{ src: "data:image/jpeg;base64,/9j/AQ==", title: "C:\\临时目录\\图表.jpg" },
|
|
298
|
+
{ src: `data:image/webp;base64,${Buffer.from("RIFF0000WEBP").toString("base64")}` },
|
|
299
|
+
];
|
|
300
|
+
const result = await client.complete({ task: "生成两张图", timeoutMs: 2000 });
|
|
301
|
+
strict_1.default.equal(result.attachments?.length, 2);
|
|
302
|
+
strict_1.default.equal(result.attachments?.[0].filename, "图表.jpg");
|
|
303
|
+
strict_1.default.equal(result.attachments?.[1].contentType, "image/webp");
|
|
304
|
+
strict_1.default.match(result.attachments?.[1].filename ?? "", /^zcode-.+\.webp$/);
|
|
305
|
+
strict_1.default.ok(result.text);
|
|
306
|
+
});
|
|
307
|
+
(0, node_test_1.default)("ZCode 拒绝非图片来源、损坏编码、错误 MIME 和无效文件名", async () => {
|
|
308
|
+
for (const image of [
|
|
309
|
+
{ src: "https://example.com/result.png" },
|
|
310
|
+
{ src: "data:text/plain;base64,SGVsbG8=" },
|
|
311
|
+
{ src: "data:image/png;base64,bm90LWltYWdl" },
|
|
312
|
+
{ src: "data:image/png;base64,/9j/AQ==" },
|
|
313
|
+
{ src: "data:image/jpeg;base64,/9j/AQ==!" },
|
|
314
|
+
{ src: "data:image/jpeg;base64,/9j/AQ" },
|
|
315
|
+
{ src: "data:image/jpeg;base64,/9j/AQ==", title: "坏\u0000图.jpg" },
|
|
316
|
+
]) {
|
|
317
|
+
const { page, client } = fixture();
|
|
318
|
+
page.images["3"] = [image];
|
|
319
|
+
await strict_1.default.rejects(client.complete({ task: "返回图片", timeoutMs: 2000 }), /图片|编码|文件名/, JSON.stringify(image));
|
|
320
|
+
strict_1.default.equal(page.sent, 1);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
(0, node_test_1.default)("ZCode 图片返回前限制附件数量、单项和总大小", async () => {
|
|
324
|
+
for (const images of [
|
|
325
|
+
Array(attachments_1.MAX_ATTACHMENTS + 1).fill({ src: "data:image/jpeg;base64,/9j/AQ==" }),
|
|
326
|
+
[{ src: "data:image/png;base64," + "A".repeat(Math.ceil((attachments_1.MAX_ATTACHMENT_BYTES + 1) / 3) * 4) }],
|
|
327
|
+
Array(3).fill({ src: "data:image/png;base64," + "A".repeat(Math.ceil(attachments_1.MAX_TOTAL_ATTACHMENT_BYTES / 3 / 3) * 4 + 4) }),
|
|
328
|
+
]) {
|
|
329
|
+
const { page, client } = fixture();
|
|
330
|
+
page.images["3"] = images;
|
|
331
|
+
await strict_1.default.rejects(client.complete({ task: "返回超限图片", timeoutMs: 2000 }), /32|64|8/);
|
|
332
|
+
strict_1.default.equal(page.sent, 1);
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
(0, node_test_1.default)("ZCode 读取最终图片时再次核对任务和轮次", async () => {
|
|
336
|
+
for (const change of ["session", "user"]) {
|
|
337
|
+
const { page, client, internal } = fixture();
|
|
338
|
+
page.images["3"] = [{ src: "data:image/jpeg;base64,/9j/AQ==" }];
|
|
339
|
+
const evaluate = internal.evaluate;
|
|
340
|
+
internal.evaluate = async (expression) => {
|
|
341
|
+
if (expression.includes("data-markdown-image")) {
|
|
342
|
+
if (change === "session")
|
|
343
|
+
page.sessionId = "其他任务";
|
|
344
|
+
else
|
|
345
|
+
page.rows.push({ id: "4", role: "user", text: "人工追加" });
|
|
346
|
+
}
|
|
347
|
+
return evaluate(expression);
|
|
348
|
+
};
|
|
349
|
+
await strict_1.default.rejects(client.complete({ task: "私有图片", timeoutMs: 2000 }), /变化|切换|轮次/);
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
(0, node_test_1.default)("ZCode 最终回复仍在更新时等待稳定附件而不误报换轮", async () => {
|
|
353
|
+
const { page, client, internal } = fixture();
|
|
354
|
+
const image = { src: "data:image/png;base64,iVBORw0KGgoBAgM=", title: "本轮图片.png" };
|
|
355
|
+
page.images["3"] = [image];
|
|
356
|
+
const evaluate = internal.evaluate;
|
|
357
|
+
let changed = false;
|
|
358
|
+
internal.evaluate = async (expression) => {
|
|
359
|
+
const value = await evaluate(expression);
|
|
360
|
+
if (!changed && expression.includes("data-markdown-image")) {
|
|
361
|
+
changed = true;
|
|
362
|
+
const reply = page.rows.at(-1);
|
|
363
|
+
const text = reply.text;
|
|
364
|
+
setTimeout(() => {
|
|
365
|
+
reply.text = "正文挂载中";
|
|
366
|
+
setTimeout(() => { reply.text = text; }, 500);
|
|
367
|
+
}, 150);
|
|
368
|
+
}
|
|
369
|
+
return value;
|
|
370
|
+
};
|
|
371
|
+
const result = await client.complete({ task: "生成图片", timeoutMs: 3000 });
|
|
372
|
+
strict_1.default.equal(result.text, "本轮回复");
|
|
373
|
+
strict_1.default.equal(result.attachments?.[0].filename, "本轮图片.png");
|
|
374
|
+
strict_1.default.equal(changed, true);
|
|
375
|
+
});
|
|
376
|
+
(0, node_test_1.default)("ZCode 完成回复没有正文和图片时不虚报附件已生成", async () => {
|
|
377
|
+
const { page, client } = fixture();
|
|
378
|
+
page.reply = "";
|
|
379
|
+
await strict_1.default.rejects(client.complete({ task: "返回图片", timeoutMs: 600 }), /超时/);
|
|
380
|
+
});
|
|
381
|
+
(0, node_test_1.default)("ZCode 图片无替代文字时以正式文件链接的名称回传并去重", async () => {
|
|
382
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-image-link-name-"));
|
|
383
|
+
try {
|
|
384
|
+
const path = (0, node_path_1.join)(dir, "正式图片.png");
|
|
385
|
+
const data = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 1, 2, 3]);
|
|
386
|
+
await (0, promises_1.writeFile)(path, data);
|
|
387
|
+
const { page, client } = fixture();
|
|
388
|
+
page.images["3"] = [{ src: `data:image/png;base64,${data.toString("base64")}` }];
|
|
389
|
+
page.files["3"] = [{ path }];
|
|
390
|
+
const result = await client.complete({ task: "生成图片", timeoutMs: 2000 });
|
|
391
|
+
strict_1.default.equal(result.attachments?.length, 1);
|
|
392
|
+
strict_1.default.equal(result.attachments?.[0].filename, "正式图片.png");
|
|
393
|
+
strict_1.default.deepEqual(Buffer.from(result.attachments[0].data), data);
|
|
394
|
+
}
|
|
395
|
+
finally {
|
|
396
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
(0, node_test_1.default)("ZCode 等待正文完成后异步挂载的图片", async () => {
|
|
400
|
+
const { page, client, internal } = fixture();
|
|
401
|
+
const image = { src: "data:image/png;base64,iVBORw0KGgoBAgM=", title: "延迟图.png" };
|
|
402
|
+
let reads = 0;
|
|
403
|
+
const evaluate = internal.evaluate;
|
|
404
|
+
internal.evaluate = async (expression) => {
|
|
405
|
+
if (expression.includes("data-markdown-image") && ++reads === 1)
|
|
406
|
+
setTimeout(() => { page.images["3"] = [image]; }, 500);
|
|
407
|
+
return evaluate(expression);
|
|
408
|
+
};
|
|
409
|
+
const result = await client.complete({ task: "异步生成图片", timeoutMs: 3000 });
|
|
410
|
+
strict_1.default.equal(result.attachments?.[0].filename, "延迟图.png");
|
|
411
|
+
});
|
|
412
|
+
(0, node_test_1.default)("ZCode 最终助手行的正式文件链接回传普通文件、视频,并与预览图片去重", async () => {
|
|
413
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-files-"));
|
|
414
|
+
try {
|
|
415
|
+
const text = Buffer.from("本轮文件\n");
|
|
416
|
+
const png = Buffer.from([137, 80, 78, 71, 13, 10, 26, 10, 1, 2, 3]);
|
|
417
|
+
const mp4 = Buffer.from("0000ftypisom0000");
|
|
418
|
+
for (const [name, data] of [["结果.txt", text], ["图片.png", png], ["视频.mp4", mp4]])
|
|
419
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, name), data);
|
|
420
|
+
const { page, client } = fixture();
|
|
421
|
+
const current = ["结果.txt", "图片.png", "视频.mp4"].map(name => ({ path: (0, node_path_1.join)(dir, name) }));
|
|
422
|
+
page.files = { "1": [{ path: (0, node_path_1.join)(dir, "旧文件.txt") }], "2": current, "3": [...current, current[0], { path: (0, node_path_1.join)(dir, "任意提示.txt"), plain: true }], tool: current };
|
|
423
|
+
page.images["3"] = [{ src: `data:image/png;base64,${png.toString("base64")}`, title: (0, node_path_1.join)(dir, "图片.png") }];
|
|
424
|
+
const result = await client.complete({ task: "生成并交付三个文件", timeoutMs: 3000 });
|
|
425
|
+
strict_1.default.equal(result.attachments?.length, 3);
|
|
426
|
+
for (const [name, data, type, kind] of [["结果.txt", text, "text/plain", "file"], ["图片.png", png, "image/png", "image"], ["视频.mp4", mp4, "video/mp4", "file"]]) {
|
|
427
|
+
const file = result.attachments.find(item => item.filename === name);
|
|
428
|
+
strict_1.default.ok(file, name);
|
|
429
|
+
strict_1.default.equal(file.contentType, type);
|
|
430
|
+
strict_1.default.equal(file.kind, kind);
|
|
431
|
+
strict_1.default.deepEqual(Buffer.from(file.data), data);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
finally {
|
|
435
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
(0, node_test_1.default)("ZCode 仅文件回复也回传,并保留未知格式的文件字节", async () => {
|
|
439
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-file-only-"));
|
|
440
|
+
try {
|
|
441
|
+
const path = (0, node_path_1.join)(dir, "结果.bin");
|
|
442
|
+
const bytes = Buffer.from([0, 1, 2, 255]);
|
|
443
|
+
await (0, promises_1.writeFile)(path, bytes);
|
|
444
|
+
const { page, client } = fixture();
|
|
445
|
+
page.reply = "";
|
|
446
|
+
page.files["3"] = [{ path }];
|
|
447
|
+
const result = await client.complete({ task: "交付文件", timeoutMs: 2000 });
|
|
448
|
+
strict_1.default.deepEqual(Buffer.from(result.attachments[0].data), bytes);
|
|
449
|
+
strict_1.default.equal(result.attachments[0].contentType, "application/octet-stream");
|
|
450
|
+
strict_1.default.ok(result.text);
|
|
451
|
+
}
|
|
452
|
+
finally {
|
|
453
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
(0, node_test_1.default)("ZCode 文件链接拒绝相对路径、网络路径和远程工作区", async () => {
|
|
457
|
+
for (const file of [{ path: "relative.txt" }, { path: "https://example.com/a.txt" }, { path: "\\\\server\\share\\a.txt" }, { path: "\\root-relative.txt" }, { path: "//server/share/a.txt" }, { path: (0, node_path_1.join)((0, node_os_1.tmpdir)(), "a.txt"), remote: "ssh-private" }]) {
|
|
458
|
+
const { page, client } = fixture();
|
|
459
|
+
page.files["3"] = [file];
|
|
460
|
+
await strict_1.default.rejects(client.complete({ task: "交付文件", timeoutMs: 2500 }), /文件.*路径|远程工作区/);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
(0, node_test_1.default)("ZCode 文件读取明确报告缺失、目录、超限和伪装视频", async () => {
|
|
464
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-file-errors-"));
|
|
465
|
+
try {
|
|
466
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(dir, "目录"));
|
|
467
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "超限.bin"), "a");
|
|
468
|
+
await (0, promises_1.truncate)((0, node_path_1.join)(dir, "超限.bin"), attachments_1.MAX_ATTACHMENT_BYTES + 1);
|
|
469
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "伪装.mp4"), "not a video");
|
|
470
|
+
for (const [name, expected] of [["缺失.txt", /读取失败/], ["目录", /普通文件/], ["超限.bin", /32 MiB/], ["伪装.mp4", /视频.*类型/]]) {
|
|
471
|
+
const { page, client } = fixture();
|
|
472
|
+
page.files["3"] = [{ path: (0, node_path_1.join)(dir, name) }];
|
|
473
|
+
await strict_1.default.rejects(client.complete({ task: "交付文件", timeoutMs: 2500 }), expected);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
(0, node_test_1.default)("ZCode 在文件路径被替换的读取窗口拒绝新文件", async (t) => {
|
|
481
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-file-replaced-"));
|
|
482
|
+
const fsp = require("node:fs/promises");
|
|
483
|
+
try {
|
|
484
|
+
const path = (0, node_path_1.join)(dir, "原文件.txt");
|
|
485
|
+
await (0, promises_1.writeFile)(path, "原始交付内容");
|
|
486
|
+
const originalOpen = fsp.open;
|
|
487
|
+
t.mock.method(fsp, "open", async (...args) => {
|
|
488
|
+
await fsp.rename(path, (0, node_path_1.join)(dir, "原文件备份.txt"));
|
|
489
|
+
await (0, promises_1.writeFile)(path, "替换后的内容");
|
|
490
|
+
return originalOpen(...args);
|
|
491
|
+
});
|
|
492
|
+
await strict_1.default.rejects((fixture().internal).replyFileAttachments([path], []), /读取前已变化/);
|
|
493
|
+
}
|
|
494
|
+
finally {
|
|
495
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
(0, node_test_1.default)("ZCode 文件附件超出数量时明确失败", async () => {
|
|
499
|
+
const { page, client } = fixture();
|
|
500
|
+
page.files["3"] = Array.from({ length: attachments_1.MAX_ATTACHMENTS + 1 }, (_, i) => ({ path: (0, node_path_1.join)((0, node_os_1.tmpdir)(), `${i}.txt`) }));
|
|
501
|
+
await strict_1.default.rejects(client.complete({ task: "交付文件", timeoutMs: 2500 }), /8 个/);
|
|
502
|
+
});
|
|
503
|
+
(0, node_test_1.default)("ZCode 附件回传前复核会话、链接与取消状态", async () => {
|
|
504
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-file-state-"));
|
|
505
|
+
try {
|
|
506
|
+
const path = (0, node_path_1.join)(dir, "结果.txt");
|
|
507
|
+
await (0, promises_1.writeFile)(path, "测试");
|
|
508
|
+
for (const change of ["session", "link", "cancel"]) {
|
|
509
|
+
const { page, client, internal } = fixture();
|
|
510
|
+
page.files["3"] = [{ path }];
|
|
511
|
+
const controller = new AbortController();
|
|
512
|
+
const read = internal.replyFileAttachments.bind(client);
|
|
513
|
+
internal.replyFileAttachments = async (...args) => { const result = await read(...args); if (change === "session")
|
|
514
|
+
page.sessionId = "另一个任务";
|
|
515
|
+
else if (change === "link")
|
|
516
|
+
page.files["3"] = [];
|
|
517
|
+
else
|
|
518
|
+
controller.abort(new Error("已取消")); return result; };
|
|
519
|
+
await strict_1.default.rejects(client.complete({ task: "交付文件", timeoutMs: 2000, signal: controller.signal }), /变化|取消/);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
finally {
|
|
523
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
(0, node_test_1.default)("ZCode 文件读取遵循普通文件检查及合并附件总量限制", async (t) => {
|
|
527
|
+
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "zcode-file-limits-"));
|
|
528
|
+
try {
|
|
529
|
+
const target = (0, node_path_1.join)(dir, "源文件.txt");
|
|
530
|
+
await (0, promises_1.writeFile)(target, "abc");
|
|
531
|
+
if (process.platform !== "win32") {
|
|
532
|
+
const link = (0, node_path_1.join)(dir, "链接.txt");
|
|
533
|
+
await (0, promises_1.symlink)(target, link);
|
|
534
|
+
await strict_1.default.rejects((fixture().internal).replyFileAttachments([link], []), /普通文件/);
|
|
535
|
+
}
|
|
536
|
+
else
|
|
537
|
+
t.diagnostic("Windows 符号链接创建需要额外系统权限;普通文件和大小限制仍执行");
|
|
538
|
+
const paths = [];
|
|
539
|
+
for (let i = 0; i < 3; i++) {
|
|
540
|
+
const path = (0, node_path_1.join)(dir, `${i}.bin`);
|
|
541
|
+
await (0, promises_1.writeFile)(path, "a");
|
|
542
|
+
await (0, promises_1.truncate)(path, 22 * 1024 * 1024);
|
|
543
|
+
paths.push(path);
|
|
544
|
+
}
|
|
545
|
+
await strict_1.default.rejects((fixture().internal).replyFileAttachments(paths, []), /64 MiB/);
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
(0, node_test_1.default)("ZCode 有用户草稿或正在生成时不输入、不发送", async () => {
|
|
552
|
+
for (const kind of ["draft", "running"]) {
|
|
553
|
+
const { page, client } = fixture();
|
|
554
|
+
if (kind === "draft")
|
|
555
|
+
page.draft = "用户未发送的内容";
|
|
556
|
+
else
|
|
557
|
+
page.running = true;
|
|
558
|
+
await strict_1.default.rejects(() => client.direct("新请求", 100), /草稿|生成/);
|
|
559
|
+
strict_1.default.equal(page.inserted, 0);
|
|
560
|
+
strict_1.default.equal(page.sent, 0);
|
|
561
|
+
if (kind === "draft")
|
|
562
|
+
strict_1.default.equal(page.draft, "用户未发送的内容");
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
(0, node_test_1.default)("ZCode 发送端必须核对逻辑会话预期的原生任务", async () => {
|
|
566
|
+
for (const expectedConversationId of ["session_original", null]) {
|
|
567
|
+
const { page, client } = fixture();
|
|
568
|
+
await strict_1.default.rejects(() => client.complete({ task: "peer 私有续聊" }, undefined, false, expectedConversationId), /任务.*切换|任务.*不符/);
|
|
569
|
+
strict_1.default.equal(page.inserted, 0);
|
|
570
|
+
strict_1.default.equal(page.sent, 0);
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
(0, node_test_1.default)("ZCode 点击发送时复核所属任务未开始运行", async () => {
|
|
574
|
+
const { page, client, internal } = fixture();
|
|
575
|
+
const original = internal.evaluate;
|
|
576
|
+
internal.evaluate = async (expression) => {
|
|
577
|
+
if (expression.includes('button.click(); return true;'))
|
|
578
|
+
page.hostState = "true";
|
|
579
|
+
return original(expression);
|
|
580
|
+
};
|
|
581
|
+
await strict_1.default.rejects(() => client.direct("禁止并行发送", 50), /未发送消息/);
|
|
582
|
+
strict_1.default.equal(page.sent, 0);
|
|
583
|
+
});
|
|
584
|
+
(0, node_test_1.default)("ZCode 本轮提交后的页面错误不能当成成功回复", async () => {
|
|
585
|
+
const { page, client, internal } = fixture();
|
|
586
|
+
const evaluate = internal.evaluate;
|
|
587
|
+
internal.evaluate = async (expression) => {
|
|
588
|
+
if (page.sent)
|
|
589
|
+
page.errors = "服务暂不可用";
|
|
590
|
+
return evaluate(expression);
|
|
591
|
+
};
|
|
592
|
+
await strict_1.default.rejects(() => client.direct("测试", 100), /服务暂不可用/);
|
|
593
|
+
strict_1.default.equal(page.sent, 1);
|
|
594
|
+
});
|
|
595
|
+
(0, node_test_1.default)("ZCode 本轮真实配额错误横幅及时回传且不重复发送", async () => {
|
|
596
|
+
const { page, client, internal } = fixture();
|
|
597
|
+
const evaluate = internal.evaluate;
|
|
598
|
+
internal.evaluate = async (expression) => {
|
|
599
|
+
if (page.sent)
|
|
600
|
+
page.quotaError = "当前系统繁忙,请切换模型、升级账户,或稍后再试。";
|
|
601
|
+
return evaluate(expression);
|
|
602
|
+
};
|
|
603
|
+
await strict_1.default.rejects(() => client.direct("验收", 1000), /系统繁忙/);
|
|
604
|
+
strict_1.default.equal(page.sent, 1);
|
|
605
|
+
});
|
|
606
|
+
(0, node_test_1.default)("ZCode v4 chat-error-banner 本轮配额错误及时回传且不等待超时", async () => {
|
|
607
|
+
const { page, client, internal } = fixture();
|
|
608
|
+
const evaluate = internal.evaluate;
|
|
609
|
+
internal.evaluate = async (expression) => {
|
|
610
|
+
if (page.sent)
|
|
611
|
+
page.chatError = "[1313][您的账户当前使用模式不符合公平使用策略,请求频率已受到限制。]";
|
|
612
|
+
return evaluate(expression);
|
|
613
|
+
};
|
|
614
|
+
await strict_1.default.rejects(() => client.direct("验收", 1000), /1313|公平使用/);
|
|
615
|
+
strict_1.default.equal(page.sent, 1);
|
|
616
|
+
});
|
|
617
|
+
(0, node_test_1.default)("ZCode 输入校验失败时不点击发送", async () => {
|
|
618
|
+
const { page, client, internal } = fixture();
|
|
619
|
+
internal.cdp = async () => { page.draft = "错误的输入"; };
|
|
620
|
+
await strict_1.default.rejects(() => client.direct("正确的输入", 100), /校验/);
|
|
621
|
+
strict_1.default.equal(page.sent, 0);
|
|
622
|
+
});
|
|
623
|
+
(0, node_test_1.default)("ZCode 等待新回复超时后不返回旧助手消息,也不重复提交", async () => {
|
|
624
|
+
const { page, client, internal } = fixture();
|
|
625
|
+
const original = internal.evaluate;
|
|
626
|
+
internal.evaluate = async (expression) => {
|
|
627
|
+
const value = await original(expression);
|
|
628
|
+
if (page.sent)
|
|
629
|
+
page.rows = page.rows.filter(row => row.id !== "3");
|
|
630
|
+
return value;
|
|
631
|
+
};
|
|
632
|
+
await strict_1.default.rejects(() => client.direct("测试超时", 30), /未确认.*完成|超时/);
|
|
633
|
+
strict_1.default.equal(page.sent, 1);
|
|
634
|
+
});
|
|
635
|
+
(0, node_test_1.default)("ZCode 不在生成中的短暂停顿提前返回", async () => {
|
|
636
|
+
const { page, client, internal } = fixture();
|
|
637
|
+
const original = internal.evaluate;
|
|
638
|
+
let polls = 0;
|
|
639
|
+
internal.evaluate = async (expression) => {
|
|
640
|
+
if (page.sent && expression === cdp_client_1.zcodeSnapshotScript) {
|
|
641
|
+
polls++;
|
|
642
|
+
page.running = polls < 4;
|
|
643
|
+
if (polls === 4)
|
|
644
|
+
page.rows.find(row => row.id === "3").text = "最终回复";
|
|
645
|
+
}
|
|
646
|
+
return original(expression);
|
|
647
|
+
};
|
|
648
|
+
strict_1.default.equal(await client.direct("测试长任务", 2500), "最终回复");
|
|
649
|
+
strict_1.default.ok(polls >= 5);
|
|
650
|
+
});
|
|
651
|
+
(0, node_test_1.default)("ZCode 查询行完成后被移除,按所属任务状态确认结束", async () => {
|
|
652
|
+
const { page, client, internal } = fixture();
|
|
653
|
+
const original = internal.evaluate;
|
|
654
|
+
let polls = 0;
|
|
655
|
+
internal.evaluate = async (expression) => {
|
|
656
|
+
if (page.sent && expression === cdp_client_1.zcodeSnapshotScript) {
|
|
657
|
+
page.hostState = ++polls < 4 ? "true" : "false";
|
|
658
|
+
if (polls === 4)
|
|
659
|
+
page.rows.find(row => row.id === "3").text = "任务最终回复";
|
|
660
|
+
}
|
|
661
|
+
return original(expression);
|
|
662
|
+
};
|
|
663
|
+
strict_1.default.equal(await client.direct("等待所属任务完成", 2500), "任务最终回复");
|
|
664
|
+
strict_1.default.ok(polls >= 5);
|
|
665
|
+
});
|
|
666
|
+
(0, node_test_1.default)("ZCode 等待期间桌面被切换时拒绝返回其他任务消息", async () => {
|
|
667
|
+
const { page, client, internal } = fixture();
|
|
668
|
+
const original = internal.evaluate;
|
|
669
|
+
internal.evaluate = async (expression) => {
|
|
670
|
+
if (page.sent)
|
|
671
|
+
page.sessionId = "session_other";
|
|
672
|
+
return original(expression);
|
|
673
|
+
};
|
|
674
|
+
await strict_1.default.rejects(() => client.direct("测试", 2000), /切换/);
|
|
675
|
+
});
|
|
676
|
+
(0, node_test_1.default)("ZCode 不混入桌面人工追加的下一轮回复", async () => {
|
|
677
|
+
const { page, client, internal } = fixture();
|
|
678
|
+
const original = internal.evaluate;
|
|
679
|
+
internal.evaluate = async (expression) => {
|
|
680
|
+
if (page.sent && page.rows.length === 3)
|
|
681
|
+
page.rows.push({ id: "4", role: "user", text: "人工新问题" }, { id: "5", role: "assistant", text: "其他轮的回复" });
|
|
682
|
+
return original(expression);
|
|
683
|
+
};
|
|
684
|
+
await strict_1.default.rejects(() => client.direct("插件的问题", 2000), /新增.*用户消息|追加/);
|
|
685
|
+
});
|
|
686
|
+
(0, node_test_1.default)("ZCode 在停止按钮短暂消失但没有完成标记时继续等待", async () => {
|
|
687
|
+
const { page, client, internal } = fixture();
|
|
688
|
+
const original = internal.evaluate;
|
|
689
|
+
let polls = 0;
|
|
690
|
+
page.completed = false;
|
|
691
|
+
internal.evaluate = async (expression) => {
|
|
692
|
+
if (page.sent && expression === cdp_client_1.zcodeSnapshotScript && ++polls === 4) {
|
|
693
|
+
page.completed = true;
|
|
694
|
+
page.rows.find(row => row.id === "3").text = "真正最终回复";
|
|
695
|
+
}
|
|
696
|
+
return original(expression);
|
|
697
|
+
};
|
|
698
|
+
strict_1.default.equal(await client.direct("等待最终结果", 2500), "真正最终回复");
|
|
699
|
+
});
|
|
700
|
+
(0, node_test_1.default)("ZCode 等待发送按钮时取消不能继续点击发送", async () => {
|
|
701
|
+
const { page, client, internal } = fixture();
|
|
702
|
+
const controller = new AbortController();
|
|
703
|
+
const original = internal.evaluate;
|
|
704
|
+
let checks = 0;
|
|
705
|
+
internal.evaluate = async (expression) => {
|
|
706
|
+
if (expression.includes("return button && !button.disabled;") && ++checks === 1) {
|
|
707
|
+
controller.abort(new Error("请求已取消"));
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
return original(expression);
|
|
711
|
+
};
|
|
712
|
+
await strict_1.default.rejects(() => client.complete({ task: "测试", timeoutMs: 1000, signal: controller.signal }), /取消/);
|
|
713
|
+
strict_1.default.equal(page.sent, 0);
|
|
714
|
+
});
|
|
715
|
+
(0, node_test_1.default)("ZCode 提交后取消及时释放等待且不重复发送", async () => {
|
|
716
|
+
const { page, client, internal } = fixture();
|
|
717
|
+
const controller = new AbortController();
|
|
718
|
+
const original = internal.evaluate;
|
|
719
|
+
internal.evaluate = async (expression) => {
|
|
720
|
+
if (page.sent) {
|
|
721
|
+
controller.abort(new Error("请求已取消"));
|
|
722
|
+
page.running = true;
|
|
723
|
+
}
|
|
724
|
+
return original(expression);
|
|
725
|
+
};
|
|
726
|
+
const started = Date.now();
|
|
727
|
+
await strict_1.default.rejects(() => client.complete({ task: "测试", timeoutMs: 1500, signal: controller.signal }), /取消/);
|
|
728
|
+
strict_1.default.ok(Date.now() - started < 1000);
|
|
729
|
+
strict_1.default.equal(page.sent, 1);
|
|
730
|
+
});
|
|
731
|
+
(0, node_test_1.default)("ZCode 新建任务点击前再次检查新出现的草稿", async () => {
|
|
732
|
+
const { page, client, internal } = fixture();
|
|
733
|
+
const original = internal.evaluate;
|
|
734
|
+
internal.evaluate = async (expression) => {
|
|
735
|
+
if (expression.includes("task-new-button"))
|
|
736
|
+
page.draft = "用户刚输入的草稿";
|
|
737
|
+
return original(expression);
|
|
738
|
+
};
|
|
739
|
+
await strict_1.default.rejects(() => client.createTask(), /草稿/);
|
|
740
|
+
strict_1.default.equal(page.draft, "用户刚输入的草稿");
|
|
741
|
+
});
|
|
742
|
+
function permissionFixture() {
|
|
743
|
+
const value = fixture();
|
|
744
|
+
const original = value.internal.evaluate;
|
|
745
|
+
let shown = false;
|
|
746
|
+
value.internal.evaluate = async (expression) => {
|
|
747
|
+
if (value.page.sent && !shown) {
|
|
748
|
+
shown = true;
|
|
749
|
+
value.page.running = true;
|
|
750
|
+
value.page.permission = { prompt: "需要权限\n运行命令:python 绘制图表.py", labels: ["允许", "始终允许此命令", "拒绝"] };
|
|
751
|
+
}
|
|
752
|
+
return original(expression);
|
|
753
|
+
};
|
|
754
|
+
return value;
|
|
755
|
+
}
|
|
756
|
+
(0, node_test_1.default)("ZCode 权限说明与编号选项先返回,不等待最终正文或自动批准", async () => {
|
|
757
|
+
const { page, client } = permissionFixture();
|
|
758
|
+
const result = await client.complete({ task: "生成测试图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
759
|
+
strict_1.default.match(result.text, /运行命令:python 绘制图表.py/);
|
|
760
|
+
strict_1.default.match(result.text, /1\. 允许\n2\. 始终允许此命令\n3\. 拒绝/);
|
|
761
|
+
strict_1.default.equal(client.pendingInteractionSessionId?.(), "peer-A");
|
|
762
|
+
strict_1.default.deepEqual(page.clicked, []);
|
|
763
|
+
});
|
|
764
|
+
(0, node_test_1.default)("ZCode peer 的编号或选项文字选择继续原轮次,不作为新聊天发送", async () => {
|
|
765
|
+
for (const task of ["3", "拒绝"]) {
|
|
766
|
+
const { page, client } = permissionFixture();
|
|
767
|
+
await client.complete({ task: "生成测试图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
768
|
+
strict_1.default.equal((await client.complete({ task, sessionId: "peer-A", timeoutMs: 1500 }, undefined, false, "session_test")).text, "本轮回复");
|
|
769
|
+
strict_1.default.deepEqual(page.clicked, ["拒绝"]);
|
|
770
|
+
strict_1.default.equal(page.sent, 1);
|
|
771
|
+
strict_1.default.equal(page.inserted, 1);
|
|
772
|
+
strict_1.default.equal(client.pendingInteractionSessionId?.(), undefined);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
(0, node_test_1.default)("ZCode 权限确认后将原轮生成图片交给插件返回", async () => {
|
|
776
|
+
const { page, client } = permissionFixture();
|
|
777
|
+
client.connected = () => true;
|
|
778
|
+
const plugin = new zcode_1.ZcodePlugin({ client });
|
|
779
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
780
|
+
try {
|
|
781
|
+
strict_1.default.match((await plugin.handle({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 })).text, /权限/);
|
|
782
|
+
page.images["3"] = [{ src: "data:image/jpeg;base64,/9j/AQ==", title: "result.jpg" }];
|
|
783
|
+
const result = await plugin.handle({ task: "允许", sessionId: "peer-A", timeoutMs: 2000 });
|
|
784
|
+
strict_1.default.equal(result.attachments?.length, 1);
|
|
785
|
+
strict_1.default.equal(result.attachments?.[0].filename, "result.jpg");
|
|
786
|
+
strict_1.default.equal(page.sent, 1);
|
|
787
|
+
}
|
|
788
|
+
finally {
|
|
789
|
+
await plugin.deactivate("stop");
|
|
790
|
+
}
|
|
791
|
+
});
|
|
792
|
+
(0, node_test_1.default)("ZCode 无效选择再次返回选项,并保持等待", async () => {
|
|
793
|
+
const { page, client } = permissionFixture();
|
|
794
|
+
const first = await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
795
|
+
strict_1.default.deepEqual(await client.complete({ task: "随便", sessionId: "peer-A" }), first);
|
|
796
|
+
strict_1.default.deepEqual(page.clicked, []);
|
|
797
|
+
strict_1.default.equal(page.sent, 1);
|
|
798
|
+
});
|
|
799
|
+
(0, node_test_1.default)("ZCode 其他 peer 或话题不得确认当前权限", async () => {
|
|
800
|
+
const { page, client } = permissionFixture();
|
|
801
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
802
|
+
for (const sessionId of ["peer-B", undefined])
|
|
803
|
+
await strict_1.default.rejects(() => client.complete({ task: "1", sessionId }), /另一个会话/);
|
|
804
|
+
strict_1.default.deepEqual(page.clicked, []);
|
|
805
|
+
strict_1.default.equal(page.sent, 1);
|
|
806
|
+
});
|
|
807
|
+
(0, node_test_1.default)("ZCode 选择前面板被同文案的新权限替换也拒绝确认", async () => {
|
|
808
|
+
const { page, client } = permissionFixture();
|
|
809
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
810
|
+
page.permission = { ...page.permission, labels: [...page.permission.labels] };
|
|
811
|
+
await strict_1.default.rejects(() => client.complete({ task: "1", sessionId: "peer-A" }), /权限.*变化|权限.*不符/);
|
|
812
|
+
strict_1.default.deepEqual(page.clicked, []);
|
|
813
|
+
});
|
|
814
|
+
(0, node_test_1.default)("ZCode 点击权限时再次核对任务、面板与新增人工消息", async () => {
|
|
815
|
+
for (const change of ["session", "permission", "user", "draft", "disabled"]) {
|
|
816
|
+
const { page, client, internal } = permissionFixture();
|
|
817
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
818
|
+
const original = internal.evaluate;
|
|
819
|
+
internal.evaluate = async (expression) => {
|
|
820
|
+
if (expression.includes("option.click()")) {
|
|
821
|
+
if (change === "session")
|
|
822
|
+
page.sessionId = "another-task";
|
|
823
|
+
if (change === "permission")
|
|
824
|
+
page.permission = { prompt: "另一条命令", labels: ["允许", "拒绝"] };
|
|
825
|
+
if (change === "user")
|
|
826
|
+
page.rows.push({ id: "4", role: "user", text: "桌面人工追加" });
|
|
827
|
+
if (change === "draft")
|
|
828
|
+
page.draft = "人工草稿";
|
|
829
|
+
if (change === "disabled")
|
|
830
|
+
page.permission.disabled = true;
|
|
831
|
+
}
|
|
832
|
+
return original(expression);
|
|
833
|
+
};
|
|
834
|
+
await strict_1.default.rejects(() => client.complete({ task: "1", sessionId: "peer-A" }), /变化|不可用|草稿|人工|切换/);
|
|
835
|
+
strict_1.default.deepEqual(page.clicked, [], change);
|
|
836
|
+
}
|
|
837
|
+
});
|
|
838
|
+
(0, node_test_1.default)("ZCode 权限等待时不切换任务,同一任务无需点击恢复", async () => {
|
|
839
|
+
const { page, client } = permissionFixture();
|
|
840
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
841
|
+
await client.activateConversation("session_test");
|
|
842
|
+
await strict_1.default.rejects(() => client.createTask(), /权限|确认|生成/);
|
|
843
|
+
await strict_1.default.rejects(() => client.activateConversation("other-task"), /权限|确认|生成/);
|
|
844
|
+
strict_1.default.equal(page.sessionId, "session_test");
|
|
845
|
+
});
|
|
846
|
+
(0, node_test_1.default)("ZCode 取消权限选择不能点击,之后仍可拒绝原权限", async () => {
|
|
847
|
+
const { page, client, internal } = permissionFixture();
|
|
848
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
849
|
+
const controller = new AbortController();
|
|
850
|
+
const original = internal.evaluate;
|
|
851
|
+
internal.evaluate = async (expression) => {
|
|
852
|
+
const result = await original(expression);
|
|
853
|
+
if (expression.includes("option.focus()"))
|
|
854
|
+
controller.abort(new Error("选择已取消"));
|
|
855
|
+
return result;
|
|
856
|
+
};
|
|
857
|
+
await strict_1.default.rejects(() => client.complete({ task: "1", sessionId: "peer-A", signal: controller.signal }), /取消/);
|
|
858
|
+
strict_1.default.deepEqual(page.clicked, []);
|
|
859
|
+
internal.evaluate = original;
|
|
860
|
+
strict_1.default.equal((await client.complete({ task: "3", sessionId: "peer-A", timeoutMs: 1500 })).text, "本轮回复");
|
|
861
|
+
});
|
|
862
|
+
(0, node_test_1.default)("ZCode 连续权限分别回传,确认后等待最终回复", async () => {
|
|
863
|
+
const { page, client } = permissionFixture();
|
|
864
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
865
|
+
page.nextPermission = { prompt: "需要权限\n写入测试图片", labels: ["允许", "拒绝"] };
|
|
866
|
+
strict_1.default.match((await client.complete({ task: "3", sessionId: "peer-A", timeoutMs: 1000 })).text, /写入测试图片/);
|
|
867
|
+
strict_1.default.equal((await client.complete({ task: "拒绝", sessionId: "peer-A", timeoutMs: 1500 })).text, "本轮回复");
|
|
868
|
+
strict_1.default.deepEqual(page.clicked, ["拒绝", "拒绝"]);
|
|
869
|
+
strict_1.default.equal(page.sent, 1);
|
|
870
|
+
});
|
|
871
|
+
(0, node_test_1.default)("ZCode 权限被桌面人工处理后,peer 可读取原轮最终回复", async () => {
|
|
872
|
+
const { page, client } = permissionFixture();
|
|
873
|
+
await client.complete({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 });
|
|
874
|
+
page.permission = undefined;
|
|
875
|
+
page.running = false;
|
|
876
|
+
strict_1.default.equal((await client.complete({ task: "继续", sessionId: "peer-A", timeoutMs: 1500 })).text, "本轮回复");
|
|
877
|
+
strict_1.default.equal(page.sent, 1);
|
|
878
|
+
});
|
|
879
|
+
(0, node_test_1.default)("ZCode 插件拒绝其他 peer 后,原 peer 仍能选择且保持原任务", async () => {
|
|
880
|
+
const { page, client } = permissionFixture();
|
|
881
|
+
client.connected = () => true;
|
|
882
|
+
const plugin = new zcode_1.ZcodePlugin({ client });
|
|
883
|
+
await plugin.activate({ config: {}, logger: { info() { }, warn() { }, error() { } } });
|
|
884
|
+
try {
|
|
885
|
+
strict_1.default.match((await plugin.handle({ task: "生成图表", sessionId: "peer-A", timeoutMs: 1000 })).text, /需要权限/);
|
|
886
|
+
await strict_1.default.rejects(() => plugin.handle({ task: "1", sessionId: "peer-B" }), /另一个会话/);
|
|
887
|
+
strict_1.default.equal((await plugin.handle({ task: "3", sessionId: "peer-A", timeoutMs: 1500 })).text, "本轮回复");
|
|
888
|
+
strict_1.default.deepEqual(page.clicked, ["拒绝"]);
|
|
889
|
+
strict_1.default.equal(page.sent, 1);
|
|
890
|
+
}
|
|
891
|
+
finally {
|
|
892
|
+
await plugin.deactivate("stop");
|
|
893
|
+
}
|
|
894
|
+
});
|