mu-agent-bridge 1.0.56 → 1.0.57

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.
@@ -207,6 +207,17 @@ async function startUpdatedDaemon() {
207
207
  };
208
208
  return { healthy };
209
209
  }
210
+ let coreUpdateCheckRunning = false;
211
+ function scheduleCoreUpdateCheck() {
212
+ if (coreUpdateCheckRunning)
213
+ return;
214
+ coreUpdateCheckRunning = true;
215
+ const env = { ...process.env, AGENT_BRIDGE_CORE_UPDATE_ONLY: "1" };
216
+ const child = (0, node_child_process_1.spawn)(process.execPath, [(0, node_path_1.resolve)(__dirname, "cli.js"), "start"], { detached: true, stdio: "ignore", windowsHide: true, env });
217
+ child.once("exit", () => { coreUpdateCheckRunning = false; });
218
+ child.once("error", () => { coreUpdateCheckRunning = false; });
219
+ child.unref();
220
+ }
210
221
  async function stop() {
211
222
  await stopDaemon();
212
223
  console.log("旧 daemon 已停止");
@@ -304,6 +315,8 @@ async function serve() {
304
315
  const intervalMs = Math.max(60_000, Number(process.env.AGENT_BRIDGE_PLUGIN_UPDATE_INTERVAL_MS ?? 300_000) || 300_000);
305
316
  const updateTimer = setInterval(() => void checkAndUpgradePlugins(manager), intervalMs);
306
317
  updateTimer.unref?.();
318
+ const coreUpdateTimer = setInterval(scheduleCoreUpdateCheck, intervalMs);
319
+ coreUpdateTimer.unref?.();
307
320
  let stopping = false;
308
321
  const shutdown = async () => {
309
322
  if (stopping)
@@ -325,6 +338,7 @@ async function serve() {
325
338
  }
326
339
  catch { /* 地址文件可能已被清理 */ }
327
340
  clearInterval(updateTimer);
341
+ clearInterval(coreUpdateTimer);
328
342
  };
329
343
  process.once("SIGINT", () => void shutdown().finally(() => process.exit(0)));
330
344
  process.once("SIGTERM", () => void shutdown().finally(() => process.exit(0)));
@@ -399,6 +413,10 @@ async function main() {
399
413
  await logs.flush();
400
414
  return 0;
401
415
  }
416
+ if (process.env.AGENT_BRIDGE_CORE_UPDATE_ONLY === "1") {
417
+ await logs.flush();
418
+ return 0;
419
+ }
402
420
  await stopDaemon(false);
403
421
  return serve();
404
422
  }
@@ -167,7 +167,7 @@ class ControlServer {
167
167
  }
168
168
  if (parts[0] === "api" && parts[1] === "agents" && parts[2] && parts[3] === "v1") {
169
169
  const id = decodeURIComponent(parts[2]);
170
- if (req.headers["x-evolcore-session-id"] !== undefined && !authorized && !isLoopback(req.socket.remoteAddress))
170
+ if (["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"].some((name) => req.headers[name] !== undefined) && !authorized && !isLoopback(req.socket.remoteAddress))
171
171
  return this.json(res, 401, { error: "会话身份仅允许已认证或本机调用方使用" });
172
172
  const adapter = new openai_adapter_1.OpenAIAdapter(async (task, _signal, context) => {
173
173
  if (!context)
@@ -66,6 +66,7 @@ async function updateCoreAtStartup(options) {
66
66
  await (0, promises_1.rm)(packageRoot, { recursive: true, force: true });
67
67
  await (0, promises_1.cp)(backup, packageRoot, { recursive: true });
68
68
  options.log?.("error", "daemon 自动升级失败,已恢复旧版本", { error: error instanceof Error ? error.message : String(error), version: metadata.version });
69
+ await options.startDaemon().catch((restartError) => options.log?.("error", "旧 daemon 恢复启动失败", { error: restartError instanceof Error ? restartError.message : String(restartError), version: metadata.version }));
69
70
  await (0, promises_1.rm)(backupRoot, { recursive: true, force: true });
70
71
  return false;
71
72
  }
@@ -73,13 +73,11 @@ class OpenAIAdapter {
73
73
  return;
74
74
  }
75
75
  const session = req.headers["x-evolcore-session-id"];
76
- if (session !== undefined) {
77
- for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
78
- const value = req.headers[name];
79
- if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
80
- json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
81
- return;
82
- }
76
+ for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
77
+ const value = req.headers[name];
78
+ if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
79
+ json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
80
+ return;
83
81
  }
84
82
  }
85
83
  const abort = new AbortController();
@@ -100,7 +98,8 @@ class OpenAIAdapter {
100
98
  }
101
99
  this.onLog?.(`chat request model=${payload.model ?? this.model} stream=${Boolean(payload.stream)}`);
102
100
  this.onExchange?.("request", prompt, peerId, agentAid);
103
- const content = await this.complete(prompt, abort.signal, typeof session === "string" ? { sessionId: session, peerId, agentAid } : undefined);
101
+ const hasSessionContext = typeof session === "string" || peerId !== undefined || agentAid !== undefined;
102
+ const content = await this.complete(prompt, abort.signal, hasSessionContext ? { sessionId: typeof session === "string" ? session : "", peerId, agentAid } : undefined);
104
103
  this.onExchange?.("reply", content, peerId, agentAid);
105
104
  if (abort.signal.aborted || res.destroyed)
106
105
  return;
@@ -120,7 +120,7 @@ class WorkbuddyCdpClient {
120
120
  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 }; })()`);
121
121
  if (before.loginRequired)
122
122
  throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
123
- if (!await this.evaluate(`(() => { const button = [...document.querySelectorAll('button,[role="button"]')].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; })()`))
123
+ 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; })()`))
124
124
  throw new Error("WorkBuddy 新建任务入口未找到或不可用");
