mu-agent-bridge 1.0.28 → 1.0.29
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/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +20 -1
- package/dist/src-ts/plugins/abweb/public/index.html +3 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/index.js +5 -5
- package/dist/src-ts/plugins/aun/public/app.js +17 -4
- package/dist/tests-ts/abweb-login.test.js +1 -0
- package/dist/tests-ts/aun-plugin.test.js +6 -1
- package/dist/tests-ts/channel-feedback.test.js +99 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +20 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +16 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +3 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +5 -5
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +17 -4
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
|
@@ -35,6 +35,25 @@
|
|
|
35
35
|
let loading = false;
|
|
36
36
|
const installingPackages = new Set();
|
|
37
37
|
function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
|
|
38
|
+
function showResult(title, message, failed) {
|
|
39
|
+
node("#operation-result-title").textContent = title;
|
|
40
|
+
const detail = node("#operation-result-message");
|
|
41
|
+
detail.textContent = message;
|
|
42
|
+
detail.className = failed ? "error" : "";
|
|
43
|
+
const dialog = node("#operation-result");
|
|
44
|
+
if (!dialog.open)
|
|
45
|
+
dialog.showModal();
|
|
46
|
+
}
|
|
47
|
+
node("#operation-result-close").onclick = () => node("#operation-result").close();
|
|
48
|
+
window.addEventListener("message", (event) => {
|
|
49
|
+
const frame = channelPanel.querySelector("iframe.channel-frame");
|
|
50
|
+
if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow)
|
|
51
|
+
return;
|
|
52
|
+
const result = event.data;
|
|
53
|
+
if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean")
|
|
54
|
+
return;
|
|
55
|
+
showResult(result.title, result.message, result.failed);
|
|
56
|
+
});
|
|
38
57
|
async function loadCatalog() {
|
|
39
58
|
const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
|
|
40
59
|
const items = result.plugins ?? [];
|
|
@@ -113,7 +132,7 @@
|
|
|
113
132
|
item.type = "button";
|
|
114
133
|
item.textContent = label;
|
|
115
134
|
item.title = title;
|
|
116
|
-
item.onclick = () => { item.disabled = true; void run().catch((cause) =>
|
|
135
|
+
item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); };
|
|
117
136
|
return item;
|
|
118
137
|
}
|
|
119
138
|
async function load(refreshCatalog = true) {
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
<div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
|
|
22
22
|
<p id="error" class="error" role="alert" hidden></p>
|
|
23
23
|
</main>
|
|
24
|
+
<dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
|
|
25
|
+
<h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
|
|
26
|
+
</dialog>
|
|
24
27
|
<script src="/abweb/app.js"></script>
|
|
25
28
|
</body>
|
|
26
29
|
</html>
|
|
@@ -56,3 +56,7 @@ th { color: #566761; font-weight: 600; background: #edf1ef; }
|
|
|
56
56
|
th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child(3), th:nth-child(4) { width: 145px; } th:nth-child(5) { width: 62px; } th:nth-child(7) { width: 140px; }
|
|
57
57
|
.message-error td:last-child { color: #a83427; }
|
|
58
58
|
@media (max-width: 620px) { .shell { padding: 16px; } .section-heading { align-items: flex-start; flex-direction: column; } form, form input { width: 100%; } }
|
|
59
|
+
|
|
60
|
+
#operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
|
|
61
|
+
#operation-result::backdrop { background: rgb(0 0 0 / 35%); }
|
|
62
|
+
#operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
|
|
@@ -10,7 +10,7 @@ const node_url_1 = require("node:url");
|
|
|
10
10
|
const messages_1 = require("./messages");
|
|
11
11
|
const evolcore_1 = require("./evolcore");
|
|
12
12
|
const manifest = {
|
|
13
|
-
id: "aun", version: "1.0.
|
|
13
|
+
id: "aun", version: "1.0.17", type: "channel", displayName: "AUN 通道",
|
|
14
14
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
15
15
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
|
|
16
16
|
configSchema: "config.schema.json",
|
|
@@ -87,9 +87,8 @@ class AunPlugin {
|
|
|
87
87
|
await this.persist();
|
|
88
88
|
this.context?.logger.info("AUN 身份已创建", { aid, owner });
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
|
|
90
|
+
const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
|
|
91
|
+
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
|
|
93
92
|
}
|
|
94
93
|
finally {
|
|
95
94
|
this.creating = false;
|
|
@@ -244,7 +243,8 @@ class AunPlugin {
|
|
|
244
243
|
catch (error) {
|
|
245
244
|
this.status = "degraded";
|
|
246
245
|
this.healthMessage = safeError(error);
|
|
247
|
-
this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
|
|
246
|
+
this.context?.logger.warn("AUN 通道连接失败", { aid, error: this.healthMessage });
|
|
247
|
+
return this.healthMessage;
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
async deactivate(_reason) {
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
17
17
|
function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
|
|
18
|
+
function notify(title, message, failed = false) {
|
|
19
|
+
show(message, failed);
|
|
20
|
+
if (window.parent !== window) window.parent.postMessage({ type: "mab:operation-result", title, message, failed }, window.location.origin);
|
|
21
|
+
else window.alert(`${title}\n${message}`);
|
|
22
|
+
}
|
|
23
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
18
24
|
async function load(refreshForm = true) {
|
|
19
25
|
if (loading) return;
|
|
20
26
|
loading = true;
|
|
@@ -50,15 +56,22 @@
|
|
|
50
56
|
async function bind(aid, pluginId) {
|
|
51
57
|
try {
|
|
52
58
|
await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
|
|
53
|
-
await load();
|
|
54
|
-
} catch (error) {
|
|
59
|
+
await load(); notify("关联成功", `${aid} 已关联 ${pluginId}`);
|
|
60
|
+
} catch (error) { notify("关联失败", errorMessage(error), true); }
|
|
55
61
|
}
|
|
56
62
|
createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
|
|
57
|
-
|
|
63
|
+
show("正在创建 AID 并连接,请稍候……");
|
|
64
|
+
void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
|
|
65
|
+
const failed = result.connection === "degraded";
|
|
66
|
+
const message = failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
|
|
67
|
+
: result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建并连接`;
|
|
68
|
+
notify(failed ? "AID 创建完成,连接失败" : "AID 创建结果", message, failed);
|
|
69
|
+
return load();
|
|
70
|
+
}).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
|
|
58
71
|
};
|
|
59
72
|
configForm.onsubmit = (event) => { event.preventDefault(); const changes = { agent_plugin: configForm.elements.agent_plugin.value, allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
|
|
60
73
|
if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
|
|
61
|
-
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() =>
|
|
74
|
+
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
|
|
62
75
|
};
|
|
63
76
|
find("#refresh").onclick = () => void load();
|
|
64
77
|
void load();
|
|
@@ -34,6 +34,7 @@ class Element {
|
|
|
34
34
|
Date: class extends Date {
|
|
35
35
|
static now() { return now; }
|
|
36
36
|
},
|
|
37
|
+
window: { addEventListener() { } },
|
|
37
38
|
setInterval() { }, confirm: () => false,
|
|
38
39
|
fetch: async (path, options) => {
|
|
39
40
|
calls.push({ path, token: options.headers.authorization });
|
|
@@ -137,6 +137,7 @@ const aun_1 = require("../src-ts/plugins/aun");
|
|
|
137
137
|
const calls = [];
|
|
138
138
|
let registration = { ok: true, data: {} };
|
|
139
139
|
let loadOk = true;
|
|
140
|
+
let connectError = "";
|
|
140
141
|
let loadFailAid = "";
|
|
141
142
|
const clients = [];
|
|
142
143
|
const sdk = {
|
|
@@ -150,7 +151,8 @@ const aun_1 = require("../src-ts/plugins/aun");
|
|
|
150
151
|
AUNClient: class {
|
|
151
152
|
constructor() { const current = this; current.emit = (payload) => current.handler?.(payload); clients.push(current); }
|
|
152
153
|
on(_event, handler) { this.handler = handler; return { unsubscribe() { } }; }
|
|
153
|
-
async connect() { calls.push("client.connect");
|
|
154
|
+
async connect() { calls.push("client.connect"); if (connectError)
|
|
155
|
+
throw new Error(connectError); }
|
|
154
156
|
async close() { calls.push("client.close"); }
|
|
155
157
|
async call(method, params) { calls.push(`${method}:${params.to}:${params.encrypt}`); }
|
|
156
158
|
},
|
|
@@ -203,6 +205,9 @@ const aun_1 = require("../src-ts/plugins/aun");
|
|
|
203
205
|
strict_1.default.deepEqual(await plugin.identity.create("pending.agentid.pub", "owner.agentid.pub"), { aid: "pending.agentid.pub", status: "已创建", connection: "ready" });
|
|
204
206
|
strict_1.default.ok(calls.includes("register:pending.agentid.pub"));
|
|
205
207
|
strict_1.default.equal((await plugin.health()).details?.aids instanceof Array, true);
|
|
208
|
+
connectError = "no trusted roots available for auth certificate verification";
|
|
209
|
+
strict_1.default.deepEqual(await plugin.identity.create("unconnected.agentid.pub", "owner.agentid.pub"), { aid: "unconnected.agentid.pub", status: "已创建", connection: "degraded", connectionError: connectError });
|
|
210
|
+
connectError = "";
|
|
206
211
|
registration = { ok: false, error: { message: "注册失败" } };
|
|
207
212
|
await strict_1.default.rejects(plugin.identity.create("failed.agentid.pub", "owner.agentid.pub"), /注册失败/);
|
|
208
213
|
registration = { ok: true, data: {} };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const strict_1 = __importDefault(require("node:assert/strict"));
|
|
7
|
+
const node_test_1 = __importDefault(require("node:test"));
|
|
8
|
+
const promises_1 = require("node:fs/promises");
|
|
9
|
+
const node_vm_1 = require("node:vm");
|
|
10
|
+
class Element {
|
|
11
|
+
hidden = true;
|
|
12
|
+
value = "";
|
|
13
|
+
textContent = "";
|
|
14
|
+
className = "";
|
|
15
|
+
open = false;
|
|
16
|
+
options = [];
|
|
17
|
+
selectedIndex = 0;
|
|
18
|
+
children = [];
|
|
19
|
+
elements = {};
|
|
20
|
+
onclick;
|
|
21
|
+
onsubmit;
|
|
22
|
+
replaceChildren(...items) { this.children = items; }
|
|
23
|
+
append(...items) { this.children.push(...items); }
|
|
24
|
+
setAttribute() { }
|
|
25
|
+
querySelector() { return this.children.find(item => item.className === "channel-frame"); }
|
|
26
|
+
showModal() { this.open = true; }
|
|
27
|
+
close() { this.open = false; }
|
|
28
|
+
}
|
|
29
|
+
const flush = () => new Promise(resolve => setImmediate(resolve));
|
|
30
|
+
(0, node_test_1.default)("ABWeb只接受当前同源通道页面的结果,刷新不关闭弹窗", async () => {
|
|
31
|
+
const nodes = new Map();
|
|
32
|
+
const node = (selector) => { if (!nodes.has(selector))
|
|
33
|
+
nodes.set(selector, new Element()); return nodes.get(selector); };
|
|
34
|
+
const source = {};
|
|
35
|
+
const frame = new Element();
|
|
36
|
+
frame.className = "channel-frame";
|
|
37
|
+
frame.contentWindow = source;
|
|
38
|
+
let receive;
|
|
39
|
+
(0, node_vm_1.runInNewContext)(await (0, promises_1.readFile)("src-ts/plugins/abweb/public/app.js", "utf8"), {
|
|
40
|
+
document: { querySelector: node, createElement: () => new Element(), hidden: false },
|
|
41
|
+
window: { location: { origin: "http://localhost:26107" }, addEventListener(name, handler) { if (name === "message")
|
|
42
|
+
receive = handler; } },
|
|
43
|
+
localStorage: { getItem: () => null, removeItem() { } }, setInterval() { }, confirm: () => false,
|
|
44
|
+
fetch: async () => ({ ok: true, status: 200, json: async () => ({ plugins: [], messages: [], sessions: [] }) }),
|
|
45
|
+
});
|
|
46
|
+
await flush();
|
|
47
|
+
node("#channel-panel").children = [frame];
|
|
48
|
+
const data = { type: "mab:operation-result", title: "创建 AID 失败", message: "owner AID has no active owner profile", failed: true };
|
|
49
|
+
strict_1.default.equal(typeof receive, "function");
|
|
50
|
+
receive({ origin: "http://other.invalid", source, data });
|
|
51
|
+
receive({ origin: "http://localhost:26107", source: {}, data });
|
|
52
|
+
strict_1.default.equal(node("#operation-result").open, false);
|
|
53
|
+
receive({ origin: "http://localhost:26107", source, data });
|
|
54
|
+
strict_1.default.equal(node("#operation-result").open, true);
|
|
55
|
+
strict_1.default.equal(node("#operation-result-title").textContent, data.title);
|
|
56
|
+
strict_1.default.equal(node("#operation-result-message").textContent, data.message);
|
|
57
|
+
node("#refresh").onclick();
|
|
58
|
+
await flush();
|
|
59
|
+
strict_1.default.equal(node("#operation-result").open, true);
|
|
60
|
+
node("#operation-result-close").onclick();
|
|
61
|
+
strict_1.default.equal(node("#operation-result").open, false);
|
|
62
|
+
});
|
|
63
|
+
for (const scenario of [
|
|
64
|
+
{ ok: false, result: { error: "注册被拒绝" }, expected: "注册被拒绝", failed: true },
|
|
65
|
+
{ ok: true, result: { status: "已创建", connection: "degraded", connectionError: "no trusted roots available" }, expected: "已创建,但连接失败:no trusted roots available", failed: true },
|
|
66
|
+
{ ok: true, result: { status: "已创建", connection: "ready" }, expected: "已创建并连接", failed: false },
|
|
67
|
+
])
|
|
68
|
+
(0, node_test_1.default)(`AUN创建结果通知ABWeb:${scenario.expected}`, async () => {
|
|
69
|
+
const nodes = new Map();
|
|
70
|
+
const node = (selector) => { if (!nodes.has(selector))
|
|
71
|
+
nodes.set(selector, new Element()); return nodes.get(selector); };
|
|
72
|
+
for (const [selector, keys] of [["#create-form", ["aid", "ownerAid"]], ["#config-form", ["agent_plugin", "allow_from", "aun_path", "encryption_seed"]]]) {
|
|
73
|
+
for (const key of keys)
|
|
74
|
+
node(selector).elements[key] = new Element();
|
|
75
|
+
}
|
|
76
|
+
const messages = [];
|
|
77
|
+
let refresh;
|
|
78
|
+
(0, node_vm_1.runInNewContext)(await (0, promises_1.readFile)("src-ts/plugins/aun/public/app.js", "utf8"), {
|
|
79
|
+
document: { querySelector: node, createElement: () => new Element(), hidden: false },
|
|
80
|
+
window: { parent: { postMessage(data, origin) { messages.push({ data, origin }); } }, location: { origin: "http://localhost:26107" } },
|
|
81
|
+
Option: function () { return new Element(); }, localStorage: { getItem: () => null }, setInterval(handler) { refresh = handler; },
|
|
82
|
+
fetch: async (path, options) => options.method === "POST"
|
|
83
|
+
? { ok: scenario.ok, status: scenario.ok ? 201 : 502, json: async () => scenario.result }
|
|
84
|
+
: { ok: true, json: async () => path.endsWith("/identities") ? { groups: { muAgentBridge: [], evolcore: [] } } : { plugins: [] } },
|
|
85
|
+
});
|
|
86
|
+
await flush();
|
|
87
|
+
node("#create-form").elements.aid.value = "new.agentid.cn";
|
|
88
|
+
node("#create-form").elements.ownerAid.value = "owner.agentid.cn";
|
|
89
|
+
node("#create-form").onsubmit({ preventDefault() { } });
|
|
90
|
+
await flush();
|
|
91
|
+
strict_1.default.equal(messages.length, 1);
|
|
92
|
+
strict_1.default.equal(messages[0].data.type, "mab:operation-result");
|
|
93
|
+
strict_1.default.equal(messages[0].data.failed, scenario.failed);
|
|
94
|
+
strict_1.default.ok(messages[0].data.message.includes(scenario.expected));
|
|
95
|
+
strict_1.default.equal(messages[0].origin, "http://localhost:26107");
|
|
96
|
+
refresh();
|
|
97
|
+
await flush();
|
|
98
|
+
strict_1.default.equal(messages.length, 1, "自动刷新不反复弹窗");
|
|
99
|
+
});
|
package/package.json
CHANGED
|
@@ -35,6 +35,25 @@
|
|
|
35
35
|
let loading = false;
|
|
36
36
|
const installingPackages = new Set();
|
|
37
37
|
function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
|
|
38
|
+
function showResult(title, message, failed) {
|
|
39
|
+
node("#operation-result-title").textContent = title;
|
|
40
|
+
const detail = node("#operation-result-message");
|
|
41
|
+
detail.textContent = message;
|
|
42
|
+
detail.className = failed ? "error" : "";
|
|
43
|
+
const dialog = node("#operation-result");
|
|
44
|
+
if (!dialog.open)
|
|
45
|
+
dialog.showModal();
|
|
46
|
+
}
|
|
47
|
+
node("#operation-result-close").onclick = () => node("#operation-result").close();
|
|
48
|
+
window.addEventListener("message", (event) => {
|
|
49
|
+
const frame = channelPanel.querySelector("iframe.channel-frame");
|
|
50
|
+
if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow)
|
|
51
|
+
return;
|
|
52
|
+
const result = event.data;
|
|
53
|
+
if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean")
|
|
54
|
+
return;
|
|
55
|
+
showResult(result.title, result.message, result.failed);
|
|
56
|
+
});
|
|
38
57
|
async function loadCatalog() {
|
|
39
58
|
const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
|
|
40
59
|
const items = result.plugins ?? [];
|
|
@@ -113,7 +132,7 @@
|
|
|
113
132
|
item.type = "button";
|
|
114
133
|
item.textContent = label;
|
|
115
134
|
item.title = title;
|
|
116
|
-
item.onclick = () => { item.disabled = true; void run().catch((cause) =>
|
|
135
|
+
item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); };
|
|
117
136
|
return item;
|
|
118
137
|
}
|
|
119
138
|
async function load(refreshCatalog = true) {
|
|
@@ -34,6 +34,21 @@
|
|
|
34
34
|
const installingPackages = new Set<string>();
|
|
35
35
|
|
|
36
36
|
function error(message: string): void { errorNode.textContent = message; errorNode.hidden = !message; }
|
|
37
|
+
function showResult(title: string, message: string, failed: boolean): void {
|
|
38
|
+
node<HTMLElement>("#operation-result-title").textContent = title;
|
|
39
|
+
const detail = node<HTMLElement>("#operation-result-message");
|
|
40
|
+
detail.textContent = message; detail.className = failed ? "error" : "";
|
|
41
|
+
const dialog = node<HTMLDialogElement>("#operation-result");
|
|
42
|
+
if (!dialog.open) dialog.showModal();
|
|
43
|
+
}
|
|
44
|
+
node<HTMLButtonElement>("#operation-result-close").onclick = () => node<HTMLDialogElement>("#operation-result").close();
|
|
45
|
+
window.addEventListener("message", (event: MessageEvent) => {
|
|
46
|
+
const frame = channelPanel.querySelector<HTMLIFrameElement>("iframe.channel-frame");
|
|
47
|
+
if (event.origin !== window.location.origin || !frame || event.source !== frame.contentWindow) return;
|
|
48
|
+
const result = event.data;
|
|
49
|
+
if (!result || result.type !== "mab:operation-result" || typeof result.title !== "string" || typeof result.message !== "string" || typeof result.failed !== "boolean") return;
|
|
50
|
+
showResult(result.title, result.message, result.failed);
|
|
51
|
+
});
|
|
37
52
|
async function loadCatalog(): Promise<void> {
|
|
38
53
|
const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
|
|
39
54
|
const items: Array<{ name: string; version: string; description: string; type: string }> = result.plugins ?? [];
|
|
@@ -80,7 +95,7 @@
|
|
|
80
95
|
}
|
|
81
96
|
function button(label: string, title: string, run: () => Promise<void>): HTMLButtonElement {
|
|
82
97
|
const item = document.createElement("button"); item.type = "button"; item.textContent = label; item.title = title;
|
|
83
|
-
item.onclick = () => { item.disabled = true; void run().catch((cause) =>
|
|
98
|
+
item.onclick = () => { item.disabled = true; void run().catch((cause) => { const message = cause instanceof Error ? cause.message : String(cause); error(message); showResult(`${title}失败`, message, true); }).finally(() => { item.disabled = false; void load(false); }); }; return item;
|
|
84
99
|
}
|
|
85
100
|
async function load(refreshCatalog = true): Promise<void> {
|
|
86
101
|
if (loading) return;
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
<div id="logs-panel" role="tabpanel" aria-labelledby="logs-tab" hidden><section><div class="section-heading"><h2>运行日志</h2><button id="logs-refresh" type="button">刷新日志</button></div><div class="table-scroll"><table><thead><tr><th>时间</th><th>级别</th><th>来源</th><th>消息</th><th>详细字段</th></tr></thead><tbody id="logs"></tbody></table></div><p id="logs-empty" class="muted" hidden>暂无运行日志</p><div class="section-heading"><button id="logs-prev" type="button">上一页</button><span id="logs-page" class="muted">第 1 页</span><button id="logs-next" type="button">下一页</button></div></section></div>
|
|
22
22
|
<p id="error" class="error" role="alert" hidden></p>
|
|
23
23
|
</main>
|
|
24
|
+
<dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
|
|
25
|
+
<h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
|
|
26
|
+
</dialog>
|
|
24
27
|
<script src="/abweb/app.js"></script>
|
|
25
28
|
</body>
|
|
26
29
|
</html>
|
|
@@ -56,3 +56,7 @@ th { color: #566761; font-weight: 600; background: #edf1ef; }
|
|
|
56
56
|
th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child(3), th:nth-child(4) { width: 145px; } th:nth-child(5) { width: 62px; } th:nth-child(7) { width: 140px; }
|
|
57
57
|
.message-error td:last-child { color: #a83427; }
|
|
58
58
|
@media (max-width: 620px) { .shell { padding: 16px; } .section-heading { align-items: flex-start; flex-direction: column; } form, form input { width: 100%; } }
|
|
59
|
+
|
|
60
|
+
#operation-result { width: min(520px, calc(100% - 32px)); border: 1px solid #bfc9c5; border-radius: 8px; padding: 24px; }
|
|
61
|
+
#operation-result::backdrop { background: rgb(0 0 0 / 35%); }
|
|
62
|
+
#operation-result-message { white-space: pre-wrap; overflow-wrap: anywhere; margin: 16px 0; }
|
|
@@ -10,7 +10,7 @@ const node_url_1 = require("node:url");
|
|
|
10
10
|
const messages_1 = require("./messages");
|
|
11
11
|
const evolcore_1 = require("./evolcore");
|
|
12
12
|
const manifest = {
|
|
13
|
-
id: "aun", version: "1.0.
|
|
13
|
+
id: "aun", version: "1.0.17", type: "channel", displayName: "AUN 通道",
|
|
14
14
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
15
15
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health"],
|
|
16
16
|
configSchema: "config.schema.json",
|
|
@@ -87,9 +87,8 @@ class AunPlugin {
|
|
|
87
87
|
await this.persist();
|
|
88
88
|
this.context?.logger.info("AUN 身份已创建", { aid, owner });
|
|
89
89
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded" };
|
|
90
|
+
const connectionError = !this.clients.has(aid) ? await this.connectAid(aid) : undefined;
|
|
91
|
+
return { aid, status: "已创建", connection: this.clients.has(aid) ? "ready" : "degraded", ...(connectionError ? { connectionError } : {}) };
|
|
93
92
|
}
|
|
94
93
|
finally {
|
|
95
94
|
this.creating = false;
|
|
@@ -244,7 +243,8 @@ class AunPlugin {
|
|
|
244
243
|
catch (error) {
|
|
245
244
|
this.status = "degraded";
|
|
246
245
|
this.healthMessage = safeError(error);
|
|
247
|
-
this.context?.logger.warn("AUN 通道连接失败", { error: this.healthMessage });
|
|
246
|
+
this.context?.logger.warn("AUN 通道连接失败", { aid, error: this.healthMessage });
|
|
247
|
+
return this.healthMessage;
|
|
248
248
|
}
|
|
249
249
|
}
|
|
250
250
|
async deactivate(_reason) {
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
return result;
|
|
16
16
|
}
|
|
17
17
|
function show(message, failed = false) { status.textContent = message; status.className = failed ? "error" : ""; }
|
|
18
|
+
function notify(title, message, failed = false) {
|
|
19
|
+
show(message, failed);
|
|
20
|
+
if (window.parent !== window) window.parent.postMessage({ type: "mab:operation-result", title, message, failed }, window.location.origin);
|
|
21
|
+
else window.alert(`${title}\n${message}`);
|
|
22
|
+
}
|
|
23
|
+
const errorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
18
24
|
async function load(refreshForm = true) {
|
|
19
25
|
if (loading) return;
|
|
20
26
|
loading = true;
|
|
@@ -50,15 +56,22 @@
|
|
|
50
56
|
async function bind(aid, pluginId) {
|
|
51
57
|
try {
|
|
52
58
|
await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
|
|
53
|
-
await load();
|
|
54
|
-
} catch (error) {
|
|
59
|
+
await load(); notify("关联成功", `${aid} 已关联 ${pluginId}`);
|
|
60
|
+
} catch (error) { notify("关联失败", errorMessage(error), true); }
|
|
55
61
|
}
|
|
56
62
|
createForm.onsubmit = (event) => { event.preventDefault(); const aid = createForm.elements.aid.value.trim(); const owner = createForm.elements.ownerAid.value.trim();
|
|
57
|
-
|
|
63
|
+
show("正在创建 AID 并连接,请稍候……");
|
|
64
|
+
void request("/api/plugins/aun/aid", "POST", { aid, ownerAid: owner }).then((result) => {
|
|
65
|
+
const failed = result.connection === "degraded";
|
|
66
|
+
const message = failed ? `${aid} 已创建,但连接失败:${result.connectionError || "未提供连接失败原因,请查看运行日志"}\n身份已经保存在本地,请勿重复注册。`
|
|
67
|
+
: result.status === "等待 Owner 确认" ? `${aid} 等待 Owner 确认` : `${aid} 已创建并连接`;
|
|
68
|
+
notify(failed ? "AID 创建完成,连接失败" : "AID 创建结果", message, failed);
|
|
69
|
+
return load();
|
|
70
|
+
}).catch((error) => notify("创建 AID 失败", errorMessage(error), true));
|
|
58
71
|
};
|
|
59
72
|
configForm.onsubmit = (event) => { event.preventDefault(); const changes = { agent_plugin: configForm.elements.agent_plugin.value, allow_from: configForm.elements.allow_from.value, aun_path: configForm.elements.aun_path.value };
|
|
60
73
|
if (configForm.elements.encryption_seed.value) changes.encryption_seed = configForm.elements.encryption_seed.value;
|
|
61
|
-
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() =>
|
|
74
|
+
void request("/api/plugins/aun/settings", "POST", changes).then(() => request("/api/plugins/aun/reload", "POST")).then(() => load()).then(() => notify("保存成功", "配置已保存")).catch((error) => notify("保存配置失败", errorMessage(error), true));
|
|
62
75
|
};
|
|
63
76
|
find("#refresh").onclick = () => void load();
|
|
64
77
|
void load();
|
package/plugins/aun/package.json
CHANGED