mu-agent-bridge 1.0.41 → 1.0.42
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/dist/src-ts/agent-availability.js +2 -1
- package/dist/src-ts/control-server.js +4 -0
- package/dist/src-ts/plugin-manager.js +16 -0
- package/dist/src-ts/plugins/chatgpt/index.js +41 -5
- package/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/dist/src-ts/plugins/doubao/index.js +14 -12
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +3 -1
- package/dist/src-ts/plugins/workbuddy/index.js +30 -19
- package/dist/tests-ts/agent-availability.test.js +15 -1
- package/dist/tests-ts/control-server.test.js +20 -0
- package/dist/tests-ts/desktop-launch.test.js +98 -0
- package/dist/tests-ts/installer-assets.test.js +30 -7
- package/dist/tests-ts/plugin-manager.test.js +18 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +7 -0
- package/package.json +1 -1
- package/plugins/chatgpt/config.schema.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +41 -5
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +191 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +14 -12
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +191 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +3 -1
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +30 -19
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
|
@@ -7,7 +7,8 @@ async function agentAvailability(id, config, status) {
|
|
|
7
7
|
const configured = typeof config.executablePath === "string" ? config.executablePath : undefined;
|
|
8
8
|
const executable = id === "workbuddy" ? (0, workbuddy_1.findExecutable)(configured)
|
|
9
9
|
: id === "qwenwork" ? await (0, desktop_launch_1.findWindowsExecutable)("QwenWorkCN.exe", configured)
|
|
10
|
-
: id === "doubao" ? await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", configured)
|
|
10
|
+
: id === "doubao" ? await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", configured)
|
|
11
|
+
: id === "dsh" ? await (0, desktop_launch_1.findWindowsExecutable)("DeepSeek Harness.exe", configured) : true;
|
|
11
12
|
if (!executable)
|
|
12
13
|
return { available: false, reason: "未找到可执行文件" };
|
|
13
14
|
return { available: status === "ready", reason: status === "ready" ? "" : "插件未就绪" };
|
|
@@ -273,6 +273,10 @@ class ControlServer {
|
|
|
273
273
|
await this.options.manager.reload(id);
|
|
274
274
|
return this.json(res, 200, { ok: true });
|
|
275
275
|
}
|
|
276
|
+
if (method === "POST" && parts[3] === "restart-desktop") {
|
|
277
|
+
await this.options.manager.restartDesktop(id);
|
|
278
|
+
return this.json(res, 200, { ok: true });
|
|
279
|
+
}
|
|
276
280
|
if (method === "POST" && parts[3] === "update") {
|
|
277
281
|
const body = await this.body(req);
|
|
278
282
|
return this.json(res, 200, { ok: true, plugin: await this.options.manager.upgradePackage(id, body.packageName, body.version) });
|
|
@@ -21,6 +21,8 @@ const channel_sessions_1 = require("./channel-sessions");
|
|
|
21
21
|
const session_management_1 = require("./session-management");
|
|
22
22
|
const _2020_1 = __importDefault(require("ajv/dist/2020"));
|
|
23
23
|
const attachments_1 = require("./attachments");
|
|
24
|
+
const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
25
|
+
const workbuddy_1 = require("./plugins/workbuddy");
|
|
24
26
|
const logger = { info() { }, warn() { }, error() { } };
|
|
25
27
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
26
28
|
class PluginManager {
|
|
@@ -507,6 +509,20 @@ class PluginManager {
|
|
|
507
509
|
await this.logs.flush();
|
|
508
510
|
}
|
|
509
511
|
async reload(id) { await this.stop(id, "reload"); await this.start(id); }
|
|
512
|
+
async restartDesktop(id) {
|
|
513
|
+
if (id !== "workbuddy")
|
|
514
|
+
throw new agent_1.AgentError(`不支持桌面重启:${id}`);
|
|
515
|
+
const config = await this.config(id);
|
|
516
|
+
if (config.transport !== "cdp")
|
|
517
|
+
throw new agent_1.AgentError("请先将 WorkBuddy 设置为 CDP 模式");
|
|
518
|
+
const executable = (0, workbuddy_1.findExecutable)(typeof config.executablePath === "string" ? config.executablePath : undefined);
|
|
519
|
+
if (!executable)
|
|
520
|
+
throw new agent_1.AgentError("未找到 WorkBuddy 桌面程序,无法重启");
|
|
521
|
+
await this.stop(id);
|
|
522
|
+
const processIds = await (0, desktop_launch_1.terminateWindowsDesktop)(executable);
|
|
523
|
+
this.log("info", "后台已清理 WorkBuddy 旧进程,将以 CDP 模式启动", { processIds, executable }, id);
|
|
524
|
+
await this.start(id);
|
|
525
|
+
}
|
|
510
526
|
async uninstall(id) {
|
|
511
527
|
const loaded = this.plugins.get(id);
|
|
512
528
|
if (!loaded)
|
|
@@ -4,6 +4,9 @@ exports.ChatgptCdpClient = exports.ChatgptPlugin = exports.chatgptManifest = voi
|
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const cdp_client_1 = require("./cdp-client");
|
|
6
6
|
const session_store_1 = require("./session-store");
|
|
7
|
+
const desktop_launch_1 = require("../desktop-launch");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
7
10
|
exports.chatgptManifest = {
|
|
8
11
|
id: "chatgpt",
|
|
9
12
|
version: "1.0.10",
|
|
@@ -35,6 +38,8 @@ class ChatgptPlugin {
|
|
|
35
38
|
historyScope = "";
|
|
36
39
|
historyGeneration = 0;
|
|
37
40
|
historyProvider;
|
|
41
|
+
ownedProcess;
|
|
42
|
+
reconnect;
|
|
38
43
|
constructor(options = {}) {
|
|
39
44
|
this.options = options;
|
|
40
45
|
}
|
|
@@ -87,21 +92,52 @@ class ChatgptPlugin {
|
|
|
87
92
|
timeoutMs: typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 30_000,
|
|
88
93
|
ssh,
|
|
89
94
|
});
|
|
95
|
+
const desktopExecutable = !ssh && config.autoStart !== false && !this.options.client && process.platform === "win32"
|
|
96
|
+
? await (0, desktop_launch_1.findChatgptExecutable)(typeof config.executablePath === "string" ? config.executablePath : undefined)
|
|
97
|
+
: undefined;
|
|
98
|
+
const desktopProfile = (0, node_path_1.join)(context.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "chatgpt"), "desktop-profile");
|
|
99
|
+
const desktopArgs = [`--remote-debugging-port=${port(config.localCdpPort, 9230)}`];
|
|
100
|
+
const startupTimeoutMs = typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000;
|
|
101
|
+
this.reconnect = async () => {
|
|
102
|
+
try {
|
|
103
|
+
await this.client.connect();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
catch (first) {
|
|
107
|
+
if (!desktopExecutable)
|
|
108
|
+
throw first;
|
|
109
|
+
await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, desktopExecutable, desktopArgs, desktopProfile, () => this.client.connect(), startupTimeoutMs, child => { this.ownedProcess = child; });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
90
112
|
this.sessionOperations = this.client.createSession ? ["new"] : [];
|
|
91
113
|
try {
|
|
92
114
|
await this.client.connect();
|
|
93
115
|
}
|
|
94
116
|
catch (error) {
|
|
95
|
-
|
|
117
|
+
let connectionError = error;
|
|
118
|
+
if (desktopExecutable) {
|
|
119
|
+
try {
|
|
120
|
+
await (0, desktop_launch_1.launchManagedAndWait)(desktopExecutable, desktopArgs, desktopProfile, () => this.client.connect(), startupTimeoutMs, child => { this.ownedProcess = child; });
|
|
121
|
+
this.failure = undefined;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
catch (launchError) {
|
|
125
|
+
connectionError = launchError;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.failure = connectionError instanceof Error ? connectionError.message : String(connectionError);
|
|
96
129
|
context.logger.warn(this.failure);
|
|
97
130
|
if (!this.store.sessions.length)
|
|
98
|
-
throw
|
|
131
|
+
throw connectionError;
|
|
99
132
|
}
|
|
100
133
|
}
|
|
101
134
|
async deactivate() {
|
|
102
135
|
await this.queue;
|
|
103
136
|
await this.store?.drain();
|
|
104
137
|
this.client?.close();
|
|
138
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
|
|
139
|
+
this.ownedProcess = undefined;
|
|
140
|
+
this.reconnect = undefined;
|
|
105
141
|
this.client = undefined;
|
|
106
142
|
this.remote = false;
|
|
107
143
|
}
|
|
@@ -246,7 +282,7 @@ class ChatgptPlugin {
|
|
|
246
282
|
}
|
|
247
283
|
async synchronize() {
|
|
248
284
|
if (!this.client.connected())
|
|
249
|
-
await this.client.connect();
|
|
285
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
250
286
|
if (!this.client.listSessions)
|
|
251
287
|
throw new Error("当前 ChatGPT 客户端不支持会话列表");
|
|
252
288
|
this.store.synchronize(await this.client.listSessions());
|
|
@@ -254,7 +290,7 @@ class ChatgptPlugin {
|
|
|
254
290
|
}
|
|
255
291
|
async select(session) {
|
|
256
292
|
if (!this.client.connected())
|
|
257
|
-
await this.client.connect();
|
|
293
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
258
294
|
if (!this.client.switchSession)
|
|
259
295
|
throw new Error("当前 ChatGPT 客户端不支持会话切换");
|
|
260
296
|
const active = await this.client.activeSession?.();
|
|
@@ -321,7 +357,7 @@ class ChatgptPlugin {
|
|
|
321
357
|
await this.select(selected);
|
|
322
358
|
}
|
|
323
359
|
if (!this.client.connected())
|
|
324
|
-
await this.client.connect();
|
|
360
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
325
361
|
selected ??= await this.client.activeSession?.();
|
|
326
362
|
const message = explicit?.[2]?.trim() || request.task;
|
|
327
363
|
const reply = await this.client.direct(message, request.timeoutMs, request.attachments);
|
|
@@ -3,10 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.findWindowsExecutable = findWindowsExecutable;
|
|
4
4
|
exports.findExecutableInTree = findExecutableInTree;
|
|
5
5
|
exports.launchAndWait = launchAndWait;
|
|
6
|
+
exports.launchManagedAndWait = launchManagedAndWait;
|
|
7
|
+
exports.restartOwnedAndWait = restartOwnedAndWait;
|
|
8
|
+
exports.terminateWindowsDesktop = terminateWindowsDesktop;
|
|
9
|
+
exports.terminateOwnedProcess = terminateOwnedProcess;
|
|
10
|
+
exports.findChatgptExecutable = findChatgptExecutable;
|
|
6
11
|
exports.waitFor = waitFor;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
13
|
const promises_1 = require("node:fs/promises");
|
|
8
14
|
const node_fs_1 = require("node:fs");
|
|
9
15
|
const node_path_1 = require("node:path");
|
|
16
|
+
const node_util_1 = require("node:util");
|
|
17
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
10
18
|
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
11
19
|
/** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
|
|
12
20
|
async function findWindowsExecutable(fileName, configured) {
|
|
@@ -31,8 +39,11 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
31
39
|
}
|
|
32
40
|
if (process.platform !== "win32")
|
|
33
41
|
return undefined;
|
|
34
|
-
const pattern = /doubao|豆包|qwen|qwwork
|
|
42
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh/i;
|
|
35
43
|
const roots = [...new Set([
|
|
44
|
+
process.env.DSH_INSTALL_DIR,
|
|
45
|
+
"D:\\Programs\\DeepSeek Harness",
|
|
46
|
+
"D:\\DSH",
|
|
36
47
|
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
37
48
|
process.env.ProgramFiles,
|
|
38
49
|
process.env["ProgramFiles(x86)"],
|
|
@@ -83,6 +94,87 @@ async function launchAndWait(executable, args, connect, timeoutMs, launch = (fil
|
|
|
83
94
|
child.unref();
|
|
84
95
|
return waitFor(connect, timeoutMs, 250);
|
|
85
96
|
}
|
|
97
|
+
async function launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
98
|
+
if (profileDirectory)
|
|
99
|
+
await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
|
|
100
|
+
const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
|
|
101
|
+
child.unref();
|
|
102
|
+
onProcess(child);
|
|
103
|
+
try {
|
|
104
|
+
return await waitFor(connect, timeoutMs, 250);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
await terminateOwnedProcess(child).catch(() => undefined);
|
|
108
|
+
onProcess(undefined);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function restartOwnedAndWait(child, executable, args, profileDirectory, connect, timeoutMs, onProcess) {
|
|
113
|
+
await terminateOwnedProcess(child);
|
|
114
|
+
onProcess(undefined);
|
|
115
|
+
return launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess);
|
|
116
|
+
}
|
|
117
|
+
/** 仅在用户确认重启后清理同一用户、同一安装路径的桌面进程。 */
|
|
118
|
+
async function terminateWindowsDesktop(executable) {
|
|
119
|
+
if (process.platform !== "win32")
|
|
120
|
+
return [];
|
|
121
|
+
const script = `
|
|
122
|
+
$ErrorActionPreference = 'Stop'
|
|
123
|
+
$target = '${(0, node_path_1.resolve)(executable).replace(/'/g, "''")}'
|
|
124
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
125
|
+
$processes = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -ieq $target } | Where-Object { (Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid).Sid -eq $sid })
|
|
126
|
+
$ids = @($processes.ProcessId)
|
|
127
|
+
$roots = @($processes | Where-Object { $ids -notcontains $_.ParentProcessId })
|
|
128
|
+
foreach ($item in $roots) {
|
|
129
|
+
$current = Get-CimInstance Win32_Process -Filter "ProcessId=$($item.ProcessId)"
|
|
130
|
+
if (-not $current -or $current.CreationDate -ne $item.CreationDate) { continue }
|
|
131
|
+
& taskkill.exe /PID $item.ProcessId /T /F | Out-Null
|
|
132
|
+
if ($LASTEXITCODE -ne 0 -and (Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue)) { throw "无法结束桌面进程 $($item.ProcessId)" }
|
|
133
|
+
$remaining = Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue
|
|
134
|
+
if ($remaining -and -not $remaining.WaitForExit(10000)) { throw "等待桌面进程退出超时 $($item.ProcessId)" }
|
|
135
|
+
}
|
|
136
|
+
ConvertTo-Json -Compress -InputObject @($roots | ForEach-Object { [int]$_.ProcessId })
|
|
137
|
+
`;
|
|
138
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 30_000, windowsHide: true });
|
|
139
|
+
return JSON.parse(stdout.trim());
|
|
140
|
+
}
|
|
141
|
+
async function terminateOwnedProcess(child, platform = process.platform, run = async (file, args) => execFileAsync(file, args, { timeout: 10_000 })) {
|
|
142
|
+
const pid = child?.pid;
|
|
143
|
+
if (!Number.isSafeInteger(pid) || !pid || child?.exitCode !== null && child?.exitCode !== undefined || child?.signalCode)
|
|
144
|
+
return;
|
|
145
|
+
if (platform === "win32") {
|
|
146
|
+
try {
|
|
147
|
+
await run("taskkill.exe", ["/PID", String(pid), "/T", "/F"]);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!child.exitCode && !child.signalCode && error.code !== "ESRCH")
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
try {
|
|
156
|
+
process.kill(pid, "SIGTERM");
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error.code !== "ESRCH")
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function findChatgptExecutable(configured) {
|
|
165
|
+
if (configured?.trim() && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)))
|
|
166
|
+
return (0, node_path_1.resolve)(configured);
|
|
167
|
+
if (process.platform !== "win32")
|
|
168
|
+
return undefined;
|
|
169
|
+
const script = "$candidates = foreach ($package in (Get-AppxPackage | Where-Object { $_.Name -like 'OpenAI.Codex*' })) { $appDir = Join-Path $package.InstallLocation 'app'; $path = Join-Path $appDir 'ChatGPT.exe'; if (-not (Test-Path -LiteralPath $path)) { $path = Get-ChildItem -LiteralPath $appDir -Filter 'ChatGPT*.exe' -File -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName }; if ($path) { [pscustomobject]@{ Version = [version]$package.Version; Executable = $path } } }; $candidate = $candidates | Sort-Object Version -Descending | Select-Object -First 1; if ($candidate) { $candidate.Executable }";
|
|
170
|
+
try {
|
|
171
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 10_000 });
|
|
172
|
+
return stdout.trim().split(/\r?\n/).at(-1)?.trim() || undefined;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
86
178
|
async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
87
179
|
const deadline = Date.now() + timeoutMs;
|
|
88
180
|
let last;
|
|
@@ -97,4 +189,3 @@ async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
|
97
189
|
}
|
|
98
190
|
throw last instanceof Error ? last : new Error("桌面程序启动超时");
|
|
99
191
|
}
|
|
100
|
-
const node_child_process_1 = require("node:child_process");
|
|
@@ -7,6 +7,7 @@ const cdp_client_1 = require("./cdp-client");
|
|
|
7
7
|
const session_history_1 = require("./session-history");
|
|
8
8
|
const plugin_1 = require("../../plugin");
|
|
9
9
|
const desktop_launch_1 = require("../desktop-launch");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
10
11
|
exports.doubaoManifest = {
|
|
11
12
|
id: "doubao",
|
|
12
13
|
version: "1.0.20",
|
|
@@ -35,6 +36,7 @@ class DoubaoPlugin {
|
|
|
35
36
|
}
|
|
36
37
|
disposer;
|
|
37
38
|
reconnect;
|
|
39
|
+
ownedProcess;
|
|
38
40
|
async activate(context) {
|
|
39
41
|
this.dataDir = context.dataDir;
|
|
40
42
|
this.bindings.clear();
|
|
@@ -68,20 +70,18 @@ class DoubaoPlugin {
|
|
|
68
70
|
}
|
|
69
71
|
catch (first) {
|
|
70
72
|
if (context.config.autoStart !== false && !this.injectedClient && process.platform === "win32") {
|
|
71
|
-
const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
|
|
72
73
|
const executable = await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
|
|
73
|
-
if (executable)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
if (!executable)
|
|
75
|
+
throw new Error("未找到 Doubao.exe,请配置 executablePath");
|
|
76
|
+
const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
|
|
77
|
+
context.logger?.warn?.(`正在恢复豆包:${executable},等待 CDP ${port}`);
|
|
78
|
+
try {
|
|
79
|
+
await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, executable, [`--remote-debugging-port=${port}`], (0, node_path_1.join)(this.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "doubao"), "desktop-profile"), () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000, child => { this.ownedProcess = child; });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
first = new Error(`已启动豆包,但 CDP ${port} 未就绪:${error instanceof Error ? error.message : String(error)}`);
|
|
82
84
|
}
|
|
83
|
-
else
|
|
84
|
-
first = new Error("未找到 Doubao.exe,请配置 executablePath");
|
|
85
85
|
}
|
|
86
86
|
throw first;
|
|
87
87
|
}
|
|
@@ -106,6 +106,8 @@ class DoubaoPlugin {
|
|
|
106
106
|
this.disposer = undefined;
|
|
107
107
|
this.reconnect = undefined;
|
|
108
108
|
await this.client?.close();
|
|
109
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
|
|
110
|
+
this.ownedProcess = undefined;
|
|
109
111
|
this.client = undefined;
|
|
110
112
|
this.state = "stopped";
|
|
111
113
|
}
|
|
@@ -91,7 +91,9 @@ class WorkbuddyCdpClient {
|
|
|
91
91
|
pending.reject(new Error("WorkBuddy CDP 已关闭")); this.pending.clear(); }
|
|
92
92
|
async createTask() {
|
|
93
93
|
const snapshot = `(() => { const selected = [...document.querySelectorAll('[data-conversation-id]')].find((item) => String(item.className).includes('_selected_')); const editor = document.querySelector(${JSON.stringify(exports.editorSelector)}); const text = editor?.cloneNode(true); text?.querySelectorAll('[data-slate-placeholder="true"],[data-slate-zero-width]').forEach((item) => item.remove()); return { url: location.href, conversationId: selected?.getAttribute('data-conversation-id') ?? null, selected: Boolean(selected), messages: document.querySelectorAll('.cr-agent[data-message-id]').length, editor: text?.textContent?.replace(/[\\uFEFF\\u200B]/g, '').trim() ?? null, home: Boolean(editor?.querySelector('[data-slate-placeholder="true"]')) }; })()`;
|
|
94
|
-
const before = await this.evaluate(snapshot);
|
|
94
|
+
const before = await this.evaluate(`(() => { const state = ${snapshot}; const loginRequired = [...document.querySelectorAll('a,button,[role="button"]')].some(item => /^(?:登录|立即登录|sign in)$/i.test(item.textContent?.trim() ?? '')); return { ...state, loginRequired }; })()`);
|
|
95
|
+
if (before.loginRequired)
|
|
96
|
+
throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
|
|
95
97
|
if (!await this.evaluate(`(() => { const button = [...document.querySelectorAll('button,[role="button"]')].find((item) => item.textContent?.trim() === '新建任务'); if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false; button.click(); return true; })()`))
|
|
96
98
|
throw new Error("WorkBuddy 新建任务入口未找到或不可用");
|
|
97
99
|
// WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
|
|
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.WorkbuddyPlugin = exports.workbuddyManifest = void 0;
|
|
4
4
|
exports.extractExpertQuery = extractExpertQuery;
|
|
5
5
|
exports.findExecutable = findExecutable;
|
|
6
|
-
const node_child_process_1 = require("node:child_process");
|
|
7
6
|
const node_fs_1 = require("node:fs");
|
|
8
7
|
const promises_1 = require("node:fs/promises");
|
|
9
8
|
const node_os_1 = require("node:os");
|
|
@@ -13,6 +12,7 @@ const plugin_1 = require("../../plugin");
|
|
|
13
12
|
const cdp_client_1 = require("./cdp-client");
|
|
14
13
|
const cli_client_1 = require("./cli-client");
|
|
15
14
|
const session_history_1 = require("./session-history");
|
|
15
|
+
const desktop_launch_1 = require("../desktop-launch");
|
|
16
16
|
exports.workbuddyManifest = {
|
|
17
17
|
id: "workbuddy", version: "1.1.13", displayName: "WorkBuddy", type: "agent",
|
|
18
18
|
entry: "dist/index.js", apiVersion: "1", process: "in-process",
|
|
@@ -28,13 +28,19 @@ function findExecutable(configured) {
|
|
|
28
28
|
if (configured)
|
|
29
29
|
return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
|
|
30
30
|
const local = process.env.LOCALAPPDATA ?? (0, node_path_1.join)((0, node_os_1.homedir)(), "AppData", "Local");
|
|
31
|
-
return [
|
|
31
|
+
return [
|
|
32
|
+
(0, node_path_1.join)(local, "Programs", "WorkBuddy", "WorkBuddy.exe"),
|
|
33
|
+
(0, node_path_1.join)(local, "Programs", "WorkBuddyAI", "WorkBuddyAI.exe"),
|
|
34
|
+
"D:\\Programs\\WorkBuddy\\WorkBuddy.exe",
|
|
35
|
+
"D:\\Programs\\WorkBuddyAI\\WorkBuddyAI.exe",
|
|
36
|
+
].find(node_fs_1.existsSync);
|
|
32
37
|
}
|
|
33
38
|
class WorkbuddyPlugin {
|
|
34
39
|
options;
|
|
35
40
|
manifest = exports.workbuddyManifest;
|
|
36
41
|
client;
|
|
37
42
|
process;
|
|
43
|
+
reconnect;
|
|
38
44
|
cliPath;
|
|
39
45
|
model;
|
|
40
46
|
transport = "auto";
|
|
@@ -125,10 +131,11 @@ class WorkbuddyPlugin {
|
|
|
125
131
|
async deactivate(_reason) {
|
|
126
132
|
await this.queue;
|
|
127
133
|
await this.sessionWrites.catch(() => { });
|
|
134
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.process).catch(() => undefined);
|
|
128
135
|
this.client?.close();
|
|
129
136
|
this.client = undefined;
|
|
130
|
-
// WorkBuddy 是用户桌面应用,插件停止时不结束桌面进程。
|
|
131
137
|
this.process = undefined;
|
|
138
|
+
this.reconnect = undefined;
|
|
132
139
|
}
|
|
133
140
|
async health() {
|
|
134
141
|
if (this.client?.connected())
|
|
@@ -165,6 +172,8 @@ class WorkbuddyPlugin {
|
|
|
165
172
|
throw new Error("WorkBuddy 新会话缺少 sessionId");
|
|
166
173
|
if (this.transport === "cli" || (this.transport !== "cdp" && this.cliPath) || !this.client?.createTask)
|
|
167
174
|
throw new Error("WorkBuddy 当前传输不支持新建会话");
|
|
175
|
+
if (!this.client.connected())
|
|
176
|
+
await this.reconnect?.();
|
|
168
177
|
if (!this.client.connected())
|
|
169
178
|
throw new Error("WorkBuddy CDP 未连接");
|
|
170
179
|
return this.enqueue(async () => {
|
|
@@ -181,6 +190,8 @@ class WorkbuddyPlugin {
|
|
|
181
190
|
const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
|
|
182
191
|
if (hasAttachments && !this.attachmentInput)
|
|
183
192
|
throw new Error("WorkBuddy 当前传输不支持附件输入");
|
|
193
|
+
if (!usesCli && !this.client?.connected())
|
|
194
|
+
await this.reconnect?.();
|
|
184
195
|
const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
|
|
185
196
|
if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
|
|
186
197
|
throw new Error("WorkBuddy 正在等待另一个会话的选择");
|
|
@@ -304,6 +315,21 @@ class WorkbuddyPlugin {
|
|
|
304
315
|
return run;
|
|
305
316
|
}
|
|
306
317
|
async connectOrStart(config, logger, port) {
|
|
318
|
+
const executable = findExecutable(typeof config.executablePath === "string" ? config.executablePath : this.options.executablePath);
|
|
319
|
+
// 沿用 WorkBuddy 的默认用户目录,保留用户在桌面端的登录状态。
|
|
320
|
+
const profile = undefined;
|
|
321
|
+
const timeoutMs = typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000;
|
|
322
|
+
this.reconnect = async () => {
|
|
323
|
+
try {
|
|
324
|
+
await this.client.connect();
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
catch (first) {
|
|
328
|
+
if (config.autoStart === false || this.options.client || !executable)
|
|
329
|
+
throw first;
|
|
330
|
+
await (0, desktop_launch_1.restartOwnedAndWait)(this.process, executable, [`--remote-debugging-port=${port}`], profile, () => this.client.connect(), timeoutMs, child => { this.process = child; });
|
|
331
|
+
}
|
|
332
|
+
};
|
|
307
333
|
try {
|
|
308
334
|
await this.client.connect();
|
|
309
335
|
return;
|
|
@@ -311,25 +337,10 @@ class WorkbuddyPlugin {
|
|
|
311
337
|
catch (first) {
|
|
312
338
|
if (config.autoStart === false || this.options.client)
|
|
313
339
|
throw first;
|
|
314
|
-
const executable = findExecutable(typeof config.executablePath === "string" ? config.executablePath : this.options.executablePath);
|
|
315
340
|
if (!executable)
|
|
316
341
|
throw new Error("未找到 WorkBuddy.exe,请配置 executablePath");
|
|
317
|
-
this.process = (0, node_child_process_1.spawn)(executable, [`--remote-debugging-port=${port}`], { detached: true, stdio: "ignore", windowsHide: false });
|
|
318
|
-
this.process.unref();
|
|
319
342
|
logger.info(`WorkBuddy 已启动,等待 CDP ${port}`);
|
|
320
|
-
|
|
321
|
-
let last = first;
|
|
322
|
-
while (Date.now() < deadline) {
|
|
323
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
324
|
-
try {
|
|
325
|
-
await this.client.connect();
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
catch (error) {
|
|
329
|
-
last = error;
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
throw last instanceof Error ? last : new Error("WorkBuddy CDP 启动超时");
|
|
343
|
+
await (0, desktop_launch_1.launchManagedAndWait)(executable, [`--remote-debugging-port=${port}`], profile, () => this.client.connect(), timeoutMs, child => { this.process = child; });
|
|
333
344
|
}
|
|
334
345
|
}
|
|
335
346
|
}
|
|
@@ -9,19 +9,33 @@ const node_os_1 = require("node:os");
|
|
|
9
9
|
const node_path_1 = require("node:path");
|
|
10
10
|
const node_test_1 = __importDefault(require("node:test"));
|
|
11
11
|
const agent_availability_1 = require("../src-ts/agent-availability");
|
|
12
|
+
const workbuddy_1 = require("../src-ts/plugins/workbuddy");
|
|
12
13
|
(0, node_test_1.default)("办公 Agent 根据可执行文件和就绪状态决定是否可选", async () => {
|
|
13
14
|
const dir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "office-agent-"));
|
|
15
|
+
const previousLocalAppData = process.env.LOCALAPPDATA;
|
|
14
16
|
try {
|
|
15
17
|
const file = (0, node_path_1.join)(dir, "WorkBuddy.exe");
|
|
18
|
+
const dshFile = (0, node_path_1.join)(dir, "DeepSeek Harness.exe");
|
|
16
19
|
strict_1.default.deepEqual(await (0, agent_availability_1.agentAvailability)("workbuddy", { executablePath: file }, "ready"), { available: false, reason: "未找到可执行文件" });
|
|
17
20
|
await (0, promises_1.writeFile)(file, "测试程序");
|
|
18
21
|
strict_1.default.deepEqual(await (0, agent_availability_1.agentAvailability)("workbuddy", { executablePath: file }, "ready"), { available: true, reason: "" });
|
|
22
|
+
const workbuddyAi = (0, node_path_1.join)(dir, "Programs", "WorkBuddyAI", "WorkBuddyAI.exe");
|
|
23
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(dir, "Programs", "WorkBuddyAI"), { recursive: true });
|
|
24
|
+
await (0, promises_1.writeFile)(workbuddyAi, "测试程序");
|
|
25
|
+
process.env.LOCALAPPDATA = dir;
|
|
26
|
+
strict_1.default.equal((0, workbuddy_1.findExecutable)(), workbuddyAi);
|
|
19
27
|
strict_1.default.equal((await (0, agent_availability_1.agentAvailability)("workbuddy", { executablePath: file }, "failed")).available, false);
|
|
20
28
|
strict_1.default.equal((await (0, agent_availability_1.agentAvailability)("qwenwork", { executablePath: (0, node_path_1.join)(dir, "missing.exe") }, "ready")).available, false);
|
|
21
29
|
strict_1.default.equal((await (0, agent_availability_1.agentAvailability)("doubao", { executablePath: file }, "ready")).available, true);
|
|
22
|
-
strict_1.default.
|
|
30
|
+
strict_1.default.deepEqual(await (0, agent_availability_1.agentAvailability)("dsh", {}, "ready"), { available: false, reason: "未找到可执行文件" });
|
|
31
|
+
await (0, promises_1.writeFile)(dshFile, "测试程序");
|
|
32
|
+
strict_1.default.deepEqual(await (0, agent_availability_1.agentAvailability)("dsh", { executablePath: dshFile }, "ready"), { available: true, reason: "" });
|
|
23
33
|
}
|
|
24
34
|
finally {
|
|
35
|
+
if (previousLocalAppData === undefined)
|
|
36
|
+
delete process.env.LOCALAPPDATA;
|
|
37
|
+
else
|
|
38
|
+
process.env.LOCALAPPDATA = previousLocalAppData;
|
|
25
39
|
await (0, promises_1.rm)(dir, { recursive: true, force: true });
|
|
26
40
|
}
|
|
27
41
|
});
|
|
@@ -48,6 +48,26 @@ const node_os_1 = require("node:os");
|
|
|
48
48
|
const node_path_1 = require("node:path");
|
|
49
49
|
const node_crypto_1 = require("node:crypto");
|
|
50
50
|
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
51
|
+
(0, node_test_1.default)("桌面重启请求交给 mab 后台执行并保留失败原因", async () => {
|
|
52
|
+
const calls = [];
|
|
53
|
+
const manager = { restartDesktop: async (id) => { calls.push(id); if (id !== "workbuddy")
|
|
54
|
+
throw new Error("不支持桌面重启"); } };
|
|
55
|
+
const server = new control_server_1.ControlServer({ manager, host: "127.0.0.1", port: 0 });
|
|
56
|
+
const address = await server.start();
|
|
57
|
+
const base = `http://${address.host}:${address.port}`;
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(`${base}/api/plugins/workbuddy/restart-desktop`, { method: "POST" });
|
|
60
|
+
strict_1.default.equal(response.status, 200);
|
|
61
|
+
strict_1.default.deepEqual(await response.json(), { ok: true });
|
|
62
|
+
const failed = await fetch(`${base}/api/plugins/other/restart-desktop`, { method: "POST" });
|
|
63
|
+
strict_1.default.equal(failed.status, 400);
|
|
64
|
+
strict_1.default.deepEqual(await failed.json(), { error: "不支持桌面重启" });
|
|
65
|
+
strict_1.default.deepEqual(calls, ["workbuddy", "other"]);
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await server.stop();
|
|
69
|
+
}
|
|
70
|
+
});
|
|
51
71
|
(0, node_test_1.default)("关联 EvolCore AID 时配置相应的 Agent 路由", async () => {
|
|
52
72
|
const calls = [];
|
|
53
73
|
await (0, evolcore_1.configureAidForPlugin)("dsh", "test.agentid.cn", 26107, async (args) => { calls.push(args); return { ok: true }; });
|
|
@@ -1,10 +1,44 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
37
|
};
|
|
5
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
39
|
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
40
|
const promises_1 = require("node:fs/promises");
|
|
41
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
42
|
const node_os_1 = require("node:os");
|
|
9
43
|
const node_path_1 = require("node:path");
|
|
10
44
|
const node_test_1 = __importDefault(require("node:test"));
|
|
@@ -77,6 +111,70 @@ const desktop_launch_1 = require("../src-ts/plugins/desktop-launch");
|
|
|
77
111
|
strict_1.default.equal(launches, 1);
|
|
78
112
|
strict_1.default.equal(attempts, 3);
|
|
79
113
|
});
|
|
114
|
+
(0, node_test_1.default)("结束桌面程序时只按插件保存的 PID 结束进程树", async () => {
|
|
115
|
+
const calls = [];
|
|
116
|
+
await (0, desktop_launch_1.terminateOwnedProcess)({ pid: 3210, exitCode: null, signalCode: null, unref() { } }, "win32", async (file, args) => { calls.push({ file, args }); });
|
|
117
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(undefined, "win32", async (file, args) => { calls.push({ file, args }); });
|
|
118
|
+
strict_1.default.deepEqual(calls, [{ file: "taskkill.exe", args: ["/PID", "3210", "/T", "/F"] }]);
|
|
119
|
+
});
|
|
120
|
+
(0, node_test_1.default)("管理式启动增加独立用户目录并只记录本次启动进程", async () => {
|
|
121
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "desktop-profile-"));
|
|
122
|
+
let ownedPid;
|
|
123
|
+
let launchArgs = [];
|
|
124
|
+
try {
|
|
125
|
+
const result = await (await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/desktop-launch")))).launchManagedAndWait("client.exe", ["--remote-debugging-port=9229"], (0, node_path_1.join)(root, "profile"), async () => "connected", 1000, child => { ownedPid = child?.pid; }, (_file, args) => { launchArgs = args; return { pid: 4567, exitCode: null, signalCode: null, unref() { } }; });
|
|
126
|
+
strict_1.default.equal(result, "connected");
|
|
127
|
+
strict_1.default.equal(ownedPid, 4567);
|
|
128
|
+
strict_1.default.deepEqual(launchArgs, ["--remote-debugging-port=9229", `--user-data-dir=${(0, node_path_1.join)(root, "profile")}`]);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
(0, node_test_1.default)("使用客户端原有用户目录时,管理式启动不覆盖用户数据目录", async () => {
|
|
135
|
+
const { launchManagedAndWait } = await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/desktop-launch")));
|
|
136
|
+
let args = [];
|
|
137
|
+
const result = await launchManagedAndWait("WorkBuddyAI.exe", ["--remote-debugging-port=9229"], undefined, async () => "connected", 1000, () => { }, (_file, argv) => {
|
|
138
|
+
args = argv;
|
|
139
|
+
return { pid: 1234, exitCode: null, signalCode: null, unref() { } };
|
|
140
|
+
});
|
|
141
|
+
strict_1.default.equal(result, "connected");
|
|
142
|
+
strict_1.default.deepEqual(args, ["--remote-debugging-port=9229"]);
|
|
143
|
+
});
|
|
144
|
+
(0, node_test_1.default)("Windows 后台清理已有 CDP 桌面进程,保留不同安装路径的同名程序", { skip: process.platform !== "win32" }, async () => {
|
|
145
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-kill-desktop-"));
|
|
146
|
+
const children = [];
|
|
147
|
+
try {
|
|
148
|
+
for (const folder of ["selected", "other"]) {
|
|
149
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, folder));
|
|
150
|
+
const executable = (0, node_path_1.join)(root, folder, "WorkBuddyAI.exe");
|
|
151
|
+
await (0, promises_1.copyFile)((0, node_path_1.join)(process.env.SystemRoot, "System32", "cmd.exe"), executable);
|
|
152
|
+
const child = (0, node_child_process_1.spawn)(executable, ["/d", "/s", "/c", "ping -n 60 127.0.0.1 >nul & rem --remote-debugging-port=9229"], { windowsHide: true, stdio: "ignore" });
|
|
153
|
+
await new Promise((resolve, reject) => { child.once("spawn", resolve); child.once("error", reject); });
|
|
154
|
+
children.push(child);
|
|
155
|
+
}
|
|
156
|
+
const pids = await (0, desktop_launch_1.terminateWindowsDesktop)((0, node_path_1.join)(root, "selected", "WorkBuddyAI.exe"));
|
|
157
|
+
strict_1.default.deepEqual(pids, [children[0].pid]);
|
|
158
|
+
process.kill(children[1].pid, 0);
|
|
159
|
+
strict_1.default.deepEqual(await (0, desktop_launch_1.terminateWindowsDesktop)((0, node_path_1.join)(root, "selected", "WorkBuddyAI.exe")), []);
|
|
160
|
+
}
|
|
161
|
+
finally {
|
|
162
|
+
for (const child of children)
|
|
163
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(child);
|
|
164
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
(0, node_test_1.default)("ChatGPT 可执行文件支持显式配置路径", async () => {
|
|
168
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "chatgpt-executable-"));
|
|
169
|
+
try {
|
|
170
|
+
const executable = (0, node_path_1.join)(root, "ChatGPT.exe");
|
|
171
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
172
|
+
strict_1.default.equal(await (await Promise.resolve().then(() => __importStar(require("../src-ts/plugins/desktop-launch")))).findChatgptExecutable(executable), executable);
|
|
173
|
+
}
|
|
174
|
+
finally {
|
|
175
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
80
178
|
(0, node_test_1.default)("豆包模糊查找版本目录,升级后自动避开旧版和上层启动器", async () => {
|
|
81
179
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "doubao-upgrade-"));
|
|
82
180
|
try {
|