mu-agent-bridge 1.0.55 → 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.
- package/dist/src-ts/cli.js +18 -0
- package/dist/src-ts/control-server.js +1 -1
- package/dist/src-ts/core-updater.js +1 -0
- package/dist/src-ts/plugins/qwenwork/openai-adapter.js +7 -8
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +14 -2
- package/dist/src-ts/plugins/workbuddy/index.js +1 -1
- package/dist/tests-ts/control-server.test.js +25 -2
- package/dist/tests-ts/core-updater.test.js +5 -3
- package/dist/tests-ts/workbuddy-plugin.test.js +22 -0
- package/package.json +1 -1
- package/plugins/dsh/dist/src-ts/plugins/qwenwork/openai-adapter.js +7 -8
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/openai-adapter.js +7 -8
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +14 -2
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +1 -1
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
package/dist/src-ts/cli.js
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkbuddyCdpClient = exports.assistantSelector = exports.editorSelector = void 0;
|
|
3
|
+
exports.WorkbuddyCdpClient = exports.chineseLocaleScript = exports.assistantSelector = exports.editorSelector = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const attachments_1 = require("../../attachments");
|
|
6
6
|
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
|
|
@@ -17,6 +17,16 @@ function interactionChoice(value, options) {
|
|
|
17
17
|
}
|
|
18
18
|
exports.editorSelector = '[data-slate-editor][contenteditable="true"]';
|
|
19
19
|
exports.assistantSelector = '.cr-agent[data-message-id] [data-assistant-content]';
|
|
20
|
+
exports.chineseLocaleScript = `(() => {
|
|
21
|
+
const key = 'CODEBUDDY_IDE_STORAGE_LANG';
|
|
22
|
+
const legacyKey = 'workbuddy-language';
|
|
23
|
+
const locale = 'zh-CN';
|
|
24
|
+
const changed = localStorage.getItem(key) !== locale || localStorage.getItem(legacyKey) !== locale || /[?&]locale=en-US(?:&|$)/i.test(location.search);
|
|
25
|
+
localStorage.setItem(key, locale);
|
|
26
|
+
localStorage.setItem(legacyKey, locale);
|
|
27
|
+
if (changed) setTimeout(() => location.reload(), 0);
|
|
28
|
+
return changed;
|
|
29
|
+
})()`;
|
|
20
30
|
class WorkbuddyCdpClient {
|
|
21
31
|
attachmentInput = true;
|
|
22
32
|
socket;
|
|
@@ -95,6 +105,8 @@ class WorkbuddyCdpClient {
|
|
|
95
105
|
this.socket.onmessage = (event) => this.onMessage(String(event.data));
|
|
96
106
|
try {
|
|
97
107
|
await new Promise((resolve, reject) => { const socket = this.socket; socket.onopen = () => resolve(); socket.onerror = () => reject(new Error("WorkBuddy CDP WebSocket 连接失败")); });
|
|
108
|
+
if (await this.evaluate(exports.chineseLocaleScript))
|
|
109
|
+
await this.waitFor("!/[?&]locale=en-US(?:&|$)/i.test(location.search)", "WorkBuddy 中文界面切换超时");
|
|
98
110
|
}
|
|
99
111
|
catch (error) {
|
|
100
112
|
this.close();
|
|
@@ -108,7 +120,7 @@ class WorkbuddyCdpClient {
|
|
|
108
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 }; })()`);
|
|
109
121
|
if (before.loginRequired)
|
|
110
122
|
throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
|
|
111
|
-
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; })()`))
|
|
112
124
|
throw new Error("WorkBuddy 新建任务入口未找到或不可用");
|
|
113
125
|
// WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
|
|
114
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
|
+
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
|
};
|
|
@@ -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 () =>
|
|
63
|
-
startDaemon: async () =>
|
|
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 });
|
|
@@ -51,6 +51,12 @@ const node_test_1 = __importDefault(require("node:test"));
|
|
|
51
51
|
const node_vm_1 = require("node:vm");
|
|
52
52
|
const workbuddy_1 = require("../src-ts/plugins/workbuddy");
|
|
53
53
|
const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
|
|
54
|
+
(0, node_test_1.default)("WorkBuddy CDP 启动后固定中文语言偏好", () => {
|
|
55
|
+
strict_1.default.match(cdp_client_1.chineseLocaleScript, /CODEBUDDY_IDE_STORAGE_LANG/);
|
|
56
|
+
strict_1.default.match(cdp_client_1.chineseLocaleScript, /workbuddy-language/);
|
|
57
|
+
strict_1.default.match(cdp_client_1.chineseLocaleScript, /zh-CN/);
|
|
58
|
+
strict_1.default.match(cdp_client_1.chineseLocaleScript, /location\.reload/);
|
|
59
|
+
});
|
|
54
60
|
(0, node_test_1.default)("workbuddy CDP 拒绝连接其他应用的调试页面", async () => {
|
|
55
61
|
const client = new cdp_client_1.WorkbuddyCdpClient({ fetchImpl: async () => new Response(JSON.stringify([{ type: "page", title: "Other App", webSocketDebuggerUrl: "ws://127.0.0.1:9229/devtools/page/1" }])) });
|
|
56
62
|
await strict_1.default.rejects(() => client.connect(), /未找到 WorkBuddy 页面 target/);
|
|
@@ -972,6 +978,22 @@ const cdp_client_1 = require("../src-ts/plugins/workbuddy/cdp-client");
|
|
|
972
978
|
strict_1.default.equal(conversationId, null);
|
|
973
979
|
strict_1.default.equal(messages, 0);
|
|
974
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
|
+
});
|
|
975
997
|
(0, node_test_1.default)("workbuddy CDP 新建不接受缺少任务身份的旧消息界面直接清空", async () => {
|
|
976
998
|
let messages = 1;
|
|
977
999
|
const button = { textContent: "新建任务", disabled: false, getAttribute: () => null, click: () => { messages = 0; } };
|
package/package.json
CHANGED
|
@@ -73,13 +73,11 @@ class OpenAIAdapter {
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
const session = req.headers["x-evolcore-session-id"];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkbuddyCdpClient = exports.assistantSelector = exports.editorSelector = void 0;
|
|
3
|
+
exports.WorkbuddyCdpClient = exports.chineseLocaleScript = exports.assistantSelector = exports.editorSelector = void 0;
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const attachments_1 = require("../../attachments");
|
|
6
6
|
function sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
|
|
@@ -17,6 +17,16 @@ function interactionChoice(value, options) {
|
|
|
17
17
|
}
|
|
18
18
|
exports.editorSelector = '[data-slate-editor][contenteditable="true"]';
|
|
19
19
|
exports.assistantSelector = '.cr-agent[data-message-id] [data-assistant-content]';
|
|
20
|
+
exports.chineseLocaleScript = `(() => {
|
|
21
|
+
const key = 'CODEBUDDY_IDE_STORAGE_LANG';
|
|
22
|
+
const legacyKey = 'workbuddy-language';
|
|
23
|
+
const locale = 'zh-CN';
|
|
24
|
+
const changed = localStorage.getItem(key) !== locale || localStorage.getItem(legacyKey) !== locale || /[?&]locale=en-US(?:&|$)/i.test(location.search);
|
|
25
|
+
localStorage.setItem(key, locale);
|
|
26
|
+
localStorage.setItem(legacyKey, locale);
|
|
27
|
+
if (changed) setTimeout(() => location.reload(), 0);
|
|
28
|
+
return changed;
|
|
29
|
+
})()`;
|
|
20
30
|
class WorkbuddyCdpClient {
|
|
21
31
|
attachmentInput = true;
|
|
22
32
|
socket;
|
|
@@ -95,6 +105,8 @@ class WorkbuddyCdpClient {
|
|
|
95
105
|
this.socket.onmessage = (event) => this.onMessage(String(event.data));
|
|
96
106
|
try {
|
|
97
107
|
await new Promise((resolve, reject) => { const socket = this.socket; socket.onopen = () => resolve(); socket.onerror = () => reject(new Error("WorkBuddy CDP WebSocket 连接失败")); });
|
|
108
|
+
if (await this.evaluate(exports.chineseLocaleScript))
|
|
109
|
+
await this.waitFor("!/[?&]locale=en-US(?:&|$)/i.test(location.search)", "WorkBuddy 中文界面切换超时");
|
|
98
110
|
}
|
|
99
111
|
catch (error) {
|
|
100
112
|
this.close();
|
|
@@ -108,7 +120,7 @@ class WorkbuddyCdpClient {
|
|
|
108
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 }; })()`);
|
|
109
121
|
if (before.loginRequired)
|
|
110
122
|
throw new Error("WorkBuddy 尚未登录,请先在桌面端登录后重试");
|
|
111
|
-
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; })()`))
|
|
112
124
|
throw new Error("WorkBuddy 新建任务入口未找到或不可用");
|
|
113
125
|
// WorkBuddy 首页不分配任务 ID;此时仅接受无选中任务的空白首页。
|
|
114
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
|
+
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
|
};
|