agent-bridge-aun 1.0.36 → 1.0.40
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 -1
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +64 -12
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
该插件依赖 `@agentunion/fastaun`,要求 Node.js 22.16 或更高版本。
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
附件回复按图片、视频和普通文件分组发送,并保留原话题和请求引用。图片使用不带普通说明正文的 `type: "image"` 消息,说明和下载链接另发加密文本,兼容 AUN 网页的图片卡片显示规则;MIME 类型为 `video/*` 的附件使用 `type: "video"`,普通文件使用 `type: "file"`,二者正文均附下载链接。三类附件参考 EvolCore 的标准附件格式,由插件直接通过 AUN SDK 上传可公开读取的对象,发送不带受限分享 URL 的标准对象引用,便于接收端取得下载票据及播放器分段读取。持有对象地址者可读取附件;旧版本已发送的私有分享不会自动改为公开。发送失败或状态未确认时仍向原 Peer 提供下载链接,不自动重发附件。图片已发送而说明失败时明确报告说明发送阶段,不误报 FS 上传失败。附件链路不调用 `ec` 命令,也不依赖 EvolCore 运行。
|
|
14
14
|
|
|
15
15
|
ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。成功注册并记录在插件 `created_aids` 中的 AID 属于本工程;共享密钥库中其余 AID 属于 EvolCore。旧版 `created-aids.json` 记录继续兼容。来源只用于展示,连接和关联由 AUN 插件直接管理。列表先显示已关联 Agent 的身份,各组按最近成功关联的时间倒序排列,时间相同或旧记录没有时间时按 AID 名称排序。关联时间保存在插件配置的 `binding_times` 中,刷新或重启后保留;旧记录不补造时间,再次关联成功后开始记录。
|
|
16
16
|
|
|
@@ -250,30 +250,12 @@ async function uploadAunAttachments(client, ownerAid, peerAid, items) {
|
|
|
250
250
|
const objectKey = `shared/${(0, node_crypto_1.randomUUID)()}/${filename.replace(/[^A-Za-z0-9._-]/g, "_")}`;
|
|
251
251
|
const sha256 = digest(item.data);
|
|
252
252
|
const contentType = item.contentType.toLowerCase();
|
|
253
|
-
//
|
|
254
|
-
const video = contentType.startsWith("video/");
|
|
253
|
+
// 会话媒体控件不携带 AID 凭据,沿用 ec msg 的标准可读对象上传方式。
|
|
255
254
|
const ref = { ownerAid, bucket: "default", objectKey, filename, contentType, sizeBytes: item.data.byteLength, sha256 };
|
|
256
|
-
const uploaded = await client.storage.writeBytes(`/${objectKey}`, item.data, { contentType, public:
|
|
255
|
+
const uploaded = await client.storage.writeBytes(`/${objectKey}`, item.data, { contentType, public: true, overwrite: true })
|
|
257
256
|
.catch(error => { throw new agent_1.AgentOperationError("AUN_FS_UPLOAD_FAILED", `上传 AUN FS 文件“${filename}”`, error); });
|
|
258
257
|
checkedMetadata({ ...uploaded, size: uploaded.size ?? uploaded.size_bytes ?? item.data.byteLength }, ref);
|
|
259
|
-
|
|
260
|
-
result.push({ owner_aid: ownerAid, object_key: objectKey, filename, content_type: contentType, size_bytes: item.data.byteLength, sha256 });
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const share = await client.storage.lowlevel.createShareLink({ owner: ownerAid, bucket: "default", objectKey, allowedAids: [peerAid], expireInSeconds: 86400 })
|
|
264
|
-
.catch(error => { throw new agent_1.AgentOperationError("AUN_FS_SHARE_FAILED", `创建 AUN FS 文件“${filename}”的接收方授权`, error); });
|
|
265
|
-
if (share.owner_aid && share.owner_aid !== ownerAid || share.bucket && share.bucket !== "default" || share.object_key && share.object_key !== objectKey)
|
|
266
|
-
throw new Error("AUN 附件分享对象不一致");
|
|
267
|
-
if (share.allowed_aids !== undefined && (!Array.isArray(share.allowed_aids) || share.allowed_aids.length !== 1 || share.allowed_aids[0] !== peerAid))
|
|
268
|
-
throw new Error("AUN 附件分享授权不一致");
|
|
269
|
-
const shareId = typeof share.share_id === "string" && /^[A-Za-z0-9]{10}$/.test(share.share_id) ? share.share_id : undefined;
|
|
270
|
-
if (share.share_id !== undefined && !shareId)
|
|
271
|
-
throw new Error("AUN 附件分享标识无效");
|
|
272
|
-
const url = typeof share.path_url === "string" && share.path_url ? share.path_url : shareId ? `https://${ownerAid}/${objectKey}?t=${shareId}` : "";
|
|
273
|
-
const token = url ? pathToken(url, ownerAid, objectKey) : undefined;
|
|
274
|
-
if (!token || shareId && shareId !== token)
|
|
275
|
-
throw new Error("AUN 附件分享链接无效");
|
|
276
|
-
result.push({ owner_aid: ownerAid, bucket: "default", object_key: objectKey, url, filename, content_type: item.contentType, size_bytes: item.data.byteLength, sha256 });
|
|
258
|
+
result.push({ owner_aid: ownerAid, object_key: objectKey, filename, content_type: contentType, size_bytes: item.data.byteLength, sha256 });
|
|
277
259
|
}
|
|
278
260
|
return result;
|
|
279
261
|
}
|
|
@@ -13,7 +13,7 @@ const evolcore_1 = require("./evolcore");
|
|
|
13
13
|
const messages_1 = require("./messages");
|
|
14
14
|
const attachments_1 = require("./attachments");
|
|
15
15
|
const manifest = {
|
|
16
|
-
id: "aun", version: "1.0.
|
|
16
|
+
id: "aun", version: "1.0.40", type: "channel", displayName: "AUN 通道",
|
|
17
17
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
18
18
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
19
19
|
configSchema: "config.schema.json",
|
|
@@ -22,15 +22,14 @@ const manifest = {
|
|
|
22
22
|
const newSessionCommand = /^\/new(?:[ \t]+[^\r\n]+)?$/;
|
|
23
23
|
const successfulSendStatuses = new Set(["accepted", "sent", "delivered", "duplicate"]);
|
|
24
24
|
function attachmentDownloadLinks(refs) {
|
|
25
|
-
return refs.flatMap((ref) => typeof ref.url === "string" && ref.url ? [ref.url]
|
|
25
|
+
return refs.flatMap((ref) => typeof ref.url === "string" && ref.url ? [ref.url]
|
|
26
|
+
: typeof ref.owner_aid === "string" && typeof ref.object_key === "string" ? [`https://${ref.owner_aid}/${ref.object_key}`] : []);
|
|
26
27
|
}
|
|
27
28
|
function withAttachmentDownloadLinks(text, kind, refs) {
|
|
28
|
-
if (kind !== "image")
|
|
29
|
-
return text;
|
|
30
29
|
const links = attachmentDownloadLinks(refs);
|
|
31
30
|
if (!links.length)
|
|
32
31
|
return text;
|
|
33
|
-
return `${text || "
|
|
32
|
+
return `${text || "附件已生成。"}\n${kind === "file" ? "文件" : "附件"}下载链接:\n${links.join("\n")}`;
|
|
34
33
|
}
|
|
35
34
|
function withFailureDownloadLinks(text, refs) {
|
|
36
35
|
const links = attachmentDownloadLinks(refs);
|
|
@@ -960,7 +959,8 @@ class AunPlugin {
|
|
|
960
959
|
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
961
960
|
...(attachments.length ? { attachments } : {}),
|
|
962
961
|
agentPlugin,
|
|
963
|
-
reply: async (text) => {
|
|
962
|
+
reply: async (text, signal) => {
|
|
963
|
+
signal?.throwIfAborted();
|
|
964
964
|
if (!text.trim())
|
|
965
965
|
return;
|
|
966
966
|
try {
|
|
@@ -972,7 +972,8 @@ class AunPlugin {
|
|
|
972
972
|
throw new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
|
|
973
973
|
}
|
|
974
974
|
},
|
|
975
|
-
replyAttachments: async (items, text) => {
|
|
975
|
+
replyAttachments: async (items, text, signal) => {
|
|
976
|
+
signal?.throwIfAborted();
|
|
976
977
|
if (!items.length)
|
|
977
978
|
return;
|
|
978
979
|
let sent = 0;
|
|
@@ -984,7 +985,8 @@ class AunPlugin {
|
|
|
984
985
|
if (!client?.storage)
|
|
985
986
|
throw new Error("AUN SDK 不支持附件存储接口");
|
|
986
987
|
const refs = uploadedRefs && uploadedItems === items ? uploadedRefs : await (0, attachments_1.uploadAunAttachments)({ storage: client.storage }, aid, message.peerId, items);
|
|
987
|
-
|
|
988
|
+
signal?.throwIfAborted();
|
|
989
|
+
this.context?.logger.info("AUN FS 标准附件上传完成", { ...trace, stage: "上传附件", files: items.map(item => ({ filename: item.filename, sizeBytes: item.data.byteLength })) });
|
|
988
990
|
if (!uploadedRefs || uploadedItems !== items) {
|
|
989
991
|
uploadedItems = items;
|
|
990
992
|
uploadedRefs = refs;
|
|
@@ -998,8 +1000,10 @@ class AunPlugin {
|
|
|
998
1000
|
}
|
|
999
1001
|
groupsCount = groups.size;
|
|
1000
1002
|
for (const [kind, group] of groups) {
|
|
1003
|
+
signal?.throwIfAborted();
|
|
1001
1004
|
const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
|
|
1002
|
-
|
|
1005
|
+
// AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
|
|
1006
|
+
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText }), attachments: group };
|
|
1003
1007
|
if (message.threadId)
|
|
1004
1008
|
payload.thread_id = message.threadId;
|
|
1005
1009
|
if (message.messageId)
|
|
@@ -1008,15 +1012,29 @@ class AunPlugin {
|
|
|
1008
1012
|
fallbackLinks = attachmentDownloadLinks(group);
|
|
1009
1013
|
sending = true;
|
|
1010
1014
|
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
1011
|
-
const sendResult = await
|
|
1015
|
+
const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
|
|
1012
1016
|
if (typeof sendResult?.status !== "string" || !successfulSendStatuses.has(sendResult.status))
|
|
1013
1017
|
throw new Error(`AUN 附件发送状态未确认:${sendResult?.status === undefined ? "未返回状态" : String(sendResult.status)}`);
|
|
1014
1018
|
this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
|
|
1015
1019
|
sending = false;
|
|
1016
1020
|
sent++;
|
|
1021
|
+
if (kind === "image" && payloadText) {
|
|
1022
|
+
try {
|
|
1023
|
+
signal?.throwIfAborted();
|
|
1024
|
+
const { attachments: _attachments, ...caption } = payload;
|
|
1025
|
+
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText }, encrypt: true });
|
|
1026
|
+
if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
|
|
1027
|
+
throw new Error(`AUN 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
|
|
1028
|
+
this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1017
1034
|
}
|
|
1018
1035
|
}
|
|
1019
1036
|
catch (error) {
|
|
1037
|
+
signal?.throwIfAborted();
|
|
1020
1038
|
deliveryFailed = true;
|
|
1021
1039
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
1022
1040
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
@@ -1053,12 +1071,38 @@ class AunPlugin {
|
|
|
1053
1071
|
payload.thread_id = threadId;
|
|
1054
1072
|
const params = { to: peerId, payload, encrypt: true };
|
|
1055
1073
|
try {
|
|
1056
|
-
await this.
|
|
1074
|
+
await this.callAunWithRecovery(aid, params);
|
|
1057
1075
|
}
|
|
1058
1076
|
catch (error) {
|
|
1059
1077
|
if (encryptedOnly || !this.isE2eeError(error))
|
|
1060
1078
|
throw error;
|
|
1061
|
-
await this.
|
|
1079
|
+
await this.callAunWithRecovery(aid, { ...params, encrypt: false });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async callAunWithRecovery(aid, params) {
|
|
1083
|
+
const connection = this.clients.get(aid);
|
|
1084
|
+
if (!connection)
|
|
1085
|
+
throw new agent_1.AgentOperationError("AUN_CONNECTION_MISSING", "查找 AUN 身份连接", new Error(`AID ${aid} 当前没有已连接的客户端`));
|
|
1086
|
+
try {
|
|
1087
|
+
return await connection.client.call("message.send", params);
|
|
1088
|
+
}
|
|
1089
|
+
catch (error) {
|
|
1090
|
+
if (!isAunWriteNotStarted(error))
|
|
1091
|
+
throw error;
|
|
1092
|
+
this.context?.logger.warn("AUN 发送前连接已断开,正在恢复 AUN 到 Peer 的连接并重试一次", { aid, peer: params.to, stage: "恢复 AUN 连接", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1093
|
+
try {
|
|
1094
|
+
connection.reconnecting ??= connection.client.connect({ auto_reconnect: true }).finally(() => { connection.reconnecting = undefined; });
|
|
1095
|
+
await connection.reconnecting;
|
|
1096
|
+
}
|
|
1097
|
+
catch (cause) {
|
|
1098
|
+
throw new agent_1.AgentOperationError("AUN_RECONNECT_FAILED", "恢复 AUN 到 Peer 的连接(消息确认未写出)", cause);
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
return await connection.client.call("message.send", params);
|
|
1102
|
+
}
|
|
1103
|
+
catch (cause) {
|
|
1104
|
+
throw new agent_1.AgentOperationError("AUN_SEND_FAILED", "AUN 重连后发送消息(仅重试一次)", cause);
|
|
1105
|
+
}
|
|
1062
1106
|
}
|
|
1063
1107
|
}
|
|
1064
1108
|
isE2eeError(error) {
|
|
@@ -1077,6 +1121,14 @@ exports.default = AunPlugin;
|
|
|
1077
1121
|
function safeError(error) {
|
|
1078
1122
|
return redact(error instanceof Error ? error.message : String(error));
|
|
1079
1123
|
}
|
|
1124
|
+
function isAunWriteNotStarted(error) {
|
|
1125
|
+
for (let current = error; current && typeof current === "object"; current = current.cause) {
|
|
1126
|
+
const value = current;
|
|
1127
|
+
if (value.name === "ConnectionError" && typeof value.message === "string" && /^(?:client is not connected|transport not connected|send cancelled before write:|transport closed$)/i.test(value.message))
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1080
1132
|
async function readResponse(response, limit) {
|
|
1081
1133
|
if (!response.body) {
|
|
1082
1134
|
const raw = await response.text();
|
package/package.json
CHANGED