125
125
  // WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
126
126
  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 新任务状态校验失败");
@@ -15,7 +15,7 @@ const session_history_1 = require("./session-history");
15
15
  const desktop_launch_1 = require("../desktop-launch");
16
16
  const desktop_launch_2 = require("./desktop-launch");
17
17
  exports.workbuddyManifest = {
18
- id: "workbuddy", version: "1.1.18", displayName: "WorkBuddy", type: "agent",
18
+ id: "workbuddy", version: "1.1.19", displayName: "WorkBuddy", type: "agent",
19
19
  entry: "dist/index.js", apiVersion: "1", process: "in-process",
20
20
  capabilities: ["chat", "health"], configSchema: "config.schema.json",
21
21
  };
@@ -265,22 +265,22 @@ class WorkbuddyPlugin {
265
265
  const timeout = request.timeoutMs ?? this.timeoutMs;
266
266
  const expert = extractExpertQuery(request.task);
267
267
  const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
268
- // 内部恢复消息可能没有 sessionId,沿用当前桌面会话,避免恢复任务被会话保护拒绝。
269
- const sessionId = request.sessionId ?? this.activeSessionId;
270
268
  const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
271
269
  if (hasAttachments && !this.attachmentInput)
272
270
  throw new Error("WorkBuddy 当前传输不支持附件输入");
273
271
  if (!usesCli && !this.client?.connected())
274
272
  await this.reconnect?.();
275
273
  const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
276
- if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== sessionId)
274
+ if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
277
275
  throw new Error("WorkBuddy 正在等待另一个会话的选择");
278
276
  if (usesCli && request.sessionId)
279
277
  throw new Error("CodeBuddy CLI 会话隔离未验证,不能处理带 sessionId 的请求");
280
- if (!usesCli && !sessionId && !this.desktopSessionValid)
278
+ if (!usesCli && !request.sessionId && this.activeSessionId)
279
+ throw new Error("WorkBuddy 已绑定逻辑会话,请使用相同 sessionId 或先执行 /new");
280
+ if (!usesCli && !request.sessionId && !this.desktopSessionValid)
281
281
  throw new Error("WorkBuddy 桌面任务状态未知,请先创建新会话");
282
282
  if (!usesCli)
283
- await this.ensureSession(sessionId);
283
+ await this.ensureSession(request.sessionId);
284
284
  if (expert) {
285
285
  if (!this.client?.connected())
286
286
  throw new Error(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
@@ -447,16 +447,39 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
447
447
  await server.stop();
448
448
  }
449
449
  });
