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.
@@ -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.46", type: "channel", displayName: "AUN 通道",
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.onMessage(payload, aid));
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.onMessage(payload, aid))
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.ownerProof.verified(session, aid, ownerAid));
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
- if (paths.length) {
958
- const session = this.context?.managementSession?.(request);
959
- const owner = this.abilityOwners[aid];
960
- if (!session || !owner || !this.ownerProof.verified(session, aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable)
961
- return this.json(response, 409, { error: "逐次 Owner 确认需可用操作账本、本机已认证管理会话,并先验证该 Provider 的 Owner AID" });
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: `请在本次管理会话发起后回复以下指令,证明你控制 Owner AID。此操作只验证身份,不批准任何能力调用:\n/ability-owner ${challenge.nonce}` } });
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 original = await store.get(body.key);
1054
- if (!original)
1055
- return this.json(response, 404, { error: "能力操作不存在" });
1056
- const ownerAid = this.abilityOwners[original.providerAid];
1057
- if (!ownerAid || !this.ownerProof.verified(session, original.providerAid, ownerAid))
1058
- return this.json(response, 403, { error: "需本机已认证管理会话,并完成该 Provider 的 Owner AID 验证" });
1059
- if (original.confirmationFingerprint !== body.confirmationFingerprint)
1060
- return this.json(response, 409, { error: "确认指纹与待确认请求不一致" });
1061
- const revision = this.abilityRevisions.get(original.providerAid) ?? 0;
1062
- if (body.approved && original.state === "pending_confirmation") {
1063
- const failure = await this.abilityInvocationFailure(original.providerAid, original.authenticatedCaller, original.path, original.definitionId, original.params);
1064
- const pluginId = this.bindings[original.providerAid];
1065
- 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) ?? ""))
1066
- return this.json(response, 409, { error: failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用" });
1067
- }
1068
- if (!this.acceptingMessages || !this.ownerProof.verified(session, original.providerAid, ownerAid))
1069
- return this.json(response, 403, { error: "通道或 Owner 管理会话已失效" });
1070
- const confirmation = body.approved ? await store.approve(original.key, body.confirmationFingerprint) : await store.reject(original.key, body.confirmationFingerprint);
1071
- let entry = confirmation.entry;
1072
- if (confirmation.started) {
1073
- let result;
1074
- let state;
1075
- let handlerAuthorized = false;
1076
- try {
1077
- const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
1078
- const expired = Date.now() >= entry.expiresAt;
1079
- const invalidated = (this.abilityRevisions.get(entry.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(entry.providerAid);
1080
- if (failure || expired || invalidated || !this.ownerProof.verified(session, 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) {
1081
- result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, expired ? "message_expired" : failure?.code ?? (invalidated ? "provider_invalidated" : "permission_denied"), expired ? "调用已超过最晚启动时间" : failure?.message ?? "Owner 管理会话或执行目标已改变,本次能力没有执行"), execution_state: "not_executed" };
1082
- state = "failed";
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
- else {
1085
- const context = this.context;
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
- catch (error) {
1103
- this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
1104
- const code = error.code;
1105
- const notExecuted = !handlerAuthorized && ["message_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
1106
- 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" ? "调用已超过最晚启动时间" : "管理会话或 Provider 状态已改变,本次能力没有执行") : "能力执行失败,请检查 Provider 运行日志;不会自动重试"), execution_state: notExecuted ? "not_executed" : "failed" };
1107
- state = "failed";
1108
- }
1109
- entry = await store.finish(entry.key, state, result);
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
- this.json(response, 409, { error: safeError(error) });
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
- const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
1719
- const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
1720
- const verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
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} 的当前管理会话已通过验证。此操作不会批准任何能力调用,请回到 ABWeb 继续操作。`
2004
+ ? `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`
1725
2005
  : !ownerCommand
1726
- ? "Owner 控制权验证失败:指令格式不正确,请复制最新验证消息中的完整 /ability-owner 指令。"
1727
- : "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请在 ABWeb 重新发起验证,并使用指定 Owner AID 回复。";
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 current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => this.processMessage(message, aid, agentPlugin, inboundId));
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
- throw new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
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
- const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
2044
- // AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
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 (kind === "image" && payloadText) {
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 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
2067
- this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
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", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
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(`${action}:${safeError((0, agent_1.errorDetails)(error))}`);
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) {