agent-bridge-aun 1.0.45-sdk0531.5 → 1.0.48

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 CHANGED
@@ -69,3 +69,6 @@ ABWeb 的 AUN 设置只展示已连接 AID,并按来源分组。成功注册
69
69
  AUN 独立包支持核心后台每五分钟检测更新;可信提供者在自动安装策略下会下载新副本并重载通道。
70
70
 
71
71
  显式关联已有 EvolCore Agent 的 AID 时,先执行 `ec agent disable <aid>`,再配置 `ecagent` 路由并重载。`ec` 不存在或命令失败时,只将所选 AID 的 JSON 配置更新为 `enabled=false`、`active_baseagent=ecagent` 和对应路由;其它 AID 保持原值。仍有 EvolCore 运行实例时,必须确认重载成功才完成关联,避免原 Claude 与桌面 Agent 同时回复。纯密钥库身份不调用 `ec`,解绑不自动启用原 Agent。
72
+ ## Ability Owner 确认
73
+
74
+ ABWeb 为 Provider AID 发起能力管理授权时,AUN 会向配置的 Owner AID 发送加密通知。Owner 在同一 AUN 会话中直接回复“确认”即可,不需要复制命令或令牌。确认按 Provider AID 保存,AUN 插件重载或升级后继续有效;只有 Provider 的 Owner 配置改变时才会撤销并要求重新确认。旧版 `/ability-owner <nonce>` 回复仍兼容。
@@ -127,6 +127,18 @@
127
127
  "type": "string",
128
128
  "pattern": "^[a-z0-9][a-z0-9_-]{2,63}\\.[a-z0-9-]+(?:\\.[a-z0-9-]+)+$"
129
129
  }
130
+ },
131
+ "ability_owner_verifications": {
132
+ "type": "object",
133
+ "default": {},
134
+ "description": "由 AUN 插件维护的 Owner 确认结果;仅当值与当前 ability_owners 相同时有效。",
135
+ "propertyNames": {
136
+ "pattern": "^[a-z0-9][a-z0-9_-]{2,63}\\.[a-z0-9-]+(?:\\.[a-z0-9-]+)+$"
137
+ },
138
+ "additionalProperties": {
139
+ "type": "string",
140
+ "pattern": "^[a-z0-9][a-z0-9_-]{2,63}\\.[a-z0-9-]+(?:\\.[a-z0-9-]+)+$"
141
+ }
130
142
  }
131
143
  },
132
144
  "additionalProperties": false
@@ -5,6 +5,7 @@ exports.abilityOperationKey = abilityOperationKey;
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const promises_1 = require("node:fs/promises");
7
7
  const node_path_1 = require("node:path");
8
+ const confirmationLifetimeMs = 12 * 60 * 60 * 1000;
8
9
  function failure(code, message) {
9
10
  return Object.assign(new Error(`${code}: ${message}`), { code });
10
11
  }
@@ -35,7 +36,10 @@ function operationFingerprint(input) {
35
36
  return digest({ providerAid: input.providerAid, authenticatedCaller: input.authenticatedCaller, operationId: input.operationId,
36
37
  path: input.path, definitionId: input.definitionId, params: input.params, effects: input.effects, executionTarget: input.executionTarget, async: input.async ?? false });
37
38
  }
