mu-agent-bridge 1.0.32 → 1.0.34
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 +1 -15
- package/dist/src-ts/attachments.js +62 -0
- package/dist/src-ts/channel-sessions.js +8 -7
- package/dist/src-ts/cli-commands.js +2 -6
- package/dist/src-ts/control-server.js +0 -2
- package/dist/src-ts/daemon-process.js +2 -3
- package/dist/src-ts/dsh-agent.js +2 -4
- package/dist/src-ts/plugin-manager.js +29 -25
- package/dist/src-ts/plugins/aun/attachments.js +245 -0
- package/dist/src-ts/plugins/aun/index.js +117 -14
- package/dist/src-ts/plugins/aun/messages.js +25 -1
- package/dist/src-ts/plugins/cdp-draft.js +54 -0
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +10 -1
- package/dist/src-ts/plugins/chatgpt/index.js +16 -6
- package/dist/src-ts/plugins/doubao/cdp-client.js +72 -7
- package/dist/src-ts/plugins/doubao/index.js +2 -2
- package/dist/src-ts/plugins/dsh/index.js +0 -1
- package/dist/src-ts/plugins/feishu/index.js +8 -21
- package/dist/src-ts/plugins/feishu/public/app.js +8 -20
- package/dist/src-ts/plugins/feishu/public/index.html +1 -1
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +26 -0
- package/dist/src-ts/plugins/qwenwork/index.js +2 -1
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +330 -12
- package/dist/src-ts/plugins/workbuddy/index.js +56 -11
- package/dist/tests-ts/attachments.test.js +89 -0
- package/dist/tests-ts/aun-attachments.test.js +209 -0
- package/dist/tests-ts/aun-messages.test.js +41 -0
- package/dist/tests-ts/aun-plugin.test.js +222 -6
- package/dist/tests-ts/cdp-draft-recovery.test.js +149 -0
- package/dist/tests-ts/channel-sessions.test.js +0 -22
- package/dist/tests-ts/chatgpt-plugin.test.js +66 -6
- package/dist/tests-ts/cli-commands.test.js +0 -58
- package/dist/tests-ts/daemon-process.test.js +0 -1
- package/dist/tests-ts/doubao-plugin.test.js +143 -6
- package/dist/tests-ts/dsh-agent.test.js +0 -25
- package/dist/tests-ts/feishu-page.test.js +3 -10
- package/dist/tests-ts/feishu-plugin.test.js +1 -24
- package/dist/tests-ts/plugin-manager.test.js +61 -97
- package/dist/tests-ts/qwenwork-plugin.test.js +3 -7
- package/dist/tests-ts/workbuddy-plugin.test.js +589 -7
- package/package.json +1 -2
- package/plugins/aun/dist/src-ts/attachments.js +62 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +245 -0
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +117 -14
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +25 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +54 -0
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +10 -1
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +16 -6
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +72 -7
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +2 -2
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/dist/src-ts/dsh-agent.js +2 -4
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +0 -1
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +8 -21
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/app.js +8 -20
- package/plugins/feishu/dist/src-ts/plugins/feishu/public/index.html +1 -1
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +54 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +26 -0
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +2 -1
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +1 -1
- package/plugins/workbuddy/dist/src-ts/attachments.js +62 -0
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +330 -12
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +56 -11
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +1 -1
- package/dist/installer/mu-agent-bridge.msi +0 -0
- package/dist/src-ts/secret-input.js +0 -47
- package/dist/tests-ts/installer-assets.test.js +0 -84
|
@@ -9,8 +9,9 @@ const node_path_1 = require("node:path");
|
|
|
9
9
|
const node_os_1 = require("node:os");
|
|
10
10
|
const node_url_1 = require("node:url");
|
|
11
11
|
const messages_1 = require("./messages");
|
|
12
|
+
const attachments_1 = require("./attachments");
|
|
12
13
|
const manifest = {
|
|
13
|
-
id: "aun", version: "1.0.
|
|
14
|
+
id: "aun", version: "1.0.23", type: "channel", displayName: "AUN 通道",
|
|
14
15
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
15
16
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
16
17
|
configSchema: "config.schema.json",
|
|
@@ -71,6 +72,8 @@ class AunPlugin {
|
|
|
71
72
|
status = "degraded";
|
|
72
73
|
healthMessage = "尚未配置 AID";
|
|
73
74
|
seen = new messages_1.DuplicateTracker();
|
|
75
|
+
pendingMessages = new Map();
|
|
76
|
+
acceptingMessages = false;
|
|
74
77
|
constructor(options = {}) {
|
|
75
78
|
this.options = options;
|
|
76
79
|
this.sdk = options.sdk;
|
|
@@ -251,6 +254,7 @@ class AunPlugin {
|
|
|
251
254
|
}
|
|
252
255
|
if (this.aid)
|
|
253
256
|
this.identities.add(this.aid);
|
|
257
|
+
this.acceptingMessages = true;
|
|
254
258
|
for (const aid of this.identities)
|
|
255
259
|
await this.connectAid(aid);
|
|
256
260
|
// 密钥库中的其余 AID 也自动连接:不再依赖 ec 或 EvolCore 进程
|
|
@@ -291,10 +295,12 @@ class AunPlugin {
|
|
|
291
295
|
}
|
|
292
296
|
async deactivate(_reason) {
|
|
293
297
|
this.status = "degraded";
|
|
294
|
-
|
|
298
|
+
this.acceptingMessages = false;
|
|
299
|
+
for (const { subscription } of this.clients.values())
|
|
295
300
|
subscription?.unsubscribe();
|
|
301
|
+
await Promise.allSettled(this.pendingMessages.values());
|
|
302
|
+
for (const { client } of this.clients.values())
|
|
296
303
|
await client.close();
|
|
297
|
-
}
|
|
298
304
|
this.clients.clear();
|
|
299
305
|
await this.store?.close();
|
|
300
306
|
this.store = undefined;
|
|
@@ -735,28 +741,109 @@ class AunPlugin {
|
|
|
735
741
|
}
|
|
736
742
|
};
|
|
737
743
|
async onMessage(payload, aid) {
|
|
744
|
+
if (!this.acceptingMessages)
|
|
745
|
+
return;
|
|
738
746
|
const message = (0, messages_1.normalizeAunMessage)(payload, aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
|
|
739
747
|
if (!message || !this.seen.add(message.messageId ? `${aid}:${message.messageId}` : ""))
|
|
740
748
|
return;
|
|
749
|
+
const sourceKey = JSON.stringify([aid, message.peerId, message.threadId ?? ""]);
|
|
750
|
+
const pending = this.pendingMessages.get(sourceKey);
|
|
751
|
+
if (pending && !message.attachments?.length && !message.attachmentError && newSessionCommand.test(message.text.trim())) {
|
|
752
|
+
try {
|
|
753
|
+
await this.sendText(aid, message.peerId, "新建会话失败:当前会话正在执行任务,请等待完成后再新建会话", message.threadId);
|
|
754
|
+
}
|
|
755
|
+
catch (error) {
|
|
756
|
+
this.context?.logger.warn("AUN 错误回复失败", { error: safeError(error) });
|
|
757
|
+
}
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => this.processMessage(message, aid));
|
|
761
|
+
this.pendingMessages.set(sourceKey, current);
|
|
762
|
+
try {
|
|
763
|
+
await current;
|
|
764
|
+
}
|
|
765
|
+
finally {
|
|
766
|
+
if (this.pendingMessages.get(sourceKey) === current)
|
|
767
|
+
this.pendingMessages.delete(sourceKey);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
async processMessage(message, aid) {
|
|
741
771
|
let replyAttempted = false;
|
|
772
|
+
const client = this.clients.get(aid)?.client;
|
|
773
|
+
let attachments = [];
|
|
774
|
+
let uploadedItems;
|
|
775
|
+
let uploadedRefs;
|
|
776
|
+
try {
|
|
777
|
+
if (message.attachmentError)
|
|
778
|
+
throw new Error(message.attachmentError);
|
|
779
|
+
if (message.attachments?.length) {
|
|
780
|
+
if (!client?.storage)
|
|
781
|
+
throw new Error("AUN SDK 不支持附件存储接口");
|
|
782
|
+
attachments = await (0, attachments_1.downloadAunAttachments)({ storage: client.storage }, message.attachments);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
catch (error) {
|
|
786
|
+
this.context?.logger.warn("AUN 附件下载失败", { error: safeError(error) });
|
|
787
|
+
try {
|
|
788
|
+
await this.sendText(aid, message.peerId, `附件接收失败:${safeError(error)}`, message.threadId);
|
|
789
|
+
}
|
|
790
|
+
catch { /* 尽力反馈 */ }
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
742
793
|
const channelMessage = {
|
|
743
794
|
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
795
|
+
...(attachments.length ? { attachments } : {}),
|
|
744
796
|
agentPlugin: this.bindings[aid] || (typeof this.config.agent_plugin === "string" && this.config.agent_plugin.trim() ? this.config.agent_plugin.trim() : "qwenwork"),
|
|
745
797
|
reply: async (text) => {
|
|
746
798
|
if (!text.trim())
|
|
747
799
|
return;
|
|
748
800
|
replyAttempted = true;
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
801
|
+
await this.sendText(aid, message.peerId, text, message.threadId);
|
|
802
|
+
},
|
|
803
|
+
replyAttachments: async (items, text) => {
|
|
804
|
+
if (!items.length)
|
|
805
|
+
return;
|
|
806
|
+
replyAttempted = true;
|
|
807
|
+
let sent = 0;
|
|
808
|
+
let groupsCount = 0;
|
|
809
|
+
let sending = false;
|
|
753
810
|
try {
|
|
754
|
-
|
|
811
|
+
if (!client?.storage)
|
|
812
|
+
throw new Error("AUN SDK 不支持附件存储接口");
|
|
813
|
+
const refs = uploadedRefs && uploadedItems === items ? uploadedRefs : await (0, attachments_1.uploadAunAttachments)({ storage: client.storage }, aid, message.peerId, items);
|
|
814
|
+
if (!uploadedRefs || uploadedItems !== items) {
|
|
815
|
+
uploadedItems = items;
|
|
816
|
+
uploadedRefs = refs;
|
|
817
|
+
}
|
|
818
|
+
const groups = new Map();
|
|
819
|
+
for (const [index, item] of items.entries()) {
|
|
820
|
+
const kind = item.kind;
|
|
821
|
+
const ref = refs[index];
|
|
822
|
+
const group = groups.get(kind) ?? [];
|
|
823
|
+
group.push(ref);
|
|
824
|
+
groups.set(kind, group);
|
|
825
|
+
}
|
|
826
|
+
groupsCount = groups.size;
|
|
827
|
+
for (const [kind, group] of groups) {
|
|
828
|
+
const payload = { type: kind, text: text || (kind === "image" ? "图片已生成。" : "文件已生成。"), attachments: group };
|
|
829
|
+
if (message.threadId)
|
|
830
|
+
payload.thread_id = message.threadId;
|
|
831
|
+
if (message.messageId)
|
|
832
|
+
payload.ref_message_id = message.messageId;
|
|
833
|
+
sending = true;
|
|
834
|
+
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
835
|
+
await client.call("message.send", { to: message.peerId, payload, encrypt: true });
|
|
836
|
+
sending = false;
|
|
837
|
+
sent++;
|
|
838
|
+
}
|
|
755
839
|
}
|
|
756
840
|
catch (error) {
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
841
|
+
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
842
|
+
try {
|
|
843
|
+
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError(error)}`, message.threadId);
|
|
844
|
+
}
|
|
845
|
+
catch { /* 尽力反馈 */ }
|
|
846
|
+
throw error;
|
|
760
847
|
}
|
|
761
848
|
},
|
|
762
849
|
};
|
|
@@ -765,9 +852,10 @@ class AunPlugin {
|
|
|
765
852
|
}
|
|
766
853
|
catch (error) {
|
|
767
854
|
this.context?.logger.warn("AUN 消息处理失败", { error: safeError(error) });
|
|
768
|
-
if (!replyAttempted
|
|
855
|
+
if (!replyAttempted) {
|
|
856
|
+
const action = attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
769
857
|
try {
|
|
770
|
-
await channelMessage.reply(
|
|
858
|
+
await channelMessage.reply(`${action}:${safeError(error)}`);
|
|
771
859
|
}
|
|
772
860
|
catch (replyError) {
|
|
773
861
|
this.context?.logger.warn("AUN 错误回复失败", { error: safeError(replyError) });
|
|
@@ -775,6 +863,20 @@ class AunPlugin {
|
|
|
775
863
|
}
|
|
776
864
|
}
|
|
777
865
|
}
|
|
866
|
+
async sendText(aid, peerId, text, threadId) {
|
|
867
|
+
const payload = { type: "text", text };
|
|
868
|
+
if (threadId)
|
|
869
|
+
payload.thread_id = threadId;
|
|
870
|
+
const params = { to: peerId, payload, encrypt: true };
|
|
871
|
+
try {
|
|
872
|
+
await this.clients.get(aid).client.call("message.send", params);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
if (!this.isE2eeError(error))
|
|
876
|
+
throw error;
|
|
877
|
+
await this.clients.get(aid).client.call("message.send", { ...params, encrypt: false });
|
|
878
|
+
}
|
|
879
|
+
}
|
|
778
880
|
isE2eeError(error) {
|
|
779
881
|
const sdk = this.sdk;
|
|
780
882
|
return [sdk.E2EEError, sdk.E2EEDecryptFailedError, sdk.E2EEDegradedError].some((type) => !!type && error instanceof type)
|
|
@@ -820,5 +922,6 @@ async function readResponse(response, limit) {
|
|
|
820
922
|
return Buffer.concat(chunks).toString("utf8");
|
|
821
923
|
}
|
|
822
924
|
function redact(message) {
|
|
823
|
-
return message.replace(/
|
|
925
|
+
return message.replace(/https?:\/\/[^\s"'<>]+/gi, (value) => value.includes("?") ? `${value.slice(0, value.indexOf("?"))}?[已隐藏]` : value)
|
|
926
|
+
.replace(/(private[_ -]?key|seed|token|share_id|certificate)["']?\s*[:=]\s*["']?[^\s,;"'}]+/gi, "$1=[已隐藏]");
|
|
824
927
|
}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.DuplicateTracker = void 0;
|
|
4
4
|
exports.normalizeAunMessage = normalizeAunMessage;
|
|
5
|
+
const attachments_1 = require("./attachments");
|
|
6
|
+
const attachments_2 = require("../../attachments");
|
|
5
7
|
function normalizeAunMessage(payload, selfAid, allowFrom) {
|
|
6
8
|
if (!payload || typeof payload !== "object")
|
|
7
9
|
return undefined;
|
|
@@ -12,13 +14,35 @@ function normalizeAunMessage(payload, selfAid, allowFrom) {
|
|
|
12
14
|
return undefined;
|
|
13
15
|
const body = data.payload && typeof data.payload === "object" ? data.payload : {};
|
|
14
16
|
const text = typeof data.payload === "string" ? data.payload : typeof body.text === "string" ? body.text : "";
|
|
15
|
-
|
|
17
|
+
let attachments = [];
|
|
18
|
+
let attachmentError;
|
|
19
|
+
if (body.attachments !== undefined) {
|
|
20
|
+
try {
|
|
21
|
+
if (!Array.isArray(body.attachments))
|
|
22
|
+
throw new Error("AUN 附件列表无效");
|
|
23
|
+
if (body.attachments.length > attachments_2.MAX_ATTACHMENTS)
|
|
24
|
+
throw new Error("单条消息最多包含 8 个附件");
|
|
25
|
+
attachments = body.attachments.map((item) => {
|
|
26
|
+
const ref = (0, attachments_1.parseAunAttachment)(item);
|
|
27
|
+
if (!ref.token && ref.ownerAid !== peerId)
|
|
28
|
+
throw new Error("AUN 附件所有者必须是消息对端,第三方对象需要显式分享令牌");
|
|
29
|
+
return ref;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
attachments = [];
|
|
34
|
+
attachmentError = error instanceof Error ? error.message : "AUN 附件引用无效";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (!text.trim() && !attachments.length && !attachmentError)
|
|
16
38
|
return undefined;
|
|
17
39
|
return {
|
|
18
40
|
peerId,
|
|
19
41
|
...(typeof data.message_id === "string" ? { messageId: data.message_id } : {}),
|
|
20
42
|
text,
|
|
21
43
|
...(typeof body.thread_id === "string" ? { threadId: body.thread_id } : {}),
|
|
44
|
+
...(attachments.length ? { attachments } : {}),
|
|
45
|
+
...(attachmentError ? { attachmentError } : {}),
|
|
22
46
|
};
|
|
23
47
|
}
|
|
24
48
|
class DuplicateTracker {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.recoverCdpDraft = recoverCdpDraft;
|
|
4
|
+
const promises_1 = require("node:fs/promises");
|
|
5
|
+
const node_os_1 = require("node:os");
|
|
6
|
+
const node_path_1 = require("node:path");
|
|
7
|
+
/** 空闲时先备份旧草稿,再通过编辑器原生删除事件清理,避免只修改 DOM。 */
|
|
8
|
+
async function recoverCdpDraft(evaluate, editorSelector, stopSelector, dataDir = (0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-draft-recovery")) {
|
|
9
|
+
const state = await evaluate(`(() => ({
|
|
10
|
+
text: document.querySelector(${JSON.stringify(editorSelector)})?.innerText ?? null,
|
|
11
|
+
generating: Boolean(document.querySelector(${JSON.stringify(stopSelector)})),
|
|
12
|
+
url: document.URL ?? ''
|
|
13
|
+
}))()`);
|
|
14
|
+
if (state.generating)
|
|
15
|
+
throw new Error("桌面端正在生成回复,不能清理输入或切换会话");
|
|
16
|
+
if (state.text === null)
|
|
17
|
+
throw new Error("桌面端输入框未就绪");
|
|
18
|
+
if (!state.text?.trim())
|
|
19
|
+
return;
|
|
20
|
+
await (0, promises_1.mkdir)(dataDir, { recursive: true, mode: 0o700 });
|
|
21
|
+
await (0, promises_1.appendFile)((0, node_path_1.join)(dataDir, "draft-recovery.jsonl"), JSON.stringify({ time: new Date().toISOString(), url: state.url, text: state.text }) + "\n", { encoding: "utf8", mode: 0o600 });
|
|
22
|
+
const cleared = await evaluate(`(() => {
|
|
23
|
+
const editor = document.querySelector(${JSON.stringify(editorSelector)});
|
|
24
|
+
const unchanged = () => editor && editor.innerText === ${JSON.stringify(state.text)}
|
|
25
|
+
&& !document.querySelector(${JSON.stringify(stopSelector)});
|
|
26
|
+
if (!unchanged()) return false;
|
|
27
|
+
editor.focus();
|
|
28
|
+
if (!unchanged()) return false;
|
|
29
|
+
// 千问编辑器由 React 管理内部值,优先调用编辑器控制器,避免只清空 DOM。
|
|
30
|
+
let fiber = editor[Object.keys(editor).find(key => key.startsWith('__reactFiber')) ?? ''];
|
|
31
|
+
for (let depth = 0; fiber && depth < 24; depth++, fiber = fiber.return) {
|
|
32
|
+
const controller = fiber.memoizedProps?.ref?.current;
|
|
33
|
+
if (controller && typeof controller.clear === 'function') {
|
|
34
|
+
controller.clear();
|
|
35
|
+
return !editor.innerText.trim();
|
|
36
|
+
}
|
|
37
|
+
if (controller && typeof controller.setValue === 'function') {
|
|
38
|
+
controller.setValue('');
|
|
39
|
+
return !editor.innerText.trim();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const selection = window.getSelection();
|
|
43
|
+
if (!selection) return false;
|
|
44
|
+
const range = document.createRange();
|
|
45
|
+
range.selectNodeContents(editor);
|
|
46
|
+
selection.removeAllRanges();
|
|
47
|
+
selection.addRange(range);
|
|
48
|
+
document.execCommand('delete');
|
|
49
|
+
const current = document.querySelector(${JSON.stringify(editorSelector)});
|
|
50
|
+
return Boolean(current && !current.innerText.trim());
|
|
51
|
+
})()`);
|
|
52
|
+
if (cleared !== true)
|
|
53
|
+
throw new Error("旧草稿已备份,但输入内容发生变化或清理失败,保留当前内容");
|
|
54
|
+
}
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.ChatgptCdpClient = exports.userBubbleSelector = exports.turnSelector = exports.editorSelector = void 0;
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
5
|
const session_store_1 = require("./session-store");
|
|
6
|
+
const cdp_draft_1 = require("../cdp-draft");
|
|
6
7
|
exports.editorSelector = '[data-codex-composer="true"][contenteditable="true"]';
|
|
7
8
|
exports.turnSelector = '[data-chatgpt-conversation-turn="true"]';
|
|
8
9
|
exports.userBubbleSelector = "[data-user-message-bubble]";
|
|
@@ -24,12 +25,14 @@ class ChatgptCdpClient {
|
|
|
24
25
|
fetchImpl;
|
|
25
26
|
webSocketFactory;
|
|
26
27
|
ssh;
|
|
28
|
+
dataDir;
|
|
27
29
|
constructor(options = {}) {
|
|
28
30
|
this.port = options.port ?? options.ssh?.localPort ?? 9230;
|
|
29
31
|
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
30
32
|
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
31
33
|
this.webSocketFactory = options.webSocketFactory ?? ((url) => new WebSocket(url));
|
|
32
34
|
this.ssh = options.ssh;
|
|
35
|
+
this.dataDir = options.dataDir;
|
|
33
36
|
}
|
|
34
37
|
connected() { return Boolean(this.socket); }
|
|
35
38
|
async listSessions() {
|
|
@@ -42,7 +45,7 @@ class ChatgptCdpClient {
|
|
|
42
45
|
})()`);
|
|
43
46
|
if (!Array.isArray(entries))
|
|
44
47
|
throw new Error("ChatGPT 侧栏会话列表格式无效");
|
|
45
|
-
return entries.filter(session_store_1.validSession);
|
|
48
|
+
return [...new Map(entries.filter(session_store_1.validSession).map(item => [JSON.stringify([item.host, item.id]), item])).values()];
|
|
46
49
|
}
|
|
47
50
|
async activeSession() {
|
|
48
51
|
const entry = await this.evaluate(`(() => {
|
|
@@ -56,6 +59,7 @@ class ChatgptCdpClient {
|
|
|
56
59
|
return (0, session_store_1.validSession)(entry) ? entry : undefined;
|
|
57
60
|
}
|
|
58
61
|
async switchSession(session) {
|
|
62
|
+
await this.recoverDraft();
|
|
59
63
|
const locate = `[...document.querySelectorAll(${JSON.stringify(sidebarSelector)})].find(item =>
|
|
60
64
|
item.getAttribute('data-app-action-sidebar-thread-id') === ${JSON.stringify(session.id)}
|
|
61
65
|
&& item.getAttribute('data-app-action-sidebar-thread-host-id') === ${JSON.stringify(session.host)})`;
|
|
@@ -78,6 +82,7 @@ class ChatgptCdpClient {
|
|
|
78
82
|
})()`, "ChatGPT 会话切换未确认,未发送消息");
|
|
79
83
|
}
|
|
80
84
|
async createSession(_name) {
|
|
85
|
+
await this.recoverDraft();
|
|
81
86
|
const before = await this.activeSession();
|
|
82
87
|
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)})) })`);
|
|
83
88
|
const initial = JSON.parse(String(state));
|
|
@@ -187,6 +192,7 @@ class ChatgptCdpClient {
|
|
|
187
192
|
}
|
|
188
193
|
async direct(text, timeoutMs = 240_000) {
|
|
189
194
|
await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
|
|
195
|
+
await this.recoverDraft();
|
|
190
196
|
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 })))`);
|
|
191
197
|
const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
|
|
192
198
|
const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
|
|
@@ -306,6 +312,9 @@ class ChatgptCdpClient {
|
|
|
306
312
|
});
|
|
307
313
|
});
|
|
308
314
|
}
|
|
315
|
+
recoverDraft() {
|
|
316
|
+
return (0, cdp_draft_1.recoverCdpDraft)(this.evaluate.bind(this), exports.editorSelector, stopButtonSelector, this.dataDir);
|
|
317
|
+
}
|
|
309
318
|
async waitFor(expression, message) {
|
|
310
319
|
const deadline = Date.now() + this.timeoutMs;
|
|
311
320
|
while (Date.now() < deadline) {
|
|
@@ -5,7 +5,7 @@ const cdp_client_1 = require("./cdp-client");
|
|
|
5
5
|
const session_store_1 = require("./session-store");
|
|
6
6
|
exports.chatgptManifest = {
|
|
7
7
|
id: "chatgpt",
|
|
8
|
-
version: "1.0.
|
|
8
|
+
version: "1.0.6",
|
|
9
9
|
displayName: "ChatGPT(Codex)",
|
|
10
10
|
type: "agent",
|
|
11
11
|
entry: "dist/index.js",
|
|
@@ -60,6 +60,7 @@ class ChatgptPlugin {
|
|
|
60
60
|
this.failure = undefined;
|
|
61
61
|
this.client = this.options.client ?? new cdp_client_1.ChatgptCdpClient({
|
|
62
62
|
port: port(config.localCdpPort, 9230),
|
|
63
|
+
dataDir: context.dataDir,
|
|
63
64
|
timeoutMs: typeof config.timeoutMs === "number" && config.timeoutMs > 0 ? config.timeoutMs : 30_000,
|
|
64
65
|
ssh,
|
|
65
66
|
});
|
|
@@ -206,11 +207,20 @@ class ChatgptPlugin {
|
|
|
206
207
|
const reply = await this.client.direct(message, request.timeoutMs);
|
|
207
208
|
const baseline = request.sessionId ? store.pendingBindings[request.sessionId] : undefined;
|
|
208
209
|
if (baseline) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
210
|
+
try {
|
|
211
|
+
const existing = new Set(baseline);
|
|
212
|
+
const created = (await this.client.listSessions?.() ?? []).filter(item => !existing.has(JSON.stringify([item.host, item.id])));
|
|
213
|
+
const active = created.length === 1 ? created[0] : await this.client.activeSession?.();
|
|
214
|
+
if (!active || !created.some(item => item.id === active.id && item.host === active.host)) {
|
|
215
|
+
this.logger?.warn("ChatGPT 已生成回复,但新会话仍待确认", { created: created.length });
|
|
216
|
+
return reply;
|
|
217
|
+
}
|
|
218
|
+
selected = active;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
this.logger?.warn("ChatGPT 已生成回复,但新会话状态读取失败", { error: error instanceof Error ? error.message : String(error) });
|
|
222
|
+
return reply;
|
|
223
|
+
}
|
|
214
224
|
delete store.pendingBindings[request.sessionId];
|
|
215
225
|
}
|
|
216
226
|
if (selected) {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DoubaoCdpClient = exports.responseTextScript = void 0;
|
|
3
|
+
exports.DoubaoCdpClient = exports.responseImagesScript = exports.responseTextScript = void 0;
|
|
4
4
|
exports.doubaoStreamErrorMessage = doubaoStreamErrorMessage;
|
|
5
5
|
exports.requestBody = requestBody;
|
|
6
6
|
exports.mergeDoubaoReply = mergeDoubaoReply;
|
|
7
7
|
exports.cleanDoubaoReply = cleanDoubaoReply;
|
|
8
|
+
exports.downloadDoubaoImage = downloadDoubaoImage;
|
|
8
9
|
const node_crypto_1 = require("node:crypto");
|
|
9
10
|
function doubaoStreamErrorMessage(data) {
|
|
10
11
|
let code = "";
|
|
@@ -39,7 +40,7 @@ function mergeDoubaoReply(current, next) {
|
|
|
39
40
|
return current;
|
|
40
41
|
if (!current)
|
|
41
42
|
return next;
|
|
42
|
-
if (current.
|
|
43
|
+
if (current.endsWith(next))
|
|
43
44
|
return current;
|
|
44
45
|
if (next.includes(current))
|
|
45
46
|
return next;
|
|
@@ -57,6 +58,64 @@ function cleanDoubaoReply(reply, prompt) {
|
|
|
57
58
|
return value.slice(input.length).trim();
|
|
58
59
|
return value;
|
|
59
60
|
}
|
|
61
|
+
exports.responseImagesScript = `(value) => {
|
|
62
|
+
const urls = new Set();
|
|
63
|
+
const visit = (item) => {
|
|
64
|
+
if (Array.isArray(item)) { for (const child of item) visit(child); return; }
|
|
65
|
+
if (!item || typeof item !== 'object') return;
|
|
66
|
+
for (const creation of item.creation_block?.creations || []) {
|
|
67
|
+
const image = creation.image;
|
|
68
|
+
if (image?.status === 2 && typeof image.image_ori?.url === 'string' && image.image_ori.url) urls.add(image.image_ori.url);
|
|
69
|
+
}
|
|
70
|
+
for (const child of Object.values(item)) visit(child);
|
|
71
|
+
};
|
|
72
|
+
visit(value); return [...urls];
|
|
73
|
+
}`;
|
|
74
|
+
async function downloadDoubaoImage(address, fetchImpl = fetch, signal) {
|
|
75
|
+
const url = new URL(address);
|
|
76
|
+
if (url.protocol !== "https:" || !/^p\d+-flow-imagex-sign\.byteimg\.com$/.test(url.hostname) || url.username || url.password || (url.port && url.port !== "443"))
|
|
77
|
+
throw new Error("豆包图片地址无效");
|
|
78
|
+
const response = await fetchImpl(url.href, { redirect: "error", signal: signal ? AbortSignal.any([signal, AbortSignal.timeout(60_000)]) : AbortSignal.timeout(60_000) });
|
|
79
|
+
if (!response.ok)
|
|
80
|
+
throw new Error(`下载豆包图片 HTTP ${response.status}`);
|
|
81
|
+
const contentType = response.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase() ?? "";
|
|
82
|
+
const extension = { "image/png": "png", "image/jpeg": "jpg", "image/webp": "webp" }[contentType];
|
|
83
|
+
if (!extension)
|
|
84
|
+
throw new Error("豆包图片类型不支持");
|
|
85
|
+
const limit = 32 * 1024 * 1024;
|
|
86
|
+
if (Number(response.headers.get("content-length")) > limit) {
|
|
87
|
+
await response.body?.cancel();
|
|
88
|
+
throw new Error("豆包图片过大(上限 32 MiB)");
|
|
89
|
+
}
|
|
90
|
+
const reader = response.body?.getReader();
|
|
91
|
+
if (!reader)
|
|
92
|
+
throw new Error("豆包图片内容为空");
|
|
93
|
+
const chunks = [];
|
|
94
|
+
let size = 0;
|
|
95
|
+
try {
|
|
96
|
+
for (;;) {
|
|
97
|
+
const { done, value } = await reader.read();
|
|
98
|
+
if (done)
|
|
99
|
+
break;
|
|
100
|
+
size += value.byteLength;
|
|
101
|
+
if (size > limit) {
|
|
102
|
+
await reader.cancel();
|
|
103
|
+
throw new Error("豆包图片过大(上限 32 MiB)");
|
|
104
|
+
}
|
|
105
|
+
chunks.push(value);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
finally {
|
|
109
|
+
reader.releaseLock();
|
|
110
|
+
}
|
|
111
|
+
const data = Buffer.concat(chunks);
|
|
112
|
+
const valid = contentType === "image/png" ? data.subarray(0, 8).toString("hex") === "89504e470d0a1a0a"
|
|
113
|
+
: contentType === "image/jpeg" ? data.subarray(0, 3).toString("hex") === "ffd8ff"
|
|
114
|
+
: data.subarray(0, 4).toString() === "RIFF" && data.subarray(8, 12).toString() === "WEBP";
|
|
115
|
+
if (!valid)
|
|
116
|
+
throw new Error("豆包图片内容与类型不符");
|
|
117
|
+
return { data, contentType, filename: `doubao-${(0, node_crypto_1.randomUUID)()}.${extension}` };
|
|
118
|
+
}
|
|
60
119
|
class DoubaoCdpClient {
|
|
61
120
|
socket;
|
|
62
121
|
sequence = 0;
|
|
@@ -98,6 +157,9 @@ class DoubaoCdpClient {
|
|
|
98
157
|
this.pending.clear();
|
|
99
158
|
}
|
|
100
159
|
async direct(text, signal) {
|
|
160
|
+
return (await this.complete(text, signal)).text;
|
|
161
|
+
}
|
|
162
|
+
async complete(text, signal) {
|
|
101
163
|
await this.evaluate(buildInjection(requestBody(text, this.options.conversationId, this.options.botId, this.options.pluginList)));
|
|
102
164
|
const deadline = Date.now() + this.options.timeoutMs;
|
|
103
165
|
const requestSignal = signal ? AbortSignal.any([signal, this.closeController.signal]) : this.closeController.signal;
|
|
@@ -109,9 +171,12 @@ class DoubaoCdpClient {
|
|
|
109
171
|
const parsed = JSON.parse(result);
|
|
110
172
|
if (!parsed.ok)
|
|
111
173
|
throw new Error(`豆包请求失败: ${parsed.reason ?? "未知错误"}`);
|
|
174
|
+
const images = [];
|
|
175
|
+
for (const address of new Set(parsed.images ?? []))
|
|
176
|
+
images.push(await downloadDoubaoImage(address, this.options.fetchImpl, requestSignal));
|
|
112
177
|
const reply = cleanDoubaoReply(parsed.reply ?? "", text);
|
|
113
|
-
if (reply)
|
|
114
|
-
return reply;
|
|
178
|
+
if (images.length || reply)
|
|
179
|
+
return { text: reply || "图片已生成。", ...(images.length ? { images } : {}) };
|
|
115
180
|
}
|
|
116
181
|
throw new Error("等待豆包回复超时");
|
|
117
182
|
}
|
|
@@ -152,11 +217,11 @@ function buildInjection(body) {
|
|
|
152
217
|
const response = await fetch(requestUrl, { method: 'POST', headers: { 'content-type': 'application/json' }, credentials: 'include', body: ${JSON.stringify(body)}, signal: window.__mab_doubao_controller.signal });
|
|
153
218
|
if (!response.ok) throw new Error('HTTP ' + response.status);
|
|
154
219
|
const reader = response.body?.getReader(); if (!reader) throw new Error('响应无 body');
|
|
155
|
-
const decoder = new TextDecoder(); let buffer = '', reply = '', events = [];
|
|
156
|
-
const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error((${doubaoStreamErrorMessage.toString()})(data)); if (!data) return; try { const json = JSON.parse(data); if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || ''); if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text); if (event === 'STREAM_MSG_NOTIFY' || event === 'FULL_MSG_NOTIFY') for (const text of (${exports.responseTextScript})(json)) reply = merge(reply, text); if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''); } catch {} };
|
|
220
|
+
const decoder = new TextDecoder(); let buffer = '', reply = '', events = []; const images = new Set();
|
|
221
|
+
const consume = (block) => { const event = (block.match(/^event:\s*(.+)$/m) || [])[1] || ''; const data = (block.match(/^data:\s*(.+)$/m) || [])[1] || ''; if (event) events.push(event); if (event === 'STREAM_ERROR') throw new Error((${doubaoStreamErrorMessage.toString()})(data)); if (!data) return; try { const json = JSON.parse(data); if (['STREAM_CHUNK', 'STREAM_MSG_NOTIFY', 'FULL_MSG_NOTIFY'].includes(event)) for (const url of (${exports.responseImagesScript})(json)) images.add(url); if (event === 'STREAM_CHUNK') for (const patch of (json.patch_op || [])) reply = merge(reply, patch.patch_value?.tts_content || ''); if (event === 'CHUNK_DELTA' && typeof json.text === 'string') reply = merge(reply, json.text); if (event === 'STREAM_MSG_NOTIFY' || event === 'FULL_MSG_NOTIFY') for (const text of (${exports.responseTextScript})(json)) reply = merge(reply, text); if (event === 'SSE_REPLY_END') reply = merge(reply, json.msg_finish_attr?.brief || json.data?.msg_finish_attr?.brief || ''); } catch {} };
|
|
157
222
|
for (;;) { const item = await reader.read(); if (item.done) break; buffer += decoder.decode(item.value, { stream: true }).replace(/\r\n/g, '\n'); let index; while ((index = buffer.indexOf('\n\n')) >= 0) { consume(buffer.slice(0, index)); buffer = buffer.slice(index + 2); } }
|
|
158
223
|
if (buffer.trim()) consume(buffer);
|
|
159
|
-
window.__mab_doubao_result = JSON.stringify({ ok: Boolean(reply.trim()), reply, reason: reply.trim() ? '' : 'SSE
|
|
224
|
+
window.__mab_doubao_result = JSON.stringify({ ok: Boolean(reply.trim() || images.size), reply, images: [...images], reason: reply.trim() || images.size ? '' : 'SSE 未提取到文本或图片; events=' + events.slice(0, 20).join(',') });
|
|
160
225
|
} catch (error) { window.__mab_doubao_result = JSON.stringify({ ok: false, reason: String(error) }); }
|
|
161
226
|
})(); return 'started';
|
|
162
227
|
})()`;
|
|
@@ -6,7 +6,7 @@ const plugin_1 = require("../../plugin");
|
|
|
6
6
|
const desktop_launch_1 = require("../desktop-launch");
|
|
7
7
|
exports.doubaoManifest = {
|
|
8
8
|
id: "doubao",
|
|
9
|
-
version: "1.0.
|
|
9
|
+
version: "1.0.14",
|
|
10
10
|
displayName: "豆包",
|
|
11
11
|
entry: "dist/index.js",
|
|
12
12
|
apiVersion: "1",
|
|
@@ -79,7 +79,7 @@ class DoubaoPlugin {
|
|
|
79
79
|
async handle(request) {
|
|
80
80
|
if (this.state !== "ready" || !this.client)
|
|
81
81
|
throw new Error("豆包插件未就绪");
|
|
82
|
-
return { text: await this.client.direct(request.task) };
|
|
82
|
+
return this.client.complete ? this.client.complete(request.task) : { text: await this.client.direct(request.task) };
|
|
83
83
|
}
|
|
84
84
|
async handleHttp(request, response) {
|
|
85
85
|
let raw = "";
|
|
@@ -11,7 +11,6 @@ const commands = [
|
|
|
11
11
|
{ name: "status", summary: "查看飞书通道状态", usage: "mab feishu status" },
|
|
12
12
|
{ name: "list", summary: "列出机器人连接状态和关联 Agent", usage: "mab feishu list" },
|
|
13
13
|
{ name: "agents", summary: "列出已启动的 Agent 插件", usage: "mab feishu agents" },
|
|
14
|
-
{ name: "configure", summary: "保存机器人凭据并关联办公 Agent", usage: "mab feishu configure <app-id> [--agent <plugin-id>]", secretPrompt: "请输入飞书 App Secret" },
|
|
15
14
|
{ name: "bind", summary: "将已连接的机器人关联到 Agent", usage: "mab feishu bind <bot-id> <plugin-id>" },
|
|
16
15
|
{ name: "unbind", summary: "解除机器人关联并保留凭据", usage: "mab feishu unbind <bot-id>" },
|
|
17
16
|
];
|
|
@@ -150,10 +149,8 @@ class FeishuPlugin {
|
|
|
150
149
|
}
|
|
151
150
|
if (body.protocolVersion !== 1 || !Array.isArray(body.argv) || !body.argv.every((arg) => typeof arg === "string"))
|
|
152
151
|
return this.json(response, 400, { ok: false, message: "命令协议版本或 argv 无效" });
|
|
153
|
-
const [command, appId,
|
|
154
|
-
const
|
|
155
|
-
const secret = typeof body.secret === "string" ? body.secret.trim() : "";
|
|
156
|
-
const expectedLength = command === "bind" ? 3 : command === "unbind" ? 2 : command === "configure" ? (body.argv.length === 4 ? 4 : 2) : 1;
|
|
152
|
+
const [command, appId, pluginId] = body.argv;
|
|
153
|
+
const expectedLength = command === "bind" ? 3 : command === "unbind" ? 2 : 1;
|
|
157
154
|
if (!commands.some((item) => item.name === command) || body.argv.length !== expectedLength || body.argv.some((arg) => !arg.trim()))
|
|
158
155
|
return this.json(response, 400, { ok: false, message: "命令或参数无效,请查看 mab feishu help" });
|
|
159
156
|
if (command === "status") {
|
|
@@ -164,44 +161,34 @@ class FeishuPlugin {
|
|
|
164
161
|
const bots = this.bots.map((bot) => ({ app_id: bot.app_id, status: this.botStates.get(bot.app_id)?.status || "degraded", pluginId: this.bindings[bot.app_id] || "" }));
|
|
165
162
|
return this.json(response, 200, { ok: true, message: bots.map((bot) => `${bot.app_id}:${bot.status},Agent:${bot.pluginId || "未关联"}`).join("\n") || "尚未配置飞书机器人", data: bots });
|
|
166
163
|
}
|
|
167
|
-
if (command
|
|
168
|
-
return this.json(response, 400, { ok: false, message: "用法:mab feishu configure <app-id> [--agent <plugin-id>],Secret 必须输入" });
|
|
169
|
-
if (command !== "configure" && command !== "agents" && !this.bots.some((bot) => bot.app_id === appId))
|
|
164
|
+
if (command !== "agents" && !this.bots.some((bot) => bot.app_id === appId))
|
|
170
165
|
return this.json(response, 400, { ok: false, message: "飞书机器人标识不存在" });
|
|
171
166
|
if (command === "bind" && this.botStates.get(appId)?.status !== "ready")
|
|
172
167
|
return this.json(response, 400, { ok: false, message: "飞书机器人当前未连接" });
|
|
173
|
-
if (command === "agents" || command === "bind"
|
|
168
|
+
if (command === "agents" || command === "bind") {
|
|
174
169
|
const runtime = this.context?.runtime;
|
|
175
170
|
const controlUrl = runtime && "controlUrl" in runtime ? runtime.controlUrl : undefined;
|
|
176
171
|
if (typeof controlUrl !== "string" || !controlUrl)
|
|
177
172
|
return this.json(response, 503, { ok: false, message: "运行时未提供控制 API 地址,请升级核心并重启后台" });
|
|
178
173
|
let agents;
|
|
179
174
|
try {
|
|
180
|
-
const result = await fetch(new URL(
|
|
175
|
+
const result = await fetch(new URL("/api/plugins", controlUrl), { signal: AbortSignal.timeout(5000) });
|
|
181
176
|
if (!result.ok)
|
|
182
177
|
throw new Error("控制 API 请求失败");
|
|
183
178
|
const data = await result.json();
|
|
184
|
-
|
|
185
|
-
if (!Array.isArray(entries))
|
|
179
|
+
if (!Array.isArray(data.plugins))
|
|
186
180
|
throw new Error("控制 API 响应无效");
|
|
187
|
-
agents =
|
|
181
|
+
agents = data.plugins.filter((item) => item && item.type === "agent" && item.status === "ready" && typeof item.id === "string").map((item) => ({ id: item.id }));
|
|
188
182
|
}
|
|
189
183
|
catch {
|
|
190
184
|
return this.json(response, 502, { ok: false, message: "无法读取当前 Agent 插件,请检查后台控制 API" });
|
|
191
185
|
}
|
|
192
186
|
if (command === "agents")
|
|
193
187
|
return this.json(response, 200, { ok: true, message: agents.map((agent) => agent.id).join("\n") || "没有已启动的 Agent 插件", data: agents });
|
|
194
|
-
if (
|
|
188
|
+
if (!agents.some((agent) => agent.id === pluginId))
|
|
195
189
|
return this.json(response, 400, { ok: false, message: "目标 Agent 插件未启动或类型无效" });
|
|
196
190
|
}
|
|
197
191
|
try {
|
|
198
|
-
if (command === "configure") {
|
|
199
|
-
this.saveBot(appId, secret);
|
|
200
|
-
await this.persist();
|
|
201
|
-
if (pluginId)
|
|
202
|
-
await this.bindBot(appId, pluginId);
|
|
203
|
-
return this.json(response, 200, { ok: true, message: "飞书凭据已保存,请重载飞书插件以连接机器人", data: { app_id: appId, ...(pluginId ? { pluginId } : {}) } });
|
|
204
|
-
}
|
|
205
192
|
await this.bindBot(appId, command === "bind" ? pluginId : "");
|
|
206
193
|
this.json(response, 200, { ok: true, message: command === "bind" ? "飞书机器人已关联 Agent" : "飞书机器人已解除 Agent 关联", data: { app_id: appId, ...(command === "bind" ? { pluginId } : {}) } });
|
|
207
194
|
}
|