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.
@@ -10,7 +10,7 @@ function normalizeAunMessage(payload, selfAid, allowFrom) {
10
10
  const data = payload;
11
11
  const peerId = typeof data.from === "string" ? data.from.trim() : "";
12
12
  const rules = (allowFrom ?? "").split(",").map((value) => value.trim()).filter(Boolean);
13
- if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes(peerId)))
13
+ if (!peerId || peerId === selfAid || (rules.length > 0 && !rules.includes("*") && !rules.includes(peerId)))
14
14
  return undefined;
15
15
  const body = data.payload && typeof data.payload === "object" ? data.payload : {};
16
16
  // 对端的系统事件只作通知,转为任务回复会导致两个 Agent 循环互答。
@@ -4,6 +4,12 @@
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;
7
13
  let agents = [];
8
14
  let loading = false;
9
15
 
@@ -29,7 +35,8 @@
29
35
  agents = (plugins.plugins || []).filter((plugin) => plugin.type === "agent");
30
36
  if (refreshForm) createForm.elements.ownerAid.value = config.owner_aid || "";
31
37
  if (refreshForm) {
32
- for (const key of ["allow_from", "aun_path"]) configForm.elements[key].value = config[key] || "";
38
+ configForm.elements.allow_from.value = config.allow_from?.split(",").some(value => value.trim() === "*") ? "" : config.allow_from || "";
39
+ configForm.elements.aun_path.value = config.aun_path || "";
33
40
  configForm.elements.encryption_seed.value = "";
34
41
  }
35
42
  list.replaceChildren();
@@ -52,10 +59,129 @@
52
59
  cell.append(select); row.append(cell); list.append(row);
53
60
  }
54
61
  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();
55
64
  show(`${entries.length} 个已连接 AID`);
56
65
  } catch (error) { show(String(error), true); }
57
66
  finally { loading = false; }
58
67
  }
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 ? `等待指定 Owner 通过 AUN 回复验证指令;验证截止:${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: false, publishAvailable: false };
118
+ state.challengeExpiresAt = result.expiresAt;
119
+ notify("Owner 验证已发送", `验证指令已通过 AUN 发送给 ${ownerAid}。请该 Owner 在 AUN 中回复收到的 /ability-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)}\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 expired = !Number.isFinite(entry.expiresAt) || entry.expiresAt <= Date.now();
164
+ const unavailable = confirming || !verified || entry.state !== "pending_confirmation";
165
+ approve.disabled = unavailable || expired; reject.disabled = unavailable;
166
+ expiry.textContent = expired ? "已过期,不能批准" : "有效期内,每次批准只执行本条请求";
167
+ proof.textContent = verified ? "当前会话的 Owner 已验证" : "此 Provider 的 Owner 未验证,请先验证 Owner";
168
+ } };
169
+ const confirm = async (approved) => {
170
+ pending.sync();
171
+ if (confirming || (approved ? approve.disabled : reject.disabled)) return;
172
+ confirming = true; for (const item of pendingRows) item.sync();
173
+ try {
174
+ const result = await request("/api/plugins/aun/abilities/confirm", "POST", { key: entry.key, confirmationFingerprint: entry.confirmationFingerprint, approved });
175
+ notify("能力确认结果", `状态:${result.state}\n${result.result ? JSON.stringify(result.result, null, 2) : ""}\n${result.delivered ? "结果已回投调用方" : "结果尚未送达调用方;不会自动重复执行"}`, result.state === "failed" || result.state === "unknown" || result.delivered === false);
176
+ } catch (error) { notify("能力确认失败", errorMessage(error), true); }
177
+ finally { confirming = false; for (const item of pendingRows) item.sync(); }
178
+ try { await loadPending(true); } catch (error) { show(`待确认列表刷新失败:${errorMessage(error)}`, true); }
179
+ };
180
+ approve.onclick = () => void confirm(true); reject.onclick = () => void confirm(false);
181
+ actions.append(proof, approve, reject); pendingRows.push(pending); pending.sync(); pendingList.append(row);
182
+ }
183
+ if (!pendingList.children.length) emptyRow(pendingList, 7, "暂无待确认的能力调用");
184
+ }
59
185
  async function bind(aid, pluginId) {
60
186
  try {
61
187
  await request(`/api/plugins/${encodeURIComponent(pluginId)}/aid`, "PATCH", { aid });
@@ -6,8 +6,10 @@
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 回复收到的 /ability-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>核对认证调用方、原始参数、契约指纹、影响和执行目标后,逐项批准一次或拒绝。批准只绑定表格中的完整请求;过期请求不能批准。没有当前会话的 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>
9
11
  <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>
10
- <form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from"></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>
12
+ <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>
11
13
  </main>
12
14
  <script src="/abweb/channels/aun/app.js"></script>
13
15
  </body>
@@ -17,5 +17,14 @@ 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
+ 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; }
20
29
  .error { color: #a83427; }
21
30
  @media (max-width: 620px) { .fields { grid-template-columns: 1fr; } }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bridge-aun",
3
- "version": "1.0.40",
3
+ "version": "1.0.44",
4
4
  "description": "mu-agent-bridge 的 AUN 通道插件。",
5
5
  "keywords": [
6
6
  "mu-agent-bridge-plugin",
@@ -30,6 +30,7 @@
30
30
  "node": ">=22.16.0"
31
31
  },
32
32
  "dependencies": {
33
- "@agentunion/fastaun": "0.5.18"
33
+ "@agentunion/fastaun": "0.5.18",
34
+ "ajv": "^8.20.0"
34
35
  }
35
36
  }
package/plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "aun",
3
- "version": "1.0.40",
3
+ "version": "1.0.44",
4
4
  "type": "channel",
5
5
  "displayName": "AUN 通道",
6
6
  "entry": "dist/src-ts/plugins/aun/index.js",