mu-agent-bridge 1.0.41 → 1.0.43
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 +4 -0
- package/dist/src-ts/agent-availability.js +2 -1
- package/dist/src-ts/control-server.js +4 -0
- package/dist/src-ts/headless-account.patch.yml +10 -0
- package/dist/src-ts/plugin-manager.js +16 -0
- package/dist/src-ts/plugin-manifest.js +3 -1
- package/dist/src-ts/plugins/abweb/public/app.js +34 -0
- package/dist/src-ts/plugins/abweb/public/index.html +10 -0
- package/dist/src-ts/plugins/abweb/public/style.css +3 -0
- package/dist/src-ts/plugins/aun/index.js +5 -5
- package/dist/src-ts/plugins/aun/public/app.js +1 -4
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/chatgpt/index.js +41 -5
- package/dist/src-ts/plugins/claude-cli/index.js +14 -0
- package/dist/src-ts/plugins/codex-cli/index.js +14 -0
- package/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/dist/src-ts/plugins/doubao/index.js +14 -12
- package/dist/src-ts/plugins/local-cli/agent.js +321 -0
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +3 -1
- package/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/dist/src-ts/plugins/workbuddy/index.js +35 -21
- package/dist/tests-ts/abweb-cli.test.js +65 -0
- package/dist/tests-ts/agent-availability.test.js +15 -1
- package/dist/tests-ts/aun-default-owner-page.test.js +1 -1
- package/dist/tests-ts/aun-plugin.test.js +16 -6
- package/dist/tests-ts/bundled-package.test.js +1 -1
- package/dist/tests-ts/control-server.test.js +20 -0
- package/dist/tests-ts/desktop-launch.test.js +98 -0
- package/dist/tests-ts/installer-assets.test.js +30 -7
- package/dist/tests-ts/local-cli.test.js +170 -0
- package/dist/tests-ts/platform-integration.test.js +1 -1
- package/dist/tests-ts/plugin-manager.test.js +18 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +63 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +34 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +30 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +10 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +3 -0
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -5
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +1 -4
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/chatgpt/config.schema.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +41 -5
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +191 -0
- package/plugins/claude-cli/config.schema.json +37 -0
- package/plugins/claude-cli/dist/src-ts/agent.js +10 -0
- package/plugins/claude-cli/dist/src-ts/plugin.js +13 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +14 -0
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +321 -0
- package/plugins/claude-cli/package.json +35 -0
- package/plugins/claude-cli/plugin.json +15 -0
- package/plugins/codex-cli/config.schema.json +37 -0
- package/plugins/codex-cli/dist/src-ts/agent.js +10 -0
- package/plugins/codex-cli/dist/src-ts/plugin.js +13 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +14 -0
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +321 -0
- package/plugins/codex-cli/package.json +35 -0
- package/plugins/codex-cli/plugin.json +15 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +14 -12
- package/plugins/dsh/dist/src-ts/headless-account.patch.yml +10 -0
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +93 -2
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +191 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +3 -1
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +35 -21
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -162,3 +162,7 @@ mab feishu unbind cli_你的应用ID
|
|
|
162
162
|
```
|
|
163
163
|
|
|
164
164
|
`configure` 保存凭据及可选关联后,需要在 ABWeb 重载飞书插件以连接机器人。非交互终端请使用网页密码框;不要把 Secret 写进命令行、脚本或仓库。
|
|
165
|
+
|
|
166
|
+
### 本地 CLI Agent
|
|
167
|
+
|
|
168
|
+
新增两个独立插件:`agent-bridge-codex-cli`(Codex CLI)与 `agent-bridge-claude-cli`(Claude Code CLI),随核心默认打包。支持在 abweb 配置、启停及通过通道关联 AID,沿用本机 CLI 的登录和默认模型。详见 [本地 CLI 插件使用说明](docs/local-cli-agents.md)。
|
|
@@ -7,7 +7,8 @@ async function agentAvailability(id, config, status) {
|
|
|
7
7
|
const configured = typeof config.executablePath === "string" ? config.executablePath : undefined;
|
|
8
8
|
const executable = id === "workbuddy" ? (0, workbuddy_1.findExecutable)(configured)
|
|
9
9
|
: id === "qwenwork" ? await (0, desktop_launch_1.findWindowsExecutable)("QwenWorkCN.exe", configured)
|
|
10
|
-
: id === "doubao" ? await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", configured)
|
|
10
|
+
: id === "doubao" ? await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", configured)
|
|
11
|
+
: id === "dsh" ? await (0, desktop_launch_1.findWindowsExecutable)("DeepSeek Harness.exe", configured) : true;
|
|
11
12
|
if (!executable)
|
|
12
13
|
return { available: false, reason: "未找到可执行文件" };
|
|
13
14
|
return { available: status === "ready", reason: status === "ready" ? "" : "插件未就绪" };
|
|
@@ -273,6 +273,10 @@ class ControlServer {
|
|
|
273
273
|
await this.options.manager.reload(id);
|
|
274
274
|
return this.json(res, 200, { ok: true });
|
|
275
275
|
}
|
|
276
|
+
if (method === "POST" && parts[3] === "restart-desktop") {
|
|
277
|
+
await this.options.manager.restartDesktop(id);
|
|
278
|
+
return this.json(res, 200, { ok: true });
|
|
279
|
+
}
|
|
276
280
|
if (method === "POST" && parts[3] === "update") {
|
|
277
281
|
const body = await this.body(req);
|
|
278
282
|
return this.json(res, 200, { ok: true, plugin: await this.options.manager.upgradePackage(id, body.packageName, body.version) });
|
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
# 复用 DeepSeek Harness 桌面端登录账号的模型。
|
|
2
|
+
# 新版 headless 默认禁用凭据及账号服务,桥接需要显式恢复才能复用桌面登录。
|
|
3
|
+
- id: credentials
|
|
4
|
+
disabled: false
|
|
5
|
+
|
|
6
|
+
- id: authorization
|
|
7
|
+
disabled: false
|
|
8
|
+
|
|
9
|
+
- id: deepseek-account
|
|
10
|
+
disabled: false
|
|
11
|
+
|
|
2
12
|
- id: agent-default-model
|
|
3
13
|
config:
|
|
4
14
|
provider: deepseek-account
|
|
@@ -21,6 +21,8 @@ const channel_sessions_1 = require("./channel-sessions");
|
|
|
21
21
|
const session_management_1 = require("./session-management");
|
|
22
22
|
const _2020_1 = __importDefault(require("ajv/dist/2020"));
|
|
23
23
|
const attachments_1 = require("./attachments");
|
|
24
|
+
const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
25
|
+
const workbuddy_1 = require("./plugins/workbuddy");
|
|
24
26
|
const logger = { info() { }, warn() { }, error() { } };
|
|
25
27
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
26
28
|
class PluginManager {
|
|
@@ -507,6 +509,20 @@ class PluginManager {
|
|
|
507
509
|
await this.logs.flush();
|
|
508
510
|
}
|
|
509
511
|
async reload(id) { await this.stop(id, "reload"); await this.start(id); }
|
|
512
|
+
async restartDesktop(id) {
|
|
513
|
+
if (id !== "workbuddy")
|
|
514
|
+
throw new agent_1.AgentError(`不支持桌面重启:${id}`);
|
|
515
|
+
const config = await this.config(id);
|
|
516
|
+
if (config.transport !== "cdp")
|
|
517
|
+
throw new agent_1.AgentError("请先将 WorkBuddy 设置为 CDP 模式");
|
|
518
|
+
const executable = (0, workbuddy_1.findExecutable)(typeof config.executablePath === "string" ? config.executablePath : undefined);
|
|
519
|
+
if (!executable)
|
|
520
|
+
throw new agent_1.AgentError("未找到 WorkBuddy 桌面程序,无法重启");
|
|
521
|
+
await this.stop(id);
|
|
522
|
+
const processIds = await (0, desktop_launch_1.terminateWindowsDesktop)(executable);
|
|
523
|
+
this.log("info", "后台已清理 WorkBuddy 旧进程,将以 CDP 模式启动", { processIds, executable }, id);
|
|
524
|
+
await this.start(id);
|
|
525
|
+
}
|
|
510
526
|
async uninstall(id) {
|
|
511
527
|
const loaded = this.plugins.get(id);
|
|
512
528
|
if (!loaded)
|
|
@@ -21,6 +21,8 @@ function validateManifest(value, baseDir) {
|
|
|
21
21
|
throw new agent_1.AgentError("invalid plugin capabilities");
|
|
22
22
|
if (m.type && !["agent", "channel", "management", "other"].includes(m.type))
|
|
23
23
|
throw new agent_1.AgentError("invalid plugin type");
|
|
24
|
+
if (m.executionMode !== undefined && m.executionMode !== "cli" && m.executionMode !== "cdp")
|
|
25
|
+
throw new agent_1.AgentError("插件运行方式无效");
|
|
24
26
|
const inferredType = m.capabilities.includes("channel") ? "channel" : m.capabilities.includes("chat") ? "agent" : m.capabilities.includes("management") ? "management" : "other";
|
|
25
27
|
if (m.type && inferredType !== "other" && m.type !== inferredType)
|
|
26
28
|
throw new agent_1.AgentError("插件类型与能力不匹配");
|
|
@@ -39,5 +41,5 @@ function validateManifest(value, baseDir) {
|
|
|
39
41
|
if ((0, node_path_1.isAbsolute)(m.entry) || rel.startsWith(`..${node_path_1.sep}`) || rel === "..")
|
|
40
42
|
throw new agent_1.AgentError("plugin entry path escapes plugin directory");
|
|
41
43
|
}
|
|
42
|
-
return { id: m.id, version: m.version, displayName: m.displayName ?? m.id, type: m.type ?? inferredType, entry: m.entry, apiVersion: m.apiVersion, process: m.process ?? "in-process", capabilities: [...m.capabilities], configSchema: m.configSchema, routes: m.routes, ...(m.dependencies ? { dependencies: { ...m.dependencies } } : {}), ...(m.managementPage ? { managementPage: m.managementPage } : {}), ...(m.clientDownloadUrl ? { clientDownloadUrl: m.clientDownloadUrl } : {}) };
|
|
44
|
+
return { id: m.id, version: m.version, displayName: m.displayName ?? m.id, type: m.type ?? inferredType, entry: m.entry, apiVersion: m.apiVersion, process: m.process ?? "in-process", capabilities: [...m.capabilities], ...(m.executionMode ? { executionMode: m.executionMode } : {}), configSchema: m.configSchema, routes: m.routes, ...(m.dependencies ? { dependencies: { ...m.dependencies } } : {}), ...(m.managementPage ? { managementPage: m.managementPage } : {}), ...(m.clientDownloadUrl ? { clientDownloadUrl: m.clientDownloadUrl } : {}) };
|
|
43
45
|
}
|
|
@@ -308,6 +308,8 @@
|
|
|
308
308
|
const status = document.createElement("span");
|
|
309
309
|
status.className = `status status-${plugin.status}`;
|
|
310
310
|
status.textContent = plugin.status;
|
|
311
|
+
if (plugin.type === "agent")
|
|
312
|
+
type.textContent += ` · ${{ cli: "CLI", cdp: "CDP" }[plugin.manifest?.executionMode ?? ""] ?? "未声明"}`;
|
|
311
313
|
const dependencies = Object.entries(plugin.manifest?.dependencies ?? {});
|
|
312
314
|
if (dependencies.length) {
|
|
313
315
|
const detail = document.createElement("small");
|
|
@@ -340,6 +342,8 @@
|
|
|
340
342
|
}
|
|
341
343
|
const controls = document.createElement("td");
|
|
342
344
|
controls.className = "controls";
|
|
345
|
+
if (plugin.manifest?.executionMode === "cli")
|
|
346
|
+
controls.append(button("配置", `配置 ${plugin.id}`, () => configureCli(plugin)));
|
|
343
347
|
if (pluginVersions[plugin.id]?.state === "update-available" && plugin.path.includes("node_modules")) {
|
|
344
348
|
controlsUpgrade(plugin.id);
|
|
345
349
|
controls.append(button("下载", `下载 ${plugin.id}`, async () => { await request(`/api/plugins/${encodeURIComponent(plugin.id)}/download`, "POST", { packageName: plugin.packageName, version: pluginVersions[plugin.id]?.latestVersion }); await load(); }));
|
|
@@ -367,6 +371,36 @@
|
|
|
367
371
|
pluginsNode.append(row);
|
|
368
372
|
}
|
|
369
373
|
}
|
|
374
|
+
let configuringCli = "";
|
|
375
|
+
async function configureCli(plugin) {
|
|
376
|
+
const config = await request(`/api/plugins/${encodeURIComponent(plugin.id)}/config`);
|
|
377
|
+
configuringCli = plugin.id;
|
|
378
|
+
node("#cli-config-title").textContent = `${plugin.displayName} 配置`;
|
|
379
|
+
node("#cli-config-cwd").value = config.cwd ?? "";
|
|
380
|
+
node("#cli-config-executable").value = config.executablePath ?? "";
|
|
381
|
+
node("#cli-config-timeout").value = String(config.timeoutMs ?? 240000);
|
|
382
|
+
node("#cli-config-error").hidden = true;
|
|
383
|
+
node("#cli-config-dialog").showModal();
|
|
384
|
+
}
|
|
385
|
+
node("#cli-config-cancel").onclick = () => node("#cli-config-dialog").close();
|
|
386
|
+
node("#cli-config-form").onsubmit = event => {
|
|
387
|
+
event.preventDefault();
|
|
388
|
+
const save = node("#cli-config-save");
|
|
389
|
+
save.disabled = true;
|
|
390
|
+
void request(`/api/plugins/${encodeURIComponent(configuringCli)}/config`, "PATCH", {
|
|
391
|
+
cwd: node("#cli-config-cwd").value.trim(),
|
|
392
|
+
executablePath: node("#cli-config-executable").value.trim(),
|
|
393
|
+
timeoutMs: Number(node("#cli-config-timeout").value),
|
|
394
|
+
}).then(() => {
|
|
395
|
+
node("#cli-config-dialog").close();
|
|
396
|
+
showResult("CLI 配置已保存", "请启动插件,然后在 Channel 设置中关联 AID。", false);
|
|
397
|
+
return load(false);
|
|
398
|
+
}).catch(cause => {
|
|
399
|
+
const detail = node("#cli-config-error");
|
|
400
|
+
detail.textContent = String(cause);
|
|
401
|
+
detail.hidden = false;
|
|
402
|
+
}).finally(() => { save.disabled = false; });
|
|
403
|
+
};
|
|
370
404
|
function renderMessages(messages) {
|
|
371
405
|
messagesNode.replaceChildren();
|
|
372
406
|
messagesEmpty.hidden = messages.length > 0;
|
|
@@ -21,6 +21,16 @@
|
|
|
21
21
|
<div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
|
|
22
22
|
<p id="error" class="error" role="alert" hidden></p>
|
|
23
23
|
</main>
|
|
24
|
+
<dialog id="cli-config-dialog" aria-labelledby="cli-config-title">
|
|
25
|
+
<h2 id="cli-config-title">CLI 配置</h2><p class="muted">沿用后台运行用户的 CLI 登录、默认模型及权限。AID 请在 Channel 设置关联。</p>
|
|
26
|
+
<form id="cli-config-form">
|
|
27
|
+
<label for="cli-config-cwd">工作目录(必填,绝对路径)</label><input id="cli-config-cwd" required placeholder="C:\Users\用户名\projects\demo">
|
|
28
|
+
<label for="cli-config-executable">CLI 程序路径(可选,留空使用 PATH)</label><input id="cli-config-executable" placeholder="例如 C:\Users\用户名\AppData\Roaming\npm\codex.cmd">
|
|
29
|
+
<label for="cli-config-timeout">任务超时(毫秒)</label><input id="cli-config-timeout" type="number" min="1" max="2147483647" required value="240000">
|
|
30
|
+
<p id="cli-config-error" class="error" role="alert" hidden></p>
|
|
31
|
+
<div><button id="cli-config-save" type="submit">保存配置</button><button id="cli-config-cancel" type="button">取消</button></div>
|
|
32
|
+
</form>
|
|
33
|
+
</dialog>
|
|
24
34
|
<dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
|
|
25
35
|
<h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
|
|
26
36
|
</dialog>
|
|
@@ -60,3 +60,6 @@ th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child
|
|
|
60
60
|
#operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
|
|
61
61
|
#operation-result::backdrop { background: rgb(0 0 0 / 35%); }
|
|
62
62
|
#operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
|
|
63
|
+
|
|
64
|
+
#cli-config-form { display: grid; min-width: min(70vw, 480px); }
|
|
65
|
+
#cli-config-form input { width: 100%; }
|
|
@@ -214,7 +214,7 @@ class AunPlugin {
|
|
|
214
214
|
if (typeof aid === "string" && aidPattern.test(aid))
|
|
215
215
|
this.createdAids.add(aid);
|
|
216
216
|
}
|
|
217
|
-
for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from"
|
|
217
|
+
for (const key of ["aid", "owner_aid", "aun_path", "encryption_seed", "root_ca_path", "allow_from"]) {
|
|
218
218
|
if (this.config[key] !== undefined && typeof this.config[key] !== "string")
|
|
219
219
|
throw new Error(`${key} 必须是字符串`);
|
|
220
220
|
}
|
|
@@ -419,12 +419,12 @@ class AunPlugin {
|
|
|
419
419
|
throw new Error("请求内容过大");
|
|
420
420
|
}
|
|
421
421
|
const body = JSON.parse(raw || "{}");
|
|
422
|
-
for (const key of ["
|
|
422
|
+
for (const key of ["allow_from", "aun_path"])
|
|
423
423
|
if (typeof body[key] !== "string")
|
|
424
424
|
return this.json(response, 400, { error: `${key} 必须是字符串` });
|
|
425
425
|
if (body.encryption_seed !== undefined && typeof body.encryption_seed !== "string")
|
|
426
426
|
return this.json(response, 400, { error: "encryption_seed 必须是字符串" });
|
|
427
|
-
this.config = { ...this.config,
|
|
427
|
+
this.config = { ...this.config, allow_from: body.allow_from, aun_path: body.aun_path, ...(body.encryption_seed ? { encryption_seed: body.encryption_seed } : {}) };
|
|
428
428
|
await this.persist();
|
|
429
429
|
this.context?.logger.info("AUN 通道配置已保存");
|
|
430
430
|
this.json(response, 200, { ok: true });
|
|
@@ -741,7 +741,7 @@ class AunPlugin {
|
|
|
741
741
|
}
|
|
742
742
|
};
|
|
743
743
|
async onMessage(payload, aid) {
|
|
744
|
-
if (!this.acceptingMessages)
|
|
744
|
+
if (!this.acceptingMessages || !this.bindings[aid])
|
|
745
745
|
return;
|
|
746
746
|
const message = (0, messages_1.normalizeAunMessage)(payload, aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
|
|
747
747
|
if (!message || !this.seen.add(message.messageId ? `${aid}:${message.messageId}` : ""))
|
|
@@ -793,7 +793,7 @@ class AunPlugin {
|
|
|
793
793
|
const channelMessage = {
|
|
794
794
|
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
795
795
|
...(attachments.length ? { attachments } : {}),
|
|
796
|
-
agentPlugin: this.bindings[aid]
|
|
796
|
+
agentPlugin: this.bindings[aid],
|
|
797
797
|
reply: async (text) => {
|
|
798
798
|
if (!text.trim())
|
|
799
799
|
return;
|
|
@@ -29,10 +29,7 @@
|
|
|
29
29
|
agents = (plugins.plugins || []).filter((plugin) => plugin.type === "agent");
|
|
30
30
|
if (refreshForm) createForm.elements.ownerAid.value = config.owner_aid || "";
|
|
31
31
|
await Promise.all(agents.map(async (agent) => { agent.configAid = (await request(`/api/plugins/${encodeURIComponent(agent.id)}/config`)).aid || ""; }));
|
|
32
|
-
const target = configForm.elements.agent_plugin;
|
|
33
32
|
if (refreshForm) {
|
|
34
|
-
target.replaceChildren(new Option("默认 Agent", ""), ...agents.map((agent) => new Option(agent.displayName || agent.id, agent.id)));
|
|
35
|
-
target.value = config.agent_plugin || "";
|
|
36
33
|
for (const key of ["allow_from", "aun_path"]) configForm.elements[key].value = config[key] || "";
|
|
37
34
|
configForm.elements.encryption_seed.value = "";
|
|
38
35
|
}
|
|
@@ -76,7 +73,7 @@
|
|
|
76
73
|
return load();
|
|
77
74
|
}).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
|
|
78
75
|
};
|
|
79
|
-
configForm.onsubmit = (event) => { event.preventDefault(); const changes = {
|
|
76
|
+
configForm.onsubmit = (event) => { event.preventDefault(); const changes = { allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
|
|
80
77
|
if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
|
|
81
78
|
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
|
|
82
79
|
};
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
<p id="status" role="status"></p>
|
|
8
8
|
<div class="table-scroll"><table><thead><tr><th>AID</th><th>来源</th><th>已关联 Agent</th><th>操作</th></tr></thead><tbody id="identities"></tbody></table></div>
|
|
9
9
|
<form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
|
|
10
|
-
<form id="config-form"><h2>通道配置</h2><div class="fields"><label
|
|
10
|
+
<form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
|
|
11
11
|
</main>
|
|
12
12
|
<script src="/abweb/channels/aun/app.js"></script>
|
|
13
13
|
</body>
|
|
@@ -4,6 +4,9 @@ exports.ChatgptCdpClient = exports.ChatgptPlugin = exports.chatgptManifest = voi
|
|
|
4
4
|
const node_crypto_1 = require("node:crypto");
|
|
5
5
|
const cdp_client_1 = require("./cdp-client");
|
|
6
6
|
const session_store_1 = require("./session-store");
|
|
7
|
+
const desktop_launch_1 = require("../desktop-launch");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
7
10
|
exports.chatgptManifest = {
|
|
8
11
|
id: "chatgpt",
|
|
9
12
|
version: "1.0.10",
|
|
@@ -35,6 +38,8 @@ class ChatgptPlugin {
|
|
|
35
38
|
historyScope = "";
|
|
36
39
|
historyGeneration = 0;
|
|
37
40
|
historyProvider;
|
|
41
|
+
ownedProcess;
|
|
42
|
+
reconnect;
|
|
38
43
|
constructor(options = {}) {
|
|
39
44
|
this.options = options;
|
|
40
45
|
}
|
|
@@ -87,21 +92,52 @@ class ChatgptPlugin {
|
|
|
87
92
|
timeoutMs: typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 30_000,
|
|
88
93
|
ssh,
|
|
89
94
|
});
|
|
95
|
+
const desktopExecutable = !ssh && config.autoStart !== false && !this.options.client && process.platform === "win32"
|
|
96
|
+
? await (0, desktop_launch_1.findChatgptExecutable)(typeof config.executablePath === "string" ? config.executablePath : undefined)
|
|
97
|
+
: undefined;
|
|
98
|
+
const desktopProfile = (0, node_path_1.join)(context.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "chatgpt"), "desktop-profile");
|
|
99
|
+
const desktopArgs = [`--remote-debugging-port=${port(config.localCdpPort, 9230)}`];
|
|
100
|
+
const startupTimeoutMs = typeof config.startupTimeoutMs === "number" ? config.startupTimeoutMs : 30_000;
|
|
101
|
+
this.reconnect = async () => {
|
|
102
|
+
try {
|
|
103
|
+
await this.client.connect();
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
catch (first) {
|
|
107
|
+
if (!desktopExecutable)
|
|
108
|
+
throw first;
|
|
109
|
+
await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, desktopExecutable, desktopArgs, desktopProfile, () => this.client.connect(), startupTimeoutMs, child => { this.ownedProcess = child; });
|
|
110
|
+
}
|
|
111
|
+
};
|
|
90
112
|
this.sessionOperations = this.client.createSession ? ["new"] : [];
|
|
91
113
|
try {
|
|
92
114
|
await this.client.connect();
|
|
93
115
|
}
|
|
94
116
|
catch (error) {
|
|
95
|
-
|
|
117
|
+
let connectionError = error;
|
|
118
|
+
if (desktopExecutable) {
|
|
119
|
+
try {
|
|
120
|
+
await (0, desktop_launch_1.launchManagedAndWait)(desktopExecutable, desktopArgs, desktopProfile, () => this.client.connect(), startupTimeoutMs, child => { this.ownedProcess = child; });
|
|
121
|
+
this.failure = undefined;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
catch (launchError) {
|
|
125
|
+
connectionError = launchError;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.failure = connectionError instanceof Error ? connectionError.message : String(connectionError);
|
|
96
129
|
context.logger.warn(this.failure);
|
|
97
130
|
if (!this.store.sessions.length)
|
|
98
|
-
throw
|
|
131
|
+
throw connectionError;
|
|
99
132
|
}
|
|
100
133
|
}
|
|
101
134
|
async deactivate() {
|
|
102
135
|
await this.queue;
|
|
103
136
|
await this.store?.drain();
|
|
104
137
|
this.client?.close();
|
|
138
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
|
|
139
|
+
this.ownedProcess = undefined;
|
|
140
|
+
this.reconnect = undefined;
|
|
105
141
|
this.client = undefined;
|
|
106
142
|
this.remote = false;
|
|
107
143
|
}
|
|
@@ -246,7 +282,7 @@ class ChatgptPlugin {
|
|
|
246
282
|
}
|
|
247
283
|
async synchronize() {
|
|
248
284
|
if (!this.client.connected())
|
|
249
|
-
await this.client.connect();
|
|
285
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
250
286
|
if (!this.client.listSessions)
|
|
251
287
|
throw new Error("当前 ChatGPT 客户端不支持会话列表");
|
|
252
288
|
this.store.synchronize(await this.client.listSessions());
|
|
@@ -254,7 +290,7 @@ class ChatgptPlugin {
|
|
|
254
290
|
}
|
|
255
291
|
async select(session) {
|
|
256
292
|
if (!this.client.connected())
|
|
257
|
-
await this.client.connect();
|
|
293
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
258
294
|
if (!this.client.switchSession)
|
|
259
295
|
throw new Error("当前 ChatGPT 客户端不支持会话切换");
|
|
260
296
|
const active = await this.client.activeSession?.();
|
|
@@ -321,7 +357,7 @@ class ChatgptPlugin {
|
|
|
321
357
|
await this.select(selected);
|
|
322
358
|
}
|
|
323
359
|
if (!this.client.connected())
|
|
324
|
-
await this.client.connect();
|
|
360
|
+
await (this.reconnect?.() ?? this.client.connect());
|
|
325
361
|
selected ??= await this.client.activeSession?.();
|
|
326
362
|
const message = explicit?.[2]?.trim() || request.task;
|
|
327
363
|
const reply = await this.client.direct(message, request.timeoutMs, request.attachments);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ClaudeCliPlugin = exports.manifest = void 0;
|
|
4
|
+
const agent_1 = require("../local-cli/agent");
|
|
5
|
+
exports.manifest = {
|
|
6
|
+
id: "claude-cli", version: "1.0.0", displayName: "Claude Code CLI", type: "agent", executionMode: "cli",
|
|
7
|
+
entry: "dist/src-ts/plugins/claude-cli/index.js", apiVersion: "1", process: "in-process",
|
|
8
|
+
capabilities: ["chat", "health"], configSchema: "config.schema.json",
|
|
9
|
+
};
|
|
10
|
+
class ClaudeCliPlugin extends agent_1.LocalCliAgent {
|
|
11
|
+
constructor() { super(exports.manifest, "claude"); }
|
|
12
|
+
}
|
|
13
|
+
exports.ClaudeCliPlugin = ClaudeCliPlugin;
|
|
14
|
+
exports.default = ClaudeCliPlugin;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CodexCliPlugin = exports.manifest = void 0;
|
|
4
|
+
const agent_1 = require("../local-cli/agent");
|
|
5
|
+
exports.manifest = {
|
|
6
|
+
id: "codex-cli", version: "1.0.0", displayName: "Codex CLI", type: "agent", executionMode: "cli",
|
|
7
|
+
entry: "dist/src-ts/plugins/codex-cli/index.js", apiVersion: "1", process: "in-process",
|
|
8
|
+
capabilities: ["chat", "health"], configSchema: "config.schema.json",
|
|
9
|
+
};
|
|
10
|
+
class CodexCliPlugin extends agent_1.LocalCliAgent {
|
|
11
|
+
constructor() { super(exports.manifest, "codex"); }
|
|
12
|
+
}
|
|
13
|
+
exports.CodexCliPlugin = CodexCliPlugin;
|
|
14
|
+
exports.default = CodexCliPlugin;
|
|
@@ -3,10 +3,18 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.findWindowsExecutable = findWindowsExecutable;
|
|
4
4
|
exports.findExecutableInTree = findExecutableInTree;
|
|
5
5
|
exports.launchAndWait = launchAndWait;
|
|
6
|
+
exports.launchManagedAndWait = launchManagedAndWait;
|
|
7
|
+
exports.restartOwnedAndWait = restartOwnedAndWait;
|
|
8
|
+
exports.terminateWindowsDesktop = terminateWindowsDesktop;
|
|
9
|
+
exports.terminateOwnedProcess = terminateOwnedProcess;
|
|
10
|
+
exports.findChatgptExecutable = findChatgptExecutable;
|
|
6
11
|
exports.waitFor = waitFor;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
7
13
|
const promises_1 = require("node:fs/promises");
|
|
8
14
|
const node_fs_1 = require("node:fs");
|
|
9
15
|
const node_path_1 = require("node:path");
|
|
16
|
+
const node_util_1 = require("node:util");
|
|
17
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
10
18
|
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
11
19
|
/** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
|
|
12
20
|
async function findWindowsExecutable(fileName, configured) {
|
|
@@ -31,8 +39,11 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
31
39
|
}
|
|
32
40
|
if (process.platform !== "win32")
|
|
33
41
|
return undefined;
|
|
34
|
-
const pattern = /doubao|豆包|qwen|qwwork
|
|
42
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh/i;
|
|
35
43
|
const roots = [...new Set([
|
|
44
|
+
process.env.DSH_INSTALL_DIR,
|
|
45
|
+
"D:\\Programs\\DeepSeek Harness",
|
|
46
|
+
"D:\\DSH",
|
|
36
47
|
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
37
48
|
process.env.ProgramFiles,
|
|
38
49
|
process.env["ProgramFiles(x86)"],
|
|
@@ -83,6 +94,87 @@ async function launchAndWait(executable, args, connect, timeoutMs, launch = (fil
|
|
|
83
94
|
child.unref();
|
|
84
95
|
return waitFor(connect, timeoutMs, 250);
|
|
85
96
|
}
|
|
97
|
+
async function launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
98
|
+
if (profileDirectory)
|
|
99
|
+
await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
|
|
100
|
+
const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
|
|
101
|
+
child.unref();
|
|
102
|
+
onProcess(child);
|
|
103
|
+
try {
|
|
104
|
+
return await waitFor(connect, timeoutMs, 250);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
await terminateOwnedProcess(child).catch(() => undefined);
|
|
108
|
+
onProcess(undefined);
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function restartOwnedAndWait(child, executable, args, profileDirectory, connect, timeoutMs, onProcess) {
|
|
113
|
+
await terminateOwnedProcess(child);
|
|
114
|
+
onProcess(undefined);
|
|
115
|
+
return launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess);
|
|
116
|
+
}
|
|
117
|
+
/** 仅在用户确认重启后清理同一用户、同一安装路径的桌面进程。 */
|
|
118
|
+
async function terminateWindowsDesktop(executable) {
|
|
119
|
+
if (process.platform !== "win32")
|
|
120
|
+
return [];
|
|
121
|
+
const script = `
|
|
122
|
+
$ErrorActionPreference = 'Stop'
|
|
123
|
+
$target = '${(0, node_path_1.resolve)(executable).replace(/'/g, "''")}'
|
|
124
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
125
|
+
$processes = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -ieq $target } | Where-Object { (Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid).Sid -eq $sid })
|
|
126
|
+
$ids = @($processes.ProcessId)
|
|
127
|
+
$roots = @($processes | Where-Object { $ids -notcontains $_.ParentProcessId })
|
|
128
|
+
foreach ($item in $roots) {
|
|
129
|
+
$current = Get-CimInstance Win32_Process -Filter "ProcessId=$($item.ProcessId)"
|
|
130
|
+
if (-not $current -or $current.CreationDate -ne $item.CreationDate) { continue }
|
|
131
|
+
& taskkill.exe /PID $item.ProcessId /T /F | Out-Null
|
|
132
|
+
if ($LASTEXITCODE -ne 0 -and (Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue)) { throw "无法结束桌面进程 $($item.ProcessId)" }
|
|
133
|
+
$remaining = Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue
|
|
134
|
+
if ($remaining -and -not $remaining.WaitForExit(10000)) { throw "等待桌面进程退出超时 $($item.ProcessId)" }
|
|
135
|
+
}
|
|
136
|
+
ConvertTo-Json -Compress -InputObject @($roots | ForEach-Object { [int]$_.ProcessId })
|
|
137
|
+
`;
|
|
138
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 30_000, windowsHide: true });
|
|
139
|
+
return JSON.parse(stdout.trim());
|
|
140
|
+
}
|
|
141
|
+
async function terminateOwnedProcess(child, platform = process.platform, run = async (file, args) => execFileAsync(file, args, { timeout: 10_000 })) {
|
|
142
|
+
const pid = child?.pid;
|
|
143
|
+
if (!Number.isSafeInteger(pid) || !pid || child?.exitCode !== null && child?.exitCode !== undefined || child?.signalCode)
|
|
144
|
+
return;
|
|
145
|
+
if (platform === "win32") {
|
|
146
|
+
try {
|
|
147
|
+
await run("taskkill.exe", ["/PID", String(pid), "/T", "/F"]);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
if (!child.exitCode && !child.signalCode && error.code !== "ESRCH")
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
try {
|
|
156
|
+
process.kill(pid, "SIGTERM");
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error.code !== "ESRCH")
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async function findChatgptExecutable(configured) {
|
|
165
|
+
if (configured?.trim() && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)))
|
|
166
|
+
return (0, node_path_1.resolve)(configured);
|
|
167
|
+
if (process.platform !== "win32")
|
|
168
|
+
return undefined;
|
|
169
|
+
const script = "$candidates = foreach ($package in (Get-AppxPackage | Where-Object { $_.Name -like 'OpenAI.Codex*' })) { $appDir = Join-Path $package.InstallLocation 'app'; $path = Join-Path $appDir 'ChatGPT.exe'; if (-not (Test-Path -LiteralPath $path)) { $path = Get-ChildItem -LiteralPath $appDir -Filter 'ChatGPT*.exe' -File -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName }; if ($path) { [pscustomobject]@{ Version = [version]$package.Version; Executable = $path } } }; $candidate = $candidates | Sort-Object Version -Descending | Select-Object -First 1; if ($candidate) { $candidate.Executable }";
|
|
170
|
+
try {
|
|
171
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 10_000 });
|
|
172
|
+
return stdout.trim().split(/\r?\n/).at(-1)?.trim() || undefined;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
86
178
|
async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
87
179
|
const deadline = Date.now() + timeoutMs;
|
|
88
180
|
let last;
|
|
@@ -97,4 +189,3 @@ async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
|
97
189
|
}
|
|
98
190
|
throw last instanceof Error ? last : new Error("桌面程序启动超时");
|
|
99
191
|
}
|
|
100
|
-
const node_child_process_1 = require("node:child_process");
|
|
@@ -7,6 +7,7 @@ const cdp_client_1 = require("./cdp-client");
|
|
|
7
7
|
const session_history_1 = require("./session-history");
|
|
8
8
|
const plugin_1 = require("../../plugin");
|
|
9
9
|
const desktop_launch_1 = require("../desktop-launch");
|
|
10
|
+
const node_os_1 = require("node:os");
|
|
10
11
|
exports.doubaoManifest = {
|
|
11
12
|
id: "doubao",
|
|
12
13
|
version: "1.0.20",
|
|
@@ -35,6 +36,7 @@ class DoubaoPlugin {
|
|
|
35
36
|
}
|
|
36
37
|
disposer;
|
|
37
38
|
reconnect;
|
|
39
|
+
ownedProcess;
|
|
38
40
|
async activate(context) {
|
|
39
41
|
this.dataDir = context.dataDir;
|
|
40
42
|
this.bindings.clear();
|
|
@@ -68,20 +70,18 @@ class DoubaoPlugin {
|
|
|
68
70
|
}
|
|
69
71
|
catch (first) {
|
|
70
72
|
if (context.config.autoStart !== false && !this.injectedClient && process.platform === "win32") {
|
|
71
|
-
const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
|
|
72
73
|
const executable = await (0, desktop_launch_1.findWindowsExecutable)("Doubao.exe", typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
|
|
73
|
-
if (executable)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
if (!executable)
|
|
75
|
+
throw new Error("未找到 Doubao.exe,请配置 executablePath");
|
|
76
|
+
const port = (0, plugin_1.resolveRuntimePort)(context.config, context.runtime, "doubao", "DOUBAO_CDP_PORT", 9227);
|
|
77
|
+
context.logger?.warn?.(`正在恢复豆包:${executable},等待 CDP ${port}`);
|
|
78
|
+
try {
|
|
79
|
+
await (0, desktop_launch_1.restartOwnedAndWait)(this.ownedProcess, executable, [`--remote-debugging-port=${port}`], (0, node_path_1.join)(this.dataDir ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".agent-bridge", "doubao"), "desktop-profile"), () => this.client.connect(), typeof context.config.startupTimeoutMs === "number" ? context.config.startupTimeoutMs : 30_000, child => { this.ownedProcess = child; });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
first = new Error(`已启动豆包,但 CDP ${port} 未就绪:${error instanceof Error ? error.message : String(error)}`);
|
|
82
84
|
}
|
|
83
|
-
else
|
|
84
|
-
first = new Error("未找到 Doubao.exe,请配置 executablePath");
|
|
85
85
|
}
|
|
86
86
|
throw first;
|
|
87
87
|
}
|
|
@@ -106,6 +106,8 @@ class DoubaoPlugin {
|
|
|
106
106
|
this.disposer = undefined;
|
|
107
107
|
this.reconnect = undefined;
|
|
108
108
|
await this.client?.close();
|
|
109
|
+
await (0, desktop_launch_1.terminateOwnedProcess)(this.ownedProcess).catch(() => undefined);
|
|
110
|
+
this.ownedProcess = undefined;
|
|
109
111
|
this.client = undefined;
|
|
110
112
|
this.state = "stopped";
|
|
111
113
|
}
|