450
+ (0, node_test_1.default)("OpenAI 未提供会话头时按对端和本端 AID建立稳定会话", async () => {
451
+ const calls = [];
452
+ const server = new control_server_1.ControlServer({ manager: { handle: async (id, request) => { calls.push({ id, request }); return { text: "回复" }; }, recordMessage() { } }, host: "127.0.0.1", port: 0 });
453
+ const address = await server.start();
454
+ try {
455
+ const send = async (peer, aid = "workassists.agentid.cn") => {
456
+ const response = await fetch(`http://127.0.0.1:${address.port}/api/agents/workbuddy/v1/chat/completions`, {
457
+ method: "POST", headers: { "x-aun-peer": peer, "x-evolcore-agent-aid": aid },
458
+ body: JSON.stringify({ messages: [{ role: "user", content: "继续当前任务" }] }),
459
+ });
460
+ strict_1.default.equal(response.status, 200);
461
+ return calls.at(-1).request.sessionId;
462
+ };
463
+ const first = await send("peer-a.agentid.cn");
464
+ strict_1.default.match(first, /^openai:[a-f0-9]{64}$/);
465
+ strict_1.default.equal(await send("peer-a.agentid.cn"), first);
466
+ strict_1.default.notEqual(await send("peer-b.agentid.cn"), first);
467
+ strict_1.default.notEqual(await send("peer-a.agentid.cn", "other.agentid.cn"), first);
468
+ }
469
+ finally {
470
+ await server.stop();
471
+ }
472
+ });
450
473
  (0, node_test_1.default)("未认证远程调用不能使用会话身份,旧请求仍兼容", async () => {
451
474
  const calls = [];
452
475
  const server = new control_server_1.ControlServer({ manager: { handle: async (_id, request) => { calls.push(request); return { text: "回复" }; }, recordMessage() { } } });
453
- for (const headers of [{ "x-evolcore-session-id": "client-id" }, { "x-evolcore-session-id": "client-id", authorization: "Bearer undefined" }, {}]) {
476
+ for (const headers of [{ "x-evolcore-session-id": "client-id" }, { "x-evolcore-session-id": "client-id", authorization: "Bearer undefined" }, { "x-aun-peer": "peer.agentid.cn", "x-evolcore-agent-aid": "local.agentid.cn" }, {}]) {
454
477
  const req = Object.assign(node_stream_1.Readable.from([JSON.stringify({ messages: [{ role: "user", content: "测试" }] })]), {
455
478
  method: "POST", url: "/api/agents/workbuddy/v1/chat/completions", headers, socket: { remoteAddress: "192.168.31.10" },
456
479
  });
457
480
  const res = Object.assign(new node_events_1.EventEmitter(), { statusCode: 0, writableEnded: false, setHeader() { }, end() { this.writableEnded = true; } });
458
481
  await server.handle(req, res);
459
- strict_1.default.equal(res.statusCode, "x-evolcore-session-id" in headers ? 401 : 200);
482
+ strict_1.default.equal(res.statusCode, "x-evolcore-session-id" in headers || "x-aun-peer" in headers ? 401 : 200);
460
483
  }
461
484
  strict_1.default.deepEqual(calls, [{ task: "测试" }]);
462
485
  });
@@ -48,6 +48,7 @@ async function fixture() {
48
48
  });
49
49
  (0, node_test_1.default)("新 daemon 健康检查失败时恢复旧核心包并继续旧进程", async () => {
50
50
  const value = await fixture();
51
+ const events = [];
51
52
  try {
52
53
  const updated = await (0, core_updater_1.updateCoreAtStartup)({
53
54
  packageRoot: value.packageRoot,
@@ -59,12 +60,13 @@ async function fixture() {
59
60
  await (0, promises_1.writeFile)((0, node_path_1.join)(value.packageRoot, "package.json"), JSON.stringify({ name: "mu-agent-bridge", version: "1.1.0" }));
60
61
  return "";
61
62
  },
62
- stopDaemon: async () => undefined,
63
- startDaemon: async () => undefined,
64
- waitForHealth: async () => false,
63
+ stopDaemon: async () => { events.push("stop"); },
64
+ startDaemon: async () => { events.push("start"); },
65
+ waitForHealth: async () => { events.push("health"); return false; },
65
66
  });
66
67
  strict_1.default.equal(updated, false);
67
68
  strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(value.packageRoot, "package.json"), "utf8")).version, "1.0.0");
69
+ strict_1.default.deepEqual(events, ["stop", "start", "health", "stop", "start"]);
68
70
  }
69
71
  finally {
70
72
  await (0, promises_1.rm)(value.root, { recursive: true, force: true });
@@ -209,19 +209,6 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
209
209
  strict_1.default.equal((await plugin.health()).status, "ready");
210
210
  await plugin.deactivate("stop");
211
211
  });
