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
|
@@ -13,18 +13,27 @@ const node_util_1 = require("node:util");
|
|
|
13
13
|
strict_1.default.match(stdout, /验证通过/);
|
|
14
14
|
});
|
|
15
15
|
(0, node_test_1.default)("Windows 安装包包括中文向导、四类 Agent 和 per-user 卸载定义", async () => {
|
|
16
|
-
const [wxs, setup, launcher, build, qwenPlugin, workbuddyPlugin] = await Promise.all([
|
|
16
|
+
const [wxs, setup, launcher, build, uninstall, qwenPlugin, workbuddyPlugin] = await Promise.all([
|
|
17
17
|
(0, promises_1.readFile)("installer/Product.wxs", "utf8"),
|
|
18
18
|
(0, promises_1.readFile)("installer/setup.ps1", "utf8"),
|
|
19
|
-
(0, promises_1.readFile)("installer/launch.
|
|
19
|
+
(0, promises_1.readFile)("installer/launch.vbs", "utf8"),
|
|
20
20
|
(0, promises_1.readFile)("scripts/build-installer.ps1", "utf8"),
|
|
21
|
+
(0, promises_1.readFile)("installer/uninstall.ps1", "utf8"),
|
|
21
22
|
(0, promises_1.readFile)("plugins/qwenwork/plugin.json", "utf8"),
|
|
22
23
|
(0, promises_1.readFile)("plugins/workbuddy/plugin.json", "utf8"),
|
|
23
24
|
]);
|
|
24
25
|
strict_1.default.match(wxs, /Scope="perUser"/);
|
|
25
26
|
strict_1.default.match(wxs, /On="uninstall"/);
|
|
26
27
|
strict_1.default.match(wxs, /LaunchWizard/);
|
|
28
|
+
strict_1.default.match(wxs, /launch\.vbs/);
|
|
29
|
+
strict_1.default.match(wxs, /Source="\$\(var\.SourceDir\)\\uninstall\.ps1"/);
|
|
30
|
+
strict_1.default.match(wxs, /<CustomAction Id="CleanupBridge"[^>]*Execute="deferred"[^>]*Return="ignore"/);
|
|
31
|
+
strict_1.default.match(wxs, /<Custom Action="CleanupBridge" Before="RemoveFiles" Condition="REMOVE="ALL""/);
|
|
32
|
+
strict_1.default.match(uninstall, /api\/plugins/);
|
|
33
|
+
strict_1.default.match(uninstall, /Stop-Process/);
|
|
34
|
+
strict_1.default.match(uninstall, /node_modules.*mu-agent-bridge/);
|
|
27
35
|
strict_1.default.match(launcher, /setup\.ps1/);
|
|
36
|
+
strict_1.default.match(launcher, /WindowStyle Hidden/);
|
|
28
37
|
strict_1.default.match(build, /ProductVersion=\$installerVersion/);
|
|
29
38
|
strict_1.default.match(build, /\$packageVersion\.Build \+ 1/);
|
|
30
39
|
strict_1.default.match(setup, /22\.16\.0/);
|
|
@@ -38,7 +47,13 @@ const node_util_1 = require("node:util");
|
|
|
38
47
|
strict_1.default.doesNotMatch(setup, /mu-agent-bridge@\d+\.\d+\.\d+/);
|
|
39
48
|
strict_1.default.match(setup, /api\/agents\/availability/);
|
|
40
49
|
strict_1.default.match(setup, /function Find-AgentExecutable/);
|
|
41
|
-
strict_1.default.match(setup, /
|
|
50
|
+
strict_1.default.match(setup, /CheckedListBox/);
|
|
51
|
+
strict_1.default.match(setup, /DataGridViewComboBoxColumn/);
|
|
52
|
+
strict_1.default.match(setup, /\[void\]\$grid\.EndEdit\(\)/);
|
|
53
|
+
strict_1.default.match(setup, /\$grid\.Rows\.Add\(\$true, \$aid, \$null\)/);
|
|
54
|
+
strict_1.default.match(setup, /显示详细信息/);
|
|
55
|
+
strict_1.default.match(setup, /环境与桥接服务/);
|
|
56
|
+
strict_1.default.match(setup, /Get-ChildItem -LiteralPath \$root -Filter \$fileName -File -Recurse/);
|
|
42
57
|
strict_1.default.match(setup, /api\/plugins\/\$agent\/config/);
|
|
43
58
|
strict_1.default.match(setup, /TopMost = \$true/);
|
|
44
59
|
strict_1.default.match(setup, /BringToFront\(\)/);
|
|
@@ -58,6 +73,10 @@ const node_util_1 = require("node:util");
|
|
|
58
73
|
strict_1.default.equal(condition[1], "NOT REMOVE AND NOT UPGRADINGPRODUCTCODE");
|
|
59
74
|
strict_1.default.match(wxs, /<InstallUISequence>\s*<Custom Action="LaunchWizard" After="ExecuteAction"/);
|
|
60
75
|
});
|
|
76
|
+
(0, node_test_1.default)("Windows 卸载时先停止插件再结束 mab 后台", { skip: process.platform !== "win32" }, async () => {
|
|
77
|
+
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "tests/windows-uninstall.test.ps1"]);
|
|
78
|
+
strict_1.default.match(stdout, /卸载清理验证通过/);
|
|
79
|
+
});
|
|
61
80
|
(0, node_test_1.default)("向导后台启动服务并提供缺失客户端的下载和重试入口", async () => {
|
|
62
81
|
const setup = await (0, promises_1.readFile)("installer/setup.ps1", "utf8");
|
|
63
82
|
strict_1.default.doesNotMatch(setup, /& mab\.cmd start/);
|
|
@@ -72,17 +91,17 @@ const node_util_1 = require("node:util");
|
|
|
72
91
|
const setup = await (0, promises_1.readFile)("installer/setup.ps1", "utf8");
|
|
73
92
|
const selectAid = setup.indexOf("$aid = Resolve-SetupAid");
|
|
74
93
|
const detect = setup.indexOf("$agents = Invoke-RestMethod");
|
|
75
|
-
const bind = setup.indexOf("Invoke-
|
|
94
|
+
const bind = setup.indexOf("Invoke-GuiBindingPlan @(");
|
|
76
95
|
strict_1.default.ok(detect > 0 && detect < selectAid && selectAid < bind);
|
|
77
96
|
strict_1.default.match(setup, /新 AID(示例:workbuddy11\.agentid\.cn)/);
|
|
78
97
|
strict_1.default.match(setup, /Owner AID(示例:lihui7\.agentid\.cn)/);
|
|
79
98
|
strict_1.default.match(setup, /\$readyAgents.Count -lt 4/);
|
|
80
|
-
strict_1.default.match(setup, /\$InstalledIds -
|
|
99
|
+
strict_1.default.match(setup, /\$InstalledIds -contains \$_.Id/);
|
|
81
100
|
strict_1.default.match(setup, /尚未就绪/);
|
|
82
101
|
strict_1.default.match(setup, /AID 关联失败/);
|
|
83
102
|
strict_1.default.match(setup, /setup\.log/);
|
|
84
103
|
strict_1.default.match(setup, /Write-SetupLog/);
|
|
85
|
-
strict_1.default.match(setup,
|
|
104
|
+
strict_1.default.match(setup, /包括已有 CDP 进程/);
|
|
86
105
|
strict_1.default.match(setup, /taskkill\.exe \/PID/);
|
|
87
106
|
strict_1.default.match(setup, /function Stop-BridgeDaemons/);
|
|
88
107
|
strict_1.default.match(setup, /api\/plugins\/aun\/commands/);
|
|
@@ -91,9 +110,13 @@ const node_util_1 = require("node:util");
|
|
|
91
110
|
strict_1.default.match(setup, /确认改为 \$agent?输入 YES 继续\"\)\.Trim\(\) -ne 'YES'/);
|
|
92
111
|
strict_1.default.doesNotMatch(setup, /-cne 'YES'/);
|
|
93
112
|
strict_1.default.match(setup, /qwenwork\.cn\/download/);
|
|
94
|
-
strict_1.default.match(setup,
|
|
113
|
+
strict_1.default.match(setup, /选择本机已有 AID/);
|
|
95
114
|
strict_1.default.match(setup, /IdentityConflictError/);
|
|
96
115
|
strict_1.default.match(setup, /返回 AID 输入步骤/);
|
|
116
|
+
strict_1.default.match(setup, /MessageBox\]::Show\(\$script:WizardForm, \$message, 'AID 已注册'/);
|
|
97
117
|
strict_1.default.match(setup, /transport = 'cdp'/);
|
|
98
118
|
strict_1.default.match(setup, /workbuddy\/stop/);
|
|
119
|
+
strict_1.default.match(setup, /workbuddy\/restart-desktop/);
|
|
120
|
+
const prepare = setup.slice(setup.indexOf('function Initialize-WorkbuddyCdp'), setup.indexOf('function Invoke-GuiBindingPlan'));
|
|
121
|
+
strict_1.default.doesNotMatch(prepare, /taskkill|Stop-Process|cdpReady/);
|
|
99
122
|
});
|
|
@@ -45,6 +45,24 @@ const node_http_1 = require("node:http");
|
|
|
45
45
|
const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
46
46
|
const version_manager_1 = require("../src-ts/version-manager");
|
|
47
47
|
const channel_sessions_1 = require("../src-ts/channel-sessions");
|
|
48
|
+
(0, node_test_1.default)("后台桌面重启先停止插件再启动,拒绝无关插件", { skip: process.platform === "win32" }, async () => {
|
|
49
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-desktop-restart-"));
|
|
50
|
+
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
|
|
51
|
+
const calls = [];
|
|
52
|
+
manager.config = async () => ({ transport: "cdp", executablePath: (0, node_path_1.join)(root, "WorkBuddyAI.exe") });
|
|
53
|
+
manager.stop = async (id) => { calls.push(`stop:${id}`); };
|
|
54
|
+
manager.start = async (id) => { calls.push(`start:${id}`); };
|
|
55
|
+
try {
|
|
56
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "WorkBuddyAI.exe"), "");
|
|
57
|
+
await manager.restartDesktop("workbuddy");
|
|
58
|
+
strict_1.default.deepEqual(calls, ["stop:workbuddy", "start:workbuddy"]);
|
|
59
|
+
await strict_1.default.rejects(() => manager.restartDesktop("other"), /不支持桌面重启/);
|
|
60
|
+
strict_1.default.equal(calls.length, 2);
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
48
66
|
(0, node_test_1.default)("插件重载同时刷新内部依赖,其他插件的缓存保持不变", async () => {
|
|
49
67
|
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-reload-dependency-"));
|
|
50
68
|
const manager = new plugin_manager_1.PluginManager({ pluginsDir: (0, node_path_1.join)(root, "plugins"), stateDir: (0, node_path_1.join)(root, "state") });
|
|
@@ -8,6 +8,13 @@ const promises_1 = require("node:fs/promises");
|
|
|
8
8
|
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
|
+
(0, node_test_1.default)("WorkBuddy 登录页阻止新建验证任务并提示先登录", async () => {
|
|
12
|
+
const client = new cdp_client_1.WorkbuddyCdpClient();
|
|
13
|
+
let calls = 0;
|
|
14
|
+
client.evaluate = async () => { calls++; return { loginRequired: true }; };
|
|
15
|
+
await strict_1.default.rejects(() => client.createTask(), /尚未登录.*桌面端登录/);
|
|
16
|
+
strict_1.default.equal(calls, 1);
|
|
17
|
+
});
|
|
11
18
|
const node_vm_1 = require("node:vm");
|
|
12
19
|
const workbuddy_1 = require("../src-ts/plugins/workbuddy");
|
|
13
20
|
const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
|
package/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"object","properties":{"sshHost":{"type":"string","description":"Windows 主机地址"},"sshUser":{"type":"string","description":"SSH 用户名"},"sshPort":{"type":"integer","minimum":1,"maximum":65535,"default":22},"sshKeyPath":{"type":"string","description":"SSH 私钥路径"},"remoteCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"localCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"timeoutMs":{"type":"integer","minimum":1,"default":30000}},"additionalProperties":false}
|
|
1
|
+
{"type":"object","properties":{"sshHost":{"type":"string","description":"Windows 主机地址"},"sshUser":{"type":"string","description":"SSH 用户名"},"sshPort":{"type":"integer","minimum":1,"maximum":65535,"default":22},"sshKeyPath":{"type":"string","description":"SSH 私钥路径"},"remoteCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"localCdpPort":{"type":"integer","minimum":1,"maximum":65535,"default":9230},"executablePath":{"type":"string","description":"ChatGPT.exe 路径,省略时自动查找 Windows Store 安装版本"},"autoStart":{"type":"boolean","default":true,"description":"本机 CDP 未连接时自动启动 ChatGPT;配置 SSH 时不在 bridge 主机启动"},"startupTimeoutMs":{"type":"integer","minimum":1000,"default":30000},"timeoutMs":{"type":"integer","minimum":1,"default":30000}},"additionalProperties":false}
|
|
@@ -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);
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findWindowsExecutable = findWindowsExecutable;
|
|
4
|
+
exports.findExecutableInTree = findExecutableInTree;
|
|
5
|
+
exports.launchAndWait = launchAndWait;
|
|
6
|
+
exports.launchManagedAndWait = launchManagedAndWait;
|
|
7
|
+
exports.restartOwnedAndWait = restartOwnedAndWait;
|
|
8
|
+
exports.terminateWindowsDesktop = terminateWindowsDesktop;
|
|
9
|
+
exports.terminateOwnedProcess = terminateOwnedProcess;
|
|
10
|
+
exports.findChatgptExecutable = findChatgptExecutable;
|
|
11
|
+
exports.waitFor = waitFor;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const promises_1 = require("node:fs/promises");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
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);
|
|
18
|
+
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
19
|
+
/** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
|
|
20
|
+
async function findWindowsExecutable(fileName, configured) {
|
|
21
|
+
if (configured?.trim()) {
|
|
22
|
+
const configuredPath = (0, node_path_1.resolve)(configured);
|
|
23
|
+
if (fileName.toLowerCase() === "doubao.exe") {
|
|
24
|
+
let root = (0, node_path_1.dirname)(configuredPath);
|
|
25
|
+
while (/^app$/i.test((0, node_path_1.basename)(root)) || versionDirectory.test((0, node_path_1.basename)(root)))
|
|
26
|
+
root = (0, node_path_1.dirname)(root);
|
|
27
|
+
const latest = await findExecutableInTree(root, fileName, /^/, 3);
|
|
28
|
+
if (latest)
|
|
29
|
+
return latest;
|
|
30
|
+
}
|
|
31
|
+
// 千问升级会保留或删除旧版本目录,从同一安装位置重新定位新版。
|
|
32
|
+
if (fileName.toLowerCase() === "qwenworkcn.exe" && /^v?\d+(?:[.-]\d+)+$/i.test((0, node_path_1.basename)((0, node_path_1.dirname)(configuredPath)))) {
|
|
33
|
+
const latest = await findExecutableInTree((0, node_path_1.dirname)((0, node_path_1.dirname)(configuredPath)), fileName, /^/, 1);
|
|
34
|
+
if (latest)
|
|
35
|
+
return latest;
|
|
36
|
+
}
|
|
37
|
+
if ((0, node_fs_1.existsSync)(configuredPath))
|
|
38
|
+
return configuredPath;
|
|
39
|
+
}
|
|
40
|
+
if (process.platform !== "win32")
|
|
41
|
+
return undefined;
|
|
42
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh/i;
|
|
43
|
+
const roots = [...new Set([
|
|
44
|
+
process.env.DSH_INSTALL_DIR,
|
|
45
|
+
"D:\\Programs\\DeepSeek Harness",
|
|
46
|
+
"D:\\DSH",
|
|
47
|
+
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
48
|
+
process.env.ProgramFiles,
|
|
49
|
+
process.env["ProgramFiles(x86)"],
|
|
50
|
+
...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
|
|
51
|
+
].filter((value) => typeof value === "string" && (0, node_fs_1.existsSync)(value)))];
|
|
52
|
+
for (const root of roots) {
|
|
53
|
+
const found = await findExecutableInTree(root, fileName, pattern, 6);
|
|
54
|
+
if (found)
|
|
55
|
+
return found;
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
async function findExecutableInTree(directory, fileName, pattern, depth) {
|
|
60
|
+
return search(directory, fileName, pattern, depth, pattern.test(directory));
|
|
61
|
+
}
|
|
62
|
+
async function search(directory, fileName, pattern, depth, insideProduct) {
|
|
63
|
+
try {
|
|
64
|
+
const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
|
|
65
|
+
// 豆包上层文件是启动器,优先查找新版目录或 app 中的实际程序。
|
|
66
|
+
if (insideProduct && depth > 0 && fileName.toLowerCase() === "doubao.exe") {
|
|
67
|
+
const versions = entries.filter(entry => entry.isDirectory() && (versionDirectory.test(entry.name) || /^app$/i.test(entry.name)));
|
|
68
|
+
versions.sort((left, right) => right.name.replace(/^\D+/, "").localeCompare(left.name.replace(/^\D+/, ""), "en", { numeric: true }));
|
|
69
|
+
for (const entry of versions) {
|
|
70
|
+
const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, true);
|
|
71
|
+
if (found)
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const entry of entries)
|
|
76
|
+
if (entry.isFile() && insideProduct && entry.name.toLowerCase() === fileName.toLowerCase())
|
|
77
|
+
return (0, node_path_1.join)(directory, entry.name);
|
|
78
|
+
if (depth <= 0)
|
|
79
|
+
return undefined;
|
|
80
|
+
// 按数字比较版本目录,避免 1.2.5 排在 1.2.10 之前。
|
|
81
|
+
entries.sort((left, right) => right.name.localeCompare(left.name, "en", { numeric: true }));
|
|
82
|
+
for (const entry of entries)
|
|
83
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && (insideProduct || pattern.test(entry.name) || /^(?:Program Files(?: \(x86\))?|Programs)$/i.test(entry.name))) {
|
|
84
|
+
const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, insideProduct || pattern.test(entry.name));
|
|
85
|
+
if (found)
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
catch { /* 无权限或目录在扫描期间消失时继续查找 */ }
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
async function launchAndWait(executable, args, connect, timeoutMs, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
93
|
+
const child = launch(executable, args);
|
|
94
|
+
child.unref();
|
|
95
|
+
return waitFor(connect, timeoutMs, 250);
|
|
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
|
+
}
|
|
178
|
+
async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
179
|
+
const deadline = Date.now() + timeoutMs;
|
|
180
|
+
let last;
|
|
181
|
+
while (Date.now() < deadline) {
|
|
182
|
+
try {
|
|
183
|
+
return await action();
|
|
184
|
+
}
|
|
185
|
+
catch (error) {
|
|
186
|
+
last = error;
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
throw last instanceof Error ? last : new Error("桌面程序启动超时");
|
|
191
|
+
}
|
|
@@ -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
|
}
|