38
- function confirmationFingerprint(input, fingerprint) {
39
+ function confirmationFingerprint(input, fingerprint, confirmationExpiresAt) {
40
+ return digest({ key: abilityOperationKey(input), fingerprint, expiresAt: input.expiresAt, confirmationExpiresAt });
41
+ }
42
+ function legacyConfirmationFingerprint(input, fingerprint) {
39
43
  return digest({ key: abilityOperationKey(input), fingerprint, expiresAt: input.expiresAt });
40
44
  }
41
45
  function validateInput(input) {
@@ -62,11 +66,14 @@ function response(entry, code, message, executionState = "not_executed") {
62
66
  function validEntry(value) {
63
67
  if (!value || typeof value !== "object" || Array.isArray(value))
64
68
  return false;
65
- const entry = value;
69
+ const raw = value;
70
+ const legacy = raw.confirmationExpiresAt === undefined;
71
+ const entry = legacy ? { ...raw, confirmationExpiresAt: raw.expiresAt } : raw;
66
72
  try {
67
73
  validateInput(entry);
68
74
  if (entry.key !== abilityOperationKey(entry) || entry.fingerprint !== operationFingerprint(entry)
69
- || entry.confirmationFingerprint !== confirmationFingerprint(entry, entry.fingerprint)
75
+ || !Number.isSafeInteger(entry.confirmationExpiresAt) || entry.confirmationExpiresAt <= 0
76
+ || entry.confirmationFingerprint !== (legacy ? legacyConfirmationFingerprint(entry, entry.fingerprint) : confirmationFingerprint(entry, entry.fingerprint, entry.confirmationExpiresAt))
70
77
  || !["pending_confirmation", "executing", "succeeded", "failed", "unknown"].includes(entry.state)
71
78
  || ![entry.createdAt, entry.updatedAt, entry.retainUntil].every(Number.isSafeInteger)
72
79
  || entry.createdAt < 0 || entry.updatedAt < entry.createdAt || entry.retainUntil < entry.createdAt + entry.dedupeRetentionSeconds * 1000)
@@ -113,7 +120,10 @@ class AbilityOperationStore {
113
120
  if (!snapshot || snapshot.version !== 1 || !Array.isArray(snapshot.entries) || !snapshot.entries.every(validEntry))
114
121
  throw failure("operation_store_corrupt", "操作账本格式无效,已停止新的执行");
115
122
  let migrated = false;
116
- for (const entry of snapshot.entries) {
123
+ for (const saved of snapshot.entries) {
124
+ const legacy = saved.confirmationExpiresAt === undefined;
125
+ const entry = legacy ? { ...saved, confirmationExpiresAt: saved.expiresAt,
126
+ confirmationFingerprint: confirmationFingerprint(saved, saved.fingerprint, saved.expiresAt) } : saved;
117
127
  if (store.entries.has(entry.key))
118
128
  throw failure("operation_store_corrupt", "操作账本含重复幂等键");
119
129
  const legacyExpired = entry.result?.error?.code === "expired";
@@ -125,6 +135,7 @@ class AbilityOperationStore {
125
135
  }
126
136
  else
127
137
  store.entries.set(entry.key, entry);
138
+ migrated ||= legacy;
128
139
  }
129
140
  if (migrated)
130
141
  await store.persist(new Map(store.entries));
@@ -161,11 +172,11 @@ class AbilityOperationStore {
161
172
  const next = new Map(entries);
162
173
  let changed = false;
163
174
  for (const entry of entries.values()) {
164
- if (entry.state !== "pending_confirmation" || now < entry.expiresAt)
175
+ if (entry.state !== "pending_confirmation" || now < entry.confirmationExpiresAt)
165
176
  continue;
166
177
  next.set(entry.key, { ...entry, state: "failed", updatedAt: Math.max(entry.updatedAt, now),
167
178
  retainUntil: Math.max(entry.retainUntil, now + entry.dedupeRetentionSeconds * 1000),
168
- result: response(entry, "message_expired", "调用消息已过期,不会执行") });
179
+ result: response(entry, "confirmation_expired", "Owner 确认申请已过期,不会执行") });
169
180
  changed = true;
170
181
  }
171
182
  return { entries: next, changed };
@@ -188,9 +199,10 @@ class AbilityOperationStore {
188
199
  return { created: false, entry: structuredClone(existing) };
189
200
  }
190
201
  const retainUntil = now + copied.dedupeRetentionSeconds * 1000;
191
- if (!Number.isSafeInteger(retainUntil))
202
+ const confirmationExpiresAt = now + confirmationLifetimeMs;
203
+ if (!Number.isSafeInteger(retainUntil) || !Number.isSafeInteger(confirmationExpiresAt))
192
204
  throw failure("params_invalid", "操作保留期无效");
193
- const entry = { ...copied, key, fingerprint, confirmationFingerprint: confirmationFingerprint(copied, fingerprint),
205
+ const entry = { ...copied, key, fingerprint, confirmationExpiresAt, confirmationFingerprint: confirmationFingerprint(copied, fingerprint, confirmationExpiresAt),
194
206
  state: "pending_confirmation", createdAt: now, updatedAt: now, retainUntil };
195
207
  next.set(key, entry);
196
208
  await this.persist(next);
@@ -216,11 +228,12 @@ class AbilityOperationStore {
216
228
  if (existing.state !== "pending_confirmation")
217
229
  return { started: false, entry: structuredClone(existing) };
218
230
  const now = this.now();
231
+ const confirmationExpired = now >= existing.confirmationExpiresAt;
219
232
  const messageExpired = now >= existing.expiresAt;
220
- const started = approve && !messageExpired;
233
+ const started = approve && !confirmationExpired && !messageExpired;
221
234
  const entry = { ...existing, state: started ? "executing" : "failed", updatedAt: Math.max(existing.updatedAt, now),
222
235
  retainUntil: Math.max(existing.retainUntil, now + existing.dedupeRetentionSeconds * 1000),
223
- ...(!started ? { result: messageExpired ? response(existing, "message_expired", "调用消息已过期,不会执行") : response(existing, "user_denied", "Owner 已拒绝本次调用") } : {}) };
236
+ ...(!started ? { result: confirmationExpired ? response(existing, "confirmation_expired", "Owner 确认申请已过期,不会执行") : approve && messageExpired ? response(existing, "message_expired", "调用已超过最晚启动时间") : response(existing, "user_denied", "Owner 已拒绝本次调用") } : {}) };
224
237
  const next = new Map(this.entries);
225
238
  next.set(key, entry);
226
239
  await this.persist(next);
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OwnerProof = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const lifetime = 10 * 60_000;
6
+ const confirmationLifetime = 12 * 60 * 60_000;
6
7
  const limit = 1024;
7
8
  const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
8
9
  function key(session, providerAid, ownerAid) {
@@ -32,7 +33,7 @@ class OwnerProof {
32
33
  if (!this.entries.has(recordKey) && this.entries.size >= limit)
33
34
  throw new Error("Owner 证明记录已达上限,请稍后重试");
34
35
  const nonce = (0, node_crypto_1.randomBytes)(32).toString("hex");
35
- const expiresAt = Math.min(session.expiresAt, now + lifetime);
36
+ const expiresAt = Math.min(session.expiresAt, now + confirmationLifetime);
36
37
  this.entries.set(recordKey, { providerAid, ownerAid, sessionExpiresAt: session.expiresAt, expiresAt, nonceHash: (0, node_crypto_1.createHash)("sha256").update(nonce).digest("hex") });
37
38
  return { nonce, expiresAt };
38
39
  }
@@ -51,6 +52,27 @@ class OwnerProof {
51
52
  }
52
53
  return false;
53
54
  }
55
+ acceptConfirmation(providerAid, authenticatedSenderAid, text, encrypted) {
56
+ const now = this.now();
57
+ this.prune(now);
58
+ if (encrypted !== true || !authenticatedSenderAid || text !== "确认")
59
+ return false;
60
+ for (const entry of this.entries.values()) {
61
+ if (entry.providerAid !== providerAid || entry.ownerAid !== authenticatedSenderAid || entry.nonceHash === undefined)
62
+ continue;
63
+ this.clear(providerAid);
64
+ return true;
65
+ }
66
+ return false;
67
+ }
68
+ hasPendingConfirmation(providerAid) {
69
+ const now = this.now();
70
+ this.prune(now);
71
+ for (const entry of this.entries.values())
72
+ if (entry.providerAid === providerAid && entry.nonceHash !== undefined)
73
+ return true;
74
+ return false;
75
+ }
54
76
  verified(session, providerAid, ownerAid) {
55
77
  const now = this.now();
56
78
  this.prune(now);
@@ -68,15 +68,14 @@ async function configureAidForPlugin(pluginId, aid, port, run = runEc, home = pr
68
68
  throw new Error("插件 ID 格式无效");
69
69
  if (!Number.isInteger(port) || port < 1 || port > 65535)
70
70
  throw new Error("后台端口无效");
71
- const path = pluginId === "dsh" ? "/dsh/v1" : `/api/agents/${pluginId}/v1`;
72
- const ecagent = { baseUrl: `http://127.0.0.1:${port}${path}`, apiKey: "mab-local", model: pluginId };
71
+ const baseagent = { provider: "provider-1", model: pluginId };
73
72
  try {
74
73
  // MAB 直接接收 AUN 消息,先停用该 AID 的 EvolCore Agent,避免双重回复。
75
74
  const disabled = await run(["agent", "disable", aid, "--format", "json"], home);
76
75
  if (!disabled?.ok)
77
76
  throw new Error(`停用原 Agent 失败:${disabled?.code ?? ""} ${disabled?.error ?? "未知错误"}`);
78
77
  for (const [key, value] of [
79
- ...Object.entries(ecagent).map(([key, value]) => [`baseagents.ecagent.${key}`, value]),
78
+ ...Object.entries(baseagent).map(([key, value]) => [`baseagents.ecagent.${key}`, value]),
80
79
  ["active_baseagent", "ecagent"],
81
80
  ]) {
82
81
  const result = await run(["agent", "set", aid, key, value, "--format", "json"], home);
@@ -95,7 +94,10 @@ async function configureAidForPlugin(pluginId, aid, port, run = runEc, home = pr
95
94
  throw new Error(`AID ${aid} 原配置格式无效,未覆盖文件`);
96
95
  config.enabled = false;
97
96
  config.active_baseagent = "ecagent";
98
- config.baseagents = { ...config.baseagents, ecagent: { ...config.baseagents?.ecagent, ...ecagent } };
97
+ const ecagent = config.baseagents?.ecagent ?? {};
98
+ delete ecagent.baseUrl;
99
+ delete ecagent.apiKey;
100
+ config.baseagents = { ...config.baseagents, ecagent: { ...ecagent, ...baseagent } };
99
101
  const temporary = `${file}.mab-${(0, node_crypto_1.randomUUID)()}.tmp`;
100
102
  try {
101
103
  await (0, promises_1.writeFile)(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
@@ -30,7 +30,7 @@ const ownership_1 = require("./ownership");
30
30
  const sdk_loader_1 = require("./sdk-loader");
31
31
  const owned_profiles_1 = require("./owned-profiles");
32
32
  const manifest = {
33
- id: "aun", version: "1.0.45-sdk0531.5", type: "channel", displayName: "AUN 通道",
33
+ id: "aun", version: "1.0.48", type: "channel", displayName: "AUN 通道",
34
34
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
35
35
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
36
36
  configSchema: "config.schema.json",
@@ -107,6 +107,7 @@ class AunPlugin {
107
107
  abilitySelections = {};
108
108
  abilityLedgers = {};
109
109
  abilityOwners = {};
110
+ abilityOwnerVerifications = {};
110
111
  abilityOperations;
111
112
  peerMessages;
112
113
  abilityAuditUnavailable = false;
@@ -511,8 +512,23 @@ class AunPlugin {
511
512
  this.config.ability_selections = this.abilitySelections;
512
513
  this.config.ability_ledgers = this.abilityLedgers;
513
514
  this.config.ability_owners = this.abilityOwners;
515
+ this.config.ability_owner_verifications = this.abilityOwnerVerifications;
514
516
  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
517
  }
518
+ async saveAbilityOwnerVerification(aid, ownerAid) {
519
+ const previous = this.abilityOwnerVerifications;
520
+ this.abilityOwnerVerifications = { ...previous, [aid]: ownerAid };
521
+ try {
522
+ await this.persist();
523
+ }
524
+ catch (error) {
525
+ this.abilityOwnerVerifications = previous;
526
+ throw error;
527
+ }
528
+ }
529
+ isAbilityOwnerVerified(aid, ownerAid) {
530
+ return this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
531
+ }
516
532
  async activate(context) {
517
533
  this.context = context;
518
534
  this.config = { ...context.config };
@@ -610,12 +626,21 @@ class AunPlugin {
610
626
  || Object.entries(owners).some(([provider, owner]) => !aidPattern.test(provider) || typeof owner !== "string" || !aidPattern.test(owner))))
611
627
  throw new Error("ability_owners 必须按 Provider AID 配置有效的 Owner AID");
612
628
  this.abilityOwners = { ...owners };
629
+ const verifications = this.config.ability_owner_verifications;
630
+ if (verifications !== undefined && (!verifications || typeof verifications !== "object" || Array.isArray(verifications)
631
+ || Object.entries(verifications).some(([provider, owner]) => !aidPattern.test(provider) || typeof owner !== "string" || !aidPattern.test(owner))))
632
+ throw new Error("ability_owner_verifications 必须按 Provider AID 配置有效的 Owner AID");
633
+ const persistedVerifications = { ...verifications };
634
+ this.abilityOwnerVerifications = Object.fromEntries(Object.entries(persistedVerifications).filter(([provider, owner]) => this.abilityOwners[provider] === owner));
635
+ const staleOwnerVerification = Object.keys(this.abilityOwnerVerifications).length !== Object.keys(persistedVerifications).length;
613
636
  for (const aid of Array.isArray(this.config.identities) ? this.config.identities : []) {
614
637
  if (typeof aid === "string" && aidPattern.test(aid))
615
638
  this.identities.add(aid);
616
639
  }
617
640
  if (this.aid)
618
641
  this.identities.add(this.aid);
642
+ if (staleOwnerVerification)
643
+ await this.persist();
619
644
  this.acceptingMessages = true;
620
645
  // 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
621
646
  for (const aid of Object.keys(this.bindings)) {
@@ -735,7 +760,11 @@ class AunPlugin {
735
760
  const connection = this.clients.get(aid);
736
761
  if (!connection || connection.subscription || !this.bindings[aid])
737
762
  return;
738
- connection.subscription = connection.client.on("message.received", (payload) => this.onMessage(payload, aid));
763
+ connection.subscription = connection.client.on("message.received", (payload) => this.enqueueMessage(payload, aid));
764
+ }
765
+ enqueueMessage(payload, aid) {
766
+ // SDK 串行等待事件回调;任务由现有入站账本排空,不能占住 SDK 的消息分发队列。
767
+ void this.onMessage(payload, aid).catch(error => this.context?.logger.warn("AUN 入站消息处理失败", { aid, stage: "处理已接收消息", error: safeError((0, agent_1.errorDetails)(error)) }));
739
768
  }
740
769
  async connectAid(aid) {
741
770
  try {
@@ -747,7 +776,7 @@ class AunPlugin {
747
776
  const ownedSubscription = this.ownedProfiles && !this.config.owner_watch_aids?.includes(aid)
748
777
  ? client.on("owned_agentmd.changed", payload => this.onOwnedProfile(aid, payload)) : undefined;
749
778
  const subscription = this.bindings[aid]
750
- ? client.on("message.received", (payload) => this.onMessage(payload, aid))
779
+ ? client.on("message.received", (payload) => this.enqueueMessage(payload, aid))
751
780
  : undefined;
752
781
  this.clients.set(aid, { client, subscription, ownedSubscription });
753
782
  try {
@@ -932,7 +961,7 @@ class AunPlugin {
932
961
  for (const aid of Object.keys(this.bindings)) {
933
962
  const definitions = await this.abilityDefinitions(aid);
934
963
  const ownerAid = this.abilityOwners[aid];
935
- const ownerVerified = Boolean(session && ownerAid && this.ownerProof.verified(session, aid, ownerAid));
964
+ const ownerVerified = Boolean(session && ownerAid && this.isAbilityOwnerVerified(aid, ownerAid));
936
965
  const publishAvailable = Boolean(this.context?.managementActor?.(request) && ownerVerified && this.context?.callPluginAbility && this.abilityOperations && !this.abilityAuditUnavailable);
937
966
  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
967
  }
@@ -957,10 +986,10 @@ class AunPlugin {
957
986
  if (paths.length) {
958
987
  const session = this.context?.managementSession?.(request);
959
988
  const owner = this.abilityOwners[aid];
960
- if (!session || !owner || !this.ownerProof.verified(session, aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable)
989
+ if (!session || !owner || !this.isAbilityOwnerVerified(aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable)
961
990
  return this.json(response, 409, { error: "逐次 Owner 确认需可用操作账本、本机已认证管理会话,并先验证该 Provider 的 Owner AID" });
962
991
  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))
992
+ if (!this.isAbilityOwnerVerified(aid, owner) || this.abilityOwners[aid] !== owner || this.revokingAbilityAids.has(aid))
964
993
  throw new Error("Owner 验证已失效或身份正在撤销,请重新确认");
965
994
  const storage = this.clients.get(aid)?.client.storage;
966
995
  if (!storage)
@@ -1000,6 +1029,8 @@ class AunPlugin {
1000
1029
  const ownerAid = typeof body.ownerAid === "string" ? body.ownerAid.trim() : "";
1001
1030
  if (!aidPattern.test(aid) || !this.bindings[aid] || !aidPattern.test(ownerAid))
1002
1031
  return this.json(response, 400, { error: "需要已关联的 Provider AID 和明确指定的 Owner AID" });
1032
+ if (this.isAbilityOwnerVerified(aid, ownerAid))
1033
+ return this.json(response, 200, { ok: true, aid, ownerAid, alreadyVerified: true });
1003
1034
  if (this.abilityOwners[aid] !== ownerAid) {
1004
1035
  this.revokingAbilityAids.add(aid);
1005
1036
  this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
@@ -1007,12 +1038,16 @@ class AunPlugin {
1007
1038
  try {
1008
1039
  await this.invalidateAbilityOperations(aid);
1009
1040
  const previous = this.abilityOwners;
1041
+ const previousVerifications = this.abilityOwnerVerifications;
1010
1042
  this.abilityOwners = { ...previous, [aid]: ownerAid };
1043
+ this.abilityOwnerVerifications = { ...previousVerifications };
1044
+ delete this.abilityOwnerVerifications[aid];
1011
1045
  try {
1012
1046
  await this.persist();
1013
1047
  }
1014
1048
  catch (error) {
1015
1049
  this.abilityOwners = previous;
1050
+ this.abilityOwnerVerifications = previousVerifications;
1016
1051
  throw error;
1017
1052
  }
1018
1053
  }
@@ -1021,7 +1056,7 @@ class AunPlugin {
1021
1056
  }
1022
1057
  }
1023
1058
  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}` } });
1059
+ 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
1060
  this.context?.logger.info("AUN Owner 会话验证已发起,需本机已认证管理会话", { aid, ownerAid });
1026
1061
  this.json(response, 200, { ok: true, aid, ownerAid, expiresAt: challenge.expiresAt });
1027
1062
  }
@@ -1054,7 +1089,7 @@ class AunPlugin {
1054
1089
  if (!original)
1055
1090
  return this.json(response, 404, { error: "能力操作不存在" });
1056
1091
  const ownerAid = this.abilityOwners[original.providerAid];
1057
- if (!ownerAid || !this.ownerProof.verified(session, original.providerAid, ownerAid))
1092
+ if (!ownerAid || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
1058
1093
  return this.json(response, 403, { error: "需本机已认证管理会话,并完成该 Provider 的 Owner AID 验证" });
1059
1094
  if (original.confirmationFingerprint !== body.confirmationFingerprint)
1060
1095
  return this.json(response, 409, { error: "确认指纹与待确认请求不一致" });
@@ -1065,7 +1100,7 @@ class AunPlugin {
1065
1100
  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
1101
  return this.json(response, 409, { error: failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用" });
1067
1102
  }
1068
- if (!this.acceptingMessages || !this.ownerProof.verified(session, original.providerAid, ownerAid))
1103
+ if (!this.acceptingMessages || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
1069
1104
  return this.json(response, 403, { error: "通道或 Owner 管理会话已失效" });
1070
1105
  const confirmation = body.approved ? await store.approve(original.key, body.confirmationFingerprint) : await store.reject(original.key, body.confirmationFingerprint);
1071
1106
  let entry = confirmation.entry;
@@ -1075,10 +1110,12 @@ class AunPlugin {
1075
1110
  let handlerAuthorized = false;
1076
1111
  try {
1077
1112
  const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
1078
- const expired = Date.now() >= entry.expiresAt;
1113
+ const messageExpired = Date.now() >= entry.expiresAt;
1114
+ const confirmationExpired = Date.now() >= entry.confirmationExpiresAt;
1115
+ const expired = messageExpired || confirmationExpired;
1079
1116
  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" };
1117
+ if (failure || expired || invalidated || !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) {
1118
+ 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" };
1082
1119
  state = "failed";
1083
1120
  }
1084
1121
  else {
@@ -1086,7 +1123,7 @@ class AunPlugin {
1086
1123
  const confirmed = entry;
1087
1124
  const beforeStart = () => {
1088
1125
  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))
1126
+ if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !context.managementActor?.(request) || !currentSession || currentSession.id !== session.id || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
1090
1127
  throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
1091
1128
  const pluginId = String(confirmed.executionTarget.pluginId);
1092
1129
  const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
@@ -1102,8 +1139,8 @@ class AunPlugin {
1102
1139
  catch (error) {
1103
1140
  this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
1104
1141
  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" };
1142
+ const notExecuted = !handlerAuthorized && ["message_expired", "confirmation_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
1143
+ 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" };
1107
1144
  state = "failed";
1108
1145
  }
1109
1146
  entry = await store.finish(entry.key, state, result);
@@ -1713,17 +1750,61 @@ class AunPlugin {
1713
1750
  return;
1714
1751
  }
1715
1752
  const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
1753
+ const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
1754
+ const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
1755
+ if (body?.text === "确认" && this.ownerProof.hasPendingConfirmation(aid)) {
1756
+ const ownerAid = this.abilityOwners[aid];
1757
+ const authenticated = Boolean(ownerAid && sender === ownerAid && raw?.encrypted === true
1758
+ && (raw.to === undefined || raw.to === aid) && (raw.sender_aid === undefined || raw.sender_aid === sender)
1759
+ && (envelope?.from === undefined || envelope.from === sender) && (envelope?.to === undefined || envelope.to === aid));
1760
+ if (!authenticated || !this.ownerProof.acceptConfirmation(aid, sender, body.text, true)) {
1761
+ this.context?.logger.warn("AUN Owner 确认未通过验证", { traceId: inboundId, aid, sender: sender ?? "", stage: "Owner 确认校验", reason: authenticated ? "没有有效的待确认通知" : "需要配置 Owner AID 通过加密消息直接回复确认" });
1762
+ return;
1763
+ }
1764
+ try {
1765
+ await this.saveAbilityOwnerVerification(aid, ownerAid);
1766
+ }
1767
+ catch (error) {
1768
+ this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, ownerAid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
1769
+ try {
1770
+ await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。", typeof body.thread_id === "string" ? body.thread_id : undefined, true);
1771
+ }
1772
+ catch (replyError) {
1773
+ this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: false, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(replyError)) });
1774
+ }
1775
+ return;
1776
+ }
1777
+ try {
1778
+ await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请回到 ABWeb 继续操作。`, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
1779
+ }
1780
+ catch (error) {
1781
+ this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: true, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(error)) });
1782
+ }
1783
+ this.context?.logger.info("AUN Owner 确认验证成功并已持久保存", { traceId: inboundId, aid, ownerAid });
1784
+ return;
1785
+ }
1716
1786
  const ownerCommand = typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text) ? body.text.match(/^\/ability-owner ([0-9a-f]{64})$/) : undefined;
1717
1787
  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));
1788
+ let verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
1789
+ if (verified) {
1790
+ const ownerAid = this.abilityOwners[aid];
1791
+ try {
1792
+ if (ownerAid === sender)
1793
+ await this.saveAbilityOwnerVerification(aid, ownerAid);
1794
+ else
1795
+ verified = false;
1796
+ }
1797
+ catch (error) {
1798
+ verified = false;
1799
+ this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
1800
+ }
1801
+ }
1721
1802
  this.context?.logger.info("AUN 宿主委托的 Owner 控制权验证已处理", { aid, verified });
1722
1803
  if (sender && raw?.encrypted === true && (envelope?.from === undefined || envelope.from === sender)) {
1723
1804
  const text = verified
1724
- ? `Owner 控制权验证成功:${aid} 的当前管理会话已通过验证。此操作不会批准任何能力调用,请回到 ABWeb 继续操作。`
1805
+ ? `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请回到 ABWeb 继续操作。`
1725
1806
  : !ownerCommand
1726
- ? "Owner 控制权验证失败:指令格式不正确,请复制最新验证消息中的完整 /ability-owner 指令。"
1807
+ ? "Owner 控制权验证失败:指令格式不正确。请在 ABWeb 重新发送确认通知,并直接回复“确认”。"
1727
1808
  : "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请在 ABWeb 重新发起验证,并使用指定 Owner AID 回复。";
1728
1809
  try {
1729
1810
  await this.sendText(aid, sender, text, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
@@ -1764,14 +1845,51 @@ class AunPlugin {
1764
1845
  }
1765
1846
  return;
1766
1847
  }
1767
- const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => this.processMessage(message, aid, agentPlugin, inboundId));
1848
+ const queuedAt = Date.now();
1849
+ const notices = new Set();
1850
+ let queueTimer;
1851
+ const reportQueue = (started = false) => {
1852
+ if (!started && notices.size)
1853
+ return;
1854
+ const elapsedSeconds = Math.floor((Date.now() - queuedAt) / 1000);
1855
+ const stage = started ? "结束会话排队" : "等待同会话前序任务";
1856
+ const text = started ? `本条消息已结束会话排队,开始处理(排队 ${elapsedSeconds} 秒)。`
1857
+ : elapsedSeconds === 0 ? "消息已排队:当前会话仍有任务未完成,本条将按接收顺序处理。"
1858
+ : `本条消息仍在排队,已等待 ${elapsedSeconds} 秒;尚未转交 Agent,前序任务完成后自动处理。`;
1859
+ const payload = {
1860
+ type: "notice", subtype: started ? "task.dequeued" : "task.queued", severity: "info", text,
1861
+ task_id: message.messageId || inboundId, sent_at: Date.now(),
1862
+ ...(message.threadId ? { thread_id: message.threadId } : {}),
1863
+ ...(message.messageId ? { ref_message_id: message.messageId } : {}),
1864
+ };
1865
+ this.context?.logger.info("AUN 会话排队状态", { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", agentPlugin, stage, elapsedSeconds });
1866
+ // 通知不占执行队列;notice 不会被对端作为普通任务自动互答。
1867
+ const notice = this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true })
1868
+ .then(() => undefined)
1869
+ .catch(error => this.context?.logger.warn("AUN 排队通知发送失败", { aid, peer: message.peerId, traceId: message.messageId || inboundId, stage, error: safeError((0, agent_1.errorDetails)(error)) }))
1870
+ .finally(() => { notices.delete(notice); });
1871
+ notices.add(notice);
1872
+ };
1873
+ const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => {
1874
+ clearInterval(queueTimer);
1875
+ if (pending)
1876
+ reportQueue(true);
1877
+ return this.processMessage(message, aid, agentPlugin, inboundId);
1878
+ });
1768
1879
  this.pendingMessages.set(sourceKey, current);
1880
+ if (pending) {
1881
+ reportQueue();
1882
+ queueTimer = setInterval(reportQueue, 60_000);
1883
+ queueTimer.unref();
1884
+ }
1769
1885
  try {
1770
1886
  await current;
1771
1887
  }
1772
1888
  finally {
1889
+ clearInterval(queueTimer);
1773
1890
  if (this.pendingMessages.get(sourceKey) === current)
1774
1891
  this.pendingMessages.delete(sourceKey);
1892
+ await Promise.allSettled(notices);
1775
1893
  }
1776
1894
  }
1777
1895
  async onFsMessage(raw, message, aid) {
@@ -1840,6 +1958,7 @@ class AunPlugin {
1840
1958
  const { payload, callerAid } = call;
1841
1959
  const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
1842
1960
  let result;
1961
+ let ownerNotice;
1843
1962
  if (rules.length && !rules.includes("*") && !rules.includes(callerAid))
1844
1963
  result = (0, ability_operations_1.abilityFailureResult)(payload, "permission_denied", "调用来源不在 AUN 允许名单中");
1845
1964
  else if (payload.ability_path === "protocol/discover") {
@@ -1895,6 +2014,8 @@ class AunPlugin {
1895
2014
  else {
1896
2015
  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
2016
  result = this.abilityOperationResponse(draft.entry);
2017
+ if (draft.created)
2018
+ ownerNotice = draft.entry;
1898
2019
  }
1899
2020
  }
1900
2021
  }
@@ -1907,6 +2028,30 @@ class AunPlugin {
1907
2028
  }
1908
2029
  }
1909
2030
  await this.sendAbilityResponse(aid, callerAid, result);
2031
+ if (ownerNotice)
2032
+ await this.notifyAbilityOwner(ownerNotice);
2033
+ }
2034
+ async notifyAbilityOwner(entry) {
2035
+ const ownerAid = String(entry.executionTarget.ownerAid ?? "");
2036
+ const text = [
2037
+ "有新的远程 Ability 调用申请",
2038
+ `调用方:${entry.authenticatedCaller}`,
2039
+ `Provider AID:${entry.providerAid}`,
2040
+ `能力路径:${entry.path}`,
2041
+ `operation_id:${entry.operationId}`,
2042
+ `Owner 审批截止:${new Date(entry.confirmationExpiresAt).toLocaleString("zh-CN")}`,
2043
+ `调用最晚启动时间:${new Date(entry.expiresAt).toLocaleString("zh-CN")}`,
2044
+ "请在 ABWeb → AUN 通道 → 待确认能力调用中核对并批准一次或拒绝。只有在调用最晚启动时间前批准才会执行;12 小时审批期限不会延长协议调用 TTL。",
2045
+ ].join("\n");
2046
+ try {
2047
+ const sent = await this.callAunWithRecovery(entry.providerAid, { to: ownerAid, encrypt: true, payload: { type: "text", text } });
2048
+ if (typeof sent?.status !== "string" || !successfulSendStatuses.has(sent.status))
2049
+ throw new Error(`AUN 通知投递状态未确认:${sent?.status === undefined ? "未返回状态" : String(sent.status)}`);
2050
+ this.context?.logger.info("AUN Ability Owner 申请通知已投递", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, status: sent.status, stage: "Owner 申请通知" });
2051
+ }
2052
+ catch (error) {
2053
+ this.context?.logger.warn("AUN Ability Owner 申请通知发送失败,待确认请求仍保留在 ABWeb", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知", error: safeError((0, agent_1.errorDetails)(error)) });
2054
+ }
1910
2055
  }
1911
2056
  async abilityInvocationFailure(aid, callerAid, path, definitionId, params) {
1912
2057
  if (this.abilityAuditUnavailable)
@@ -2038,17 +2183,18 @@ class AunPlugin {
2038
2183
  groups.set(kind, group);
2039
2184
  }
2040
2185
  groupsCount = groups.size;
2186
+ const captionKind = (groupsCount === 1 ? groups.keys().next().value : undefined) ?? "attachment";
2187
+ const payloadText = withAttachmentDownloadLinks(text || (captionKind === "image" ? "图片已生成。" : captionKind === "video" ? "视频已生成。" : captionKind === "file" ? "文件已生成。" : "附件已生成。"), captionKind, refs);
2188
+ fallbackText = payloadText;
2189
+ fallbackLinks = attachmentDownloadLinks(refs);
2041
2190
  for (const [kind, group] of groups) {
2042
2191
  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 };
2192
+ // AUN 网页可能隐藏附件卡片的正文;所有附件发完后只另发一次完整正文。
2193
+ const payload = { type: kind, attachments: group };
2046
2194
  if (message.threadId)
2047
2195
  payload.thread_id = message.threadId;
2048
2196
  if (message.messageId)
2049
2197
  payload.ref_message_id = message.messageId;
2050
- fallbackText = payloadText;
2051
- fallbackLinks = attachmentDownloadLinks(group);
2052
2198
  sending = true;
2053
2199
  // message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
2054
2200
  const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
@@ -2057,17 +2203,17 @@ class AunPlugin {
2057
2203
  this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
2058
2204
  sending = false;
2059
2205
  sent++;
2060
- if (kind === "image" && payloadText) {
2206
+ if (sent === groupsCount && payloadText) {
2061
2207
  try {
2062
2208
  signal?.throwIfAborted();
2063
2209
  const { attachments: _attachments, ...caption } = payload;
2064
2210
  const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText, format: "markdown" }, encrypt: true });
2065
2211
  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: "发送图片说明" });
2212
+ throw new Error(`AUN 附件正文发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
2213
+ this.context?.logger.info("AUN 附件正文已发送", { ...trace, stage: "发送附件正文" });
2068
2214
  }
2069
2215
  catch (error) {
2070
- throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
2216
+ throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "附件已发送,发送 AUN 附件正文(结果未确认,不自动重发)", error);
2071
2217
  }
2072
2218
  }
2073
2219
  }
@@ -189,7 +189,7 @@ class AunPeerMessagingStore {
189
189
  return false;
190
190
  const payload = value.payload;
191
191
  const senderDeviceId = typeof value.sender_device_id === "string" ? value.sender_device_id : undefined;
192
- const requestId = payload.type === "ability" && payload.role === "result" ? payload.call_id : payload.thread_id;
192
+ const requestId = payload.type === "ability" && ["result", "ack"].includes(String(payload.role)) ? payload.call_id : payload.thread_id;
193
193
  if (typeof requestId !== "string")
194
194
  return false;
195
195
  const current = this.entries.get(key(selfAid, requestId));
@@ -197,9 +197,11 @@ class AunPeerMessagingStore {
197
197
  return false;
198
198
  const expected = current.payload;
199
199
  if (expected.type === "ability") {
200
- if (payload.type !== "ability" || payload.role !== "result" || ["call_id", "operation_id", "ability_path", "definition_id"].some(field => expected[field] !== payload[field]))
200
+ if (payload.type !== "ability" || !["result", "ack"].includes(String(payload.role)) || ["call_id", "operation_id", "ability_path", "definition_id"].some(field => expected[field] !== payload[field]))
201
201
  return false;
202
- if (typeof payload.ok !== "boolean" || typeof payload.execution_state !== "string" || discovery(expected) && !senderDeviceId)
202
+ if (typeof payload.execution_state !== "string" || discovery(expected) && (!senderDeviceId || payload.role !== "result"))
203
+ return false;
204
+ if (payload.role === "ack" ? !["pending", "pending_confirmation", "executing", "accepted"].includes(payload.execution_state) || payload.result !== undefined || payload.ok === false : typeof payload.ok !== "boolean")
203
205
  return false;
204
206
  }
205
207
  else if (expected.type !== "text" || payload.type !== "text" || typeof payload.text !== "string")
@@ -82,7 +82,7 @@
82
82
  for (const value of [aid, `${item.agent} / ${item.pluginVersion || "版本未知"}`]) { const cell = document.createElement("td"); cell.textContent = value; row.append(cell); }
83
83
  const ownerCell = document.createElement("td"); ownerCell.className = "ability-owner";
84
84
  const owner = document.createElement("input"); owner.type = "text"; owner.value = item.ownerAid || ""; owner.placeholder = "明确指定 Owner AID"; owner.setAttribute("aria-label", `${aid} 宿主委托 Owner AID`);
85
- const challenge = document.createElement("button"); challenge.type = "button"; challenge.textContent = "发送 Owner 验证";
85
+ const challenge = document.createElement("button"); challenge.type = "button"; challenge.textContent = "发送 Owner 确认通知";
86
86
  const proof = document.createElement("p"); proof.setAttribute("role", "status");
87
87
  ownerCell.append(owner, challenge, proof); row.append(ownerCell);
88
88
  const choices = document.createElement("td");
@@ -102,7 +102,7 @@
102
102
  save.title = verified ? state.item.reason || "保存当前选择的能力" : "请先验证当前管理会话的宿主委托 Owner";
103
103
  revoke.disabled = state.busy || !state.item.published;
104
104
  challenge.disabled = state.challengeBusy || !owner.value.trim();
105
- proof.textContent = verified ? "当前管理会话的 Owner 已验证;每次调用仍须单独批准。" : state.challengeExpiresAt > Date.now() && owner.value.trim() === state.item.ownerAid ? `等待指定 Owner 通过 AUN 回复验证指令;验证截止:${new Date(state.challengeExpiresAt).toLocaleString()}` : "当前 Owner 未验证,请发送验证。";
105
+ proof.textContent = verified ? "当前 Owner 已验证;每次调用仍须单独批准。" : state.challengeExpiresAt > Date.now() && owner.value.trim() === state.item.ownerAid ? `验证通知已通过 AUN 发送给指定 Owner,请直接回复“确认”;截止:${new Date(state.challengeExpiresAt).toLocaleString()}` : "当前 Owner 未验证,请发送确认通知。";
106
106
  } };
107
107
  abilityRows.set(aid, state);
108
108
  owner.oninput = () => { state.sync(); for (const pending of pendingRows) pending.sync(); };
@@ -114,9 +114,9 @@
114
114
  state.sync(); for (const pending of pendingRows) pending.sync();
115
115
  try {
116
116
  const result = await request("/api/plugins/aun/abilities/owner-challenge", "POST", { aid, ownerAid });
117
- state.item = { ...state.item, ownerAid, ownerVerified: false, publishAvailable: false };
118
- state.challengeExpiresAt = result.expiresAt;
119
- notify("Owner 验证已发送", `验证指令已通过 AUN 发送给 ${ownerAid}。请该 Owner 在 AUN 中回复收到的 /ability-owner 指令;仅验证当前管理会话,不会执行能力。`);
117
+ state.item = { ...state.item, ownerAid, ownerVerified: Boolean(result.alreadyVerified), publishAvailable: Boolean(result.alreadyVerified) };
118
+ state.challengeExpiresAt = result.alreadyVerified ? 0 : result.expiresAt;
119
+ notify("Owner 确认通知结果", result.alreadyVerified ? `${ownerAid} 已完成验证,无需重复确认。` : `加密确认通知已通过 AUN 发送给 ${ownerAid}。请直接回复“确认”;仅验证 Owner 身份,不会执行能力。`);
120
120
  } catch (error) { notify("Owner 验证发送失败", errorMessage(error), true); }
121
121
  finally { state.challengeBusy = false; state.sync(); for (const pending of pendingRows) pending.sync(); }
122
122
  };
@@ -153,17 +153,18 @@
153
153
  cell(`operation_id: ${entry.operationId}\nkey: ${entry.key}\nconfirmationFingerprint: ${entry.confirmationFingerprint}`, true);
154
154
  cell(`path: ${entry.path}\ndefinition_id: ${entry.definitionId}`, true);
155
155
  cell(JSON.stringify(entry.params, null, 2), true);
156
- const details = cell(""); const contract = document.createElement("pre"); contract.textContent = `effects: ${JSON.stringify(entry.effects)}\n执行目标: ${JSON.stringify(entry.executionTarget, null, 2)}\n过期时间: ${new Date(entry.expiresAt).toLocaleString()} (${entry.expiresAt})\n状态: ${entry.state}`; details.append(contract);
156
+ const details = cell(""); const contract = document.createElement("pre"); contract.textContent = `effects: ${JSON.stringify(entry.effects)}\n执行目标: ${JSON.stringify(entry.executionTarget, null, 2)}\nOwner 审批截止时间: ${new Date(entry.confirmationExpiresAt).toLocaleString()} (${entry.confirmationExpiresAt})\n调用最晚启动时间: ${new Date(entry.expiresAt).toLocaleString()} (${entry.expiresAt})\n状态: ${entry.state}`; details.append(contract);
157
157
  const expiry = document.createElement("p"); details.append(expiry);
158
158
  const actions = cell(""); const proof = document.createElement("p");
159
159
  const approve = document.createElement("button"); approve.type = "button"; approve.textContent = "批准一次";
160
160
  const reject = document.createElement("button"); reject.type = "button"; reject.textContent = "拒绝";
161
161
  const pending = { sync: () => {
162
162
  const verified = ownerVerified(entry.providerAid);
163
- const expired = !Number.isFinite(entry.expiresAt) || entry.expiresAt <= Date.now();
163
+ const confirmationExpired = !Number.isFinite(entry.confirmationExpiresAt) || entry.confirmationExpiresAt <= Date.now();
164
+ const messageExpired = !Number.isFinite(entry.expiresAt) || entry.expiresAt <= Date.now();
164
165
  const unavailable = confirming || !verified || entry.state !== "pending_confirmation";
165
- approve.disabled = unavailable || expired; reject.disabled = unavailable;
166
- expiry.textContent = expired ? "已过期,不能批准" : "有效期内,每次批准只执行本条请求";
166
+ approve.disabled = unavailable || confirmationExpired || messageExpired; reject.disabled = unavailable || confirmationExpired;
167
+ expiry.textContent = confirmationExpired ? "Owner 审批申请已过期" : messageExpired ? "调用已超过最晚启动时间;不能再执行,但仍可拒绝" : "可在调用截止前批准一次;审批记录最长保留 12 小时";
167
168
  proof.textContent = verified ? "当前会话的 Owner 已验证" : "此 Provider 的 Owner 未验证,请先验证 Owner";
168
169
  } };
169
170
  const confirm = async (approved) => {
@@ -6,8 +6,8 @@
6
6
  <header><h1>AUN 身份</h1><button id="refresh" type="button">刷新</button></header>
7
7
  <p id="status" role="status"></p>
8
8
  <div class="table-scroll"><table><thead><tr><th>AID</th><th>来源</th><th>已关联 Agent</th><th>操作</th></tr></thead><tbody id="identities"></tbody></table></div>
9
- <section><h2>Agent 能力发布</h2><p>默认不发布。请为每个 AID 明确选择公开能力,并验证宿主本地委托策略中的 Owner AID;此配置不代表 AUN 协议中的 ownership 关系。验证指令只通过 AUN 发送给指定 Owner,Owner 回复收到的 /ability-owner 指令仅验证当前管理会话,不会执行业务。发布能力后,每次调用仍须单独确认。</p><div class="table-scroll"><table><thead><tr><th>Provider AID</th><th>关联 Agent / 版本</th><th>宿主委托 Owner</th><th>可发布能力</th><th>操作</th></tr></thead><tbody id="abilities"></tbody></table></div></section>
10
- <section><h2>待确认能力调用</h2><p>核对认证调用方、原始参数、契约指纹、影响和执行目标后,逐项批准一次或拒绝。批准只绑定表格中的完整请求;过期请求不能批准。没有当前会话的 Owner 证明时,不能确认执行。</p><div class="table-scroll"><table class="ability-pending-table"><thead><tr><th>Provider AID</th><th>认证调用方</th><th>操作与确认指纹</th><th>能力路径 / 契约版本</th><th>原始参数</th><th>影响、执行目标与有效期</th><th>逐次确认</th></tr></thead><tbody id="ability-pending"></tbody></table></div></section>
9
+ <section><h2>Agent 能力发布</h2><p>默认不发布。请为每个 AID 明确选择公开能力,并验证宿主本地委托策略中的 Owner AID;此配置不代表 AUN 协议中的 ownership 关系。验证通知通过 AUN 加密发送给指定 Owner,Owner 直接回复“确认”即可,不需要复制命令或令牌。发布能力后,每次调用仍须单独确认。</p><div class="table-scroll"><table><thead><tr><th>Provider AID</th><th>关联 Agent / 版本</th><th>宿主委托 Owner</th><th>可发布能力</th><th>操作</th></tr></thead><tbody id="abilities"></tbody></table></div></section>
10
+ <section><h2>待确认能力调用</h2><p>有效请求会出现在此表格,Peer 同时收到 pending 回执,Owner 会收到加密通知。Owner 审批记录保留 12 小时;协议 TTL 仍是调用最晚启动时间,超过后不能再执行。核对认证调用方、原始参数、契约指纹、影响和执行目标;没有当前会话的 Owner 证明时,不能确认执行。</p><div class="table-scroll"><table class="ability-pending-table"><thead><tr><th>Provider AID</th><th>认证调用方</th><th>操作与确认指纹</th><th>能力路径 / 契约版本</th><th>原始参数</th><th>影响、执行目标与有效期</th><th>逐次确认</th></tr></thead><tbody id="ability-pending"></tbody></table></div></section>
11
11
  <form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
12
12
  <form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from" placeholder="留空接受所有 Peer,填写 AID 后限制"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
13
13
  </main>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.45-sdk0531.5",
3
+ "version": "1.0.48",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.45-sdk0531.5",
3
+ "version": "1.0.48",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",