agent-bridge-abweb 1.0.0 → 1.0.1

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.
@@ -5,7 +5,7 @@ const node_fs_1 = require("node:fs");
5
5
  const node_path_1 = require("node:path");
6
6
  const MANIFEST = {
7
7
  id: "abweb",
8
- version: "1.0.0",
8
+ version: "1.0.1",
9
9
  displayName: "Agent Bridge Web",
10
10
  entry: "dist/index.js",
11
11
  apiVersion: "1",
@@ -7,6 +7,8 @@
7
7
  const tokenNode = node("#token");
8
8
  const pairCodeNode = node("#pair-code");
9
9
  const packageNode = node("#package-name");
10
+ const packageSearchNode = node("#package-search");
11
+ const catalogNode = node("#catalog");
10
12
  const pluginPathNode = node("#plugin-path");
11
13
  const picker = node("#plugin-picker");
12
14
  const aidList = node("#aids");
@@ -17,6 +19,19 @@
17
19
  let pluginConfigs = {};
18
20
  let pluginVersions = {};
19
21
  function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
22
+ async function loadCatalog() {
23
+ const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
24
+ const items = result.plugins ?? [];
25
+ packageNode.replaceChildren(...[["Agent", "agent"], ["Channel", "channel"], ["管理", "management"]].flatMap(([label, type]) => {
26
+ const group = document.createElement("optgroup");
27
+ group.label = label;
28
+ group.append(...items.filter((item) => item.type === type).map((item) => new Option(`${item.name} · v${item.version}`, item.name)));
29
+ return group.children.length ? [group] : [];
30
+ }));
31
+ catalogNode.replaceChildren(...items.map((plugin) => { const item = document.createElement("div"); item.className = "catalog-row"; item.textContent = `${plugin.type} · ${plugin.name} · ${plugin.description || "无描述"}`; return item; }));
32
+ if (!(result.plugins ?? []).length)
33
+ catalogNode.textContent = "未找到已声明类型的 npm 插件";
34
+ }
20
35
  async function request(path, method = "GET", body) {
21
36
  const response = await fetch(path, { method, headers: {
22
37
  accept: "application/json", ...(body ? { "content-type": "application/json" } : {}),
@@ -96,7 +111,13 @@
96
111
  function renderPlugins(items) {
97
112
  plugins = items;
98
113
  const selected = picker.value || items[0]?.id;
99
- picker.replaceChildren(...items.map((plugin) => new Option(`${plugin.displayName || plugin.id} · v${plugin.version} · ${plugin.status}`, plugin.id)));
114
+ const groups = [["Agent", items.filter((plugin) => plugin.type === "agent" || (!plugin.type && plugin.capabilities?.includes("chat")))], ["Channel", items.filter((plugin) => plugin.type === "channel" || plugin.capabilities?.includes("channel"))], ["管理", items.filter((plugin) => plugin.type === "management" || (!plugin.type && plugin.capabilities?.includes("management")))]];
115
+ picker.replaceChildren(...groups.flatMap(([label, group]) => {
116
+ const options = document.createElement("optgroup");
117
+ options.label = label;
118
+ options.append(...group.map((plugin) => new Option(`${plugin.displayName || plugin.id} · v${plugin.version} · ${plugin.status}`, plugin.id)));
119
+ return group.length ? [options] : [];
120
+ }));
100
121
  picker.value = items.some((plugin) => plugin.id === selected) ? selected : items[0]?.id ?? "";
101
122
  pluginsNode.replaceChildren();
102
123
  for (const plugin of items.filter((item) => item.id === picker.value)) {
@@ -188,11 +209,20 @@
188
209
  return load();
189
210
  }).catch((cause) => error(String(cause)));
190
211
  };
191
- node("#install-form").onsubmit = (event) => {
192
- event.preventDefault();
212
+ node("#install-package").onclick = () => {
213
+ if (!packageNode.value)
214
+ return error("请先选择插件");
193
215
  void request("/api/plugins/install", "POST", { packageName: packageNode.value })
194
216
  .then(() => load()).catch((cause) => error(String(cause)));
195
217
  };
218
+ node("#install-name-form").onsubmit = (event) => {
219
+ event.preventDefault();
220
+ const packageName = node("#manual-package-name").value.trim();
221
+ void request("/api/plugins/install", "POST", { packageName }).then(() => load()).catch((cause) => error(String(cause)));
222
+ };
223
+ node("#search-packages").onclick = () => void loadCatalog().catch((cause) => error(String(cause)));
224
+ packageSearchNode.onkeydown = (event) => { if (event.key === "Enter")
225
+ void loadCatalog().catch((cause) => error(String(cause))); };
196
226
  node("#install-local-form").onsubmit = (event) => {
197
227
  event.preventDefault();
198
228
  void request("/api/plugins/install", "POST", { source: pluginPathNode.value.trim() })
@@ -210,4 +240,5 @@
210
240
  }).catch((cause) => { result.textContent = ""; error(String(cause)); });
211
241
  };
212
242
  void load();
243
+ void loadCatalog().catch((cause) => error(String(cause)));
213
244
  })();
@@ -1,5 +1,5 @@
1
1
  (() => {
2
- type Plugin = { id: string; displayName: string; version: string; status: string; error?: string; path: string };
2
+ type Plugin = { id: string; displayName: string; version: string; status: string; type?: string; capabilities?: string[]; error?: string; path: string };
3
3
  const node = <T extends HTMLElement>(selector: string): T => document.querySelector<T>(selector)!;
4
4
  const pluginsNode = node<HTMLDivElement>("#plugins");
5
5
  const messagesNode = node<HTMLTableSectionElement>("#messages");
@@ -8,6 +8,8 @@
8
8
  const tokenNode = node<HTMLInputElement>("#token");
9
9
  const pairCodeNode = node<HTMLInputElement>("#pair-code");
10
10
  const packageNode = node<HTMLSelectElement>("#package-name");
11
+ const packageSearchNode = node<HTMLInputElement>("#package-search");
12
+ const catalogNode = node<HTMLDivElement>("#catalog");
11
13
  const pluginPathNode = node<HTMLInputElement>("#plugin-path");
12
14
  const picker = node<HTMLSelectElement>("#plugin-picker");
13
15
  const aidList = node<HTMLDivElement>("#aids");
@@ -19,6 +21,17 @@
19
21
  let pluginVersions: Record<string, any> = {};
20
22
 
21
23
  function error(message: string): void { errorNode.textContent = message; errorNode.hidden = !message; }
24
+ async function loadCatalog(): Promise<void> {
25
+ const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
26
+ const items: Array<{ name: string; version: string; description: string; type: string }> = result.plugins ?? [];
27
+ packageNode.replaceChildren(...[["Agent", "agent"], ["Channel", "channel"], ["管理", "management"]].flatMap(([label, type]) => {
28
+ const group = document.createElement("optgroup"); group.label = label;
29
+ group.append(...items.filter((item) => item.type === type).map((item) => new Option(`${item.name} · v${item.version}`, item.name)));
30
+ return group.children.length ? [group] : [];
31
+ }));
32
+ catalogNode.replaceChildren(...items.map((plugin) => { const item = document.createElement("div"); item.className = "catalog-row"; item.textContent = `${plugin.type} · ${plugin.name} · ${plugin.description || "无描述"}`; return item; }));
33
+ if (!(result.plugins ?? []).length) catalogNode.textContent = "未找到已声明类型的 npm 插件";
34
+ }
22
35
  async function request(path: string, method = "GET", body?: unknown): Promise<any> {
23
36
  const response = await fetch(path, { method, headers: {
24
37
  accept: "application/json", ...(body ? { "content-type": "application/json" } : {}),
@@ -78,7 +91,12 @@
78
91
  function renderPlugins(items: Plugin[]): void {
79
92
  plugins = items;
80
93
  const selected = picker.value || items[0]?.id;
81
- picker.replaceChildren(...items.map((plugin) => new Option(`${plugin.displayName || plugin.id} · v${plugin.version} · ${plugin.status}`, plugin.id)));
94
+ const groups = [["Agent", items.filter((plugin) => plugin.type === "agent" || (!plugin.type && plugin.capabilities?.includes("chat")))], ["Channel", items.filter((plugin) => plugin.type === "channel" || plugin.capabilities?.includes("channel"))], ["管理", items.filter((plugin) => plugin.type === "management" || (!plugin.type && plugin.capabilities?.includes("management")))]] as const;
95
+ picker.replaceChildren(...groups.flatMap(([label, group]) => {
96
+ const options = document.createElement("optgroup"); options.label = label;
97
+ options.append(...group.map((plugin) => new Option(`${plugin.displayName || plugin.id} · v${plugin.version} · ${plugin.status}`, plugin.id)));
98
+ return group.length ? [options] : [];
99
+ }));
82
100
  picker.value = items.some((plugin) => plugin.id === selected) ? selected : items[0]?.id ?? "";
83
101
  pluginsNode.replaceChildren();
84
102
  for (const plugin of items.filter((item) => item.id === picker.value)) {
@@ -140,11 +158,18 @@
140
158
  tokenNode.value = result.token; sessionStorage.setItem("mab-token", result.token); pairCodeNode.value = ""; return load();
141
159
  }).catch((cause) => error(String(cause)));
142
160
  };
143
- node<HTMLFormElement>("#install-form").onsubmit = (event) => {
144
- event.preventDefault();
161
+ node<HTMLButtonElement>("#install-package").onclick = () => {
162
+ if (!packageNode.value) return error("请先选择插件");
145
163
  void request("/api/plugins/install", "POST", { packageName: packageNode.value })
146
164
  .then(() => load()).catch((cause) => error(String(cause)));
147
165
  };
166
+ node<HTMLFormElement>("#install-name-form").onsubmit = (event) => {
167
+ event.preventDefault();
168
+ const packageName = node<HTMLInputElement>("#manual-package-name").value.trim();
169
+ void request("/api/plugins/install", "POST", { packageName }).then(() => load()).catch((cause) => error(String(cause)));
170
+ };
171
+ node<HTMLButtonElement>("#search-packages").onclick = () => void loadCatalog().catch((cause) => error(String(cause)));
172
+ packageSearchNode.onkeydown = (event) => { if (event.key === "Enter") void loadCatalog().catch((cause) => error(String(cause))); };
148
173
  node<HTMLFormElement>("#install-local-form").onsubmit = (event) => {
149
174
  event.preventDefault();
150
175
  void request("/api/plugins/install", "POST", { source: pluginPathNode.value.trim() })
@@ -161,5 +186,5 @@
161
186
  return load();
162
187
  }).catch((cause) => { result.textContent = ""; error(String(cause)); });
163
188
  };
164
- void load();
189
+ void load(); void loadCatalog().catch((cause) => error(String(cause)));
165
190
  })();
@@ -13,7 +13,7 @@
13
13
  <nav class="tabs" role="tablist" aria-label="管理视图"><button id="settings-tab" type="button" role="tab" aria-selected="true" aria-controls="settings-panel">插件设置</button><button id="messages-tab" type="button" role="tab" aria-selected="false" aria-controls="messages-panel">通信记录</button></nav>
14
14
  <div id="settings-panel" role="tabpanel" aria-labelledby="settings-tab">
15
15
  <section><div class="section-heading"><h2>版本管理</h2><button id="check-versions" type="button">检查更新</button></div><div id="versions" class="muted"></div></section>
16
- <section><div class="section-heading"><h2>插件</h2><select id="plugin-picker" aria-label="选择插件"></select></div><div id="plugins" class="plugins"></div><form id="install-form"><select id="package-name" aria-label="选择 npm 插件"><option value="agent-bridge-qwenwork">千问办公</option><option value="agent-bridge-abweb">管理页面</option><option value="agent-bridge-dsh">DeepSeek Harness</option><option value="agent-bridge-doubao">豆包</option></select><button type="submit">安装 npm 插件</button></form><form id="install-local-form"><input id="plugin-path" required placeholder="Windows 上的插件目录路径" aria-label="本地插件目录"><button type="submit">安装本地插件</button></form></section>
16
+ <section><div class="section-heading"><h2>插件</h2><select id="plugin-picker" aria-label="选择插件"></select></div><div id="plugins" class="plugins"></div><div class="plugin-catalog"><div class="section-heading"><h3>npm 插件目录</h3><button id="search-packages" type="button">刷新目录</button></div><input id="package-search" placeholder="搜索插件名称或功能" aria-label="搜索 npm 插件"><select id="package-name" aria-label="选择 npm 插件"></select><button id="install-package" type="button">安装选中插件</button><form id="install-name-form"><input id="manual-package-name" required pattern="agent-bridge-[a-z0-9-]+" placeholder="输入 npm 包名,例如 agent-bridge-workbuddy" aria-label="直接输入 npm 包名"><button type="submit">按包名安装</button></form><div id="catalog" class="catalog"></div></div><form id="install-local-form"><input id="plugin-path" required placeholder="Windows 上的插件目录路径" aria-label="本地插件目录"><button type="submit">安装本地插件</button></form></section>
17
17
  <section><div class="section-heading"><h2>已连接 AID</h2><span id="aid-count" class="muted"></span></div><div id="aids" class="aid-list"></div><form id="create-aid-form"><input id="new-aid" required placeholder="新 AID,例如 new.agentid.cn" aria-label="新 AID"><input id="owner-aid" required placeholder="owner AID" aria-label="owner AID"><button type="submit">创建 AID</button></form><p id="aid-result" class="muted" role="status"></p><p class="muted">在插件列表中选择插件,再用“关联 AID”下拉框保存对应身份。</p></section>
18
18
  </div>
19
19
  <div id="messages-panel" role="tabpanel" aria-labelledby="messages-tab" hidden><section><div class="section-heading"><h2>最近通信</h2></div><div class="table-scroll"><table><thead><tr><th scope="col">时间</th><th scope="col">插件</th><th scope="col">接收 AID</th><th scope="col">来源 Peer</th><th scope="col">方向</th><th scope="col">消息</th><th scope="col">错误</th></tr></thead><tbody id="messages"></tbody></table></div><p id="messages-empty" class="muted" hidden>暂无通信记录</p></section></div>
@@ -29,6 +29,12 @@ form { margin-top: 14px; flex-wrap: wrap; }
29
29
  .plugin-main { flex-wrap: wrap; }
30
30
  .controls { flex-wrap: wrap; }
31
31
  .controls select { max-width: 210px; }
32
+ .plugin-catalog { border-top: 1px solid #e0e5e2; margin-top: 18px; padding-top: 14px; }
33
+ .plugin-catalog h3 { font-size: 14px; margin: 0; }
34
+ .plugin-catalog input { width: min(100%, 420px); margin: 10px 0; }
35
+ .catalog { margin-top: 10px; }
36
+ .catalog-row { padding: 7px 0; color: #566761; font-size: 13px; border-bottom: 1px solid #edf1ef; }
37
+ .plugin-catalog form { margin-top: 8px; }
32
38
  .muted { color: #65736e; font-size: 13px; }
33
39
  .status { font-size: 12px; padding: 3px 7px; background: #e7ebea; border-radius: 4px; }
34
40
  .status-ready { color: #136a44; background: #def3e5; }
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "agent-bridge-abweb",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "agent-bridge 的网页管理插件。",
5
+ "keywords": ["mu-agent-bridge-plugin", "mu-agent-bridge-management"],
5
6
  "repository": { "type": "git", "url": "https://codeup.aliyun.com/6808da56fb3da14b899afd54/agent_bridge.git", "directory": "packages/abweb" },
6
7
  "main": "dist/src-ts/plugins/abweb/index.js",
7
8
  "files": [
@@ -11,7 +12,8 @@
11
12
  "README.md"
12
13
  ],
13
14
  "agentBridge": {
14
- "plugin": "./plugin.json"
15
+ "plugin": "./plugin.json",
16
+ "type": "management"
15
17
  },
16
18
  "publishConfig": {
17
19
  "access": "public",
package/plugin.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "id": "abweb",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
+ "type": "management",
4
5
  "displayName": "Agent Bridge Web",
5
6
  "entry": "dist/src-ts/plugins/abweb/index.js",
6
7
  "apiVersion": "1",