mu-agent-bridge 1.0.73 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -8
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +7 -28
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +7 -2
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-manager.js +185 -58
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +89 -11
- package/dist/src-ts/plugins/aun/messages.js +1 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/dist/src-ts/plugins/doubao/index.js +119 -7
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +102 -23
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +27 -8
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-model-config.test.js +44 -41
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +14 -0
- package/dist/tests-ts/aun-plugin.test.js +207 -19
- package/dist/tests-ts/bundled-package.test.js +1 -1
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/chatgpt-plugin.test.js +19 -0
- package/dist/tests-ts/control-server.test.js +20 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +688 -19
- package/dist/tests-ts/dsh-agent.test.js +91 -19
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/local-cli.test.js +29 -2
- package/dist/tests-ts/model-provider-cancellation.test.js +2 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +16 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +7 -1
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant.test.js +277 -3
- package/dist/tests-ts/platform-integration.test.js +60 -1
- package/dist/tests-ts/plugin-manager.test.js +78 -6
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +1 -1
- package/dist/tests-ts/qwenwork-plugin.test.js +30 -0
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +8 -8
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +356 -13
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +8 -6
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +7 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -3
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +89 -11
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +3 -2
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +1 -1
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +1 -1
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +231 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +119 -7
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/dsh-agent.js +7 -2
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +102 -23
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +3 -2
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +29 -8
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +20 -6
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +4 -3
- package/plugins/personal-assistant/package.json +2 -2
- package/plugins/personal-assistant/plugin.json +1 -1
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +43 -7
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +52 -18
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +349 -83
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +24 -12
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -256,15 +256,45 @@ class QwenCdpClient {
|
|
|
256
256
|
const addedIds = attachments?.length ? await this.injectAttachments(attachments) : [];
|
|
257
257
|
stage = "向千问办公提交任务(尚未确认发送)";
|
|
258
258
|
code = "QWEN_SUBMIT_FAILED";
|
|
259
|
+
let submitUrl = "";
|
|
260
|
+
let inserted = false;
|
|
261
|
+
let submitAttempted = false;
|
|
259
262
|
try {
|
|
263
|
+
submitUrl = String(await this.evaluate("location.href"));
|
|
264
|
+
const readyToSubmit = `(() => {
|
|
265
|
+
const input = document.querySelector(${JSON.stringify(exports.inputSelector)});
|
|
266
|
+
const button = document.querySelector('button[aria-label="发送消息"]');
|
|
267
|
+
const generating = document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]');
|
|
268
|
+
return Boolean(!document.hidden && location.href === ${JSON.stringify(submitUrl)} && input && input.innerText.trim() === ${JSON.stringify(text.trim())}
|
|
269
|
+
&& button && !button.disabled && button.getAttribute('aria-disabled') !== 'true' && !generating);
|
|
270
|
+
})()`;
|
|
260
271
|
await this.cdp("Input.insertText", { text });
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
272
|
+
inserted = true;
|
|
273
|
+
// 只有复查确认尚未点击时才再试一次,不能重发提交结果未知的任务。
|
|
274
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
275
|
+
await this.waitFor(readyToSubmit, "千问发送按钮未就绪或输入校验失败,未发送消息");
|
|
276
|
+
submitAttempted = true;
|
|
277
|
+
const sent = await this.evaluate(`(() => {
|
|
278
|
+
if (!${readyToSubmit}) return false;
|
|
279
|
+
document.querySelector('button[aria-label="发送消息"]').click();
|
|
280
|
+
return true;
|
|
281
|
+
})()`);
|
|
282
|
+
if (sent === true)
|
|
283
|
+
break;
|
|
284
|
+
submitAttempted = false;
|
|
285
|
+
if (attempt === 1)
|
|
286
|
+
throw new Error("千问发送按钮未就绪或输入校验失败,未发送消息");
|
|
287
|
+
}
|
|
264
288
|
await this.waitFor(`(() => { const input = document.querySelector(${JSON.stringify(exports.inputSelector)}); return Boolean(input && !input.innerText.trim()); })()`, "千问未确认消息已提交");
|
|
265
289
|
}
|
|
266
290
|
catch (error) {
|
|
267
291
|
// CDP 断开时清理也可能失败,保留最初的发送错误。
|
|
292
|
+
if (inserted && !submitAttempted) {
|
|
293
|
+
try {
|
|
294
|
+
await this.recoverDraft({ text, url: submitUrl });
|
|
295
|
+
}
|
|
296
|
+
catch { /* 清理失败不能覆盖原错。 */ }
|
|
297
|
+
}
|
|
268
298
|
try {
|
|
269
299
|
await this.cleanupAttachments(addedIds);
|
|
270
300
|
}
|
|
@@ -283,6 +313,7 @@ class QwenCdpClient {
|
|
|
283
313
|
while (Date.now() < deadline || signal?.aborted) {
|
|
284
314
|
// 上游取消不停止桌面生成;确认结束前保留插件队列。
|
|
285
315
|
await wait(2_000);
|
|
316
|
+
await this.restoreVisible();
|
|
286
317
|
const messagesJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.assistantMessageSelector)})].map(element => ({ id: element.getAttribute('data-message-id'), text: element.innerText })))`);
|
|
287
318
|
const messages = JSON.parse(messagesJson || "[]");
|
|
288
319
|
const fresh = freshAssistantMessages(beforeIds, messages);
|
|
@@ -546,9 +577,8 @@ class QwenCdpClient {
|
|
|
546
577
|
try {
|
|
547
578
|
await this.evaluate(`(async () => {
|
|
548
579
|
if (document.querySelector('button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]')) throw new Error('千问桌面端正在生成回复');
|
|
549
|
-
// 隐藏窗口会暂停消息渲染,使桌面端的提交前检查一直等不到确认。
|
|
550
|
-
if (document.hidden && typeof window.desktopApi?.focusMainWindow === 'function') await window.desktopApi.focusMainWindow();
|
|
551
580
|
})()`);
|
|
581
|
+
await this.restoreVisible();
|
|
552
582
|
}
|
|
553
583
|
catch (error) {
|
|
554
584
|
let permission;
|
|
@@ -561,8 +591,13 @@ class QwenCdpClient {
|
|
|
561
591
|
throw error;
|
|
562
592
|
}
|
|
563
593
|
}
|
|
564
|
-
|
|
565
|
-
|
|
594
|
+
async restoreVisible() {
|
|
595
|
+
await this.evaluate(`(async () => {
|
|
596
|
+
if (document.hidden && typeof window.desktopApi?.focusMainWindow === 'function') await window.desktopApi.focusMainWindow();
|
|
597
|
+
})()`);
|
|
598
|
+
}
|
|
599
|
+
recoverDraft(expected) {
|
|
600
|
+
return (0, cdp_draft_1.recoverCdpDraft)(this.evaluate.bind(this), exports.inputSelector, 'button[aria-label="停止"],button[aria-label="停止生成"],button[aria-label="Stop generating"]', this.options.dataDir, expected);
|
|
566
601
|
}
|
|
567
602
|
async cdp(method, params = {}, timeoutMs = this.options.timeoutMs) {
|
|
568
603
|
if (!this.socket)
|
|
@@ -624,6 +659,7 @@ class QwenCdpClient {
|
|
|
624
659
|
async waitFor(expression, message) {
|
|
625
660
|
const deadline = Date.now() + this.options.timeoutMs;
|
|
626
661
|
while (Date.now() < deadline) {
|
|
662
|
+
await this.restoreVisible();
|
|
627
663
|
if (await this.evaluate(expression))
|
|
628
664
|
return;
|
|
629
665
|
await wait(200);
|
|
@@ -12,7 +12,7 @@ const desktop_launch_1 = require("../desktop-launch");
|
|
|
12
12
|
const session_history_1 = require("./session-history");
|
|
13
13
|
exports.qwenworkManifest = {
|
|
14
14
|
id: "qwenwork",
|
|
15
|
-
version: "1.0.
|
|
15
|
+
version: "1.0.28",
|
|
16
16
|
displayName: "千问办公",
|
|
17
17
|
entry: "dist/index.js",
|
|
18
18
|
apiVersion: "1",
|
|
@@ -52,6 +52,7 @@ class QwenworkPlugin {
|
|
|
52
52
|
state = "stopped";
|
|
53
53
|
message = "";
|
|
54
54
|
healthIntervalMs;
|
|
55
|
+
restartDesktopHook;
|
|
55
56
|
healthTimer;
|
|
56
57
|
recovery;
|
|
57
58
|
context;
|
|
@@ -70,6 +71,7 @@ class QwenworkPlugin {
|
|
|
70
71
|
this.configuredCdpPort = options.cdpPort;
|
|
71
72
|
this.configuredHttpPort = options.httpPort;
|
|
72
73
|
this.healthIntervalMs = options.healthIntervalMs ?? 30_000;
|
|
74
|
+
this.restartDesktopHook = options.restartDesktop;
|
|
73
75
|
}
|
|
74
76
|
async activate(context) {
|
|
75
77
|
if (this.state === "ready")
|
|
@@ -133,6 +135,7 @@ class QwenworkPlugin {
|
|
|
133
135
|
if (!executable)
|
|
134
136
|
throw new Error("未找到 QwenWorkCN.exe,请配置 executablePath");
|
|
135
137
|
context.logger?.info(`正在启动千问办公,等待 CDP ${this.activeCdpPort}`, { executablePath: executable });
|
|
138
|
+
await this.restartDesktopProcess(executable);
|
|
136
139
|
try {
|
|
137
140
|
await (0, desktop_launch_1.launchAndWait)(executable, [`--remote-debugging-port=${this.activeCdpPort}`], () => client.connect(), typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000);
|
|
138
141
|
}
|
|
@@ -167,8 +170,15 @@ class QwenworkPlugin {
|
|
|
167
170
|
throw error;
|
|
168
171
|
}
|
|
169
172
|
}
|
|
170
|
-
async deactivate(
|
|
173
|
+
async deactivate(reason, options) {
|
|
174
|
+
const reloadDesktop = !options?.preserveDesktop && reason === "reload" && Boolean(this.client) && !this.relayTarget;
|
|
171
175
|
await this.cleanup();
|
|
176
|
+
if (reloadDesktop) {
|
|
177
|
+
if (this.restartDesktopHook)
|
|
178
|
+
await this.restartDesktopHook();
|
|
179
|
+
else if (!this.injectedClient && process.platform === "win32")
|
|
180
|
+
await this.restartDesktopProcess();
|
|
181
|
+
}
|
|
172
182
|
this.state = "stopped";
|
|
173
183
|
this.stopped = true;
|
|
174
184
|
}
|
|
@@ -181,8 +191,6 @@ class QwenworkPlugin {
|
|
|
181
191
|
return { status: this.state, ...(this.message ? { message: this.message } : {}), details };
|
|
182
192
|
}
|
|
183
193
|
async handle(request) {
|
|
184
|
-
if (this.recovery)
|
|
185
|
-
await this.recovery;
|
|
186
194
|
if (this.state === "stopped" || (!this.relayTarget && !this.client))
|
|
187
195
|
throw new Error("qwenwork 插件未就绪");
|
|
188
196
|
return this.enqueueTask(() => this.completeRequest(request));
|
|
@@ -197,9 +205,8 @@ class QwenworkPlugin {
|
|
|
197
205
|
throw new Error("千问办公当前忙,无法创建新会话");
|
|
198
206
|
if (!this.client?.createSession)
|
|
199
207
|
throw new Error("千问办公当前传输不支持新建会话");
|
|
200
|
-
if (!this.client.connected())
|
|
201
|
-
throw new Error("千问办公 CDP 未连接");
|
|
202
208
|
return this.enqueueTask(async () => {
|
|
209
|
+
await this.ensureConnected();
|
|
203
210
|
await this.client.createSession(request.name);
|
|
204
211
|
return { text: "千问办公已创建新会话", sessionId };
|
|
205
212
|
});
|
|
@@ -212,8 +219,7 @@ class QwenworkPlugin {
|
|
|
212
219
|
if (!this.client)
|
|
213
220
|
throw new Error("qwenwork 插件未就绪");
|
|
214
221
|
try {
|
|
215
|
-
|
|
216
|
-
throw new Error("千问 CDP 已断开,等待空闲后自动恢复");
|
|
222
|
+
await this.ensureConnected(signal);
|
|
217
223
|
const reply = this.client.complete ? (await this.client.complete(prompt, timeoutMs, signal, undefined, undefined, onMessage)).text : await this.client.direct(prompt, timeoutMs, signal);
|
|
218
224
|
if ((0, cdp_client_1.isQwenApiUnavailableReply)(reply))
|
|
219
225
|
throw new agent_1.AgentOperationError("QWEN_REMOTE_ERROR", "等待千问办公回复", new Error("千问生成接口不可用;桌面未提供具体服务端错误码,CDP 仍连接,未重启或重新提交任务"));
|
|
@@ -223,7 +229,7 @@ class QwenworkPlugin {
|
|
|
223
229
|
return reply;
|
|
224
230
|
}
|
|
225
231
|
catch (error) {
|
|
226
|
-
if (!signal?.aborted) {
|
|
232
|
+
if (!this.stopped && !signal?.aborted) {
|
|
227
233
|
this.message = error instanceof Error ? error.message : String(error);
|
|
228
234
|
this.failures = this.client.connected() ? 0 : 2;
|
|
229
235
|
this.state = "degraded";
|
|
@@ -238,8 +244,10 @@ class QwenworkPlugin {
|
|
|
238
244
|
if (!this.client)
|
|
239
245
|
throw new Error("qwenwork 插件未就绪");
|
|
240
246
|
try {
|
|
241
|
-
if (!this.client.connected())
|
|
242
|
-
throw new Error("千问 CDP
|
|
247
|
+
if (modelRequest && !this.client.connected())
|
|
248
|
+
throw new Error("千问 CDP 已断开,模型请求不会启动或重启桌面");
|
|
249
|
+
if (!modelRequest)
|
|
250
|
+
await this.ensureConnected(request.signal);
|
|
243
251
|
if (request.attachments?.length && (!this.client.attachmentInput || !this.client.complete))
|
|
244
252
|
throw new Error("千问当前客户端不支持附件输入");
|
|
245
253
|
const timeoutMs = request.timeoutMs ?? asNumber(this.context?.config.timeoutMs, 240_000);
|
|
@@ -254,7 +262,7 @@ class QwenworkPlugin {
|
|
|
254
262
|
return response;
|
|
255
263
|
}
|
|
256
264
|
catch (error) {
|
|
257
|
-
if (!request.signal?.aborted && !modelRequest) {
|
|
265
|
+
if (!this.stopped && !request.signal?.aborted && !modelRequest) {
|
|
258
266
|
this.message = error instanceof Error ? error.message : String(error);
|
|
259
267
|
this.failures = this.client.connected() ? 0 : 2;
|
|
260
268
|
this.state = "degraded";
|
|
@@ -343,6 +351,14 @@ class QwenworkPlugin {
|
|
|
343
351
|
this.callPlugin = undefined;
|
|
344
352
|
this.pluginStatus = undefined;
|
|
345
353
|
}
|
|
354
|
+
async restartDesktopProcess(configuredExecutable) {
|
|
355
|
+
const config = this.context?.config;
|
|
356
|
+
const executable = configuredExecutable ?? await (0, desktop_launch_1.findWindowsExecutable)("QwenWorkCN.exe", typeof config?.executablePath === "string" ? config.executablePath : undefined);
|
|
357
|
+
if (!executable)
|
|
358
|
+
throw new Error("未找到 QwenWorkCN.exe,请配置 executablePath");
|
|
359
|
+
const processIds = await (0, desktop_launch_1.terminateWindowsDesktop)(executable);
|
|
360
|
+
this.context?.logger?.info("千问办公重载前已清理旧进程,将以 CDP 模式启动", { processIds, executable });
|
|
361
|
+
}
|
|
346
362
|
startHealthWatch() {
|
|
347
363
|
if (this.relayTarget || !this.client || this.healthTimer)
|
|
348
364
|
return;
|
|
@@ -365,9 +381,26 @@ class QwenworkPlugin {
|
|
|
365
381
|
});
|
|
366
382
|
}
|
|
367
383
|
async recover() {
|
|
384
|
+
if (this.stopped || this.busy)
|
|
385
|
+
return;
|
|
386
|
+
try {
|
|
387
|
+
await this.reconnect();
|
|
388
|
+
}
|
|
389
|
+
catch { /* 失败已记录,留待下次空闲检查。 */ }
|
|
390
|
+
}
|
|
391
|
+
async ensureConnected(signal) {
|
|
392
|
+
signal?.throwIfAborted();
|
|
393
|
+
if (this.stopped || !this.client)
|
|
394
|
+
throw new Error("qwenwork 插件未就绪");
|
|
395
|
+
await this.reconnect();
|
|
396
|
+
signal?.throwIfAborted();
|
|
397
|
+
if (this.stopped)
|
|
398
|
+
throw new Error("qwenwork 插件未就绪");
|
|
399
|
+
}
|
|
400
|
+
async reconnect() {
|
|
368
401
|
if (this.recovery)
|
|
369
402
|
return this.recovery;
|
|
370
|
-
if (this.stopped
|
|
403
|
+
if (this.stopped)
|
|
371
404
|
return;
|
|
372
405
|
const client = this.client;
|
|
373
406
|
if (!client || client.connected())
|
|
@@ -388,11 +421,12 @@ class QwenworkPlugin {
|
|
|
388
421
|
this.restarts += 1;
|
|
389
422
|
this.context?.logger?.info("千问 CDP 自动重连成功", { restarts: this.restarts });
|
|
390
423
|
})().catch((error) => {
|
|
391
|
-
if (this.stopped)
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
424
|
+
if (!this.stopped) {
|
|
425
|
+
this.state = "failed";
|
|
426
|
+
this.message = error instanceof Error ? error.message : String(error);
|
|
427
|
+
this.context?.logger?.error(`千问 CDP 重连失败,桌面未重启,下次空闲检查重试:${this.message}`);
|
|
428
|
+
}
|
|
429
|
+
throw error;
|
|
396
430
|
}).finally(() => { this.recovery = undefined; });
|
|
397
431
|
return this.recovery;
|
|
398
432
|
}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"cdpPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": 9229 },
|
|
10
10
|
"autoStart": { "type": "boolean", "default": true, "description": "未连接时自动启动 WorkBuddy" },
|
|
11
11
|
"startupTimeoutMs": { "type": "integer", "minimum": 1000, "default": 30000 },
|
|
12
|
-
"timeoutMs": { "type": "integer", "minimum": 1, "default":
|
|
12
|
+
"timeoutMs": { "type": "integer", "minimum": 1, "default": 1200000, "description": "单次回复等待时间(毫秒),最多二十分钟;较短配置继续生效,超时不终止桌面任务" },
|
|
13
13
|
"dailyPoints": { "type": "boolean", "default": true, "description": "每天自动检查并领取页面明确提供的免费积分" },
|
|
14
14
|
"dailyPointsHour": { "type": "integer", "minimum": 0, "maximum": 23, "default": 9, "description": "每日免费积分领取时间(本机小时)" },
|
|
15
15
|
"dailyPointsMinute": { "type": "integer", "minimum": 0, "maximum": 59, "default": 0, "description": "每日免费积分领取时间(本机分钟)" }
|
|
@@ -16,8 +16,8 @@ const node_path_1 = require("node:path");
|
|
|
16
16
|
const node_util_1 = require("node:util");
|
|
17
17
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
18
|
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
19
|
-
/**
|
|
20
|
-
async function findWindowsExecutable(fileName, configured) {
|
|
19
|
+
/** 先从 Windows 注册信息和开始菜单解析,失败时再查常见安装目录。 */
|
|
20
|
+
async function findWindowsExecutable(fileName, configured, run = execFileAsync) {
|
|
21
21
|
if (configured?.trim()) {
|
|
22
22
|
const configuredPath = (0, node_path_1.resolve)(configured);
|
|
23
23
|
if (fileName.toLowerCase() === "doubao.exe") {
|
|
@@ -42,12 +42,16 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
42
42
|
}
|
|
43
43
|
if (process.platform !== "win32")
|
|
44
44
|
return undefined;
|
|
45
|
-
const
|
|
45
|
+
const registered = await findRegisteredWindowsExecutable(fileName, run);
|
|
46
|
+
if (registered)
|
|
47
|
+
return registered;
|
|
48
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh|zcode/i;
|
|
46
49
|
const roots = [...new Set([
|
|
47
50
|
process.env.DSH_INSTALL_DIR,
|
|
48
51
|
"D:\\Programs\\DeepSeek Harness",
|
|
49
52
|
"D:\\DSH",
|
|
50
53
|
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
54
|
+
process.env.LOCALAPPDATA,
|
|
51
55
|
process.env.ProgramFiles,
|
|
52
56
|
process.env["ProgramFiles(x86)"],
|
|
53
57
|
...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
|
|
@@ -59,6 +63,36 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
59
63
|
}
|
|
60
64
|
return undefined;
|
|
61
65
|
}
|
|
66
|
+
async function findRegisteredWindowsExecutable(fileName, run) {
|
|
67
|
+
const target = fileName.replace(/'/g, "''");
|
|
68
|
+
const script = `$ErrorActionPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$target='${target}'
|
|
69
|
+
function Emit-Path($value){if(-not $value){return};$value=[Environment]::ExpandEnvironmentVariables(([string]$value).Trim());if($value.StartsWith('"')){$end=$value.IndexOf('"',1);if($end -gt 1){$value=$value.Substring(1,$end-1)}}elseif($value -match '^(\S+?\.exe)(?:\s|$)'){$value=$Matches[1]};if($value -match '(?i)\.exe$'){[Console]::WriteLine($value)}}
|
|
70
|
+
$keys=@("HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKCU:\\Software\\Classes\\Applications\\$target\\shell\\open\\command","HKLM:\\Software\\Classes\\Applications\\$target\\shell\\open\\command")
|
|
71
|
+
foreach($key in $keys){$item=Get-Item $key -ErrorAction SilentlyContinue;if($item){Emit-Path $item.GetValue('')}}
|
|
72
|
+
$dirs=@();if($env:APPDATA){$dirs+=[IO.Path]::Combine($env:APPDATA,'Microsoft\\Windows\\Start Menu\\Programs')};if($env:ProgramData){$dirs+=[IO.Path]::Combine($env:ProgramData,'Microsoft\\Windows\\Start Menu\\Programs')};$shell=New-Object -ComObject WScript.Shell
|
|
73
|
+
foreach($dir in $dirs){Get-ChildItem $dir -Filter '*.lnk' -File -Recurse -ErrorAction SilentlyContinue|ForEach-Object{$link=$shell.CreateShortcut($_.FullName);if($link.TargetPath -and [IO.Path]::GetFileName($link.TargetPath) -ieq $target){Emit-Path $link.TargetPath}}}`;
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 10_000, windowsHide: true });
|
|
76
|
+
for (const candidate of stdout.split(/\r?\n/).map(value => value.trim()).filter(Boolean)) {
|
|
77
|
+
const found = await resolveRegisteredCandidate(fileName, candidate);
|
|
78
|
+
if (found)
|
|
79
|
+
return found;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { /* 注册表或开始菜单不可读时继续使用目录兜底 */ }
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
async function resolveRegisteredCandidate(fileName, candidate) {
|
|
86
|
+
const candidatePath = (0, node_path_1.resolve)(candidate.replace(/^"|"$/g, ""));
|
|
87
|
+
if (fileName.toLowerCase() === "doubao.exe" && (0, node_path_1.basename)((0, node_path_1.dirname)(candidatePath)).toLowerCase() === "application") {
|
|
88
|
+
const client = (0, node_path_1.join)((0, node_path_1.dirname)(candidatePath), "app", fileName);
|
|
89
|
+
if ((0, node_fs_1.existsSync)(client))
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
if ((0, node_fs_1.existsSync)(candidatePath) && (0, node_path_1.basename)(candidatePath).toLowerCase() === fileName.toLowerCase())
|
|
93
|
+
return candidatePath;
|
|
94
|
+
return findExecutableInTree((0, node_path_1.dirname)(candidatePath), fileName, /^/, 3);
|
|
95
|
+
}
|
|
62
96
|
async function findExecutableInTree(directory, fileName, pattern, depth) {
|
|
63
97
|
return search(directory, fileName, pattern, depth, pattern.test(directory));
|
|
64
98
|
}
|
|
@@ -104,9 +138,16 @@ async function launchManagedAndWait(executable, args, profileDirectory, connect,
|
|
|
104
138
|
if (profileDirectory)
|
|
105
139
|
await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
|
|
106
140
|
const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
|
|
141
|
+
// 先等待 spawn,避免异步 error 终止 daemon 或在启动失败后继续轮询。
|
|
142
|
+
const events = child;
|
|
143
|
+
const started = typeof events.once === "function" ? new Promise((resolve, reject) => {
|
|
144
|
+
events.once("error", reject);
|
|
145
|
+
events.once("spawn", resolve);
|
|
146
|
+
}) : Promise.resolve();
|
|
107
147
|
child.unref();
|
|
108
148
|
onProcess(child);
|
|
109
149
|
try {
|
|
150
|
+
await started;
|
|
110
151
|
return await waitFor(connect, timeoutMs, 250);
|
|
111
152
|
}
|
|
112
153
|
catch (error) {
|