agent-bridge-aun 1.0.48 → 1.0.50
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 +2 -2
- package/dist/src-ts/plugins/aun/ability-control.js +81 -0
- package/dist/src-ts/plugins/aun/index.js +371 -141
- package/dist/src-ts/plugins/aun/public/app.js +6 -130
- 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
|
@@ -23,7 +23,7 @@ mab aun repair-old-aids --json
|
|
|
23
23
|
|
|
24
24
|
安装后在 ABWeb 的插件管理中启动 AUN 通道。收到的 AID 消息仅投递给用户在 AUN 身份页中明确关联的 Agent;未关联的 AID 消息会被忽略。旧配置中的 `agent_plugin` 字段不再作为默认路由。配置字段 `owner_aid` 是可选字符串,用作默认 Owner。AID 创建接口为 `POST /api/plugins/aun/aid`,请求内容为 `{"aid":"新AID.agentid.cn","owner_aid":"所有者AID.agentid.cn"}`。
|
|
25
25
|
|
|
26
|
-
创建调用 SDK 0.5.31 的 `register(aid, { type: "agent", owner })`。注册、连接成功与 Owner 双边共识是不同状态;Owner 声明仍可能为 `single_sided/pending
|
|
26
|
+
创建调用 SDK 0.5.31 的 `register(aid, { type: "agent", owner })`。注册、连接成功与 Owner 双边共识是不同状态;Owner 声明仍可能为 `single_sided/pending`,不能把“已创建”解释为归属已激活。创建后先读取新鲜签名归属;关联请求已送达但 Owner 尚未确认时,接口返回 `202`、`status: "等待 Owner 确认"`,页面和 CLI 仅给普通提醒,日志为 `info`,不重复注册或发送邀请,也不尝试用 Owner 公钥登录。仅共识成立后尝试把新 AID 加入 Owner 名录,默认 `allow_pa_use: true`;只有本地拥有 Owner 私钥且 Storage 可用时才能同步,失败通过 `directorySync` 单独返回。Gateway 信任根使用随插件提供并校验指纹的 AUN 根证书,不从 Gateway 下载未经验证的 CA 链。
|
|
27
27
|
|
|
28
28
|
核心支持插件命令后,可运行 `mab aun status`、`mab aun list`、`mab aun agents`、`mab aun default-owner`、`mab aun default-owner set <owner-aid>`、`mab aun create <aid> [--owner <owner-aid>] [--agent <plugin-id>]`、`mab aun bind <aid> <plugin-id>` 和 `mab aun unbind <aid>`。关联或解绑只修改指定 AID,不会影响同一 Agent 下的其它 AID,也不会写入 Agent 全局 AID 配置。`default-owner` 不带参数显示当前配置,`default-owner set <owner-aid>` 校验格式并持久化新的默认值。`create` 明确传入 `--owner` 时优先使用该值;省略时回退到同一配置中的 `owner_aid`,两者都没有时返回错误,不会创建 AID。创建新身份时,实际使用的 Owner 会保存为下次默认值。ABWeb 从同一插件配置读取 `owner_aid` 并预填创建表单,因此 CLI 和 ABWeb 共用默认 Owner。Agent 从当前已就绪的插件动态发现。创建后连接或关联失败时,响应会标明 AID 已创建;先用 `mab aun list` 检查,不要重试创建。运行日志不记录原始参数或 Owner AID;默认 Owner 查询会在终端显示当前值。
|
|
29
29
|
|
|
@@ -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.50", 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 = [];
|
|
@@ -329,6 +332,7 @@ class AunPlugin {
|
|
|
329
332
|
this.context?.logger.info("AUN 身份已创建", { aid });
|
|
330
333
|
}
|
|
331
334
|
const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
|
|
335
|
+
let status = "已创建";
|
|
332
336
|
let directorySync;
|
|
333
337
|
try {
|
|
334
338
|
await this.syncOwnerDirectory(aid, owner);
|
|
@@ -336,10 +340,17 @@ class AunPlugin {
|
|
|
336
340
|
this.context?.logger.info("AUN 新 AID 已加入 Owner 名录", { aid });
|
|
337
341
|
}
|
|
338
342
|
catch (error) {
|
|
339
|
-
|
|
340
|
-
|
|
343
|
+
if (error instanceof fs_1.AunFsError && error.code === "OWNERSHIP_PENDING") {
|
|
344
|
+
status = "等待 Owner 确认";
|
|
345
|
+
directorySync = { status: "not_synced" };
|
|
346
|
+
this.context?.logger.info("AUN AID 已创建并连接,关联请求已发送,等待 Owner 确认", { aid });
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
directorySync = { status: "not_synced", error: safeError(error) };
|
|
350
|
+
this.context?.logger.warn("AUN AID 已创建,但 Owner 名录未同步", { aid, error: directorySync.error });
|
|
351
|
+
}
|
|
341
352
|
}
|
|
342
|
-
return { aid, status
|
|
353
|
+
return { aid, status, connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}), directorySync };
|
|
343
354
|
}
|
|
344
355
|
finally {
|
|
345
356
|
this.creating = false;
|
|
@@ -461,12 +472,25 @@ class AunPlugin {
|
|
|
461
472
|
async syncOwnerDirectory(aid, ownerAid) {
|
|
462
473
|
if (aid === ownerAid)
|
|
463
474
|
throw new Error("新 AID 与 Owner AID 相同,未写入 Owner 名录");
|
|
475
|
+
const subClient = this.clients.get(aid)?.client;
|
|
476
|
+
if (this.store?.resolve && subClient?.checkOwnershipConsensus) {
|
|
477
|
+
const verified = await (0, ownership_1.readAunOwnership)(this.store, subClient, aid);
|
|
478
|
+
if (verified.declaredOwner !== ownerAid)
|
|
479
|
+
throw new fs_1.AunFsError("OWNERSHIP_CONFLICT", "指定 Owner 与已验签的现有 Owner 不同,未同步名录");
|
|
480
|
+
if (verified.ownership.status === "single_sided")
|
|
481
|
+
throw new fs_1.AunFsError("OWNERSHIP_PENDING", "关联请求已发送,等待 Owner 确认;确认前暂不加入通信名录,请勿重复创建 AID");
|
|
482
|
+
if (verified.ownership.status !== "consensus")
|
|
483
|
+
throw new fs_1.AunFsError("OWNERSHIP_UNAVAILABLE", `Owner 归属尚未核验完成(${verified.ownership.status}),未同步名录`);
|
|
484
|
+
}
|
|
464
485
|
let client = this.ownerClients.get(ownerAid)?.client ?? this.clients.get(ownerAid)?.client;
|
|
465
486
|
let temporary = false;
|
|
466
487
|
if (!client) {
|
|
467
488
|
const loaded = this.store?.load(ownerAid);
|
|
468
489
|
if (!loaded?.ok || !loaded.data)
|
|
469
490
|
throw new Error("Owner AID 不在本地密钥库,AID 已创建但无法同步 Owner 名录");
|
|
491
|
+
// SDK load() 成功也可能只有公钥证书;list() 只返回持有有效私钥的身份。
|
|
492
|
+
if (!this.listStoreAids().includes(ownerAid))
|
|
493
|
+
throw new Error("本机没有 Owner 的有效私钥,AID 已创建但无法在本机同步 Owner 名录");
|
|
470
494
|
await this.prepareTrustRoots(ownerAid);
|
|
471
495
|
const trusted = this.store?.load(ownerAid);
|
|
472
496
|
if (!trusted?.ok || !trusted.data)
|
|
@@ -516,18 +540,38 @@ class AunPlugin {
|
|
|
516
540
|
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
541
|
}
|
|
518
542
|
async saveAbilityOwnerVerification(aid, ownerAid) {
|
|
519
|
-
const
|
|
520
|
-
|
|
543
|
+
const pending = this.abilityOwnerVerificationWrites.get(aid);
|
|
544
|
+
if (pending)
|
|
545
|
+
return pending;
|
|
546
|
+
if (this.isAbilityOwnerVerified(aid, ownerAid))
|
|
547
|
+
return;
|
|
548
|
+
const operation = Promise.resolve().then(async () => {
|
|
549
|
+
if (this.abilityOwners[aid] !== ownerAid)
|
|
550
|
+
throw new Error("Provider 的 Owner 配置已改变");
|
|
551
|
+
const previous = this.abilityOwnerVerifications[aid];
|
|
552
|
+
this.abilityOwnerVerifications = { ...this.abilityOwnerVerifications, [aid]: ownerAid };
|
|
553
|
+
try {
|
|
554
|
+
await this.persist();
|
|
555
|
+
}
|
|
556
|
+
catch (error) {
|
|
557
|
+
if (previous === undefined)
|
|
558
|
+
delete this.abilityOwnerVerifications[aid];
|
|
559
|
+
else
|
|
560
|
+
this.abilityOwnerVerifications[aid] = previous;
|
|
561
|
+
throw error;
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
this.abilityOwnerVerificationWrites.set(aid, operation);
|
|
521
565
|
try {
|
|
522
|
-
await
|
|
566
|
+
await operation;
|
|
523
567
|
}
|
|
524
|
-
|
|
525
|
-
this.
|
|
526
|
-
|
|
568
|
+
finally {
|
|
569
|
+
if (this.abilityOwnerVerificationWrites.get(aid) === operation)
|
|
570
|
+
this.abilityOwnerVerificationWrites.delete(aid);
|
|
527
571
|
}
|
|
528
572
|
}
|
|
529
573
|
isAbilityOwnerVerified(aid, ownerAid) {
|
|
530
|
-
return this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
|
|
574
|
+
return !this.abilityOwnerVerificationWrites.has(aid) && this.abilityOwners[aid] === ownerAid && this.abilityOwnerVerifications[aid] === ownerAid;
|
|
531
575
|
}
|
|
532
576
|
async activate(context) {
|
|
533
577
|
this.context = context;
|
|
@@ -570,6 +614,7 @@ class AunPlugin {
|
|
|
570
614
|
this.sdk ??= await loadSdk();
|
|
571
615
|
await (0, promises_1.mkdir)(this.path(), { recursive: true });
|
|
572
616
|
this.ownerProof.clear();
|
|
617
|
+
this.abilityApprovalViews.clear();
|
|
573
618
|
this.abilityAuditUnavailable = false;
|
|
574
619
|
try {
|
|
575
620
|
this.abilityOperations = await ability_operation_store_1.AbilityOperationStore.open((0, node_path_1.join)(context.dataDir, "ability-operations.json"));
|
|
@@ -806,6 +851,7 @@ class AunPlugin {
|
|
|
806
851
|
clearInterval(this.ownedProfileTimer);
|
|
807
852
|
this.ownedProfileTimer = undefined;
|
|
808
853
|
this.ownerProof.clear();
|
|
854
|
+
this.abilityApprovalViews.clear();
|
|
809
855
|
for (const { subscription, ownedSubscription } of this.clients.values()) {
|
|
810
856
|
subscription?.unsubscribe();
|
|
811
857
|
ownedSubscription?.unsubscribe();
|
|
@@ -983,42 +1029,52 @@ class AunPlugin {
|
|
|
983
1029
|
const paths = publish ? (Array.isArray(body.paths) && body.paths.every((path) => typeof path === "string") ? body.paths : undefined) : [];
|
|
984
1030
|
if (!paths)
|
|
985
1031
|
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 });
|
|
1032
|
+
const context = this.context;
|
|
1033
|
+
const session = context.managementSession?.(request);
|
|
1034
|
+
await this.setPublishedAbilities(aid, paths, () => this.context === context && Boolean(context.managementActor?.(request))
|
|
1035
|
+
&& (!paths.length || Boolean(session && context.managementSession?.(request)?.id === session.id)));
|
|
1036
|
+
return this.json(response, 200, { ok: true, aid, revoked: !paths.length });
|
|
1016
1037
|
}
|
|
1017
1038
|
catch (error) {
|
|
1018
1039
|
this.context?.logger.warn("AUN Ability 能力索引更新失败", { error: safeError(error) });
|
|
1019
1040
|
return this.json(response, 409, { error: safeError(error) });
|
|
1020
1041
|
}
|
|
1021
1042
|
};
|
|
1043
|
+
async setPublishedAbilities(aid, paths, authorized) {
|
|
1044
|
+
const owner = this.abilityOwners[aid];
|
|
1045
|
+
const assertAuthorized = () => {
|
|
1046
|
+
if (paths.length && (!owner || !this.isAbilityOwnerVerified(aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable || this.revokingAbilityAids.has(aid)))
|
|
1047
|
+
throw new Error("逐次 Owner 确认需可用操作账本和已验证的 Owner AID,请重新确认");
|
|
1048
|
+
if (!authorized())
|
|
1049
|
+
throw new Error("能力发布的管理身份已失效");
|
|
1050
|
+
};
|
|
1051
|
+
assertAuthorized();
|
|
1052
|
+
if (!paths.length) {
|
|
1053
|
+
await this.revokePublishedAbilities(aid);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1057
|
+
assertAuthorized();
|
|
1058
|
+
const storage = this.clients.get(aid)?.client.storage;
|
|
1059
|
+
if (!storage)
|
|
1060
|
+
throw new Error("AUN 身份存储不可用");
|
|
1061
|
+
const pluginId = this.bindings[aid];
|
|
1062
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1063
|
+
assertAuthorized();
|
|
1064
|
+
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(); } });
|
|
1065
|
+
this.abilityLedgers[aid] = published.ledger;
|
|
1066
|
+
this.abilitySelections[aid] = [...new Set(paths)];
|
|
1067
|
+
await this.persist();
|
|
1068
|
+
});
|
|
1069
|
+
this.abilityPublications.set(aid, operation);
|
|
1070
|
+
try {
|
|
1071
|
+
await operation;
|
|
1072
|
+
}
|
|
1073
|
+
finally {
|
|
1074
|
+
if (this.abilityPublications.get(aid) === operation)
|
|
1075
|
+
this.abilityPublications.delete(aid);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1022
1078
|
challengeAbilityOwner = async (request, response) => {
|
|
1023
1079
|
const session = this.context?.managementSession?.(request);
|
|
1024
1080
|
if (!this.context?.managementActor?.(request) || !session)
|
|
@@ -1035,6 +1091,9 @@ class AunPlugin {
|
|
|
1035
1091
|
this.revokingAbilityAids.add(aid);
|
|
1036
1092
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1037
1093
|
this.ownerProof.clear(aid);
|
|
1094
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1095
|
+
if (JSON.parse(key)[0] === aid)
|
|
1096
|
+
this.abilityApprovalViews.delete(key);
|
|
1038
1097
|
try {
|
|
1039
1098
|
await this.invalidateAbilityOperations(aid);
|
|
1040
1099
|
const previous = this.abilityOwners;
|
|
@@ -1078,84 +1137,233 @@ class AunPlugin {
|
|
|
1078
1137
|
const session = this.context?.managementSession?.(request);
|
|
1079
1138
|
if (!this.context?.managementActor?.(request) || !session)
|
|
1080
1139
|
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
1140
|
try {
|
|
1085
1141
|
const body = await this.readJsonBody(request);
|
|
1086
1142
|
if (typeof body.key !== "string" || typeof body.confirmationFingerprint !== "string" || typeof body.approved !== "boolean")
|
|
1087
1143
|
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
|
-
|
|
1144
|
+
const context = this.context;
|
|
1145
|
+
const result = await this.decideAbilityOperation(body.key, body.confirmationFingerprint, body.approved, () => this.context === context && Boolean(context.managementActor?.(request)) && context.managementSession?.(request)?.id === session.id);
|
|
1146
|
+
this.json(response, 200, result);
|
|
1147
|
+
}
|
|
1148
|
+
catch (error) {
|
|
1149
|
+
this.json(response, error instanceof AunCommandError ? error.statusCode : 409, { error: safeError(error) });
|
|
1150
|
+
}
|
|
1151
|
+
};
|
|
1152
|
+
async decideAbilityOperation(key, fingerprint, approved, authorized, providerAid) {
|
|
1153
|
+
const store = this.abilityOperations;
|
|
1154
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1155
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1156
|
+
const original = await store.get(key);
|
|
1157
|
+
if (!original || (providerAid !== undefined && original.providerAid !== providerAid))
|
|
1158
|
+
throw new AunCommandError("能力操作不存在", 404);
|
|
1159
|
+
const ownerAid = this.abilityOwners[original.providerAid];
|
|
1160
|
+
if (!ownerAid || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1161
|
+
throw new AunCommandError("需完成该 Provider 的 Owner AID 验证", 403);
|
|
1162
|
+
if (original.confirmationFingerprint !== fingerprint)
|
|
1163
|
+
throw new AunCommandError("确认指纹与待确认请求不一致", 409);
|
|
1164
|
+
const revision = this.abilityRevisions.get(original.providerAid) ?? 0;
|
|
1165
|
+
if (approved && original.state === "pending_confirmation") {
|
|
1166
|
+
const failure = await this.abilityInvocationFailure(original.providerAid, original.authenticatedCaller, original.path, original.definitionId, original.params);
|
|
1167
|
+
const pluginId = this.bindings[original.providerAid];
|
|
1168
|
+
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) ?? ""))
|
|
1169
|
+
throw new AunCommandError(failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用", 409);
|
|
1170
|
+
}
|
|
1171
|
+
if (!authorized() || !this.acceptingMessages || !this.isAbilityOwnerVerified(original.providerAid, ownerAid))
|
|
1172
|
+
throw new AunCommandError("通道或 Owner 管理会话已失效", 403);
|
|
1173
|
+
const confirmation = approved ? await store.approve(original.key, fingerprint) : await store.reject(original.key, fingerprint);
|
|
1174
|
+
let entry = confirmation.entry;
|
|
1175
|
+
if (confirmation.started) {
|
|
1176
|
+
let result;
|
|
1177
|
+
let state;
|
|
1178
|
+
let handlerAuthorized = false;
|
|
1179
|
+
try {
|
|
1180
|
+
const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
|
|
1181
|
+
const messageExpired = Date.now() >= entry.expiresAt;
|
|
1182
|
+
const confirmationExpired = Date.now() >= entry.confirmationExpiresAt;
|
|
1183
|
+
const expired = messageExpired || confirmationExpired;
|
|
1184
|
+
const invalidated = (this.abilityRevisions.get(entry.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(entry.providerAid);
|
|
1185
|
+
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) {
|
|
1186
|
+
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" };
|
|
1187
|
+
state = "failed";
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
const context = this.context;
|
|
1191
|
+
const confirmed = entry;
|
|
1192
|
+
const beforeStart = () => {
|
|
1193
|
+
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !authorized() || !this.isAbilityOwnerVerified(confirmed.providerAid, ownerAid) || this.abilityOwners[confirmed.providerAid] !== ownerAid)
|
|
1194
|
+
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
1195
|
+
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
1196
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1197
|
+
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)))
|
|
1198
|
+
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
1199
|
+
handlerAuthorized = true;
|
|
1200
|
+
};
|
|
1201
|
+
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
1202
|
+
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
1203
|
+
state = "succeeded";
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
catch (error) {
|
|
1207
|
+
this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
|
|
1208
|
+
const code = error.code;
|
|
1209
|
+
const notExecuted = !handlerAuthorized && ["message_expired", "confirmation_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
|
|
1210
|
+
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" };
|
|
1211
|
+
state = "failed";
|
|
1212
|
+
}
|
|
1213
|
+
entry = await store.finish(entry.key, state, result);
|
|
1214
|
+
}
|
|
1215
|
+
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
1216
|
+
this.context?.logger.info("AUN Ability 逐次确认完成", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
1217
|
+
return { ok: true, state: entry.state, result: entry.result, delivered };
|
|
1218
|
+
}
|
|
1219
|
+
abilityApprovalViewKey(aid, ownerAid, threadId) {
|
|
1220
|
+
return JSON.stringify([aid, ownerAid, threadId ?? ""]);
|
|
1221
|
+
}
|
|
1222
|
+
async sendAbilityControlText(aid, ownerAid, text, threadId) {
|
|
1223
|
+
const chunks = [];
|
|
1224
|
+
const characters = Array.from(text);
|
|
1225
|
+
for (let offset = 0; offset < characters.length; offset += 4000)
|
|
1226
|
+
chunks.push(characters.slice(offset, offset + 4000).join(""));
|
|
1227
|
+
for (const [index, part] of chunks.entries()) {
|
|
1228
|
+
const message = chunks.length > 1 ? `Ability 管理消息(第 ${index + 1}/${chunks.length} 段,收全后再确认)\n${part}` : part;
|
|
1229
|
+
const sent = await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text: message, ...(threadId ? { thread_id: threadId } : {}) } });
|
|
1230
|
+
if (typeof sent?.status !== "string" || !successfulSendStatuses.has(sent.status))
|
|
1231
|
+
throw new Error(`AUN 管理回复投递状态未确认:${String(sent?.status ?? "未返回状态")}`);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
abilityEntrySummary(entry) {
|
|
1235
|
+
return [
|
|
1236
|
+
`操作键:${entry.key}`,
|
|
1237
|
+
`确认指纹:${entry.confirmationFingerprint}`,
|
|
1238
|
+
`调用方:${entry.authenticatedCaller}`,
|
|
1239
|
+
`Provider AID:${entry.providerAid}`,
|
|
1240
|
+
`能力路径:${entry.path}`,
|
|
1241
|
+
`definition_id:${entry.definitionId}`,
|
|
1242
|
+
`原始参数:${JSON.stringify(entry.params)}`,
|
|
1243
|
+
`影响:${entry.effects.join("、")}`,
|
|
1244
|
+
`执行目标:${String(entry.executionTarget.pluginId)}(${String(entry.executionTarget.pluginVersion)})`,
|
|
1245
|
+
`operation_id:${entry.operationId}`,
|
|
1246
|
+
`审批截止:${new Date(entry.confirmationExpiresAt).toLocaleString("zh-CN")}`,
|
|
1247
|
+
`最晚启动:${new Date(entry.expiresAt).toLocaleString("zh-CN")}`,
|
|
1248
|
+
`确认:/ability approve ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1249
|
+
`拒绝:/ability reject ${entry.key} ${entry.confirmationFingerprint}`,
|
|
1250
|
+
].join("\n");
|
|
1251
|
+
}
|
|
1252
|
+
async showAbilityPending(aid, ownerAid, threadId) {
|
|
1253
|
+
const store = this.abilityOperations;
|
|
1254
|
+
if (!store || this.abilityAuditUnavailable)
|
|
1255
|
+
throw new AunCommandError("持久操作账本不可用", 503);
|
|
1256
|
+
const entries = (await store.pending()).filter(entry => entry.providerAid === aid);
|
|
1257
|
+
if (!entries.length) {
|
|
1258
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1259
|
+
await this.sendAbilityControlText(aid, ownerAid, "当前没有待确认的 Ability 调用。", threadId);
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const viewKey = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1263
|
+
const view = { explicit: true, entries: undefined };
|
|
1264
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
1265
|
+
const text = `待确认 Ability 调用清单(${entries.length} 项,仅包含本条消息展示的固定记录)\n\n${entries.map((entry, index) => `第 ${index + 1} 项\n${this.abilityEntrySummary(entry)}`).join("\n\n---\n\n")}\n\n回复“确认”或“拒绝”按本清单逐项处理;清单之后的新请求不会加入。`;
|
|
1266
|
+
await this.sendAbilityControlText(aid, ownerAid, text, threadId);
|
|
1267
|
+
if (this.abilityApprovalViews.get(viewKey) === view && this.abilityOperations === store && this.abilityOwners[aid] === ownerAid && this.acceptingMessages)
|
|
1268
|
+
view.entries = entries.map(({ key, confirmationFingerprint }) => ({ key, confirmationFingerprint }));
|
|
1269
|
+
}
|
|
1270
|
+
async processAbilityControl(payload, aid) {
|
|
1271
|
+
const raw = payload && typeof payload === "object" ? payload : undefined;
|
|
1272
|
+
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1273
|
+
const text = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1274
|
+
const isControlText = text === "确认" || text === "拒绝" || /^ *\/ability(?:\s|$)/.test(text);
|
|
1275
|
+
if (!isControlText)
|
|
1276
|
+
return false;
|
|
1277
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1278
|
+
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1279
|
+
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1280
|
+
if (ownerAid && sender !== ownerAid)
|
|
1281
|
+
return text === "确认" || text === "拒绝" ? false : true;
|
|
1282
|
+
if (!ownerAid && (text === "确认" || text === "拒绝"))
|
|
1283
|
+
return false;
|
|
1284
|
+
let authenticated = false;
|
|
1285
|
+
const context = this.context;
|
|
1286
|
+
const store = this.abilityOperations;
|
|
1287
|
+
const revision = this.abilityRevisions.get(aid) ?? 0;
|
|
1288
|
+
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);
|
|
1289
|
+
try {
|
|
1290
|
+
const authPayload = typeof raw?.payload === "string" ? "/ability help" : { ...body, text: "/ability help" };
|
|
1291
|
+
(0, ability_control_1.parseAbilityControlMessage)({ ...raw, payload: authPayload }, aid, ownerAid);
|
|
1292
|
+
authenticated = true;
|
|
1293
|
+
const message = (0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid);
|
|
1294
|
+
if (!message)
|
|
1295
|
+
return false;
|
|
1296
|
+
if (message.messageId && !this.seen.add(`ability-control:${aid}:${message.messageId}`))
|
|
1297
|
+
return true;
|
|
1298
|
+
if (!this.isAbilityOwnerVerified(aid, ownerAid))
|
|
1299
|
+
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1300
|
+
const { command, peerId, threadId } = message;
|
|
1301
|
+
if (command.action === "confirm-view" || command.action === "reject-view") {
|
|
1302
|
+
const key = this.abilityApprovalViewKey(aid, ownerAid, threadId);
|
|
1303
|
+
const view = this.abilityApprovalViews.get(key);
|
|
1304
|
+
this.abilityApprovalViews.delete(key);
|
|
1305
|
+
if (!view?.entries?.length) {
|
|
1306
|
+
await this.sendAbilityControlText(aid, peerId, "当前话题没有已展示的待确认清单。请先发送 /ability pending,核对清单后再回复确认或拒绝。", threadId);
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
const approved = command.action === "confirm-view";
|
|
1310
|
+
const results = [];
|
|
1311
|
+
for (const entry of view.entries) {
|
|
1312
|
+
try {
|
|
1313
|
+
const result = await this.decideAbilityOperation(entry.key, entry.confirmationFingerprint, approved, authorized, aid);
|
|
1314
|
+
results.push(`${entry.key.slice(0, 12)}:${String(result.state)}`);
|
|
1120
1315
|
}
|
|
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";
|
|
1316
|
+
catch (error) {
|
|
1317
|
+
results.push(`${entry.key.slice(0, 12)}:未处理(${safeError(error)})`);
|
|
1137
1318
|
}
|
|
1138
1319
|
}
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1320
|
+
await this.sendAbilityControlText(aid, peerId, `${approved ? "确认" : "拒绝"}处理完成:\n${results.join("\n")}`, threadId);
|
|
1321
|
+
return true;
|
|
1322
|
+
}
|
|
1323
|
+
if (command.action === "help") {
|
|
1324
|
+
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);
|
|
1325
|
+
}
|
|
1326
|
+
else if (command.action === "list") {
|
|
1327
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1328
|
+
const selected = new Set(this.abilitySelections[aid] ?? []);
|
|
1329
|
+
const lines = definitions.map(item => `${selected.has(item.path) ? "已发布" : "未发布"} ${item.path} | ${item.name} | ${(0, ability_publication_1.abilityDefinitionId)({ ...item })}`);
|
|
1330
|
+
await this.sendAbilityControlText(aid, peerId, lines.length ? lines.join("\n") : "当前关联 Agent 没有可发布的 Ability。", threadId);
|
|
1331
|
+
}
|
|
1332
|
+
else if (command.action === "publish") {
|
|
1333
|
+
await this.setPublishedAbilities(aid, command.paths, authorized);
|
|
1334
|
+
await this.sendAbilityControlText(aid, peerId, `已发布 ${command.paths.length} 项 Ability:\n${command.paths.join("\n")}`, threadId);
|
|
1335
|
+
}
|
|
1336
|
+
else if (command.action === "revoke") {
|
|
1337
|
+
this.abilityApprovalViews.delete(this.abilityApprovalViewKey(aid, ownerAid, threadId));
|
|
1338
|
+
await this.setPublishedAbilities(aid, [], authorized);
|
|
1339
|
+
await this.sendAbilityControlText(aid, peerId, "已撤销本 Provider 的 Ability 索引,并使现有待确认操作失效。", threadId);
|
|
1340
|
+
}
|
|
1341
|
+
else if (command.action === "pending")
|
|
1342
|
+
await this.showAbilityPending(aid, peerId, threadId);
|
|
1343
|
+
else if (command.action === "approve" || command.action === "reject") {
|
|
1344
|
+
const result = await this.decideAbilityOperation(command.key, command.confirmationFingerprint, command.action === "approve", authorized, aid);
|
|
1345
|
+
await this.sendAbilityControlText(aid, peerId, `Ability 操作状态:${String(result.state)};结果已${result.delivered ? "回传 Peer" : "保存,等待重投"}。`, threadId);
|
|
1147
1346
|
}
|
|
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
1347
|
}
|
|
1152
1348
|
catch (error) {
|
|
1153
|
-
|
|
1349
|
+
const replyAid = authenticated ? ownerAid : error.code === "ability_owner_unconfigured" ? sender : undefined;
|
|
1350
|
+
if (replyAid) {
|
|
1351
|
+
try {
|
|
1352
|
+
await this.sendAbilityControlText(aid, replyAid, `Ability 管理操作未完成:${safeError(error)}`, typeof body?.thread_id === "string" ? body.thread_id : undefined);
|
|
1353
|
+
}
|
|
1354
|
+
catch (replyError) {
|
|
1355
|
+
this.context?.logger.warn("AUN Ability 管理错误回复失败", { aid, ownerAid, error: safeError(replyError) });
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1154
1358
|
}
|
|
1155
|
-
|
|
1359
|
+
return true;
|
|
1360
|
+
}
|
|
1156
1361
|
async revokePublishedAbilities(aid) {
|
|
1157
1362
|
this.revokingAbilityAids.add(aid);
|
|
1158
1363
|
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
1364
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1365
|
+
if (JSON.parse(key)[0] === aid)
|
|
1366
|
+
this.abilityApprovalViews.delete(key);
|
|
1159
1367
|
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
1160
1368
|
await this.invalidateAbilityOperations(aid);
|
|
1161
1369
|
this.abilitySelections[aid] = [];
|
|
@@ -1431,8 +1639,8 @@ class AunPlugin {
|
|
|
1431
1639
|
let task;
|
|
1432
1640
|
let operationError;
|
|
1433
1641
|
if (verified.ownership.status !== "consensus") {
|
|
1434
|
-
const local = this.store?.load(ownerAid);
|
|
1435
|
-
const connectedOwner = this.clients.get(ownerAid)?.client;
|
|
1642
|
+
const local = this.listStoreAids().includes(ownerAid) ? this.store?.load(ownerAid) : undefined;
|
|
1643
|
+
const connectedOwner = this.ownerClients.get(ownerAid)?.client ?? this.clients.get(ownerAid)?.client;
|
|
1436
1644
|
let temporary;
|
|
1437
1645
|
try {
|
|
1438
1646
|
if (connectedOwner || local?.ok && local.data) {
|
|
@@ -1579,7 +1787,7 @@ class AunPlugin {
|
|
|
1579
1787
|
await this.requireAgent(agent);
|
|
1580
1788
|
}
|
|
1581
1789
|
const created = await this.createIdentity(aid, owner);
|
|
1582
|
-
const data = { created: true, aid, connection: created.connection ?? "ready", directorySync: created.directorySync };
|
|
1790
|
+
const data = { created: true, aid, connection: created.connection ?? "ready", directorySync: created.directorySync, ...(created.status === "等待 Owner 确认" ? { status: created.status } : {}) };
|
|
1583
1791
|
if (created.connection !== "ready")
|
|
1584
1792
|
throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
|
|
1585
1793
|
if (agent) {
|
|
@@ -1592,9 +1800,10 @@ class AunPlugin {
|
|
|
1592
1800
|
}
|
|
1593
1801
|
}
|
|
1594
1802
|
return {
|
|
1595
|
-
message: created.
|
|
1596
|
-
|
|
1597
|
-
|
|
1803
|
+
message: created.status === "等待 Owner 确认" ? `AID 已创建并连接${agent ? `并关联到 ${agent}` : ""},关联请求已发送,等待 Owner 确认`
|
|
1804
|
+
: created.directorySync?.status === "not_synced"
|
|
1805
|
+
? `AID 已创建${agent ? `并关联到 ${agent}` : "并连接"},但 Owner 名录未同步:${created.directorySync.error ?? "原因未知"}`
|
|
1806
|
+
: agent ? `AID 已创建并关联到 ${agent}` : "AID 已创建并连接",
|
|
1598
1807
|
data: { ...data, ...(agent ? { agent } : {}) },
|
|
1599
1808
|
};
|
|
1600
1809
|
}
|
|
@@ -1752,22 +1961,31 @@ class AunPlugin {
|
|
|
1752
1961
|
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1753
1962
|
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1754
1963
|
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1755
|
-
|
|
1964
|
+
const inboundText = typeof raw?.payload === "string" ? raw.payload : typeof body?.text === "string" ? body.text : "";
|
|
1965
|
+
const inboundThreadId = typeof body?.thread_id === "string" ? body.thread_id : undefined;
|
|
1966
|
+
if (inboundText === "确认" && this.ownerProof.hasPendingConfirmation(aid)) {
|
|
1756
1967
|
const ownerAid = this.abilityOwners[aid];
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1968
|
+
let authenticated = false;
|
|
1969
|
+
try {
|
|
1970
|
+
authenticated = Boolean((0, ability_control_1.parseAbilityControlMessage)(payload, aid, ownerAid));
|
|
1971
|
+
}
|
|
1972
|
+
catch { /* 无效信封不能验证 Owner 身份。 */ }
|
|
1973
|
+
if (!authenticated || !this.ownerProof.acceptConfirmation(aid, sender, inboundText, true)) {
|
|
1761
1974
|
this.context?.logger.warn("AUN Owner 确认未通过验证", { traceId: inboundId, aid, sender: sender ?? "", stage: "Owner 确认校验", reason: authenticated ? "没有有效的待确认通知" : "需要配置 Owner AID 通过加密消息直接回复确认" });
|
|
1762
1975
|
return;
|
|
1763
1976
|
}
|
|
1977
|
+
if (typeof raw?.message_id === "string")
|
|
1978
|
+
this.seen.add(`ability-control:${aid}:${raw.message_id}`);
|
|
1979
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
1980
|
+
if (JSON.parse(key)[0] === aid)
|
|
1981
|
+
this.abilityApprovalViews.delete(key);
|
|
1764
1982
|
try {
|
|
1765
1983
|
await this.saveAbilityOwnerVerification(aid, ownerAid);
|
|
1766
1984
|
}
|
|
1767
1985
|
catch (error) {
|
|
1768
1986
|
this.context?.logger.error("AUN Owner 验证状态保存失败", { traceId: inboundId, aid, ownerAid, stage: "保存 Owner 验证", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1769
1987
|
try {
|
|
1770
|
-
await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。",
|
|
1988
|
+
await this.sendText(aid, sender, "Owner 控制权确认失败:验证结果无法持久保存,请检查 AUN 插件配置写入权限。", inboundThreadId, true);
|
|
1771
1989
|
}
|
|
1772
1990
|
catch (replyError) {
|
|
1773
1991
|
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: false, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
@@ -1775,7 +1993,7 @@ class AunPlugin {
|
|
|
1775
1993
|
return;
|
|
1776
1994
|
}
|
|
1777
1995
|
try {
|
|
1778
|
-
await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner
|
|
1996
|
+
await this.sendText(aid, sender, `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`, inboundThreadId, true);
|
|
1779
1997
|
}
|
|
1780
1998
|
catch (error) {
|
|
1781
1999
|
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified: true, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
@@ -1787,6 +2005,9 @@ class AunPlugin {
|
|
|
1787
2005
|
if (typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text)) {
|
|
1788
2006
|
let verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
|
|
1789
2007
|
if (verified) {
|
|
2008
|
+
for (const key of this.abilityApprovalViews.keys())
|
|
2009
|
+
if (JSON.parse(key)[0] === aid)
|
|
2010
|
+
this.abilityApprovalViews.delete(key);
|
|
1790
2011
|
const ownerAid = this.abilityOwners[aid];
|
|
1791
2012
|
try {
|
|
1792
2013
|
if (ownerAid === sender)
|
|
@@ -1802,10 +2023,10 @@ class AunPlugin {
|
|
|
1802
2023
|
this.context?.logger.info("AUN 宿主委托的 Owner 控制权验证已处理", { aid, verified });
|
|
1803
2024
|
if (sender && raw?.encrypted === true && (envelope?.from === undefined || envelope.from === sender)) {
|
|
1804
2025
|
const text = verified
|
|
1805
|
-
? `Owner 控制权验证成功:${aid} 的 Owner
|
|
2026
|
+
? `Owner 控制权验证成功:${aid} 的 Owner 已保存。此操作不会批准任何能力调用,请发送 /ability help 查看管理命令。`
|
|
1806
2027
|
: !ownerCommand
|
|
1807
|
-
? "Owner
|
|
1808
|
-
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner
|
|
2028
|
+
? "Owner 控制权验证失败:指令格式不正确。请使用已配置的 Owner AID 发送 /ability help。"
|
|
2029
|
+
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请使用已配置的 Owner AID 发送 /ability help 重新验证。";
|
|
1809
2030
|
try {
|
|
1810
2031
|
await this.sendText(aid, sender, text, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
|
|
1811
2032
|
}
|
|
@@ -1815,6 +2036,8 @@ class AunPlugin {
|
|
|
1815
2036
|
}
|
|
1816
2037
|
return;
|
|
1817
2038
|
}
|
|
2039
|
+
if (await this.processAbilityControl(payload, aid))
|
|
2040
|
+
return;
|
|
1818
2041
|
if ((0, ability_operations_1.isAbilityMessage)(payload)) {
|
|
1819
2042
|
if (this.revokingAbilityAids.has(aid)) {
|
|
1820
2043
|
this.context?.logger.warn("AUN 身份正在撤销能力索引,能力调用已阻止", { aid, traceId: inboundId, stage: "撤销保护" });
|
|
@@ -2033,24 +2256,25 @@ class AunPlugin {
|
|
|
2033
2256
|
}
|
|
2034
2257
|
async notifyAbilityOwner(entry) {
|
|
2035
2258
|
const ownerAid = String(entry.executionTarget.ownerAid ?? "");
|
|
2036
|
-
const
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
].join("\n");
|
|
2259
|
+
const viewKey = this.abilityApprovalViewKey(entry.providerAid, ownerAid);
|
|
2260
|
+
const existingView = this.abilityApprovalViews.get(viewKey);
|
|
2261
|
+
const view = existingView ?? {};
|
|
2262
|
+
if (!existingView)
|
|
2263
|
+
this.abilityApprovalViews.set(viewKey, view);
|
|
2264
|
+
const pending = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2265
|
+
if (pending.length !== 1 && !view.explicit)
|
|
2266
|
+
view.entries = undefined;
|
|
2267
|
+
const text = `有新的远程 Ability 调用申请\n${this.abilityEntrySummary(entry)}\n\n审批窗口最长 12 小时,不会延长调用最晚启动时间或协议 TTL。\n只有本话题中唯一、已完整投递的申请可直接回复“确认”或“拒绝”;多项、已处理或不确定时先发送 /ability pending 查看固定清单。`;
|
|
2046
2268
|
try {
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2269
|
+
await this.sendAbilityControlText(entry.providerAid, ownerAid, text);
|
|
2270
|
+
const current = (await this.abilityOperations?.pending() ?? []).filter(item => item.providerAid === entry.providerAid);
|
|
2271
|
+
if (!existingView && this.abilityApprovalViews.get(viewKey) === view)
|
|
2272
|
+
view.entries = current.length === 1 && current[0].key === entry.key && this.abilityOwners[entry.providerAid] === ownerAid && this.acceptingMessages
|
|
2273
|
+
? [{ key: entry.key, confirmationFingerprint: entry.confirmationFingerprint }] : undefined;
|
|
2274
|
+
this.context?.logger.info("AUN Ability Owner 申请通知已投递", { aid: entry.providerAid, ownerAid, callerAid: entry.authenticatedCaller, path: entry.path, operationId: entry.operationId, stage: "Owner 申请通知" });
|
|
2051
2275
|
}
|
|
2052
2276
|
catch (error) {
|
|
2053
|
-
this.context?.logger.warn("AUN Ability Owner
|
|
2277
|
+
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
2278
|
}
|
|
2055
2279
|
}
|
|
2056
2280
|
async abilityInvocationFailure(aid, callerAid, path, definitionId, params) {
|
|
@@ -2106,6 +2330,7 @@ class AunPlugin {
|
|
|
2106
2330
|
}
|
|
2107
2331
|
async processMessage(message, aid, agentPlugin, inboundId) {
|
|
2108
2332
|
let deliveryFailed = false;
|
|
2333
|
+
let failureText;
|
|
2109
2334
|
const trace = { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", messageId: message.messageId ?? "", agentPlugin };
|
|
2110
2335
|
this.context?.logger.info("AUN 收到任务", { ...trace, stage: "接收消息", attachments: message.attachments?.length ?? 0 });
|
|
2111
2336
|
const client = this.clients.get(aid)?.client;
|
|
@@ -2153,7 +2378,9 @@ class AunPlugin {
|
|
|
2153
2378
|
}
|
|
2154
2379
|
catch (error) {
|
|
2155
2380
|
deliveryFailed = true;
|
|
2156
|
-
|
|
2381
|
+
const failure = new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
|
|
2382
|
+
failureText = `回复回传失败:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2383
|
+
throw failure;
|
|
2157
2384
|
}
|
|
2158
2385
|
},
|
|
2159
2386
|
replyAttachments: async (items, text, signal) => {
|
|
@@ -2223,6 +2450,7 @@ class AunPlugin {
|
|
|
2223
2450
|
deliveryFailed = true;
|
|
2224
2451
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
2225
2452
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
2453
|
+
failureText = `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`;
|
|
2226
2454
|
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
2227
2455
|
try {
|
|
2228
2456
|
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true, "markdown");
|
|
@@ -2233,12 +2461,13 @@ class AunPlugin {
|
|
|
2233
2461
|
},
|
|
2234
2462
|
};
|
|
2235
2463
|
const taskId = message.messageId || inboundId;
|
|
2236
|
-
const notifyStatus = (status) => {
|
|
2464
|
+
const notifyStatus = (status, text) => {
|
|
2237
2465
|
if (!client?.notify)
|
|
2238
2466
|
return;
|
|
2239
2467
|
const params = {
|
|
2240
2468
|
type: "task.status", status, task_id: taskId, session_id: taskId,
|
|
2241
2469
|
severity: status === "error" ? "error" : "info", initiator: message.peerId,
|
|
2470
|
+
...(text ? { text } : {}),
|
|
2242
2471
|
...(message.threadId ? { thread_id: message.threadId } : {}),
|
|
2243
2472
|
...(message.messageId ? { ref_message_id: message.messageId } : {}),
|
|
2244
2473
|
};
|
|
@@ -2263,10 +2492,11 @@ class AunPlugin {
|
|
|
2263
2492
|
catch (error) {
|
|
2264
2493
|
failed = true;
|
|
2265
2494
|
this.context?.logger.warn("AUN 消息处理失败", { ...trace, stage: deliveryFailed ? "回传回复" : "执行 Agent 任务", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
2495
|
+
const action = deliveryFailed ? "回复回传失败" : attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2496
|
+
failureText ??= `${action}:${safeError((0, agent_1.errorDetails)(error))}`;
|
|
2266
2497
|
if (!deliveryFailed) {
|
|
2267
|
-
const action = attachments.length > 0 ? "附件处理失败" : newSessionCommand.test(message.text.trim()) ? "新建会话失败" : "消息处理失败";
|
|
2268
2498
|
try {
|
|
2269
|
-
await channelMessage.reply(
|
|
2499
|
+
await channelMessage.reply(failureText);
|
|
2270
2500
|
}
|
|
2271
2501
|
catch (replyError) {
|
|
2272
2502
|
this.context?.logger.warn("AUN 错误回复失败", { ...trace, stage: "发送最终错误消息", error: safeError((0, agent_1.errorDetails)(replyError)) });
|
|
@@ -2275,7 +2505,7 @@ class AunPlugin {
|
|
|
2275
2505
|
}
|
|
2276
2506
|
finally {
|
|
2277
2507
|
clearInterval(progressTimer);
|
|
2278
|
-
notifyStatus(failed || deliveryFailed ? "error" : "completed");
|
|
2508
|
+
notifyStatus(failed || deliveryFailed ? "error" : "completed", failureText);
|
|
2279
2509
|
}
|
|
2280
2510
|
}
|
|
2281
2511
|
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 });
|
|
@@ -199,14 +73,16 @@
|
|
|
199
73
|
show("正在创建 AID 并连接,请稍候……");
|
|
200
74
|
void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
|
|
201
75
|
const failed = result.connection === "degraded";
|
|
202
|
-
const
|
|
76
|
+
const pending = result.status === "等待 Owner 确认";
|
|
77
|
+
const directoryFailed = !pending && result.directorySync?.status === "not_synced";
|
|
203
78
|
const message = [
|
|
204
79
|
failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
|
|
205
|
-
:
|
|
206
|
-
|
|
80
|
+
: `${aid} 已创建并连接`,
|
|
81
|
+
pending ? "关联请求已发送,等待 Owner 确认;确认前暂不加入通信名录,请勿重复创建 AID。"
|
|
82
|
+
: directoryFailed ? `Owner 名录未同步:${result.directorySync.error || "原因未知"}`
|
|
207
83
|
: result.directorySync?.status === "synced" ? "已加入 Owner 名录,个人助理可按默认权限使用该联系人。" : "Owner 名录同步状态未知,请查看运行日志。",
|
|
208
84
|
].join("\n");
|
|
209
|
-
notify(failed || directoryFailed ? "AID 创建完成,部分步骤失败" : "AID 创建结果", message, failed || directoryFailed);
|
|
85
|
+
notify(failed || directoryFailed ? "AID 创建完成,部分步骤失败" : pending ? "等待 Owner 确认" : "AID 创建结果", message, failed || directoryFailed);
|
|
210
86
|
return load();
|
|
211
87
|
}).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
|
|
212
88
|
};
|
|
@@ -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