agent-bridge-aun 1.0.36 → 1.0.39
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 +56 -10
- 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.39", 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);
|
|
@@ -984,7 +983,7 @@ class AunPlugin {
|
|
|
984
983
|
if (!client?.storage)
|
|
985
984
|
throw new Error("AUN SDK 不支持附件存储接口");
|
|
986
985
|
const refs = uploadedRefs && uploadedItems === items ? uploadedRefs : await (0, attachments_1.uploadAunAttachments)({ storage: client.storage }, aid, message.peerId, items);
|
|
987
|
-
this.context?.logger.info("AUN FS
|
|
986
|
+
this.context?.logger.info("AUN FS 标准附件上传完成", { ...trace, stage: "上传附件", files: items.map(item => ({ filename: item.filename, sizeBytes: item.data.byteLength })) });
|
|
988
987
|
if (!uploadedRefs || uploadedItems !== items) {
|
|
989
988
|
uploadedItems = items;
|
|
990
989
|
uploadedRefs = refs;
|
|
@@ -999,7 +998,8 @@ class AunPlugin {
|
|
|
999
998
|
groupsCount = groups.size;
|
|
1000
999
|
for (const [kind, group] of groups) {
|
|
1001
1000
|
const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
|
|
1002
|
-
|
|
1001
|
+
// AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
|
|
1002
|
+
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText }), attachments: group };
|
|
1003
1003
|
if (message.threadId)
|
|
1004
1004
|
payload.thread_id = message.threadId;
|
|
1005
1005
|
if (message.messageId)
|
|
@@ -1008,12 +1008,24 @@ class AunPlugin {
|
|
|
1008
1008
|
fallbackLinks = attachmentDownloadLinks(group);
|
|
1009
1009
|
sending = true;
|
|
1010
1010
|
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
1011
|
-
const sendResult = await
|
|
1011
|
+
const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
|
|
1012
1012
|
if (typeof sendResult?.status !== "string" || !successfulSendStatuses.has(sendResult.status))
|
|
1013
1013
|
throw new Error(`AUN 附件发送状态未确认:${sendResult?.status === undefined ? "未返回状态" : String(sendResult.status)}`);
|
|
1014
1014
|
this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
|
|
1015
1015
|
sending = false;
|
|
1016
1016
|
sent++;
|
|
1017
|
+
if (kind === "image" && payloadText) {
|
|
1018
|
+
try {
|
|
1019
|
+
const { attachments: _attachments, ...caption } = payload;
|
|
1020
|
+
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText }, encrypt: true });
|
|
1021
|
+
if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
|
|
1022
|
+
throw new Error(`AUN 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
|
|
1023
|
+
this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
|
|
1024
|
+
}
|
|
1025
|
+
catch (error) {
|
|
1026
|
+
throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1017
1029
|
}
|
|
1018
1030
|
}
|
|
1019
1031
|
catch (error) {
|
|
@@ -1053,12 +1065,38 @@ class AunPlugin {
|
|
|
1053
1065
|
payload.thread_id = threadId;
|
|
1054
1066
|
const params = { to: peerId, payload, encrypt: true };
|
|
1055
1067
|
try {
|
|
1056
|
-
await this.
|
|
1068
|
+
await this.callAunWithRecovery(aid, params);
|
|
1057
1069
|
}
|
|
1058
1070
|
catch (error) {
|
|
1059
1071
|
if (encryptedOnly || !this.isE2eeError(error))
|
|
1060
1072
|
throw error;
|
|
1061
|
-
await this.
|
|
1073
|
+
await this.callAunWithRecovery(aid, { ...params, encrypt: false });
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
async callAunWithRecovery(aid, params) {
|
|
1077
|
+
const connection = this.clients.get(aid);
|
|
1078
|
+
if (!connection)
|
|
1079
|
+
throw new agent_1.AgentOperationError("AUN_CONNECTION_MISSING", "查找 AUN 身份连接", new Error(`AID ${aid} 当前没有已连接的客户端`));
|
|
1080
|
+
try {
|
|
1081
|
+
return await connection.client.call("message.send", params);
|
|
1082
|
+
}
|
|
1083
|
+
catch (error) {
|
|
1084
|
+
if (!isAunWriteNotStarted(error))
|
|
1085
|
+
throw error;
|
|
1086
|
+
this.context?.logger.warn("AUN 发送前连接已断开,正在恢复 AUN 到 Peer 的连接并重试一次", { aid, peer: params.to, stage: "恢复 AUN 连接", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1087
|
+
try {
|
|
1088
|
+
connection.reconnecting ??= connection.client.connect({ auto_reconnect: true }).finally(() => { connection.reconnecting = undefined; });
|
|
1089
|
+
await connection.reconnecting;
|
|
1090
|
+
}
|
|
1091
|
+
catch (cause) {
|
|
1092
|
+
throw new agent_1.AgentOperationError("AUN_RECONNECT_FAILED", "恢复 AUN 到 Peer 的连接(消息确认未写出)", cause);
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
return await connection.client.call("message.send", params);
|
|
1096
|
+
}
|
|
1097
|
+
catch (cause) {
|
|
1098
|
+
throw new agent_1.AgentOperationError("AUN_SEND_FAILED", "AUN 重连后发送消息(仅重试一次)", cause);
|
|
1099
|
+
}
|
|
1062
1100
|
}
|
|
1063
1101
|
}
|
|
1064
1102
|
isE2eeError(error) {
|
|
@@ -1077,6 +1115,14 @@ exports.default = AunPlugin;
|
|
|
1077
1115
|
function safeError(error) {
|
|
1078
1116
|
return redact(error instanceof Error ? error.message : String(error));
|
|
1079
1117
|
}
|
|
1118
|
+
function isAunWriteNotStarted(error) {
|
|
1119
|
+
for (let current = error; current && typeof current === "object"; current = current.cause) {
|
|
1120
|
+
const value = current;
|
|
1121
|
+
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))
|
|
1122
|
+
return true;
|
|
1123
|
+
}
|
|
1124
|
+
return false;
|
|
1125
|
+
}
|
|
1080
1126
|
async function readResponse(response, limit) {
|
|
1081
1127
|
if (!response.body) {
|
|
1082
1128
|
const raw = await response.text();
|
package/package.json
CHANGED