agent-bridge-aun 1.0.48 → 1.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/src-ts/plugins/aun/ability-control.js +81 -0
- package/dist/src-ts/plugins/aun/index.js +340 -132
- package/dist/src-ts/plugins/aun/public/app.js +0 -126
- package/dist/src-ts/plugins/aun/public/index.html +0 -2
- package/dist/src-ts/plugins/aun/public/style.css +0 -8
- package/package.json +1 -1
- package/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -71,4 +71,4 @@ AUN 独立包支持核心后台每五分钟检测更新;可信提供者在自
|
|
|
71
71
|
显式关联已有 EvolCore Agent 的 AID 时,先执行 `ec agent disable <aid>`,再配置 `ecagent` 路由并重载。`ec` 不存在或命令失败时,只将所选 AID 的 JSON 配置更新为 `enabled=false`、`active_baseagent=ecagent` 和对应路由;其它 AID 保持原值。仍有 EvolCore 运行实例时,必须确认重载成功才完成关联,避免原 Claude 与桌面 Agent 同时回复。纯密钥库身份不调用 `ec`,解绑不自动启用原 Agent。
|
|
72
72
|
## Ability Owner 确认
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
能力发布和远程调用审批由配置的 `ability_owners[providerAid]` 对应 Owner 在 AUN 加密私聊中管理。发送 `/ability help` 查看命令;`/ability list` 查看能力,`/ability publish <路径...>` 发布,`/ability revoke` 撤销,`/ability pending` 查看待审批清单。逐项处理使用通知中的 `/ability approve <操作键> <确认指纹>` 或 `/ability reject <操作键> <确认指纹>`。只有一项已送达申请时可直接回复“确认”或“拒绝”;多项需先送达固定清单,再用这两个词批量处理。新申请不会加入已展示清单,插件重载后需重新发送 `/ability pending`。Owner 首次发送有效管理命令时,AUN 验证其已配置 AID 并持久保存验证记录;远端消息不能设置或认领 Owner。能力的网页发布、Owner 验证和待审批区域已从 ABWeb 移除;已认证的本机管理 API 保留用于兼容旧管理客户端。
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseAbilityControl = parseAbilityControl;
|
|
4
|
+
exports.parseAbilityControlMessage = parseAbilityControlMessage;
|
|
5
|
+
const aidPattern = /^[a-z0-9][a-z0-9_-]{2,63}\.[a-z0-9-]+(?:\.[a-z0-9-]+)+$/;
|
|
6
|
+
const pathPattern = /^(?!protocol\/|definitions\/)[a-z0-9]+(?:[a-z0-9._-]*[a-z0-9])?(?:\/[a-z0-9]+(?:[a-z0-9._-]*[a-z0-9])?)+$/;
|
|
7
|
+
const fingerprintPattern = /^[0-9a-f]{64}$/;
|
|
8
|
+
const maxControlTextLength = 4096;
|
|
9
|
+
function parseAbilityControl(text) {
|
|
10
|
+
if (text === "确认")
|
|
11
|
+
return { action: "confirm-view" };
|
|
12
|
+
if (text === "拒绝")
|
|
13
|
+
return { action: "reject-view" };
|
|
14
|
+
const trimmed = text.replace(/^ +| +$/g, "");
|
|
15
|
+
if (!/^\/ability(?:\s|$)/.test(trimmed))
|
|
16
|
+
return undefined;
|
|
17
|
+
if (text.length > maxControlTextLength)
|
|
18
|
+
throw new Error("Ability 管理命令过长,最多允许 4096 个字符");
|
|
19
|
+
if (!/^[a-z0-9./ _-]+$/.test(trimmed))
|
|
20
|
+
throw new Error("Ability 管理命令格式无效:请发送单行完整命令,仅使用空格分隔参数");
|
|
21
|
+
const [, action, ...args] = trimmed.split(/ +/);
|
|
22
|
+
if (["help", "list", "revoke", "pending"].includes(action) && args.length === 0)
|
|
23
|
+
return { action: action };
|
|
24
|
+
if (action === "publish") {
|
|
25
|
+
if (!args.length)
|
|
26
|
+
throw new Error("Ability 发布命令缺少路径参数");
|
|
27
|
+
if (args.some(path => path.length > 512 || !pathPattern.test(path)))
|
|
28
|
+
throw new Error("Ability 路径格式无效:必须符合插件 Ability path 规则且不能使用协议保留命名空间");
|
|
29
|
+
if (new Set(args).size !== args.length)
|
|
30
|
+
throw new Error("Ability 发布路径不能重复");
|
|
31
|
+
return { action, paths: args };
|
|
32
|
+
}
|
|
33
|
+
if ((action === "approve" || action === "reject") && args.length === 2) {
|
|
34
|
+
if (!args.every(value => fingerprintPattern.test(value)))
|
|
35
|
+
throw new Error("Ability 操作键与确认指纹格式无效:必须是完整的 64 位小写十六进制摘要");
|
|
36
|
+
return { action, key: args[0], confirmationFingerprint: args[1] };
|
|
37
|
+
}
|
|
38
|
+
throw new Error("Ability 管理命令或参数数量不正确,请发送 /ability help 查看用法");
|
|
39
|
+
}
|
|
40
|
+
function record(value) {
|
|
41
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
42
|
+
}
|
|
43
|
+
function parseAbilityControlMessage(value, providerAid, ownerAid) {
|
|
44
|
+
const raw = record(value);
|
|
45
|
+
if (!raw)
|
|
46
|
+
return undefined;
|
|
47
|
+
const body = record(raw.payload);
|
|
48
|
+
const text = typeof raw.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
49
|
+
const command = parseAbilityControl(text);
|
|
50
|
+
if (!command)
|
|
51
|
+
return undefined;
|
|
52
|
+
const envelope = record(raw.envelope);
|
|
53
|
+
if (raw.envelope !== undefined && !envelope)
|
|
54
|
+
throw new Error("Ability 管理消息的认证信封格式无效");
|
|
55
|
+
const peerId = typeof raw.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : "";
|
|
56
|
+
if (!aidPattern.test(peerId) || peerId === providerAid || !aidPattern.test(providerAid))
|
|
57
|
+
throw new Error("Ability 管理消息来源必须是该 Provider 配置的 Owner 身份");
|
|
58
|
+
for (const fields of [raw, body, envelope]) {
|
|
59
|
+
if (!fields)
|
|
60
|
+
continue;
|
|
61
|
+
if ([fields.from, fields.sender_aid].some(sender => sender !== undefined && sender !== peerId))
|
|
62
|
+
throw new Error("Ability 管理消息来源与认证信封身份不一致");
|
|
63
|
+
if (fields.to !== undefined && fields.to !== providerAid)
|
|
64
|
+
throw new Error("Ability 管理消息收件者与当前 Provider 不一致");
|
|
65
|
+
}
|
|
66
|
+
const privateMessage = [raw, body, envelope].every(fields => !fields || (fields.group_id === undefined && fields.group_aid === undefined
|
|
67
|
+
&& (fields.chat_type === undefined || ["private", "direct", "p2p", "dm"].includes(String(fields.chat_type)))));
|
|
68
|
+
if (raw.encrypted !== true || !privateMessage)
|
|
69
|
+
throw new Error("Ability 管理命令仅接受 Owner 的加密私聊消息");
|
|
70
|
+
if ((raw.type !== undefined && !["text", "message"].includes(String(raw.type))) || (body?.type !== undefined && body.type !== "text")
|
|
71
|
+
|| raw.attachments !== undefined || body?.attachments !== undefined)
|
|
72
|
+
throw new Error("Ability 管理命令必须是无附件的纯文本消息,通知和错误不能作为命令");
|
|
73
|
+
if (!ownerAid || !aidPattern.test(ownerAid))
|
|
74
|
+
throw Object.assign(new Error("尚未配置有效的 Ability Owner,请先在本机配置该 Provider 的 Owner AID"), { code: "ability_owner_unconfigured" });
|
|
75
|
+
if (peerId !== ownerAid)
|
|
76
|
+
throw new Error("Ability 管理消息来源必须是该 Provider 配置的 Owner 身份");
|
|
77
|
+
return { command, peerId,
|
|
78
|
+
...(typeof raw.message_id === "string" ? { messageId: raw.message_id } : {}),
|
|
79
|
+
...(typeof body?.thread_id === "string" ? { threadId: body.thread_id } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -20,6 +20,7 @@ const ability_publication_1 = require("./ability-publication");
|
|
|
20
20
|
const ability_operations_1 = require("./ability-operations");
|
|
21
21
|
const ability_operation_store_1 = require("./ability-operation-store");
|
|
22
22
|
const ability_owner_1 = require("./ability-owner");
|
|
23
|
+
const ability_control_1 = require("./ability-control");
|
|
23
24
|
const fs_1 = require("./fs");
|
|
24
25
|
const fs_commands_1 = require("./fs-commands");
|
|
25
26
|
const fs_transfer_1 = require("./fs-transfer");
|
|
@@ -30,7 +31,7 @@ const ownership_1 = require("./ownership");
|
|
|
30
31
|
const sdk_loader_1 = require("./sdk-loader");
|
|
31
32
|
const owned_profiles_1 = require("./owned-profiles");
|
|
32
33
|
const manifest = {
|
|
33
|
-
id: "aun", version: "1.0.
|
|
34
|
+
id: "aun", version: "1.0.49", type: "channel", displayName: "AUN 通道",
|
|
34
35
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
35
36
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
36
37
|
configSchema: "config.schema.json",
|
|
@@ -108,6 +109,7 @@ class AunPlugin {
|
|
|
108
109
|
abilityLedgers = {};
|
|
109
110
|
abilityOwners = {};
|
|
110
111
|
abilityOwnerVerifications = {};
|
|
112
|
+
abilityOwnerVerificationWrites = new Map();
|
|
111
113
|
abilityOperations;
|
|
112
114
|
peerMessages;
|
|
113
115
|
abilityAuditUnavailable = false;
|
|
@@ -115,6 +117,7 @@ class AunPlugin {
|
|
|
115
117
|
abilityPublications = new Map();
|
|
116
118
|
revokingAbilityAids = new Set();
|
|
117
119
|
abilityRevisions = new Map();
|
|
120
|
+
abilityApprovalViews = new Map();
|
|
118
121
|
creating = false;
|
|
119
122
|
repairingOwners = new Set();
|
|
120
123
|
unregister = [];
|
|
@@ -516,18 +519,38 @@ class AunPlugin {
|
|
|
516
519
|
await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
|
|
517
520
|
}
|
|
518
521
|
async saveAbilityOwnerVerification(aid, ownerAid) {
|
|
519
|
-
const
|
|
520
|
-
|
|
522
|
+
const pending = this.abilityOwnerVerificationWrites.get(aid);
|
|
523
|
+
if (pending)
|
|
524
|
+
return pending;
|
|
525
|
+
if (this.isAbilityOwnerVerified(aid, ownerAid))
|
|
526
|
+
return;
|
|
527
|
+
const operation = Promise.resolve().then(async () => {
|
|
528
|
+
if (this.abilityOwners[aid] !== ownerAid)
|
|
529
|
+
throw new Error("Provider 的 Owner 配置已改变");
|
|
530
|
+
const previous = this.abilityOwnerVerifications[aid];
|
|
531
|
+
this.abilityOwnerVerifications = { ...this.abilityOwnerVerifications, [aid]: ownerAid };
|
|
532
|
+
try {
|
|
533
|
+
await this.persist();
|
|
534
|
+
}
|
|
535
|
+
catch (error) {
|
|
536
|
+
if (previous === undefined)
|
|
537
|
+
delete this.abilityOwnerVerifications[aid];
|
|
538
|
+
else
|
|
539
|
+
this.abilityOwnerVerifications[aid] = previous;
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
});
|
|
543
|
+
this.abilityOwnerVerificationWrites.set(aid, operation);
|
|
521
544
|
try {
|
|
522
|
-
await
|
|
545
|
+
await operation;
|
|
523
546
|
}
|
|
524
|
-
|
|
525
|
-
this.
|
|
526
|
-
|
|
547
|
+
finally {
|
|
548
|
+
if (this.abilityOwnerVerificationWrites.get(aid) === operation)
|
|
549
|
+
this.abilityOwnerVerificationWrites.delete(aid);
|
|
527
550
|
}
|
|
528
551
|
}
|
|
529
552
|
isAbilityOwnerVerified(aid, ownerAid) {
|
|
530
|
-
return this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
|
|
553
|
+
return !this.abilityOwnerVerificationWrites.has(aid) && this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
|
|
531
554
|
}
|
|
532
555
|
async activate(context) {
|
|
533
556
|
this.context = context;
|
|
@@ -570,6 +593,7 @@ class AunPlugin {
|
|
|
570
593
|
this.sdk ??= await loadSdk();
|
|
571
594
|
await (0, promises_1.mkdir)(this.path(), { recursive: true });
|
|
572
595
|
this.ownerProof.clear();
|
|
596
|
+
this.abilityApprovalViews.clear();
|
|
573
597
|
this.abilityAuditUnavailable = false;
|
|
574
598
|
try {
|
|
575
599
|
this.abilityOperations = await ability_operation_store_1.AbilityOperationStore.open((0, node_path_1.join)(context.dataDir, "ability-operations.json"));
|
|
@@ -806,6 +830,7 @@ class AunPlugin {
|
|
|
806
830
|
clearInterval(this.ownedProfileTimer);
|
|
807
831
|
this.ownedProfileTimer = undefined;
|
|
808
832
|
this.ownerProof.clear();
|
|
833
|
+
this.abilityApprovalViews.clear();
|
|
809
834
|
for (const { subscription, ownedSubscription } of this.clients.values()) {
|
|
810
835
|
subscription?.unsubscribe();
|
|
811
836
|
ownedSubscription?.unsubscribe();
|
|
@@ -983,42 +1008,52 @@ class AunPlugin {
|
|
|
983
1008
|
const paths = publish ? (Array.isArray(body.paths) && body.paths.every((path) => typeof path === "string") ? body.paths : undefined) : [];
|
|
984
1009
|
if (!paths)
|
|
985
1010
|
return this.json(response, 400, { error: "发布路径必须是字符串数组" });
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
992
|
-
if (!this.isAbilityOwnerVerified(aid, owner) || this.abilityOwners[aid] !== owner || this.revokingAbilityAids.has(aid))
|
|
993
|
-
throw new Error("Owner 验证已失效或身份正在撤销,请重新确认");
|
|
994
|
-
const storage = this.clients.get(aid)?.client.storage;
|
|
995
|
-
if (!storage)
|
|
996
|
-
throw new Error("AUN 身份存储不可用");
|
|
997
|
-
const pluginId = this.bindings[aid];
|
|
998
|
-
const definitions = await this.abilityDefinitions(aid);
|
|
999
|
-
const published = await (0, ability_publication_1.publishAbilityCatalog)({ storage, aid, pluginId, pluginVersion: this.context?.pluginVersion?.(pluginId) ?? "", definitions: definitions.map(definition => ({ ...definition })), selectedPaths: paths, ledger: this.abilityLedgers[aid] ?? {}, beforeIndexWrite: async (pending) => { this.abilityLedgers[aid] = pending; await this.persist(); } });
|
|
1000
|
-
this.abilityLedgers[aid] = published.ledger;
|
|
1001
|
-
this.abilitySelections[aid] = [...new Set(paths)];
|
|
1002
|
-
await this.persist();
|
|
1003
|
-
});
|
|
1004
|
-
this.abilityPublications.set(aid, operation);
|
|
1005
|
-
try {
|
|
1006
|
-
await operation;
|
|
1007
|
-
}
|
|
1008
|
-
finally {
|
|
1009
|
-
if (this.abilityPublications.get(aid) === operation)
|
|
1010
|
-
this.abilityPublications.delete(aid);
|
|
1011
|
-
}
|
|
1012
|
-
return this.json(response, 200, { ok: true, aid, revoked: false });
|
|
1013
|
-
}
|
|
1014
|
-
await this.revokePublishedAbilities(aid);
|
|
1015
|
-
return this.json(response, 200, { ok: true, aid, revoked: true });
|
|
1011
|
+
const context = this.context;
|
|
1012
|
+
const session = context.managementSession?.(request);
|
|
1013
|
+
await this.setPublishedAbilities(aid, paths, () => this.context === context && Boolean(context.managementActor?.(request))
|
|
1014
|
+
&& (!paths.length || Boolean(session && context.managementSession?.(request)?.id === session.id)));
|
|
1015
|
+
return this.json(response, 200, { ok: true, aid, revoked: !paths.length });
|
|
1016
1016
|
}
|
|
1017
1017
|
catch (error) {
|
|
1018
1018
|
this.context?.logger.warn("AUN Ability 能力索引更新失败", { error: safeError(error) });
|
|
1019
1019
|
return this.json(response, 409, { error: safeError(error) });
|
|
1020
1020
|
}
|
|
1021
1021
|
};
|
|
1022
|
+
async setPublishedAbilities(aid, paths, authorized) {
|
|
1023
|
+
const owner = this.abilityOwners[aid];
|
|
1024
|
+
const assertAuthorized = () => {
|
|
1025
|
+
if (paths.length && (!owner || !this.isAbilityOwnerVerified(aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable || this.revokingAbilityAids.has(aid)))
|
|
1026
|
+
throw new Error("逐次 Owner 确认需可用操作账本和已验证的 Owner AID,请重新确认");
|
|
1027
|
+
if (!authorized())
|
|
1028
|
+
throw new Error("能力发布的管理身份已失效");
|
|
1029
|
+
};
|
|
1030
|
+
assertAuthorized();
|
|
1031
|
+
if (!paths.length) {
|
|
1032
|
+
await this.revokePublishedAbilities(aid);
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1036
|
+
assertAuthorized();
|
|
1037
|
+
const storage = this.clients.get(aid)?.client.storage;
|
|
1038
|
+
if (!storage)
|
|
1039
|
+
throw new Error("AUN 身份存储不可用");
|
|
1040
|
+
const pluginId = this.bindings[aid];
|
|
1041
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1042
|
+
assertAuthorized();
|
|
1043
|
+
const published = await (0, ability_publication_1.publishAbilityCatalog)({ storage, aid, pluginId, pluginVersion: this.context?.pluginVersion?.(pluginId) ?? "", definitions: definitions.map(definition => ({ ...definition })), selectedPaths: paths, ledger: this.abilityLedgers[aid] ?? {}, beforeIndexWrite: async (pending) => { assertAuthorized(); this.abilityLedgers[aid] = pending; await this.persist(); assertAuthorized(); } });
|
|
1044
|
+
this.abilityLedgers[aid] = published.ledger;
|
|
1045
|
+
this.abilitySelections[aid] = [...new Set(paths)];
|
|
1046
|
+
await this.persist();
|
|
1047
|
+
});
|
|
1048
|
+
this.abilityPublications.set(aid, operation);
|
|
1049
|
+
try {
|
|
1050
|
+
await operation;
|
|
1051
|
+
}
|
|
1052
|
+
finally {
|
|
1053
|
+
if (this.abilityPublications.get(aid) === operation)
|
|
1054
|
+
this.abilityPublications.delete(aid);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1022
1057
|
challengeAbilityOwner = async (request, response) => {
|
|
1023
1058
|
const session = this.context?.managementSession?.(request);
|
|
1024
1059
|
if (!this.context?.managementActor?.(request) || !session)
|
|
@@ -1035,6 +1070,9 @@ class AunPlugin {
|
|
|
1035
1070
|
this.revokingAbilityAids.add(aid);
|
|
1036
1071
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1037
1072
|
this.ownerProof.clear(aid);
|
|
1073
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1074
|
+
if (JSON.parse(key)[0] === aid)
|
|
1075
|
+
this.abilityApprovalViews.delete(key);
|
|
1038
1076
|
try {
|
|
1039
1077
|
await this.invalidateAbilityOperations(aid);
|
|
1040
1078
|
const previous = this.abilityOwners;
|
|
@@ -1078,84 +1116,233 @@ class AunPlugin {
|
|
|
1078
1116
|
const session = this.context?.managementSession?.(request);
|
|
1079
1117
|
if (!this.context?.managementActor?.(request) || !session)
|
|
1080
1118
|
return this.json(response, 403, { error: "逐次 Owner 确认需本机已认证管理会话" });
|
|
1081
|
-
const store = this.abilityOperations;
|
|
1082
|
-
if (!store || this.abilityAuditUnavailable)
|
|
1083
|
-
return this.json(response, 503, { error: "持久操作账本不可用" });
|
|
1084
1119
|
try {
|
|
1085
1120
|
const body = await this.readJsonBody(request);
|
|
1086
1121
|
if (typeof body.key !== "string" || typeof body.confirmationFingerprint !== "string" || typeof body.approved !== "boolean")
|
|
1087
1122
|
return this.json(response, 400, { error: "必须明确提交操作键、完整确认指纹和批准或拒绝决定" });
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1123
|
+
const context = this.context;
|
|
1124
|
+
const result = await this.decideAbilityOperation(body.key, body.confirmationFingerprint, body.approved, () => this.context === context && Boolean(context.managementActor?.(request)) && context.managementSession?.(request)?.id === session.id);
|
|
1125
|
+
this.json(response, 200, result);
|
|
1126
|
+
}
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
this.json(response, error instanceof AunCommandError ? error.statusCode : 409, { error: safeError(error) });
|
|
1129
|
+
}
|
|
1130
|
+
};
|
|
1131
|
+
async decideAbilityOperation(key, fingerprint, approved, authorized, providerAid) {
|
|
1132
|
+
const store = this.abilityOperations;
|
|
1133
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1134
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1135
|
+
const original = await store.get(key);
|
|
1136
|
+
if (!original || (providerAid !== undefined && original.providerAid !== providerAid))
|
|
1137
|
+
throw new AunCommandError("能力操作不存在", 404);
|
|
1138
|
+
const ownerAid = this.abilityOwners[original.providerAid];
|
|
1139
|
+
if (!ownerAid || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1140
|
+
throw new AunCommandError("需完成该 Provider 的 Owner AID 验证", 403);
|
|
1141
|
+
if (original.confirmationFingerprint !== fingerprint)
|
|
1142
|
+
throw new AunCommandError("确认指纹与待确认请求不一致", 409);
|
|
1143
|
+
const revision = this.abilityRevisions.get(original.providerAid) ?? 0;
|
|
1144
|
+
if (approved && original.state === "pending_confirmation") {
|
|
1145
|
+
const failure = await this.abilityInvocationFailure(original.providerAid, original.authenticatedCaller, original.path, original.definitionId, original.params);
|
|
1146
|
+
const pluginId = this.bindings[original.providerAid];
|
|
1147
|
+
if (failure || (this.abilityRevisions.get(original.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(original.providerAid) || original.executionTarget.pluginId !== pluginId || original.executionTarget.ownerAid !== ownerAid || original.executionTarget.pluginVersion !== (this.context?.pluginVersion?.(pluginId) ?? ""))
|
|
1148
|
+
throw new AunCommandError(failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用", 409);
|
|
1149
|
+
}
|
|
1150
|
+
if (!authorized() || !this.acceptingMessages || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1151
|
+
throw new AunCommandError("通道或 Owner 管理会话已失效", 403);
|
|
1152
|
+
const confirmation = approved ? await store.approve(original.key, fingerprint) : await store.reject(original.key, fingerprint);
|
|
1153
|
+
let entry = confirmation.entry;
|
|
1154
|
+
if (confirmation.started) {
|
|
1155
|
+
let result;
|
|
1156
|
+
let state;
|
|
1157
|
+
let handlerAuthorized = false;
|
|
1158
|
+
try {
|
|
1159
|
+
const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
|
|
1160
|
+
const messageExpired = Date.now() >= entry.expiresAt;
|
|
1161
|
+
const confirmationExpired = Date.now() >= entry.confirmationExpiresAt;
|
|
1162
|
+
const expired = messageExpired || confirmationExpired;
|
|
1163
|
+
const invalidated = (this.abilityRevisions.get(entry.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(entry.providerAid);
|
|
1164
|
+
if (failure || expired || invalidated || !authorized() || !this.isAbilityOwnerVerified(entry.providerAid, ownerAid) || this.abilityOwners[entry.providerAid] !== entry.executionTarget.ownerAid || this.bindings[entry.providerAid] !== entry.executionTarget.pluginId || (this.context?.pluginVersion?.(String(entry.executionTarget.pluginId)) ?? "") !== entry.executionTarget.pluginVersion) {
|
|
1165
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, expired ? (messageExpired ? "message_expired" : "confirmation_expired") : failure?.code ?? (invalidated ? "provider_invalidated" : "permission_denied"), messageExpired ? "调用已超过最晚启动时间" : confirmationExpired ? "Owner 确认申请已过期,不会执行" : failure?.message ?? "Owner 管理会话或执行目标已改变,本次能力没有执行"), execution_state: "not_executed" };
|
|
1166
|
+
state = "failed";
|
|
1167
|
+
}
|
|
1168
|
+
else {
|
|
1169
|
+
const context = this.context;
|
|
1170
|
+
const confirmed = entry;
|
|
1171
|
+
const beforeStart = () => {
|
|
1172
|
+
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !authorized() || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
|
|
1173
|
+
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
1174
|
+
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
1175
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1176
|
+
if ((this.abilityRevisions.get(confirmed.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(confirmed.providerAid) || !this.clients.has(confirmed.providerAid) || this.bindings[confirmed.providerAid] !== pluginId || this.abilityOwners[confirmed.providerAid] !== confirmed.executionTarget.ownerAid || (context.pluginVersion?.(pluginId) ?? "") !== confirmed.executionTarget.pluginVersion || context.pluginStatus(pluginId) !== "ready" || !this.abilitySelections[confirmed.providerAid]?.includes(confirmed.path) || this.abilityLedgers[confirmed.providerAid]?.[confirmed.path]?.definition_id !== confirmed.definitionId || (rules.length && !rules.includes("*") && !rules.includes(confirmed.authenticatedCaller)))
|
|
1177
|
+
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
1178
|
+
handlerAuthorized = true;
|
|
1179
|
+
};
|
|
1180
|
+
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
1181
|
+
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
1182
|
+
state = "succeeded";
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
|
|
1187
|
+
const code = error.code;
|
|
1188
|
+
const notExecuted = !handlerAuthorized && ["message_expired", "confirmation_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
|
|
1189
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, notExecuted ? code : "execution_failed", notExecuted ? (code === "message_expired" ? "调用已超过最晚启动时间" : code === "confirmation_expired" ? "Owner 确认申请已过期,不会执行" : "管理会话或 Provider 状态已改变,本次能力没有执行") : "能力执行失败,请检查 Provider 运行日志;不会自动重试"), execution_state: notExecuted ? "not_executed" : "failed" };
|
|
1190
|
+
state = "failed";
|
|
1191
|
+
}
|
|
1192
|
+
entry = await store.finish(entry.key, state, result);
|
|
1193
|
+
}
|
|
1194
|
+
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
1195
|
+
this.context?.logger.info("AUN Ability 逐次确认完成", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
1196
|
+
return { ok: true, state: entry.state, result: entry.result, delivered };
|
|
1197
|
+
}
|
|
1198
|
+
abilityApprovalViewKey(aid, ownerAid, threadId) {
|
|
1199
|
+
return JSON.stringify([aid, ownerAid, threadId ?? ""]);
|
|
1200
|
+
}
|
|
1201
|
+
async sendAbilityControlText(aid, ownerAid, text, threadId) {
|
|
1202
|
+
const chunks = [];
|
|
1203
|
+
const characters = Array.from(text);
|
|
1204
|
+
for (let offset = 0; offset < characters.length; offset += 4000)
|
|
1205
|
+
chunks.push(characters.slice(offset, offset + 4000).join(""));
|
|
1206
|
+
for (const [index, part] of chunks.entries()) {
|
|
1207
|
+
const message = chunks.length > 1 ? `Ability 管理消息(第 ${index + 1}/${chunks.length} 段,收全后再确认)\n${part}` : part;
|
|
1208
|
+
const sent = await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text: message, ...(threadId ? { thread_id: threadId } : {}) } });
|
|
1209
|
+
if (typeof sent?.status !== "string" || !successfulSendStatuses.has(sent.status))
|
|
1210
|
+
throw new Error(`AUN 管理回复投递状态未确认:${String(sent?.status ?? "未返回状态")}`);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
abilityEntrySummary(entry) {
|
|
1214
|
+
return [
|
|
1215
|
+
`操作键:${entry.key}`,
|
|
1216
|
+
`确认指纹:${entry.confirmationFingerprint}`,
|
|
1217
|
+
`调用方:${entry.authenticatedCaller}`,
|
|
1218
|
+
`Provider AID:${entry.providerAid}`,
|
|
1219
|
+
`能力路径:${entry.path}`,
|
|
1220
|
+
`definition_id:${entry.definitionId}`,
|
|
1221
|
+
`原始参数:${JSON.stringify(entry.params)}`,
|
|
1222
|
+
`影响:${entry.effects.join("、")}`,
|
|
1223
|
+
`执行目标:${String(entry.executionTarget.pluginId)}(${String(entry.executionTarget.pluginVersion)})`,
|
|
1224
|
+
`operation_id:${entry.operationId}`,
|
|
1225
|
+
`审批截止:${new Date(entry.confirmationExpiresAt).toLocaleString("zh-CN")}`,
|
|
1226
|
+
`最晚启动:${new Date(entry.expiresAt).toLocaleString("zh-CN")}`,
|
|
1227
|
+
`确认:/ability approve ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1228
|
+
`拒绝:/ability reject ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1229
|
+
].join("\n");
|
|
1230
|
+
}
|
|
1231
|
+
async showAbilityPending(aid, ownerAid, threadId) {
|
|
1232
|
+
const store = this.abilityOperations;
|
|
1233
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1234
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1235
|
+
const entries = (await store.pending()).filter(entry => entry.providerAid === aid);
|
|
1236
|
+
if (!entries.length) {
|
|
1237
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1238
|
+
await this.sendAbilityControlText(aid, ownerAid, "当前没有待确认的 Ability 调用。", threadId);
|
|
1239
|
+
return;
|
|
1240
|
+
}
|
|
1241
|
+
const viewKey = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1242
|
+
const view = { explicit: true, entries: undefined };
|
|
1243
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
1244
|
+
const text = `待确认 Ability 调用清单(${entries.length} 项,仅包含本条消息展示的固定记录)\n\n${entries.map((entry, index) => `第 ${index + 1} 项\n${this.abilityEntrySummary(entry)}`).join("\n\n---\n\n")}\n\n回复“确认”或“拒绝”按本清单逐项处理;清单之后的新请求不会加入。`;
|
|
1245
|
+
await this.sendAbilityControlText(aid, ownerAid, text, threadId);
|
|
1246
|
+
if (this.abilityApprovalViews.get(viewKey) === view && this.abilityOperations === store && this.abilityOwners[aid] === ownerAid && this.acceptingMessages)
|
|
1247
|
+
view.entries = entries.map(({ key, confirmationFingerprint }) => ({ key, confirmationFingerprint }));
|
|
1248
|
+
}
|
|
1249
|
+
async processAbilityControl(payload, aid) {
|
|
1250
|
+
const raw = payload && typeof payload === "object" ? payload : undefined;
|
|
1251
|
+
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1252
|
+
const text = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1253
|
+
const isControlText = text === "确认" || text === "拒绝" || /^ *\/ability(?:\s|$)/.test(text);
|
|
1254
|
+
if (!isControlText)
|
|
1255
|
+
return false;
|
|
1256
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1257
|
+
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1258
|
+
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1259
|
+
if (ownerAid && sender !== ownerAid)
|
|
1260
|
+
return text === "确认" || text === "拒绝" ? false : true;
|
|
1261
|
+
if (!ownerAid && (text === "确认" || text === "拒绝"))
|
|
1262
|
+
return false;
|
|
1263
|
+
let authenticated = false;
|
|
1264
|
+
const context = this.context;
|
|
1265
|
+
const store = this.abilityOperations;
|
|
1266
|
+
const revision = this.abilityRevisions.get(aid) ?? 0;
|
|
1267
|
+
const authorized = () => this.context === context && this.abilityOperations === store && this.acceptingMessages && !this.revokingAbilityAids.has(aid) && this.clients.has(aid) && Boolean(this.bindings[aid]) && (this.abilityRevisions.get(aid) ?? 0) === revision && this.abilityOwners[aid] === ownerAid && this.isAbilityOwnerVerified(aid, ownerAid);
|
|
1268
|
+
try {
|
|
1269
|
+
const authPayload = typeof raw?.payload === "string" ? "/ability help" : { ...body, text: "/ability help" };
|
|
1270
|
+
(0, ability_control_1.parseAbilityControlMessage)({ ...raw, payload: authPayload }, aid, ownerAid);
|
|
1271
|
+
authenticated = true;
|
|
1272
|
+
const message = (0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid);
|
|
1273
|
+
if (!message)
|
|
1274
|
+
return false;
|
|
1275
|
+
if (message.messageId && !this.seen.add(`ability-control:${aid}:${message.messageId}`))
|
|
1276
|
+
return true;
|
|
1277
|
+
if (!this.isAbilityOwnerVerified(aid, ownerAid))
|
|
1278
|
+
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1279
|
+
const { command, peerId, threadId } = message;
|
|
1280
|
+
if (command.action === "confirm-view" || command.action === "reject-view") {
|
|
1281
|
+
const key = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1282
|
+
const view = this.abilityApprovalViews.get(key);
|
|
1283
|
+
this.abilityApprovalViews.delete(key);
|
|
1284
|
+
if (!view?.entries?.length) {
|
|
1285
|
+
await this.sendAbilityControlText(aid, peerId, "当前话题没有已展示的待确认清单。请先发送 /ability pending,核对清单后再回复确认或拒绝。", threadId);
|
|
1286
|
+
return true;
|
|
1287
|
+
}
|
|
1288
|
+
const approved = command.action === "confirm-view";
|
|
1289
|
+
const results = [];
|
|
1290
|
+
for (const entry of view.entries) {
|
|
1291
|
+
try {
|
|
1292
|
+
const result = await this.decideAbilityOperation(entry.key, entry.confirmationFingerprint, approved, authorized, aid);
|
|
1293
|
+
results.push(`${entry.key.slice(0, 12)}:${String(result.state)}`);
|
|
1120
1294
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
const confirmed = entry;
|
|
1124
|
-
const beforeStart = () => {
|
|
1125
|
-
const currentSession = context.managementSession?.(request);
|
|
1126
|
-
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !context.managementActor?.(request) || !currentSession || currentSession.id !== session.id || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
|
|
1127
|
-
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
1128
|
-
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
1129
|
-
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1130
|
-
if ((this.abilityRevisions.get(confirmed.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(confirmed.providerAid) || !this.clients.has(confirmed.providerAid) || this.bindings[confirmed.providerAid] !== pluginId || this.abilityOwners[confirmed.providerAid] !== confirmed.executionTarget.ownerAid || (context.pluginVersion?.(pluginId) ?? "") !== confirmed.executionTarget.pluginVersion || context.pluginStatus(pluginId) !== "ready" || !this.abilitySelections[confirmed.providerAid]?.includes(confirmed.path) || this.abilityLedgers[confirmed.providerAid]?.[confirmed.path]?.definition_id !== confirmed.definitionId || (rules.length && !rules.includes("*") && !rules.includes(confirmed.authenticatedCaller)))
|
|
1131
|
-
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
1132
|
-
handlerAuthorized = true;
|
|
1133
|
-
};
|
|
1134
|
-
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
1135
|
-
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
1136
|
-
state = "succeeded";
|
|
1295
|
+
catch (error) {
|
|
1296
|
+
results.push(`${entry.key.slice(0, 12)}:未处理(${safeError(error)})`);
|
|
1137
1297
|
}
|
|
1138
1298
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1299
|
+
await this.sendAbilityControlText(aid, peerId, `${approved ? "确认" : "拒绝"}处理完成:\n${results.join("\n")}`, threadId);
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
if (command.action === "help") {
|
|
1303
|
+
await this.sendAbilityControlText(aid, peerId, "AUN Ability 管理命令:\n/ability list:查看当前 Provider 的能力\n/ability publish <路径...>:按完整路径列表发布\n/ability revoke:撤销本 Provider 的能力索引\n/ability pending:展示固定待确认清单\n/ability approve <操作键> <确认指纹>:批准单项\n/ability reject <操作键> <确认指纹>:拒绝单项\n也可在清单投递成功后回复“确认”或“拒绝”。", threadId);
|
|
1304
|
+
}
|
|
1305
|
+
else if (command.action === "list") {
|
|
1306
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1307
|
+
const selected = new Set(this.abilitySelections[aid] ?? []);
|
|
1308
|
+
const lines = definitions.map(item => `${selected.has(item.path) ? "已发布" : "未发布"} ${item.path} | ${item.name} | ${(0, ability_publication_1.abilityDefinitionId)({ ...item })}`);
|
|
1309
|
+
await this.sendAbilityControlText(aid, peerId, lines.length ? lines.join("\n") : "当前关联 Agent 没有可发布的 Ability。", threadId);
|
|
1310
|
+
}
|
|
1311
|
+
else if (command.action === "publish") {
|
|
1312
|
+
await this.setPublishedAbilities(aid, command.paths, authorized);
|
|
1313
|
+
await this.sendAbilityControlText(aid, peerId, `已发布 ${command.paths.length} 项 Ability:\n${command.paths.join("\n")}`, threadId);
|
|
1314
|
+
}
|
|
1315
|
+
else if (command.action === "revoke") {
|
|
1316
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1317
|
+
await this.setPublishedAbilities(aid, [], authorized);
|
|
1318
|
+
await this.sendAbilityControlText(aid, peerId, "已撤销本 Provider 的 Ability 索引,并使现有待确认操作失效。", threadId);
|
|
1319
|
+
}
|
|
1320
|
+
else if (command.action === "pending")
|
|
1321
|
+
await this.showAbilityPending(aid, peerId, threadId);
|
|
1322
|
+
else if (command.action === "approve" || command.action === "reject") {
|
|
1323
|
+
const result = await this.decideAbilityOperation(command.key, command.confirmationFingerprint, command.action === "approve", authorized, aid);
|
|
1324
|
+
await this.sendAbilityControlText(aid, peerId, `Ability 操作状态:${String(result.state)};结果已${result.delivered ? "回传 Peer" : "保存,等待重投"}。`, threadId);
|
|
1147
1325
|
}
|
|
1148
|
-
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
1149
|
-
this.context?.logger.info("AUN Ability 逐次确认完成,需本机已认证管理会话", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
1150
|
-
this.json(response, 200, { ok: true, state: entry.state, result: entry.result, delivered });
|
|
1151
1326
|
}
|
|
1152
1327
|
catch (error) {
|
|
1153
|
-
|
|
1328
|
+
const replyAid = authenticated ? ownerAid : error.code === "ability_owner_unconfigured" ? sender : undefined;
|
|
1329
|
+
if (replyAid) {
|
|
1330
|
+
try {
|
|
1331
|
+
await this.sendAbilityControlText(aid, replyAid, `Ability 管理操作未完成:${safeError(error)}`, typeof body?.thread_id === "string" ? body.thread_id : undefined);
|
|
1332
|
+
}
|
|
1333
|
+
catch (replyError) {
|
|
1334
|
+
this.context?.logger.warn("AUN Ability 管理错误回复失败", { aid, ownerAid, error: safeError(replyError) });
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1154
1337
|
}
|
|
1155
|
-
|
|
1338
|
+
return true;
|
|
1339
|
+
}
|
|
1156
1340
|
async revokePublishedAbilities(aid) {
|
|
1157
1341
|
this.revokingAbilityAids.add(aid);
|
|
1158
1342
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1343
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1344
|
+
if (JSON.parse(key)[0] === aid)
|
|
1345
|
+
this.abilityApprovalViews.delete(key);
|
|
1159
1346
|
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1160
1347
|
await this.invalidateAbilityOperations(aid);
|
|
1161
1348
|
this.abilitySelections[aid] = [];
|
|
@@ -1752,22 +1939,31 @@ class AunPlugin {
|
|
|
1752
1939
|
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1753
1940
|
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1754
1941
|
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1755
|
-
|
|
1942
|
+
const inboundText = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1943
|
+
const inboundThreadId = typeof body?.thread_id === "string" ? body.thread_id : undefined;
|
|
1944
|
+
if (inboundText === "确认" && this.ownerProof.hasPendingConfirmation(aid)) {
|
|
1756
1945
|
const ownerAid = this.abilityOwners[aid];
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1946
|
+
let authenticated = false;
|
|
1947
|
+
try {
|
|
1948
|
+
authenticated = Boolean((0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid));
|
|
1949
|
+
}
|
|
1950
|
+
catch { /* 无效信封不能验证 Owner 身份。 */ }
|
|
1951
|
+
if (!authenticated || !this.ownerProof.acceptConfirmation(aid, sender, inboundText, true)) {
|
|
1761
1952
|
this.context?.logger.warn("AUN Owner 确认未通过验证", { traceId: inboundId, aid, sender: sender ?? "", stage: "Owner 确认校验", reason: authenticated ? "没有有效的待确认通知" : "需要配置 Owner AID 通过加密消息直接回复确认" });
|
|
1762
1953
|
return;
|
|
1763
1954
|
}
|
|
1955
|
+
if (typeof raw?.message_id === "string")
|
|
1956
|
+
this.seen.add(`ability-control:${aid}:${raw.message_id}`);
|
|
1957
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1958
|
+
if (JSON.parse(key)[0] === aid)
|
|
1959
|
+
this.abilityApprovalViews.delete(key);
|
|
1764
1960
|
try {
|
|
1765
1961
|
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1766
1962
|
}
|
|
1767
1963
|
catch (error) {
|
|
1768
1964
|
this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, ownerAid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1769
1965
|
try {
|
|
1770
|
-
await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。",
|
|
1966
|
+
await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。", inboundThreadId, true);
|
|
1771
1967
|
}
|
|
1772
1968
|
catch (replyError) {
|
|
1773
1969
|
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: false, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
@@ -1775,7 +1971,7 @@ class AunPlugin {
|
|
|
1775
1971
|
return;
|
|
1776
1972
|
}
|
|
1777
1973
|
try {
|
|
1778
|
-
await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner
|
|
1974
|
+
await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`, inboundThreadId, true);
|
|
1779
1975
|
}
|
|
1780
1976
|
catch (error) {
|
|
1781
1977
|
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: true, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
@@ -1787,6 +1983,9 @@ class AunPlugin {
|
|
|
1787
1983
|
if (typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text)) {
|
|
1788
1984
|
let verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
|
|
1789
1985
|
if (verified) {
|
|
1986
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1987
|
+
if (JSON.parse(key)[0] === aid)
|
|
1988
|
+
this.abilityApprovalViews.delete(key);
|
|
1790
1989
|
const ownerAid = this.abilityOwners[aid];
|
|
1791
1990
|
try {
|
|
1792
1991
|
if (ownerAid === sender)
|
|
@@ -1802,10 +2001,10 @@ class AunPlugin {
|
|
|
1802
2001
|
this.context?.logger.info("AUN 宿主委托的 Owner 控制权验证已处理", { aid, verified });
|
|
1803
2002
|
if (sender && raw?.encrypted === true && (envelope?.from === undefined || envelope.from === sender)) {
|
|
1804
2003
|
const text = verified
|
|
1805
|
-
? `Owner 控制权验证成功:${aid} 的 Owner
|
|
2004
|
+
? `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`
|
|
1806
2005
|
: !ownerCommand
|
|
1807
|
-
? "Owner
|
|
1808
|
-
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner
|
|
2006
|
+
? "Owner 控制权验证失败:指令格式不正确。请使用已配置的 Owner AID 发送 /ability help。"
|
|
2007
|
+
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请使用已配置的 Owner AID 发送 /ability help 重新验证。";
|
|
1809
2008
|
try {
|
|
1810
2009
|
await this.sendText(aid, sender, text, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
|
|
1811
2010
|
}
|
|
@@ -1815,6 +2014,8 @@ class AunPlugin {
|
|
|
1815
2014
|
}
|
|
1816
2015
|
return;
|
|
1817
2016
|
}
|
|
2017
|
+
if (await this.processAbilityControl(payload, aid))
|
|
2018
|
+
return;
|
|
1818
2019
|
if ((0, ability_operations_1.isAbilityMessage)(payload)) {
|
|
1819
2020
|
if (this.revokingAbilityAids.has(aid)) {
|
|
1820
2021
|
this.context?.logger.warn("AUN 身份正在撤销能力索引,能力调用已阻止", { aid, traceId: inboundId, stage: "撤销保护" });
|
|
@@ -2033,24 +2234,25 @@ class AunPlugin {
|
|
|
2033
2234
|
}
|
|
2034
2235
|
async notifyAbilityOwner(entry) {
|
|
2035
2236
|
const ownerAid = String(entry.executionTarget.ownerAid ?? "");
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
].join("\n");
|
|
2237
|
+
const viewKey = this.abilityApprovalViewKey(entry.providerAid, ownerAid);
|
|
2238
|
+
const existingView = this.abilityApprovalViews.get(viewKey);
|
|
2239
|
+
const view = existingView ?? {};
|
|
2240
|
+
if (!existingView)
|
|
2241
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
2242
|
+
const pending = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2243
|
+
if (pending.length !== 1 && !view.explicit)
|
|
2244
|
+
view.entries = undefined;
|
|
2245
|
+
const text = `有新的远程 Ability 调用申请\n${this.abilityEntrySummary(entry)}\n\n审批窗口最长 12 小时,不会延长调用最晚启动时间或协议 TTL。\n只有本话题中唯一、已完整投递的申请可直接回复“确认”或“拒绝”;多项、已处理或不确定时先发送 /ability pending 查看固定清单。`;
|
|
2046
2246
|
try {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2247
|
+
await this.sendAbilityControlText(entry.providerAid, ownerAid, text);
|
|
2248
|
+
const current = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2249
|
+
if (!existingView && this.abilityApprovalViews.get(viewKey) === view)
|
|
2250
|
+
view.entries = current.length === 1 && current[0].key === entry.key && this.abilityOwners[entry.providerAid] === ownerAid && this.acceptingMessages
|
|
2251
|
+
? [{ key: entry.key, confirmationFingerprint: entry.confirmationFingerprint }] : undefined;
|
|
2252
|
+
this.context?.logger.info("AUN Ability Owner 申请通知已投递", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知" });
|
|
2051
2253
|
}
|
|
2052
2254
|
catch (error) {
|
|
2053
|
-
this.context?.logger.warn("AUN Ability Owner
|
|
2255
|
+
this.context?.logger.warn("AUN Ability Owner 申请通知发送失败,请用 /ability pending 读取保留的待确认请求", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
2054
2256
|
}
|
|
2055
2257
|
}
|
|
2056
2258
|
async abilityInvocationFailure(aid, callerAid, path, definitionId, params) {
|
|
@@ -2106,6 +2308,7 @@ class AunPlugin {
|
|
|
2106
2308
|
}
|
|
2107
2309
|
async processMessage(message, aid, agentPlugin, inboundId) {
|
|
2108
2310
|
let deliveryFailed = false;
|
|
2311
|
+
let failureText;
|
|
2109
2312
|
const trace = { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", messageId: message.messageId ?? "", agentPlugin };
|
|
2110
2313
|
this.context?.logger.info("AUN 收到任务", { ...trace, stage: "接收消息", attachments: message.attachments?.length ?? 0 });
|
|
2111
2314
|
const client = this.clients.get(aid)?.client;
|
|
@@ -2153,7 +2356,9 @@ class AunPlugin {
|
|
|
2153
2356
|
}
|
|
2154
2357
|
catch (error) {
|
|
2155
2358
|
deliveryFailed = true;
|
|
2156
|
-
|
|
2359
|
+
const failure = new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
|
|
2360
|
+
failureText = `回复回传失败:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2361
|
+
throw failure;
|
|
2157
2362
|
}
|
|
2158
2363
|
},
|
|
2159
2364
|
replyAttachments: async (items, text, signal) => {
|
|
@@ -2223,6 +2428,7 @@ class AunPlugin {
|
|
|
2223
2428
|
deliveryFailed = true;
|
|
2224
2429
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
2225
2430
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
2431
|
+
failureText = `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2226
2432
|
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
2227
2433
|
try {
|
|
2228
2434
|
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true, "markdown");
|
|
@@ -2233,12 +2439,13 @@ class AunPlugin {
|
|
|
2233
2439
|
},
|
|
2234
2440
|
};
|
|
2235
2441
|
const taskId = message.messageId || inboundId;
|
|
2236
|
-
const notifyStatus = (status) => {
|
|
2442
|
+
const notifyStatus = (status, text) => {
|
|
2237
2443
|
if (!client?.notify)
|
|
2238
2444
|
return;
|
|
2239
2445
|
const params = {
|
|
2240
2446
|
type: "task.status", status, task_id: taskId, session_id: taskId,
|
|
2241
2447
|
severity: status === "error" ? "error" : "info", initiator: message.peerId,
|
|
2448
|
+
...(text ? { text } : {}),
|
|
2242
2449
|
...(message.threadId ? { thread_id: message.threadId } : {}),
|
|
2243
2450
|
...(message.messageId ? { ref_message_id: message.messageId } : {}),
|
|
2244
2451
|
};
|
|
@@ -2263,10 +2470,11 @@ class AunPlugin {
|
|
|
2263
2470
|
catch (error) {
|
|
2264
2471
|
failed = true;
|
|
2265
2472
|
this.context?.logger.warn("AUN 消息处理失败", { ...trace, stage: deliveryFailed ? "回传回复" : "执行 Agent 任务", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
2473
|
+
const action = deliveryFailed ? "回复回传失败" : attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2474
|
+
failureText ??= `${action}:${safeError((0, agent_1.errorDetails)(error))}`;
|
|
2266
2475
|
if (!deliveryFailed) {
|
|
2267
|
-
const action = attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2268
2476
|
try {
|
|
2269
|
-
await channelMessage.reply(
|
|
2477
|
+
await channelMessage.reply(failureText);
|
|
2270
2478
|
}
|
|
2271
2479
|
catch (replyError) {
|
|
2272
2480
|
this.context?.logger.warn("AUN 错误回复失败", { ...trace, stage: "发送最终错误消息", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
@@ -2275,7 +2483,7 @@ class AunPlugin {
|
|
|
2275
2483
|
}
|
|
2276
2484
|
finally {
|
|
2277
2485
|
clearInterval(progressTimer);
|
|
2278
|
-
notifyStatus(failed || deliveryFailed ? "error" : "completed");
|
|
2486
|
+
notifyStatus(failed || deliveryFailed ? "error" : "completed", failureText);
|
|
2279
2487
|
}
|
|
2280
2488
|
}
|
|
2281
2489
|
async sendText(aid, peerId, text, threadId, encryptedOnly = false, format) {
|
|
@@ -4,12 +4,6 @@
|
|
|
4
4
|
const list = find("#identities");
|
|
5
5
|
const createForm = find("#create-form");
|
|
6
6
|
const configForm = find("#config-form");
|
|
7
|
-
const abilityList = find("#abilities");
|
|
8
|
-
const pendingList = find("#ability-pending");
|
|
9
|
-
const abilityRows = new Map();
|
|
10
|
-
let pendingSnapshot;
|
|
11
|
-
let pendingRows = [];
|
|
12
|
-
let confirming = false;
|
|
13
7
|
let agents = [];
|
|
14
8
|
let loading = false;
|
|
15
9
|
|
|
@@ -59,130 +53,10 @@
|
|
|
59
53
|
cell.append(select); row.append(cell); list.append(row);
|
|
60
54
|
}
|
|
61
55
|
if (!list.children.length) { const row = document.createElement("tr"); const cell = document.createElement("td"); cell.colSpan = 4; cell.textContent = "暂无已连接 AID"; row.append(cell); list.append(row); }
|
|
62
|
-
await loadAbilities(refreshForm);
|
|
63
|
-
await loadPending();
|
|
64
56
|
show(`${entries.length} 个已连接 AID`);
|
|
65
57
|
} catch (error) { show(String(error), true); }
|
|
66
58
|
finally { loading = false; }
|
|
67
59
|
}
|
|
68
|
-
function emptyRow(list, columns, message) {
|
|
69
|
-
const row = document.createElement("tr"); const cell = document.createElement("td"); cell.colSpan = columns; cell.textContent = message; row.append(cell); list.replaceChildren(row);
|
|
70
|
-
}
|
|
71
|
-
function ownerVerified(aid) {
|
|
72
|
-
const state = abilityRows.get(aid);
|
|
73
|
-
return Boolean(state?.item.ownerVerified && !state.challengeBusy && state.owner.value.trim() === state.item.ownerAid);
|
|
74
|
-
}
|
|
75
|
-
async function loadAbilities(refresh = true) {
|
|
76
|
-
const { abilities } = await request("/api/plugins/aun/abilities");
|
|
77
|
-
if (refresh) abilityRows.clear();
|
|
78
|
-
for (const [aid, item] of Object.entries(abilities || {})) {
|
|
79
|
-
const existing = abilityRows.get(aid);
|
|
80
|
-
if (existing) { existing.item = item; existing.sync(); continue; }
|
|
81
|
-
const row = document.createElement("tr");
|
|
82
|
-
for (const value of [aid, `${item.agent} / ${item.pluginVersion || "版本未知"}`]) { const cell = document.createElement("td"); cell.textContent = value; row.append(cell); }
|
|
83
|
-
const ownerCell = document.createElement("td"); ownerCell.className = "ability-owner";
|
|
84
|
-
const owner = document.createElement("input"); owner.type = "text"; owner.value = item.ownerAid || ""; owner.placeholder = "明确指定 Owner AID"; owner.setAttribute("aria-label", `${aid} 宿主委托 Owner AID`);
|
|
85
|
-
const challenge = document.createElement("button"); challenge.type = "button"; challenge.textContent = "发送 Owner 确认通知";
|
|
86
|
-
const proof = document.createElement("p"); proof.setAttribute("role", "status");
|
|
87
|
-
ownerCell.append(owner, challenge, proof); row.append(ownerCell);
|
|
88
|
-
const choices = document.createElement("td");
|
|
89
|
-
choices.className = "ability-options";
|
|
90
|
-
for (const definition of item.definitions || []) {
|
|
91
|
-
const label = document.createElement("label"); const input = document.createElement("input"); input.type = "checkbox"; input.value = definition.path; input.checked = (item.selected || []).includes(definition.path);
|
|
92
|
-
label.append(input, document.createTextNode(`${definition.name}(${definition.path})`)); label.title = definition.summary; choices.append(label);
|
|
93
|
-
}
|
|
94
|
-
if (!choices.children.length) choices.textContent = "关联插件未就绪或没有声明能力";
|
|
95
|
-
if (item.reason) { const reason = document.createElement("p"); reason.textContent = item.reason; choices.append(reason); }
|
|
96
|
-
const actions = document.createElement("td");
|
|
97
|
-
const save = document.createElement("button"); save.type = "button"; save.textContent = "保存发布";
|
|
98
|
-
const revoke = document.createElement("button"); revoke.type = "button"; revoke.textContent = "撤销发布";
|
|
99
|
-
const state = { row, owner, item, busy: false, challengeBusy: false, challengeExpiresAt: 0, sync: () => {
|
|
100
|
-
const verified = ownerVerified(aid);
|
|
101
|
-
save.disabled = state.busy || !state.item.publishAvailable || !verified;
|
|
102
|
-
save.title = verified ? state.item.reason || "保存当前选择的能力" : "请先验证当前管理会话的宿主委托 Owner";
|
|
103
|
-
revoke.disabled = state.busy || !state.item.published;
|
|
104
|
-
challenge.disabled = state.challengeBusy || !owner.value.trim();
|
|
105
|
-
proof.textContent = verified ? "当前 Owner 已验证;每次调用仍须单独批准。" : state.challengeExpiresAt > Date.now() && owner.value.trim() === state.item.ownerAid ? `验证通知已通过 AUN 发送给指定 Owner,请直接回复“确认”;截止:${new Date(state.challengeExpiresAt).toLocaleString()}` : "当前 Owner 未验证,请发送确认通知。";
|
|
106
|
-
} };
|
|
107
|
-
abilityRows.set(aid, state);
|
|
108
|
-
owner.oninput = () => { state.sync(); for (const pending of pendingRows) pending.sync(); };
|
|
109
|
-
challenge.onclick = async () => {
|
|
110
|
-
if (state.challengeBusy || !owner.value.trim()) return;
|
|
111
|
-
const ownerAid = owner.value.trim();
|
|
112
|
-
state.challengeBusy = true;
|
|
113
|
-
state.item = { ...state.item, ownerVerified: false, publishAvailable: false };
|
|
114
|
-
state.sync(); for (const pending of pendingRows) pending.sync();
|
|
115
|
-
try {
|
|
116
|
-
const result = await request("/api/plugins/aun/abilities/owner-challenge", "POST", { aid, ownerAid });
|
|
117
|
-
state.item = { ...state.item, ownerAid, ownerVerified: Boolean(result.alreadyVerified), publishAvailable: Boolean(result.alreadyVerified) };
|
|
118
|
-
state.challengeExpiresAt = result.alreadyVerified ? 0 : result.expiresAt;
|
|
119
|
-
notify("Owner 确认通知结果", result.alreadyVerified ? `${ownerAid} 已完成验证,无需重复确认。` : `加密确认通知已通过 AUN 发送给 ${ownerAid}。请直接回复“确认”;仅验证 Owner 身份,不会执行能力。`);
|
|
120
|
-
} catch (error) { notify("Owner 验证发送失败", errorMessage(error), true); }
|
|
121
|
-
finally { state.challengeBusy = false; state.sync(); for (const pending of pendingRows) pending.sync(); }
|
|
122
|
-
};
|
|
123
|
-
const publish = async (paths) => {
|
|
124
|
-
if (state.busy) return;
|
|
125
|
-
if (paths.length && (!ownerVerified(aid) || !state.item.publishAvailable)) { notify("能力发布失败", "请先验证当前管理会话的宿主委托 Owner", true); return; }
|
|
126
|
-
state.busy = true; state.sync();
|
|
127
|
-
try { await request("/api/plugins/aun/abilities/publish", "POST", { aid, paths }); await loadAbilities(); notify("能力发布结果", paths.length ? `${aid} 已更新能力索引` : `${aid} 已撤销本通道发布的能力`); }
|
|
128
|
-
catch (error) { notify("能力发布失败", errorMessage(error), true); }
|
|
129
|
-
finally { state.busy = false; state.sync(); }
|
|
130
|
-
};
|
|
131
|
-
save.onclick = () => void publish([...choices.querySelectorAll("input:checked")].map(input => input.value));
|
|
132
|
-
revoke.onclick = () => void publish([]);
|
|
133
|
-
actions.append(save, revoke); row.append(choices, actions); state.sync();
|
|
134
|
-
}
|
|
135
|
-
for (const aid of abilityRows.keys()) if (!Object.hasOwn(abilities || {}, aid)) abilityRows.delete(aid);
|
|
136
|
-
const rows = [...abilityRows.values()].map(state => state.row);
|
|
137
|
-
if (refresh || rows.length !== abilityList.children.length || rows.some((row, index) => row !== abilityList.children[index])) abilityList.replaceChildren(...rows);
|
|
138
|
-
if (!abilityList.children.length) emptyRow(abilityList, 5, "暂无关联 Agent 的 AID");
|
|
139
|
-
for (const pending of pendingRows) pending.sync();
|
|
140
|
-
}
|
|
141
|
-
async function loadPending(force = false) {
|
|
142
|
-
const interacting = () => confirming || (!force && document.activeElement && pendingList.contains(document.activeElement));
|
|
143
|
-
if (interacting()) return;
|
|
144
|
-
const { operations = [] } = await request("/api/plugins/aun/abilities/pending");
|
|
145
|
-
if (interacting()) return;
|
|
146
|
-
const snapshot = JSON.stringify(operations);
|
|
147
|
-
if (snapshot === pendingSnapshot) { for (const pending of pendingRows) pending.sync(); return; }
|
|
148
|
-
pendingSnapshot = snapshot; pendingRows = []; pendingList.replaceChildren();
|
|
149
|
-
for (const entry of operations) {
|
|
150
|
-
const row = document.createElement("tr");
|
|
151
|
-
const cell = (text, pre = false) => { const td = document.createElement("td"); const content = pre ? document.createElement("pre") : td; content.textContent = text; if (pre) td.append(content); row.append(td); return td; };
|
|
152
|
-
cell(entry.providerAid); cell(entry.authenticatedCaller);
|
|
153
|
-
cell(`operation_id: ${entry.operationId}\nkey: ${entry.key}\nconfirmationFingerprint: ${entry.confirmationFingerprint}`, true);
|
|
154
|
-
cell(`path: ${entry.path}\ndefinition_id: ${entry.definitionId}`, true);
|
|
155
|
-
cell(JSON.stringify(entry.params, null, 2), true);
|
|
156
|
-
const details = cell(""); const contract = document.createElement("pre"); contract.textContent = `effects: ${JSON.stringify(entry.effects)}\n执行目标: ${JSON.stringify(entry.executionTarget, null, 2)}\nOwner 审批截止时间: ${new Date(entry.confirmationExpiresAt).toLocaleString()} (${entry.confirmationExpiresAt})\n调用最晚启动时间: ${new Date(entry.expiresAt).toLocaleString()} (${entry.expiresAt})\n状态: ${entry.state}`; details.append(contract);
|
|
157
|
-
const expiry = document.createElement("p"); details.append(expiry);
|
|
158
|
-
const actions = cell(""); const proof = document.createElement("p");
|
|
159
|
-
const approve = document.createElement("button"); approve.type = "button"; approve.textContent = "批准一次";
|
|
160
|
-
const reject = document.createElement("button"); reject.type = "button"; reject.textContent = "拒绝";
|
|
161
|
-
const pending = { sync: () => {
|
|
162
|
-
const verified = ownerVerified(entry.providerAid);
|
|
163
|
-
const confirmationExpired = !Number.isFinite(entry.confirmationExpiresAt) || entry.confirmationExpiresAt <= Date.now();
|
|
164
|
-
const messageExpired = !Number.isFinite(entry.expiresAt) || entry.expiresAt <= Date.now();
|
|
165
|
-
const unavailable = confirming || !verified || entry.state !== "pending_confirmation";
|
|
166
|
-
approve.disabled = unavailable || confirmationExpired || messageExpired; reject.disabled = unavailable || confirmationExpired;
|
|
167
|
-
expiry.textContent = confirmationExpired ? "Owner 审批申请已过期" : messageExpired ? "调用已超过最晚启动时间;不能再执行,但仍可拒绝" : "可在调用截止前批准一次;审批记录最长保留 12 小时";
|
|
168
|
-
proof.textContent = verified ? "当前会话的 Owner 已验证" : "此 Provider 的 Owner 未验证,请先验证 Owner";
|
|
169
|
-
} };
|
|
170
|
-
const confirm = async (approved) => {
|
|
171
|
-
pending.sync();
|
|
172
|
-
if (confirming || (approved ? approve.disabled : reject.disabled)) return;
|
|
173
|
-
confirming = true; for (const item of pendingRows) item.sync();
|
|
174
|
-
try {
|
|
175
|
-
const result = await request("/api/plugins/aun/abilities/confirm", "POST", { key: entry.key, confirmationFingerprint: entry.confirmationFingerprint, approved });
|
|
176
|
-
notify("能力确认结果", `状态:${result.state}\n${result.result ? JSON.stringify(result.result, null, 2) : ""}\n${result.delivered ? "结果已回投调用方" : "结果尚未送达调用方;不会自动重复执行"}`, result.state === "failed" || result.state === "unknown" || result.delivered === false);
|
|
177
|
-
} catch (error) { notify("能力确认失败", errorMessage(error), true); }
|
|
178
|
-
finally { confirming = false; for (const item of pendingRows) item.sync(); }
|
|
179
|
-
try { await loadPending(true); } catch (error) { show(`待确认列表刷新失败:${errorMessage(error)}`, true); }
|
|
180
|
-
};
|
|
181
|
-
approve.onclick = () => void confirm(true); reject.onclick = () => void confirm(false);
|
|
182
|
-
actions.append(proof, approve, reject); pendingRows.push(pending); pending.sync(); pendingList.append(row);
|
|
183
|
-
}
|
|
184
|
-
if (!pendingList.children.length) emptyRow(pendingList, 7, "暂无待确认的能力调用");
|
|
185
|
-
}
|
|
186
60
|
async function bind(aid, pluginId) {
|
|
187
61
|
try {
|
|
188
62
|
await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
|
|
@@ -6,8 +6,6 @@
|
|
|
6
6
|
<header><h1>AUN 身份</h1><button id="refresh" type="button">刷新</button></header>
|
|
7
7
|
<p id="status" role="status"></p>
|
|
8
8
|
<div class="table-scroll"><table><thead><tr><th>AID</th><th>来源</th><th>已关联 Agent</th><th>操作</th></tr></thead><tbody id="identities"></tbody></table></div>
|
|
9
|
-
<section><h2>Agent 能力发布</h2><p>默认不发布。请为每个 AID 明确选择公开能力,并验证宿主本地委托策略中的 Owner AID;此配置不代表 AUN 协议中的 ownership 关系。验证通知通过 AUN 加密发送给指定 Owner,Owner 直接回复“确认”即可,不需要复制命令或令牌。发布能力后,每次调用仍须单独确认。</p><div class="table-scroll"><table><thead><tr><th>Provider AID</th><th>关联 Agent / 版本</th><th>宿主委托 Owner</th><th>可发布能力</th><th>操作</th></tr></thead><tbody id="abilities"></tbody></table></div></section>
|
|
10
|
-
<section><h2>待确认能力调用</h2><p>有效请求会出现在此表格,Peer 同时收到 pending 回执,Owner 会收到加密通知。Owner 审批记录保留 12 小时;协议 TTL 仍是调用最晚启动时间,超过后不能再执行。核对认证调用方、原始参数、契约指纹、影响和执行目标;没有当前会话的 Owner 证明时,不能确认执行。</p><div class="table-scroll"><table class="ability-pending-table"><thead><tr><th>Provider AID</th><th>认证调用方</th><th>操作与确认指纹</th><th>能力路径 / 契约版本</th><th>原始参数</th><th>影响、执行目标与有效期</th><th>逐次确认</th></tr></thead><tbody id="ability-pending"></tbody></table></div></section>
|
|
11
9
|
<form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
|
|
12
10
|
<form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from" placeholder="留空接受所有 Peer,填写 AID 后限制"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
|
|
13
11
|
</main>
|
|
@@ -17,14 +17,6 @@ table { width: 100%; min-width: 650px; border-collapse: collapse; font-size: 13p
|
|
|
17
17
|
th, td { padding: 9px 8px; text-align: left; border-bottom: 1px solid #dce3df; }
|
|
18
18
|
th { background: #edf1ef; color: #566761; }
|
|
19
19
|
td select { max-width: 190px; }
|
|
20
|
-
.ability-options label { display: flex; align-items: center; gap: 6px; }
|
|
21
|
-
.ability-options input { min-height: auto; }
|
|
22
|
-
.ability-owner { min-width: 220px; }
|
|
23
|
-
.ability-owner input { width: 100%; margin-bottom: 6px; }
|
|
24
20
|
td { vertical-align: top; }
|
|
25
|
-
td button { margin: 0 6px 6px 0; }
|
|
26
|
-
button:disabled { cursor: default; opacity: .55; }
|
|
27
|
-
.ability-pending-table { min-width: 1050px; }
|
|
28
|
-
.ability-pending-table pre { margin: 0; min-width: 160px; max-width: 340px; max-height: 260px; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font: inherit; }
|
|
29
21
|
.error { color: #a83427; }
|
|
30
22
|
@media (max-width: 620px) { .fields { grid-template-columns: 1fr; } }
|
package/package.json
CHANGED