agent-bridge-aun 1.0.40 → 1.0.44
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 -0
- package/config.schema.json +120 -13
- package/dist/src-ts/plugins/aun/ability-operation-store.js +318 -0
- package/dist/src-ts/plugins/aun/ability-operations.js +42 -0
- package/dist/src-ts/plugins/aun/ability-owner.js +71 -0
- package/dist/src-ts/plugins/aun/ability-publication.js +165 -0
- package/dist/src-ts/plugins/aun/index.js +487 -13
- package/dist/src-ts/plugins/aun/messages.js +1 -1
- package/dist/src-ts/plugins/aun/public/app.js +127 -1
- package/dist/src-ts/plugins/aun/public/index.html +3 -1
- package/dist/src-ts/plugins/aun/public/style.css +9 -0
- package/package.json +3 -2
- package/plugin.json +1 -1
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.AunPlugin = void 0;
|
|
4
7
|
const promises_1 = require("node:fs/promises");
|
|
@@ -8,12 +11,17 @@ const node_module_1 = require("node:module");
|
|
|
8
11
|
const node_path_1 = require("node:path");
|
|
9
12
|
const node_os_1 = require("node:os");
|
|
10
13
|
const node_url_1 = require("node:url");
|
|
14
|
+
const _2020_1 = __importDefault(require("ajv/dist/2020"));
|
|
11
15
|
const agent_1 = require("../../agent");
|
|
12
16
|
const evolcore_1 = require("./evolcore");
|
|
13
17
|
const messages_1 = require("./messages");
|
|
14
18
|
const attachments_1 = require("./attachments");
|
|
19
|
+
const ability_publication_1 = require("./ability-publication");
|
|
20
|
+
const ability_operations_1 = require("./ability-operations");
|
|
21
|
+
const ability_operation_store_1 = require("./ability-operation-store");
|
|
22
|
+
const ability_owner_1 = require("./ability-owner");
|
|
15
23
|
const manifest = {
|
|
16
|
-
id: "aun", version: "1.0.
|
|
24
|
+
id: "aun", version: "1.0.44", type: "channel", displayName: "AUN 通道",
|
|
17
25
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
18
26
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
19
27
|
configSchema: "config.schema.json",
|
|
@@ -83,6 +91,15 @@ class AunPlugin {
|
|
|
83
91
|
createdAids = new Set();
|
|
84
92
|
bindings = {};
|
|
85
93
|
bindingTimes = {};
|
|
94
|
+
abilitySelections = {};
|
|
95
|
+
abilityLedgers = {};
|
|
96
|
+
abilityOwners = {};
|
|
97
|
+
abilityOperations;
|
|
98
|
+
abilityAuditUnavailable = false;
|
|
99
|
+
ownerProof = new ability_owner_1.OwnerProof();
|
|
100
|
+
abilityPublications = new Map();
|
|
101
|
+
revokingAbilityAids = new Set();
|
|
102
|
+
abilityRevisions = new Map();
|
|
86
103
|
creating = false;
|
|
87
104
|
unregister = [];
|
|
88
105
|
ingress;
|
|
@@ -203,6 +220,8 @@ class AunPlugin {
|
|
|
203
220
|
throw new Error("只能关联 Agent 插件");
|
|
204
221
|
if (this.context?.pluginStatus(pluginId) !== "ready")
|
|
205
222
|
throw new Error("目标 Agent 插件未启动");
|
|
223
|
+
if (this.bindings[aid] && this.bindings[aid] !== pluginId)
|
|
224
|
+
await this.revokePublishedAbilities(aid);
|
|
206
225
|
const previous = this.bindings;
|
|
207
226
|
const previousTimes = this.bindingTimes;
|
|
208
227
|
const connection = this.clients.get(aid);
|
|
@@ -270,6 +289,7 @@ class AunPlugin {
|
|
|
270
289
|
throw new Error("指定 AID 当前未关联到该 Agent");
|
|
271
290
|
if ([...this.pendingMessages.keys()].some(key => JSON.parse(key)[0] === aid))
|
|
272
291
|
throw new Error("该 AID 正在处理消息,请等待任务完成后再解绑");
|
|
292
|
+
await this.revokePublishedAbilities(aid);
|
|
273
293
|
const previous = this.bindings;
|
|
274
294
|
const previousTimes = this.bindingTimes;
|
|
275
295
|
this.bindings = { ...previous };
|
|
@@ -309,6 +329,9 @@ class AunPlugin {
|
|
|
309
329
|
this.config.created_aids = [...new Set([...this.createdAids, ...this.legacyCreatedAids])].sort();
|
|
310
330
|
this.config.bindings = this.bindings;
|
|
311
331
|
this.config.binding_times = this.bindingTimes;
|
|
332
|
+
this.config.ability_selections = this.abilitySelections;
|
|
333
|
+
this.config.ability_ledgers = this.abilityLedgers;
|
|
334
|
+
this.config.ability_owners = this.abilityOwners;
|
|
312
335
|
await (0, promises_1.writeFile)((0, node_path_1.join)(this.context.dataDir, "config.json"), `${JSON.stringify(this.config, null, 2)}\n`, { mode: 0o600 });
|
|
313
336
|
}
|
|
314
337
|
async activate(context) {
|
|
@@ -349,6 +372,16 @@ class AunPlugin {
|
|
|
349
372
|
throw new Error("运行时未提供通道消息入口");
|
|
350
373
|
this.sdk ??= await loadSdk();
|
|
351
374
|
await (0, promises_1.mkdir)(this.path(), { recursive: true });
|
|
375
|
+
this.ownerProof.clear();
|
|
376
|
+
this.abilityAuditUnavailable = false;
|
|
377
|
+
try {
|
|
378
|
+
this.abilityOperations = await ability_operation_store_1.AbilityOperationStore.open((0, node_path_1.join)(context.dataDir, "ability-operations.json"));
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
this.abilityOperations = undefined;
|
|
382
|
+
this.abilityAuditUnavailable = true;
|
|
383
|
+
context.logger.error("AUN Ability 操作账本不可读,已停用 Ability 发布与执行;普通聊天仍可用", { code: "operation_store_corrupt", error: safeError(error) });
|
|
384
|
+
}
|
|
352
385
|
this.store = this.newStore();
|
|
353
386
|
for (const [method, path, handler] of [
|
|
354
387
|
["POST", "/api/plugins/aun/aid", this.createAid],
|
|
@@ -356,6 +389,11 @@ class AunPlugin {
|
|
|
356
389
|
["POST", "/api/plugins/aun/bind", this.bindAgent],
|
|
357
390
|
["POST", "/api/plugins/aun/unbind", this.unbindAgent],
|
|
358
391
|
["POST", "/api/plugins/aun/settings", this.saveSettings],
|
|
392
|
+
["GET", "/api/plugins/aun/abilities", this.listAbilities],
|
|
393
|
+
["POST", "/api/plugins/aun/abilities/publish", this.publishAbilities],
|
|
394
|
+
["POST", "/api/plugins/aun/abilities/owner-challenge", this.challengeAbilityOwner],
|
|
395
|
+
["GET", "/api/plugins/aun/abilities/pending", this.pendingAbilityOperations],
|
|
396
|
+
["POST", "/api/plugins/aun/abilities/confirm", this.confirmAbilityOperation],
|
|
359
397
|
["GET", "/api/plugins/aun/commands", this.listCommands],
|
|
360
398
|
["POST", "/api/plugins/aun/commands", this.runCommand],
|
|
361
399
|
...(["", "/app.js", "/style.css"].map((suffix) => ["GET", `/abweb/channels/aun${suffix}`, this.servePage])),
|
|
@@ -367,6 +405,15 @@ class AunPlugin {
|
|
|
367
405
|
this.aid = typeof this.config.aid === "string" ? this.config.aid.trim() : "";
|
|
368
406
|
this.bindings = this.config.bindings && typeof this.config.bindings === "object" && !Array.isArray(this.config.bindings) ? { ...this.config.bindings } : {};
|
|
369
407
|
this.bindingTimes = this.config.binding_times && typeof this.config.binding_times === "object" && !Array.isArray(this.config.binding_times) ? { ...this.config.binding_times } : {};
|
|
408
|
+
this.abilitySelections = this.config.ability_selections && typeof this.config.ability_selections === "object" && !Array.isArray(this.config.ability_selections)
|
|
409
|
+
? Object.fromEntries(Object.entries(this.config.ability_selections).map(([aid, paths]) => [aid, Array.isArray(paths) ? paths.filter((path) => typeof path === "string") : []])) : {};
|
|
410
|
+
this.abilityLedgers = this.config.ability_ledgers && typeof this.config.ability_ledgers === "object" && !Array.isArray(this.config.ability_ledgers)
|
|
411
|
+
? this.config.ability_ledgers : {};
|
|
412
|
+
const owners = this.config.ability_owners;
|
|
413
|
+
if (owners !== undefined && (!owners || typeof owners !== "object" || Array.isArray(owners)
|
|
414
|
+
|| Object.entries(owners).some(([provider, owner]) => !aidPattern.test(provider) || typeof owner !== "string" || !aidPattern.test(owner))))
|
|
415
|
+
throw new Error("ability_owners 必须按 Provider AID 配置有效的 Owner AID");
|
|
416
|
+
this.abilityOwners = { ...owners };
|
|
370
417
|
for (const aid of Array.isArray(this.config.identities) ? this.config.identities : []) {
|
|
371
418
|
if (typeof aid === "string" && aidPattern.test(aid))
|
|
372
419
|
this.identities.add(aid);
|
|
@@ -425,14 +472,18 @@ class AunPlugin {
|
|
|
425
472
|
async deactivate(_reason) {
|
|
426
473
|
this.status = "degraded";
|
|
427
474
|
this.acceptingMessages = false;
|
|
475
|
+
this.ownerProof.clear();
|
|
428
476
|
for (const { subscription } of this.clients.values())
|
|
429
477
|
subscription?.unsubscribe();
|
|
478
|
+
await this.abilityOperations?.interruptExecuting();
|
|
430
479
|
await Promise.allSettled(this.pendingMessages.values());
|
|
431
480
|
for (const { client } of this.clients.values())
|
|
432
481
|
await client.close();
|
|
433
482
|
this.clients.clear();
|
|
434
483
|
await this.store?.close();
|
|
435
484
|
this.store = undefined;
|
|
485
|
+
this.abilityOperations = undefined;
|
|
486
|
+
this.abilityAuditUnavailable = false;
|
|
436
487
|
for (const dispose of this.unregister.splice(0))
|
|
437
488
|
dispose();
|
|
438
489
|
this.context?.http.unregister("aun");
|
|
@@ -539,6 +590,255 @@ class AunPlugin {
|
|
|
539
590
|
const localAids = this.listStoreAids().filter((aid) => !connected.has(aid));
|
|
540
591
|
this.json(response, 200, { groups, localAids, bindings: this.bindings, bindingTimes: this.bindingTimes });
|
|
541
592
|
};
|
|
593
|
+
async abilityDefinitions(aid) {
|
|
594
|
+
const pluginId = this.bindings[aid];
|
|
595
|
+
if (!pluginId || this.context?.pluginStatus(pluginId) !== "ready" || !this.context?.listPluginAbilities)
|
|
596
|
+
return [];
|
|
597
|
+
return this.context.listPluginAbilities(pluginId, aid);
|
|
598
|
+
}
|
|
599
|
+
listAbilities = async (request, response) => {
|
|
600
|
+
try {
|
|
601
|
+
const result = {};
|
|
602
|
+
const session = this.context?.managementSession?.(request);
|
|
603
|
+
for (const aid of Object.keys(this.bindings)) {
|
|
604
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
605
|
+
const ownerAid = this.abilityOwners[aid];
|
|
606
|
+
const ownerVerified = Boolean(session && ownerAid && this.ownerProof.verified(session, aid, ownerAid));
|
|
607
|
+
const publishAvailable = Boolean(this.context?.managementActor?.(request) && ownerVerified && this.context?.callPluginAbility && this.abilityOperations && !this.abilityAuditUnavailable);
|
|
608
|
+
result[aid] = { agent: this.bindings[aid], selected: this.abilitySelections[aid] ?? [], definitions, published: Object.keys(this.abilityLedgers[aid] ?? {}).length > 0, pluginVersion: this.context?.pluginVersion?.(this.bindings[aid]), ownerAid, ownerVerified, publishAvailable, reason: publishAvailable ? "需本机已认证管理会话,每次执行仍须明确批准。" : "逐次 Owner 确认需本机已认证管理会话,并验证明确配置的 Owner AID;已有索引仍可撤销。" };
|
|
609
|
+
}
|
|
610
|
+
this.json(response, 200, { abilities: result });
|
|
611
|
+
}
|
|
612
|
+
catch (error) {
|
|
613
|
+
this.json(response, 500, { error: safeError(error) });
|
|
614
|
+
}
|
|
615
|
+
};
|
|
616
|
+
publishAbilities = async (request, response) => {
|
|
617
|
+
if (!this.context?.managementActor?.(request))
|
|
618
|
+
return this.json(response, 403, { error: "能力发布需要本机或已配对的管理会话" });
|
|
619
|
+
try {
|
|
620
|
+
const body = await this.readJsonBody(request);
|
|
621
|
+
const aid = typeof body.aid === "string" ? body.aid.trim() : "";
|
|
622
|
+
if (!aidPattern.test(aid) || !this.bindings[aid])
|
|
623
|
+
return this.json(response, 400, { error: "需要已关联 Agent 的有效 AID" });
|
|
624
|
+
const publish = body.publish !== false;
|
|
625
|
+
const paths = publish ? (Array.isArray(body.paths) && body.paths.every((path) => typeof path === "string") ? body.paths : undefined) : [];
|
|
626
|
+
if (!paths)
|
|
627
|
+
return this.json(response, 400, { error: "发布路径必须是字符串数组" });
|
|
628
|
+
if (paths.length) {
|
|
629
|
+
const session = this.context?.managementSession?.(request);
|
|
630
|
+
const owner = this.abilityOwners[aid];
|
|
631
|
+
if (!session || !owner || !this.ownerProof.verified(session, aid, owner) || !this.context?.callPluginAbility || !this.abilityOperations || this.abilityAuditUnavailable)
|
|
632
|
+
return this.json(response, 409, { error: "逐次 Owner 确认需可用操作账本、本机已认证管理会话,并先验证该 Provider 的 Owner AID" });
|
|
633
|
+
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
634
|
+
if (!this.ownerProof.verified(session, aid, owner) || this.abilityOwners[aid] !== owner || this.revokingAbilityAids.has(aid))
|
|
635
|
+
throw new Error("Owner 验证已失效或身份正在撤销,请重新确认");
|
|
636
|
+
const storage = this.clients.get(aid)?.client.storage;
|
|
637
|
+
if (!storage)
|
|
638
|
+
throw new Error("AUN 身份存储不可用");
|
|
639
|
+
const pluginId = this.bindings[aid];
|
|
640
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
641
|
+
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(); } });
|
|
642
|
+
this.abilityLedgers[aid] = published.ledger;
|
|
643
|
+
this.abilitySelections[aid] = [...new Set(paths)];
|
|
644
|
+
await this.persist();
|
|
645
|
+
});
|
|
646
|
+
this.abilityPublications.set(aid, operation);
|
|
647
|
+
try {
|
|
648
|
+
await operation;
|
|
649
|
+
}
|
|
650
|
+
finally {
|
|
651
|
+
if (this.abilityPublications.get(aid) === operation)
|
|
652
|
+
this.abilityPublications.delete(aid);
|
|
653
|
+
}
|
|
654
|
+
return this.json(response, 200, { ok: true, aid, revoked: false });
|
|
655
|
+
}
|
|
656
|
+
await this.revokePublishedAbilities(aid);
|
|
657
|
+
return this.json(response, 200, { ok: true, aid, revoked: true });
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
this.context?.logger.warn("AUN Ability 能力索引更新失败", { error: safeError(error) });
|
|
661
|
+
return this.json(response, 409, { error: safeError(error) });
|
|
662
|
+
}
|
|
663
|
+
};
|
|
664
|
+
challengeAbilityOwner = async (request, response) => {
|
|
665
|
+
const session = this.context?.managementSession?.(request);
|
|
666
|
+
if (!this.context?.managementActor?.(request) || !session)
|
|
667
|
+
return this.json(response, 403, { error: "验证 Owner 需本机已认证管理会话" });
|
|
668
|
+
try {
|
|
669
|
+
const body = await this.readJsonBody(request);
|
|
670
|
+
const aid = typeof body.aid === "string" ? body.aid.trim() : "";
|
|
671
|
+
const ownerAid = typeof body.ownerAid === "string" ? body.ownerAid.trim() : "";
|
|
672
|
+
if (!aidPattern.test(aid) || !this.bindings[aid] || !aidPattern.test(ownerAid))
|
|
673
|
+
return this.json(response, 400, { error: "需要已关联的 Provider AID 和明确指定的 Owner AID" });
|
|
674
|
+
if (this.abilityOwners[aid] !== ownerAid) {
|
|
675
|
+
this.revokingAbilityAids.add(aid);
|
|
676
|
+
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
677
|
+
this.ownerProof.clear(aid);
|
|
678
|
+
try {
|
|
679
|
+
await this.invalidateAbilityOperations(aid);
|
|
680
|
+
const previous = this.abilityOwners;
|
|
681
|
+
this.abilityOwners = { ...previous, [aid]: ownerAid };
|
|
682
|
+
try {
|
|
683
|
+
await this.persist();
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
this.abilityOwners = previous;
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
finally {
|
|
691
|
+
this.revokingAbilityAids.delete(aid);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
const challenge = this.ownerProof.start(session, aid, ownerAid);
|
|
695
|
+
await this.callAunWithRecovery(aid, { to: ownerAid, encrypt: true, payload: { type: "text", text: `请在本次管理会话发起后回复以下指令,证明你控制 Owner AID。此操作只验证身份,不批准任何能力调用:\n/ability-owner ${challenge.nonce}` } });
|
|
696
|
+
this.context?.logger.info("AUN Owner 会话验证已发起,需本机已认证管理会话", { aid, ownerAid });
|
|
697
|
+
this.json(response, 200, { ok: true, aid, ownerAid, expiresAt: challenge.expiresAt });
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
this.json(response, 409, { error: safeError(error) });
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
pendingAbilityOperations = async (request, response) => {
|
|
704
|
+
if (!this.context?.managementActor?.(request) || !this.context?.managementSession?.(request))
|
|
705
|
+
return this.json(response, 403, { error: "读取待确认能力需本机已认证管理会话" });
|
|
706
|
+
try {
|
|
707
|
+
this.json(response, 200, { operations: await this.abilityOperations?.pending() ?? [] });
|
|
708
|
+
}
|
|
709
|
+
catch (error) {
|
|
710
|
+
this.json(response, 503, { error: safeError(error) });
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
confirmAbilityOperation = async (request, response) => {
|
|
714
|
+
const session = this.context?.managementSession?.(request);
|
|
715
|
+
if (!this.context?.managementActor?.(request) || !session)
|
|
716
|
+
return this.json(response, 403, { error: "逐次 Owner 确认需本机已认证管理会话" });
|
|
717
|
+
const store = this.abilityOperations;
|
|
718
|
+
if (!store || this.abilityAuditUnavailable)
|
|
719
|
+
return this.json(response, 503, { error: "持久操作账本不可用" });
|
|
720
|
+
try {
|
|
721
|
+
const body = await this.readJsonBody(request);
|
|
722
|
+
if (typeof body.key !== "string" || typeof body.confirmationFingerprint !== "string" || typeof body.approved !== "boolean")
|
|
723
|
+
return this.json(response, 400, { error: "必须明确提交操作键、完整确认指纹和批准或拒绝决定" });
|
|
724
|
+
const original = await store.get(body.key);
|
|
725
|
+
if (!original)
|
|
726
|
+
return this.json(response, 404, { error: "能力操作不存在" });
|
|
727
|
+
const ownerAid = this.abilityOwners[original.providerAid];
|
|
728
|
+
if (!ownerAid || !this.ownerProof.verified(session, original.providerAid, ownerAid))
|
|
729
|
+
return this.json(response, 403, { error: "需本机已认证管理会话,并完成该 Provider 的 Owner AID 验证" });
|
|
730
|
+
if (original.confirmationFingerprint !== body.confirmationFingerprint)
|
|
731
|
+
return this.json(response, 409, { error: "确认指纹与待确认请求不一致" });
|
|
732
|
+
const revision = this.abilityRevisions.get(original.providerAid) ?? 0;
|
|
733
|
+
if (body.approved && original.state === "pending_confirmation") {
|
|
734
|
+
const failure = await this.abilityInvocationFailure(original.providerAid, original.authenticatedCaller, original.path, original.definitionId, original.params);
|
|
735
|
+
const pluginId = this.bindings[original.providerAid];
|
|
736
|
+
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) ?? ""))
|
|
737
|
+
return this.json(response, 409, { error: failure?.message ?? "能力执行目标或 Owner 配置已改变,请重新发起调用" });
|
|
738
|
+
}
|
|
739
|
+
if (!this.acceptingMessages || !this.ownerProof.verified(session, original.providerAid, ownerAid))
|
|
740
|
+
return this.json(response, 403, { error: "通道或 Owner 管理会话已失效" });
|
|
741
|
+
const confirmation = body.approved ? await store.approve(original.key, body.confirmationFingerprint) : await store.reject(original.key, body.confirmationFingerprint);
|
|
742
|
+
let entry = confirmation.entry;
|
|
743
|
+
if (confirmation.started) {
|
|
744
|
+
let result;
|
|
745
|
+
let state;
|
|
746
|
+
let handlerAuthorized = false;
|
|
747
|
+
try {
|
|
748
|
+
const failure = await this.abilityInvocationFailure(entry.providerAid, entry.authenticatedCaller, entry.path, entry.definitionId, entry.params);
|
|
749
|
+
const expired = Date.now() >= entry.expiresAt;
|
|
750
|
+
const invalidated = (this.abilityRevisions.get(entry.providerAid) ?? 0) !== revision || this.revokingAbilityAids.has(entry.providerAid);
|
|
751
|
+
if (failure || expired || invalidated || !this.ownerProof.verified(session, entry.providerAid, ownerAid) || this.abilityOwners[entry.providerAid] !== entry.executionTarget.ownerAid || this.bindings[entry.providerAid] !== entry.executionTarget.pluginId || (this.context?.pluginVersion?.(String(entry.executionTarget.pluginId)) ?? "") !== entry.executionTarget.pluginVersion) {
|
|
752
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, expired ? "message_expired" : failure?.code ?? (invalidated ? "provider_invalidated" : "permission_denied"), expired ? "调用已超过最晚启动时间" : failure?.message ?? "Owner 管理会话或执行目标已改变,本次能力没有执行"), execution_state: "not_executed" };
|
|
753
|
+
state = "failed";
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
const context = this.context;
|
|
757
|
+
const confirmed = entry;
|
|
758
|
+
const beforeStart = () => {
|
|
759
|
+
const currentSession = context.managementSession?.(request);
|
|
760
|
+
if (this.context !== context || this.abilityOperations !== store || this.abilityAuditUnavailable || !this.acceptingMessages || !context.managementActor?.(request) || !currentSession || currentSession.id !== session.id || !this.ownerProof.verified(session, confirmed.providerAid, ownerAid) || !this.ownerProof.verified(currentSession, confirmed.providerAid, ownerAid))
|
|
761
|
+
throw Object.assign(new Error("管理会话、Owner 证明或操作账本已失效,本次能力没有执行"), { code: "confirmation_unavailable" });
|
|
762
|
+
const pluginId = String(confirmed.executionTarget.pluginId);
|
|
763
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
764
|
+
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)))
|
|
765
|
+
throw Object.assign(new Error("Provider 发布、Owner policy 或执行目标已改变,本次能力没有执行"), { code: "provider_invalidated" });
|
|
766
|
+
handlerAuthorized = true;
|
|
767
|
+
};
|
|
768
|
+
const value = await context.callPluginAbility(String(entry.executionTarget.pluginId), entry.path, entry.params, { providerAid: entry.providerAid, expiresAt: entry.expiresAt, beforeStart });
|
|
769
|
+
result = { type: "ability", role: "result", ok: true, execution_state: "completed", result: value };
|
|
770
|
+
state = "succeeded";
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
catch (error) {
|
|
774
|
+
this.context?.logger.warn("AUN Ability 执行失败,结果将持久保存", { aid: entry.providerAid, path: entry.path, error: safeError(error) });
|
|
775
|
+
const code = error.code;
|
|
776
|
+
const notExecuted = !handlerAuthorized && ["message_expired", "provider_invalidated", "confirmation_unavailable"].includes(code ?? "");
|
|
777
|
+
result = { ...(0, ability_operations_1.abilityFailureResult)({ call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId }, notExecuted ? code : "execution_failed", notExecuted ? (code === "message_expired" ? "调用已超过最晚启动时间" : "管理会话或 Provider 状态已改变,本次能力没有执行") : "能力执行失败,请检查 Provider 运行日志;不会自动重试"), execution_state: notExecuted ? "not_executed" : "failed" };
|
|
778
|
+
state = "failed";
|
|
779
|
+
}
|
|
780
|
+
entry = await store.finish(entry.key, state, result);
|
|
781
|
+
}
|
|
782
|
+
const delivered = await this.sendAbilityResponse(entry.providerAid, entry.authenticatedCaller, this.abilityOperationResponse(entry));
|
|
783
|
+
this.context?.logger.info("AUN Ability 逐次确认完成,需本机已认证管理会话", { aid: entry.providerAid, callerAid: entry.authenticatedCaller, operationId: entry.operationId, state: entry.state, delivered });
|
|
784
|
+
this.json(response, 200, { ok: true, state: entry.state, result: entry.result, delivered });
|
|
785
|
+
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
this.json(response, 409, { error: safeError(error) });
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
async revokePublishedAbilities(aid) {
|
|
791
|
+
this.revokingAbilityAids.add(aid);
|
|
792
|
+
this.abilityRevisions.set(aid, (this.abilityRevisions.get(aid) ?? 0) + 1);
|
|
793
|
+
const operation = (this.abilityPublications.get(aid) ?? Promise.resolve()).catch(() => undefined).then(async () => {
|
|
794
|
+
await this.invalidateAbilityOperations(aid);
|
|
795
|
+
this.abilitySelections[aid] = [];
|
|
796
|
+
await this.persist();
|
|
797
|
+
const ledger = this.abilityLedgers[aid] ?? {};
|
|
798
|
+
if (!Object.keys(ledger).length)
|
|
799
|
+
return;
|
|
800
|
+
const storage = this.clients.get(aid)?.client.storage;
|
|
801
|
+
if (!storage)
|
|
802
|
+
throw new Error("AUN 能力撤销失败:身份存储不可用;调用已停用,请恢复连接后重试解绑");
|
|
803
|
+
const pluginId = this.bindings[aid];
|
|
804
|
+
const version = this.context?.pluginVersion?.(pluginId) ?? Object.values(ledger)[0].plugin_version;
|
|
805
|
+
const published = await (0, ability_publication_1.publishAbilityCatalog)({ storage, aid, pluginId, pluginVersion: version, definitions: [], selectedPaths: [], ledger, beforeIndexWrite: async (pending) => { this.abilityLedgers[aid] = pending; await this.persist(); } });
|
|
806
|
+
this.abilityLedgers[aid] = published.ledger;
|
|
807
|
+
await this.persist();
|
|
808
|
+
this.context?.logger.info("AUN 已撤销解绑身份的能力索引", { aid, catalogRevision: published.index.catalog_revision });
|
|
809
|
+
});
|
|
810
|
+
this.abilityPublications.set(aid, operation);
|
|
811
|
+
try {
|
|
812
|
+
await operation;
|
|
813
|
+
}
|
|
814
|
+
finally {
|
|
815
|
+
if (this.abilityPublications.get(aid) === operation)
|
|
816
|
+
this.abilityPublications.delete(aid);
|
|
817
|
+
this.revokingAbilityAids.delete(aid);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
async invalidateAbilityOperations(aid) {
|
|
821
|
+
try {
|
|
822
|
+
await this.abilityOperations?.invalidateProvider(aid);
|
|
823
|
+
}
|
|
824
|
+
catch (error) {
|
|
825
|
+
this.abilityAuditUnavailable = true;
|
|
826
|
+
this.context?.logger.error("AUN Ability 待确认记录失效结果无法持久保存,已停用 Ability 发布与执行", { aid, code: "audit_unavailable", error: safeError(error) });
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
async readJsonBody(request) {
|
|
831
|
+
let raw = "";
|
|
832
|
+
for await (const chunk of request) {
|
|
833
|
+
raw += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
|
|
834
|
+
if (raw.length > 65536)
|
|
835
|
+
throw new Error("请求内容过大");
|
|
836
|
+
}
|
|
837
|
+
const body = JSON.parse(raw || "{}");
|
|
838
|
+
if (!body || typeof body !== "object" || Array.isArray(body))
|
|
839
|
+
throw new Error("请求正文必须是 JSON 对象");
|
|
840
|
+
return body;
|
|
841
|
+
}
|
|
542
842
|
saveSettings = async (request, response) => {
|
|
543
843
|
try {
|
|
544
844
|
let raw = "";
|
|
@@ -893,12 +1193,57 @@ class AunPlugin {
|
|
|
893
1193
|
}
|
|
894
1194
|
};
|
|
895
1195
|
async onMessage(payload, aid) {
|
|
1196
|
+
const raw = payload && typeof payload === "object" ? payload : undefined;
|
|
1197
|
+
const inboundId = typeof raw?.message_id === "string" ? raw.message_id : (0, node_crypto_1.randomUUID)();
|
|
1198
|
+
this.context?.logger.info("AUN 入站回调", { traceId: inboundId, aid, messageId: typeof raw?.message_id === "string" ? raw.message_id : "", stage: "SDK收到" });
|
|
896
1199
|
const agentPlugin = this.bindings[aid];
|
|
897
|
-
if (!this.acceptingMessages
|
|
1200
|
+
if (!this.acceptingMessages) {
|
|
1201
|
+
this.context?.logger.warn("AUN 入站消息已忽略", { traceId: inboundId, aid, stage: "通道未接收", reason: "通道未就绪" });
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (!agentPlugin) {
|
|
1205
|
+
this.context?.logger.warn("AUN 入站消息已忽略", { traceId: inboundId, aid, stage: "绑定检查", reason: "未绑定 Agent" });
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const body = raw?.payload && typeof raw.payload === "object" && !Array.isArray(raw.payload) ? raw.payload : undefined;
|
|
1209
|
+
const ownerCommand = typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text) ? body.text.match(/^\/ability-owner ([0-9a-f]{64})$/) : undefined;
|
|
1210
|
+
if (typeof body?.text === "string" && /^\/ability-owner(?:\s|$)/.test(body.text)) {
|
|
1211
|
+
const envelope = raw?.envelope && typeof raw.envelope === "object" ? raw.envelope : undefined;
|
|
1212
|
+
const sender = typeof raw?.from === "string" ? raw.from : typeof envelope?.from === "string" ? envelope.from : undefined;
|
|
1213
|
+
const verified = Boolean(ownerCommand && (envelope?.from === undefined || envelope.from === sender) && this.ownerProof.accept(aid, sender, ownerCommand[1], raw?.encrypted === true));
|
|
1214
|
+
this.context?.logger.info("AUN 宿主委托的 Owner 控制权验证已处理", { aid, verified });
|
|
1215
|
+
if (sender && raw?.encrypted === true && (envelope?.from === undefined || envelope.from === sender)) {
|
|
1216
|
+
const text = verified
|
|
1217
|
+
? `Owner 控制权验证成功:${aid} 的当前管理会话已通过验证。此操作不会批准任何能力调用,请回到 ABWeb 继续操作。`
|
|
1218
|
+
: !ownerCommand
|
|
1219
|
+
? "Owner 控制权验证失败:指令格式不正确,请复制最新验证消息中的完整 /ability-owner 指令。"
|
|
1220
|
+
: "Owner 控制权验证失败:验证指令已过期、已使用,或与本次 Owner 身份不匹配。请在 ABWeb 重新发起验证,并使用指定 Owner AID 回复。";
|
|
1221
|
+
try {
|
|
1222
|
+
await this.sendText(aid, sender, text, typeof body.thread_id === "string" ? body.thread_id : undefined, true);
|
|
1223
|
+
}
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
this.context?.logger.warn("AUN Owner 验证结果回复失败", { aid, peer: sender, verified, stage: "发送 Owner 验证回执", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
if ((0, ability_operations_1.isAbilityMessage)(payload)) {
|
|
1231
|
+
if (this.revokingAbilityAids.has(aid)) {
|
|
1232
|
+
this.context?.logger.warn("AUN 身份正在撤销能力索引,能力调用已阻止", { aid, traceId: inboundId, stage: "撤销保护" });
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
await this.onAbilityMessage(payload, aid);
|
|
898
1236
|
return;
|
|
1237
|
+
}
|
|
899
1238
|
const message = (0, messages_1.normalizeAunMessage)(payload, aid, typeof this.config.allow_from === "string" ? this.config.allow_from : undefined);
|
|
900
|
-
if (!message
|
|
1239
|
+
if (!message) {
|
|
1240
|
+
this.context?.logger.warn("AUN 入站消息已忽略", { traceId: inboundId, aid, stage: "消息校验", reason: "消息格式、白名单或系统事件不符合任务条件" });
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (!this.seen.add(message.messageId ? `${aid}:${message.messageId}` : "")) {
|
|
1244
|
+
this.context?.logger.warn("AUN 入站消息已忽略", { traceId: inboundId, aid, messageId: message.messageId ?? "", stage: "去重", reason: "重复消息" });
|
|
901
1245
|
return;
|
|
1246
|
+
}
|
|
902
1247
|
const sourceKey = JSON.stringify([aid, message.peerId, message.threadId ?? ""]);
|
|
903
1248
|
const pending = this.pendingMessages.get(sourceKey);
|
|
904
1249
|
if (pending && !message.attachments?.length && !message.attachmentError && newSessionCommand.test(message.text.trim())) {
|
|
@@ -910,7 +1255,7 @@ class AunPlugin {
|
|
|
910
1255
|
}
|
|
911
1256
|
return;
|
|
912
1257
|
}
|
|
913
|
-
const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => this.processMessage(message, aid, agentPlugin));
|
|
1258
|
+
const current = (pending ?? Promise.resolve()).catch(() => undefined).then(() => this.processMessage(message, aid, agentPlugin, inboundId));
|
|
914
1259
|
this.pendingMessages.set(sourceKey, current);
|
|
915
1260
|
try {
|
|
916
1261
|
await current;
|
|
@@ -920,9 +1265,138 @@ class AunPlugin {
|
|
|
920
1265
|
this.pendingMessages.delete(sourceKey);
|
|
921
1266
|
}
|
|
922
1267
|
}
|
|
923
|
-
async
|
|
1268
|
+
async onAbilityMessage(value, aid) {
|
|
1269
|
+
const receivedAt = Date.now();
|
|
1270
|
+
const call = (0, ability_operations_1.parseAbilityEnvelope)(value);
|
|
1271
|
+
if (!call) {
|
|
1272
|
+
this.context?.logger.warn("AUN Ability 入站消息校验失败,未进入聊天或执行", { aid, stage: "认证与协议校验" });
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
const { payload, callerAid } = call;
|
|
1276
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1277
|
+
let result;
|
|
1278
|
+
if (rules.length && !rules.includes("*") && !rules.includes(callerAid))
|
|
1279
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "permission_denied", "调用来源不在 AUN 允许名单中");
|
|
1280
|
+
else if (payload.ability_path === "protocol/discover") {
|
|
1281
|
+
const params = payload.params;
|
|
1282
|
+
const window = params?.window_seconds;
|
|
1283
|
+
if (params?.spec_version !== "aid-ability/2" || !Number.isInteger(window) || Number(window) < 1 || Number(window) > 30 || payload.ttl !== window || payload.definition_id !== undefined)
|
|
1284
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "invalid_params", "Discover 窗口必须为 1–30 秒,ttl 必须与窗口一致且不携带 definition_id");
|
|
1285
|
+
else {
|
|
1286
|
+
const definitions = await this.abilityDefinitions(aid);
|
|
1287
|
+
const capabilities = this.abilityOwners[aid] && this.abilityOperations && !this.abilityAuditUnavailable && this.context?.callPluginAbility
|
|
1288
|
+
? definitions.filter(definition => (this.abilitySelections[aid] ?? []).includes(definition.path) && this.abilityLedgers[aid]?.[definition.path]?.definition_id === (0, ability_publication_1.abilityDefinitionId)({ ...definition })).map(({ contract_revision: _revision, ...definition }) => ({ ...definition, definition_id: this.abilityLedgers[aid][definition.path].definition_id })) : [];
|
|
1289
|
+
result = { type: "ability", role: "result", call_id: payload.call_id, operation_id: payload.operation_id, ability_path: "protocol/discover", ok: true, execution_state: "completed", result: { spec_version: "aid-ability/2", capabilities } };
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
else {
|
|
1293
|
+
try {
|
|
1294
|
+
const store = this.abilityOperations;
|
|
1295
|
+
if (this.abilityAuditUnavailable || !store)
|
|
1296
|
+
throw Object.assign(new Error("持久操作账本不可用"), { code: "audit_unavailable" });
|
|
1297
|
+
const key = (0, ability_operation_store_1.abilityOperationKey)({ providerAid: aid, authenticatedCaller: callerAid, operationId: String(payload.operation_id) });
|
|
1298
|
+
const existing = await store?.get(key);
|
|
1299
|
+
if (existing) {
|
|
1300
|
+
const expected = { path: existing.path, definitionId: existing.definitionId, params: existing.params, async: existing.async ?? false };
|
|
1301
|
+
const incoming = { path: payload.ability_path, definitionId: payload.definition_id, params: payload.params ?? {}, async: payload.async ?? false };
|
|
1302
|
+
result = (0, ability_publication_1.abilityDefinitionId)(expected) === (0, ability_publication_1.abilityDefinitionId)(incoming) ? this.abilityOperationResponse((await store.replay(key, String(payload.call_id)))) : (0, ability_operations_1.abilityFailureResult)(payload, "idempotency_conflict", "相同 operation_id 的请求内容已改变");
|
|
1303
|
+
}
|
|
1304
|
+
else {
|
|
1305
|
+
const path = String(payload.ability_path);
|
|
1306
|
+
const params = payload.params ?? {};
|
|
1307
|
+
const revision = this.abilityRevisions.get(aid) ?? 0;
|
|
1308
|
+
const pluginId = this.bindings[aid];
|
|
1309
|
+
const pluginVersion = this.context?.pluginVersion?.(pluginId) ?? "";
|
|
1310
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1311
|
+
const failure = await this.abilityInvocationFailure(aid, callerAid, path, String(payload.definition_id), params);
|
|
1312
|
+
if (failure)
|
|
1313
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, failure.code, failure.message);
|
|
1314
|
+
else if (!ownerAid || !this.context?.callPluginAbility)
|
|
1315
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "confirmation_unavailable", "缺少宿主委托的 Owner 控制权证明配置或确认通道,本次能力没有执行");
|
|
1316
|
+
else {
|
|
1317
|
+
const sentAt = payload.sent_at;
|
|
1318
|
+
const ttl = payload.ttl;
|
|
1319
|
+
const expiresAt = (sentAt === undefined ? receivedAt : Math.min(receivedAt, Number(sentAt))) + Number(ttl) * 1000;
|
|
1320
|
+
if ((sentAt !== undefined && (!Number.isSafeInteger(sentAt) || Number(sentAt) < 0)) || !Number.isSafeInteger(ttl) || Number(ttl) < 1 || Number(ttl) > 3600 || !Number.isSafeInteger(expiresAt) || (payload.async !== undefined && typeof payload.async !== "boolean"))
|
|
1321
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "invalid_params", "调用需有效的 1–3600 秒 ttl;sent_at 和布尔 async 可省略");
|
|
1322
|
+
else if (expiresAt <= Date.now())
|
|
1323
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "message_expired", "调用已超过最晚启动时间");
|
|
1324
|
+
else {
|
|
1325
|
+
const definition = (await this.abilityDefinitions(aid)).find(item => item.path === path);
|
|
1326
|
+
if ((this.abilityRevisions.get(aid) ?? 0) !== revision || !this.acceptingMessages || this.abilityAuditUnavailable || this.revokingAbilityAids.has(aid) || this.abilityOperations !== store || this.bindings[aid] !== pluginId || this.abilityOwners[aid] !== ownerAid || (this.context?.pluginVersion?.(pluginId) ?? "") !== pluginVersion || this.context?.pluginStatus(pluginId) !== "ready" || !this.abilitySelections[aid]?.includes(path) || this.abilityLedgers[aid]?.[path]?.definition_id !== payload.definition_id)
|
|
1327
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "provider_invalidated", "Provider 发布或 Owner policy 已改变,本次调用不会执行");
|
|
1328
|
+
else if (!definition || (0, ability_publication_1.abilityDefinitionId)({ ...definition }) !== payload.definition_id)
|
|
1329
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, "contract_changed", "能力契约已变更,请重新发现后确认");
|
|
1330
|
+
else {
|
|
1331
|
+
const draft = await store.draft({ providerAid: aid, authenticatedCaller: callerAid, operationId: String(payload.operation_id), callId: String(payload.call_id), path, definitionId: String(payload.definition_id), params, effects: definition.effects, dedupeRetentionSeconds: definition.dedupe_retention_seconds, executionTarget: { pluginId, pluginVersion, ownerAid }, expiresAt, ...(payload.async === undefined ? {} : { async: payload.async }) });
|
|
1332
|
+
result = this.abilityOperationResponse(draft.entry);
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
catch (error) {
|
|
1339
|
+
const code = typeof error.code === "string" ? error.code : "audit_unavailable";
|
|
1340
|
+
this.context?.logger.warn("AUN Ability 操作账本拒绝请求", { aid, callerAid, code, error: safeError(error) });
|
|
1341
|
+
result = (0, ability_operations_1.abilityFailureResult)(payload, code, code === "idempotency_conflict" ? "相同 operation_id 的请求内容已改变" : "操作账本校验或持久化失败,本次能力没有执行");
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
await this.sendAbilityResponse(aid, callerAid, result);
|
|
1345
|
+
}
|
|
1346
|
+
async abilityInvocationFailure(aid, callerAid, path, definitionId, params) {
|
|
1347
|
+
if (this.abilityAuditUnavailable)
|
|
1348
|
+
return { code: "audit_unavailable", message: "持久操作账本不可用,本次能力没有执行" };
|
|
1349
|
+
const rules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1350
|
+
if (rules.length && !rules.includes("*") && !rules.includes(callerAid))
|
|
1351
|
+
return { code: "permission_denied", message: "调用来源不在 AUN 允许名单中" };
|
|
1352
|
+
const pluginId = this.bindings[aid];
|
|
1353
|
+
const published = this.abilityLedgers[aid]?.[path];
|
|
1354
|
+
const ownerAid = this.abilityOwners[aid];
|
|
1355
|
+
const revision = this.abilityRevisions.get(aid) ?? 0;
|
|
1356
|
+
const pluginVersion = this.context?.pluginVersion?.(pluginId);
|
|
1357
|
+
const store = this.abilityOperations;
|
|
1358
|
+
if (!this.acceptingMessages || this.revokingAbilityAids.has(aid) || !this.clients.has(aid) || this.context?.pluginStatus(pluginId) !== "ready")
|
|
1359
|
+
return { code: "provider_unavailable", message: "绑定的 Agent 插件或通道尚未就绪,本次能力没有执行" };
|
|
1360
|
+
if (!(this.abilitySelections[aid] ?? []).includes(path) || !published)
|
|
1361
|
+
return { code: "ability_not_published", message: "当前 AID 没有发布指定能力" };
|
|
1362
|
+
const definition = (await this.abilityDefinitions(aid)).find(item => item.path === path);
|
|
1363
|
+
if (this.abilityAuditUnavailable)
|
|
1364
|
+
return { code: "audit_unavailable", message: "持久操作账本不可用,本次能力没有执行" };
|
|
1365
|
+
const currentRules = String(this.config.allow_from ?? "").split(",").map(value => value.trim()).filter(Boolean);
|
|
1366
|
+
if (currentRules.length && !currentRules.includes("*") && !currentRules.includes(callerAid))
|
|
1367
|
+
return { code: "permission_denied", message: "调用来源不在 AUN 允许名单中" };
|
|
1368
|
+
if ((this.abilityRevisions.get(aid) ?? 0) !== revision || this.bindings[aid] !== pluginId || this.abilityOwners[aid] !== ownerAid || this.abilityLedgers[aid]?.[path] !== published || !this.abilitySelections[aid]?.includes(path) || (this.context?.pluginVersion?.(pluginId)) !== pluginVersion || this.abilityOperations !== store)
|
|
1369
|
+
return { code: "provider_invalidated", message: "Provider 发布或 Owner policy 已改变,本次调用不会执行" };
|
|
1370
|
+
if (!this.acceptingMessages || this.revokingAbilityAids.has(aid) || !this.clients.has(aid) || this.context?.pluginStatus(pluginId) !== "ready")
|
|
1371
|
+
return { code: "provider_unavailable", message: "绑定的 Agent 插件或通道尚未就绪,本次能力没有执行" };
|
|
1372
|
+
if (!definition)
|
|
1373
|
+
return { code: "ability_not_found", message: "绑定的 Agent 不再声明指定能力" };
|
|
1374
|
+
if (published.definition_id !== definitionId || definitionId !== (0, ability_publication_1.abilityDefinitionId)({ ...definition }))
|
|
1375
|
+
return { code: "contract_changed", message: "能力契约已变更,请重新发现后确认" };
|
|
1376
|
+
const ajv = new _2020_1.default({ allErrors: true, strict: false });
|
|
1377
|
+
const validate = ajv.compile(definition.params_schema);
|
|
1378
|
+
if (!validate(params))
|
|
1379
|
+
return { code: "invalid_params", message: `能力参数不符合契约:${ajv.errorsText(validate.errors)}` };
|
|
1380
|
+
}
|
|
1381
|
+
abilityOperationResponse(entry) {
|
|
1382
|
+
return entry.result ?? { type: "ability", role: "ack", call_id: entry.callId, operation_id: entry.operationId, ability_path: entry.path, definition_id: entry.definitionId, execution_state: entry.state === "executing" ? "executing" : "pending" };
|
|
1383
|
+
}
|
|
1384
|
+
async sendAbilityResponse(aid, callerAid, payload) {
|
|
1385
|
+
try {
|
|
1386
|
+
const sent = await this.callAunWithRecovery(aid, { to: callerAid, payload, encrypt: true });
|
|
1387
|
+
if (typeof sent?.status !== "string" || !successfulSendStatuses.has(sent.status))
|
|
1388
|
+
throw new Error("AUN 协议响应投递状态未确认");
|
|
1389
|
+
this.context?.logger.info("AUN Ability 协议响应已发送", { aid, callerAid, path: payload.ability_path, callId: payload.call_id, stage: "Ability 响应" });
|
|
1390
|
+
return true;
|
|
1391
|
+
}
|
|
1392
|
+
catch (error) {
|
|
1393
|
+
this.context?.logger.error("AUN Ability 协议响应发送失败,结果可从账本重投", { aid, callerAid, path: payload.ability_path, error: safeError(error) });
|
|
1394
|
+
return false;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
async processMessage(message, aid, agentPlugin, inboundId) {
|
|
924
1398
|
let deliveryFailed = false;
|
|
925
|
-
const trace = { aid, peer: message.peerId, threadId: message.threadId ?? "", messageId: message.messageId ?? "", agentPlugin };
|
|
1399
|
+
const trace = { traceId: message.messageId || inboundId, aid, peer: message.peerId, threadId: message.threadId ?? "", messageId: message.messageId ?? "", agentPlugin };
|
|
926
1400
|
this.context?.logger.info("AUN 收到任务", { ...trace, stage: "接收消息", attachments: message.attachments?.length ?? 0 });
|
|
927
1401
|
const client = this.clients.get(aid)?.client;
|
|
928
1402
|
let attachments = [];
|
|
@@ -956,7 +1430,7 @@ class AunPlugin {
|
|
|
956
1430
|
return;
|
|
957
1431
|
}
|
|
958
1432
|
const channelMessage = {
|
|
959
|
-
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
1433
|
+
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, traceId: message.messageId || inboundId, text: message.text, threadId: message.threadId,
|
|
960
1434
|
...(attachments.length ? { attachments } : {}),
|
|
961
1435
|
agentPlugin,
|
|
962
1436
|
reply: async (text, signal) => {
|
|
@@ -964,7 +1438,7 @@ class AunPlugin {
|
|
|
964
1438
|
if (!text.trim())
|
|
965
1439
|
return;
|
|
966
1440
|
try {
|
|
967
|
-
await this.sendText(aid, message.peerId, text, message.threadId);
|
|
1441
|
+
await this.sendText(aid, message.peerId, text, message.threadId, false, "markdown");
|
|
968
1442
|
this.context?.logger.info("AUN 文本已发送", { ...trace, stage: "发送文本消息" });
|
|
969
1443
|
}
|
|
970
1444
|
catch (error) {
|
|
@@ -1003,7 +1477,7 @@ class AunPlugin {
|
|
|
1003
1477
|
signal?.throwIfAborted();
|
|
1004
1478
|
const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
|
|
1005
1479
|
// AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
|
|
1006
|
-
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText }), attachments: group };
|
|
1480
|
+
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText, format: "markdown" }), attachments: group };
|
|
1007
1481
|
if (message.threadId)
|
|
1008
1482
|
payload.thread_id = message.threadId;
|
|
1009
1483
|
if (message.messageId)
|
|
@@ -1022,7 +1496,7 @@ class AunPlugin {
|
|
|
1022
1496
|
try {
|
|
1023
1497
|
signal?.throwIfAborted();
|
|
1024
1498
|
const { attachments: _attachments, ...caption } = payload;
|
|
1025
|
-
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText }, encrypt: true });
|
|
1499
|
+
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText, format: "markdown" }, encrypt: true });
|
|
1026
1500
|
if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
|
|
1027
1501
|
throw new Error(`AUN 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
|
|
1028
1502
|
this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
|
|
@@ -1040,7 +1514,7 @@ class AunPlugin {
|
|
|
1040
1514
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
1041
1515
|
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
1042
1516
|
try {
|
|
1043
|
-
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true);
|
|
1517
|
+
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true, "markdown");
|
|
1044
1518
|
}
|
|
1045
1519
|
catch { /* 尽力反馈 */ }
|
|
1046
1520
|
throw failure;
|
|
@@ -1065,8 +1539,8 @@ class AunPlugin {
|
|
|
1065
1539
|
}
|
|
1066
1540
|
}
|
|
1067
1541
|
}
|
|
1068
|
-
async sendText(aid, peerId, text, threadId, encryptedOnly = false) {
|
|
1069
|
-
const payload = { type: "text", text };
|
|
1542
|
+
async sendText(aid, peerId, text, threadId, encryptedOnly = false, format) {
|
|
1543
|
+
const payload = { type: "text", text, ...(format ? { format } : {}) };
|
|
1070
1544
|
if (threadId)
|
|
1071
1545
|
payload.thread_id = threadId;
|
|
1072
1546
|
const params = { to: peerId, payload, encrypt: true };
|