mu-agent-bridge 1.0.57 → 1.0.58

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.
@@ -169,6 +169,7 @@ class ChatgptCdpClient {
169
169
  return { supported: true, turns: result.turns, complete: false, missingReasons };
170
170
  }
171
171
  async switchSession(session) {
172
+ await this.waitForGeneration();
172
173
  await this.recoverDraft();
173
174
  const locate = `[...document.querySelectorAll(${JSON.stringify(sidebarSelector)})].find(item =>
174
175
  item.getAttribute('data-app-action-sidebar-thread-id') === ${JSON.stringify(session.id)}
@@ -192,6 +193,7 @@ class ChatgptCdpClient {
192
193
  })()`, "ChatGPT 会话切换未确认,未发送消息");
193
194
  }
194
195
  async createSession(_name) {
196
+ await this.waitForGeneration();
195
197
  await this.recoverDraft();
196
198
  const before = await this.activeSession();
197
199
  const state = await this.evaluate(`JSON.stringify({ editor: document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText ?? null, turns: document.querySelectorAll(${JSON.stringify(exports.turnSelector)}).length, generating: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) })`);
@@ -336,6 +338,7 @@ class ChatgptCdpClient {
336
338
  }
337
339
  async directTurn(text, timeoutMs, attachments, slot, outputDirectory, startedAt) {
338
340
  await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
341
+ await this.waitForGeneration();
339
342
  await this.recoverDraft();
340
343
  const beforeTurnsJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})].map(turn => ({ id: turn.getAttribute(${JSON.stringify(turnIdAttribute)}), users: turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length })))`);
341
344
  const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
@@ -658,6 +661,9 @@ class ChatgptCdpClient {
658
661
  }
659
662
  throw new Error(message);
660
663
  }
664
+ waitForGeneration() {
665
+ return this.waitFor(`!Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)}))`, "ChatGPT 桌面端仍在生成回复,等待超时;未清理输入或切换会话");
666
+ }
661
667
  rejectPending(error) {
662
668
  for (const pending of this.pending.values())
663
669
  pending.reject(error);
@@ -52,6 +52,7 @@ class WorkbuddyPlugin {
52
52
  /** 重载后待恢复的会话身份。 */
53
53
  recoverableSessionId;
54
54
  recoverableConversationId;
55
+ sessionBindings = new Map();
55
56
  activeDesktopConversationId;
56
57
  desktopSessionValid = true;
57
58
  knownSessions = new Set();
@@ -81,6 +82,7 @@ class WorkbuddyPlugin {
81
82
  this.activeSessionId = undefined;
82
83
  this.recoverableSessionId = undefined;
83
84
  this.recoverableConversationId = undefined;
85
+ this.sessionBindings.clear();
84
86
  this.activeDesktopConversationId = undefined;
85
87
  this.activeExpert = undefined;
86
88
  this.expertSession = false;
@@ -94,6 +96,8 @@ class WorkbuddyPlugin {
94
96
  const saved = await this.loadKnownSessions(this.sessionDataDir);
95
97
  for (const sessionId of saved.sessionIds)
96
98
  this.knownSessions.add(sessionId);
99
+ for (const [sessionId, binding] of Object.entries(saved.bindings ?? {}))
100
+ this.sessionBindings.set(sessionId, binding);
97
101
  savedBinding = saved.active;
98
102
  }
99
103
  this.sessionOperations = [];
@@ -336,11 +340,14 @@ class WorkbuddyPlugin {
336
340
  return;
337
341
  }
338
342
  if (this.knownSessions.has(sessionId)) {
339
- if (this.recoverableSessionId !== sessionId || !this.recoverableConversationId || !this.client.activateConversation)
343
+ const binding = this.sessionBindings.get(sessionId);
344
+ const conversationId = binding?.conversationId ?? (this.recoverableSessionId === sessionId ? this.recoverableConversationId : undefined);
345
+ if (!conversationId || !this.client.activateConversation)
340
346
  throw new Error("WorkBuddy 不支持恢复旧 WorkBuddy 会话");
341
- await this.client.activateConversation(this.recoverableConversationId);
347
+ await this.client.activateConversation(conversationId);
342
348
  this.activeSessionId = sessionId;
343
- this.activeDesktopConversationId = this.recoverableConversationId;
349
+ this.activeDesktopConversationId = conversationId;
350
+ this.activeExpert = binding?.expert;
344
351
  this.desktopSessionValid = true;
345
352
  this.recoverableSessionId = undefined;
346
353
  this.recoverableConversationId = undefined;
@@ -374,7 +381,14 @@ class WorkbuddyPlugin {
374
381
  const active = parsed.active;
375
382
  if (active !== undefined && (!active || typeof active.sessionId !== "string" || !sessionIds.includes(active.sessionId) || typeof active.conversationId !== "string" || !active.conversationId.trim() || (active.expert !== undefined && active.expert !== null && typeof active.expert !== "string")))
376
383
  throw new Error("活动会话绑定无效");
377
- return { sessionIds: new Set(sessionIds), ...(active ? { active: active } : {}) };
384
+ const activeBinding = active;
385
+ const bindingsValue = parsed.bindings;
386
+ if (bindingsValue !== undefined && (!bindingsValue || typeof bindingsValue !== "object" || Array.isArray(bindingsValue) || Object.entries(bindingsValue).some(([sessionId, binding]) => !sessionIds.includes(sessionId) || !binding || typeof binding !== "object" || typeof binding.conversationId !== "string" || !binding.conversationId.trim() || (binding.expert !== undefined && binding.expert !== null && typeof binding.expert !== "string"))))
387
+ throw new Error("会话绑定列表无效");
388
+ const bindings = (bindingsValue ?? {});
389
+ if (activeBinding && !bindings[activeBinding.sessionId])
390
+ bindings[activeBinding.sessionId] = { conversationId: activeBinding.conversationId, expert: activeBinding.expert };
391
+ return { sessionIds: new Set(sessionIds), ...(activeBinding ? { active: activeBinding } : {}), bindings };
378
392
  }
379
393
  catch (error) {
380
394
  if (error.code === "ENOENT")
@@ -391,7 +405,9 @@ class WorkbuddyPlugin {
391
405
  await (0, promises_1.mkdir)(this.sessionDataDir, { recursive: true });
392
406
  const file = (0, node_path_1.join)(this.sessionDataDir, "known-sessions.json");
393
407
  const active = this.desktopSessionValid && this.activeSessionId && this.activeDesktopConversationId ? { sessionId: this.activeSessionId, conversationId: this.activeDesktopConversationId, expert: this.activeExpert } : undefined;
394
- await (0, promises_1.writeFile)(`${file}.tmp`, `${JSON.stringify({ sessionIds: [...this.knownSessions].sort(), active })}\n`, { mode: 0o600 });
408
+ if (active)
409
+ this.sessionBindings.set(active.sessionId, { conversationId: active.conversationId, expert: active.expert });
410
+ await (0, promises_1.writeFile)(`${file}.tmp`, `${JSON.stringify({ sessionIds: [...this.knownSessions].sort(), active, bindings: Object.fromEntries(this.sessionBindings) })}\n`, { mode: 0o600 });
395
411
  await (0, promises_1.rename)(`${file}.tmp`, file);
396
412
  });
397
413
  this.sessionWrites = write;
@@ -367,7 +367,7 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
367
367
  strict_1.default.equal(sends, 1);
368
368
  await plugin.deactivate();
369
369
  });
370
- (0, node_test_1.default)("ChatGPT 固定 CDP 操作读取侧栏并按主机和 ID 切换,正在生成时阻止切换", async () => {
370
+ (0, node_test_1.default)("ChatGPT 固定 CDP 操作按主机和 ID 切换,生成中等待且超时不切换", async () => {
371
371
  const { runInNewContext } = await Promise.resolve().then(() => __importStar(require("node:vm")));
372
372
  const entries = [{ id: 'a"\\', host: "h1", title: "旧会话" }, { id: 'a"\\', host: "h2", title: "目标会话" }];
373
373
  let selected = 0;
@@ -384,6 +384,7 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
384
384
  };
385
385
  let onMessage = null;
386
386
  const client = new chatgpt_1.ChatgptCdpClient({
387
+ timeoutMs: 300,
387
388
  fetchImpl: async () => new Response(JSON.stringify([{ type: "page", url: "app://-/index.html", webSocketDebuggerUrl: "ws://localhost:9230/a" }])),
388
389
  webSocketFactory: () => ({
389
390
  set onmessage(fn) { onMessage = fn; },
@@ -409,8 +410,9 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
409
410
  strict_1.default.equal(selected, 1);
410
411
  strict_1.default.deepEqual(await client.activeSession(), entries[1]);
411
412
  generating = true;
412
- await strict_1.default.rejects(client.switchSession(entries[0]), /正在生成/);
413
- strict_1.default.equal(selected, 1);
413
+ setTimeout(() => { generating = false; }, 100);
414
+ await client.switchSession(entries[0]);
415
+ strict_1.default.equal(selected, 0);
414
416
  }
415
417
  finally {
416
418
  client.close();
@@ -44,6 +44,8 @@ const node_util_1 = require("node:util");
44
44
  strict_1.default.match(setup, /\$npmCommand\.Source prefix --global/);
45
45
  strict_1.default.match(setup, /@\(\$nodeDirectory, \$npmDirectory, \$npmPrefix\)/);
46
46
  strict_1.default.match(setup, /mu-agent-bridge@latest --engine-strict/);
47
+ strict_1.default.match(setup, /--prefer-online/);
48
+ strict_1.default.match(setup, /npm 安装遇到版本尚未同步或文件占用.*重试/);
47
49
  strict_1.default.doesNotMatch(setup, /mu-agent-bridge@\d+\.\d+\.\d+/);
48
50
  strict_1.default.match(setup, /api\/agents\/availability/);
49
51
  strict_1.default.match(setup, /function Find-AgentExecutable/);
@@ -1137,6 +1137,42 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
1137
1137
  await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
1138
1138
  }
1139
1139
  });
1140
+ (0, node_test_1.default)("workbuddy 多个 AUN 会话交替后重载仍分别恢复桌面会话", async () => {
1141
+ const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "wb-multiple-sessions-"));
1142
+ let creates = 0;
1143
+ let desktopId = "desktop-initial";
1144
+ const calls = [];
1145
+ const client = {
1146
+ connect: async () => undefined, close() { }, connected: () => true,
1147
+ conversationId: async () => desktopId,
1148
+ activateConversation: async (id) => { desktopId = id; },
1149
+ createTask: async () => { desktopId = `desktop-${++creates}`; },
1150
+ direct: async (task) => { calls.push(`${desktopId}:${task}`); return task; },
1151
+ };
1152
+ const context = { dataDir, config: { transport: "cdp" }, logger: { info() { }, warn() { }, error() { } } };
1153
+ try {
1154
+ const first = new workbuddy_1.WorkbuddyPlugin({ client });
1155
+ await first.activate(context);
1156
+ await first.handle({ task: "peer-a 首条", sessionId: "session-a" });
1157
+ await first.handle({ task: "peer-b 首条", sessionId: "session-b" });
1158
+ await first.handle({ task: "peer-a 续聊", sessionId: "session-a" });
1159
+ await first.deactivate("reload");
1160
+ desktopId = "desktop-after-restart";
1161
+ const second = new workbuddy_1.WorkbuddyPlugin({ client });
1162
+ await second.activate(context);
1163
+ await second.handle({ task: "peer-b 重载后续聊", sessionId: "session-b" });
1164
+ await second.handle({ task: "peer-a 重载后续聊", sessionId: "session-a" });
1165
+ strict_1.default.deepEqual(calls, [
1166
+ "desktop-1:peer-a 首条", "desktop-2:peer-b 首条", "desktop-1:peer-a 续聊",
1167
+ "desktop-2:peer-b 重载后续聊", "desktop-1:peer-a 重载后续聊",
1168
+ ]);
1169
+ strict_1.default.equal(creates, 2);
1170
+ await second.deactivate("stop");
1171
+ }
1172
+ finally {
1173
+ await (0, promises_1.rm)(dataDir, { recursive: true, force: true });
1174
+ }
1175
+ });
1140
1176
  (0, node_test_1.default)("workbuddy 新建失败的会话 ID 重启后仍禁止重试", async () => {
1141
1177
  const dataDir = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "wb-failed-session-"));
1142
1178
  let creates = 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mu-agent-bridge",
3
- "version": "1.0.57",
3
+ "version": "1.0.58",
4
4
  "description": "将 AUN Agent 连接到本地桌面 AI 客户端的插件平台。",
5
5
  "main": "dist/src-ts/cli.js",
6
6
  "bin": {
@@ -169,6 +169,7 @@ class ChatgptCdpClient {
169
169
  return { supported: true, turns: result.turns, complete: false, missingReasons };
170
170
  }
171
171
  async switchSession(session) {
172
+ await this.waitForGeneration();
172
173
  await this.recoverDraft();
173
174
  const locate = `[...document.querySelectorAll(${JSON.stringify(sidebarSelector)})].find(item =>
174
175
  item.getAttribute('data-app-action-sidebar-thread-id') === ${JSON.stringify(session.id)}
@@ -192,6 +193,7 @@ class ChatgptCdpClient {
192
193
  })()`, "ChatGPT 会话切换未确认,未发送消息");
193
194
  }
194
195
  async createSession(_name) {
196
+ await this.waitForGeneration();
195
197
  await this.recoverDraft();
196
198
  const before = await this.activeSession();
197
199
  const state = await this.evaluate(`JSON.stringify({ editor: document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText ?? null, turns: document.querySelectorAll(${JSON.stringify(exports.turnSelector)}).length, generating: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) })`);
@@ -336,6 +338,7 @@ class ChatgptCdpClient {
336
338
  }
337
339
  async directTurn(text, timeoutMs, attachments, slot, outputDirectory, startedAt) {
338
340
  await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
341
+ await this.waitForGeneration();
339
342
  await this.recoverDraft();
340
343
  const beforeTurnsJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})].map(turn => ({ id: turn.getAttribute(${JSON.stringify(turnIdAttribute)}), users: turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length })))`);