212
- (0, node_test_1.default)("workbuddy 内部恢复消息无 sessionId 时沿用当前桌面会话", async () => {
213
- const calls = [];
214
- const client = {
215
- connect: async () => undefined, close() { }, connected: () => true,
216
- direct: async (task) => { calls.push(task); return "恢复成功"; },
217
- };
218
- const plugin = new workbuddy_1.WorkbuddyPlugin({ client });
219
- await plugin.activate({ config: { transport: "cdp" }, logger: { info() { }, warn() { }, error() { } } });
220
- plugin.activeSessionId = "session-active";
221
- await strict_1.default.doesNotReject(() => plugin.handle({ task: "继续当前任务" }));
222
- strict_1.default.deepEqual(calls, ["继续当前任务"]);
223
- await plugin.deactivate("stop");
224
- });
225
212
  (0, node_test_1.default)("workbuddy 结构化客户端接收附件并保留附件响应和逻辑会话", async () => {
226
213
  const attachment = { kind: "file", data: new Uint8Array([1, 2]), filename: "输入.txt", contentType: "text/plain" };
227
214
  const output = { kind: "file", data: new Uint8Array([3]), filename: "输出.txt", contentType: "text/plain" };
@@ -991,6 +978,22 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
991
978
  strict_1.default.equal(conversationId, null);
992
979
  strict_1.default.equal(messages, 0);
993
980
  });
981
+ (0, node_test_1.default)("workbuddy CDP 识别新版侧栏 role=tab 新建任务入口", async () => {
982
+ let conversationId = "old-conversation";
983
+ let messages = 1;
984
+ const button = { innerText: "新建任务", disabled: false, getAttribute: () => null, getClientRects: () => [{}], click: () => { conversationId = null; messages = 0; } };
985
+ const editor = { innerText: "今天帮你做些什么?", querySelector: () => conversationId === null ? {} : null, cloneNode: () => ({ textContent: "", querySelectorAll: () => [] }) };
986
+ const document = {
987
+ querySelectorAll: (selector) => selector === '.cr-agent[data-message-id]' ? Array(messages).fill({}) : selector === '[data-conversation-id]' ? conversationId ? [{ className: "_selected_old", getAttribute: () => conversationId }] : [] : selector.includes('[role="tab"]') ? [button] : [],
988
+ querySelector: () => editor,
989
+ };
990
+ const client = new cdp_client_1.WorkbuddyCdpClient({ timeoutMs: 1 });
991
+ const internal = client;
992
+ internal.evaluate = async (expression) => (0, node_vm_1.runInNewContext)(expression, { document, location: { href: "app://workbuddy/task/old" } });
993
+ await client.createTask();
994
+ strict_1.default.equal(conversationId, null);
995
+ strict_1.default.equal(messages, 0);
996
+ });
994
997
  (0, node_test_1.default)("workbuddy CDP 新建不接受缺少任务身份的旧消息界面直接清空", async () => {
995
998
  let messages = 1;
996
999
  const button = { textContent: "新建任务", disabled: false, getAttribute: () => null, click: () => { messages = 0; } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.56",
3
+ "version": "1.0.57",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {
@@ -73,13 +73,11 @@ class OpenAIAdapter {
73
73
  return;
74
74
  }
75
75
  const session = req.headers["x-evolcore-session-id"];
76
- if (session !== undefined) {
77
- for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
78
- const value = req.headers[name];
79
- if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
80
- json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
81
- return;
82
- }
76
+ for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
77
+ const value = req.headers[name];
78
+ if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
79
+ json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
80
+ return;
83
81
  }
84
82
  }
85
83
  const abort = new AbortController();
@@ -100,7 +98,8 @@ class OpenAIAdapter {
100
98
  }
101
99
  this.onLog?.(`chat request model=${payload.model ?? this.model} stream=${Boolean(payload.stream)}`);
102
100
  this.onExchange?.("request", prompt, peerId, agentAid);
103
- const content = await this.complete(prompt, abort.signal, typeof session === "string" ? { sessionId: session, peerId, agentAid } : undefined);
101
+ const hasSessionContext = typeof session === "string" || peerId !== undefined || agentAid !== undefined;
102
+ const content = await this.complete(prompt, abort.signal, hasSessionContext ? { sessionId: typeof session === "string" ? session : "", peerId, agentAid } : undefined);
104
103
  this.onExchange?.("reply", content, peerId, agentAid);
105
104
  if (abort.signal.aborted || res.destroyed)
106
105
  return;
@@ -73,13 +73,11 @@ class OpenAIAdapter {
73
73
  return;
74
74
  }
75
75
  const session = req.headers["x-evolcore-session-id"];
76
- if (session !== undefined) {
77
- for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
78
- const value = req.headers[name];
79
- if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
80
- json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
81
- return;
82
- }
76
+ for (const name of ["x-evolcore-session-id", "x-aun-peer", "x-evolcore-agent-aid"]) {
77
+ const value = req.headers[name];
78
+ if (value !== undefined && (typeof value !== "string" || !value.trim() || value.length > 256 || /[\x00-\x1f\x7f-\x9f]/.test(value))) {
79
+ json(res, 400, { error: { message: `${name} 必须是 1 到 256 个字符且不含控制字符的字符串`, type: "invalid_request_error" } });
80
+ return;
83
81
  }
84
82
  }
85
83
  const abort = new AbortController();
@@ -100,7 +98,8 @@ class OpenAIAdapter {
100
98
  }
101
99
  this.onLog?.(`chat request model=${payload.model ?? this.model} stream=${Boolean(payload.stream)}`);
102
100
  this.onExchange?.("request", prompt, peerId, agentAid);
103
- const content = await this.complete(prompt, abort.signal, typeof session === "string" ? { sessionId: session, peerId, agentAid } : undefined);
101
+ const hasSessionContext = typeof session === "string" || peerId !== undefined || agentAid !== undefined;
102
+ const content = await this.complete(prompt, abort.signal, hasSessionContext ? { sessionId: typeof session === "string" ? session : "", peerId, agentAid } : undefined);
104
103
  this.onExchange?.("reply", content, peerId, agentAid);
105
104
  if (abort.signal.aborted || res.destroyed)
106
105
  return;
@@ -120,7 +120,7 @@ class WorkbuddyCdpClient {
120
120
  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 }; })()`);
121
121
  if (before.loginRequired)
122
122
  throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
123
- if (!await this.evaluate(`(() => { const button = [...document.querySelectorAll('button,[role="button"]')].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; })()`))
123
+ 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; })()`))
124
124
  throw new Error("WorkBuddy 新建任务入口未找到或不可用");
