mu-agent-bridge 1.0.71 → 1.0.78
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 +17 -10
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +12 -30
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +31 -13
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +351 -50
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +155 -21
- package/dist/src-ts/plugins/aun/messages.js +4 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/dist/src-ts/plugins/doubao/index.js +152 -8
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +172 -21
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +132 -14
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +73 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +20 -0
- package/dist/tests-ts/aun-plugin.test.js +292 -19
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
- package/dist/tests-ts/control-server.test.js +56 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +773 -12
- package/dist/tests-ts/dsh-agent.test.js +157 -8
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +146 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +663 -0
- package/dist/tests-ts/platform-integration.test.js +183 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +356 -4
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +159 -4
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -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/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +23 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
let logHasNext = false;
|
|
33
33
|
let loading = false;
|
|
34
34
|
const installingPackages = new Set<string>();
|
|
35
|
+
let assistantOriginal: Record<string, unknown> = {};
|
|
35
36
|
|
|
36
37
|
function error(message: string): void { errorNode.textContent = message; errorNode.hidden = !message; }
|
|
37
38
|
function showResult(title: string, message: string, failed: boolean): void {
|
|
@@ -51,7 +52,7 @@
|
|
|
51
52
|
});
|
|
52
53
|
async function loadCatalog(): Promise<void> {
|
|
53
54
|
const result = await request(`/api/plugins/catalog?q=${encodeURIComponent(packageSearchNode.value.trim())}`);
|
|
54
|
-
const items: Array<{ name: string; version: string; description: string; type: string }> = result.plugins ?? [];
|
|
55
|
+
const items: Array<{ id?: string; name: string; version: string; description: string; type: string }> = result.plugins ?? [];
|
|
55
56
|
catalogNode.replaceChildren();
|
|
56
57
|
if (!items.length) { catalogNode.textContent = "未找到已声明类型的 npm 插件"; return; }
|
|
57
58
|
const typeNames: Record<string, string> = { agent: "Agent", channel: "通道", management: "管理" };
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
head.append(headRow);
|
|
63
64
|
const body = document.createElement("tbody");
|
|
64
65
|
for (const plugin of items) {
|
|
65
|
-
const installed = plugins.find((item) => item.id === plugin.name.replace(/^agent-bridge-/, ""));
|
|
66
|
+
const installed = plugins.find((item) => item.id === (plugin.id ?? plugin.name.replace(/^agent-bridge-/, "")));
|
|
66
67
|
const versionStatus = installed ? pluginVersions[installed.id] : undefined;
|
|
67
68
|
const isUpgrade = Boolean(installed && versionStatus?.packageName === plugin.name && newerVersion(plugin.version, installed.version));
|
|
68
69
|
const row = document.createElement("tr");
|
|
@@ -149,7 +150,8 @@
|
|
|
149
150
|
const entries = [data.core, ...(data.plugins ?? []).map((plugin: any) => plugin.versionStatus)].filter(Boolean);
|
|
150
151
|
versionsNode.replaceChildren(...entries.map((entry: any) => {
|
|
151
152
|
const row = document.createElement("div"); row.className = "version-row";
|
|
152
|
-
|
|
153
|
+
const state = entry.state === "unpublished" ? "未发布" : entry.state;
|
|
154
|
+
row.textContent = `${entry.packageName} · 当前 ${entry.currentVersion} · ${entry.latestVersion ? `最新 ${entry.latestVersion}` : "最新版本未知"} · ${state}${entry.error ? ` · ${entry.error}` : ""}`;
|
|
153
155
|
return row;
|
|
154
156
|
}));
|
|
155
157
|
if (!entries.length) versionsNode.textContent = "尚未检查版本";
|
|
@@ -194,7 +196,7 @@
|
|
|
194
196
|
const version = document.createElement("td"); version.textContent = `v${plugin.version}`;
|
|
195
197
|
const state = document.createElement("td");
|
|
196
198
|
const status = document.createElement("span"); status.className = `status status-${plugin.status}`; status.textContent = plugin.status;
|
|
197
|
-
if (plugin.type === "agent") type.textContent +=
|
|
199
|
+
if (plugin.type === "agent") type.textContent += plugin.manifest?.executionMode === "cli" ? " · CLI" : " · 桌面";
|
|
198
200
|
const dependencies = Object.entries(plugin.manifest?.dependencies ?? {});
|
|
199
201
|
if (dependencies.length) { const detail = document.createElement("small"); detail.className = "muted plugin-id"; detail.textContent = `依赖:${dependencies.map(([id, range]) => `${id} ${range}`).join(";")}`; name.append(detail); }
|
|
200
202
|
state.append(status);
|
|
@@ -207,6 +209,7 @@
|
|
|
207
209
|
}
|
|
208
210
|
const controls = document.createElement("td"); controls.className = "controls";
|
|
209
211
|
if (plugin.manifest?.executionMode === "cli") controls.append(button("配置", `配置 ${plugin.id}`, () => configureCli(plugin)));
|
|
212
|
+
if (plugin.id === "personal-assistant") controls.append(button("配置模型", "配置个人助理模型来源", () => configureAssistant(plugin)));
|
|
210
213
|
if (pluginVersions[plugin.id]?.state === "update-available" && plugin.path.includes("node_modules")) { controlsUpgrade(plugin.id); controls.append(button("下载", `下载 ${plugin.id}`, async () => { await request(`/api/plugins/${encodeURIComponent(plugin.id)}/download`, "POST", { packageName: plugin.packageName, version: pluginVersions[plugin.id]?.latestVersion }); await load(); })); }
|
|
211
214
|
for (const [label, action] of [["启动", "start"], ["停止", "stop"], ["重载", "reload"]]) {
|
|
212
215
|
controls.append(button(label, `${label} ${plugin.id}`, async () => { await request(`/api/plugins/${encodeURIComponent(plugin.id)}/${action}`, "POST"); await load(); }));
|
|
@@ -246,6 +249,55 @@
|
|
|
246
249
|
const detail = node<HTMLElement>("#cli-config-error"); detail.textContent = String(cause); detail.hidden = false;
|
|
247
250
|
}).finally(() => { save.disabled = false; });
|
|
248
251
|
};
|
|
252
|
+
node<HTMLButtonElement>("#assistant-config-cancel").onclick = () => node<HTMLDialogElement>("#assistant-config-dialog").close();
|
|
253
|
+
async function configureAssistant(plugin: Plugin): Promise<void> {
|
|
254
|
+
const [config, sourceResult] = await Promise.all([
|
|
255
|
+
request(`/api/plugins/${encodeURIComponent(plugin.id)}/config`),
|
|
256
|
+
request("/api/model-providers"),
|
|
257
|
+
]);
|
|
258
|
+
assistantOriginal = config;
|
|
259
|
+
const select = node<HTMLSelectElement>("#assistant-model-provider");
|
|
260
|
+
select.replaceChildren();
|
|
261
|
+
const add = (value: string, label: string, disabled = false) => { const option = document.createElement("option"); option.value = value; option.textContent = label; option.disabled = disabled; select.append(option); };
|
|
262
|
+
add("openai", "OpenAI 兼容接口");
|
|
263
|
+
if (config.modelProvider === "claude-cli") add("claude-cli", "Claude CLI(原有配置,用户默认模型)");
|
|
264
|
+
for (const provider of sourceResult.providers ?? []) add(`plugin:${provider.id}`, `${provider.displayName || provider.id}${provider.available ? "" : `(不可用:${provider.reason || provider.status})`}`, !provider.available);
|
|
265
|
+
if (config.modelProvider === "plugin" && config.modelPlugin && !(sourceResult.providers ?? []).some((item: any) => item.id === config.modelPlugin)) add(`plugin:${config.modelPlugin}`, `${config.modelPlugin}(未安装或未声明模型来源)`, true);
|
|
266
|
+
select.value = config.modelProvider === "plugin" && config.modelPlugin ? `plugin:${config.modelPlugin}` : config.modelProvider ?? "openai";
|
|
267
|
+
node<HTMLInputElement>("#assistant-model").value = config.model ?? "";
|
|
268
|
+
node<HTMLInputElement>("#assistant-endpoint").value = config.endpoint ?? "";
|
|
269
|
+
node<HTMLInputElement>("#assistant-api-key").value = "";
|
|
270
|
+
node<HTMLInputElement>("#assistant-api-key").placeholder = config.apiKey ? "已配置,留空保留" : "可选,本地免认证服务可留空";
|
|
271
|
+
node<HTMLInputElement>("#assistant-timeout").value = String(config.timeoutMs ?? 120000);
|
|
272
|
+
node<HTMLTextAreaElement>("#assistant-allowed-peers").value = config.allowedPeers?.includes("*") ? "" : (config.allowedPeers ?? []).join("\n");
|
|
273
|
+
node<HTMLTextAreaElement>("#assistant-allowed-agents").value = config.allowedAgents?.includes("*") ? "" : (config.allowedAgents ?? []).join("\n");
|
|
274
|
+
node<HTMLElement>("#assistant-config-error").hidden = true;
|
|
275
|
+
node<HTMLDialogElement>("#assistant-config-dialog").showModal();
|
|
276
|
+
}
|
|
277
|
+
node<HTMLFormElement>("#assistant-config-form").onsubmit = event => {
|
|
278
|
+
event.preventDefault();
|
|
279
|
+
const save = node<HTMLButtonElement>("#assistant-config-save"); save.disabled = true;
|
|
280
|
+
const source = node<HTMLSelectElement>("#assistant-model-provider").value;
|
|
281
|
+
const provider = source.startsWith("plugin:") ? "plugin" : source;
|
|
282
|
+
const modelPlugin = source.startsWith("plugin:") ? source.slice("plugin:".length) : undefined;
|
|
283
|
+
const body: Record<string, unknown> = {};
|
|
284
|
+
if (provider !== (assistantOriginal.modelProvider ?? "openai")) body.modelProvider = provider;
|
|
285
|
+
if (provider === "plugin" && modelPlugin !== assistantOriginal.modelPlugin) body.modelPlugin = modelPlugin;
|
|
286
|
+
const model = node<HTMLInputElement>("#assistant-model").value.trim(); const endpoint = node<HTMLInputElement>("#assistant-endpoint").value.trim(); const apiKey = node<HTMLInputElement>("#assistant-api-key").value;
|
|
287
|
+
if (model && model !== String(assistantOriginal.model ?? "")) body.model = model;
|
|
288
|
+
if (endpoint && endpoint !== String(assistantOriginal.endpoint ?? "")) body.endpoint = endpoint;
|
|
289
|
+
if (apiKey.trim()) body.apiKey = apiKey.trim();
|
|
290
|
+
const timeoutMs = Number(node<HTMLInputElement>("#assistant-timeout").value);
|
|
291
|
+
if (timeoutMs !== Number(assistantOriginal.timeoutMs ?? 120000)) body.timeoutMs = timeoutMs;
|
|
292
|
+
for (const [field, selector] of [["allowedPeers", "#assistant-allowed-peers"], ["allowedAgents", "#assistant-allowed-agents"]]) {
|
|
293
|
+
const entries = [...new Set(node<HTMLTextAreaElement>(selector).value.split(/[\s,,]+/).filter(Boolean))];
|
|
294
|
+
const values = entries.includes("*") ? [] : entries;
|
|
295
|
+
if (JSON.stringify(values) !== JSON.stringify(assistantOriginal[field])) body[field] = values;
|
|
296
|
+
}
|
|
297
|
+
void request("/api/plugins/personal-assistant/config", "PATCH", body).then(result => {
|
|
298
|
+
node<HTMLDialogElement>("#assistant-config-dialog").close(); showResult("个人助理配置已保存", result.restartRequired || result.action === "restart" ? "配置已保存,需要重启后台后生效。" : "模型来源配置已更新。", false); return load(false);
|
|
299
|
+
}).catch(cause => { const detail = node<HTMLElement>("#assistant-config-error"); detail.textContent = String(cause); detail.hidden = false; }).finally(() => { save.disabled = false; });
|
|
300
|
+
};
|
|
249
301
|
function renderMessages(messages: Array<{ time: string; pluginId: string; direction: "request" | "reply" | "error"; text: string; peer?: string; agentAid?: string }>): void {
|
|
250
302
|
messagesNode.replaceChildren();
|
|
251
303
|
messagesEmpty.hidden = messages.length > 0;
|
|
@@ -280,7 +332,7 @@
|
|
|
280
332
|
node<HTMLButtonElement>("#check-versions").onclick = () => void request("/api/versions/check", "POST", {}).then(() => load()).catch((cause) => error(String(cause)));
|
|
281
333
|
node<HTMLButtonElement>("#save-update-policy").onclick = () => void request("/api/update-policy", "PATCH", { mode: policyNode.value }).then(() => load()).catch((cause) => error(String(cause)));
|
|
282
334
|
node<HTMLButtonElement>("#save-plugin-trust").onclick = () => { const button = node<HTMLButtonElement>("#save-plugin-trust"); button.disabled = true; trustStatusNode.textContent = "正在保存…"; void request("/api/plugin-trust", "PATCH", pluginTrustConfig).then(() => { pluginTrustDirty = false; trustStatusNode.textContent = "信任设置已保存"; return load(false); }).catch((cause) => { trustStatusNode.textContent = String(cause); }).finally(() => { button.disabled = false; }); };
|
|
283
|
-
node<HTMLButtonElement>("#update-core").onclick = () => { if (confirm("
|
|
335
|
+
node<HTMLButtonElement>("#update-core").onclick = () => { if (confirm("确认检查后台更新?有更高版本时将升级并自动重启")) void request("/api/update", "POST", {}).then(() => { error("后台正在检查更新,有更高版本时升级并重启;结果请查看运行日志"); }).catch((cause) => error(String(cause))); };
|
|
284
336
|
node<HTMLButtonElement>("#pair-button").onclick = () => {
|
|
285
337
|
const code = pairCodeNode.value.trim();
|
|
286
338
|
if (!/^\d{6}$/.test(code)) return error("请输入 6 位配置码");
|
|
@@ -31,6 +31,21 @@
|
|
|
31
31
|
<div><button id="cli-config-save" type="submit">保存配置</button><button id="cli-config-cancel" type="button">取消</button></div>
|
|
32
32
|
</form>
|
|
33
33
|
</dialog>
|
|
34
|
+
<dialog id="assistant-config-dialog" aria-labelledby="assistant-config-title">
|
|
35
|
+
<h2 id="assistant-config-title">个人助理模型配置</h2>
|
|
36
|
+
<form id="assistant-config-form">
|
|
37
|
+
<label for="assistant-model-provider">模型来源</label><select id="assistant-model-provider" required></select>
|
|
38
|
+
<label for="assistant-model">OpenAI 兼容模型名称</label><input id="assistant-model" placeholder="仅 HTTP 来源需要">
|
|
39
|
+
<label for="assistant-endpoint">OpenAI 兼容地址</label><input id="assistant-endpoint" type="url" placeholder="http://127.0.0.1:11434/v1/chat/completions">
|
|
40
|
+
<label for="assistant-api-key">API 密钥(留空保留已有密钥)</label><input id="assistant-api-key" type="password" autocomplete="new-password">
|
|
41
|
+
<label for="assistant-timeout">模型超时(毫秒)</label><input id="assistant-timeout" type="number" min="1000" max="120000" required>
|
|
42
|
+
<label for="assistant-allowed-peers">允许使用助理的 Peer AID(每行一个,留空接受所有)</label><textarea id="assistant-allowed-peers" rows="3" placeholder="留空接受所有 Peer"></textarea>
|
|
43
|
+
<label for="assistant-allowed-agents">允许调用的 Agent 插件 ID(每行一个,留空允许所有)</label><textarea id="assistant-allowed-agents" rows="3" placeholder="留空允许所有已就绪 Agent"></textarea>
|
|
44
|
+
<p class="muted">默认留空,接受所有 Peer 并允许调用所有已就绪的普通 Agent;填写具体名单后才限制。插件来源沿用客户端登录及当前模型,AID 关联请在 Channel 设置中完成。</p>
|
|
45
|
+
<p id="assistant-config-error" class="error" role="alert" hidden></p>
|
|
46
|
+
<div><button id="assistant-config-save" type="submit">保存配置</button><button id="assistant-config-cancel" type="button">取消</button></div>
|
|
47
|
+
</form>
|
|
48
|
+
</dialog>
|
|
34
49
|
<dialog id="operation-result" aria-labelledby="operation-result-title" aria-describedby="operation-result-message">
|
|
35
50
|
<h2 id="operation-result-title">操作结果</h2><p id="operation-result-message"></p><button id="operation-result-close" type="button" autofocus>确定</button>
|
|
36
51
|
</dialog>
|
|
@@ -63,3 +63,7 @@ th:nth-child(1) { width: 145px; } th:nth-child(2) { width: 100px; } th:nth-child
|
|
|
63
63
|
|
|
64
64
|
#cli-config-form { display: grid; min-width: min(70vw, 480px); }
|
|
65
65
|
#cli-config-form input { width: 100%; }
|
|
66
|
+
#assistant-config-form { display: grid; min-width: min(70vw, 480px); }
|
|
67
|
+
#assistant-config-form input, #assistant-config-form select, #assistant-config-form textarea { width: 100%; }
|
|
68
|
+
#assistant-config-dialog { max-height: 90vh; overflow: auto; }
|
|
69
|
+
#assistant-config-form textarea { font: inherit; padding: 6px 9px; border: 1px solid #bfc9c5; border-radius: 5px; }
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"encryption_seed": { "type": "string" },
|
|
13
13
|
"verify_ssl": { "type": "boolean", "default": true },
|
|
14
14
|
"root_ca_path": { "type": "string" },
|
|
15
|
-
"allow_from": { "type": "string" },
|
|
15
|
+
"allow_from": { "type": "string", "default": "", "description": "留空接受所有 AUN Peer;填写 AID 后限制来源,可用英文逗号分隔。" },
|
|
16
16
|
"delivery_mode": { "enum": ["fanout", "queue"] }
|
|
17
17
|
},
|
|
18
18
|
"additionalProperties": false
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentOperationError = exports.AgentError = void 0;
|
|
4
|
+
exports.abortable = abortable;
|
|
4
5
|
exports.errorDetails = errorDetails;
|
|
6
|
+
async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
|
|
11
|
+
void promise.catch(() => undefined);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
17
|
+
const abort = () => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
cleanup();
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
25
|
+
promise.then((value) => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
cleanup();
|
|
30
|
+
resolve(value);
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
class AgentError extends Error {
|
|
6
41
|
constructor(message, options) {
|
|
7
42
|
super(message, options);
|
|
@@ -250,30 +250,12 @@ async function uploadAunAttachments(client, ownerAid, peerAid, items) {
|
|
|
250
250
|
const objectKey = `shared/${(0, node_crypto_1.randomUUID)()}/${filename.replace(/[^A-Za-z0-9._-]/g, "_")}`;
|
|
251
251
|
const sha256 = digest(item.data);
|
|
252
252
|
const contentType = item.contentType.toLowerCase();
|
|
253
|
-
//
|
|
254
|
-
const video = contentType.startsWith("video/");
|
|
253
|
+
// 会话媒体控件不携带 AID 凭据,沿用 ec msg 的标准可读对象上传方式。
|
|
255
254
|
const ref = { ownerAid, bucket: "default", objectKey, filename, contentType, sizeBytes: item.data.byteLength, sha256 };
|
|
256
|
-
const uploaded = await client.storage.writeBytes(`/${objectKey}`, item.data, { contentType, public:
|
|
255
|
+
const uploaded = await client.storage.writeBytes(`/${objectKey}`, item.data, { contentType, public: true, overwrite: true })
|
|
257
256
|
.catch(error => { throw new agent_1.AgentOperationError("AUN_FS_UPLOAD_FAILED", `上传 AUN FS 文件“${filename}”`, error); });
|
|
258
257
|
checkedMetadata({ ...uploaded, size: uploaded.size ?? uploaded.size_bytes ?? item.data.byteLength }, ref);
|
|
259
|
-
|
|
260
|
-
result.push({ owner_aid: ownerAid, object_key: objectKey, filename, content_type: contentType, size_bytes: item.data.byteLength, sha256 });
|
|
261
|
-
continue;
|
|
262
|
-
}
|
|
263
|
-
const share = await client.storage.lowlevel.createShareLink({ owner: ownerAid, bucket: "default", objectKey, allowedAids: [peerAid], expireInSeconds: 86400 })
|
|
264
|
-
.catch(error => { throw new agent_1.AgentOperationError("AUN_FS_SHARE_FAILED", `创建 AUN FS 文件“${filename}”的接收方授权`, error); });
|
|
265
|
-
if (share.owner_aid && share.owner_aid !== ownerAid || share.bucket && share.bucket !== "default" || share.object_key && share.object_key !== objectKey)
|
|
266
|
-
throw new Error("AUN 附件分享对象不一致");
|
|
267
|
-
if (share.allowed_aids !== undefined && (!Array.isArray(share.allowed_aids) || share.allowed_aids.length !== 1 || share.allowed_aids[0] !== peerAid))
|
|
268
|
-
throw new Error("AUN 附件分享授权不一致");
|
|
269
|
-
const shareId = typeof share.share_id === "string" && /^[A-Za-z0-9]{10}$/.test(share.share_id) ? share.share_id : undefined;
|
|
270
|
-
if (share.share_id !== undefined && !shareId)
|
|
271
|
-
throw new Error("AUN 附件分享标识无效");
|
|
272
|
-
const url = typeof share.path_url === "string" && share.path_url ? share.path_url : shareId ? `https://${ownerAid}/${objectKey}?t=${shareId}` : "";
|
|
273
|
-
const token = url ? pathToken(url, ownerAid, objectKey) : undefined;
|
|
274
|
-
if (!token || shareId && shareId !== token)
|
|
275
|
-
throw new Error("AUN 附件分享链接无效");
|
|
276
|
-
result.push({ owner_aid: ownerAid, bucket: "default", object_key: objectKey, url, filename, content_type: item.contentType, size_bytes: item.data.byteLength, sha256 });
|
|
258
|
+
result.push({ owner_aid: ownerAid, object_key: objectKey, filename, content_type: contentType, size_bytes: item.data.byteLength, sha256 });
|
|
277
259
|
}
|
|
278
260
|
return result;
|
|
279
261
|
}
|
|
@@ -13,13 +13,30 @@ const evolcore_1 = require("./evolcore");
|
|
|
13
13
|
const messages_1 = require("./messages");
|
|
14
14
|
const attachments_1 = require("./attachments");
|
|
15
15
|
const manifest = {
|
|
16
|
-
id: "aun", version: "1.0.
|
|
16
|
+
id: "aun", version: "1.0.41", type: "channel", displayName: "AUN 通道",
|
|
17
17
|
entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
|
|
18
18
|
capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
|
|
19
19
|
configSchema: "config.schema.json",
|
|
20
20
|
managementPage: "/abweb/channels/aun",
|
|
21
21
|
};
|
|
22
22
|
const newSessionCommand = /^\/new(?:[ \t]+[^\r\n]+)?$/;
|
|
23
|
+
const successfulSendStatuses = new Set(["accepted", "sent", "delivered", "duplicate"]);
|
|
24
|
+
function attachmentDownloadLinks(refs) {
|
|
25
|
+
return refs.flatMap((ref) => typeof ref.url === "string" && ref.url ? [ref.url]
|
|
26
|
+
: typeof ref.owner_aid === "string" && typeof ref.object_key === "string" ? [`https://${ref.owner_aid}/${ref.object_key}`] : []);
|
|
27
|
+
}
|
|
28
|
+
function withAttachmentDownloadLinks(text, kind, refs) {
|
|
29
|
+
const links = attachmentDownloadLinks(refs);
|
|
30
|
+
if (!links.length)
|
|
31
|
+
return text;
|
|
32
|
+
return `${text || "附件已生成。"}\n${kind === "file" ? "文件" : "附件"}下载链接:\n${links.join("\n")}`;
|
|
33
|
+
}
|
|
34
|
+
function withFailureDownloadLinks(text, refs) {
|
|
35
|
+
const links = attachmentDownloadLinks(refs);
|
|
36
|
+
if (!links.length || text?.includes("下载链接"))
|
|
37
|
+
return text;
|
|
38
|
+
return `${text || "附件已生成。"}\n附件下载链接(消息未确认时):\n${links.join("\n")}`;
|
|
39
|
+
}
|
|
23
40
|
async function loadSdk() {
|
|
24
41
|
const resolver = (0, node_module_1.createRequire)(__filename);
|
|
25
42
|
const dynamicImport = new Function("url", "return import(url)");
|
|
@@ -81,6 +98,60 @@ class AunPlugin {
|
|
|
81
98
|
this.sdk = options.sdk;
|
|
82
99
|
this.ingress = options.ingress;
|
|
83
100
|
}
|
|
101
|
+
aunManagement = {
|
|
102
|
+
list: async () => {
|
|
103
|
+
const groups = await this.identity.list();
|
|
104
|
+
const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? []), ...this.listStoreAids()])]
|
|
105
|
+
.filter((aid) => aidPattern.test(aid)).sort();
|
|
106
|
+
const bindings = Object.fromEntries(Object.entries(this.bindings)
|
|
107
|
+
.filter(([aid, agentId]) => aidPattern.test(aid) && /^[a-z][a-z0-9-]{1,63}$/.test(agentId) && agentId !== "aun" && agentId !== "abweb")
|
|
108
|
+
.sort(([left], [right]) => left.localeCompare(right)));
|
|
109
|
+
return { aids, bindings };
|
|
110
|
+
},
|
|
111
|
+
create: async ({ aid, ownerAid }) => {
|
|
112
|
+
if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
|
|
113
|
+
throw new AunCommandError("AID 格式无效");
|
|
114
|
+
const validAid = aid.trim();
|
|
115
|
+
const owner = typeof ownerAid === "string" && ownerAid.trim()
|
|
116
|
+
? ownerAid.trim()
|
|
117
|
+
: typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
|
|
118
|
+
if (!owner)
|
|
119
|
+
throw new AunCommandError("未提供 Owner AID,且未配置默认 Owner");
|
|
120
|
+
if (!aidPattern.test(owner))
|
|
121
|
+
throw new AunCommandError("Owner AID 格式无效");
|
|
122
|
+
const result = await this.createIdentity(validAid, owner);
|
|
123
|
+
const connection = result.connection === "ready" ? "ready" : "degraded";
|
|
124
|
+
if (connection !== "ready")
|
|
125
|
+
throw new AunCommandError("AID 已创建但连接失败,请先查询 AID 列表确认状态,不要重试创建", 502, { created: true, aid: validAid, connection });
|
|
126
|
+
return { aid: validAid, status: result.status, connection };
|
|
127
|
+
},
|
|
128
|
+
bind: async ({ aid, agentId, replace }) => {
|
|
129
|
+
if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
|
|
130
|
+
throw new AunCommandError("AID 格式无效");
|
|
131
|
+
if (typeof agentId !== "string" || !/^[a-z][a-z0-9-]{1,63}$/.test(agentId) || agentId === "aun" || agentId === "abweb")
|
|
132
|
+
throw new AunCommandError("Agent 插件 ID 无效");
|
|
133
|
+
if (typeof replace !== "boolean")
|
|
134
|
+
throw new AunCommandError("必须明确指定是否替换现有绑定");
|
|
135
|
+
const validAid = aid.trim();
|
|
136
|
+
const previousAgent = this.bindings[validAid];
|
|
137
|
+
const existing = Object.entries(this.bindings)
|
|
138
|
+
.filter(([, target]) => target === agentId)
|
|
139
|
+
.map(([boundAid]) => boundAid)
|
|
140
|
+
.filter((previousAid) => previousAid !== validAid)
|
|
141
|
+
.sort();
|
|
142
|
+
if (previousAgent && previousAgent !== agentId)
|
|
143
|
+
throw new AunCommandError("该 AID 已绑定到其他 Agent,不能通过替换转移");
|
|
144
|
+
if (existing.length && !replace)
|
|
145
|
+
throw new AunCommandError("目标 Agent 已有其他 AID 绑定;如需替换请明确设置 replace");
|
|
146
|
+
try {
|
|
147
|
+
await this.patchAgentAid(agentId, validAid);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
throw new AunCommandError("AUN 身份关联失败", 502);
|
|
151
|
+
}
|
|
152
|
+
return { aid: validAid, agent: agentId, replacedAids: existing };
|
|
153
|
+
},
|
|
154
|
+
};
|
|
84
155
|
identity = {
|
|
85
156
|
list: async () => {
|
|
86
157
|
const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
|
|
@@ -672,16 +743,7 @@ class AunPlugin {
|
|
|
672
743
|
this.validatePluginId(agent);
|
|
673
744
|
await this.requireAgent(agent);
|
|
674
745
|
}
|
|
675
|
-
|
|
676
|
-
try {
|
|
677
|
-
created = await this.identity.create(aid, owner);
|
|
678
|
-
}
|
|
679
|
-
catch (error) {
|
|
680
|
-
const exists = this.identities.has(aid) || this.listStoreAids().includes(aid);
|
|
681
|
-
if (exists)
|
|
682
|
-
throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
|
|
683
|
-
throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
|
|
684
|
-
}
|
|
746
|
+
const created = await this.createIdentity(aid, owner);
|
|
685
747
|
const data = { created: true, aid, connection: created.connection ?? "ready" };
|
|
686
748
|
if (created.connection !== "ready")
|
|
687
749
|
throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
|
|
@@ -699,6 +761,17 @@ class AunPlugin {
|
|
|
699
761
|
data: { ...data, ...(agent ? { agent } : {}) },
|
|
700
762
|
};
|
|
701
763
|
}
|
|
764
|
+
async createIdentity(aid, owner) {
|
|
765
|
+
try {
|
|
766
|
+
return await this.identity.create(aid, owner);
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
if (this.identities.has(aid) || this.listStoreAids().includes(aid)) {
|
|
770
|
+
throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
|
|
771
|
+
}
|
|
772
|
+
throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
702
775
|
async agents(readyOnly = true) {
|
|
703
776
|
const payload = await this.controlRequest("/api/plugins");
|
|
704
777
|
if (!Array.isArray(payload.plugins))
|
|
@@ -886,7 +959,8 @@ class AunPlugin {
|
|
|
886
959
|
channel: "aun", recipientId: aid, peerId: message.peerId, messageId: message.messageId, text: message.text, threadId: message.threadId,
|
|
887
960
|
...(attachments.length ? { attachments } : {}),
|
|
888
961
|
agentPlugin,
|
|
889
|
-
reply: async (text) => {
|
|
962
|
+
reply: async (text, signal) => {
|
|
963
|
+
signal?.throwIfAborted();
|
|
890
964
|
if (!text.trim())
|
|
891
965
|
return;
|
|
892
966
|
try {
|
|
@@ -898,17 +972,21 @@ class AunPlugin {
|
|
|
898
972
|
throw new agent_1.AgentOperationError("AUN_TEXT_SEND_FAILED", "发送 AUN 文本消息(结果未确认,不自动重发)", error);
|
|
899
973
|
}
|
|
900
974
|
},
|
|
901
|
-
replyAttachments: async (items, text) => {
|
|
975
|
+
replyAttachments: async (items, text, signal) => {
|
|
976
|
+
signal?.throwIfAborted();
|
|
902
977
|
if (!items.length)
|
|
903
978
|
return;
|
|
904
979
|
let sent = 0;
|
|
905
980
|
let groupsCount = 0;
|
|
906
981
|
let sending = false;
|
|
982
|
+
let fallbackText;
|
|
983
|
+
let fallbackLinks = [];
|
|
907
984
|
try {
|
|
908
985
|
if (!client?.storage)
|
|
909
986
|
throw new Error("AUN SDK 不支持附件存储接口");
|
|
910
987
|
const refs = uploadedRefs && uploadedItems === items ? uploadedRefs : await (0, attachments_1.uploadAunAttachments)({ storage: client.storage }, aid, message.peerId, items);
|
|
911
|
-
|
|
988
|
+
signal?.throwIfAborted();
|
|
989
|
+
this.context?.logger.info("AUN FS 标准附件上传完成", { ...trace, stage: "上传附件", files: items.map(item => ({ filename: item.filename, sizeBytes: item.data.byteLength })) });
|
|
912
990
|
if (!uploadedRefs || uploadedItems !== items) {
|
|
913
991
|
uploadedItems = items;
|
|
914
992
|
uploadedRefs = refs;
|
|
@@ -922,25 +1000,47 @@ class AunPlugin {
|
|
|
922
1000
|
}
|
|
923
1001
|
groupsCount = groups.size;
|
|
924
1002
|
for (const [kind, group] of groups) {
|
|
925
|
-
|
|
1003
|
+
signal?.throwIfAborted();
|
|
1004
|
+
const payloadText = withAttachmentDownloadLinks(text || (kind === "image" ? "图片已生成。" : kind === "video" ? "视频已生成。" : "文件已生成。"), kind, group);
|
|
1005
|
+
// AUN 网页会隐藏带普通正文的单图卡片,图片说明需另发文本。
|
|
1006
|
+
const payload = { type: kind, ...(kind === "image" ? {} : { text: payloadText }), attachments: group };
|
|
926
1007
|
if (message.threadId)
|
|
927
1008
|
payload.thread_id = message.threadId;
|
|
928
1009
|
if (message.messageId)
|
|
929
1010
|
payload.ref_message_id = message.messageId;
|
|
1011
|
+
fallbackText = payloadText;
|
|
1012
|
+
fallbackLinks = attachmentDownloadLinks(group);
|
|
930
1013
|
sending = true;
|
|
931
1014
|
// message.send 失败时可能已送达,不自动重试或降级明文,避免重复发送和泄露引用。
|
|
932
|
-
await
|
|
1015
|
+
const sendResult = await this.callAunWithRecovery(aid, { to: message.peerId, payload, encrypt: true });
|
|
1016
|
+
if (typeof sendResult?.status !== "string" || !successfulSendStatuses.has(sendResult.status))
|
|
1017
|
+
throw new Error(`AUN 附件发送状态未确认:${sendResult?.status === undefined ? "未返回状态" : String(sendResult.status)}`);
|
|
933
1018
|
this.context?.logger.info("AUN 附件消息已发送", { ...trace, stage: "发送附件消息", kind, count: group.length });
|
|
934
1019
|
sending = false;
|
|
935
1020
|
sent++;
|
|
1021
|
+
if (kind === "image" && payloadText) {
|
|
1022
|
+
try {
|
|
1023
|
+
signal?.throwIfAborted();
|
|
1024
|
+
const { attachments: _attachments, ...caption } = payload;
|
|
1025
|
+
const result = await this.callAunWithRecovery(aid, { to: message.peerId, payload: { ...caption, type: "text", text: payloadText }, encrypt: true });
|
|
1026
|
+
if (typeof result?.status !== "string" || !successfulSendStatuses.has(result.status))
|
|
1027
|
+
throw new Error(`AUN 图片说明发送状态未确认:${result?.status === undefined ? "未返回状态" : String(result.status)}`);
|
|
1028
|
+
this.context?.logger.info("AUN 图片说明已发送", { ...trace, stage: "发送图片说明" });
|
|
1029
|
+
}
|
|
1030
|
+
catch (error) {
|
|
1031
|
+
throw new agent_1.AgentOperationError("AUN_ATTACHMENT_CAPTION_SEND_FAILED", "图片已发送,发送 AUN 图片说明(结果未确认,不自动重发)", error);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
936
1034
|
}
|
|
937
1035
|
}
|
|
938
1036
|
catch (error) {
|
|
1037
|
+
signal?.throwIfAborted();
|
|
939
1038
|
deliveryFailed = true;
|
|
940
1039
|
const failure = sending ? new agent_1.AgentOperationError("AUN_ATTACHMENT_SEND_FAILED", "发送 AUN 附件消息(FS 上传已完成)", error) : error;
|
|
941
1040
|
const progress = sending ? `(已确认发送 ${sent}/${groupsCount} 组,当前组结果未确认,请勿重复发送)` : "";
|
|
1041
|
+
const fallback = fallbackText && fallbackLinks.length ? `\n${withFailureDownloadLinks(fallbackText, fallbackLinks.map((url) => ({ url })))}` : "";
|
|
942
1042
|
try {
|
|
943
|
-
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}`, message.threadId);
|
|
1043
|
+
await this.sendText(aid, message.peerId, `附件回传失败${progress}:${safeError((0, agent_1.errorDetails)(failure))}${fallback}`, message.threadId, true);
|
|
944
1044
|
}
|
|
945
1045
|
catch { /* 尽力反馈 */ }
|
|
946
1046
|
throw failure;
|
|
@@ -965,18 +1065,44 @@ class AunPlugin {
|
|
|
965
1065
|
}
|
|
966
1066
|
}
|
|
967
1067
|
}
|
|
968
|
-
async sendText(aid, peerId, text, threadId) {
|
|
1068
|
+
async sendText(aid, peerId, text, threadId, encryptedOnly = false) {
|
|
969
1069
|
const payload = { type: "text", text };
|
|
970
1070
|
if (threadId)
|
|
971
1071
|
payload.thread_id = threadId;
|
|
972
1072
|
const params = { to: peerId, payload, encrypt: true };
|
|
973
1073
|
try {
|
|
974
|
-
await this.
|
|
1074
|
+
await this.callAunWithRecovery(aid, params);
|
|
1075
|
+
}
|
|
1076
|
+
catch (error) {
|
|
1077
|
+
if (encryptedOnly || !this.isE2eeError(error))
|
|
1078
|
+
throw error;
|
|
1079
|
+
await this.callAunWithRecovery(aid, { ...params, encrypt: false });
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
async callAunWithRecovery(aid, params) {
|
|
1083
|
+
const connection = this.clients.get(aid);
|
|
1084
|
+
if (!connection)
|
|
1085
|
+
throw new agent_1.AgentOperationError("AUN_CONNECTION_MISSING", "查找 AUN 身份连接", new Error(`AID ${aid} 当前没有已连接的客户端`));
|
|
1086
|
+
try {
|
|
1087
|
+
return await connection.client.call("message.send", params);
|
|
975
1088
|
}
|
|
976
1089
|
catch (error) {
|
|
977
|
-
if (!
|
|
1090
|
+
if (!isAunWriteNotStarted(error))
|
|
978
1091
|
throw error;
|
|
979
|
-
|
|
1092
|
+
this.context?.logger.warn("AUN 发送前连接已断开,正在恢复 AUN 到 Peer 的连接并重试一次", { aid, peer: params.to, stage: "恢复 AUN 连接", error: safeError((0, agent_1.errorDetails)(error)) });
|
|
1093
|
+
try {
|
|
1094
|
+
connection.reconnecting ??= connection.client.connect({ auto_reconnect: true }).finally(() => { connection.reconnecting = undefined; });
|
|
1095
|
+
await connection.reconnecting;
|
|
1096
|
+
}
|
|
1097
|
+
catch (cause) {
|
|
1098
|
+
throw new agent_1.AgentOperationError("AUN_RECONNECT_FAILED", "恢复 AUN 到 Peer 的连接(消息确认未写出)", cause);
|
|
1099
|
+
}
|
|
1100
|
+
try {
|
|
1101
|
+
return await connection.client.call("message.send", params);
|
|
1102
|
+
}
|
|
1103
|
+
catch (cause) {
|
|
1104
|
+
throw new agent_1.AgentOperationError("AUN_SEND_FAILED", "AUN 重连后发送消息(仅重试一次)", cause);
|
|
1105
|
+
}
|
|
980
1106
|
}
|
|
981
1107
|
}
|
|
982
1108
|
isE2eeError(error) {
|
|
@@ -995,6 +1121,14 @@ exports.default = AunPlugin;
|
|
|
995
1121
|
function safeError(error) {
|
|
996
1122
|
return redact(error instanceof Error ? error.message : String(error));
|
|
997
1123
|
}
|
|
1124
|
+
function isAunWriteNotStarted(error) {
|
|
1125
|
+
for (let current = error; current && typeof current === "object"; current = current.cause) {
|
|
1126
|
+
const value = current;
|
|
1127
|
+
if (value.name === "ConnectionError" && typeof value.message === "string" && /^(?:client is not connected|transport not connected|send cancelled before write:|transport closed$)/i.test(value.message))
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
998
1132
|
async function readResponse(response, limit) {
|
|
999
1133
|
if (!response.body) {
|
|
1000
1134
|
const raw = await response.text();
|
|
@@ -10,9 +10,12 @@ 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
|
+
// 对端的系统事件只作通知,转为任务回复会导致两个 Agent 循环互答。
|
|
17
|
+
if (body.type === "error" || body.type === "notice")
|
|
18
|
+
return undefined;
|
|
16
19
|
const text = typeof data.payload === "string" ? data.payload : typeof body.text === "string" ? body.text : "";
|
|
17
20
|
let attachments = [];
|
|
18
21
|
let attachmentError;
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
agents = (plugins.plugins || []).filter((plugin) => plugin.type === "agent");
|
|
30
30
|
if (refreshForm) createForm.elements.ownerAid.value = config.owner_aid || "";
|
|
31
31
|
if (refreshForm) {
|
|
32
|
-
|
|
32
|
+
configForm.elements.allow_from.value = config.allow_from?.split(",").some(value => value.trim() === "*") ? "" : config.allow_from || "";
|
|
33
|
+
configForm.elements.aun_path.value = config.aun_path || "";
|
|
33
34
|
configForm.elements.encryption_seed.value = "";
|
|
34
35
|
}
|
|
35
36
|
list.replaceChildren();
|
|
@@ -7,7 +7,7 @@
|
|
|
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
9
|
<form id="create-form"><h2>创建 AID</h2><div class="fields"><label>新 AID<input name="aid" required placeholder="例如:example.agentid.cn"></label><label>Owner AID<input name="ownerAid" placeholder="例如:owner.agentid.cn"><small class="hint">留空时使用已配置的默认 Owner;未配置时需填写。Owner 保存为本地归属配置;不代表 AUN 协议中的 Owner 关系已激活。</small></label></div><button type="submit">创建</button></form>
|
|
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>
|
|
10
|
+
<form id="config-form"><h2>通道配置</h2><div class="fields"><label>允许的来源<input name="allow_from" placeholder="留空接受所有 Peer,填写 AID 后限制"></label><label>AUN 存储路径<input name="aun_path"></label><label>加密种子<input name="encryption_seed" type="password" autocomplete="new-password" placeholder="留空则不修改"></label></div><button type="submit">保存配置</button></form>
|
|
11
11
|
</main>
|
|
12
12
|
<script src="/abweb/channels/aun/app.js"></script>
|
|
13
13
|
</body>
|
package/plugins/aun/package.json
CHANGED