agent-bridge-workbuddy 1.0.0 → 1.1.1
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/README.md
CHANGED
|
@@ -3,3 +3,5 @@
|
|
|
3
3
|
插件保留旧桥的 CodeBuddy CLI 调用,并支持 WorkBuddy 桌面端 CDP。默认 `auto` 优先使用 CLI;CLI 未登录时直接返回登录错误,不切换桌面端账号。没有 CLI 时才使用 CDP。未连接桌面端时默认自动启动 `WorkBuddy.exe`,CDP 默认端口为 `9229`。
|
|
4
4
|
|
|
5
5
|
将本目录作为本地插件安装,或发布后安装 `agent-bridge-workbuddy`;在 AUN 通道配置中把 `agent_plugin` 设为 `workbuddy`。插件串行处理消息;CLI 按 AUN 对端续接会话,CDP 使用当前桌面会话。可用 `transport` 固定为 `cli` 或 `cdp`。
|
|
6
|
+
|
|
7
|
+
当提示中以“只要、调用、使用、找、用”等表达指定专家(例如“只要 AI 短剧剧本专家,帮我写开场”)时,插件会切换到 WorkBuddy 桌面端,在专家中心按名称搜索并召唤唯一匹配的专家,再发送原始提示。专家请求需要桌面端 CDP;普通提示继续优先使用 CLI。简称没有唯一匹配时会提示用户指定完整专家名称。
|
package/config.schema.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
3
|
"properties": {
|
|
4
|
+
"aid": { "type": "string", "description": "关联的 AID" },
|
|
4
5
|
"transport": { "type": "string", "enum": ["auto", "cli", "cdp"], "default": "auto" },
|
|
5
6
|
"cliPath": { "type": "string", "description": "CodeBuddy CLI 路径" },
|
|
6
7
|
"model": { "type": "string", "description": "CLI 模型 ID" },
|
|
@@ -35,12 +35,23 @@ class WorkbuddyCdpClient {
|
|
|
35
35
|
}
|
|
36
36
|
close() { this.socket?.close(); this.socket = undefined; for (const pending of this.pending.values())
|
|
37
37
|
pending.reject(new Error("WorkBuddy CDP 已关闭")); this.pending.clear(); }
|
|
38
|
-
async direct(text, timeoutMs = 240_000) {
|
|
38
|
+
async direct(text, timeoutMs = 240_000, expert, newTask = false) {
|
|
39
|
+
if (expert)
|
|
40
|
+
await this.selectExpert(expert);
|
|
41
|
+
else if (newTask) {
|
|
42
|
+
if (!await this.evaluate(`(() => { const button = [...document.querySelectorAll('button,[role="button"]')].find((item) => item.textContent?.trim() === '新建任务'); button?.click(); return Boolean(button); })()`))
|
|
43
|
+
throw new Error("WorkBuddy 新建任务入口未找到");
|
|
44
|
+
}
|
|
45
|
+
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "WorkBuddy 输入框未就绪");
|
|
39
46
|
const before = JSON.parse(String(await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.assistantSelector)})].map(element => ({ id: element.closest('.cr-agent')?.getAttribute('data-message-id'), text: element.innerText })))`)));
|
|
40
47
|
const beforeIds = new Set(before.map((item) => item.id).filter((id) => Boolean(id)));
|
|
41
|
-
if (!await this.evaluate(`(() => { const element = document.querySelector(${JSON.stringify(exports.editorSelector)}); element
|
|
48
|
+
if (!await this.evaluate(`(() => { const element = document.querySelector(${JSON.stringify(exports.editorSelector)}); if (!element) return false; element.focus(); const selection = window.getSelection(); const range = document.createRange(); range.selectNodeContents(element); selection?.removeAllRanges(); selection?.addRange(range); return true; })()`))
|
|
42
49
|
throw new Error("WorkBuddy 输入框未找到");
|
|
50
|
+
await this.cdp("Input.dispatchKeyEvent", { type: "keyDown", key: "Backspace", code: "Backspace" });
|
|
51
|
+
await this.cdp("Input.dispatchKeyEvent", { type: "keyUp", key: "Backspace", code: "Backspace" });
|
|
43
52
|
await this.cdp("Input.insertText", { text });
|
|
53
|
+
if (!await this.evaluate(`document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() === ${JSON.stringify(text.trim())}`))
|
|
54
|
+
throw new Error("WorkBuddy 输入内容校验失败,未发送消息");
|
|
44
55
|
if (!await this.evaluate(`(() => { const button = document.querySelector('button[aria-label="发送"]'); if (!button || button.disabled) return false; button.click(); return true; })()`))
|
|
45
56
|
throw new Error("WorkBuddy 发送按钮不可用");
|
|
46
57
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -53,6 +64,44 @@ class WorkbuddyCdpClient {
|
|
|
53
64
|
}
|
|
54
65
|
throw new Error("WorkBuddy UI 回复超时");
|
|
55
66
|
}
|
|
67
|
+
async selectExpert(name) {
|
|
68
|
+
if (!await this.evaluate(`(() => { const element = [...document.querySelectorAll('button,[role="button"],a')].find((item) => item.textContent?.trim() === '专家·技能·连接器'); if (!element) return false; element.click(); return true; })()`))
|
|
69
|
+
throw new Error("WorkBuddy 专家入口未找到");
|
|
70
|
+
await this.waitFor(`Boolean(document.querySelector('input[placeholder="搜索专家"]'))`, "WorkBuddy 专家搜索框未就绪");
|
|
71
|
+
if (!await this.evaluate(`(() => { const element = document.querySelector('input[placeholder="搜索专家"]'); if (!element) return false; element.focus(); return true; })()`))
|
|
72
|
+
throw new Error("WorkBuddy 专家搜索框未找到");
|
|
73
|
+
for (const query of [...new Set([name, name.replace(/专家(?:团)?$/, "")])]) {
|
|
74
|
+
if (!await this.evaluate(`(() => { const element = document.querySelector('input[placeholder="搜索专家"]'); if (!element) return false; const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')?.set; setter?.call(element, ''); element.dispatchEvent(new Event('input', { bubbles: true })); element.focus(); return true; })()`))
|
|
75
|
+
throw new Error("WorkBuddy 专家搜索框未找到");
|
|
76
|
+
await this.cdp("Input.insertText", { text: query });
|
|
77
|
+
await this.waitFor(`(() => { const input = document.querySelector('input[placeholder="搜索专家"]'); const cards = document.querySelectorAll('article[role="button"] button[data-track-id="expert_summon_click"]'); return input?.value === ${JSON.stringify(query)} && cards.length > 0; })()`, "WorkBuddy 专家搜索超时");
|
|
78
|
+
const result = await this.evaluate(`(() => {
|
|
79
|
+
const cards = [...document.querySelectorAll('article[role="button"]')].filter(card => card.querySelector('button[data-track-id="expert_summon_click"]'));
|
|
80
|
+
const title = card => card.querySelector('[class*="_role_"]')?.textContent?.trim() || card.querySelector('img')?.alt || card.innerText.split('\\n').filter(Boolean)[1];
|
|
81
|
+
const exact = cards.filter(card => title(card) === ${JSON.stringify(name)});
|
|
82
|
+
const alias = cards.filter(card => card.querySelector('img')?.alt === ${JSON.stringify(name)} || card.querySelector('button[data-track-id="expert_summon_click"]')?.getAttribute('aria-label') === ${JSON.stringify(`召唤${name}`)});
|
|
83
|
+
const related = cards.filter(card => title(card)?.includes(${JSON.stringify(name)}) || ${JSON.stringify(name)}.includes(title(card) || ''));
|
|
84
|
+
const matches = exact.length ? exact : related.length === 1 ? related : alias.length === 1 ? alias : related;
|
|
85
|
+
if (matches.length !== 1) return { names: matches.map(title) };
|
|
86
|
+
const button = matches[0].querySelector('button[data-track-id="expert_summon_click"]');
|
|
87
|
+
button.click(); return { selected: true };
|
|
88
|
+
})()`);
|
|
89
|
+
if (result.selected)
|
|
90
|
+
return;
|
|
91
|
+
if (result.names?.length)
|
|
92
|
+
throw new Error(`WorkBuddy 专家匹配不唯一,请指定完整名称:${result.names.join("、")}`);
|
|
93
|
+
}
|
|
94
|
+
throw new Error(`未找到 WorkBuddy 专家:${name}`);
|
|
95
|
+
}
|
|
96
|
+
async waitFor(expression, message) {
|
|
97
|
+
const deadline = Date.now() + this.timeoutMs;
|
|
98
|
+
while (Date.now() < deadline) {
|
|
99
|
+
if (await this.evaluate(expression))
|
|
100
|
+
return;
|
|
101
|
+
await sleep(200);
|
|
102
|
+
}
|
|
103
|
+
throw new Error(message);
|
|
104
|
+
}
|
|
56
105
|
onMessage(raw) { let message; try {
|
|
57
106
|
message = JSON.parse(raw);
|
|
58
107
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WorkbuddyPlugin = exports.workbuddyManifest = void 0;
|
|
4
|
+
exports.extractExpertQuery = extractExpertQuery;
|
|
4
5
|
const node_child_process_1 = require("node:child_process");
|
|
5
6
|
const node_fs_1 = require("node:fs");
|
|
6
7
|
const node_os_1 = require("node:os");
|
|
@@ -8,10 +9,16 @@ const node_path_1 = require("node:path");
|
|
|
8
9
|
const cdp_client_1 = require("./cdp-client");
|
|
9
10
|
const cli_client_1 = require("./cli-client");
|
|
10
11
|
exports.workbuddyManifest = {
|
|
11
|
-
id: "workbuddy", version: "1.
|
|
12
|
+
id: "workbuddy", version: "1.1.1", displayName: "WorkBuddy", type: "agent",
|
|
12
13
|
entry: "dist/index.js", apiVersion: "1", process: "in-process",
|
|
13
14
|
capabilities: ["chat", "health"], configSchema: "config.schema.json",
|
|
14
15
|
};
|
|
16
|
+
function extractExpertQuery(task) {
|
|
17
|
+
const text = task.normalize("NFKC");
|
|
18
|
+
const match = text.match(/^(?:请)?(?:只要|调用|使用|帮我找|找|用|选择|切换到)\s*[“「"]?([^,。!?,!?\n“「"]{2,30}?专家(?:团)?)(?!技能)/)
|
|
19
|
+
?? text.match(/^([A-Za-z\u4e00-\u9fff]{2,24}?专家(?:团)?)(?=[,。!,!::\s]|$)/);
|
|
20
|
+
return match?.[1]?.trim() || undefined;
|
|
21
|
+
}
|
|
15
22
|
function findExecutable(configured) {
|
|
16
23
|
if (configured)
|
|
17
24
|
return (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)) ? (0, node_path_1.resolve)(configured) : undefined;
|
|
@@ -29,6 +36,7 @@ class WorkbuddyPlugin {
|
|
|
29
36
|
timeoutMs = 240_000;
|
|
30
37
|
queue = Promise.resolve();
|
|
31
38
|
failure;
|
|
39
|
+
expertSession = false;
|
|
32
40
|
constructor(options = {}) {
|
|
33
41
|
this.options = options;
|
|
34
42
|
}
|
|
@@ -82,11 +90,20 @@ class WorkbuddyPlugin {
|
|
|
82
90
|
}
|
|
83
91
|
async complete(request) {
|
|
84
92
|
const timeout = request.timeoutMs ?? this.timeoutMs;
|
|
93
|
+
const expert = extractExpertQuery(request.task);
|
|
94
|
+
if (expert) {
|
|
95
|
+
if (!this.client?.connected())
|
|
96
|
+
throw new Error(`专家请求需要已连接的 WorkBuddy 桌面端:${expert}`);
|
|
97
|
+
this.expertSession = true;
|
|
98
|
+
return this.client.direct(request.task, timeout, expert);
|
|
99
|
+
}
|
|
85
100
|
if (this.cliPath && this.transport !== "cdp")
|
|
86
101
|
return (0, cli_client_1.callCli)(this.cliPath, request.task, request.sessionId ?? "local-test", timeout, this.model);
|
|
87
102
|
if (!this.client?.connected())
|
|
88
103
|
throw new Error("WorkBuddy CDP 未连接");
|
|
89
|
-
|
|
104
|
+
const reset = this.expertSession;
|
|
105
|
+
this.expertSession = false;
|
|
106
|
+
return this.client.direct(request.task, timeout, undefined, reset);
|
|
90
107
|
}
|
|
91
108
|
async connectOrStart(config, logger, port) {
|
|
92
109
|
try {
|
package/package.json
CHANGED
|
@@ -1,12 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-bridge-workbuddy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "agent-bridge 的 CodeBuddy CLI 插件。",
|
|
5
|
-
"keywords": [
|
|
6
|
-
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mu-agent-bridge-plugin",
|
|
7
|
+
"mu-agent-bridge-agent"
|
|
8
|
+
],
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git",
|
|
12
|
+
"directory": "packages/workbuddy"
|
|
13
|
+
},
|
|
7
14
|
"main": "dist/src-ts/plugins/workbuddy/index.js",
|
|
8
|
-
"files": [
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
15
|
+
"files": [
|
|
16
|
+
"dist/",
|
|
17
|
+
"plugin.json",
|
|
18
|
+
"config.schema.json",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"agentBridge": {
|
|
22
|
+
"plugin": "./plugin.json",
|
|
23
|
+
"type": "agent"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public",
|
|
27
|
+
"registry": "https://registry.npmjs.org"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22"
|
|
31
|
+
}
|
|
12
32
|
}
|
package/plugin.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "workbuddy",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"type": "agent",
|
|
5
5
|
"displayName": "WorkBuddy(CodeBuddy CLI)",
|
|
6
6
|
"entry": "dist/src-ts/plugins/workbuddy/index.js",
|
|
7
7
|
"apiVersion": "1",
|
|
8
8
|
"process": "in-process",
|
|
9
|
-
"capabilities": [
|
|
9
|
+
"capabilities": [
|
|
10
|
+
"chat",
|
|
11
|
+
"health"
|
|
12
|
+
],
|
|
10
13
|
"configSchema": "config.schema.json"
|
|
11
14
|
}
|