125
125
  // WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
126
126
  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 新任务状态校验失败");
@@ -15,7 +15,7 @@ const session_history_1 = require("./session-history");
15
15
  const desktop_launch_1 = require("../desktop-launch");
16
16
  const desktop_launch_2 = require("./desktop-launch");
17
17
  exports.workbuddyManifest = {
18
- id: "workbuddy", version: "1.1.18", displayName: "WorkBuddy", type: "agent",
18
+ id: "workbuddy", version: "1.1.19", displayName: "WorkBuddy", type: "agent",
19
19
  entry: "dist/index.js", apiVersion: "1", process: "in-process",
20
20
  capabilities: ["chat", "health"], configSchema: "config.schema.json",
21
21
  };
@@ -265,22 +265,22 @@ class WorkbuddyPlugin {
265
265
  const timeout = request.timeoutMs ?? this.timeoutMs;
266
266
  const expert = extractExpertQuery(request.task);
267
267
  const usesCli = Boolean(this.cliPath && this.transport !== "cdp" && !expert);
268
- // 内部恢复消息可能没有 sessionId,沿用当前桌面会话,避免恢复任务被会话保护拒绝。
269
- const sessionId = request.sessionId ?? this.activeSessionId;
270
268
  const hasAttachments = Array.isArray(request.attachments) && request.attachments.length > 0;
271
269
  if (hasAttachments && !this.attachmentInput)
272
270
  throw new Error("WorkBuddy 当前传输不支持附件输入");
273
271
  if (!usesCli && !this.client?.connected())
274
272
  await this.reconnect?.();
275
273
  const pendingInteractionSessionId = this.client?.pendingInteractionSessionId?.();
276
- if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== sessionId)
274
+ if (!usesCli && pendingInteractionSessionId && pendingInteractionSessionId !== request.sessionId)
277
275
  throw new Error("WorkBuddy 正在等待另一个会话的选择");
278
276
  if (usesCli && request.sessionId)
279
277
  throw new Error("CodeBuddy CLI 会话隔离未验证,不能处理带 sessionId 的请求");
280
- if (!usesCli && !sessionId && !this.desktopSessionValid)
278
+ if (!usesCli && !request.sessionId && this.activeSessionId)
279
+ throw new Error("WorkBuddy 已绑定逻辑会话,请使用相同 sessionId 或先执行 /new");
280
+ if (!usesCli && !request.sessionId && !this.desktopSessionValid)
281
281
  throw new Error("WorkBuddy 桌面任务状态未知,请先创建新会话");
282
282
  if (!usesCli)
283
- await this.ensureSession(sessionId);
283
+ await this.ensureSession(request.sessionId);
284
284
  if (expert) {
285
285
  if (!this.client?.connected())
286
286
  throw new Error(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-workbuddy",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "description": "mu-agent-bridge 的 WorkBuddy 桌面端插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "workbuddy",
3
- "version": "1.1.18",
3
+ "version": "1.1.19",
4
4
  "type": "agent",
5
5
  "displayName": "WorkBuddy",
6
6
  "entry": "dist/src-ts/plugins/workbuddy/index.js",