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.
Files changed (40) hide show
  1. package/dist/src-ts/agent.js +25 -1
  2. package/dist/src-ts/control-server.js +3 -3
  3. package/dist/src-ts/daemon-process.js +8 -1
  4. package/dist/src-ts/plugins/aun/attachments.js +5 -2
  5. package/dist/src-ts/plugins/aun/index.js +27 -13
  6. package/dist/src-ts/plugins/qwenwork/cdp-client.js +83 -57
  7. package/dist/src-ts/plugins/qwenwork/index.js +12 -40
  8. package/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
  9. package/dist/src-ts/plugins/workbuddy/cdp-client.js +90 -70
  10. package/dist/src-ts/plugins/workbuddy/index.js +68 -60
  11. package/dist/tests-ts/aun-plugin.test.js +25 -3
  12. package/dist/tests-ts/daemon-process.test.js +48 -0
  13. package/dist/tests-ts/qwenwork-plugin.test.js +63 -51
  14. package/dist/tests-ts/qwenwork-sse.test.js +70 -0
  15. package/dist/tests-ts/workbuddy-plugin.test.js +53 -2
  16. package/package.json +1 -1
  17. package/plugins/abweb/dist/src-ts/agent.js +25 -1
  18. package/plugins/aun/dist/src-ts/agent.js +25 -1
  19. package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +5 -2
  20. package/plugins/aun/dist/src-ts/plugins/aun/index.js +27 -13
  21. package/plugins/aun/package.json +1 -1
  22. package/plugins/aun/plugin.json +1 -1
  23. package/plugins/chatgpt/dist/src-ts/agent.js +25 -1
  24. package/plugins/claude-cli/dist/src-ts/agent.js +25 -1
  25. package/plugins/codex-cli/dist/src-ts/agent.js +25 -1
  26. package/plugins/doubao/dist/src-ts/agent.js +25 -1
  27. package/plugins/dsh/dist/src-ts/agent.js +25 -1
  28. package/plugins/dsh/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
  29. package/plugins/feishu/dist/src-ts/agent.js +25 -1
  30. package/plugins/qwenwork/dist/src-ts/agent.js +25 -1
  31. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +83 -57
  32. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +12 -40
  33. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/openai-adapter.js +38 -8
  34. package/plugins/qwenwork/package.json +1 -1
  35. package/plugins/qwenwork/plugin.json +1 -1
  36. package/plugins/workbuddy/dist/src-ts/agent.js +25 -1
  37. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +90 -70
  38. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +68 -60
  39. package/plugins/workbuddy/package.json +1 -1
  40. 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 Boolean(this.socket); }
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.socket)
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
- const taskButton = `(() => { 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); }); return Boolean(button && !button.disabled && button.getAttribute('aria-disabled') !== 'true'); })()`;
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
- (0, attachments_1.validateAttachments)(request.attachments);
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
- if (request.attachments?.length) {
209
- await this.injectAttachments(request.attachments, temporaryPaths);
210
- if (!await this.evaluate(`(() => {
211
- const editor = document.querySelector(${JSON.stringify(exports.editorSelector)}); if (!editor) return false;
212
- const clone = editor.cloneNode(true);
213
- for (const tag of clone.querySelectorAll('[class*="_fileTag"]')) tag.remove();
214
- clone.style.position = 'absolute'; clone.style.left = '-100000px'; document.body.appendChild(clone);
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
- await this.waitFor(`(() => { const button = document.querySelector('button[aria-label="发送"]'); return Boolean(button && !button.disabled); })()`, "WorkBuddy 发送按钮未就绪");
221
- if (!await this.evaluate(`(() => { const button = document.querySelector('button[aria-label="发送"]'); if (!button || button.disabled) return false; button.click(); return true; })()`))
222
- throw new Error("WorkBuddy 发送按钮不可用");
223
- const response = await this.waitForReply(beforeIds, timeoutMs, undefined, undefined, request.sessionId, request.onMessage);
224
- if (this.pendingInteraction && temporaryPaths.length)
225
- this.pendingInteractionCleanup = temporaryPaths;
226
- return response;
227
- }
228
- catch (error) {
229
- failure = error;
230
- throw error;
231
- }
232
- finally {
233
- if (temporaryPaths.length && this.pendingInteractionCleanup !== temporaryPaths) {
234
- try {
235
- await this.cleanupTemporaryFiles(temporaryPaths);
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
- catch (error) {
238
- if (failure !== undefined)
239
- throw new AggregateError([failure, error], `${failure instanceof Error ? failure.message : String(failure)};临时附件清理失败`, { cause: failure });
240
- throw error;
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 Error("WorkBuddy UI 回复超时");
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
- throw new Error(`WorkBuddy 页面 JS 异常:${JSON.stringify(response.result.exceptionDetails).slice(0, 300)}`);
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.26", displayName: "WorkBuddy", type: "agent",
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
- await (0, desktop_launch_1.terminateOwnedProcess)(this.process).catch(() => undefined);
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
- const timeout = request.timeoutMs ?? this.timeoutMs;
275
- const expert = extractExpertQuery(request.task);
276
- const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
277
- const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
278
- if (hasAttachments && !this.attachmentInput)
279
- throw new Error("WorkBuddy 当前传输不支持附件输入");
280
- if (!usesCli && !this.client?.connected())
281
- await this.reconnect?.();
282
- const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
283
- if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
284
- throw new Error("WorkBuddy 正在等待另一个会话的选择");
285
- if (usesCli && request.sessionId)
286
- throw new Error("CodeBuddy CLI 会话隔离未验证,不能处理带 sessionId 的请求");
287
- if (!usesCli && !request.sessionId && this.activeSessionId)
288
- throw new Error("WorkBuddy 已绑定逻辑会话,请使用相同 sessionId 或先执行 /new");
289
- if (!usesCli && !request.sessionId && !this.desktopSessionValid)
290
- throw new Error("WorkBuddy 桌面任务状态未知,请先创建新会话");
291
- if (!usesCli)
292
- await this.ensureSession(request.sessionId);
293
- if (expert) {
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(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
296
- if (request.sessionId && this.activeExpert !== undefined && this.activeExpert !== expert)
297
- throw new Error("WorkBuddy 切换专家需要先执行 /new,避免丢失当前会话历史");
298
- const select = request.sessionId && this.activeExpert === expert ? undefined : expert;
299
- if (request.sessionId)
300
- this.activeExpert = expert;
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 }, select, false);
305
- return { text: await this.client.direct(request.task, timeout, select) };
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 (select)
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 (reset)
329
- this.desktopSessionValid = false;
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
- try {
434
- await this.client.connect();
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 普通文本任务读取产物失败回原话题,已回复任务不重复反馈", async () => {
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, 2);
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, "1.0.31");
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
+ });