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
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
11
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
12
|
+
const agent_1 = require("../src-ts/agent");
|
|
13
|
+
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
14
|
+
const version_manager_1 = require("../src-ts/version-manager");
|
|
15
|
+
const TWO_HOURS = 2 * 60 * 60 * 1000;
|
|
16
|
+
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
17
|
+
async function until(check) {
|
|
18
|
+
for (let i = 0; i < 2_000; i++) {
|
|
19
|
+
if (check())
|
|
20
|
+
return;
|
|
21
|
+
await flush();
|
|
22
|
+
}
|
|
23
|
+
strict_1.default.ok(check(), "等待预期状态超时");
|
|
24
|
+
}
|
|
25
|
+
function deferred() {
|
|
26
|
+
let resolve;
|
|
27
|
+
const promise = new Promise(done => { resolve = done; });
|
|
28
|
+
return { promise, resolve };
|
|
29
|
+
}
|
|
30
|
+
function cancelled(task, pattern = /PLUGIN_UPGRADE_WAIT_TIMEOUT/) {
|
|
31
|
+
const checked = strict_1.default.rejects(task, pattern);
|
|
32
|
+
void checked.catch(() => undefined);
|
|
33
|
+
return checked;
|
|
34
|
+
}
|
|
35
|
+
async function fixture(t, run) {
|
|
36
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-upgrade-deadline-"));
|
|
37
|
+
const key = `upgrade-${(0, node_crypto_1.randomUUID)()}`;
|
|
38
|
+
const gate = deferred();
|
|
39
|
+
const cleanup = deferred();
|
|
40
|
+
const state = { contexts: {}, signals: [], stopping: [], cleanupOptions: [], fail: false, holdCleanup: false, ignoreSignal: false };
|
|
41
|
+
state.run = async (request) => {
|
|
42
|
+
state.signals.push(request.signal);
|
|
43
|
+
state.request = request;
|
|
44
|
+
await (state.ignoreSignal ? gate.promise : (0, agent_1.abortable)(gate.promise, request.signal));
|
|
45
|
+
await request.onMessage?.("旧任务的迟到回复");
|
|
46
|
+
return { text: "旧任务完成" };
|
|
47
|
+
};
|
|
48
|
+
globalThis[key] = state;
|
|
49
|
+
const stateDir = (0, node_path_1.join)(root, "state");
|
|
50
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(stateDir, "npm"), { recursive: true });
|
|
51
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir,
|
|
52
|
+
versions: new version_manager_1.VersionManager({ stateDir, npm: async () => '"1.0.1"' }), publisherResolver: async () => "m2lihui" });
|
|
53
|
+
const pending = [];
|
|
54
|
+
const add = async (id, type = "agent") => {
|
|
55
|
+
for (const [dir, version] of [[(0, node_path_1.join)(root, "plugins", id), "1.0.0"], [(0, node_path_1.join)(stateDir, "downloads", `agent-bridge-${id}`, "1.0.1", "node_modules", `agent-bridge-${id}`), "1.0.1"]]) {
|
|
56
|
+
await (0, promises_1.mkdir)(dir, { recursive: true });
|
|
57
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "package.json"), JSON.stringify({ name: `agent-bridge-${id}`, version, agentBridge: { plugin: "./plugin.json" } }));
|
|
58
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "plugin.json"), JSON.stringify({ id, type, version, entry: "index.js", apiVersion: "1", capabilities: type === "channel" ? ["channel"] : id === "personal-assistant" ? ["chat", "management"] : ["chat", "model-provider"] }));
|
|
59
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(dir, "index.js"), `
|
|
60
|
+
const state=globalThis[${JSON.stringify(key)}];
|
|
61
|
+
module.exports=class {
|
|
62
|
+
constructor(){this.pending=new Set();this.modelProvider={complete:r=>this.handle({task:r.prompt,...r})}}
|
|
63
|
+
get sessionOperations(){return ['new']}
|
|
64
|
+
async controlSession(r){return {...await this.handle({task:'/new'}),sessionId:r.sessionId}}
|
|
65
|
+
async activate(c){this.context=c;state.contexts[${JSON.stringify(id)}]=${JSON.stringify(type)}==='channel'?{...c,ingress:{publish:async m=>{const task=c.ingress.publish(m);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}}}:c;this.dispose=c.http.register('GET','/${id}/deadline',(_q,r)=>r.end('${version}'));if('${version}'==='1.0.1'&&state.fail)throw Error('新版启动失败')}
|
|
66
|
+
async deactivate(reason,options){state.stopping.push('${id}@${version}');state.cleanupOptions.push(options);await Promise.allSettled([...this.pending]);if('${version}'==='1.0.0'&&state.failCleanup){state.failCleanup=false;throw Error('旧插件清理失败')}if('${version}'==='1.0.0'&&state.holdCleanup)await state.cleanup;this.dispose();this.context.http.unregister()}
|
|
67
|
+
async health(){return {status:'ready'}}
|
|
68
|
+
async handle(r){if('${version}'!=='1.0.0')return {text:'新版完成'};const task=state.run(r);this.pending.add(task);try{return await task}finally{this.pending.delete(task)}}
|
|
69
|
+
};`);
|
|
70
|
+
}
|
|
71
|
+
await manager.discover();
|
|
72
|
+
await manager.start(id);
|
|
73
|
+
};
|
|
74
|
+
state.cleanup = cleanup.promise;
|
|
75
|
+
const upgrade = (id = "demo") => {
|
|
76
|
+
const promise = manager.upgradePackage(id, `agent-bridge-${id}`, "1.0.1");
|
|
77
|
+
pending.push(promise);
|
|
78
|
+
void promise.catch(() => undefined);
|
|
79
|
+
return promise;
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
await add("demo");
|
|
83
|
+
t.mock.timers.enable({ apis: ["setTimeout"] });
|
|
84
|
+
await run({ manager, gate, cleanup, state, upgrade, add });
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
gate.resolve();
|
|
88
|
+
cleanup.resolve();
|
|
89
|
+
t.mock.timers.reset();
|
|
90
|
+
await Promise.allSettled(pending);
|
|
91
|
+
for (const plugin of manager.list())
|
|
92
|
+
if (manager.get(plugin.id)?.instance)
|
|
93
|
+
await manager.stop(plugin.id);
|
|
94
|
+
await manager.logs.flush();
|
|
95
|
+
delete globalThis[key];
|
|
96
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 10 });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
(0, node_test_1.default)("插件升级等待满两小时才取消旧任务并继续安装", async (t) => {
|
|
100
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
101
|
+
const task = manager.handle("demo", { task: "长任务" });
|
|
102
|
+
const failure = cancelled(task, /PLUGIN_UPGRADE_WAIT_TIMEOUT.*两小时/);
|
|
103
|
+
await until(() => state.signals.length === 1);
|
|
104
|
+
const updated = upgrade();
|
|
105
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
106
|
+
t.mock.timers.tick(TWO_HOURS - 1);
|
|
107
|
+
await flush();
|
|
108
|
+
strict_1.default.equal(Boolean(state.signals[0]?.aborted), false);
|
|
109
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
110
|
+
strict_1.default.deepEqual(state.stopping, [], "两小时内不能提前调用可能取消任务的 deactivate");
|
|
111
|
+
t.mock.timers.tick(1);
|
|
112
|
+
await flush();
|
|
113
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
114
|
+
await failure;
|
|
115
|
+
await updated;
|
|
116
|
+
strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }]);
|
|
117
|
+
strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
|
|
118
|
+
await manager.logs.flush();
|
|
119
|
+
const logs = (await manager.logs.page()).logs;
|
|
120
|
+
strict_1.default.ok(logs.some(log => log.fields?.waitMs === TWO_HOURS && typeof log.fields?.deadline === "string"));
|
|
121
|
+
strict_1.default.ok(logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
(0, node_test_1.default)("旧任务及时完成则正常升级且清除强制取消定时器", async (t) => {
|
|
125
|
+
await fixture(t, async ({ manager, state, gate, upgrade }) => {
|
|
126
|
+
const task = manager.handle("demo", { task: "短任务" });
|
|
127
|
+
await until(() => state.signals.length === 1);
|
|
128
|
+
const updated = upgrade();
|
|
129
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
130
|
+
gate.resolve();
|
|
131
|
+
strict_1.default.equal((await task).text, "旧任务完成");
|
|
132
|
+
await updated;
|
|
133
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
134
|
+
await flush();
|
|
135
|
+
await manager.logs.flush();
|
|
136
|
+
strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
137
|
+
strict_1.default.equal((await manager.handle("demo", { task: "新任务" })).text, "新版完成");
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
(0, node_test_1.default)("不响应取消的旧任务不能继续阻塞升级、发送迟到回复或注销新版路由", async (t) => {
|
|
141
|
+
await fixture(t, async ({ manager, state, cleanup, gate, upgrade, add }) => {
|
|
142
|
+
await add("other");
|
|
143
|
+
state.ignoreSignal = true;
|
|
144
|
+
state.holdCleanup = true;
|
|
145
|
+
const replies = [];
|
|
146
|
+
const task = manager.handle("demo", { task: "挂起任务", onMessage: async (text) => { replies.push(text); } });
|
|
147
|
+
const failure = cancelled(task);
|
|
148
|
+
await until(() => state.signals.length === 1);
|
|
149
|
+
const oldContext = state.contexts.demo;
|
|
150
|
+
const updated = upgrade();
|
|
151
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
152
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
153
|
+
await flush();
|
|
154
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
155
|
+
t.mock.timers.tick(5_000);
|
|
156
|
+
await flush();
|
|
157
|
+
await failure;
|
|
158
|
+
await updated;
|
|
159
|
+
let reply = "";
|
|
160
|
+
const readRoute = () => manager.dispatchHttp({ method: "GET", url: "/demo/deadline" }, { end: (text) => { reply = text; } });
|
|
161
|
+
strict_1.default.equal(await readRoute(), true);
|
|
162
|
+
strict_1.default.equal(reply, "1.0.1");
|
|
163
|
+
gate.resolve();
|
|
164
|
+
cleanup.resolve();
|
|
165
|
+
await flush();
|
|
166
|
+
await flush();
|
|
167
|
+
strict_1.default.deepEqual(replies, []);
|
|
168
|
+
strict_1.default.equal(await readRoute(), true);
|
|
169
|
+
strict_1.default.equal(reply, "1.0.1");
|
|
170
|
+
strict_1.default.throws(() => oldContext.http.register("GET", "/stale", () => undefined), /停止|取消|结束/);
|
|
171
|
+
await strict_1.default.rejects(oldContext.callPlugin("other", { task: "过期调用" }), /停止|取消|结束|PLUGIN_UPGRADE_WAIT_TIMEOUT/);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
(0, node_test_1.default)("模型来源任务也在插件升级等待两小时后取消", async (t) => {
|
|
175
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
176
|
+
const task = manager.completeModelProvider("demo", { prompt: "模型任务", sessionId: "model-session", timeoutMs: 120_000, signal: new AbortController().signal });
|
|
177
|
+
const failure = cancelled(task);
|
|
178
|
+
await until(() => state.signals.length === 1);
|
|
179
|
+
const updated = upgrade();
|
|
180
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
181
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
182
|
+
await flush();
|
|
183
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
184
|
+
await failure;
|
|
185
|
+
await updated;
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
(0, node_test_1.default)("通道升级期限取消来源任务并传递到目标 Agent,保留原会话记录", async (t) => {
|
|
189
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
190
|
+
await add("aun", "channel");
|
|
191
|
+
const source = state.contexts.aun;
|
|
192
|
+
const task = source.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "长任务", reply: async () => undefined });
|
|
193
|
+
const failure = cancelled(task);
|
|
194
|
+
await until(() => state.signals.length === 1);
|
|
195
|
+
const updated = upgrade("aun");
|
|
196
|
+
await until(() => manager.get("aun").record.status === "stopping");
|
|
197
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
198
|
+
await flush();
|
|
199
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
200
|
+
await failure;
|
|
201
|
+
await updated;
|
|
202
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
203
|
+
strict_1.default.equal(manager.sessions.isBusy('["aun","","","peer.agentid.cn",""]'), false);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
(0, node_test_1.default)("插件间调用随来源插件的升级期限取消,不取消无关插件实例", async (t) => {
|
|
207
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
208
|
+
await add("parent");
|
|
209
|
+
const original = state.run;
|
|
210
|
+
state.run = (request) => request.task === "父任务"
|
|
211
|
+
? state.contexts.parent.callPlugin("demo", { task: "子任务" })
|
|
212
|
+
: original(request);
|
|
213
|
+
const task = manager.handle("parent", { task: "父任务" });
|
|
214
|
+
const failure = cancelled(task);
|
|
215
|
+
await until(() => state.signals.length === 1);
|
|
216
|
+
const updated = upgrade("parent");
|
|
217
|
+
await until(() => manager.get("parent").record.status === "stopping");
|
|
218
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
219
|
+
await flush();
|
|
220
|
+
strict_1.default.equal(state.signals[0]?.aborted, true);
|
|
221
|
+
await failure;
|
|
222
|
+
await updated;
|
|
223
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
(0, node_test_1.default)("强制取消后新版启动失败能回滚,下一次升级重新等待两小时", async (t) => {
|
|
227
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
228
|
+
state.fail = true;
|
|
229
|
+
const first = cancelled(manager.handle("demo", { task: "第一项任务" }));
|
|
230
|
+
await until(() => state.signals.length === 1);
|
|
231
|
+
const updated = upgrade();
|
|
232
|
+
const rollback = strict_1.default.rejects(updated, /新版启动失败/);
|
|
233
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
234
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
235
|
+
await flush();
|
|
236
|
+
await first;
|
|
237
|
+
await rollback;
|
|
238
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
239
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
240
|
+
strict_1.default.deepEqual(state.cleanupOptions, [{ preserveDesktop: true }, { preserveDesktop: true }]);
|
|
241
|
+
state.fail = false;
|
|
242
|
+
await add("demo");
|
|
243
|
+
const second = cancelled(manager.handle("demo", { task: "第二项任务" }));
|
|
244
|
+
await until(() => state.signals.length === 2);
|
|
245
|
+
strict_1.default.equal(state.signals[1].aborted, false);
|
|
246
|
+
const retried = upgrade();
|
|
247
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
248
|
+
t.mock.timers.tick(TWO_HOURS - 1);
|
|
249
|
+
await flush();
|
|
250
|
+
strict_1.default.equal(state.signals[1].aborted, false);
|
|
251
|
+
t.mock.timers.tick(1);
|
|
252
|
+
await flush();
|
|
253
|
+
await second;
|
|
254
|
+
await retried;
|
|
255
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
(0, node_test_1.default)("旧插件清理失败时恢复旧版本并清除升级期限", async (t) => {
|
|
259
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
260
|
+
state.failCleanup = true;
|
|
261
|
+
await strict_1.default.rejects(upgrade(), /旧插件清理失败/);
|
|
262
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.0");
|
|
263
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
264
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
265
|
+
await flush();
|
|
266
|
+
await manager.logs.flush();
|
|
267
|
+
strict_1.default.ok(!(await manager.logs.page()).logs.some(log => log.fields?.code === "PLUGIN_UPGRADE_WAIT_TIMEOUT"));
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
(0, node_test_1.default)("普通停止不套用升级的两小时强制取消期限", async (t) => {
|
|
271
|
+
await fixture(t, async ({ manager, state, gate }) => {
|
|
272
|
+
const task = manager.handle("demo", { task: "普通任务" });
|
|
273
|
+
await until(() => state.signals.length === 1);
|
|
274
|
+
const stopping = manager.stop("demo");
|
|
275
|
+
await until(() => state.stopping.length === 1);
|
|
276
|
+
t.mock.timers.tick(TWO_HOURS + 5_000);
|
|
277
|
+
await flush();
|
|
278
|
+
strict_1.default.equal(state.signals[0].aborted, false);
|
|
279
|
+
gate.resolve();
|
|
280
|
+
await task;
|
|
281
|
+
await stopping;
|
|
282
|
+
strict_1.default.deepEqual(state.cleanupOptions, [undefined]);
|
|
283
|
+
strict_1.default.equal(manager.get("demo").record.status, "disabled");
|
|
284
|
+
});
|
|
285
|
+
});
|
|
286
|
+
(0, node_test_1.default)("个人助理升级期限传递到管理能力启动的子 Agent 任务", async (t) => {
|
|
287
|
+
await fixture(t, async ({ manager, state, add, upgrade }) => {
|
|
288
|
+
await add("personal-assistant");
|
|
289
|
+
await add("aun", "channel");
|
|
290
|
+
await manager.saveConfig("personal-assistant", { allowedPeers: ["*"], allowedAgents: ["demo"] });
|
|
291
|
+
const original = state.run;
|
|
292
|
+
state.run = (request) => request.task === "助理任务"
|
|
293
|
+
? request.management.callAgent("demo", "子任务")
|
|
294
|
+
: original(request);
|
|
295
|
+
const failure = cancelled(state.contexts.aun.ingress.publish({ channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "personal-assistant", text: "助理任务", reply: async () => undefined }));
|
|
296
|
+
await until(() => state.signals.length === 1);
|
|
297
|
+
const updated = upgrade("personal-assistant");
|
|
298
|
+
await until(() => manager.get("personal-assistant").record.status === "stopping");
|
|
299
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
300
|
+
await flush();
|
|
301
|
+
strict_1.default.equal(state.signals[0]?.aborted, true, "助理取消不能留下仍在执行的桥接子任务");
|
|
302
|
+
await failure;
|
|
303
|
+
await updated;
|
|
304
|
+
strict_1.default.equal(manager.get("demo").record.status, "ready");
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
(0, node_test_1.default)("挂起的新建会话操作同样受插件升级期限约束", async (t) => {
|
|
308
|
+
await fixture(t, async ({ manager, state, upgrade }) => {
|
|
309
|
+
let rejected = false;
|
|
310
|
+
const task = manager.controlSession("demo", { operation: "new", sessionId: "new-session" }).catch(error => { rejected = true; throw error; });
|
|
311
|
+
const failure = cancelled(task);
|
|
312
|
+
await until(() => state.signals.length === 1);
|
|
313
|
+
const updated = upgrade();
|
|
314
|
+
await until(() => manager.get("demo").record.status === "stopping");
|
|
315
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
316
|
+
await flush();
|
|
317
|
+
strict_1.default.equal(rejected, true);
|
|
318
|
+
t.mock.timers.tick(5_000);
|
|
319
|
+
await flush();
|
|
320
|
+
await failure;
|
|
321
|
+
await updated;
|
|
322
|
+
strict_1.default.equal(manager.get("demo").record.version, "1.0.1");
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
for (const delivery of ["文本", "附件"])
|
|
326
|
+
(0, node_test_1.default)(`升级取消挂起的${delivery}回传后释放来源忙碌状态并记录具体错误`, async (t) => {
|
|
327
|
+
await fixture(t, async ({ manager, state, add, gate, upgrade }) => {
|
|
328
|
+
await add("aun", "channel");
|
|
329
|
+
await manager.sessionManagement.saveConfig({ enabled: true, grants: [] });
|
|
330
|
+
state.run = async () => ({ text: "回传结果", ...(delivery === "附件" ? { attachments: [{ kind: "file", data: new Uint8Array([42]), filename: "结果.txt", contentType: "text/plain" }] } : {}) });
|
|
331
|
+
let delivering = false;
|
|
332
|
+
const deliver = async () => { delivering = true; await gate.promise; };
|
|
333
|
+
const message = { channel: "aun", peerId: "peer.agentid.cn", agentPlugin: "demo", text: "生成文件", reply: deliver, replyAttachments: deliver };
|
|
334
|
+
const failure = cancelled(state.contexts.aun.ingress.publish(message));
|
|
335
|
+
await until(() => delivering);
|
|
336
|
+
const binding = await manager.sessions.current(message, "demo");
|
|
337
|
+
const updated = upgrade("aun");
|
|
338
|
+
await until(() => manager.get("aun").record.status === "stopping");
|
|
339
|
+
t.mock.timers.tick(TWO_HOURS);
|
|
340
|
+
await flush();
|
|
341
|
+
t.mock.timers.tick(5_000);
|
|
342
|
+
await flush();
|
|
343
|
+
await failure;
|
|
344
|
+
await updated;
|
|
345
|
+
const sessions = await manager.sessionManagement.command("/sessions list", { source: binding.source, sessionId: "另一个管理会话" });
|
|
346
|
+
strict_1.default.match(sessions, new RegExp(binding.logicalSessionId));
|
|
347
|
+
strict_1.default.doesNotMatch(sessions, /正在运行/);
|
|
348
|
+
strict_1.default.ok(manager.recentMessages().some(item => item.direction === "error" && item.text.includes("PLUGIN_UPGRADE_WAIT_TIMEOUT")));
|
|
349
|
+
gate.resolve();
|
|
350
|
+
await flush();
|
|
351
|
+
strict_1.default.ok(!manager.recentMessages().some(item => item.direction === "reply"));
|
|
352
|
+
strict_1.default.equal((await manager.sessionManagement.archive.snapshotSessions()).sessions.length, 0);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
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_child_process_1 = require("node:child_process");
|
|
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_util_1 = require("node:util");
|
|
12
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
13
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
14
|
+
(0, node_test_1.default)("发布预检按真实 npm 包名选择原目录并实际加载入口", { skip: process.platform === "win32" }, async () => {
|
|
15
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-publish-name-"));
|
|
16
|
+
const bin = (0, node_path_1.join)(root, "bin");
|
|
17
|
+
const packageRoot = (0, node_path_1.join)(root, "packages", "personal-assistant");
|
|
18
|
+
try {
|
|
19
|
+
const ids = ["qwenwork", "abweb", "dsh", "doubao", "aun", "workbuddy", "zcode", "feishu", "chatgpt", "codex-cli", "claude-cli", "personal-assistant"];
|
|
20
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.0.0" }));
|
|
21
|
+
for (const id of ids) {
|
|
22
|
+
const directory = (0, node_path_1.join)(root, "packages", id);
|
|
23
|
+
await (0, promises_1.mkdir)(directory, { recursive: true });
|
|
24
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(directory, "package.json"), JSON.stringify({ name: id === "personal-assistant" ? "agent-bridge-pa" : `agent-bridge-${id}`, version: "1.0.0", main: "index.js", agentBridge: { id, plugin: "./plugin.json" } }));
|
|
25
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(directory, "plugin.json"), JSON.stringify({ id, version: "1.0.0", entry: "index.js" }));
|
|
26
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(directory, "index.js"), `module.exports=class { manifest={id:${JSON.stringify(id)},version:'1.0.0'}; };`);
|
|
27
|
+
}
|
|
28
|
+
await (0, promises_1.mkdir)(bin);
|
|
29
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(bin, "npm"), `#!${process.execPath}\nif(process.argv[2]==='pack')process.stdout.write(JSON.stringify([{files:['index.js','plugin.json','config.schema.json'].map(path=>({path}))}]));`);
|
|
30
|
+
await (0, promises_1.chmod)((0, node_path_1.join)(bin, "npm"), 0o755);
|
|
31
|
+
const env = { ...process.env, PATH: `${bin}:${process.env.PATH ?? ""}` };
|
|
32
|
+
const script = (0, node_path_1.join)(process.cwd(), "scripts", "publish-packages.mjs");
|
|
33
|
+
const result = await execFileAsync(process.execPath, [script, "--only=agent-bridge-pa"], { cwd: root, env });
|
|
34
|
+
strict_1.default.match(result.stdout, /agent-bridge-pa@1.0.0:打包预检通过/);
|
|
35
|
+
await strict_1.default.rejects(execFileAsync(process.execPath, [script, "--only=agent-bridge-personal-assistant"], { cwd: root, env }), /未知发布包/);
|
|
36
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(packageRoot, "index.js"), "throw new Error('入口加载失败');");
|
|
37
|
+
await strict_1.default.rejects(execFileAsync(process.execPath, [script, "--only=agent-bridge-pa"], { cwd: root, env }), /入口加载失败/);
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
41
|
+
}
|
|
42
|
+
});
|
|
@@ -72,7 +72,7 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
72
72
|
(0, node_test_1.default)("qwenwork manifest declares the dynamic plugin contract", () => {
|
|
73
73
|
strict_1.default.equal(qwenwork_1.qwenworkManifest.id, "qwenwork");
|
|
74
74
|
strict_1.default.equal(qwenwork_1.qwenworkManifest.entry, "dist/index.js");
|
|
75
|
-
strict_1.default.deepEqual(qwenwork_1.qwenworkManifest.capabilities, ["chat", "openai-compatible", "health"]);
|
|
75
|
+
strict_1.default.deepEqual(qwenwork_1.qwenworkManifest.capabilities, ["chat", "openai-compatible", "health", "model-provider"]);
|
|
76
76
|
strict_1.default.equal(qwenwork_1.qwenworkManifest.defaults.cdpPort, 9228);
|
|
77
77
|
strict_1.default.equal(qwenwork_1.qwenworkManifest.defaults.httpPort, 17893);
|
|
78
78
|
});
|
|
@@ -251,6 +251,36 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
251
251
|
await plugin.deactivate("stop");
|
|
252
252
|
strict_1.default.equal((await plugin.health()).status, "stopped");
|
|
253
253
|
});
|
|
254
|
+
(0, node_test_1.default)("千问办公仅在重载时请求结束桌面进程", async () => {
|
|
255
|
+
let restarts = 0;
|
|
256
|
+
const client = {
|
|
257
|
+
connect: async () => undefined,
|
|
258
|
+
close: () => undefined,
|
|
259
|
+
connected: () => true,
|
|
260
|
+
send: async () => undefined,
|
|
261
|
+
direct: async () => "回复",
|
|
262
|
+
};
|
|
263
|
+
const plugin = new qwenwork_1.QwenworkPlugin({ client, httpPort: 0, restartDesktop: async () => { restarts++; } });
|
|
264
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
265
|
+
await plugin.deactivate("reload");
|
|
266
|
+
strict_1.default.equal(restarts, 1);
|
|
267
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
268
|
+
await plugin.deactivate("stop");
|
|
269
|
+
strict_1.default.equal(restarts, 1);
|
|
270
|
+
});
|
|
271
|
+
(0, node_test_1.default)("千问办公升级清理保留桌面进程", async () => {
|
|
272
|
+
let restarts = 0;
|
|
273
|
+
let closed = 0;
|
|
274
|
+
const plugin = new qwenwork_1.QwenworkPlugin({
|
|
275
|
+
httpPort: 0, restartDesktop: async () => { restarts++; },
|
|
276
|
+
client: { connect: async () => undefined, close: () => { closed++; }, connected: () => true, send: async () => undefined, direct: async () => "回复" },
|
|
277
|
+
});
|
|
278
|
+
await plugin.activate({ config: {}, dataDir: "." });
|
|
279
|
+
await plugin.deactivate("reload", { preserveDesktop: true });
|
|
280
|
+
strict_1.default.equal(restarts, 0);
|
|
281
|
+
strict_1.default.equal(closed, 1);
|
|
282
|
+
strict_1.default.equal((await plugin.health()).status, "stopped");
|
|
283
|
+
});
|
|
254
284
|
(0, node_test_1.default)("千问办公 /new 调用真实新任务并返回逻辑会话标识", async () => {
|
|
255
285
|
let created = 0;
|
|
256
286
|
const plugin = new qwenwork_1.QwenworkPlugin({
|
|
@@ -278,6 +308,33 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
278
308
|
await plugin.deactivate("stop");
|
|
279
309
|
}
|
|
280
310
|
});
|
|
311
|
+
(0, node_test_1.default)("千问模型来源按个人助理会话恢复桌面会话并保留取消信号", async (t) => {
|
|
312
|
+
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
313
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
314
|
+
const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
315
|
+
const dataDir = await mkdtemp(join(tmpdir(), "qwen-model-provider-"));
|
|
316
|
+
t.after(() => rm(dataDir, { recursive: true, force: true }));
|
|
317
|
+
const calls = [];
|
|
318
|
+
const plugin = new qwenwork_1.QwenworkPlugin({ httpPort: 0, healthIntervalMs: 60_000, client: {
|
|
319
|
+
connect: async () => undefined, close: () => undefined, connected: () => true,
|
|
320
|
+
send: async () => undefined, direct: async () => "不应使用普通入口",
|
|
321
|
+
currentSessionId: async () => "user-chat",
|
|
322
|
+
createSession: async () => { calls.push("create"); return "desktop-chat-a"; },
|
|
323
|
+
activateSession: async (id) => { calls.push(["activate", id]); },
|
|
324
|
+
complete: async (text, _timeout, signal) => { calls.push(["complete", text, signal]); return { text: "模型 JSON", sessionId: "desktop-chat-a" }; },
|
|
325
|
+
} });
|
|
326
|
+
await plugin.activate({ config: {}, dataDir });
|
|
327
|
+
const controller = new AbortController();
|
|
328
|
+
try {
|
|
329
|
+
const provider = plugin.modelProvider;
|
|
330
|
+
strict_1.default.deepEqual(await provider.complete({ prompt: "第一轮", sessionId: "pa-thread", timeoutMs: 5000, signal: controller.signal }), { text: "模型 JSON" });
|
|
331
|
+
strict_1.default.deepEqual(await provider.complete({ prompt: "第二轮", sessionId: "pa-thread", timeoutMs: 5000, signal: controller.signal }), { text: "模型 JSON" });
|
|
332
|
+
strict_1.default.deepEqual(calls, ["create", ["complete", "第一轮", controller.signal], ["activate", "user-chat"], ["activate", "desktop-chat-a"], ["complete", "第二轮", controller.signal], ["activate", "user-chat"]]);
|
|
333
|
+
}
|
|
334
|
+
finally {
|
|
335
|
+
await plugin.deactivate("stop");
|
|
336
|
+
}
|
|
337
|
+
});
|
|
281
338
|
(0, node_test_1.default)("千问 CDP 仅在新任务状态校验通过后确认创建成功", async () => {
|
|
282
339
|
const { QwenCdpClient } = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/qwenwork/cdp-client")));
|
|
283
340
|
const original = globalThis.WebSocket;
|
|
@@ -583,6 +640,24 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
583
640
|
await strict_1.default.rejects(client.createSession(), /正在生成回复/);
|
|
584
641
|
strict_1.default.equal(clicked, false);
|
|
585
642
|
});
|
|
643
|
+
(0, node_test_1.default)("千问模型新建、恢复和提交时保留用户草稿并拒绝切换", async () => {
|
|
644
|
+
const { QwenCdpClient } = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/qwenwork/cdp-client")));
|
|
645
|
+
for (const operation of ["create", "activate", "complete"]) {
|
|
646
|
+
const client = new QwenCdpClient();
|
|
647
|
+
const editor = { innerText: "用户尚未发送的内容" };
|
|
648
|
+
let edits = 0, clicks = 0, navigations = 0;
|
|
649
|
+
let href = "file:///index.html?chat=user";
|
|
650
|
+
client.recoverDraft = async () => { edits++; editor.innerText = ""; };
|
|
651
|
+
client.waitFor = async () => undefined;
|
|
652
|
+
client.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, {
|
|
653
|
+
URL, window: {}, location: { get href() { return href; }, set href(value) { navigations++; href = value; } },
|
|
654
|
+
document: { querySelector: (selector) => selector.includes("contenteditable") ? editor : selector.includes('aria-label="新任务"') ? { click() { clicks++; }, getAttribute() { return null; } } : null, querySelectorAll: () => [] },
|
|
655
|
+
});
|
|
656
|
+
const run = () => operation === "create" ? client.createSession(undefined, true, true) : operation === "activate" ? client.activateSession("model", true) : client.complete("模型提示", 1, undefined, undefined, undefined, undefined, true);
|
|
657
|
+
await strict_1.default.rejects(run(), /未发送内容|用户草稿/);
|
|
658
|
+
strict_1.default.deepEqual({ edits, clicks, navigations, text: editor.innerText }, { edits: 0, clicks: 0, navigations: 0, text: "用户尚未发送的内容" });
|
|
659
|
+
}
|
|
660
|
+
});
|
|
586
661
|
(0, node_test_1.default)("千问 socket 断开后先重连,重连成功不重启桌面", async () => {
|
|
587
662
|
let connected = true;
|
|
588
663
|
let restarts = 0;
|
|
@@ -623,3 +698,62 @@ const cdp_client_1 = require("../src-ts/plugins/qwenwork/cdp-client");
|
|
|
623
698
|
await plugin.deactivate("stop");
|
|
624
699
|
}
|
|
625
700
|
});
|
|
701
|
+
(0, node_test_1.default)("千问模型与普通任务按调用顺序进入同一队列并恢复用户会话", async () => {
|
|
702
|
+
const { mkdtemp, rm } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
703
|
+
const { tmpdir } = await Promise.resolve().then(() => __importStar(require("node:os")));
|
|
704
|
+
const { join } = await Promise.resolve().then(() => __importStar(require("node:path")));
|
|
705
|
+
const dataDir = await mkdtemp(join(tmpdir(), "qwen-model-boundary-"));
|
|
706
|
+
let active = "user-chat";
|
|
707
|
+
const calls = [];
|
|
708
|
+
const plugin = new qwenwork_1.QwenworkPlugin({ httpPort: 0, healthIntervalMs: 60_000, client: {
|
|
709
|
+
connect: async () => undefined, close() { }, connected: () => true, send: async () => undefined, direct: async () => "回复",
|
|
710
|
+
currentSessionId: async () => active,
|
|
711
|
+
createSession: async () => { active = "model-chat"; return active; },
|
|
712
|
+
activateSession: async (id) => { active = id; },
|
|
713
|
+
complete: async (text) => { calls.push(`${active}:${text}`); return { text: "回复", sessionId: active }; },
|
|
714
|
+
} });
|
|
715
|
+
try {
|
|
716
|
+
await plugin.activate({ config: {}, dataDir });
|
|
717
|
+
const model = plugin.modelProvider.complete({ prompt: "模型提示", sessionId: "pa-fifo", timeoutMs: 5000, signal: new AbortController().signal });
|
|
718
|
+
const task = plugin.handle({ task: "用户续聊" });
|
|
719
|
+
await Promise.all([model, task]);
|
|
720
|
+
strict_1.default.deepEqual(calls, ["model-chat:模型提示", "user-chat:用户续聊"]);
|
|
721
|
+
}
|
|
722
|
+
finally {
|
|
723
|
+
await plugin.deactivate("stop");
|
|
724
|
+
await rm(dataDir, { recursive: true, force: true });
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
(0, node_test_1.default)("千问取消后继续观察桌面生成直至结束,超过等待时限也不释放底层任务", async (t) => {
|
|
728
|
+
const { QwenCdpClient } = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/qwenwork/cdp-client")));
|
|
729
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"] });
|
|
730
|
+
const client = new QwenCdpClient();
|
|
731
|
+
const controller = new AbortController();
|
|
732
|
+
let submitted = false;
|
|
733
|
+
let generating = true;
|
|
734
|
+
let settled = false;
|
|
735
|
+
client.ensureVisible = client.recoverDraft = client.waitFor = async () => undefined;
|
|
736
|
+
client.readArtifacts = async () => [];
|
|
737
|
+
client.cdp = async () => { submitted = true; };
|
|
738
|
+
client.evaluate = async (expression) => {
|
|
739
|
+
if (expression === "location.href")
|
|
740
|
+
return "file:///index.html?chat=model-chat";
|
|
741
|
+
if (expression.startsWith("JSON.stringify(["))
|
|
742
|
+
return JSON.stringify(submitted ? [{ id: "reply", text: "迟到回复" }] : []);
|
|
743
|
+
if (expression.startsWith("Boolean(document.querySelector('button"))
|
|
744
|
+
return generating;
|
|
745
|
+
return true;
|
|
746
|
+
};
|
|
747
|
+
const running = client.complete("模型提示", 10, controller.signal).then((value) => { settled = true; return value; });
|
|
748
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
749
|
+
controller.abort(new Error("停止等待"));
|
|
750
|
+
t.mock.timers.tick(2000);
|
|
751
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
752
|
+
strict_1.default.equal(settled, false);
|
|
753
|
+
generating = false;
|
|
754
|
+
for (let index = 0; index < 3; index++) {
|
|
755
|
+
t.mock.timers.tick(2000);
|
|
756
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
757
|
+
}
|
|
758
|
+
strict_1.default.deepEqual(await running, { text: "迟到回复", sessionId: "model-chat" });
|
|
759
|
+
});
|