agent-bridge-aun 1.0.46 → 1.0.49
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 +3 -0
- package/config.schema.json +12 -0
- package/dist/src-ts/plugins/aun/ability-control.js +81 -0
- package/dist/src-ts/plugins/aun/ability-operation-store.js +23 -10
- package/dist/src-ts/plugins/aun/ability-owner.js +23 -1
- package/dist/src-ts/plugins/aun/evolcore.js +6 -4
- package/dist/src-ts/plugins/aun/index.js +472 -118
- package/dist/src-ts/plugins/aun/peer-messaging.js +5 -3
- package/dist/src-ts/plugins/aun/public/app.js +0 -125
- package/dist/src-ts/plugins/aun/public/index.html +0 -2
- package/dist/src-ts/plugins/aun/public/style.css +0 -8
- package/package.json +1 -1
- package/plugin.json +1 -1
|
@@ -20,6 +20,7 @@ const ability_publication_1 = require("./ability-publication");
|
|
|
20
20
|
const ability_operations_1 = require("./ability-operations");
|
|
21
21
|
const ability_operation_store_1 = require("./ability-operation-store");
|
|
22
22
|
const ability_owner_1 = require("./ability-owner");
|
|
23
|
+
const ability_control_1 = require("./ability-control");
|
|
23
24
|
const fs_1 = require("./fs");
|
|
24
25
|
const fs_commands_1 = require("./fs-commands");
|
|
25
26
|
const fs_transfer_1 = require("./fs-transfer");
|
|
@@ -30,7 +31,7 @@ const ownership_1 = require("./ownership");
|
|
|
30
31
|
const sdk_loader_1 = require("./sdk-loader");
|
|
31
32
|
const owned_profiles_1 = require("./owned-profiles");
|
|
32
33
|
const manifest = {
|
|
33
|
-
id: "aun", version: "1.0.
|
|
34
|
+
id: "aun", version: "1.0.49", type: "channel", displayName: "AUN 通道",
|
|
34
35
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
35
36
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
36
37
|
configSchema: "config.schema.json",
|
|
@@ -107,6 +108,8 @@ class AunPlugin {
|
|
|
107
108
|
abilitySelections = {};
|
|
108
109
|
abilityLedgers = {};
|
|
109
110
|
abilityOwners = {};
|
|
111
|
+
abilityOwnerVerifications = {};
|
|
112
|
+
abilityOwnerVerificationWrites = new Map();
|
|
110
113
|
abilityOperations;
|
|
111
114
|
peerMessages;
|
|
112
115
|
abilityAuditUnavailable = false;
|
|
@@ -114,6 +117,7 @@ class AunPlugin {
|
|
|
114
117
|
abilityPublications = new Map();
|
|
115
118
|
revokingAbilityAids = new Set();
|
|
116
119
|
abilityRevisions = new Map();
|
|
120
|
+
abilityApprovalViews = new Map();
|
|
117
121
|
creating = false;
|
|
118
122
|
repairingOwners = new Set();
|
|
119
123
|
unregister = [];
|
|
@@ -511,8 +515,43 @@ class AunPlugin {
|
|
|
511
515
|
this.config.ability_selections = this.abilitySelections;
|
|
512
516
|
this.config.ability_ledgers = this.abilityLedgers;
|
|
513
517
|
this.config.ability_owners = this.abilityOwners;
|
|
518
|
+
this.config.ability_owner_verifications = this.abilityOwnerVerifications;
|
|
514
519
|
await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
|
|
515
520
|
}
|
|
521
|
+
async saveAbilityOwnerVerification(aid, ownerAid) {
|
|
522
|
+
const pending = this.abilityOwnerVerificationWrites.get(aid);
|
|
523
|
+
if (pending)
|
|
524
|
+
return pending;
|
|
525
|
+
if (this.isAbilityOwnerVerified(aid, ownerAid))
|
|
526
|
+
return;
|
|
527
|
+
const operation = Promise.resolve().then(async () => {
|
|
528
|
+
if (this.abilityOwners[aid] !== ownerAid)
|
|
529
|
+
throw new Error("Provider 的 Owner 配置已改变");
|
|
530
|
+
const previous = this.abilityOwnerVerifications[aid];
|
|
531
|
+
this.abilityOwnerVerifications = { ...this.abilityOwnerVerifications, [aid]: ownerAid };
|
|
532
|
+
try {
|
|
533
|
+
await this.persist();
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
if (previous === undefined)
|
|
537
|
+
delete this.abilityOwnerVerifications[aid];
|
|
538
|
+
else
|
|
539
|
+
this.abilityOwnerVerifications[aid] = previous;
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
this.abilityOwnerVerificationWrites.set(aid, operation);
|
|
544
|
+
try {
|
|
545
|
+
await operation;
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
if (this.abilityOwnerVerificationWrites.get(aid) === operation)
|
|
549
|
+
this.abilityOwnerVerificationWrites.delete(aid);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
isAbilityOwnerVerified(aid, ownerAid) {
|
|
553
|
+
return !this.abilityOwnerVerificationWrites.has(aid) && this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
|
|
554
|
+
}
|
|
516
555
|
async activate(context) {
|
|
517
556
|
this.context = context;
|
|
518
557
|
this.config = { ...context.config };
|
|
@@ -554,6 +593,7 @@ class AunPlugin {
|
|
|
554
593
|
this.sdk ??= await loadSdk();
|
|
555
594
|
await (0, promises_1.mkdir)(this.path(), { recursive: true });
|
|
556
595
|
this.ownerProof.clear();
|
|
596
|
+
this.abilityApprovalViews.clear();
|
|
557
597
|
this.abilityAuditUnavailable = false;
|
|
558
598
|
try {
|
|
559
599
|
this.abilityOperations = await ability_operation_store_1.AbilityOperationStore.open((0, node_path_1.join)(context.dataDir, "ability-operations.json"));
|
|
@@ -610,12 +650,21 @@ class AunPlugin {
|
|
|
610
650
|
|| Object.entries(owners).some(([provider, owner]) => !aidPattern.test(provider) || typeof owner !== "string" || !aidPattern.test(owner))))
|
|
611
651
|
throw new Error("ability_owners 必须按 Provider AID 配置有效的 Owner AID");
|
|
612
652
|
this.abilityOwners = { ...owners };
|
|
653
|
+
const verifications = this.config.ability_owner_verifications;
|
|
654
|
+
if (verifications !== undefined && (!verifications || typeof verifications !== "object" || Array.isArray(verifications)
|
|
655
|
+
|| Object.entries(verifications).some(([provider, owner]) => !aidPattern.test(provider) || typeof owner !== "string" || !aidPattern.test(owner))))
|
|
656
|
+
throw new Error("ability_owner_verifications 必须按 Provider AID 配置有效的 Owner AID");
|
|
657
|
+
const persistedVerifications = { ...verifications };
|
|
658
|
+
this.abilityOwnerVerifications = Object.fromEntries(Object.entries(persistedVerifications).filter(([provider, owner]) => this.abilityOwners[provider] === owner));
|
|
659
|
+
const staleOwnerVerification = Object.keys(this.abilityOwnerVerifications).length !== Object.keys(persistedVerifications).length;
|
|
613
660
|
for (const aid of Array.isArray(this.config.identities) ? this.config.identities : []) {
|
|
614
661
|
if (typeof aid === "string" && aidPattern.test(aid))
|
|
615
662
|
this.identities.add(aid);
|
|
616
663
|
}
|
|
617
664
|
if (this.aid)
|
|
618
665
|
this.identities.add(this.aid);
|
|
666
|
+
if (staleOwnerVerification)
|
|
667
|
+
await this.persist();
|
|
619
668
|
this.acceptingMessages = true;
|
|
620
669
|
// 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
|
|
621
670
|
for (const aid of Object.keys(this.bindings)) {
|
|
@@ -735,7 +784,11 @@ class AunPlugin {
|
|
|
735
784
|
const connection = this.clients.get(aid);
|
|
736
785
|
if (!connection || connection.subscription || !this.bindings[aid])
|
|
737
786
|
return;
|
|
738
|
-
connection.subscription = connection.client.on("message.received", (payload) => this.
|
|
787
|
+
connection.subscription = connection.client.on("message.received", (payload) => this.enqueueMessage(payload, aid));
|
|
788
|
+
}
|
|
789
|
+
enqueueMessage(payload, aid) {
|
|
790
|
+
// SDK 串行等待事件回调;任务由现有入站账本排空,不能占住 SDK 的消息分发队列。
|
|
791
|
+
void this.onMessage(payload, aid).catch(error => this.context?.logger.warn("AUN 入站消息处理失败", { aid, stage: "处理已接收消息", error: safeError((0, agent_1.errorDetails)(error)) }));
|
|
739
792
|
}
|
|
740
793
|
async connectAid(aid) {
|
|
741
794
|
try {
|
|
@@ -747,7 +800,7 @@ class AunPlugin {
|
|
|
747
800
|
const ownedSubscription = this.ownedProfiles && !this.config.owner_watch_aids?.includes(aid)
|
|
748
801
|
? client.on("owned_agentmd.changed", payload => this.onOwnedProfile(aid, payload)) : undefined;
|
|
749
802
|
const subscription = this.bindings[aid]
|
|
750
|
-
? client.on("message.received", (payload) => this.
|
|
803
|
+
? client.on("message.received", (payload) => this.enqueueMessage(payload, aid))
|
|
751
804
|
: undefined;
|
|
752
805
|
this.clients.set(aid, { client, subscription, ownedSubscription });
|
|
753
806
|
try {
|
|
@@ -777,6 +830,7 @@ class AunPlugin {
|
|
|
777
830
|
clearInterval(this.ownedProfileTimer);
|
|
778
831
|
this.ownedProfileTimer = undefined;
|
|
779
832
|
this.ownerProof.clear();
|
|
833
|
+
this.abilityApprovalViews.clear();
|
|
780
834
|
for (const { subscription, ownedSubscription } of this.clients.values()) {
|
|
781
835
|
subscription?.unsubscribe();
|
|
782
836
|
ownedSubscription?.unsubscribe();
|
|
@@ -932,7 +986,7 @@ class AunPlugin {
|
|
|
932
986
|
for (const aid of Object.keys(this.bindings)) {
|
|
933
987
|
const definitions = await this.abilityDefinitions(aid);
|
|
934
988
|
const ownerAid = this.abilityOwners[aid];
|
|
935
|
-
const ownerVerified = Boolean(session && ownerAid && this.
|
|
989
|
+
const ownerVerified = Boolean(session && ownerAid && this.isAbilityOwnerVerified(aid, ownerAid));
|
|
936
990
|
const publishAvailable = Boolean(this.context?.managementActor?.(request) && ownerVerified && this.context?.callPluginAbility && this.abilityOperations && !this.abilityAuditUnavailable);
|
|
937
991
|
result[aid] = { agent: this.bindings[aid], selected: this.abilitySelections[aid] ?? [], definitions, published: Object.keys(this.abilityLedgers[aid] ?? {}).length > 0, pluginVersion: this.context?.pluginVersion?.(this.bindings[aid]), ownerAid, ownerVerified, publishAvailable, reason: publishAvailable ? "需本机已认证管理会话,每次执行仍须明确批准。" : "逐次 Owner 确认需本机已认证管理会话,并验证明确配置的 Owner AID;已有索引仍可撤销。" };
|
|
938
992
|
}
|
|
@@ -954,42 +1008,52 @@ class AunPlugin {
|
|
|
954
1008
|
const paths = publish ? (Array.isArray(body.paths) && body.paths.every((path) => typeof path === "string") ? body.paths : undefined) : [];
|
|
955
1009
|
if (!paths)
|
|
956
1010
|
return this.json(response, 400, { error: "发布路径必须是字符串数组" });
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
963
|
-
if (!this.ownerProof.verified(session, aid, owner) || this.abilityOwners[aid] !== owner || this.revokingAbilityAids.has(aid))
|
|
964
|
-
throw new Error("Owner 验证已失效或身份正在撤销,请重新确认");
|
|
965
|
-
const storage = this.clients.get(aid)?.client.storage;
|
|
966
|
-
if (!storage)
|
|
967
|
-
throw new Error("AUN 身份存储不可用");
|
|
968
|
-
const pluginId = this.bindings[aid];
|
|
969
|
-
const definitions = await this.abilityDefinitions(aid);
|
|
970
|
-
const published = await (0, ability_publication_1.publishAbilityCatalog)({ storage, aid, pluginId, pluginVersion: this.context?.pluginVersion?.(pluginId) ?? "", definitions: definitions.map(definition => ({ ...definition })), selectedPaths: paths, ledger: this.abilityLedgers[aid] ?? {}, beforeIndexWrite: async (pending) => { this.abilityLedgers[aid] = pending; await this.persist(); } });
|
|
971
|
-
this.abilityLedgers[aid] = published.ledger;
|
|
972
|
-
this.abilitySelections[aid] = [...new Set(paths)];
|
|
973
|
-
await this.persist();
|
|
974
|
-
});
|
|
975
|
-
this.abilityPublications.set(aid, operation);
|
|
976
|
-
try {
|
|
977
|
-
await operation;
|
|
978
|
-
}
|
|
979
|
-
finally {
|
|
980
|
-
if (this.abilityPublications.get(aid) === operation)
|
|
981
|
-
this.abilityPublications.delete(aid);
|
|
982
|
-
}
|
|
983
|
-
return this.json(response, 200, { ok: true, aid, revoked: false });
|
|
984
|
-
}
|
|
985
|
-
await this.revokePublishedAbilities(aid);
|
|
986
|
-
return this.json(response, 200, { ok: true, aid, revoked: true });
|
|
1011
|
+
const context = this.context;
|
|
1012
|
+
const session = context.managementSession?.(request);
|
|
1013
|
+
await this.setPublishedAbilities(aid, paths, () => this.context === context && Boolean(context.managementActor?.(request))
|
|
1014
|
+
&& (!paths.length || Boolean(session && context.managementSession?.(request)?.id === session.id)));
|
|
1015
|
+
return this.json(response, 200, { ok: true, aid, revoked: !paths.length });
|
|
987
1016
|
}
|
|
988
1017
|
catch (error) {
|
|
989
1018
|
this.context?.logger.warn("AUN Ability 能力索引更新失败", { error: safeError(error) });
|
|
990
1019
|
return this.json(response, 409, { error: safeError(error) });
|
|
991
1020
|
}
|
|
992
1021
|
};
|
|
1022
|
+
async setPublishedAbilities(aid, paths, authorized) {
|
|
1023
|
+
const owner = this.abilityOwners[aid];
|
|
1024
|
+
const assertAuthorized = () => {
|
|
1025
|
+
if (paths.length && (!owner || !this.isAbilityOwnerVerified(aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable || this.revokingAbilityAids.has(aid)))
|
|
1026
|
+
throw new Error("逐次 Owner 确认需可用操作账本和已验证的 Owner AID,请重新确认");
|
|
1027
|
+
if (!authorized())
|
|
1028
|
+
throw new Error("能力发布的管理身份已失效");
|
|
1029
|
+
};
|
|
1030
|
+
assertAuthorized();
|
|
1031
|
+
if (!paths.length) {
|
|
1032
|
+
await this.revokePublishedAbilities(aid);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1036
|
+
assertAuthorized();
|
|
1037
|
+
const storage = this.clients.get(aid)?.client.storage;
|
|
1038
|
+
if (!storage)
|
|
1039
|
+
throw new Error("AUN 身份存储不可用");
|
|
1040
|
+
const pluginId = this.bindings[aid];
|
|
1041
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1042
|
+
assertAuthorized();
|
|
1043
|
+
const published = await (0, ability_publication_1.publishAbilityCatalog)({ storage, aid, pluginId, pluginVersion: this.context?.pluginVersion?.(pluginId) ?? "", definitions: definitions.map(definition => ({ ...definition })), selectedPaths: paths, ledger: this.abilityLedgers[aid] ?? {}, beforeIndexWrite: async (pending) => { assertAuthorized(); this.abilityLedgers[aid] = pending; await this.persist(); assertAuthorized(); } });
|
|
1044
|
+
this.abilityLedgers[aid] = published.ledger;
|
|
1045
|
+
this.abilitySelections[aid] = [...new Set(paths)];
|
|
1046
|
+
await this.persist();
|
|
1047
|
+
});
|
|
1048
|
+
this.abilityPublications.set(aid, operation);
|
|
1049
|
+
try {
|
|
1050
|
+
await operation;
|
|
1051
|
+
}
|
|
1052
|
+
finally {
|
|
1053
|
+
if (this.abilityPublications.get(aid) === operation)
|
|
1054
|
+
this.abilityPublications.delete(aid);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
993
1057
|
challengeAbilityOwner = async (request, response) => {
|
|
994
1058
|
const session = this.context?.managementSession?.(request);
|
|
995
1059
|
if (!this.context?.managementActor?.(request) || !session)
|
|
@@ -1000,19 +1064,28 @@ class AunPlugin {
|
|
|
1000
1064
|
const ownerAid = typeof body.ownerAid === "string" ? body.ownerAid.trim() : "";
|
|
1001
1065
|
if (!aidPattern.test(aid) || !this.bindings[aid] || !aidPattern.test(ownerAid))
|
|
1002
1066
|
return this.json(response, 400, { error: "需要已关联的 Provider AID 和明确指定的 Owner AID" });
|
|
1067
|
+
if (this.isAbilityOwnerVerified(aid, ownerAid))
|
|
1068
|
+
return this.json(response, 200, { ok: true, aid, ownerAid, alreadyVerified: true });
|
|
1003
1069
|
if (this.abilityOwners[aid] !== ownerAid) {
|
|
1004
1070
|
this.revokingAbilityAids.add(aid);
|
|
1005
1071
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1006
1072
|
this.ownerProof.clear(aid);
|
|
1073
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1074
|
+
if (JSON.parse(key)[0] === aid)
|
|
1075
|
+
this.abilityApprovalViews.delete(key);
|
|
1007
1076
|
try {
|
|
1008
1077
|
await this.invalidateAbilityOperations(aid);
|
|
1009
1078
|
const previous = this.abilityOwners;
|
|
1079
|
+
const previousVerifications = this.abilityOwnerVerifications;
|
|
1010
1080
|
this.abilityOwners = { ...previous, [aid]: ownerAid };
|
|
1081
|
+
this.abilityOwnerVerifications = { ...previousVerifications };
|
|
1082
|
+
delete this.abilityOwnerVerifications[aid];
|
|
1011
1083
|
try {
|
|
1012
1084
|
await this.persist();
|
|
1013
1085
|
}
|
|
1014
1086
|
catch (error) {
|
|
1015
1087
|
this.abilityOwners = previous;
|
|
1088
|
+
this.abilityOwnerVerifications = previousVerifications;
|
|
1016
1089
|
throw error;
|
|
1017
1090
|
}
|
|
1018
1091
|
}
|
|
@@ -1021,7 +1094,7 @@ class AunPlugin {
|
|
|
1021
1094
|
}
|
|
1022
1095
|
}
|
|
1023
1096
|
const challenge = this.ownerProof.start(session, aid, ownerAid);
|
|
1024
|
-
await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text:
|
|
1097
|
+
await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text: `AUN Ability 管理授权确认\nProvider AID:${aid}\nOwner AID:${ownerAid}\n用途:管理本 Provider 已公开的 Ability,并逐项确认能力调用。\n请直接回复:确认\n确认截止:${new Date(challenge.expiresAt).toLocaleString("zh-CN")}(最长 12 小时,受发起管理会话有效期限制)。\n此操作只验证 Owner 身份,不批准任何能力调用。` } });
|
|
1025
1098
|
this.context?.logger.info("AUN Owner 会话验证已发起,需本机已认证管理会话", { aid, ownerAid });
|
|
1026
1099
|
this.json(response, 200, { ok: true, aid, ownerAid, expiresAt: challenge.expiresAt });
|
|
1027
1100
|
}
|
|
@@ -1043,82 +1116,233 @@ class AunPlugin {
|
|
|
1043
1116
|
const session = this.context?.managementSession?.(request);
|
|
1044
1117
|
if (!this.context?.managementActor?.(request) || !session)
|
|
1045
1118
|
return this.json(response, 403, { error: "逐次 Owner 确认需本机已认证管理会话" });
|
|
1046
|
-
const store = this.abilityOperations;
|
|
1047
|
-
if (!store || this.abilityAuditUnavailable)
|
|
1048
|
-
return this.json(response, 503, { error: "持久操作账本不可用" });
|
|
1049
1119
|
try {
|
|
1050
1120
|
const body = await this.readJsonBody(request);
|
|
1051
1121
|
if (typeof body.key !== "string" || typeof body.confirmationFingerprint !== "string" || typeof body.approved !== "boolean")
|
|
1052
1122
|
return this.json(response, 400, { error: "必须明确提交操作键、完整确认指纹和批准或拒绝决定" });
|
|
1053
|
-
const
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1123
|
+
const context = this.context;
|
|
1124
|
+
const result = await this.decideAbilityOperation(body.key, body.confirmationFingerprint, body.approved, () => this.context === context && Boolean(context.managementActor?.(request)) && context.managementSession?.(request)?.id === session.id);
|
|
1125
|
+
this.json(response, 200, result);
|
|
1126
|
+
}
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
this.json(response, error instanceof AunCommandError ? error.statusCode : 409, { error: safeError(error) });
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
async decideAbilityOperation(key, fingerprint, approved, authorized, providerAid) {
|
|
1132
|
+
const store = this.abilityOperations;
|
|
1133
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1134
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1135
|
+
const original = await store.get(key);
|
|
1136
|
+
if (!original || (providerAid !== undefined && original.providerAid !== providerAid))
|
|
1137
|
+
throw new AunCommandError("能力操作不存在", 404);
|
|
1138
|
+
const ownerAid = this.abilityOwners[original.providerAid];
|
|
1139
|
+
if (!ownerAid || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1140
|
+
throw new AunCommandError("需完成该 Provider 的 Owner AID 验证", 403);
|
|
1141
|
+
if (original.confirmationFingerprint !== fingerprint)
|
|
1142
|
+
throw new AunCommandError("确认指纹与待确认请求不一致", 409);
|
|
1143
|
+
const revision = this.abilityRevisions.get(original.providerAid) ?? 0;
|
|
1144
|
+
if (approved && original.state === "pending_confirmation") {
|
|
1145
|
+
const failure = await this.abilityInvocationFailure(original.providerAid, original.authenticatedCaller, original.path, original.definitionId, original.params);
|
|
1146
|
+
const pluginId = this.bindings[original.providerAid];
|
|
1147
|
+
if (failure || (this.abilityRevisions.get(original.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(original.providerAid) || original.executionTarget.pluginId !== pluginId || original.executionTarget.ownerAid !== ownerAid || original.executionTarget.pluginVersion !== (this.context?.pluginVersion?.(pluginId) ?? ""))
|
|
1148
|
+
throw new AunCommandError(failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用", 409);
|
|
1149
|
+
}
|
|
1150
|
+
if (!authorized() || !this.acceptingMessages || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1151
|
+
throw new AunCommandError("通道或 Owner 管理会话已失效", 403);
|
|
1152
|
+
const confirmation = approved ? await store.approve(original.key, fingerprint) : await store.reject(original.key, fingerprint);
|
|
1153
|
+
let entry = confirmation.entry;
|
|
1154
|
+
if (confirmation.started) {
|
|
1155
|
+
let result;
|
|
1156
|
+
let state;
|
|
1157
|
+
let handlerAuthorized = false;
|
|
1158
|
+
try {
|
|
1159
|
+
const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
|
|
1160
|
+
const messageExpired = Date.now() >= entry.expiresAt;
|
|
1161
|
+
const confirmationExpired = Date.now() >= entry.confirmationExpiresAt;
|
|
1162
|
+
const expired = messageExpired || confirmationExpired;
|
|
1163
|
+
const invalidated = (this.abilityRevisions.get(entry.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(entry.providerAid);
|
|
1164
|
+
if (failure || expired || invalidated || !authorized() || !this.isAbilityOwnerVerified(entry.providerAid, ownerAid) || this.abilityOwners[entry.providerAid] !== entry.executionTarget.ownerAid || this.bindings[entry.providerAid] !== entry.executionTarget.pluginId || (this.context?.pluginVersion?.(String(entry.executionTarget.pluginId)) ?? "") !== entry.executionTarget.pluginVersion) {
|
|
1165
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, expired ? (messageExpired ? "message_expired" : "confirmation_expired") : failure?.code ?? (invalidated ? "provider_invalidated" : "permission_denied"), messageExpired ? "调用已超过最晚启动时间" : confirmationExpired ? "Owner 确认申请已过期,不会执行" : failure?.message ?? "Owner 管理会话或执行目标已改变,本次能力没有执行"), execution_state: "not_executed" };
|
|
1166
|
+
state = "failed";
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
const context = this.context;
|
|
1170
|
+
const confirmed = entry;
|
|
1171
|
+
const beforeStart = () => {
|
|
1172
|
+
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !authorized() || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
|
|
1173
|
+
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
1174
|
+
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
1175
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1176
|
+
if ((this.abilityRevisions.get(confirmed.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(confirmed.providerAid) || !this.clients.has(confirmed.providerAid) || this.bindings[confirmed.providerAid] !== pluginId || this.abilityOwners[confirmed.providerAid] !== confirmed.executionTarget.ownerAid || (context.pluginVersion?.(pluginId) ?? "") !== confirmed.executionTarget.pluginVersion || context.pluginStatus(pluginId) !== "ready" || !this.abilitySelections[confirmed.providerAid]?.includes(confirmed.path) || this.abilityLedgers[confirmed.providerAid]?.[confirmed.path]?.definition_id !== confirmed.definitionId || (rules.length && !rules.includes("*") && !rules.includes(confirmed.authenticatedCaller)))
|
|
1177
|
+
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
1178
|
+
handlerAuthorized = true;
|
|
1179
|
+
};
|
|
1180
|
+
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
1181
|
+
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
1182
|
+
state = "succeeded";
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
|
|
1187
|
+
const code = error.code;
|
|
1188
|
+
const notExecuted = !handlerAuthorized && ["message_expired", "confirmation_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
|
|
1189
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, notExecuted ? code : "execution_failed", notExecuted ? (code === "message_expired" ? "调用已超过最晚启动时间" : code === "confirmation_expired" ? "Owner 确认申请已过期,不会执行" : "管理会话或 Provider 状态已改变,本次能力没有执行") : "能力执行失败,请检查 Provider 运行日志;不会自动重试"), execution_state: notExecuted ? "not_executed" : "failed" };
|
|
1190
|
+
state = "failed";
|
|
1191
|
+
}
|
|
1192
|
+
entry = await store.finish(entry.key, state, result);
|
|
1193
|
+
}
|
|
1194
|
+
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
1195
|
+
this.context?.logger.info("AUN Ability 逐次确认完成", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
1196
|
+
return { ok: true, state: entry.state, result: entry.result, delivered };
|
|
1197
|
+
}
|
|
1198
|
+
abilityApprovalViewKey(aid, ownerAid, threadId) {
|
|
1199
|
+
return JSON.stringify([aid, ownerAid, threadId ?? ""]);
|
|
1200
|
+
}
|
|
1201
|
+
async sendAbilityControlText(aid, ownerAid, text, threadId) {
|
|
1202
|
+
const chunks = [];
|
|
1203
|
+
const characters = Array.from(text);
|
|
1204
|
+
for (let offset = 0; offset < characters.length; offset += 4000)
|
|
1205
|
+
chunks.push(characters.slice(offset, offset + 4000).join(""));
|
|
1206
|
+
for (const [index, part] of chunks.entries()) {
|
|
1207
|
+
const message = chunks.length > 1 ? `Ability 管理消息(第 ${index + 1}/${chunks.length} 段,收全后再确认)\n${part}` : part;
|
|
1208
|
+
const sent = await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text: message, ...(threadId ? { thread_id: threadId } : {}) } });
|
|
1209
|
+
if (typeof sent?.status !== "string" || !successfulSendStatuses.has(sent.status))
|
|
1210
|
+
throw new Error(`AUN 管理回复投递状态未确认:${String(sent?.status ?? "未返回状态")}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
abilityEntrySummary(entry) {
|
|
1214
|
+
return [
|
|
1215
|
+
`操作键:${entry.key}`,
|
|
1216
|
+
`确认指纹:${entry.confirmationFingerprint}`,
|
|
1217
|
+
`调用方:${entry.authenticatedCaller}`,
|
|
1218
|
+
`Provider AID:${entry.providerAid}`,
|
|
1219
|
+
`能力路径:${entry.path}`,
|
|
1220
|
+
`definition_id:${entry.definitionId}`,
|
|
1221
|
+
`原始参数:${JSON.stringify(entry.params)}`,
|
|
1222
|
+
`影响:${entry.effects.join("、")}`,
|
|
1223
|
+
`执行目标:${String(entry.executionTarget.pluginId)}(${String(entry.executionTarget.pluginVersion)})`,
|
|
1224
|
+
`operation_id:${entry.operationId}`,
|
|
1225
|
+
`审批截止:${new Date(entry.confirmationExpiresAt).toLocaleString("zh-CN")}`,
|
|
1226
|
+
`最晚启动:${new Date(entry.expiresAt).toLocaleString("zh-CN")}`,
|
|
1227
|
+
`确认:/ability approve ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1228
|
+
`拒绝:/ability reject ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1229
|
+
].join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
async showAbilityPending(aid, ownerAid, threadId) {
|
|
1232
|
+
const store = this.abilityOperations;
|
|
1233
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1234
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1235
|
+
const entries = (await store.pending()).filter(entry => entry.providerAid === aid);
|
|
1236
|
+
if (!entries.length) {
|
|
1237
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1238
|
+
await this.sendAbilityControlText(aid, ownerAid, "当前没有待确认的 Ability 调用。", threadId);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const viewKey = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1242
|
+
const view = { explicit: true, entries: undefined };
|
|
1243
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
1244
|
+
const text = `待确认 Ability 调用清单(${entries.length} 项,仅包含本条消息展示的固定记录)\n\n${entries.map((entry, index) => `第 ${index + 1} 项\n${this.abilityEntrySummary(entry)}`).join("\n\n---\n\n")}\n\n回复“确认”或“拒绝”按本清单逐项处理;清单之后的新请求不会加入。`;
|
|
1245
|
+
await this.sendAbilityControlText(aid, ownerAid, text, threadId);
|
|
1246
|
+
if (this.abilityApprovalViews.get(viewKey) === view && this.abilityOperations === store && this.abilityOwners[aid] === ownerAid && this.acceptingMessages)
|
|
1247
|
+
view.entries = entries.map(({ key, confirmationFingerprint }) => ({ key, confirmationFingerprint }));
|
|
1248
|
+
}
|
|
1249
|
+
async processAbilityControl(payload, aid) {
|
|
1250
|
+
const raw = payload && typeof payload === "object" ? payload : undefined;
|
|
1251
|
+
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1252
|
+
const text = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1253
|
+
const isControlText = text === "确认" || text === "拒绝" || /^ *\/ability(?:\s|$)/.test(text);
|
|
1254
|
+
if (!isControlText)
|
|
1255
|
+
return false;
|
|
1256
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1257
|
+
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1258
|
+
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1259
|
+
if (ownerAid && sender !== ownerAid)
|
|
1260
|
+
return text === "确认" || text === "拒绝" ? false : true;
|
|
1261
|
+
if (!ownerAid && (text === "确认" || text === "拒绝"))
|
|
1262
|
+
return false;
|
|
1263
|
+
let authenticated = false;
|
|
1264
|
+
const context = this.context;
|
|
1265
|
+
const store = this.abilityOperations;
|
|
1266
|
+
const revision = this.abilityRevisions.get(aid) ?? 0;
|
|
1267
|
+
const authorized = () => this.context === context && this.abilityOperations === store && this.acceptingMessages && !this.revokingAbilityAids.has(aid) && this.clients.has(aid) && Boolean(this.bindings[aid]) && (this.abilityRevisions.get(aid) ?? 0) === revision && this.abilityOwners[aid] === ownerAid && this.isAbilityOwnerVerified(aid, ownerAid);
|
|
1268
|
+
try {
|
|
1269
|
+
const authPayload = typeof raw?.payload === "string" ? "/ability help" : { ...body, text: "/ability help" };
|
|
1270
|
+
(0, ability_control_1.parseAbilityControlMessage)({ ...raw, payload: authPayload }, aid, ownerAid);
|
|
1271
|
+
authenticated = true;
|
|
1272
|
+
const message = (0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid);
|
|
1273
|
+
if (!message)
|
|
1274
|
+
return false;
|
|
1275
|
+
if (message.messageId && !this.seen.add(`ability-control:${aid}:${message.messageId}`))
|
|
1276
|
+
return true;
|
|
1277
|
+
if (!this.isAbilityOwnerVerified(aid, ownerAid))
|
|
1278
|
+
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1279
|
+
const { command, peerId, threadId } = message;
|
|
1280
|
+
if (command.action === "confirm-view" || command.action === "reject-view") {
|
|
1281
|
+
const key = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1282
|
+
const view = this.abilityApprovalViews.get(key);
|
|
1283
|
+
this.abilityApprovalViews.delete(key);
|
|
1284
|
+
if (!view?.entries?.length) {
|
|
1285
|
+
await this.sendAbilityControlText(aid, peerId, "当前话题没有已展示的待确认清单。请先发送 /ability pending,核对清单后再回复确认或拒绝。", threadId);
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
const approved = command.action === "confirm-view";
|
|
1289
|
+
const results = [];
|
|
1290
|
+
for (const entry of view.entries) {
|
|
1291
|
+
try {
|
|
1292
|
+
const result = await this.decideAbilityOperation(entry.key, entry.confirmationFingerprint, approved, authorized, aid);
|
|
1293
|
+
results.push(`${entry.key.slice(0, 12)}:${String(result.state)}`);
|
|
1083
1294
|
}
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
const confirmed = entry;
|
|
1087
|
-
const beforeStart = () => {
|
|
1088
|
-
const currentSession = context.managementSession?.(request);
|
|
1089
|
-
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !context.managementActor?.(request) || !currentSession || currentSession.id !== session.id || !this.ownerProof.verified(session, confirmed.providerAid, ownerAid) || !this.ownerProof.verified(currentSession, confirmed.providerAid, ownerAid))
|
|
1090
|
-
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
1091
|
-
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
1092
|
-
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1093
|
-
if ((this.abilityRevisions.get(confirmed.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(confirmed.providerAid) || !this.clients.has(confirmed.providerAid) || this.bindings[confirmed.providerAid] !== pluginId || this.abilityOwners[confirmed.providerAid] !== confirmed.executionTarget.ownerAid || (context.pluginVersion?.(pluginId) ?? "") !== confirmed.executionTarget.pluginVersion || context.pluginStatus(pluginId) !== "ready" || !this.abilitySelections[confirmed.providerAid]?.includes(confirmed.path) || this.abilityLedgers[confirmed.providerAid]?.[confirmed.path]?.definition_id !== confirmed.definitionId || (rules.length && !rules.includes("*") && !rules.includes(confirmed.authenticatedCaller)))
|
|
1094
|
-
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
1095
|
-
handlerAuthorized = true;
|
|
1096
|
-
};
|
|
1097
|
-
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
1098
|
-
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
1099
|
-
state = "succeeded";
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
results.push(`${entry.key.slice(0, 12)}:未处理(${safeError(error)})`);
|
|
1100
1297
|
}
|
|
1101
1298
|
}
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1299
|
+
await this.sendAbilityControlText(aid, peerId, `${approved ? "确认" : "拒绝"}处理完成:\n${results.join("\n")}`, threadId);
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
if (command.action === "help") {
|
|
1303
|
+
await this.sendAbilityControlText(aid, peerId, "AUN Ability 管理命令:\n/ability list:查看当前 Provider 的能力\n/ability publish <路径...>:按完整路径列表发布\n/ability revoke:撤销本 Provider 的能力索引\n/ability pending:展示固定待确认清单\n/ability approve <操作键> <确认指纹>:批准单项\n/ability reject <操作键> <确认指纹>:拒绝单项\n也可在清单投递成功后回复“确认”或“拒绝”。", threadId);
|
|
1304
|
+
}
|
|
1305
|
+
else if (command.action === "list") {
|
|
1306
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1307
|
+
const selected = new Set(this.abilitySelections[aid] ?? []);
|
|
1308
|
+
const lines = definitions.map(item => `${selected.has(item.path) ? "已发布" : "未发布"} ${item.path} | ${item.name} | ${(0, ability_publication_1.abilityDefinitionId)({ ...item })}`);
|
|
1309
|
+
await this.sendAbilityControlText(aid, peerId, lines.length ? lines.join("\n") : "当前关联 Agent 没有可发布的 Ability。", threadId);
|
|
1310
|
+
}
|
|
1311
|
+
else if (command.action === "publish") {
|
|
1312
|
+
await this.setPublishedAbilities(aid, command.paths, authorized);
|
|
1313
|
+
await this.sendAbilityControlText(aid, peerId, `已发布 ${command.paths.length} 项 Ability:\n${command.paths.join("\n")}`, threadId);
|
|
1314
|
+
}
|
|
1315
|
+
else if (command.action === "revoke") {
|
|
1316
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1317
|
+
await this.setPublishedAbilities(aid, [], authorized);
|
|
1318
|
+
await this.sendAbilityControlText(aid, peerId, "已撤销本 Provider 的 Ability 索引,并使现有待确认操作失效。", threadId);
|
|
1319
|
+
}
|
|
1320
|
+
else if (command.action === "pending")
|
|
1321
|
+
await this.showAbilityPending(aid, peerId, threadId);
|
|
1322
|
+
else if (command.action === "approve" || command.action === "reject") {
|
|
1323
|
+
const result = await this.decideAbilityOperation(command.key, command.confirmationFingerprint, command.action === "approve", authorized, aid);
|
|
1324
|
+
await this.sendAbilityControlText(aid, peerId, `Ability 操作状态:${String(result.state)};结果已${result.delivered ? "回传 Peer" : "保存,等待重投"}。`, threadId);
|
|
1110
1325
|
}
|
|
1111
|
-
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
1112
|
-
this.context?.logger.info("AUN Ability 逐次确认完成,需本机已认证管理会话", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
1113
|
-
this.json(response, 200, { ok: true, state: entry.state, result: entry.result, delivered });
|
|
1114
1326
|
}
|
|
1115
1327
|
catch (error) {
|
|
1116
|
-
|
|
1328
|
+
const replyAid = authenticated ? ownerAid : error.code === "ability_owner_unconfigured" ? sender : undefined;
|
|
1329
|
+
if (replyAid) {
|
|
1330
|
+
try {
|
|
1331
|
+
await this.sendAbilityControlText(aid, replyAid, `Ability 管理操作未完成:${safeError(error)}`, typeof body?.thread_id === "string" ? body.thread_id : undefined);
|
|
1332
|
+
}
|
|
1333
|
+
catch (replyError) {
|
|
1334
|
+
this.context?.logger.warn("AUN Ability 管理错误回复失败", { aid, ownerAid, error: safeError(replyError) });
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1117
1337
|
}
|
|
1118
|
-
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1119
1340
|
async revokePublishedAbilities(aid) {
|
|
1120
1341
|
this.revokingAbilityAids.add(aid);
|
|
1121
1342
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1343
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1344
|
+
if (JSON.parse(key)[0] === aid)
|
|
1345
|
+
this.abilityApprovalViews.delete(key);
|
|
1122
1346
|
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1123
1347
|
await this.invalidateAbilityOperations(aid);
|
|
1124
1348
|
this.abilitySelections[aid] = [];
|
|
@@ -1713,18 +1937,74 @@ class AunPlugin {
|
|
|
1713
1937
|
return;
|
|
1714
1938
|
}
|
|
1715
1939
|
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1940
|
+
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1941
|
+
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1942
|
+
const inboundText = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1943
|
+
const inboundThreadId = typeof body?.thread_id === "string" ? body.thread_id : undefined;
|
|
1944
|
+
if (inboundText === "确认" && this.ownerProof.hasPendingConfirmation(aid)) {
|
|
1945
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1946
|
+
let authenticated = false;
|
|
1947
|
+
try {
|
|
1948
|
+
authenticated = Boolean((0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid));
|
|
1949
|
+
}
|
|
1950
|
+
catch { /* 无效信封不能验证 Owner 身份。 */ }
|
|
1951
|
+
if (!authenticated || !this.ownerProof.acceptConfirmation(aid, sender, inboundText, true)) {
|
|
1952
|
+
this.context?.logger.warn("AUN Owner 确认未通过验证", { traceId: inboundId, aid, sender: sender ?? "", stage: "Owner 确认校验", reason: authenticated ? "没有有效的待确认通知" : "需要配置 Owner AID 通过加密消息直接回复确认" });
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
if (typeof raw?.message_id === "string")
|
|
1956
|
+
this.seen.add(`ability-control:${aid}:${raw.message_id}`);
|
|
1957
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1958
|
+
if (JSON.parse(key)[0] === aid)
|
|
1959
|
+
this.abilityApprovalViews.delete(key);
|
|
1960
|
+
try {
|
|
1961
|
+
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1962
|
+
}
|
|
1963
|
+
catch (error) {
|
|
1964
|
+
this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, ownerAid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1965
|
+
try {
|
|
1966
|
+
await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。", inboundThreadId, true);
|
|
1967
|
+
}
|
|
1968
|
+
catch (replyError) {
|
|
1969
|
+
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: false, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
1970
|
+
}
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
try {
|
|
1974
|
+
await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`, inboundThreadId, true);
|
|
1975
|
+
}
|
|
1976
|
+
catch (error) {
|
|
1977
|
+
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: true, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1978
|
+
}
|
|
1979
|
+
this.context?.logger.info("AUN Owner 确认验证成功并已持久保存", { traceId: inboundId, aid, ownerAid });
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1716
1982
|
const ownerCommand = typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text) ? body.text.match(/^\/ability-owner ([0-9a-f]{64})$/) : undefined;
|
|
1717
1983
|
if (typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text)) {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1984
|
+
let verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
|
|
1985
|
+
if (verified) {
|
|
1986
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1987
|
+
if (JSON.parse(key)[0] === aid)
|
|
1988
|
+
this.abilityApprovalViews.delete(key);
|
|
1989
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1990
|
+
try {
|
|
1991
|
+
if (ownerAid === sender)
|
|
1992
|
+
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1993
|
+
else
|
|
1994
|
+
verified = false;
|
|
1995
|
+
}
|
|
1996
|
+
catch (error) {
|
|
1997
|
+
verified = false;
|
|
1998
|
+
this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1999
|
+
}
|
|
2000
|
+
}
|
|
1721
2001
|
this.context?.logger.info("AUN 宿主委托的 Owner 控制权验证已处理", { aid, verified });
|
|
1722
2002
|
if (sender && raw?.encrypted === true && (envelope?.from === undefined || envelope.from === sender)) {
|
|
1723
2003
|
const text = verified
|
|
1724
|
-
? `Owner 控制权验证成功:${aid}
|
|
2004
|
+
? `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`
|
|
1725
2005
|
: !ownerCommand
|
|
1726
|
-
? "Owner
|
|
1727
|
-
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner
|
|
2006
|
+
? "Owner 控制权验证失败:指令格式不正确。请使用已配置的 Owner AID 发送 /ability help。"
|
|
2007
|
+
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请使用已配置的 Owner AID 发送 /ability help 重新验证。";
|
|
1728
2008
|
try {
|
|
1729
2009
|
await this.sendText(aid, sender, text, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
|
|
1730
2010
|
}
|
|
@@ -1734,6 +2014,8 @@ class AunPlugin {
|
|
|
1734
2014
|
}
|
|
1735
2015
|
return;
|
|
1736
2016
|
}
|
|
2017
|
+
if (await this.processAbilityControl(payload, aid))
|
|
2018
|
+
return;
|
|
1737
2019
|
if ((0, ability_operations_1.isAbilityMessage)(payload)) {
|
|
1738
2020
|
if (this.revokingAbilityAids.has(aid)) {
|
|
1739
2021
|
this.context?.logger.warn("AUN 身份正在撤销能力索引,能力调用已阻止", { aid, traceId: inboundId, stage: "撤销保护" });
|
|
@@ -1764,14 +2046,51 @@ class AunPlugin {
|
|
|
1764
2046
|
}
|
|
1765
2047
|
return;
|
|
1766
2048
|
}
|
|
1767
|
-
const
|
|
2049
|
+
const queuedAt = Date.now();
|
|
2050
|
+
const notices = new Set();
|
|
2051
|
+
let queueTimer;
|
|
2052
|
+
const reportQueue = (started = false) => {
|
|
2053
|
+
if (!started && notices.size)
|
|
2054
|
+
return;
|
|
2055
|
+
const elapsedSeconds = Math.floor((Date.now() - queuedAt) / 1000);
|
|
2056
|
+
const stage = started ? "结束会话排队" : "等待同会话前序任务";
|
|
2057
|
+
const text = started ? `本条消息已结束会话排队,开始处理(排队 ${elapsedSeconds} 秒)。`
|
|
2058
|
+
: elapsedSeconds === 0 ? "消息已排队:当前会话仍有任务未完成,本条将按接收顺序处理。"
|
|
2059
|
+
: `本条消息仍在排队,已等待 ${elapsedSeconds} 秒;尚未转交 Agent,前序任务完成后自动处理。`;
|
|
2060
|
+
const payload = {
|
|
2061
|
+
type: "notice", subtype: started ? "task.dequeued" : "task.queued", severity: "info", text,
|
|
2062
|
+
task_id: message.messageId || inboundId, sent_at: Date.now(),
|
|
2063
|
+
...(message.threadId ? { thread_id: message.threadId } : {}),
|
|
2064
|
+
...(message.messageId ? { ref_message_id: message.messageId } : {}),
|
|
2065
|
+
};
|
|
2066
|
+
this.context?.logger.info("AUN 会话排队状态", { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", agentPlugin, stage, elapsedSeconds });
|
|
2067
|
+
// 通知不占执行队列;notice 不会被对端作为普通任务自动互答。
|
|
2068
|
+
const notice = this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true })
|
|
2069
|
+
.then(() => undefined)
|
|
2070
|
+
.catch(error => this.context?.logger.warn("AUN 排队通知发送失败", { aid, peer: message.peerId, traceId: message.messageId || inboundId, stage, error: safeError((0, agent_1.errorDetails)(error)) }))
|
|
2071
|
+
.finally(() => { notices.delete(notice); });
|
|
2072
|
+
notices.add(notice);
|
|
2073
|
+
};
|
|
2074
|
+
const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => {
|
|
2075
|
+
clearInterval(queueTimer);
|
|
2076
|
+
if (pending)
|
|
2077
|
+
reportQueue(true);
|
|
2078
|
+
return this.processMessage(message, aid, agentPlugin, inboundId);
|
|
2079
|
+
});
|
|
1768
2080
|
this.pendingMessages.set(sourceKey, current);
|
|
2081
|
+
if (pending) {
|
|
2082
|
+
reportQueue();
|
|
2083
|
+
queueTimer = setInterval(reportQueue, 60_000);
|
|
2084
|
+
queueTimer.unref();
|
|
2085
|
+
}
|
|
1769
2086
|
try {
|
|
1770
2087
|
await current;
|
|
1771
2088
|
}
|
|
1772
2089
|
finally {
|
|
2090
|
+
clearInterval(queueTimer);
|
|
1773
2091
|
if (this.pendingMessages.get(sourceKey) === current)
|
|
1774
2092
|
this.pendingMessages.delete(sourceKey);
|
|
2093
|
+
await Promise.allSettled(notices);
|
|
1775
2094
|
}
|
|
1776
2095
|
}
|
|
1777
2096
|
async onFsMessage(raw, message, aid) {
|
|
@@ -1840,6 +2159,7 @@ class AunPlugin {
|
|
|
1840
2159
|
const { payload, callerAid } = call;
|
|
1841
2160
|
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1842
2161
|
let result;
|
|
2162
|
+
let ownerNotice;
|
|
1843
2163
|
if (rules.length && !rules.includes("*") && !rules.includes(callerAid))
|
|
1844
2164
|
result = (0, ability_operations_1.abilityFailureResult)(payload, "permission_denied", "调用来源不在 AUN 允许名单中");
|
|
1845
2165
|
else if (payload.ability_path === "protocol/discover") {
|
|
@@ -1895,6 +2215,8 @@ class AunPlugin {
|
|
|
1895
2215
|
else {
|
|
1896
2216
|
const draft = await store.draft({ providerAid: aid, authenticatedCaller: callerAid, operationId: String(payload.operation_id), callId: String(payload.call_id), path, definitionId: String(payload.definition_id), params, effects: definition.effects, dedupeRetentionSeconds: definition.dedupe_retention_seconds, executionTarget: { pluginId, pluginVersion, ownerAid }, expiresAt, ...(payload.async === undefined ? {} : { async: payload.async }) });
|
|
1897
2217
|
result = this.abilityOperationResponse(draft.entry);
|
|
2218
|
+
if (draft.created)
|
|
2219
|
+
ownerNotice = draft.entry;
|
|
1898
2220
|
}
|
|
1899
2221
|
}
|
|
1900
2222
|
}
|
|
@@ -1907,6 +2229,31 @@ class AunPlugin {
|
|
|
1907
2229
|
}
|
|
1908
2230
|
}
|
|
1909
2231
|
await this.sendAbilityResponse(aid, callerAid, result);
|
|
2232
|
+
if (ownerNotice)
|
|
2233
|
+
await this.notifyAbilityOwner(ownerNotice);
|
|
2234
|
+
}
|
|
2235
|
+
async notifyAbilityOwner(entry) {
|
|
2236
|
+
const ownerAid = String(entry.executionTarget.ownerAid ?? "");
|
|
2237
|
+
const viewKey = this.abilityApprovalViewKey(entry.providerAid, ownerAid);
|
|
2238
|
+
const existingView = this.abilityApprovalViews.get(viewKey);
|
|
2239
|
+
const view = existingView ?? {};
|
|
2240
|
+
if (!existingView)
|
|
2241
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
2242
|
+
const pending = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2243
|
+
if (pending.length !== 1 && !view.explicit)
|
|
2244
|
+
view.entries = undefined;
|
|
2245
|
+
const text = `有新的远程 Ability 调用申请\n${this.abilityEntrySummary(entry)}\n\n审批窗口最长 12 小时,不会延长调用最晚启动时间或协议 TTL。\n只有本话题中唯一、已完整投递的申请可直接回复“确认”或“拒绝”;多项、已处理或不确定时先发送 /ability pending 查看固定清单。`;
|
|
2246
|
+
try {
|
|
2247
|
+
await this.sendAbilityControlText(entry.providerAid, ownerAid, text);
|
|
2248
|
+
const current = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2249
|
+
if (!existingView && this.abilityApprovalViews.get(viewKey) === view)
|
|
2250
|
+
view.entries = current.length === 1 && current[0].key === entry.key && this.abilityOwners[entry.providerAid] === ownerAid && this.acceptingMessages
|
|
2251
|
+
? [{ key: entry.key, confirmationFingerprint: entry.confirmationFingerprint }] : undefined;
|
|
2252
|
+
this.context?.logger.info("AUN Ability Owner 申请通知已投递", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知" });
|
|
2253
|
+
}
|
|
2254
|
+
catch (error) {
|
|
2255
|
+
this.context?.logger.warn("AUN Ability Owner 申请通知发送失败,请用 /ability pending 读取保留的待确认请求", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
2256
|
+
}
|
|
1910
2257
|
}
|
|
1911
2258
|
async abilityInvocationFailure(aid, callerAid, path, definitionId, params) {
|
|
1912
2259
|
if (this.abilityAuditUnavailable)
|
|
@@ -1961,6 +2308,7 @@ class AunPlugin {
|
|
|
1961
2308
|
}
|
|
1962
2309
|
async processMessage(message, aid, agentPlugin, inboundId) {
|
|
1963
2310
|
let deliveryFailed = false;
|
|
2311
|
+
let failureText;
|
|
1964
2312
|
const trace = { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", messageId: message.messageId ?? "", agentPlugin };
|
|
1965
2313
|
this.context?.logger.info("AUN 收到任务", { ...trace, stage: "接收消息", attachments: message.attachments?.length ?? 0 });
|
|
1966
2314
|
const client = this.clients.get(aid)?.client;
|
|
@@ -2008,7 +2356,9 @@ class AunPlugin {
|
|
|
2008
2356
|
}
|
|
2009
2357
|
catch (error) {
|
|
2010
2358
|
deliveryFailed = true;
|
|
2011
|
-
|
|
2359
|
+
const failure = new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
|
|
2360
|
+
failureText = `回复回传失败:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2361
|
+
throw failure;
|
|
2012
2362
|
}
|
|
2013
2363
|
},
|
|
2014
2364
|
replyAttachments: async (items, text, signal) => {
|
|
@@ -2038,17 +2388,18 @@ class AunPlugin {
|
|
|
2038
2388
|
groups.set(kind, group);
|
|
2039
2389
|
}
|
|
2040
2390
|
groupsCount = groups.size;
|
|
2391
|
+
const captionKind = (groupsCount === 1 ? groups.keys().next().value : undefined) ?? "attachment";
|
|
2392
|
+
const payloadText = withAttachmentDownloadLinks(text || (captionKind === "image" ? "图片已生成。" : captionKind === "video" ? "视频已生成。" : captionKind === "file" ? "文件已生成。" : "附件已生成。"), captionKind, refs);
|
|
2393
|
+
fallbackText = payloadText;
|
|
2394
|
+
fallbackLinks = attachmentDownloadLinks(refs);
|
|
2041
2395
|
for (const [kind, group] of groups) {
|
|
2042
2396
|
signal?.throwIfAborted();
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText, format: "markdown" }), attachments: group };
|
|
2397
|
+
// AUN 网页可能隐藏附件卡片的正文;所有附件发完后只另发一次完整正文。
|
|
2398
|
+
const payload = { type: kind, attachments: group };
|
|
2046
2399
|
if (message.threadId)
|
|
2047
2400
|
payload.thread_id = message.threadId;
|
|
2048
2401
|
if (message.messageId)
|
|
2049
2402
|
payload.ref_message_id = message.messageId;
|
|
2050
|
-
fallbackText = payloadText;
|
|
2051
|
-
fallbackLinks = attachmentDownloadLinks(group);
|
|
2052
2403
|
sending = true;
|
|
2053
2404
|
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
2054
2405
|
const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
|
|
@@ -2057,17 +2408,17 @@ class AunPlugin {
|
|
|
2057
2408
|
this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
|
|
2058
2409
|
sending = false;
|
|
2059
2410
|
sent++;
|
|
2060
|
-
if (
|
|
2411
|
+
if (sent === groupsCount && payloadText) {
|
|
2061
2412
|
try {
|
|
2062
2413
|
signal?.throwIfAborted();
|
|
2063
2414
|
const { attachments: _attachments, ...caption } = payload;
|
|
2064
2415
|
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText, format: "markdown" }, encrypt: true });
|
|
2065
2416
|
if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
|
|
2066
|
-
throw new Error(`AUN
|
|
2067
|
-
this.context?.logger.info("AUN
|
|
2417
|
+
throw new Error(`AUN 附件正文发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
|
|
2418
|
+
this.context?.logger.info("AUN 附件正文已发送", { ...trace, stage: "发送附件正文" });
|
|
2068
2419
|
}
|
|
2069
2420
|
catch (error) {
|
|
2070
|
-
throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "
|
|
2421
|
+
throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "附件已发送,发送 AUN 附件正文(结果未确认,不自动重发)", error);
|
|
2071
2422
|
}
|
|
2072
2423
|
}
|
|
2073
2424
|
}
|
|
@@ -2077,6 +2428,7 @@ class AunPlugin {
|
|
|
2077
2428
|
deliveryFailed = true;
|
|
2078
2429
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
2079
2430
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
2431
|
+
failureText = `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2080
2432
|
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
2081
2433
|
try {
|
|
2082
2434
|
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true, "markdown");
|
|
@@ -2087,12 +2439,13 @@ class AunPlugin {
|
|
|
2087
2439
|
},
|
|
2088
2440
|
};
|
|
2089
2441
|
const taskId = message.messageId || inboundId;
|
|
2090
|
-
const notifyStatus = (status) => {
|
|
2442
|
+
const notifyStatus = (status, text) => {
|
|
2091
2443
|
if (!client?.notify)
|
|
2092
2444
|
return;
|
|
2093
2445
|
const params = {
|
|
2094
2446
|
type: "task.status", status, task_id: taskId, session_id: taskId,
|
|
2095
2447
|
severity: status === "error" ? "error" : "info", initiator: message.peerId,
|
|
2448
|
+
...(text ? { text } : {}),
|
|
2096
2449
|
...(message.threadId ? { thread_id: message.threadId } : {}),
|
|
2097
2450
|
...(message.messageId ? { ref_message_id: message.messageId } : {}),
|
|
2098
2451
|
};
|
|
@@ -2117,10 +2470,11 @@ class AunPlugin {
|
|
|
2117
2470
|
catch (error) {
|
|
2118
2471
|
failed = true;
|
|
2119
2472
|
this.context?.logger.warn("AUN 消息处理失败", { ...trace, stage: deliveryFailed ? "回传回复" : "执行 Agent 任务", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
2473
|
+
const action = deliveryFailed ? "回复回传失败" : attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2474
|
+
failureText ??= `${action}:${safeError((0, agent_1.errorDetails)(error))}`;
|
|
2120
2475
|
if (!deliveryFailed) {
|
|
2121
|
-
const action = attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2122
2476
|
try {
|
|
2123
|
-
await channelMessage.reply(
|
|
2477
|
+
await channelMessage.reply(failureText);
|
|
2124
2478
|
}
|
|
2125
2479
|
catch (replyError) {
|
|
2126
2480
|
this.context?.logger.warn("AUN 错误回复失败", { ...trace, stage: "发送最终错误消息", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
@@ -2129,7 +2483,7 @@ class AunPlugin {
|
|
|
2129
2483
|
}
|
|
2130
2484
|
finally {
|
|
2131
2485
|
clearInterval(progressTimer);
|
|
2132
|
-
notifyStatus(failed || deliveryFailed ? "error" : "completed");
|
|
2486
|
+
notifyStatus(failed || deliveryFailed ? "error" : "completed", failureText);
|
|
2133
2487
|
}
|
|
2134
2488
|
}
|
|
2135
2489
|
async sendText(aid, peerId, text, threadId, encryptedOnly = false, format) {
|