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.
- package/README.md +8 -1
- package/dist/src-ts/plugins/aun/ability-control.js +26 -4
- package/dist/src-ts/plugins/aun/ability-operation-store.js +90 -10
- package/dist/src-ts/plugins/aun/agentmd-command.js +67 -0
- package/dist/src-ts/plugins/aun/agentmd-menu.js +222 -0
- package/dist/src-ts/plugins/aun/agentmd-natural.js +17 -0
- package/dist/src-ts/plugins/aun/fs.js +2 -1
- package/dist/src-ts/plugins/aun/index.js +427 -60
- package/dist/src-ts/plugins/aun/menu.js +333 -0
- package/dist/src-ts/plugins/aun/public/app.js +14 -7
- package/dist/src-ts/plugins/aun/runtime-menu.js +52 -0
- package/package.json +1 -1
- package/plugin.json +1 -1
|
@@ -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.
|
|
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
|
-
|
|
343
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 ? "
|
|
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
|
|
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
|
-
|
|
1165
|
-
|
|
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
|
|
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
|
|
1261
|
-
if (!ownerAid && (
|
|
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
|
-
|
|
1422
|
+
if (command.action !== "select-view")
|
|
1423
|
+
this.abilityApprovalViews.delete(key);
|
|
1284
1424
|
if (!view?.entries?.length) {
|
|
1285
|
-
await this.sendAbilityControlText(aid, peerId, "
|
|
1425
|
+
await this.sendAbilityControlText(aid, peerId, "当前话题没有完整的待确认清单,本次尚未批准任何调用。下面重新发送清单,请核对后再回复“确认”、“拒绝”、“允许12小时”或“永久允许”。", threadId);
|
|
1426
|
+
await this.showAbilityPending(aid, peerId, threadId);
|
|
1286
1427
|
return true;
|
|
1287
1428
|
}
|
|
1288
|
-
const
|
|
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
|
|
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
|
-
|
|
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
|
|
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" : "保存,等待重投"}
|
|
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
|
|
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.
|
|
1783
|
-
|
|
1784
|
-
|
|
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
|
-
|
|
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 }) =>
|
|
2174
|
-
|
|
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
|
-
|
|
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
|
|
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))
|