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.
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +6 -0
- package/dist/src-ts/plugins/workbuddy/index.js +21 -5
- package/dist/tests-ts/chatgpt-plugin.test.js +5 -3
- package/dist/tests-ts/installer-assets.test.js +2 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +36 -0
- package/package.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +6 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +21 -5
|
@@ -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
|
-
|
|
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(
|
|
347
|
+
await this.client.activateConversation(conversationId);
|
|
342
348
|
this.activeSessionId = sessionId;
|
|
343
|
-
this.activeDesktopConversationId =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
413
|
-
|
|
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
|
@@ -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
|
-
|
|
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(
|
|
347
|
+
await this.client.activateConversation(conversationId);
|
|
342
348
|
this.activeSessionId = sessionId;
|
|
343
|
-
this.activeDesktopConversationId =
|
|
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
|
-
|
|
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
|
-
|
|
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;
|