341
344
  const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
@@ -658,6 +661,9 @@ class ChatgptCdpClient {
658
661
  }
659
662
  throw new Error(message);
660
663
  }
664
+ waitForGeneration() {
665
+ return this.waitFor(`!Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)}))`, "ChatGPT 桌面端仍在生成回复,等待超时;未清理输入或切换会话");
666
+ }
661
667
  rejectPending(error) {
662
668
  for (const pending of this.pending.values())
663
669
  pending.reject(error);
@@ -52,6 +52,7 @@ class WorkbuddyPlugin {
52
52
  /** 重载后待恢复的会话身份。 */
53
53
  recoverableSessionId;
54
54
  recoverableConversationId;
55
+ sessionBindings = new Map();
55
56
  activeDesktopConversationId;
56
57
  desktopSessionValid = true;
57
58
  knownSessions = new Set();
@@ -81,6 +82,7 @@ class WorkbuddyPlugin {
81
82
  this.activeSessionId = undefined;
82
83
  this.recoverableSessionId = undefined;
83
84
  this.recoverableConversationId = undefined;
85
+ this.sessionBindings.clear();
84
86
  this.activeDesktopConversationId = undefined;
85
87
  this.activeExpert = undefined;
86
88
  this.expertSession = false;
@@ -94,6 +96,8 @@ class WorkbuddyPlugin {
94
96
  const saved = await this.loadKnownSessions(this.sessionDataDir);
95
97
  for (const sessionId of saved.sessionIds)
96
98
  this.knownSessions.add(sessionId);
99
+ for (const [sessionId, binding] of Object.entries(saved.bindings ?? {}))
100
+ this.sessionBindings.set(sessionId, binding);
97
101
  savedBinding = saved.active;
98
102
  }
99
103
  this.sessionOperations = [];
@@ -336,11 +340,14 @@ class WorkbuddyPlugin {
336
340
  return;
337
341
  }
338
342
  if (this.knownSessions.has(sessionId)) {
339
- if (this.recoverableSessionId !== sessionId || !this.recoverableConversationId || !this.client.activateConversation)
343
+ const binding = this.sessionBindings.get(sessionId);
344
+ const conversationId = binding?.conversationId ?? (this.recoverableSessionId === sessionId ? this.recoverableConversationId : undefined);
345
+ if (!conversationId || !this.client.activateConversation)
340
346
  throw new Error("WorkBuddy 不支持恢复旧 WorkBuddy 会话");
341
- await this.client.activateConversation(this.recoverableConversationId);
347
+ await this.client.activateConversation(conversationId);
342
348
  this.activeSessionId = sessionId;
343
- this.activeDesktopConversationId = this.recoverableConversationId;
349
+ this.activeDesktopConversationId = conversationId;
350
+ this.activeExpert = binding?.expert;
344
351
  this.desktopSessionValid = true;
345
352
  this.recoverableSessionId = undefined;
346
353
  this.recoverableConversationId = undefined;
@@ -374,7 +381,14 @@ class WorkbuddyPlugin {
374
381
  const active = parsed.active;
375
382
  if (active !== undefined && (!active || typeof active.sessionId !== "string" || !sessionIds.includes(active.sessionId) || typeof active.conversationId !== "string" || !active.conversationId.trim() || (active.expert !== undefined && active.expert !== null && typeof active.expert !== "string")))
376
383
  throw new Error("活动会话绑定无效");
377
- return { sessionIds: new Set(sessionIds), ...(active ? { active: active } : {}) };
384
+ const activeBinding = active;
385
+ const bindingsValue = parsed.bindings;
386
+ if (bindingsValue !== undefined && (!bindingsValue || typeof bindingsValue !== "object" || Array.isArray(bindingsValue) || Object.entries(bindingsValue).some(([sessionId, binding]) => !sessionIds.includes(sessionId) || !binding || typeof binding !== "object" || typeof binding.conversationId !== "string" || !binding.conversationId.trim() || (binding.expert !== undefined && binding.expert !== null && typeof binding.expert !== "string"))))
387
+ throw new Error("会话绑定列表无效");
388
+ const bindings = (bindingsValue ?? {});
389
+ if (activeBinding && !bindings[activeBinding.sessionId])
390
+ bindings[activeBinding.sessionId] = { conversationId: activeBinding.conversationId, expert: activeBinding.expert };
391
+ return { sessionIds: new Set(sessionIds), ...(activeBinding ? { active: activeBinding } : {}), bindings };
378
392
  }
379
393
  catch (error) {
380
394
  if (error.code === "ENOENT")
@@ -391,7 +405,9 @@ class WorkbuddyPlugin {
391
405
  await (0, promises_1.mkdir)(this.sessionDataDir, { recursive: true });
392
406
  const file = (0, node_path_1.join)(this.sessionDataDir, "known-sessions.json");
393
407
  const active = this.desktopSessionValid && this.activeSessionId && this.activeDesktopConversationId ? { sessionId: this.activeSessionId, conversationId: this.activeDesktopConversationId, expert: this.activeExpert } : undefined;
394
- await (0, promises_1.writeFile)(`${file}.tmp`, `${JSON.stringify({ sessionIds: [...this.knownSessions].sort(), active })}\n`, { mode: 0o600 });
408
+ if (active)
409
+ this.sessionBindings.set(active.sessionId, { conversationId: active.conversationId, expert: active.expert });
410
+ await (0, promises_1.writeFile)(`${file}.tmp`, `${JSON.stringify({ sessionIds: [...this.knownSessions].sort(), active, bindings: Object.fromEntries(this.sessionBindings) })}\n`, { mode: 0o600 });
395
411
  await (0, promises_1.rename)(`${file}.tmp`, file);
396
412
  });
397
413
  this.sessionWrites = write;