agent-bridge-aun 1.0.49 → 1.0.58

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.
@@ -13,7 +13,6 @@ const node_os_1 = require("node:os");
13
13
  const _2020_1 = __importDefault(require("ajv/dist/2020"));
14
14
  const yaml_1 = require("yaml");
15
15
  const agent_1 = require("../../agent");
16
- const evolcore_1 = require("./evolcore");
17
16
  const messages_1 = require("./messages");
18
17
  const attachments_1 = require("./attachments");
19
18
  const ability_publication_1 = require("./ability-publication");
@@ -30,8 +29,12 @@ const directory_1 = require("./directory");
30
29
  const ownership_1 = require("./ownership");
31
30
  const sdk_loader_1 = require("./sdk-loader");
32
31
  const owned_profiles_1 = require("./owned-profiles");
32
+ const menu_1 = require("./menu");
33
+ const agentmd_menu_1 = require("./agentmd-menu");
34
+ const agentmd_command_1 = require("./agentmd-command");
35
+ const agentmd_natural_1 = require("./agentmd-natural");
33
36
  const manifest = {
34
- id: "aun", version: "1.0.49", type: "channel", displayName: "AUN 通道",
37
+ id: "aun", version: "1.0.58", type: "channel", displayName: "AUN 通道",
35
38
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
36
39
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
37
40
  configSchema: "config.schema.json",
@@ -67,6 +70,7 @@ async function loadSdk() {
67
70
  const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
68
71
  const aunRootFingerprint = "DD:09:E9:FD:31:6C:42:DC:7D:F3:5C:98:9C:2C:76:3A:A1:88:D8:4C:9C:C9:FC:D4:2E:A3:10:A2:62:1E:C5:45";
69
72
  const cliCommands = [
73
+ { name: "agentmd", summary: "读取或发布能力介绍与名片", usage: "mab aun agentmd get <aid> | set <aid> <name|description|version|tags|exttype|body> <值> | put <aid> <完整Markdown> [--json]" },
70
74
  { name: "status", summary: "查看 AUN 通道状态", usage: "mab aun status" },
71
75
  { name: "list", summary: "查看已连接 AID 和 Agent 关联", usage: "mab aun list" },
72
76
  { name: "agents", summary: "列出当前已就绪的 Agent 插件", usage: "mab aun agents" },
@@ -128,8 +132,11 @@ class AunPlugin {
128
132
  healthMessage = "尚未配置 AID";
129
133
  seen = new messages_1.DuplicateTracker();
130
134
  pendingMessages = new Map();
135
+ activeRuntimeSources = new Set();
131
136
  inboundMessages = new Set();
132
137
  acceptingMessages = false;
138
+ menu;
139
+ agentMdMenu = new agentmd_menu_1.AgentMdMenu();
133
140
  constructor(options = {}) {
134
141
  this.options = options;
135
142
  this.sdk = options.sdk;
@@ -308,6 +315,8 @@ class AunPlugin {
308
315
  create: async (aid, owner) => {
309
316
  if (!aidPattern.test(aid) || !aidPattern.test(owner))
310
317
  throw new Error("需要有效的 AID 和 Owner AID");
318
+ if (!this.isBridgeAid(aid) && this.listStoreAids().includes(aid))
319
+ throw new Error("EvolCore 来源身份不能由本工程创建或连接,请使用新的 AID");
311
320
  if (this.creating)
312
321
  throw new Error("正在创建身份,请稍后再试");
313
322
  this.creating = true;
@@ -332,6 +341,7 @@ class AunPlugin {
332
341
  this.context?.logger.info("AUN 身份已创建", { aid });
333
342
  }
334
343
  const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
344
+ let status = "已创建";
335
345
  let directorySync;
336
346
  try {
337
347
  await this.syncOwnerDirectory(aid, owner);
@@ -339,18 +349,27 @@ class AunPlugin {
339
349
  this.context?.logger.info("AUN 新 AID 已加入 Owner 名录", { aid });
340
350
  }
341
351
  catch (error) {
342
- directorySync = { status: "not_synced", error: safeError(error) };
343
- this.context?.logger.warn("AUN AID 已创建,但 Owner 名录未同步", { aid, error: directorySync.error });
352
+ if (error instanceof fs_1.AunFsError && error.code === "OWNERSHIP_PENDING") {
353
+ status = "等待 Owner 确认";
354
+ directorySync = { status: "not_synced" };
355
+ this.context?.logger.info("AUN AID 已创建并连接,关联请求已发送,等待 Owner 确认", { aid });
356
+ }
357
+ else {
358
+ directorySync = { status: "not_synced", error: safeError(error) };
359
+ this.context?.logger.warn("AUN AID 已创建,但 Owner 名录未同步", { aid, error: directorySync.error });
360
+ }
344
361
  }
345
- return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}), directorySync };
362
+ return { aid, status, connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}), directorySync };
346
363
  }
347
364
  finally {
348
365
  this.creating = false;
349
366
  }
350
367
  },
351
- bind: async (pluginId, aid, port) => {
368
+ bind: async (pluginId, aid) => {
352
369
  if (!aid || !aidPattern.test(aid))
353
370
  throw new Error("AID 格式无效,必须指定要关联的 AID");
371
+ if (!this.isBridgeAid(aid))
372
+ throw new Error("EvolCore 来源身份不能关联 Agent,请使用本工程创建的 AID");
354
373
  if (pluginId === "aun" || pluginId === "abweb")
355
374
  throw new Error("只能关联 Agent 插件");
356
375
  if (this.context?.pluginStatus(pluginId) !== "ready")
@@ -366,14 +385,6 @@ class AunPlugin {
366
385
  const loaded = this.store.load(aid);
367
386
  if (!loaded.ok || !loaded.data)
368
387
  throw new Error("AID 不在本地密钥库中,无法关联");
369
- // 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
370
- if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
371
- const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
372
- if (method === "json")
373
- this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
374
- else
375
- this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
376
- }
377
388
  this.identities.add(aid);
378
389
  if (!connection)
379
390
  await this.connectAid(aid);
@@ -464,12 +475,25 @@ class AunPlugin {
464
475
  async syncOwnerDirectory(aid, ownerAid) {
465
476
  if (aid === ownerAid)
466
477
  throw new Error("新 AID 与 Owner AID 相同,未写入 Owner 名录");
478
+ const subClient = this.clients.get(aid)?.client;
479
+ if (this.store?.resolve && subClient?.checkOwnershipConsensus) {
480
+ const verified = await (0, ownership_1.readAunOwnership)(this.store, subClient, aid);
481
+ if (verified.declaredOwner !== ownerAid)
482
+ throw new fs_1.AunFsError("OWNERSHIP_CONFLICT", "指定 Owner 与已验签的现有 Owner 不同,未同步名录");
483
+ if (verified.ownership.status === "single_sided")
484
+ throw new fs_1.AunFsError("OWNERSHIP_PENDING", "关联请求已发送,等待 Owner 确认;确认前暂不加入通信名录,请勿重复创建 AID");
485
+ if (verified.ownership.status !== "consensus")
486
+ throw new fs_1.AunFsError("OWNERSHIP_UNAVAILABLE", `Owner 归属尚未核验完成(${verified.ownership.status}),未同步名录`);
487
+ }
467
488
  let client = this.ownerClients.get(ownerAid)?.client ?? this.clients.get(ownerAid)?.client;
468
489
  let temporary = false;
469
490
  if (!client) {
470
491
  const loaded = this.store?.load(ownerAid);
471
492
  if (!loaded?.ok || !loaded.data)
472
493
  throw new Error("Owner AID 不在本地密钥库,AID 已创建但无法同步 Owner 名录");
494
+ // SDK load() 成功也可能只有公钥证书;list() 只返回持有有效私钥的身份。
495
+ if (!this.listStoreAids().includes(ownerAid))
496
+ throw new Error("本机没有 Owner 的有效私钥,AID 已创建但无法在本机同步 Owner 名录");
473
497
  await this.prepareTrustRoots(ownerAid);
474
498
  const trusted = this.store?.load(ownerAid);
475
499
  if (!trusted?.ok || !trusted.data)
@@ -507,6 +531,9 @@ class AunPlugin {
507
531
  }
508
532
  }
509
533
  }
534
+ isBridgeAid(aid) {
535
+ return this.createdAids.has(aid) || this.legacyCreatedAids.has(aid);
536
+ }
510
537
  async persist() {
511
538
  this.config.identities = [...this.identities].sort();
512
539
  this.config.created_aids = [...new Set([...this.createdAids, ...this.legacyCreatedAids])].sort();
@@ -628,6 +655,8 @@ class AunPlugin {
628
655
  ["POST", "/api/plugins/aun/abilities/owner-challenge", this.challengeAbilityOwner],
629
656
  ["GET", "/api/plugins/aun/abilities/pending", this.pendingAbilityOperations],
630
657
  ["POST", "/api/plugins/aun/abilities/confirm", this.confirmAbilityOperation],
658
+ ["GET", "/api/plugins/aun/abilities/grants", this.listAbilityGrants],
659
+ ["POST", "/api/plugins/aun/abilities/grants/revoke", this.revokeAbilityGrant],
631
660
  ["GET", "/api/plugins/aun/commands", this.listCommands],
632
661
  ["POST", "/api/plugins/aun/commands", this.runCommand],
633
662
  ["GET", fs_transfer_1.FS_TRANSFER_PATH, this.fsTransfer],
@@ -663,8 +692,59 @@ class AunPlugin {
663
692
  }
664
693
  if (this.aid)
665
694
  this.identities.add(this.aid);
666
- if (staleOwnerVerification)
695
+ let removedExternalIdentity = false;
696
+ // 升级时先清理本地接管记录,不能为撤销旧发布而再次连接外部身份。
697
+ for (const aid of new Set([...this.identities, ...Object.keys(this.bindings), ...Object.keys(this.bindingTimes), ...Object.keys(this.abilitySelections), ...Object.keys(this.abilityOwners), ...Object.keys(this.abilityOwnerVerifications)])) {
698
+ if (this.isBridgeAid(aid))
699
+ continue;
700
+ await this.invalidateAbilityOperations(aid);
701
+ this.identities.delete(aid);
702
+ delete this.bindings[aid];
703
+ delete this.bindingTimes[aid];
704
+ delete this.abilitySelections[aid];
705
+ delete this.abilityOwners[aid];
706
+ delete this.abilityOwnerVerifications[aid];
707
+ if (this.aid === aid)
708
+ this.config.aid = this.aid = "";
709
+ removedExternalIdentity = true;
710
+ context.logger.info("AUN 已自动解除 EvolCore 来源身份的旧关联,不再连接或接管该身份", { aid });
711
+ }
712
+ if (staleOwnerVerification || removedExternalIdentity)
667
713
  await this.persist();
714
+ this.menu = new menu_1.AunMenu(context.dataDir, {
715
+ target: aid => this.acceptingMessages && this.bindings[aid] && this.clients.has(aid)
716
+ ? JSON.stringify([this.bindings[aid], this.bindingTimes[aid] ?? ""]) : undefined,
717
+ baseagent: aid => this.bindings[aid],
718
+ runtime: aid => this.runtimeMenuSnapshot(aid),
719
+ log: (message, fields) => context.logger.info(message, fields),
720
+ owner: async (aid) => {
721
+ try {
722
+ const client = this.clients.get(aid)?.client;
723
+ if (!client || !this.store)
724
+ return "";
725
+ const snapshot = await (0, ownership_1.readAunOwnership)(this.store, client, aid);
726
+ return snapshot.ownership.status === "consensus" ? snapshot.ownership.owner_aid ?? "" : "";
727
+ }
728
+ catch {
729
+ return "";
730
+ }
731
+ },
732
+ directory: async (aid, ownerAid) => {
733
+ const client = this.ownerClients.get(ownerAid)?.client ?? this.clients.get(ownerAid)?.client ?? this.clients.get(aid)?.client;
734
+ if (!client?.storage)
735
+ throw new Error("Owner 名录读取不可用");
736
+ return (0, owner_directory_1.readOwnerDirectory)(client.storage, ownerAid);
737
+ },
738
+ send: (aid, params) => this.callAunWithRecovery(aid, params),
739
+ allowFrom: () => typeof this.config.allow_from === "string" ? this.config.allow_from : undefined,
740
+ agentmd: async (aid, raw) => {
741
+ const response = await this.agentMdMenu.handle(raw, aid, this.agentMdPort(aid), true);
742
+ if (!response)
743
+ throw new Error("名片请求无效");
744
+ return response;
745
+ },
746
+ });
747
+ await this.menu.load();
668
748
  this.acceptingMessages = true;
669
749
  // 只订阅明确关联 Agent 的身份,避免接管 EvolCore 或其它 AID 的消息。
670
750
  for (const aid of Object.keys(this.bindings)) {
@@ -826,6 +906,7 @@ class AunPlugin {
826
906
  async deactivate(_reason) {
827
907
  this.status = "degraded";
828
908
  this.acceptingMessages = false;
909
+ this.agentMdMenu.clear();
829
910
  if (this.ownedProfileTimer)
830
911
  clearInterval(this.ownedProfileTimer);
831
912
  this.ownedProfileTimer = undefined;
@@ -859,6 +940,7 @@ class AunPlugin {
859
940
  await this.abilityOperations?.interruptExecuting();
860
941
  await Promise.allSettled(this.inboundMessages);
861
942
  await Promise.allSettled(this.pendingMessages.values());
943
+ this.menu = undefined;
862
944
  for (const { client } of this.clients.values())
863
945
  await client.close();
864
946
  this.clients.clear();
@@ -977,7 +1059,14 @@ class AunPlugin {
977
1059
  const pluginId = this.bindings[aid];
978
1060
  if (!pluginId || this.context?.pluginStatus(pluginId) !== "ready" || !this.context?.listPluginAbilities)
979
1061
  return [];
980
- return this.context.listPluginAbilities(pluginId, aid);
1062
+ return (await this.context.listPluginAbilities(pluginId, aid)).map(definition => {
1063
+ const properties = definition.params_schema.properties;
1064
+ if (definition.path !== "agent/task/run" || properties?.task?.pattern !== "\\S")
1065
+ return definition;
1066
+ // AUN 共同 Schema 不支持 pattern,非空白校验在申请入口及原宿主执行入口保留。
1067
+ const { pattern: _pattern, ...task } = properties.task;
1068
+ return { ...definition, params_schema: { ...definition.params_schema, properties: { ...properties, task } } };
1069
+ });
981
1070
  }
982
1071
  listAbilities = async (request, response) => {
983
1072
  try {
@@ -988,7 +1077,7 @@ class AunPlugin {
988
1077
  const ownerAid = this.abilityOwners[aid];
989
1078
  const ownerVerified = Boolean(session && ownerAid && this.isAbilityOwnerVerified(aid, ownerAid));
990
1079
  const publishAvailable = Boolean(this.context?.managementActor?.(request) && ownerVerified && this.context?.callPluginAbility && this.abilityOperations && !this.abilityAuditUnavailable);
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;已有索引仍可撤销。" };
1080
+ 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;已有索引仍可撤销。" };
992
1081
  }
993
1082
  this.json(response, 200, { abilities: result });
994
1083
  }
@@ -1120,15 +1209,49 @@ class AunPlugin {
1120
1209
  const body = await this.readJsonBody(request);
1121
1210
  if (typeof body.key !== "string" || typeof body.confirmationFingerprint !== "string" || typeof body.approved !== "boolean")
1122
1211
  return this.json(response, 400, { error: "必须明确提交操作键、完整确认指纹和批准或拒绝决定" });
1212
+ const grantDurationMs = body.grantDurationMs === undefined ? undefined : body.grantDurationMs === null ? null : body.grantDurationMs === 43_200_000 ? 43_200_000 : undefined;
1213
+ if (body.grantDurationMs !== undefined && grantDurationMs === undefined)
1214
+ return this.json(response, 400, { error: "持续授权只支持批准 12 小时(grantDurationMs=43200000)或永久(grantDurationMs=null)" });
1215
+ if (grantDurationMs !== undefined && body.approved !== true)
1216
+ return this.json(response, 400, { error: "持续授权只能随批准提交" });
1123
1217
  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);
1218
+ const result = await this.decideAbilityOperation(body.key, body.confirmationFingerprint, body.approved, () => this.context === context && Boolean(context.managementActor?.(request)) && context.managementSession?.(request)?.id === session.id, undefined, { grantDurationMs });
1125
1219
  this.json(response, 200, result);
1126
1220
  }
1127
1221
  catch (error) {
1128
1222
  this.json(response, error instanceof AunCommandError ? error.statusCode : 409, { error: safeError(error) });
1129
1223
  }
1130
1224
  };
1131
- async decideAbilityOperation(key, fingerprint, approved, authorized, providerAid) {
1225
+ listAbilityGrants = async (request, response) => {
1226
+ if (!this.context?.managementActor?.(request) || !this.context?.managementSession?.(request))
1227
+ return this.json(response, 403, { error: "读取持续授权需本机已认证管理会话" });
1228
+ try {
1229
+ this.json(response, 200, { grants: await this.abilityOperations?.listGrants() ?? [] });
1230
+ }
1231
+ catch (error) {
1232
+ this.json(response, 503, { error: safeError(error) });
1233
+ }
1234
+ };
1235
+ revokeAbilityGrant = async (request, response) => {
1236
+ if (!this.context?.managementActor?.(request) || !this.context?.managementSession?.(request))
1237
+ return this.json(response, 403, { error: "撤销持续授权需本机已认证管理会话" });
1238
+ try {
1239
+ const body = await this.readJsonBody(request);
1240
+ const aid = typeof body.aid === "string" ? body.aid.trim() : "";
1241
+ const grantId = typeof body.grantId === "string" ? body.grantId.trim() : "";
1242
+ if (!aid || !grantId)
1243
+ return this.json(response, 400, { error: "需要 Provider AID 和授权 ID" });
1244
+ const grant = await this.abilityOperations?.revokeGrant(aid, grantId);
1245
+ if (!grant)
1246
+ return this.json(response, 503, { error: "持久操作账本不可用" });
1247
+ this.context?.logger.info("AUN Ability 持续授权已撤销", { aid, grantId });
1248
+ this.json(response, 200, { ok: true, grant });
1249
+ }
1250
+ catch (error) {
1251
+ this.json(response, error instanceof AunCommandError ? error.statusCode : 409, { error: safeError(error) });
1252
+ }
1253
+ };
1254
+ async decideAbilityOperation(key, fingerprint, approved, authorized, providerAid, options = {}) {
1132
1255
  const store = this.abilityOperations;
1133
1256
  if (!store || this.abilityAuditUnavailable)
1134
1257
  throw new AunCommandError("持久操作账本不可用", 503);
@@ -1149,9 +1272,14 @@ class AunPlugin {
1149
1272
  }
1150
1273
  if (!authorized() || !this.acceptingMessages || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
1151
1274
  throw new AunCommandError("通道或 Owner 管理会话已失效", 403);
1152
- const confirmation = approved ? await store.approve(original.key, fingerprint) : await store.reject(original.key, fingerprint);
1275
+ const confirmation = approved ? await store.approve(original.key, fingerprint, options) : await store.reject(original.key, fingerprint);
1153
1276
  let entry = confirmation.entry;
1277
+ const grantId = options.grantId ?? confirmation.grant?.id;
1278
+ if (options.grantDurationMs !== undefined && confirmation.grant)
1279
+ this.context?.logger.info("AUN Ability 持续授权已保存", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, path: entry.path, grantId, expiresAt: confirmation.grant.expiresAt, ownerAid });
1154
1280
  if (confirmation.started) {
1281
+ if (options.grantId)
1282
+ await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
1155
1283
  let result;
1156
1284
  let state;
1157
1285
  let handlerAuthorized = false;
@@ -1161,14 +1289,17 @@ class AunPlugin {
1161
1289
  const confirmationExpired = Date.now() >= entry.confirmationExpiresAt;
1162
1290
  const expired = messageExpired || confirmationExpired;
1163
1291
  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" };
1292
+ const grantUnavailable = grantId !== undefined && !store.grantActive(grantId, entry);
1293
+ if (failure || expired || invalidated || grantUnavailable || !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) {
1294
+ 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") : grantUnavailable ? "grant_unavailable" : failure?.code ?? (invalidated ? "provider_invalidated" : "permission_denied"), messageExpired ? "调用已超过最晚启动时间" : confirmationExpired ? "Owner 确认申请已过期,不会执行" : grantUnavailable ? "持续授权已到期或撤销,本次能力没有执行" : failure?.message ?? "Owner 管理会话或执行目标已改变,本次能力没有执行"), execution_state: "not_executed" };
1166
1295
  state = "failed";
1167
1296
  }
1168
1297
  else {
1169
1298
  const context = this.context;
1170
1299
  const confirmed = entry;
1171
1300
  const beforeStart = () => {
1301
+ if (grantId !== undefined && !store.grantActive(grantId, confirmed))
1302
+ throw Object.assign(new Error("持续授权已到期或撤销,本次能力没有执行"), { code: "grant_unavailable" });
1172
1303
  if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !authorized() || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
1173
1304
  throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
1174
1305
  const pluginId = String(confirmed.executionTarget.pluginId);
@@ -1177,7 +1308,7 @@ class AunPlugin {
1177
1308
  throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
1178
1309
  handlerAuthorized = true;
1179
1310
  };
1180
- const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
1311
+ const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, callerAid: entry.authenticatedCaller, expiresAt: entry.expiresAt, beforeStart });
1181
1312
  result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
1182
1313
  state = "succeeded";
1183
1314
  }
@@ -1185,15 +1316,15 @@ class AunPlugin {
1185
1316
  catch (error) {
1186
1317
  this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
1187
1318
  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" };
1319
+ const notExecuted = !handlerAuthorized && ["message_expired", "confirmation_expired", "provider_invalidated", "confirmation_unavailable", "grant_unavailable"].includes(code ?? "");
1320
+ 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 确认申请已过期,不会执行" : code === "grant_unavailable" ? "持续授权已到期或撤销,本次能力没有执行" : "管理会话或 Provider 状态已改变,本次能力没有执行") : "能力执行失败,请检查 Provider 运行日志;不会自动重试"), execution_state: notExecuted ? "not_executed" : "failed" };
1190
1321
  state = "failed";
1191
1322
  }
1192
1323
  entry = await store.finish(entry.key, state, result);
1193
1324
  }
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 };
1325
+ const delivered = options.deliver !== false ? await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry)) : false;
1326
+ this.context?.logger.info("AUN Ability 逐次确认完成", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered, ...(grantId ? { grantId, grantExpiresAt: confirmation.grant?.expiresAt } : {}) });
1327
+ return { ok: true, state: entry.state, result: entry.result, delivered, ...(confirmation.grant ? { grant: confirmation.grant } : {}) };
1197
1328
  }
1198
1329
  abilityApprovalViewKey(aid, ownerAid, threadId) {
1199
1330
  return JSON.stringify([aid, ownerAid, threadId ?? ""]);
@@ -1226,6 +1357,9 @@ class AunPlugin {
1226
1357
  `最晚启动:${new Date(entry.expiresAt).toLocaleString("zh-CN")}`,
1227
1358
  `确认:/ability approve ${entry.key} ${entry.confirmationFingerprint}`,
1228
1359
  `拒绝:/ability reject ${entry.key} ${entry.confirmationFingerprint}`,
1360
+ `允许该 Peer 使用此能力12小时:/ability approve ${entry.key} ${entry.confirmationFingerprint} 12h`,
1361
+ `永久允许该 Peer 使用此能力:/ability approve ${entry.key} ${entry.confirmationFingerprint} permanent`,
1362
+ "持续授权涵盖所示 Peer 对此 Provider 的此项能力的所有合法参数;不是只批准本次参数。可用 /ability grants 查看并撤销。",
1229
1363
  ].join("\n");
1230
1364
  }
1231
1365
  async showAbilityPending(aid, ownerAid, threadId) {
@@ -1241,7 +1375,7 @@ class AunPlugin {
1241
1375
  const viewKey = this.abilityApprovalViewKey(aid, ownerAid, threadId);
1242
1376
  const view = { explicit: true, entries: undefined };
1243
1377
  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回复“确认”或“拒绝”按本清单逐项处理;清单之后的新请求不会加入。`;
1378
+ const text = `待确认 Ability 调用清单(${entries.length} 项,仅包含本条消息展示的固定记录)\n\n${entries.map((entry, index) => `第 ${index + 1} 项\n${this.abilityEntrySummary(entry)}`).join("\n\n---\n\n")}\n\n回复编号(例如 1,2,4)只处理选中项;回复“确认”或“拒绝”处理全部;回复“允许12小时”或“永久允许”为本清单各项授予相应持续授权,覆盖对应能力的所有合法参数。清单之后的新请求不会加入。`;
1245
1379
  await this.sendAbilityControlText(aid, ownerAid, text, threadId);
1246
1380
  if (this.abilityApprovalViews.get(viewKey) === view && this.abilityOperations === store && this.abilityOwners[aid] === ownerAid && this.acceptingMessages)
1247
1381
  view.entries = entries.map(({ key, confirmationFingerprint }) => ({ key, confirmationFingerprint }));
@@ -1250,15 +1384,20 @@ class AunPlugin {
1250
1384
  const raw = payload && typeof payload === "object" ? payload : undefined;
1251
1385
  const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
1252
1386
  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;
1387
+ const isCommand = /^ *\/ability(?:\s|$)/.test(text);
1256
1388
  const ownerAid = this.abilityOwners[aid];
1257
1389
  const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
1258
1390
  const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
1391
+ const threadId = typeof body?.thread_id === "string" ? body.thread_id : undefined;
1392
+ const isSelectionForView = Boolean(ownerAid && sender === ownerAid && (0, ability_control_1.isAbilitySelectionText)(text)
1393
+ && this.abilityApprovalViews.has(this.abilityApprovalViewKey(aid, ownerAid, threadId)));
1394
+ const isReply = !isCommand && !(0, ability_control_1.isAbilitySelectionText)(text) && (0, ability_control_1.parseAbilityControl)(text) !== undefined;
1395
+ const isControlText = isCommand || isReply || isSelectionForView;
1396
+ if (!isControlText)
1397
+ return false;
1259
1398
  if (ownerAid && sender !== ownerAid)
1260
- return text === "确认" || text === "拒绝" ? false : true;
1261
- if (!ownerAid && (text === "确认" || text === "拒绝"))
1399
+ return !isReply && !isSelectionForView;
1400
+ if (!ownerAid && (isReply || isSelectionForView))
1262
1401
  return false;
1263
1402
  let authenticated = false;
1264
1403
  const context = this.context;
@@ -1277,30 +1416,42 @@ class AunPlugin {
1277
1416
  if (!this.isAbilityOwnerVerified(aid, ownerAid))
1278
1417
  await this.saveAbilityOwnerVerification(aid, ownerAid);
1279
1418
  const { command, peerId, threadId } = message;
1280
- if (command.action === "confirm-view" || command.action === "reject-view") {
1419
+ if (command.action === "confirm-view" || command.action === "reject-view" || command.action === "grant-view" || command.action === "select-view") {
1281
1420
  const key = this.abilityApprovalViewKey(aid, ownerAid, threadId);
1282
1421
  const view = this.abilityApprovalViews.get(key);
1283
- this.abilityApprovalViews.delete(key);
1422
+ if (command.action !== "select-view")
1423
+ this.abilityApprovalViews.delete(key);
1284
1424
  if (!view?.entries?.length) {
1285
- await this.sendAbilityControlText(aid, peerId, "当前话题没有已展示的待确认清单。请先发送 /ability pending,核对清单后再回复确认或拒绝。", threadId);
1425
+ await this.sendAbilityControlText(aid, peerId, "当前话题没有完整的待确认清单,本次尚未批准任何调用。下面重新发送清单,请核对后再回复“确认”、“拒绝”、“允许12小时”或“永久允许”。", threadId);
1426
+ await this.showAbilityPending(aid, peerId, threadId);
1286
1427
  return true;
1287
1428
  }
1288
- const approved = command.action === "confirm-view";
1429
+ const selected = command.action === "select-view" ? command.indexes.map(index => view.entries[index - 1]) : view.entries;
1430
+ if (selected.some(entry => !entry)) {
1431
+ await this.sendAbilityControlText(aid, peerId, `Ability 清单编号无效:请输入 1 到 ${view.entries.length} 之间的编号,可用逗号或空格分隔。`, threadId);
1432
+ return true;
1433
+ }
1434
+ this.abilityApprovalViews.delete(key);
1435
+ const approved = command.action !== "reject-view";
1289
1436
  const results = [];
1290
- for (const entry of view.entries) {
1437
+ for (const entry of selected) {
1291
1438
  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)}`);
1439
+ const result = await this.decideAbilityOperation(entry.key, entry.confirmationFingerprint, approved, authorized, aid, command.action === "grant-view" ? { grantDurationMs: command.grantDurationMs } : {});
1440
+ results.push(`${entry.key.slice(0, 12)}:${String(result.state)}${result.grant ? `;已${result.grant.expiresAt === null ? "永久授权" : "授权12小时"},发送 /ability grants 查看期限和撤销命令` : ""}`);
1294
1441
  }
1295
1442
  catch (error) {
1296
1443
  results.push(`${entry.key.slice(0, 12)}:未处理(${safeError(error)})`);
1297
1444
  }
1298
1445
  }
1299
- await this.sendAbilityControlText(aid, peerId, `${approved ? "确认" : "拒绝"}处理完成:\n${results.join("\n")}`, threadId);
1446
+ const remaining = command.action === "select-view" ? view.entries.filter((entry, index) => !command.indexes.includes(index + 1)) : [];
1447
+ if (remaining.length && this.abilityOperations === store && this.abilityOwners[aid] === ownerAid && this.acceptingMessages)
1448
+ this.abilityApprovalViews.set(key, { explicit: true, entries: remaining });
1449
+ const remainingChoices = remaining.map((entry, index) => `${index + 1}(原第 ${view.entries.indexOf(entry) + 1} 项)`).join("、");
1450
+ await this.sendAbilityControlText(aid, peerId, `${command.action === "select-view" ? "选中项" : approved ? "确认" : "拒绝"}处理完成:\n${results.join("\n")}${remaining.length ? `\n仍有 ${remaining.length} 项待处理,请按剩余项重新编号回复:${remainingChoices}。` : ""}`, threadId);
1300
1451
  return true;
1301
1452
  }
1302
1453
  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);
1454
+ await this.sendAbilityControlText(aid, peerId, "AUN Ability 管理命令:\n/ability list:查看当前 Provider 的能力\n/ability publish <路径...>:按完整路径列表发布\n/ability revoke:撤销本 Provider 的 Ability 索引\n/ability pending:展示固定待确认清单\n/ability approve <操作键> <确认指纹>:批准单项\n/ability reject <操作键> <确认指纹>:拒绝单项\n/ability approve <操作键> <确认指纹> 12h:授权所示 Peer 使用此能力12小时(所有合法参数)\n/ability approve <操作键> <确认指纹> permanent:永久授权所示 Peer 使用此能力(所有合法参数)\n/ability grants:查看持续授权及到期时间\n/ability revoke-grant <授权ID>:撤销持续授权\n也可在清单投递成功后回复编号(如 1,2,4)批准选中项;回复“确认”“拒绝”处理全部,或回复“允许12小时”“永久允许”授予持续授权。", threadId);
1304
1455
  }
1305
1456
  else if (command.action === "list") {
1306
1457
  const definitions = await this.abilityDefinitions(aid);
@@ -1319,9 +1470,23 @@ class AunPlugin {
1319
1470
  }
1320
1471
  else if (command.action === "pending")
1321
1472
  await this.showAbilityPending(aid, peerId, threadId);
1473
+ else if (command.action === "grants") {
1474
+ if (!store || this.abilityAuditUnavailable)
1475
+ throw new Error("持久操作账本不可用");
1476
+ const grants = await store.listGrants(aid);
1477
+ const lines = grants.map(grant => `${grant.revokedAt !== undefined ? "已撤销" : grant.expiresAt !== null && grant.expiresAt <= Date.now() ? "已到期" : "有效"} | ${grant.authenticatedCaller} | ${grant.path}\n授权ID:${grant.id}\n到期:${grant.expiresAt === null ? "永久(可随时撤销)" : new Date(grant.expiresAt).toLocaleString("zh-CN")}\n撤销:/ability revoke-grant ${grant.id}`);
1478
+ await this.sendAbilityControlText(aid, peerId, lines.length ? lines.join("\n\n") : "当前没有持续授权。", threadId);
1479
+ }
1480
+ else if (command.action === "revoke-grant") {
1481
+ if (!store || this.abilityAuditUnavailable || !authorized())
1482
+ throw new Error("持久操作账本或 Owner 会话不可用");
1483
+ await store.revokeGrant(aid, command.grantId);
1484
+ this.context?.logger.info("AUN Ability 持续授权已撤销", { aid, ownerAid, grantId: command.grantId });
1485
+ await this.sendAbilityControlText(aid, peerId, `持续授权已撤销:${command.grantId}。此后新调用将重新审批。`, threadId);
1486
+ }
1322
1487
  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);
1488
+ const result = await this.decideAbilityOperation(command.key, command.confirmationFingerprint, command.action === "approve", authorized, aid, { grantDurationMs: command.grantDurationMs });
1489
+ await this.sendAbilityControlText(aid, peerId, `Ability 操作状态:${String(result.state)};结果已${result.delivered ? "回传 Peer" : "保存,等待重投"}。${result.grant ? `已${result.grant.expiresAt === null ? "永久授权" : "授权12小时"},请发送 /ability grants 查看期限和撤销命令。` : ""}`, threadId);
1325
1490
  }
1326
1491
  }
1327
1492
  catch (error) {
@@ -1491,8 +1656,8 @@ class AunPlugin {
1491
1656
  }
1492
1657
  const command = input.argv[0];
1493
1658
  const legacyLocalCommand = !this.context?.managementActor && ["directory", "repair-owner", "repair-old-aids"].includes(command) && isLoopback(request.socket?.remoteAddress);
1494
- if (["fs", "directory", "repair-owner", "repair-old-aids"].includes(command) && !this.context?.managementActor?.(request) && !legacyLocalCommand)
1495
- throw new AunCommandError("AUN 文件、名录及归属命令需要本机或已认证的管理身份", 403, { code: "MANAGEMENT_AUTH_REQUIRED" });
1659
+ if (["fs", "directory", "repair-owner", "repair-old-aids", "agentmd"].includes(command) && !this.context?.managementActor?.(request) && !legacyLocalCommand)
1660
+ throw new AunCommandError("AUN 文件、名片、名录及归属命令需要本机或已认证的管理身份", 403, { code: "MANAGEMENT_AUTH_REQUIRED" });
1496
1661
  const result = await this.executeCommand(input.argv, controller.signal);
1497
1662
  const batchFailed = command === "repair-old-aids" && !!result.data && typeof result.data === "object" && Number(result.data.failed) > 0;
1498
1663
  this.commandResult(response, batchFailed ? 502 : 200, !batchFailed, result.message, result.data);
@@ -1508,6 +1673,22 @@ class AunPlugin {
1508
1673
  };
1509
1674
  async executeCommand(argv, signal) {
1510
1675
  const [command, ...args] = argv;
1676
+ if (command === "agentmd") {
1677
+ if (!args.length || args.length === 1 && ["help", "--help"].includes(args[0]))
1678
+ return { message: cliCommands.find(item => item.name === "agentmd").usage };
1679
+ const [verb, aid, ...values] = args;
1680
+ if (!aidPattern.test(aid ?? ""))
1681
+ throw new AunCommandError("需要有效的目标 AID");
1682
+ const port = this.agentMdPort(aid);
1683
+ let data;
1684
+ try {
1685
+ data = await this.agentMdMenu.local(aid, verb, values, { ...port, current: () => !signal?.aborted && port.current() });
1686
+ }
1687
+ catch (error) {
1688
+ throw new AunCommandError(safeError(error), 409, { code: typeof error.code === "string" ? error.code : "REQUEST_FAILED" });
1689
+ }
1690
+ return { message: verb === "get" ? String(data.content) : "名片已发布并通过远端读回确认", data };
1691
+ }
1511
1692
  if (command === "directory")
1512
1693
  return this.directoryCommand(args);
1513
1694
  if (command === "repair-owner")
@@ -1618,8 +1799,8 @@ class AunPlugin {
1618
1799
  let task;
1619
1800
  let operationError;
1620
1801
  if (verified.ownership.status !== "consensus") {
1621
- const local = this.store?.load(ownerAid);
1622
- const connectedOwner = this.clients.get(ownerAid)?.client;
1802
+ const local = this.listStoreAids().includes(ownerAid) ? this.store?.load(ownerAid) : undefined;
1803
+ const connectedOwner = this.ownerClients.get(ownerAid)?.client ?? this.clients.get(ownerAid)?.client;
1623
1804
  let temporary;
1624
1805
  try {
1625
1806
  if (connectedOwner || local?.ok && local.data) {
@@ -1766,7 +1947,7 @@ class AunPlugin {
1766
1947
  await this.requireAgent(agent);
1767
1948
  }
1768
1949
  const created = await this.createIdentity(aid, owner);
1769
- const data = { created: true, aid, connection: created.connection ?? "ready", directorySync: created.directorySync };
1950
+ const data = { created: true, aid, connection: created.connection ?? "ready", directorySync: created.directorySync, ...(created.status === "等待 Owner 确认" ? { status: created.status } : {}) };
1770
1951
  if (created.connection !== "ready")
1771
1952
  throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
1772
1953
  if (agent) {
@@ -1779,9 +1960,10 @@ class AunPlugin {
1779
1960
  }
1780
1961
  }
1781
1962
  return {
1782
- message: created.directorySync?.status === "not_synced"
1783
- ? `AID 已创建${agent ? `并关联到 ${agent}` : "并连接"},但 Owner 名录未同步:${created.directorySync.error ?? "原因未知"}`
1784
- : agent ? `AID 已创建并关联到 ${agent}` : "AID 已创建并连接",
1963
+ message: created.status === "等待 Owner 确认" ? `AID 已创建并连接${agent ? `并关联到 ${agent}` : ""},关联请求已发送,等待 Owner 确认`
1964
+ : created.directorySync?.status === "not_synced"
1965
+ ? `AID 已创建${agent ? `并关联到 ${agent}` : "并连接"},但 Owner 名录未同步:${created.directorySync.error ?? "原因未知"}`
1966
+ : agent ? `AID 已创建并关联到 ${agent}` : "AID 已创建并连接",
1785
1967
  data: { ...data, ...(agent ? { agent } : {}) },
1786
1968
  };
1787
1969
  }
@@ -1790,6 +1972,8 @@ class AunPlugin {
1790
1972
  return await this.identity.create(aid, owner);
1791
1973
  }
1792
1974
  catch (error) {
1975
+ if (!this.isBridgeAid(aid) && this.listStoreAids().includes(aid))
1976
+ throw new AunCommandError("EvolCore 来源身份不能由本工程创建或连接,请使用新的 AID");
1793
1977
  if (this.identities.has(aid) || this.listStoreAids().includes(aid)) {
1794
1978
  throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
1795
1979
  }
@@ -1839,6 +2023,25 @@ class AunPlugin {
1839
2023
  throw new AunCommandError("本机控制 API 响应未确认操作成功", 502);
1840
2024
  return result;
1841
2025
  }
2026
+ async runtimeMenuSnapshot(aid) {
2027
+ const pluginId = this.bindings[aid];
2028
+ if (!pluginId)
2029
+ throw new Error("当前身份未关联 Agent");
2030
+ const keys = [...this.activeRuntimeSources].map(key => JSON.parse(key)).filter(key => key[0] === aid);
2031
+ let config = {};
2032
+ try {
2033
+ config = await this.controlPayload(`/api/plugins/${encodeURIComponent(pluginId)}/config`);
2034
+ }
2035
+ catch { /* 无法读取配置时明确展示客户端管理,不猜测模型。 */ }
2036
+ const text = (value) => typeof value === "string" && value.trim() && value.length <= 256 && !value.includes("******") ? value.trim() : undefined;
2037
+ const desktop = !["personal-assistant", "claude-cli", "codex-cli"].includes(pluginId);
2038
+ const delegated = pluginId === "personal-assistant" && config.modelProvider !== "openai" && config.modelProvider !== undefined;
2039
+ const configured = !desktop && !delegated ? text(config.model) : undefined;
2040
+ const model = configured ?? (delegated ? `由 ${text(config.modelPlugin) ?? text(config.modelProvider) ?? "模型来源"} 管理` : desktop ? "由桌面客户端管理(未上报具体模型)" : pluginId === "personal-assistant" ? "模型配置未上报" : "使用 CLI 默认模型(未上报具体模型)");
2041
+ return { status: this.context?.pluginStatus(pluginId) === "ready" ? "running" : this.context?.pluginStatus(pluginId) ?? "unknown",
2042
+ connected: this.clients.has(aid), activePeers: new Set(keys.map(key => key[1])).size, activeSessions: keys.length,
2043
+ model, effort: "由客户端管理(未上报推理强度)", source: configured ? "agent" : "client" };
2044
+ }
1842
2045
  async controlPayload(path, method = "GET", body) {
1843
2046
  const base = this.controlUrl();
1844
2047
  let response;
@@ -1932,6 +2135,17 @@ class AunPlugin {
1932
2135
  }
1933
2136
  if (await this.peerMessages?.accept(aid, payload))
1934
2137
  return;
2138
+ const control = raw?.payload && typeof raw.payload === "object" ? raw.payload : undefined;
2139
+ if (typeof control?.type === "string" && control.type.startsWith("menu.")) {
2140
+ if (raw && await this.menu?.accept(aid, raw))
2141
+ return;
2142
+ await this.onAgentMdMenu(payload, aid);
2143
+ return;
2144
+ }
2145
+ if (await this.onAgentMdText(raw, aid))
2146
+ return;
2147
+ if (await this.onNaturalProfile(raw, aid))
2148
+ return;
1935
2149
  if (!agentPlugin) {
1936
2150
  this.context?.logger.warn("AUN 入站消息已忽略", { traceId: inboundId, aid, stage: "绑定检查", reason: "未绑定 Agent" });
1937
2151
  return;
@@ -1940,6 +2154,8 @@ class AunPlugin {
1940
2154
  const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
1941
2155
  const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
1942
2156
  const inboundText = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
2157
+ if (raw && await this.menu?.accept(aid, raw))
2158
+ return;
1943
2159
  const inboundThreadId = typeof body?.thread_id === "string" ? body.thread_id : undefined;
1944
2160
  if (inboundText === "确认" && this.ownerProof.hasPendingConfirmation(aid)) {
1945
2161
  const ownerAid = this.abilityOwners[aid];
@@ -2035,6 +2251,10 @@ class AunPlugin {
2035
2251
  }
2036
2252
  if (await this.onFsMessage(raw, message, aid))
2037
2253
  return;
2254
+ if (this.menu && !await this.menu.allowsChat(aid, message.peerId)) {
2255
+ this.context?.logger.info("AUN 联系人准入策略已拒绝消息", { aid, peer: message.peerId });
2256
+ return;
2257
+ }
2038
2258
  const sourceKey = JSON.stringify([aid, message.peerId, message.threadId ?? ""]);
2039
2259
  const pending = this.pendingMessages.get(sourceKey);
2040
2260
  if (pending && !message.attachments?.length && !message.attachmentError && newSessionCommand.test(message.text.trim())) {
@@ -2071,11 +2291,22 @@ class AunPlugin {
2071
2291
  .finally(() => { notices.delete(notice); });
2072
2292
  notices.add(notice);
2073
2293
  };
2074
- const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => {
2294
+ const current = (pending ?? Promise.resolve()).catch(() => undefined).then(async () => {
2075
2295
  clearInterval(queueTimer);
2296
+ if (pending && this.menu && !await this.menu.allowsChat(aid, message.peerId))
2297
+ return;
2076
2298
  if (pending)
2077
2299
  reportQueue(true);
2078
- return this.processMessage(message, aid, agentPlugin, inboundId);
2300
+ const isPrivate = !raw?.group_id && !raw?.group_aid && raw?.chat_type !== "group" && !envelope?.group_id && !envelope?.group_aid && envelope?.chat_type !== "group" && aidPattern.test(message.peerId);
2301
+ if (isPrivate)
2302
+ this.activeRuntimeSources.add(sourceKey);
2303
+ try {
2304
+ return await this.processMessage(message, aid, agentPlugin, inboundId);
2305
+ }
2306
+ finally {
2307
+ if (isPrivate)
2308
+ this.activeRuntimeSources.delete(sourceKey);
2309
+ }
2079
2310
  });
2080
2311
  this.pendingMessages.set(sourceKey, current);
2081
2312
  if (pending) {
@@ -2149,6 +2380,125 @@ class AunPlugin {
2149
2380
  }
2150
2381
  return true;
2151
2382
  }
2383
+ agentMdPort(aid) {
2384
+ const connection = this.clients.get(aid);
2385
+ const store = this.store;
2386
+ if (!connection || !store)
2387
+ throw new AunCommandError("目标 AID 当前未连接");
2388
+ const context = this.context;
2389
+ const binding = this.bindings[aid];
2390
+ const current = () => this.acceptingMessages && this.context === context && this.clients.get(aid) === connection && this.store === store && this.bindings[aid] === binding;
2391
+ return {
2392
+ current,
2393
+ read: async () => {
2394
+ const result = await (0, ownership_1.readAunOwnership)(store, connection.client, aid);
2395
+ return { content: result.agentMd.content, etag: result.agentMd.etag ?? "", ownership: result.ownership };
2396
+ },
2397
+ parse: content => {
2398
+ if (!this.sdk?.validateAgentMdDocument)
2399
+ throw Object.assign(new Error("SDK 不支持名片校验"), { code: "NOT_SUPPORTED" });
2400
+ return this.sdk.validateAgentMdDocument(content, { expectedAid: aid, validateOwnership: false });
2401
+ },
2402
+ upload: async (content, sourceGuard) => {
2403
+ if (!store.uploadAgentMd)
2404
+ throw Object.assign(new Error("SDK 不支持名片上传"), { code: "NOT_SUPPORTED" });
2405
+ const result = await store.uploadAgentMd(aid, content, { sourceGuard });
2406
+ if (!result.ok)
2407
+ throw Object.assign(new Error("SDK 名片上传未完成"), { code: result.error?.code === "conflict" ? "REMOTE_VERSION_CONFLICT" : "PUBLISH_NOT_CONFIRMED" });
2408
+ },
2409
+ };
2410
+ }
2411
+ async onAgentMdMenu(value, aid) {
2412
+ const port = this.agentMdPort(aid);
2413
+ const response = await this.agentMdMenu.handle(value, aid, port);
2414
+ if (!response || !port.current())
2415
+ return;
2416
+ const event = value;
2417
+ const envelope = event.envelope && typeof event.envelope === "object" ? event.envelope : undefined;
2418
+ const peer = typeof event.from === "string" ? event.from : envelope?.from;
2419
+ if (typeof peer !== "string" || !aidPattern.test(peer) || event.encrypted !== true || (envelope?.from !== undefined && envelope.from !== peer))
2420
+ return;
2421
+ try {
2422
+ await this.callAunWithRecovery(aid, { to: peer, encrypt: true, payload: response, ...(typeof event.sender_device_id === "string" ? { to_device_id: event.sender_device_id } : {}) });
2423
+ }
2424
+ catch (error) {
2425
+ this.context?.logger.warn("AUN 名片 Menu 响应未确认,请刷新远端状态", { aid, peer, error: safeError(error) });
2426
+ }
2427
+ }
2428
+ async onNaturalProfile(event, receiverAid) {
2429
+ if (!event || this.bindings[receiverAid] !== "personal-assistant")
2430
+ return false;
2431
+ const body = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : undefined;
2432
+ if (body?.type !== undefined && body.type !== "text")
2433
+ return false;
2434
+ const text = typeof event.payload === "string" ? event.payload : typeof body?.text === "string" ? body.text : "";
2435
+ const command = (0, agentmd_natural_1.parseNaturalProfileCommand)(text);
2436
+ if (!command)
2437
+ return false;
2438
+ const envelope = event.envelope && typeof event.envelope === "object" ? event.envelope : undefined;
2439
+ const peer = typeof event.from === "string" ? event.from : envelope?.from;
2440
+ if (typeof peer !== "string" || !aidPattern.test(peer) || event.encrypted !== true || (envelope?.from !== undefined && envelope.from !== peer) || (event.to !== undefined && event.to !== receiverAid) || (envelope?.to !== undefined && envelope.to !== receiverAid) || event.group_id || envelope?.group_id || event.group_aid || envelope?.group_aid || event.chat_type === "group" || envelope?.chat_type === "group")
2441
+ return true;
2442
+ let response;
2443
+ try {
2444
+ if (body?.attachments !== undefined || Buffer.byteLength(text) > 16 * 1024)
2445
+ throw new Error("名片设置请使用不带附件的简短文字");
2446
+ // 先核验当前用户对个人助理的权限,不使用消息正文自报身份。
2447
+ const receiver = this.agentMdPort(receiverAid);
2448
+ await this.agentMdMenu.text(event, receiverAid, "get", [], receiver);
2449
+ const matches = command.target.includes(".") ? [...this.clients.keys()].filter(aid => aid === command.target) : [...this.clients.keys()].filter(aid => aid.split(".")[0] === command.target);
2450
+ if (matches.length !== 1)
2451
+ throw new Error(matches.length ? "目标名称有歧义,请提供完整 AID" : "目标未连接,请提供本机已连接 Agent 的准确 AID");
2452
+ const target = matches[0];
2453
+ const port = this.agentMdPort(target);
2454
+ const data = await this.agentMdMenu.text(event, target, command.verb, command.values, { ...port, current: () => receiver.current() && port.current() }, receiverAid);
2455
+ response = command.verb === "get" ? `${target} 当前名片:\n${data.content}` : `已更新 ${target} 的${command.values[0] === "description" ? "描述" : command.values[0] === "name" ? "名称" : "能力介绍"},并通过远端验签读回确认。\n\n${command.values[1]}\n\n请在 App 刷新该 Agent 的资料页查看。`;
2456
+ }
2457
+ catch (error) {
2458
+ response = `名片设置未完成:${safeError(error)}。没有确认成功前,请先查看目标名片,不要重复提交。`;
2459
+ }
2460
+ try {
2461
+ await this.sendText(receiverAid, peer, response, typeof body?.thread_id === "string" ? body.thread_id : undefined, true);
2462
+ }
2463
+ catch (error) {
2464
+ this.context?.logger.warn("个人助理名片设置回复未确认", { receiverAid, peer, error: safeError(error) });
2465
+ }
2466
+ return true;
2467
+ }
2468
+ async onAgentMdText(event, aid) {
2469
+ if (!event)
2470
+ return false;
2471
+ const body = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : undefined;
2472
+ if (body?.type !== undefined && body.type !== "text")
2473
+ return false;
2474
+ const text = typeof event.payload === "string" ? event.payload : typeof body?.text === "string" ? body.text : "";
2475
+ if (!/^(?:ec[ \t]+aid|mab[ \t]+aun)[ \t]+agentmd(?:[ \t]+|$)/.test(text))
2476
+ return false;
2477
+ const envelope = event.envelope && typeof event.envelope === "object" ? event.envelope : undefined;
2478
+ const peer = typeof event.from === "string" ? event.from : envelope?.from;
2479
+ if (typeof peer !== "string" || !aidPattern.test(peer) || event.encrypted !== true || (envelope?.from !== undefined && envelope.from !== peer) || event.group_id || envelope?.group_id || event.chat_type === "group" || envelope?.chat_type === "group")
2480
+ return true;
2481
+ let response;
2482
+ try {
2483
+ if (Buffer.byteLength(text) > 16 * 1024 || body?.attachments !== undefined)
2484
+ throw new Error("名片命令不接受附件,且不能超过 16 KiB");
2485
+ const command = (0, agentmd_command_1.parseAgentMdCommand)(text);
2486
+ if (command.aid !== aid)
2487
+ throw new Error("名片命令只能操作当前聊天的 Agent AID");
2488
+ const data = await this.agentMdMenu.text(event, aid, command.verb, command.values, this.agentMdPort(aid));
2489
+ response = command.json ? JSON.stringify({ ok: true, data }, null, 2) : command.verb === "get" ? String(data.content) : "能力介绍与名片已发布,并通过远端读回确认。App 刷新资料后可查看。";
2490
+ }
2491
+ catch (error) {
2492
+ response = `名片命令未完成:${safeError(error)}。请先读取当前名片核对状态。`;
2493
+ }
2494
+ try {
2495
+ await this.sendText(aid, peer, response, typeof body?.thread_id === "string" ? body.thread_id : undefined, true);
2496
+ }
2497
+ catch (error) {
2498
+ this.context?.logger.warn("AUN 名片文本命令回复未确认,请核对远端状态", { aid, peer, error: safeError(error) });
2499
+ }
2500
+ return true;
2501
+ }
2152
2502
  async onAbilityMessage(value, aid) {
2153
2503
  const receivedAt = Date.now();
2154
2504
  const call = (0, ability_operations_1.parseAbilityEnvelope)(value);
@@ -2170,8 +2520,11 @@ class AunPlugin {
2170
2520
  else {
2171
2521
  const definitions = await this.abilityDefinitions(aid);
2172
2522
  const capabilities = this.abilityOwners[aid] && this.abilityOperations && !this.abilityAuditUnavailable && this.context?.callPluginAbility
2173
- ? definitions.filter(definition => (this.abilitySelections[aid] ?? []).includes(definition.path) && this.abilityLedgers[aid]?.[definition.path]?.definition_id === (0, ability_publication_1.abilityDefinitionId)({ ...definition })).map(({ contract_revision: _revision, ...definition }) => ({ ...definition, definition_id: this.abilityLedgers[aid][definition.path].definition_id })) : [];
2174
- result = { type: "ability", role: "result", call_id: payload.call_id, operation_id: payload.operation_id, ability_path: "protocol/discover", ok: true, execution_state: "completed", result: { spec_version: "aid-ability/2", capabilities } };
2523
+ ? definitions.filter(definition => (this.abilitySelections[aid] ?? []).includes(definition.path) && this.abilityLedgers[aid]?.[definition.path]?.definition_id === (0, ability_publication_1.abilityDefinitionId)({ ...definition })).map(({ contract_revision: _revision, ...definition }) => {
2524
+ const published = this.abilityLedgers[aid][definition.path];
2525
+ return { ...definition, definition_id: published.definition_id, release_id: published.release_id, plugin_version: published.plugin_version, detail_ref: `${aid}:/ability/definitions/${published.definition_id}/${definition.path}.json` };
2526
+ }) : [];
2527
+ result = { type: "ability", role: "result", call_id: payload.call_id, operation_id: payload.operation_id, ability_path: "protocol/discover", ok: true, execution_state: "completed", result: { protocol: { spec_version: "aid-ability/2" }, spec_version: "aid-ability/2", capabilities } };
2175
2528
  }
2176
2529
  }
2177
2530
  else {
@@ -2215,8 +2568,20 @@ class AunPlugin {
2215
2568
  else {
2216
2569
  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 }) });
2217
2570
  result = this.abilityOperationResponse(draft.entry);
2218
- if (draft.created)
2219
- ownerNotice = draft.entry;
2571
+ if (draft.created) {
2572
+ const grant = await store.activeGrant(draft.entry);
2573
+ if (grant) {
2574
+ const context = this.context;
2575
+ const authorized = () => this.context === context && this.abilityOperations === store && this.acceptingMessages
2576
+ && !this.revokingAbilityAids.has(aid) && this.clients.has(aid) && this.bindings[aid] === pluginId
2577
+ && this.abilityOwners[aid] === ownerAid && this.isAbilityOwnerVerified(aid, ownerAid);
2578
+ const decision = await this.decideAbilityOperation(draft.entry.key, draft.entry.confirmationFingerprint, true, authorized, aid, { grantId: grant.id, deliver: false });
2579
+ result = decision.result ?? result;
2580
+ this.context?.logger.info("AUN Ability 使用已存在的持续授权", { aid, callerAid, path, operationId: payload.operation_id, grantId: grant.id, stage: "持续授权准入" });
2581
+ }
2582
+ else
2583
+ ownerNotice = draft.entry;
2584
+ }
2220
2585
  }
2221
2586
  }
2222
2587
  }
@@ -2242,7 +2607,7 @@ class AunPlugin {
2242
2607
  const pending = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
2243
2608
  if (pending.length !== 1 && !view.explicit)
2244
2609
  view.entries = undefined;
2245
- const text = `有新的远程 Ability 调用申请\n${this.abilityEntrySummary(entry)}\n\n审批窗口最长 12 小时,不会延长调用最晚启动时间或协议 TTL。\n只有本话题中唯一、已完整投递的申请可直接回复“确认”或“拒绝”;多项、已处理或不确定时先发送 /ability pending 查看固定清单。`;
2610
+ const text = `有新的远程 Ability 调用申请\n${this.abilityEntrySummary(entry)}\n\n审批窗口最长 12 小时,不会延长调用最晚启动时间或协议 TTL。\n只有本话题中唯一、已完整投递的申请可直接回复“确认”或“拒绝”;也可回复“允许12小时”或“永久允许”明确授权该 Peer 对此能力的所有合法参数。多项、已处理或不确定时先发送 /ability pending 查看固定清单,再回复编号(如 1,2,4)处理选中项。`;
2246
2611
  try {
2247
2612
  await this.sendAbilityControlText(entry.providerAid, ownerAid, text);
2248
2613
  const current = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
@@ -2285,6 +2650,8 @@ class AunPlugin {
2285
2650
  return { code: "ability_not_found", message: "绑定的 Agent 不再声明指定能力" };
2286
2651
  if (published.definition_id !== definitionId || definitionId !== (0, ability_publication_1.abilityDefinitionId)({ ...definition }))
2287
2652
  return { code: "contract_changed", message: "能力契约已变更,请重新发现后确认" };
2653
+ if (path === "agent/task/run" && typeof params.task === "string" && !params.task.trim())
2654
+ return { code: "invalid_params", message: "任务参数 task 不能只包含空白" };
2288
2655
  const ajv = new _2020_1.default({ allErrors: true, strict: false });
2289
2656
  const validate = ajv.compile(definition.params_schema);
2290
2657
  if (!validate(params))