mu-agent-bridge 1.0.20 → 1.0.22
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/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +0 -1
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +11 -2
- package/dist/tests-ts/abweb-login.test.js +72 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +14 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +0 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +11 -2
|
@@ -145,7 +145,6 @@
|
|
|
145
145
|
void loadCatalog().catch((cause) => { catalogNode.textContent = `远程插件目录暂时不可用:${cause instanceof Error ? cause.message : String(cause)}`; });
|
|
146
146
|
}
|
|
147
147
|
catch (cause) {
|
|
148
|
-
node("#access-panel").hidden = false;
|
|
149
148
|
error(cause instanceof Error ? cause.message : String(cause));
|
|
150
149
|
}
|
|
151
150
|
finally {
|
|
@@ -5,6 +5,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
5
5
|
exports.editorSelector = '[data-codex-composer="true"][contenteditable="true"]';
|
|
6
6
|
exports.turnSelector = '[data-chatgpt-conversation-turn="true"]';
|
|
7
7
|
exports.userBubbleSelector = "[data-user-message-bubble]";
|
|
8
|
+
const assistantMessageSelector = '[data-markdown-text-style="assistant-message"]';
|
|
8
9
|
const turnIdAttribute = "data-chatgpt-conversation-turn-id";
|
|
9
10
|
const contentUnitSelector = "[data-content-search-unit-key]";
|
|
10
11
|
const sendButtonSelector = 'button[aria-label="Send"], button[aria-label="发送"]';
|
|
@@ -88,7 +89,8 @@ class ChatgptCdpClient {
|
|
|
88
89
|
if (!response.ok)
|
|
89
90
|
throw new Error(`ChatGPT CDP /json HTTP ${response.status}`);
|
|
90
91
|
const targets = await response.json();
|
|
91
|
-
const page = targets.find((target) => target.type === "page" && /chatgpt|codex/i.test(`${target.title} ${target.url}`)
|
|
92
|
+
const page = targets.find((target) => target.type === "page" && (/chatgpt|codex/i.test(`${target.title} ${target.url}`)
|
|
93
|
+
|| target.url === "app://-/index.html"));
|
|
92
94
|
if (page?.webSocketDebuggerUrl)
|
|
93
95
|
return page;
|
|
94
96
|
last = new Error(`未找到 ChatGPT/Codex 页面 target(端口 ${this.port})`);
|
|
@@ -111,6 +113,7 @@ class ChatgptCdpClient {
|
|
|
111
113
|
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
|
|
112
114
|
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 })))`);
|
|
113
115
|
const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
|
|
116
|
+
const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
|
|
114
117
|
const ready = await this.evaluate(`(() => {
|
|
115
118
|
const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
|
|
116
119
|
if (!editor || editor.innerText.trim()) return false;
|
|
@@ -164,7 +167,13 @@ class ChatgptCdpClient {
|
|
|
164
167
|
const fresh = oldIndex < 0 || index >= before.length || users.length > (old?.users ?? 0);
|
|
165
168
|
return fresh && users.item(users.length - 1)?.innerText.trim() === ${JSON.stringify(text.trim())};
|
|
166
169
|
});
|
|
167
|
-
if (!turn)
|
|
170
|
+
if (!turn) {
|
|
171
|
+
const assistants = [...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})];
|
|
172
|
+
const fallback = assistants.length > ${beforeAssistantCount}
|
|
173
|
+
? assistants.at(-1)?.innerText.trim() ?? ""
|
|
174
|
+
: "";
|
|
175
|
+
return JSON.stringify({ reply: fallback, active: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) });
|
|
176
|
+
}
|
|
168
177
|
const user = turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).item(turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length - 1);
|
|
169
178
|
const unit = user?.closest(${JSON.stringify(contentUnitSelector)});
|
|
170
179
|
const units = [...turn.querySelectorAll(${JSON.stringify(contentUnitSelector)})];
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_vm_1 = require("node:vm");
|
|
10
|
+
class Element {
|
|
11
|
+
hidden = true;
|
|
12
|
+
value = "";
|
|
13
|
+
textContent = "";
|
|
14
|
+
options = [];
|
|
15
|
+
selectedIndex = 0;
|
|
16
|
+
onclick;
|
|
17
|
+
replaceChildren() { }
|
|
18
|
+
append() { }
|
|
19
|
+
setAttribute() { }
|
|
20
|
+
}
|
|
21
|
+
(0, node_test_1.default)("ABWeb缓存登录在刷新和后台暂时不可用时保留,到期才重新配对", async () => {
|
|
22
|
+
const script = await (0, promises_1.readFile)("src-ts/plugins/abweb/public/app.js", "utf8");
|
|
23
|
+
let now = Date.now();
|
|
24
|
+
const storage = new Map();
|
|
25
|
+
let unavailable = false;
|
|
26
|
+
const calls = [];
|
|
27
|
+
const open = async () => {
|
|
28
|
+
const nodes = new Map();
|
|
29
|
+
const node = (selector) => { if (!nodes.has(selector))
|
|
30
|
+
nodes.set(selector, new Element()); return nodes.get(selector); };
|
|
31
|
+
(0, node_vm_1.runInNewContext)(script, {
|
|
32
|
+
document: { querySelector: node, createElement: () => new Element(), hidden: false },
|
|
33
|
+
localStorage: { getItem: (key) => storage.get(key) ?? null, setItem: (key, value) => storage.set(key, value), removeItem: (key) => storage.delete(key) },
|
|
34
|
+
Date: class extends Date {
|
|
35
|
+
static now() { return now; }
|
|
36
|
+
},
|
|
37
|
+
setInterval() { }, confirm: () => false,
|
|
38
|
+
fetch: async (path, options) => {
|
|
39
|
+
calls.push({ path, token: options.headers.authorization });
|
|
40
|
+
if (unavailable)
|
|
41
|
+
throw new Error("后台正在重启");
|
|
42
|
+
if (path === "/api/pair")
|
|
43
|
+
return { ok: true, status: 200, json: async () => ({ token: "测试会话", expiresAt: now + 86400000 }) };
|
|
44
|
+
const authorized = options.headers.authorization === "Bearer 测试会话" && Number(storage.get("mab-login-expires-at")) > now;
|
|
45
|
+
return { ok: authorized, status: authorized ? 200 : 401, json: async () => ({ plugins: [], messages: [], sessions: [], mode: "auto-install" }) };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
49
|
+
return node;
|
|
50
|
+
};
|
|
51
|
+
const first = await open();
|
|
52
|
+
strict_1.default.equal(first("#access-panel").hidden, false);
|
|
53
|
+
first("#pair-code").value = "123456";
|
|
54
|
+
first("#pair-button").onclick();
|
|
55
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
56
|
+
strict_1.default.equal(first("#access-panel").hidden, true);
|
|
57
|
+
const expiry = storage.get("mab-login-expires-at");
|
|
58
|
+
now += 23 * 3600000;
|
|
59
|
+
const refreshed = await open();
|
|
60
|
+
strict_1.default.equal(refreshed("#access-panel").hidden, true);
|
|
61
|
+
unavailable = true;
|
|
62
|
+
const restarting = await open();
|
|
63
|
+
strict_1.default.equal(restarting("#access-panel").hidden, true, "后台暂时不可用不能提示重新配对");
|
|
64
|
+
strict_1.default.equal(storage.get("mab-login-token"), "测试会话");
|
|
65
|
+
unavailable = false;
|
|
66
|
+
strict_1.default.equal((await open())("#access-panel").hidden, true);
|
|
67
|
+
strict_1.default.equal(storage.get("mab-login-expires-at"), expiry);
|
|
68
|
+
now = Number(expiry);
|
|
69
|
+
strict_1.default.equal((await open())("#access-panel").hidden, false);
|
|
70
|
+
strict_1.default.equal(storage.has("mab-login-token"), false);
|
|
71
|
+
strict_1.default.equal(calls.filter(call => call.path === "/api/pair").length, 1);
|
|
72
|
+
});
|
|
@@ -35,6 +35,20 @@ const chatgpt_1 = require("../src-ts/plugins/chatgpt");
|
|
|
35
35
|
strict_1.default.equal(client.connected(), true);
|
|
36
36
|
client.close();
|
|
37
37
|
});
|
|
38
|
+
(0, node_test_1.default)("ChatGPT/Codex 桌面端使用 app 页面 target 时可以连接", async () => {
|
|
39
|
+
let connectedUrl = "";
|
|
40
|
+
const client = new chatgpt_1.ChatgptCdpClient({
|
|
41
|
+
timeoutMs: 100,
|
|
42
|
+
fetchImpl: async () => new Response(JSON.stringify([{ type: "page", title: "请只回复:CDP连通测试成功", url: "app://-/index.html", webSocketDebuggerUrl: "ws://127.0.0.1:9230/devtools/page/2" }])),
|
|
43
|
+
webSocketFactory: (url) => {
|
|
44
|
+
connectedUrl = url.toString();
|
|
45
|
+
return { set onmessage(_) { }, set onclose(_) { }, set onerror(_) { }, set onopen(handler) { queueMicrotask(handler); }, close() { }, send() { } };
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
await client.connect();
|
|
49
|
+
strict_1.default.equal(connectedUrl, "ws://127.0.0.1:9230/devtools/page/2");
|
|
50
|
+
client.close();
|
|
51
|
+
});
|
|
38
52
|
(0, node_test_1.default)("ChatGPT CDP 只返回本轮新用户消息之后的回复", async () => {
|
|
39
53
|
let messageHandler;
|
|
40
54
|
let sentText = "";
|
package/package.json
CHANGED
|
@@ -145,7 +145,6 @@
|
|
|
145
145
|
void loadCatalog().catch((cause) => { catalogNode.textContent = `远程插件目录暂时不可用:${cause instanceof Error ? cause.message : String(cause)}`; });
|
|
146
146
|
}
|
|
147
147
|
catch (cause) {
|
|
148
|
-
node("#access-panel").hidden = false;
|
|
149
148
|
error(cause instanceof Error ? cause.message : String(cause));
|
|
150
149
|
}
|
|
151
150
|
finally {
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
sessionTable.append(row);
|
|
102
102
|
}
|
|
103
103
|
if (refreshCatalog) void loadCatalog().catch((cause) => { catalogNode.textContent = `远程插件目录暂时不可用:${cause instanceof Error ? cause.message : String(cause)}`; });
|
|
104
|
-
} catch (cause) {
|
|
104
|
+
} catch (cause) { error(cause instanceof Error ? cause.message : String(cause)); }
|
|
105
105
|
finally { loading = false; }
|
|
106
106
|
}
|
|
107
107
|
function renderChannels(items: Plugin[]): void {
|
|
@@ -5,6 +5,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
5
5
|
exports.editorSelector = '[data-codex-composer="true"][contenteditable="true"]';
|
|
6
6
|
exports.turnSelector = '[data-chatgpt-conversation-turn="true"]';
|
|
7
7
|
exports.userBubbleSelector = "[data-user-message-bubble]";
|
|
8
|
+
const assistantMessageSelector = '[data-markdown-text-style="assistant-message"]';
|
|
8
9
|
const turnIdAttribute = "data-chatgpt-conversation-turn-id";
|
|
9
10
|
const contentUnitSelector = "[data-content-search-unit-key]";
|
|
10
11
|
const sendButtonSelector = 'button[aria-label="Send"], button[aria-label="发送"]';
|
|
@@ -88,7 +89,8 @@ class ChatgptCdpClient {
|
|
|
88
89
|
if (!response.ok)
|
|
89
90
|
throw new Error(`ChatGPT CDP /json HTTP ${response.status}`);
|
|
90
91
|
const targets = await response.json();
|
|
91
|
-
const page = targets.find((target) => target.type === "page" && /chatgpt|codex/i.test(`${target.title} ${target.url}`)
|
|
92
|
+
const page = targets.find((target) => target.type === "page" && (/chatgpt|codex/i.test(`${target.title} ${target.url}`)
|
|
93
|
+
|| target.url === "app://-/index.html"));
|
|
92
94
|
if (page?.webSocketDebuggerUrl)
|
|
93
95
|
return page;
|
|
94
96
|
last = new Error(`未找到 ChatGPT/Codex 页面 target(端口 ${this.port})`);
|
|
@@ -111,6 +113,7 @@ class ChatgptCdpClient {
|
|
|
111
113
|
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
|
|
112
114
|
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 })))`);
|
|
113
115
|
const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
|
|
116
|
+
const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
|
|
114
117
|
const ready = await this.evaluate(`(() => {
|
|
115
118
|
const editor = document.querySelector(${JSON.stringify(exports.editorSelector)});
|
|
116
119
|
if (!editor || editor.innerText.trim()) return false;
|
|
@@ -164,7 +167,13 @@ class ChatgptCdpClient {
|
|
|
164
167
|
const fresh = oldIndex < 0 || index >= before.length || users.length > (old?.users ?? 0);
|
|
165
168
|
return fresh && users.item(users.length - 1)?.innerText.trim() === ${JSON.stringify(text.trim())};
|
|
166
169
|
});
|
|
167
|
-
if (!turn)
|
|
170
|
+
if (!turn) {
|
|
171
|
+
const assistants = [...document.querySelectorAll(${JSON.stringify(assistantMessageSelector)})];
|
|
172
|
+
const fallback = assistants.length > ${beforeAssistantCount}
|
|
173
|
+
? assistants.at(-1)?.innerText.trim() ?? ""
|
|
174
|
+
: "";
|
|
175
|
+
return JSON.stringify({ reply: fallback, active: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) });
|
|
176
|
+
}
|
|
168
177
|
const user = turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).item(turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length - 1);
|
|
169
178
|
const unit = user?.closest(${JSON.stringify(contentUnitSelector)});
|
|
170
179
|
const units = [...turn.querySelectorAll(${JSON.stringify(contentUnitSelector)})];
|