mu-agent-bridge 1.0.60 → 1.0.63
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.js +25 -1
- package/dist/src-ts/control-server.js +3 -3
- package/dist/src-ts/daemon-process.js +8 -1
- package/dist/src-ts/plugins/aun/attachments.js +5 -2
- package/dist/src-ts/plugins/aun/index.js +27 -13
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +83 -57
- package/dist/src-ts/plugins/qwenwork/index.js +12 -40
- package/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +90 -70
- package/dist/src-ts/plugins/workbuddy/index.js +68 -60
- package/dist/tests-ts/aun-plugin.test.js +25 -3
- package/dist/tests-ts/daemon-process.test.js +48 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +63 -51
- package/dist/tests-ts/qwenwork-sse.test.js +70 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +53 -2
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +25 -1
- package/plugins/aun/dist/src-ts/agent.js +25 -1
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +5 -2
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +27 -13
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/agent.js +25 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +25 -1
- package/plugins/codex-cli/dist/src-ts/agent.js +25 -1
- package/plugins/doubao/dist/src-ts/agent.js +25 -1
- package/plugins/dsh/dist/src-ts/agent.js +25 -1
- package/plugins/dsh/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
- package/plugins/feishu/dist/src-ts/agent.js +25 -1
- package/plugins/qwenwork/dist/src-ts/agent.js +25 -1
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +83 -57
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +12 -40
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +25 -1
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +90 -70
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +68 -60
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WorkbuddyCdpClient = exports.chineseLocaleScript = exports.assistantSelector = exports.editorSelector = void 0;
|
|
4
|
+
const agent_1 = require("../../agent");
|
|
4
5
|
const node_crypto_1 = require("node:crypto");
|
|
5
6
|
const promises_1 = require("node:fs/promises");
|
|
6
7
|
const node_url_1 = require("node:url");
|
|
@@ -39,7 +40,7 @@ class WorkbuddyCdpClient {
|
|
|
39
40
|
timeoutMs;
|
|
40
41
|
fetchImpl;
|
|
41
42
|
constructor(options = {}) { this.port = options.port ?? Number(process.env.WORKBUDDY_CDP_PORT ?? "9229"); this.timeoutMs = options.timeoutMs ?? 30_000; this.fetchImpl = options.fetchImpl ?? fetch; }
|
|
42
|
-
connected() { return
|
|
43
|
+
connected() { return this.socket?.readyState === WebSocket.OPEN; }
|
|
43
44
|
pendingInteractionSessionId() { return this.pendingInteraction?.sessionId; }
|
|
44
45
|
async conversationId() {
|
|
45
46
|
const value = await this.evaluate(`([...document.querySelectorAll('[data-conversation-id]')].find(item => String(item.className).includes('_selected_')))?.getAttribute('data-conversation-id')`);
|
|
@@ -95,8 +96,9 @@ class WorkbuddyCdpClient {
|
|
|
95
96
|
}))()`);
|
|
96
97
|
}
|
|
97
98
|
async connect() {
|
|
98
|
-
if (this.
|
|
99
|
+
if (this.connected())
|
|
99
100
|
return;
|
|
101
|
+
this.close();
|
|
100
102
|
const response = await this.fetchImpl(`http://127.0.0.1:${this.port}/json`);
|
|
101
103
|
if (!response.ok)
|
|
102
104
|
throw new Error(`WorkBuddy CDP /json HTTP ${response.status}`);
|
|
@@ -106,6 +108,9 @@ class WorkbuddyCdpClient {
|
|
|
106
108
|
throw new Error(`未找到 WorkBuddy 页面 target(端口 ${this.port})`);
|
|
107
109
|
this.socket = new WebSocket(page.webSocketDebuggerUrl);
|
|
108
110
|
this.socket.onmessage = (event) => this.onMessage(String(event.data));
|
|
111
|
+
const socket = this.socket;
|
|
112
|
+
socket.onclose = () => { if (this.socket === socket)
|
|
113
|
+
this.close(); };
|
|
109
114
|
try {
|
|
110
115
|
await new Promise((resolve, reject) => { const socket = this.socket; socket.onopen = () => resolve(); socket.onerror = () => reject(new Error("WorkBuddy CDP WebSocket 连接失败")); });
|
|
111
116
|
await this.evaluate(exports.chineseLocaleScript);
|
|
@@ -122,10 +127,7 @@ class WorkbuddyCdpClient {
|
|
|
122
127
|
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 }; })()`);
|
|
123
128
|
if (before.loginRequired)
|
|
124
129
|
throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
|
|
125
|
-
|
|
126
|
-
await this.waitFor(taskButton, "WorkBuddy 新建任务入口未找到或不可用");
|
|
127
|
-
if (!await this.evaluate(`(() => { const button = [...document.querySelectorAll('button,[role="button"],[role="tab"]')].find(item => { const label = [item.innerText, item.textContent, item.getAttribute('aria-label'), item.getAttribute('title'), item.getAttribute('data-testid')].filter(Boolean).map(value => String(value).normalize('NFKC').replace(/\\s+/g, ' ').trim().toLowerCase()); return label.some(value => /^(?:新建任务|新建会话|新建对话|开始新任务|new (?:task|chat|conversation))$/.test(value)) && (!item.getClientRects || item.getClientRects().length > 0); }); if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false; button.click(); return true; })()`))
|
|
128
|
-
throw new Error("WorkBuddy 新建任务入口未找到或不可用");
|
|
130
|
+
await this.waitFor(`(() => { const button = [...document.querySelectorAll('button,[role="button"],[role="tab"]')].find(item => { const label = [item.innerText, item.textContent, item.getAttribute('aria-label'), item.getAttribute('title'), item.getAttribute('data-testid')].filter(Boolean).map(value => String(value).normalize('NFKC').replace(/\\s+/g, ' ').trim().toLowerCase()); return label.some(value => /^(?:新建任务|新建会话|新建对话|开始新任务|new (?:task|chat|conversation))$/.test(value)) && (!item.getClientRects || item.getClientRects().length > 0); }); if (!button || button.disabled || button.getAttribute('aria-disabled') === 'true') return false; button.click(); return true; })()`, "WorkBuddy 新建任务入口未找到或不可用");
|
|
129
131
|
// WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
|
|
130
132
|
await this.waitFor(`(() => { const state = ${snapshot}; const blank = state.editor !== null && state.editor.trim() === '' && state.messages === 0; return blank && ((state.conversationId && state.conversationId !== ${JSON.stringify(before.conversationId)}) || state.url !== ${JSON.stringify(before.url)} || (state.home && !state.selected)); })()`, "WorkBuddy 新任务状态校验失败");
|
|
131
133
|
this.pendingInteraction = undefined;
|
|
@@ -175,72 +177,87 @@ class WorkbuddyCdpClient {
|
|
|
175
177
|
return (await this.complete({ task: text, timeoutMs }, expert, newTask)).text;
|
|
176
178
|
}
|
|
177
179
|
async complete(request, expert, newTask = false) {
|
|
178
|
-
|
|
179
|
-
const text = request.task;
|
|
180
|
-
const timeoutMs = request.timeoutMs ?? 3_600_000;
|
|
181
|
-
if (!this.pendingInteraction && request.sessionId && !newTask && !expert) {
|
|
182
|
-
const interaction = await this.readInteraction();
|
|
183
|
-
if (interaction)
|
|
184
|
-
this.pendingInteraction = { ...interaction, sessionId: request.sessionId };
|
|
185
|
-
}
|
|
186
|
-
if (this.pendingInteraction) {
|
|
187
|
-
if (this.pendingInteraction.sessionId && this.pendingInteraction.sessionId !== request.sessionId)
|
|
188
|
-
throw new Error("WorkBuddy 正在等待另一个会话的选择");
|
|
189
|
-
return this.continueInteraction(text, timeoutMs, request.onMessage);
|
|
190
|
-
}
|
|
191
|
-
if (expert)
|
|
192
|
-
await this.selectExpert(expert);
|
|
193
|
-
else if (newTask)
|
|
194
|
-
await this.createTask();
|
|
195
|
-
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "WorkBuddy 输入框未就绪");
|
|
196
|
-
const before = JSON.parse(String(await this.evaluate(`JSON.stringify([...document.querySelectorAll('.cr-agent[data-message-id]')].map(element => ({ id: element.getAttribute('data-message-id') })))`)));
|
|
197
|
-
const beforeIds = new Set(before.map((item) => item.id).filter((id) => Boolean(id)));
|
|
198
|
-
if (!await this.evaluate(`(() => { const element = document.querySelector(${JSON.stringify(exports.editorSelector)}); if (!element) return false; element.focus(); const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(element); selection?.removeAllRanges(); selection?.addRange(range); return true; })()`))
|
|
199
|
-
throw new Error("WorkBuddy 输入框未找到");
|
|
200
|
-
await this.cdp("Input.dispatchKeyEvent", { type: "keyDown", key: "Backspace", code: "Backspace" });
|
|
201
|
-
await this.cdp("Input.dispatchKeyEvent", { type: "keyUp", key: "Backspace", code: "Backspace" });
|
|
202
|
-
await this.cdp("Input.insertText", { text });
|
|
203
|
-
if (!await this.evaluate(`document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() === ${JSON.stringify(text.trim())}`))
|
|
204
|
-
throw new Error("WorkBuddy 输入内容校验失败,未发送消息");
|
|
205
|
-
const temporaryPaths = [];
|
|
206
|
-
let failure;
|
|
180
|
+
let stage = "准备 WorkBuddy 桌面任务";
|
|
207
181
|
try {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
try { return clone.innerText.replace(/[\\uFEFF\\u200B]/g, '').trim() === ${JSON.stringify(text.trim())}; }
|
|
216
|
-
finally { clone.remove(); }
|
|
217
|
-
})()`))
|
|
218
|
-
throw new Error("WorkBuddy 附件上传后的正文校验失败,未发送消息");
|
|
182
|
+
(0, attachments_1.validateAttachments)(request.attachments);
|
|
183
|
+
const text = request.task;
|
|
184
|
+
const timeoutMs = request.timeoutMs ?? 3_600_000;
|
|
185
|
+
if (!this.pendingInteraction && request.sessionId && !newTask && !expert) {
|
|
186
|
+
const interaction = await this.readInteraction();
|
|
187
|
+
if (interaction)
|
|
188
|
+
this.pendingInteraction = { ...interaction, sessionId: request.sessionId };
|
|
219
189
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
190
|
+
if (this.pendingInteraction) {
|
|
191
|
+
if (this.pendingInteraction.sessionId && this.pendingInteraction.sessionId !== request.sessionId)
|
|
192
|
+
throw new Error("WorkBuddy 正在等待另一个会话的选择");
|
|
193
|
+
stage = "继续 WorkBuddy 待确认的任务";
|
|
194
|
+
return await this.continueInteraction(text, timeoutMs, request.onMessage);
|
|
195
|
+
}
|
|
196
|
+
if (expert)
|
|
197
|
+
await this.selectExpert(expert);
|
|
198
|
+
else if (newTask)
|
|
199
|
+
await this.createTask();
|
|
200
|
+
stage = "填写 WorkBuddy 输入框";
|
|
201
|
+
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "WorkBuddy 输入框未就绪");
|
|
202
|
+
const before = JSON.parse(String(await this.evaluate(`JSON.stringify([...document.querySelectorAll('.cr-agent[data-message-id]')].map(element => ({ id: element.getAttribute('data-message-id') })))`)));
|
|
203
|
+
const beforeIds = new Set(before.map((item) => item.id).filter((id) => Boolean(id)));
|
|
204
|
+
if (!await this.evaluate(`(() => { const element = document.querySelector(${JSON.stringify(exports.editorSelector)}); if (!element) return false; element.focus(); const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(element); selection?.removeAllRanges(); selection?.addRange(range); return true; })()`))
|
|
205
|
+
throw new Error("WorkBuddy 输入框未找到");
|
|
206
|
+
await this.cdp("Input.dispatchKeyEvent", { type: "keyDown", key: "Backspace", code: "Backspace" });
|
|
207
|
+
await this.cdp("Input.dispatchKeyEvent", { type: "keyUp", key: "Backspace", code: "Backspace" });
|
|
208
|
+
await this.cdp("Input.insertText", { text });
|
|
209
|
+
if (!await this.evaluate(`document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() === ${JSON.stringify(text.trim())}`))
|
|
210
|
+
throw new Error("WorkBuddy 输入内容校验失败,未发送消息");
|
|
211
|
+
const temporaryPaths = [];
|
|
212
|
+
let failure;
|
|
213
|
+
try {
|
|
214
|
+
if (request.attachments?.length) {
|
|
215
|
+
stage = "向 WorkBuddy 提交入站附件";
|
|
216
|
+
await this.injectAttachments(request.attachments, temporaryPaths);
|
|
217
|
+
if (!await this.evaluate(`(() => {
|
|
218
|
+
const editor = document.querySelector(${JSON.stringify(exports.editorSelector)}); if (!editor) return false;
|
|
219
|
+
const clone = editor.cloneNode(true);
|
|
220
|
+
for (const tag of clone.querySelectorAll('[class*="_fileTag"]')) tag.remove();
|
|
221
|
+
clone.style.position = 'absolute'; clone.style.left = '-100000px'; document.body.appendChild(clone);
|
|
222
|
+
try { return clone.innerText.replace(/[\\uFEFF\\u200B]/g, '').trim() === ${JSON.stringify(text.trim())}; }
|
|
223
|
+
finally { clone.remove(); }
|
|
224
|
+
})()`))
|
|
225
|
+
throw new Error("WorkBuddy 附件上传后的正文校验失败,未发送消息");
|
|
236
226
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
throw
|
|
227
|
+
stage = "向 WorkBuddy 提交任务(尚未确认发送)";
|
|
228
|
+
await this.waitFor(`(() => { const button = document.querySelector('button[aria-label="发送"]'); return Boolean(button && !button.disabled); })()`, "WorkBuddy 发送按钮未就绪");
|
|
229
|
+
if (!await this.evaluate(`(() => { const button = document.querySelector('button[aria-label="发送"]'); if (!button || button.disabled) return false; button.click(); return true; })()`))
|
|
230
|
+
throw new Error("WorkBuddy 发送按钮不可用");
|
|
231
|
+
stage = "等待 WorkBuddy 回复(任务已提交,桌面未被停止)";
|
|
232
|
+
const response = await this.waitForReply(beforeIds, timeoutMs, undefined, undefined, request.sessionId, request.onMessage);
|
|
233
|
+
if (this.pendingInteraction && temporaryPaths.length)
|
|
234
|
+
this.pendingInteractionCleanup = temporaryPaths;
|
|
235
|
+
return response;
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
failure = error;
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
if (temporaryPaths.length && this.pendingInteractionCleanup !== temporaryPaths) {
|
|
243
|
+
try {
|
|
244
|
+
await this.cleanupTemporaryFiles(temporaryPaths);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (failure !== undefined)
|
|
248
|
+
throw new AggregateError([failure, error], `${failure instanceof Error ? failure.message : String(failure)};临时附件清理失败`, { cause: failure });
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
241
251
|
}
|
|
242
252
|
}
|
|
243
253
|
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (error instanceof agent_1.AgentOperationError)
|
|
256
|
+
throw error;
|
|
257
|
+
if (error instanceof AggregateError)
|
|
258
|
+
throw Object.assign(error, { code: "WORKBUDDY_TASK_FAILED", stage });
|
|
259
|
+
throw new agent_1.AgentOperationError("WORKBUDDY_TASK_FAILED", stage, error);
|
|
260
|
+
}
|
|
244
261
|
}
|
|
245
262
|
async continueInteraction(answer, timeoutMs, onMessage) {
|
|
246
263
|
const interaction = this.pendingInteraction;
|
|
@@ -311,7 +328,8 @@ class WorkbuddyCdpClient {
|
|
|
311
328
|
const reply = candidates.map((item) => item.text?.trim()).filter((text) => text && !forwarded.has(text)).join("\n\n");
|
|
312
329
|
const latestComplete = latest && (latest.finished || Boolean(latest.stopped && (stablePolls.get(latest.id) ?? 0) >= 2));
|
|
313
330
|
if (latestComplete && completed.length) {
|
|
314
|
-
const attachments = await this.readArtifacts(new Set(completed.map((item) => item.id)))
|
|
331
|
+
const attachments = await this.readArtifacts(new Set(completed.map((item) => item.id)))
|
|
332
|
+
.catch(error => { throw new agent_1.AgentOperationError("WORKBUDDY_ARTIFACT_READ_FAILED", "读取 WorkBuddy 已完成任务的产物(尚未上传 AUN FS)", error); });
|
|
315
333
|
if (attachments.length)
|
|
316
334
|
return { text: reply ?? "", attachments };
|
|
317
335
|
if (reply || forwarded.size)
|
|
@@ -327,7 +345,7 @@ class WorkbuddyCdpClient {
|
|
|
327
345
|
}
|
|
328
346
|
}
|
|
329
347
|
}
|
|
330
|
-
throw new
|
|
348
|
+
throw new agent_1.AgentOperationError("WORKBUDDY_REPLY_TIMEOUT", "等待 WorkBuddy 回复", new Error(`已等待 ${Math.round(timeoutMs / 1000)} 秒,未确认任务完成;桌面任务未被停止,请勿重复提交`));
|
|
331
349
|
}
|
|
332
350
|
async readInteraction() {
|
|
333
351
|
const raw = await this.evaluate(`JSON.stringify((() => { const nodes = [...document.querySelectorAll(${JSON.stringify(interactionOptionSelector)})]; if (!nodes.length) return null; const root = nodes[0].closest('[role="dialog"],.pending-sandbox-panel,.conversation-high-credit-approval') || nodes[0].parentElement; const rawOptions = nodes.map(node => (node.innerText || node.textContent)?.trim() || '').filter(Boolean); let prompt = root?.innerText?.trim() || ''; for (const option of rawOptions) prompt = prompt.replace(option, ''); return { prompt: prompt.replace(/\\n{2,}/g, '\\n').trim(), options: rawOptions.map(option => option.replace(/^\\d+\\s*[.、::)]?\\s*/, '').trim()), messageId: nodes[0].closest('.cr-agent[data-message-id]')?.getAttribute('data-message-id') || undefined, initialText: nodes[0].closest('.cr-agent[data-message-id]')?.querySelector('[data-assistant-content]')?.innerText?.trim() || '' }; })())`);
|
|
@@ -571,8 +589,10 @@ class WorkbuddyCdpClient {
|
|
|
571
589
|
const response = await this.cdp("Runtime.evaluate", { expression: wrapped, returnByValue: true, awaitPromise: true });
|
|
572
590
|
if (response.error)
|
|
573
591
|
throw new Error(`WorkBuddy CDP Runtime.evaluate 失败:${response.error.message ?? response.error.code}`);
|
|
574
|
-
if (response.result?.exceptionDetails)
|
|
575
|
-
|
|
592
|
+
if (response.result?.exceptionDetails) {
|
|
593
|
+
const details = response.result.exceptionDetails;
|
|
594
|
+
throw new Error(`WorkBuddy 页面 JS 异常:${details.exception?.description ?? details.text ?? JSON.stringify(details)}`);
|
|
595
|
+
}
|
|
576
596
|
return response.result?.result?.value;
|
|
577
597
|
}
|
|
578
598
|
finally {
|
|
@@ -7,6 +7,7 @@ const node_fs_1 = require("node:fs");
|
|
|
7
7
|
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
|
+
const agent_1 = require("../../agent");
|
|
10
11
|
const attachments_1 = require("../../attachments");
|
|
11
12
|
const plugin_1 = require("../../plugin");
|
|
12
13
|
const cdp_client_1 = require("./cdp-client");
|
|
@@ -15,7 +16,7 @@ const session_history_1 = require("./session-history");
|
|
|
15
16
|
const desktop_launch_1 = require("../desktop-launch");
|
|
16
17
|
const desktop_launch_2 = require("./desktop-launch");
|
|
17
18
|
exports.workbuddyManifest = {
|
|
18
|
-
id: "workbuddy", version: "1.1.
|
|
19
|
+
id: "workbuddy", version: "1.1.28", displayName: "WorkBuddy", type: "agent",
|
|
19
20
|
entry: "dist/index.js", apiVersion: "1", process: "in-process",
|
|
20
21
|
capabilities: ["chat", "health"], configSchema: "config.schema.json",
|
|
21
22
|
};
|
|
@@ -59,6 +60,7 @@ class WorkbuddyPlugin {
|
|
|
59
60
|
sessionDataDir;
|
|
60
61
|
sessionWrites = Promise.resolve();
|
|
61
62
|
dailyPointsTimer;
|
|
63
|
+
dailyPointsActive = false;
|
|
62
64
|
dailyPointsDataDir;
|
|
63
65
|
dailyPointsLogger;
|
|
64
66
|
failure;
|
|
@@ -155,23 +157,28 @@ class WorkbuddyPlugin {
|
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
async deactivate(_reason) {
|
|
160
|
+
this.dailyPointsActive = false;
|
|
158
161
|
if (this.dailyPointsTimer)
|
|
159
162
|
clearTimeout(this.dailyPointsTimer);
|
|
160
163
|
this.dailyPointsTimer = undefined;
|
|
161
164
|
await this.queue;
|
|
162
165
|
await this.sessionWrites.catch(() => { });
|
|
163
|
-
|
|
166
|
+
if (_reason !== "reload")
|
|
167
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.process).catch(() => undefined);
|
|
164
168
|
this.client?.close();
|
|
165
169
|
this.client = undefined;
|
|
166
170
|
this.process = undefined;
|
|
167
171
|
this.reconnect = undefined;
|
|
168
172
|
}
|
|
169
173
|
scheduleDailyPoints(hour, minute) {
|
|
174
|
+
this.dailyPointsActive = true;
|
|
170
175
|
const now = new Date();
|
|
171
176
|
const scheduled = new Date(now);
|
|
172
177
|
scheduled.setHours(hour, minute, 0, 0);
|
|
173
178
|
const due = scheduled <= now;
|
|
174
179
|
const arm = () => {
|
|
180
|
+
if (!this.dailyPointsActive)
|
|
181
|
+
return;
|
|
175
182
|
const next = new Date();
|
|
176
183
|
next.setHours(hour, minute, 0, 0);
|
|
177
184
|
if (next <= new Date())
|
|
@@ -271,63 +278,73 @@ class WorkbuddyPlugin {
|
|
|
271
278
|
});
|
|
272
279
|
}
|
|
273
280
|
async complete(request) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
281
|
+
let stage = "连接 WorkBuddy 桌面";
|
|
282
|
+
try {
|
|
283
|
+
const timeout = request.timeoutMs ?? this.timeoutMs;
|
|
284
|
+
const expert = extractExpertQuery(request.task);
|
|
285
|
+
const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
|
|
286
|
+
const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
|
|
287
|
+
if (hasAttachments && !this.attachmentInput)
|
|
288
|
+
throw new Error("WorkBuddy 当前传输不支持附件输入");
|
|
289
|
+
if (!usesCli && !this.client?.connected())
|
|
290
|
+
await this.reconnect?.();
|
|
291
|
+
const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
|
|
292
|
+
if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
|
|
293
|
+
throw new Error("WorkBuddy 正在等待另一个会话的选择");
|
|
294
|
+
if (usesCli && request.sessionId)
|
|
295
|
+
throw new Error("CodeBuddy CLI 会话隔离未验证,不能处理带 sessionId 的请求");
|
|
296
|
+
if (!usesCli && !request.sessionId && this.activeSessionId)
|
|
297
|
+
throw new Error("WorkBuddy 已绑定逻辑会话,请使用相同 sessionId 或先执行 /new");
|
|
298
|
+
if (!usesCli && !request.sessionId && !this.desktopSessionValid)
|
|
299
|
+
throw new Error("WorkBuddy 桌面任务状态未知,请先创建新会话");
|
|
300
|
+
stage = "准备或恢复 WorkBuddy 会话";
|
|
301
|
+
if (!usesCli)
|
|
302
|
+
await this.ensureSession(request.sessionId);
|
|
303
|
+
stage = "执行 WorkBuddy 桌面任务";
|
|
304
|
+
if (expert) {
|
|
305
|
+
if (!this.client?.connected())
|
|
306
|
+
throw new Error(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
|
|
307
|
+
if (request.sessionId && this.activeExpert !== undefined && this.activeExpert !== expert)
|
|
308
|
+
throw new Error("WorkBuddy 切换专家需要先执行 /new,避免丢失当前会话历史");
|
|
309
|
+
const select = request.sessionId && this.activeExpert === expert ? undefined : expert;
|
|
310
|
+
if (request.sessionId)
|
|
311
|
+
this.activeExpert = expert;
|
|
312
|
+
this.expertSession = true;
|
|
313
|
+
try {
|
|
314
|
+
if (this.client.complete)
|
|
315
|
+
return await this.client.complete({ ...request, timeoutMs: timeout }, select, false);
|
|
316
|
+
return { text: await this.client.direct(request.task, timeout, select) };
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (select)
|
|
320
|
+
this.desktopSessionValid = false;
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (usesCli)
|
|
325
|
+
return { text: await (0, cli_client_1.callCli)(this.cliPath, request.task, "local-test", timeout, this.model) };
|
|
294
326
|
if (!this.client?.connected())
|
|
295
|
-
throw new Error(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
if (request.sessionId)
|
|
300
|
-
this.activeExpert =
|
|
301
|
-
this.expertSession = true;
|
|
327
|
+
throw new Error("WorkBuddy CDP 未连接");
|
|
328
|
+
const reset = !request.sessionId && this.expertSession;
|
|
329
|
+
if (!request.sessionId)
|
|
330
|
+
this.expertSession = false;
|
|
331
|
+
if (request.sessionId && this.activeExpert === undefined)
|
|
332
|
+
this.activeExpert = null;
|
|
302
333
|
try {
|
|
303
334
|
if (this.client.complete)
|
|
304
|
-
return await this.client.complete({ ...request, timeoutMs: timeout },
|
|
305
|
-
return { text: await this.client.direct(request.task, timeout,
|
|
335
|
+
return await this.client.complete({ ...request, timeoutMs: timeout }, undefined, reset);
|
|
336
|
+
return { text: await this.client.direct(request.task, timeout, undefined, reset) };
|
|
306
337
|
}
|
|
307
338
|
catch (error) {
|
|
308
|
-
if (
|
|
339
|
+
if (reset)
|
|
309
340
|
this.desktopSessionValid = false;
|
|
310
341
|
throw error;
|
|
311
342
|
}
|
|
312
343
|
}
|
|
313
|
-
if (usesCli)
|
|
314
|
-
return { text: await (0, cli_client_1.callCli)(this.cliPath, request.task, "local-test", timeout, this.model) };
|
|
315
|
-
if (!this.client?.connected())
|
|
316
|
-
throw new Error("WorkBuddy CDP 未连接");
|
|
317
|
-
const reset = !request.sessionId && this.expertSession;
|
|
318
|
-
if (!request.sessionId)
|
|
319
|
-
this.expertSession = false;
|
|
320
|
-
if (request.sessionId && this.activeExpert === undefined)
|
|
321
|
-
this.activeExpert = null;
|
|
322
|
-
try {
|
|
323
|
-
if (this.client.complete)
|
|
324
|
-
return await this.client.complete({ ...request, timeoutMs: timeout }, undefined, reset);
|
|
325
|
-
return { text: await this.client.direct(request.task, timeout, undefined, reset) };
|
|
326
|
-
}
|
|
327
344
|
catch (error) {
|
|
328
|
-
if (
|
|
329
|
-
|
|
330
|
-
throw error;
|
|
345
|
+
if (error instanceof agent_1.AgentOperationError || error?.stage)
|
|
346
|
+
throw error;
|
|
347
|
+
throw new agent_1.AgentOperationError("WORKBUDDY_TASK_FAILED", stage, error);
|
|
331
348
|
}
|
|
332
349
|
}
|
|
333
350
|
async ensureSession(sessionId) {
|
|
@@ -430,17 +447,8 @@ class WorkbuddyPlugin {
|
|
|
430
447
|
const launch = (file, args) => (0, desktop_launch_2.launchWorkbuddyDesktop)(file, args, profile, port);
|
|
431
448
|
const timeoutMs = typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000;
|
|
432
449
|
this.reconnect = async () => {
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
catch (first) {
|
|
438
|
-
if (config.autoStart === false || this.options.client || !executable)
|
|
439
|
-
throw first;
|
|
440
|
-
await (0, desktop_launch_1.terminateOwnedProcess)(this.process);
|
|
441
|
-
this.process = undefined;
|
|
442
|
-
await (0, desktop_launch_1.launchManagedAndWait)(executable, [`--remote-debugging-port=${port}`], profile, () => this.client.connect(), timeoutMs, child => { this.process = child; }, launch);
|
|
443
|
-
}
|
|
450
|
+
// 请求恢复只重连 socket,页面错误不能触发桌面进程重启。
|
|
451
|
+
await this.client.connect();
|
|
444
452
|
};
|
|
445
453
|
try {
|
|
446
454
|
await this.client.connect();
|
|
@@ -224,13 +224,14 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
224
224
|
strict_1.default.equal(sent[0].to, "peer.agentid.cn");
|
|
225
225
|
}, async () => { published = true; });
|
|
226
226
|
});
|
|
227
|
-
(0, node_test_1.default)("AUN
|
|
227
|
+
(0, node_test_1.default)("AUN 普通文本任务读取产物失败回原话题,已发送进度仍反馈最终失败", async () => {
|
|
228
228
|
await withAunAttachmentPlugin(async (receive, sent) => {
|
|
229
229
|
await receive({ from: "peer.agentid.cn", message_id: "artifact-error", payload: { text: "生成文件", thread_id: "topic-artifact" } });
|
|
230
230
|
strict_1.default.deepEqual(sent, [{ to: "peer.agentid.cn", payload: { type: "text", text: "消息处理失败:产物文件已消失", thread_id: "topic-artifact" }, encrypt: true }]);
|
|
231
231
|
await receive({ from: "peer.agentid.cn", message_id: "after-reply", payload: { text: "已回复", thread_id: "topic-artifact" } });
|
|
232
|
-
strict_1.default.equal(sent.length,
|
|
232
|
+
strict_1.default.equal(sent.length, 3);
|
|
233
233
|
strict_1.default.equal(sent[1].payload.text, "已反馈");
|
|
234
|
+
strict_1.default.match(sent[2].payload.text, /产物文件已消失/);
|
|
234
235
|
}, async (message) => {
|
|
235
236
|
if (message.text === "已回复")
|
|
236
237
|
await message.reply("已反馈");
|
|
@@ -536,7 +537,7 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
536
537
|
await routes.get(`GET ${commandPath}`)({}, manifestResponse);
|
|
537
538
|
strict_1.default.equal(manifestResponse.body.protocolVersion, 1);
|
|
538
539
|
strict_1.default.equal(manifestResponse.body.plugin, "aun");
|
|
539
|
-
strict_1.default.equal(manifestResponse.body.version,
|
|
540
|
+
strict_1.default.equal(manifestResponse.body.version, plugin.manifest.version);
|
|
540
541
|
strict_1.default.deepEqual(manifestResponse.body.commands.map((command) => command.name), ["status", "list", "agents", "default-owner", "create", "bind", "unbind"]);
|
|
541
542
|
strict_1.default.ok(manifestResponse.body.commands.every((command) => command.summary && command.usage));
|
|
542
543
|
const status = await runAunCommand(routes, commandResponse(), ["status"]);
|
|
@@ -1320,3 +1321,24 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1320
1321
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1321
1322
|
}
|
|
1322
1323
|
});
|
|
1324
|
+
(0, node_test_1.default)("AUN 已发送进度后 Agent 失败仍向原 peer 返回最终具体错误并记录关联字段", async () => {
|
|
1325
|
+
await withAunAttachmentPlugin(async (receive, sent, _storage, logs) => {
|
|
1326
|
+
await receive({ from: "peer.agentid.cn", message_id: "progress-failed", payload: { text: "制作 PDF", thread_id: "topic-errors" } });
|
|
1327
|
+
strict_1.default.equal(sent.length, 2);
|
|
1328
|
+
strict_1.default.match(sent[1].payload.text, /WORKBUDDY_ARTIFACT_READ_FAILED.*ENOENT/);
|
|
1329
|
+
const details = logs.join("\n");
|
|
1330
|
+
for (const value of ["local.agentid.cn", "peer.agentid.cn", "progress-failed", "topic-errors", "qwenwork"])
|
|
1331
|
+
strict_1.default.ok(details.includes(value), value);
|
|
1332
|
+
}, async (message) => {
|
|
1333
|
+
await message.reply("PDF 正在生成");
|
|
1334
|
+
throw new Error("WORKBUDDY_ARTIFACT_READ_FAILED:读取最终产物失败:ENOENT");
|
|
1335
|
+
});
|
|
1336
|
+
});
|
|
1337
|
+
(0, node_test_1.default)("AUN 文件上传失败明确标出 FS 阶段和系统错误码", async () => {
|
|
1338
|
+
await withAunAttachmentPlugin(async (receive, sent, storage, logs) => {
|
|
1339
|
+
storage.writeBytes = async () => { throw Object.assign(new Error("fetch failed"), { cause: Object.assign(new Error("connection refused"), { code: "ECONNREFUSED" }) }); };
|
|
1340
|
+
await receive({ from: "peer.agentid.cn", message_id: "fs-failed", payload: { text: "制作 PDF" } });
|
|
1341
|
+
strict_1.default.match(sent[0].payload.text, /AUN_FS_UPLOAD_FAILED.*报告.pdf.*ECONNREFUSED/);
|
|
1342
|
+
strict_1.default.match(logs.join("\n"), /fs-failed.*AUN_FS_UPLOAD_FAILED|AUN_FS_UPLOAD_FAILED.*fs-failed/);
|
|
1343
|
+
}, async (message) => { await message.replyAttachments([{ kind: "file", filename: "报告.pdf", contentType: "application/pdf", data: new Uint8Array([1]) }]); });
|
|
1344
|
+
});
|
|
@@ -1,4 +1,37 @@
|
|
|
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
|
};
|
|
@@ -70,3 +103,18 @@ const cli = "C:\\Users\\molian\\AppData\\Roaming\\npm\\node_modules\\mu-agent-br
|
|
|
70
103
|
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
71
104
|
}
|
|
72
105
|
});
|
|
106
|
+
(0, node_test_1.default)("同一安装的符号链接路径也能识别,避免自动更新留下重复 daemon", { skip: process.platform === "win32" }, async () => {
|
|
107
|
+
const { symlink } = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
|
|
108
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-alias-"));
|
|
109
|
+
try {
|
|
110
|
+
const actual = (0, node_path_1.join)(root, "actual");
|
|
111
|
+
const alias = (0, node_path_1.join)(root, "alias");
|
|
112
|
+
await (0, promises_1.mkdir)(actual);
|
|
113
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(actual, "cli.js"), "");
|
|
114
|
+
await symlink(actual, alias, "dir");
|
|
115
|
+
strict_1.default.equal((0, daemon_process_1.isDaemonCommand)(`node "${(0, node_path_1.join)(alias, "cli.js")}" start`, (0, node_path_1.join)(actual, "cli.js")), true);
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
});
|