agent-bridge-aun 1.0.34 → 1.0.36
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 +2 -0
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/plugins/aun/evolcore.js +93 -17
- package/dist/src-ts/plugins/aun/index.js +108 -17
- package/dist/src-ts/plugins/aun/messages.js +3 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -15,3 +15,5 @@
|
|
|
15
15
|
ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。成功注册并记录在插件 `created_aids` 中的 AID 属于本工程;共享密钥库中其余 AID 属于 EvolCore。旧版 `created-aids.json` 记录继续兼容。来源只用于展示,连接和关联由 AUN 插件直接管理。列表先显示已关联 Agent 的身份,各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
|
|
16
16
|
|
|
17
17
|
AUN 独立包支持核心后台每五分钟检测更新;可信提供者在自动安装策略下会下载新副本并重载通道。
|
|
18
|
+
|
|
19
|
+
显式关联已有 EvolCore Agent 的 AID 时,先执行 `ec agent disable <aid>`,再配置 `ecagent` 路由并重载。`ec` 不存在或命令失败时,只将所选 AID 的 JSON 配置更新为 `enabled=false`、`active_baseagent=ecagent` 和对应路由;其它 AID 保持原值。仍有 EvolCore 运行实例时,必须确认重载成功才完成关联,避免原 Claude 与桌面 Agent 同时回复。纯密钥库身份不调用 `ec`,解绑不自动启用原 Agent。
|
package/dist/src-ts/agent.js
CHANGED
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentOperationError = exports.AgentError = void 0;
|
|
4
|
+
exports.abortable = abortable;
|
|
4
5
|
exports.errorDetails = errorDetails;
|
|
6
|
+
async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
|
|
11
|
+
void promise.catch(() => undefined);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
17
|
+
const abort = () => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
cleanup();
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
25
|
+
promise.then((value) => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
cleanup();
|
|
30
|
+
resolve(value);
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
class AgentError extends Error {
|
|
6
41
|
constructor(message, options) {
|
|
7
42
|
super(message, options);
|
|
@@ -4,10 +4,14 @@ exports.runEc = runEc;
|
|
|
4
4
|
exports.configureAidForPlugin = configureAidForPlugin;
|
|
5
5
|
const node_child_process_1 = require("node:child_process");
|
|
6
6
|
const node_util_1 = require("node:util");
|
|
7
|
+
const promises_1 = require("node:fs/promises");
|
|
8
|
+
const node_crypto_1 = require("node:crypto");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
10
|
+
const node_path_1 = require("node:path");
|
|
7
11
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
8
|
-
async function runEc(args) {
|
|
12
|
+
async function runEc(args, home) {
|
|
9
13
|
try {
|
|
10
|
-
const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout:
|
|
14
|
+
const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout: 60_000, env: { ...process.env, ...(home ? { EVOLCORE_HOME: home } : {}) }, windowsHide: true, maxBuffer: 1024 * 1024 });
|
|
11
15
|
return JSON.parse(stdout);
|
|
12
16
|
}
|
|
13
17
|
catch (error) {
|
|
@@ -21,24 +25,96 @@ async function runEc(args) {
|
|
|
21
25
|
throw error;
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
|
-
async function
|
|
25
|
-
|
|
26
|
-
|
|
28
|
+
async function ecIsRunning(home) {
|
|
29
|
+
const records = [];
|
|
30
|
+
try {
|
|
31
|
+
records.push(Number(await (0, promises_1.readFile)((0, node_path_1.join)(home, "daemon.pid"), "utf8")));
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code !== "ENOENT")
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
const directory = (0, node_path_1.join)(home, "data", "instance");
|
|
38
|
+
let names;
|
|
39
|
+
try {
|
|
40
|
+
names = await (0, promises_1.readdir)(directory);
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
if (error.code !== "ENOENT")
|
|
44
|
+
throw error;
|
|
45
|
+
names = [];
|
|
46
|
+
}
|
|
47
|
+
for (const name of names.filter(name => /^main-\d+\.json$/.test(name))) {
|
|
48
|
+
records.push(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, name), "utf8")).pid);
|
|
49
|
+
}
|
|
50
|
+
return records.some(pid => {
|
|
51
|
+
if (!Number.isSafeInteger(pid) || pid <= 0)
|
|
52
|
+
return false;
|
|
53
|
+
try {
|
|
54
|
+
process.kill(pid, 0);
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code === "ESRCH")
|
|
59
|
+
return false;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
async function configureAidForPlugin(pluginId, aid, port, run = runEc, home = process.env.EVOLCORE_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".evolcore")) {
|
|
65
|
+
if (!/^[a-z0-9][a-z0-9_.-]+$/i.test(aid) || !aid.includes("."))
|
|
66
|
+
throw new Error("AID 格式无效");
|
|
27
67
|
if (!/^[a-z0-9_-]+$/.test(pluginId))
|
|
28
68
|
throw new Error("插件 ID 格式无效");
|
|
29
|
-
|
|
69
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535)
|
|
70
|
+
throw new Error("后台端口无效");
|
|
30
71
|
const path = pluginId === "dsh" ? "/dsh/v1" : `/api/agents/${pluginId}/v1`;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
["
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
72
|
+
const ecagent = { baseUrl: `http://127.0.0.1:${port}${path}`, apiKey: "mab-local", model: pluginId };
|
|
73
|
+
try {
|
|
74
|
+
// MAB 直接接收 AUN 消息,先停用该 AID 的 EvolCore Agent,避免双重回复。
|
|
75
|
+
const disabled = await run(["agent", "disable", aid, "--format", "json"], home);
|
|
76
|
+
if (!disabled?.ok)
|
|
77
|
+
throw new Error(`停用原 Agent 失败:${disabled?.code ?? ""} ${disabled?.error ?? "未知错误"}`);
|
|
78
|
+
for (const [key, value] of [
|
|
79
|
+
...Object.entries(ecagent).map(([key, value]) => [`baseagents.ecagent.${key}`, value]),
|
|
80
|
+
["active_baseagent", "ecagent"],
|
|
81
|
+
]) {
|
|
82
|
+
const result = await run(["agent", "set", aid, key, value, "--format", "json"], home);
|
|
83
|
+
if (!result?.ok)
|
|
84
|
+
throw new Error(`配置 ${key} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
85
|
+
}
|
|
86
|
+
const result = await run(["agent", "reload", aid, "--format", "json"], home);
|
|
38
87
|
if (!result?.ok)
|
|
39
|
-
throw new Error(
|
|
88
|
+
throw new Error(`重载失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
89
|
+
return "ec";
|
|
90
|
+
}
|
|
91
|
+
catch (commandError) {
|
|
92
|
+
const file = (0, node_path_1.join)(home, "agents", aid, "config.json");
|
|
93
|
+
const config = JSON.parse(await (0, promises_1.readFile)(file, "utf8"));
|
|
94
|
+
if (!config || typeof config !== "object" || Array.isArray(config) || config.baseagents && (typeof config.baseagents !== "object" || Array.isArray(config.baseagents)))
|
|
95
|
+
throw new Error(`AID ${aid} 原配置格式无效,未覆盖文件`);
|
|
96
|
+
config.enabled = false;
|
|
97
|
+
config.active_baseagent = "ecagent";
|
|
98
|
+
config.baseagents = { ...config.baseagents, ecagent: { ...config.baseagents?.ecagent, ...ecagent } };
|
|
99
|
+
const temporary = `${file}.mab-${(0, node_crypto_1.randomUUID)()}.tmp`;
|
|
100
|
+
try {
|
|
101
|
+
await (0, promises_1.writeFile)(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
102
|
+
await (0, promises_1.rename)(temporary, file);
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
await (0, promises_1.rm)(temporary, { force: true });
|
|
106
|
+
}
|
|
107
|
+
// 写盘不能关闭运行中的旧 Agent;存在活跃实例时必须确认重载成功。
|
|
108
|
+
if (await ecIsRunning(home)) {
|
|
109
|
+
try {
|
|
110
|
+
const result = await run(["agent", "reload", aid, "--format", "json"], home);
|
|
111
|
+
if (!result?.ok)
|
|
112
|
+
throw new Error(result?.error ?? "重载失败");
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
throw new AggregateError([commandError, error], `AID ${aid} 配置已保存,但 EvolCore 仍在运行,原 Agent 未确认停用:${error instanceof Error ? error.message : error}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return "json";
|
|
40
119
|
}
|
|
41
|
-
const result = await run(["agent", "reload", aid, "--format", "json"]);
|
|
42
|
-
if (!result?.ok)
|
|
43
|
-
throw new Error(`重载 AID ${aid} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
|
|
44
120
|
}
|
|
@@ -9,16 +9,35 @@ 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 agent_1 = require("../../agent");
|
|
12
|
+
const evolcore_1 = require("./evolcore");
|
|
12
13
|
const messages_1 = require("./messages");
|
|
13
14
|
const attachments_1 = require("./attachments");
|
|
14
15
|
const manifest = {
|
|
15
|
-
id: "aun", version: "1.0.
|
|
16
|
+
id: "aun", version: "1.0.36", type: "channel", displayName: "AUN 通道",
|
|
16
17
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
17
18
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
18
19
|
configSchema: "config.schema.json",
|
|
19
20
|
managementPage: "/abweb/channels/aun",
|
|
20
21
|
};
|
|
21
22
|
const newSessionCommand = /^\/new(?:[ \t]+[^\r\n]+)?$/;
|
|
23
|
+
const successfulSendStatuses = new Set(["accepted", "sent", "delivered", "duplicate"]);
|
|
24
|
+
function attachmentDownloadLinks(refs) {
|
|
25
|
+
return refs.flatMap((ref) => typeof ref.url === "string" && ref.url ? [ref.url] : []);
|
|
26
|
+
}
|
|
27
|
+
function withAttachmentDownloadLinks(text, kind, refs) {
|
|
28
|
+
if (kind !== "image")
|
|
29
|
+
return text;
|
|
30
|
+
const links = attachmentDownloadLinks(refs);
|
|
31
|
+
if (!links.length)
|
|
32
|
+
return text;
|
|
33
|
+
return `${text || "图片已生成。"}\n图片下载链接(预览不可用时):\n${links.join("\n")}`;
|
|
34
|
+
}
|
|
35
|
+
function withFailureDownloadLinks(text, refs) {
|
|
36
|
+
const links = attachmentDownloadLinks(refs);
|
|
37
|
+
if (!links.length || text?.includes("下载链接"))
|
|
38
|
+
return text;
|
|
39
|
+
return `${text || "附件已生成。"}\n附件下载链接(消息未确认时):\n${links.join("\n")}`;
|
|
40
|
+
}
|
|
22
41
|
async function loadSdk() {
|
|
23
42
|
const resolver = (0, node_module_1.createRequire)(__filename);
|
|
24
43
|
const dynamicImport = new Function("url", "return import(url)");
|
|
@@ -80,6 +99,60 @@ class AunPlugin {
|
|
|
80
99
|
this.sdk = options.sdk;
|
|
81
100
|
this.ingress = options.ingress;
|
|
82
101
|
}
|
|
102
|
+
aunManagement = {
|
|
103
|
+
list: async () => {
|
|
104
|
+
const groups = await this.identity.list();
|
|
105
|
+
const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? []), ...this.listStoreAids()])]
|
|
106
|
+
.filter((aid) => aidPattern.test(aid)).sort();
|
|
107
|
+
const bindings = Object.fromEntries(Object.entries(this.bindings)
|
|
108
|
+
.filter(([aid, agentId]) => aidPattern.test(aid) && /^[a-z][a-z0-9-]{1,63}$/.test(agentId) && agentId !== "aun" && agentId !== "abweb")
|
|
109
|
+
.sort(([left], [right]) => left.localeCompare(right)));
|
|
110
|
+
return { aids, bindings };
|
|
111
|
+
},
|
|
112
|
+
create: async ({ aid, ownerAid }) => {
|
|
113
|
+
if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
|
|
114
|
+
throw new AunCommandError("AID 格式无效");
|
|
115
|
+
const validAid = aid.trim();
|
|
116
|
+
const owner = typeof ownerAid === "string" && ownerAid.trim()
|
|
117
|
+
? ownerAid.trim()
|
|
118
|
+
: typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
|
|
119
|
+
if (!owner)
|
|
120
|
+
throw new AunCommandError("未提供 Owner AID,且未配置默认 Owner");
|
|
121
|
+
if (!aidPattern.test(owner))
|
|
122
|
+
throw new AunCommandError("Owner AID 格式无效");
|
|
123
|
+
const result = await this.createIdentity(validAid, owner);
|
|
124
|
+
const connection = result.connection === "ready" ? "ready" : "degraded";
|
|
125
|
+
if (connection !== "ready")
|
|
126
|
+
throw new AunCommandError("AID 已创建但连接失败,请先查询 AID 列表确认状态,不要重试创建", 502, { created: true, aid: validAid, connection });
|
|
127
|
+
return { aid: validAid, status: result.status, connection };
|
|
128
|
+
},
|
|
129
|
+
bind: async ({ aid, agentId, replace }) => {
|
|
130
|
+
if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
|
|
131
|
+
throw new AunCommandError("AID 格式无效");
|
|
132
|
+
if (typeof agentId !== "string" || !/^[a-z][a-z0-9-]{1,63}$/.test(agentId) || agentId === "aun" || agentId === "abweb")
|
|
133
|
+
throw new AunCommandError("Agent 插件 ID 无效");
|
|
134
|
+
if (typeof replace !== "boolean")
|
|
135
|
+
throw new AunCommandError("必须明确指定是否替换现有绑定");
|
|
136
|
+
const validAid = aid.trim();
|
|
137
|
+
const previousAgent = this.bindings[validAid];
|
|
138
|
+
const existing = Object.entries(this.bindings)
|
|
139
|
+
.filter(([, target]) => target === agentId)
|
|
140
|
+
.map(([boundAid]) => boundAid)
|
|
141
|
+
.filter((previousAid) => previousAid !== validAid)
|
|
142
|
+
.sort();
|
|
143
|
+
if (previousAgent && previousAgent !== agentId)
|
|
144
|
+
throw new AunCommandError("该 AID 已绑定到其他 Agent,不能通过替换转移");
|
|
145
|
+
if (existing.length && !replace)
|
|
146
|
+
throw new AunCommandError("目标 Agent 已有其他 AID 绑定;如需替换请明确设置 replace");
|
|
147
|
+
try {
|
|
148
|
+
await this.patchAgentAid(agentId, validAid);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new AunCommandError("AUN 身份关联失败", 502);
|
|
152
|
+
}
|
|
153
|
+
return { aid: validAid, agent: agentId, replacedAids: existing };
|
|
154
|
+
},
|
|
155
|
+
};
|
|
83
156
|
identity = {
|
|
84
157
|
list: async () => {
|
|
85
158
|
const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
|
|
@@ -124,7 +197,7 @@ class AunPlugin {
|
|
|
124
197
|
this.creating = false;
|
|
125
198
|
}
|
|
126
199
|
},
|
|
127
|
-
bind: async (pluginId, aid,
|
|
200
|
+
bind: async (pluginId, aid, port) => {
|
|
128
201
|
if (!aid || !aidPattern.test(aid))
|
|
129
202
|
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
130
203
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
@@ -140,6 +213,14 @@ class AunPlugin {
|
|
|
140
213
|
const loaded = this.store.load(aid);
|
|
141
214
|
if (!loaded.ok || !loaded.data)
|
|
142
215
|
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
216
|
+
// 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
|
|
217
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
|
|
218
|
+
const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
|
|
219
|
+
if (method === "json")
|
|
220
|
+
this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
|
|
221
|
+
else
|
|
222
|
+
this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
|
|
223
|
+
}
|
|
143
224
|
this.identities.add(aid);
|
|
144
225
|
if (!connection)
|
|
145
226
|
await this.connectAid(aid);
|
|
@@ -663,16 +744,7 @@ class AunPlugin {
|
|
|
663
744
|
this.validatePluginId(agent);
|
|
664
745
|
await this.requireAgent(agent);
|
|
665
746
|
}
|
|
666
|
-
|
|
667
|
-
try {
|
|
668
|
-
created = await this.identity.create(aid, owner);
|
|
669
|
-
}
|
|
670
|
-
catch (error) {
|
|
671
|
-
const exists = this.identities.has(aid) || this.listStoreAids().includes(aid);
|
|
672
|
-
if (exists)
|
|
673
|
-
throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
|
|
674
|
-
throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
|
|
675
|
-
}
|
|
747
|
+
const created = await this.createIdentity(aid, owner);
|
|
676
748
|
const data = { created: true, aid, connection: created.connection ?? "ready" };
|
|
677
749
|
if (created.connection !== "ready")
|
|
678
750
|
throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
|
|
@@ -690,6 +762,17 @@ class AunPlugin {
|
|
|
690
762
|
data: { ...data, ...(agent ? { agent } : {}) },
|
|
691
763
|
};
|
|
692
764
|
}
|
|
765
|
+
async createIdentity(aid, owner) {
|
|
766
|
+
try {
|
|
767
|
+
return await this.identity.create(aid, owner);
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
if (this.identities.has(aid) || this.listStoreAids().includes(aid)) {
|
|
771
|
+
throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
|
|
772
|
+
}
|
|
773
|
+
throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
693
776
|
async agents(readyOnly = true) {
|
|
694
777
|
const payload = await this.controlRequest("/api/plugins");
|
|
695
778
|
if (!Array.isArray(payload.plugins))
|
|
@@ -895,6 +978,8 @@ class AunPlugin {
|
|
|
895
978
|
let sent = 0;
|
|
896
979
|
let groupsCount = 0;
|
|
897
980
|
let sending = false;
|
|
981
|
+
let fallbackText;
|
|
982
|
+
let fallbackLinks = [];
|
|
898
983
|
try {
|
|
899
984
|
if (!client?.storage)
|
|
900
985
|
throw new Error("AUN SDK 不支持附件存储接口");
|
|
@@ -913,14 +998,19 @@ class AunPlugin {
|
|
|
913
998
|
}
|
|
914
999
|
groupsCount = groups.size;
|
|
915
1000
|
for (const [kind, group] of groups) {
|
|
916
|
-
const
|
|
1001
|
+
const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
|
|
1002
|
+
const payload = { type: kind, text: payloadText, attachments: group };
|
|
917
1003
|
if (message.threadId)
|
|
918
1004
|
payload.thread_id = message.threadId;
|
|
919
1005
|
if (message.messageId)
|
|
920
1006
|
payload.ref_message_id = message.messageId;
|
|
1007
|
+
fallbackText = payloadText;
|
|
1008
|
+
fallbackLinks = attachmentDownloadLinks(group);
|
|
921
1009
|
sending = true;
|
|
922
1010
|
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
923
|
-
await client.call("message.send", { to: message.peerId, payload, encrypt: true });
|
|
1011
|
+
const sendResult = await client.call("message.send", { to: message.peerId, payload, encrypt: true });
|
|
1012
|
+
if (typeof sendResult?.status !== "string" || !successfulSendStatuses.has(sendResult.status))
|
|
1013
|
+
throw new Error(`AUN 附件发送状态未确认:${sendResult?.status === undefined ? "未返回状态" : String(sendResult.status)}`);
|
|
924
1014
|
this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
|
|
925
1015
|
sending = false;
|
|
926
1016
|
sent++;
|
|
@@ -930,8 +1020,9 @@ class AunPlugin {
|
|
|
930
1020
|
deliveryFailed = true;
|
|
931
1021
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
932
1022
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
1023
|
+
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
933
1024
|
try {
|
|
934
|
-
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`, message.threadId);
|
|
1025
|
+
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true);
|
|
935
1026
|
}
|
|
936
1027
|
catch { /* 尽力反馈 */ }
|
|
937
1028
|
throw failure;
|
|
@@ -956,7 +1047,7 @@ class AunPlugin {
|
|
|
956
1047
|
}
|
|
957
1048
|
}
|
|
958
1049
|
}
|
|
959
|
-
async sendText(aid, peerId, text, threadId) {
|
|
1050
|
+
async sendText(aid, peerId, text, threadId, encryptedOnly = false) {
|
|
960
1051
|
const payload = { type: "text", text };
|
|
961
1052
|
if (threadId)
|
|
962
1053
|
payload.thread_id = threadId;
|
|
@@ -965,7 +1056,7 @@ class AunPlugin {
|
|
|
965
1056
|
await this.clients.get(aid).client.call("message.send", params);
|
|
966
1057
|
}
|
|
967
1058
|
catch (error) {
|
|
968
|
-
if (!this.isE2eeError(error))
|
|
1059
|
+
if (encryptedOnly || !this.isE2eeError(error))
|
|
969
1060
|
throw error;
|
|
970
1061
|
await this.clients.get(aid).client.call("message.send", { ...params, encrypt: false });
|
|
971
1062
|
}
|
|
@@ -13,6 +13,9 @@ function normalizeAunMessage(payload, selfAid, allowFrom) {
|
|
|
13
13
|
if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes(peerId)))
|
|
14
14
|
return undefined;
|
|
15
15
|
const body = data.payload && typeof data.payload === "object" ? data.payload : {};
|
|
16
|
+
// 对端的系统事件只作通知,转为任务回复会导致两个 Agent 循环互答。
|
|
17
|
+
if (body.type === "error" || body.type === "notice")
|
|
18
|
+
return undefined;
|
|
16
19
|
const text = typeof data.payload === "string" ? data.payload : typeof body.text === "string" ? body.text : "";
|
|
17
20
|
let attachments = [];
|
|
18
21
|
let attachmentError;
|
package/package.json
CHANGED