mu-agent-bridge 1.0.70 → 1.0.73

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.
Files changed (128) hide show
  1. package/README.md +9 -6
  2. package/dist/src-ts/agent.js +35 -0
  3. package/dist/src-ts/channel-sessions.js +3 -2
  4. package/dist/src-ts/control-server.js +5 -2
  5. package/dist/src-ts/dsh-agent.js +31 -15
  6. package/dist/src-ts/dsh-paths.js +7 -7
  7. package/dist/src-ts/plugin-catalog.js +11 -2
  8. package/dist/src-ts/plugin-manager.js +183 -9
  9. package/dist/src-ts/plugins/abweb/index.js +1 -1
  10. package/dist/src-ts/plugins/abweb/public/app.js +68 -2
  11. package/dist/src-ts/plugins/abweb/public/index.html +15 -0
  12. package/dist/src-ts/plugins/abweb/public/style.css +4 -0
  13. package/dist/src-ts/plugins/aun/evolcore.js +93 -17
  14. package/dist/src-ts/plugins/aun/index.js +77 -12
  15. package/dist/src-ts/plugins/aun/messages.js +3 -0
  16. package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  17. package/dist/src-ts/plugins/chatgpt/index.js +77 -17
  18. package/dist/src-ts/plugins/claude-cli/index.js +19 -2
  19. package/dist/src-ts/plugins/codex-cli/index.js +19 -2
  20. package/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
  21. package/dist/src-ts/plugins/doubao/index.js +36 -4
  22. package/dist/src-ts/plugins/dsh/index.js +82 -7
  23. package/dist/src-ts/plugins/local-cli/agent.js +74 -9
  24. package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  25. package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  26. package/dist/src-ts/plugins/personal-assistant/index.js +242 -0
  27. package/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
  28. package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  29. package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  30. package/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
  31. package/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
  32. package/dist/src-ts/plugins/qwenwork/index.js +89 -8
  33. package/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
  34. package/dist/src-ts/plugins/workbuddy/index.js +104 -33
  35. package/dist/src-ts/session-management.js +105 -6
  36. package/dist/tests-ts/abweb-cli.test.js +10 -2
  37. package/dist/tests-ts/abweb-model-config.test.js +70 -0
  38. package/dist/tests-ts/agent.test.js +27 -0
  39. package/dist/tests-ts/aun-messages.test.js +6 -0
  40. package/dist/tests-ts/aun-plugin.test.js +149 -0
  41. package/dist/tests-ts/bundled-package.test.js +15 -3
  42. package/dist/tests-ts/channel-sessions.test.js +27 -0
  43. package/dist/tests-ts/chatgpt-plugin.test.js +99 -0
  44. package/dist/tests-ts/control-server.test.js +88 -2
  45. package/dist/tests-ts/doubao-plugin.test.js +92 -0
  46. package/dist/tests-ts/dsh-agent.test.js +115 -8
  47. package/dist/tests-ts/feishu-plugin.test.js +1 -1
  48. package/dist/tests-ts/installer-assets.test.js +2 -2
  49. package/dist/tests-ts/local-cli.test.js +119 -1
  50. package/dist/tests-ts/model-provider-cancellation.test.js +150 -0
  51. package/dist/tests-ts/personal-assistant-coordination.test.js +76 -0
  52. package/dist/tests-ts/personal-assistant-extensions.test.js +29 -0
  53. package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
  54. package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
  55. package/dist/tests-ts/personal-assistant.test.js +389 -0
  56. package/dist/tests-ts/platform-integration.test.js +124 -1
  57. package/dist/tests-ts/plugin-catalog.test.js +12 -2
  58. package/dist/tests-ts/plugin-manager.test.js +280 -0
  59. package/dist/tests-ts/publish-packages.test.js +42 -0
  60. package/dist/tests-ts/qwenwork-plugin.test.js +105 -1
  61. package/dist/tests-ts/session-management.test.js +155 -0
  62. package/dist/tests-ts/workbuddy-plugin.test.js +154 -2
  63. package/package.json +1 -1
  64. package/plugins/abweb/dist/src-ts/agent.js +35 -0
  65. package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
  66. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +68 -2
  67. package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +53 -3
  68. package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
  69. package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
  70. package/plugins/abweb/package.json +1 -1
  71. package/plugins/abweb/plugin.json +1 -1
  72. package/plugins/aun/dist/src-ts/agent.js +35 -0
  73. package/plugins/aun/dist/src-ts/plugins/aun/evolcore.js +93 -17
  74. package/plugins/aun/dist/src-ts/plugins/aun/index.js +77 -12
  75. package/plugins/aun/dist/src-ts/plugins/aun/messages.js +3 -0
  76. package/plugins/aun/package.json +1 -1
  77. package/plugins/aun/plugin.json +1 -1
  78. package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
  79. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
  80. package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +77 -17
  81. package/plugins/chatgpt/package.json +1 -1
  82. package/plugins/chatgpt/plugin.json +1 -1
  83. package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
  84. package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
  85. package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  86. package/plugins/claude-cli/package.json +1 -1
  87. package/plugins/claude-cli/plugin.json +3 -2
  88. package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
  89. package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
  90. package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
  91. package/plugins/codex-cli/package.json +1 -1
  92. package/plugins/codex-cli/plugin.json +3 -2
  93. package/plugins/doubao/dist/src-ts/agent.js +35 -0
  94. package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +5 -2
  95. package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +36 -4
  96. package/plugins/doubao/package.json +1 -1
  97. package/plugins/doubao/plugin.json +3 -2
  98. package/plugins/dsh/config.schema.json +1 -0
  99. package/plugins/dsh/dist/src-ts/agent.js +35 -0
  100. package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -15
  101. package/plugins/dsh/dist/src-ts/dsh-paths.js +7 -7
  102. package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +197 -0
  103. package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +82 -7
  104. package/plugins/dsh/package.json +1 -1
  105. package/plugins/dsh/plugin.json +3 -2
  106. package/plugins/feishu/dist/src-ts/agent.js +35 -0
  107. package/plugins/personal-assistant/config.schema.json +22 -0
  108. package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
  109. package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
  110. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
  111. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
  112. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +242 -0
  113. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +167 -0
  114. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
  115. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
  116. package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +166 -0
  117. package/plugins/personal-assistant/package.json +39 -0
  118. package/plugins/personal-assistant/plugin.json +15 -0
  119. package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
  120. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +47 -11
  121. package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +89 -8
  122. package/plugins/qwenwork/package.json +1 -1
  123. package/plugins/qwenwork/plugin.json +3 -2
  124. package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
  125. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +9 -4
  126. package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +104 -33
  127. package/plugins/workbuddy/package.json +1 -1
  128. package/plugins/workbuddy/plugin.json +3 -2
@@ -34,6 +34,7 @@
34
34
  let logHasNext = false;
35
35
  let loading = false;
36
36
  const installingPackages = new Set();
37
+ let assistantOriginal = {};
37
38
  function error(message) { errorNode.textContent = message; errorNode.hidden = !message; }
38
39
  function showResult(title, message, failed) {
39
40
  node("#operation-result-title").textContent = title;
@@ -76,7 +77,7 @@
76
77
  head.append(headRow);
77
78
  const body = document.createElement("tbody");
78
79
  for (const plugin of items) {
79
- const installed = plugins.find((item) => item.id === plugin.name.replace(/^agent-bridge-/, ""));
80
+ const installed = plugins.find((item) => item.id === (plugin.id ?? plugin.name.replace(/^agent-bridge-/, "")));
80
81
  const versionStatus = installed ? pluginVersions[installed.id] : undefined;
81
82
  const isUpgrade = Boolean(installed && versionStatus?.packageName === plugin.name && newerVersion(plugin.version, installed.version));
82
83
  const row = document.createElement("tr");
@@ -309,7 +310,7 @@
309
310
  status.className = `status status-${plugin.status}`;
310
311
  status.textContent = plugin.status;
311
312
  if (plugin.type === "agent")
312
- type.textContent += ` · ${{ cli: "CLI", cdp: "CDP" }[plugin.manifest?.executionMode ?? ""] ?? "未声明"}`;
313
+ type.textContent += plugin.manifest?.executionMode === "cli" ? " · CLI" : " · 桌面";
313
314
  const dependencies = Object.entries(plugin.manifest?.dependencies ?? {});
314
315
  if (dependencies.length) {
315
316
  const detail = document.createElement("small");
@@ -344,6 +345,8 @@
344
345
  controls.className = "controls";
345
346
  if (plugin.manifest?.executionMode === "cli")
346
347
  controls.append(button("配置", `配置 ${plugin.id}`, () => configureCli(plugin)));
348
+ if (plugin.id === "personal-assistant")
349
+ controls.append(button("配置模型", "配置个人助理模型来源", () => configureAssistant(plugin)));
347
350
  if (pluginVersions[plugin.id]?.state === "update-available" && plugin.path.includes("node_modules")) {
348
351
  controlsUpgrade(plugin.id);
349
352
  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(); }));
@@ -401,6 +404,69 @@
401
404
  detail.hidden = false;
402
405
  }).finally(() => { save.disabled = false; });
403
406
  };
407
+ node("#assistant-config-cancel").onclick = () => node("#assistant-config-dialog").close();
408
+ async function configureAssistant(plugin) {
409
+ const [config, sourceResult] = await Promise.all([
410
+ request(`/api/plugins/${encodeURIComponent(plugin.id)}/config`),
411
+ request("/api/model-providers"),
412
+ ]);
413
+ assistantOriginal = config;
414
+ const select = node("#assistant-model-provider");
415
+ select.replaceChildren();
416
+ const add = (value, label, disabled = false) => { const option = document.createElement("option"); option.value = value; option.textContent = label; option.disabled = disabled; select.append(option); };
417
+ add("openai", "OpenAI 兼容接口");
418
+ if (config.modelProvider === "claude-cli")
419
+ add("claude-cli", "Claude CLI(原有配置,用户默认模型)");
420
+ for (const provider of sourceResult.providers ?? [])
421
+ add(`plugin:${provider.id}`, `${provider.displayName || provider.id}${provider.available ? "" : `(不可用:${provider.reason || provider.status})`}`, !provider.available);
422
+ if (config.modelProvider === "plugin" && config.modelPlugin && !(sourceResult.providers ?? []).some((item) => item.id === config.modelPlugin))
423
+ add(`plugin:${config.modelPlugin}`, `${config.modelPlugin}(未安装或未声明模型来源)`, true);
424
+ select.value = config.modelProvider === "plugin" && config.modelPlugin ? `plugin:${config.modelPlugin}` : config.modelProvider ?? "openai";
425
+ node("#assistant-model").value = config.model ?? "";
426
+ node("#assistant-endpoint").value = config.endpoint ?? "";
427
+ node("#assistant-api-key").value = "";
428
+ node("#assistant-api-key").placeholder = config.apiKey ? "已配置,留空保留" : "可选,本地免认证服务可留空";
429
+ node("#assistant-timeout").value = String(config.timeoutMs ?? 120000);
430
+ node("#assistant-allowed-peers").value = (config.allowedPeers ?? []).join("\n");
431
+ node("#assistant-allowed-agents").value = (config.allowedAgents ?? []).join("\n");
432
+ node("#assistant-config-error").hidden = true;
433
+ node("#assistant-config-dialog").showModal();
434
+ }
435
+ node("#assistant-config-form").onsubmit = event => {
436
+ event.preventDefault();
437
+ const save = node("#assistant-config-save");
438
+ save.disabled = true;
439
+ const source = node("#assistant-model-provider").value;
440
+ const provider = source.startsWith("plugin:") ? "plugin" : source;
441
+ const modelPlugin = source.startsWith("plugin:") ? source.slice("plugin:".length) : undefined;
442
+ const body = {};
443
+ if (provider !== (assistantOriginal.modelProvider ?? "openai"))
444
+ body.modelProvider = provider;
445
+ if (provider === "plugin" && modelPlugin !== assistantOriginal.modelPlugin)
446
+ body.modelPlugin = modelPlugin;
447
+ const model = node("#assistant-model").value.trim();
448
+ const endpoint = node("#assistant-endpoint").value.trim();
449
+ const apiKey = node("#assistant-api-key").value;
450
+ if (model && model !== String(assistantOriginal.model ?? ""))
451
+ body.model = model;
452
+ if (endpoint && endpoint !== String(assistantOriginal.endpoint ?? ""))
453
+ body.endpoint = endpoint;
454
+ if (apiKey.trim())
455
+ body.apiKey = apiKey.trim();
456
+ const timeoutMs = Number(node("#assistant-timeout").value);
457
+ if (timeoutMs !== Number(assistantOriginal.timeoutMs ?? 120000))
458
+ body.timeoutMs = timeoutMs;
459
+ for (const [field, selector] of [["allowedPeers", "#assistant-allowed-peers"], ["allowedAgents", "#assistant-allowed-agents"]]) {
460
+ const values = [...new Set(node(selector).value.split(/[\s,,]+/).filter(Boolean))];
461
+ if (JSON.stringify(values) !== JSON.stringify(assistantOriginal[field]))
462
+ body[field] = values;
463
+ }
464
+ void request("/api/plugins/personal-assistant/config", "PATCH", body).then(result => {
465
+ node("#assistant-config-dialog").close();
466
+ showResult("个人助理配置已保存", result.restartRequired || result.action === "restart" ? "配置已保存,需要重启后台后生效。" : "模型来源配置已更新。", false);
467
+ return load(false);
468
+ }).catch(cause => { const detail = node("#assistant-config-error"); detail.textContent = String(cause); detail.hidden = false; }).finally(() => { save.disabled = false; });
469
+ };
404
470
  function renderMessages(messages) {
405
471
  messagesNode.replaceChildren();
406
472
  messagesEmpty.hidden = messages.length > 0;
@@ -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="example.agentid.cn"></textarea>
43
+ <label for="assistant-allowed-agents">允许调用的 Agent 插件 ID(每行一个)</label><textarea id="assistant-allowed-agents" rows="3" placeholder="workbuddy&#10;dsh"></textarea>
44
+ <p class="muted">插件来源沿用客户端登录及当前模型;留空白名单表示不授权。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; }
@@ -4,10 +4,14 @@ exports.runEc = runEc;
4
4
  exports.configureAidForPlugin = configureAidForPlugin;
5
5
  const node_child_process_1 = require("node:child_process");
6
6
  const node_util_1 = require("node:util");
7
+ const promises_1 = require("node:fs/promises");
8
+ const node_crypto_1 = require("node:crypto");
9
+ const node_os_1 = require("node:os");
10
+ const node_path_1 = require("node:path");
7
11
  const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
8
- async function runEc(args) {
12
+ async function runEc(args, home) {
9
13
  try {
10
- const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout: 15_000, maxBuffer: 1024 * 1024 });
14
+ const { stdout } = await execFileAsync("ec", args, { shell: process.platform === "win32", timeout: 60_000, env: { ...process.env, ...(home ? { EVOLCORE_HOME: home } : {}) }, windowsHide: true, maxBuffer: 1024 * 1024 });
11
15
  return JSON.parse(stdout);
12
16
  }
13
17
  catch (error) {
@@ -21,24 +25,96 @@ async function runEc(args) {
21
25
  throw error;
22
26
  }
23
27
  }
24
- async function configureAidForPlugin(pluginId, aid, port, run = runEc) {
25
- if (!aid)
26
- return;
28
+ async function ecIsRunning(home) {
29
+ const records = [];
30
+ try {
31
+ records.push(Number(await (0, promises_1.readFile)((0, node_path_1.join)(home, "daemon.pid"), "utf8")));
32
+ }
33
+ catch (error) {
34
+ if (error.code !== "ENOENT")
35
+ throw error;
36
+ }
37
+ const directory = (0, node_path_1.join)(home, "data", "instance");
38
+ let names;
39
+ try {
40
+ names = await (0, promises_1.readdir)(directory);
41
+ }
42
+ catch (error) {
43
+ if (error.code !== "ENOENT")
44
+ throw error;
45
+ names = [];
46
+ }
47
+ for (const name of names.filter(name => /^main-\d+\.json$/.test(name))) {
48
+ records.push(JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, name), "utf8")).pid);
49
+ }
50
+ return records.some(pid => {
51
+ if (!Number.isSafeInteger(pid) || pid <= 0)
52
+ return false;
53
+ try {
54
+ process.kill(pid, 0);
55
+ return true;
56
+ }
57
+ catch (error) {
58
+ if (error.code === "ESRCH")
59
+ return false;
60
+ throw error;
61
+ }
62
+ });
63
+ }
64
+ async function configureAidForPlugin(pluginId, aid, port, run = runEc, home = process.env.EVOLCORE_HOME ?? (0, node_path_1.join)((0, node_os_1.homedir)(), ".evolcore")) {
65
+ if (!/^[a-z0-9][a-z0-9_.-]+$/i.test(aid) || !aid.includes("."))
66
+ throw new Error("AID 格式无效");
27
67
  if (!/^[a-z0-9_-]+$/.test(pluginId))
28
68
  throw new Error("插件 ID 格式无效");
29
- // 通过 daemon 的统一插件代理访问所有 Agent;否则 qwenwork 的裸 /v1 路由在有插件 HTTP 注册表时不存在。
69
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
70
+ throw new Error("后台端口无效");
30
71
  const path = pluginId === "dsh" ? "/dsh/v1" : `/api/agents/${pluginId}/v1`;
31
- for (const [key, value] of [
32
- ["baseagents.ecagent.baseUrl", `http://127.0.0.1:${port}${path}`],
33
- ["baseagents.ecagent.apiKey", "mab-local"],
34
- ["baseagents.ecagent.model", pluginId],
35
- ["active_baseagent", "ecagent"],
36
- ]) {
37
- const result = await run(["agent", "set", aid, key, value, "--format", "json"]);
72
+ const ecagent = { baseUrl: `http://127.0.0.1:${port}${path}`, apiKey: "mab-local", model: pluginId };
73
+ try {
74
+ // MAB 直接接收 AUN 消息,先停用该 AID 的 EvolCore Agent,避免双重回复。
75
+ const disabled = await run(["agent", "disable", aid, "--format", "json"], home);
76
+ if (!disabled?.ok)
77
+ throw new Error(`停用原 Agent 失败:${disabled?.code ?? ""} ${disabled?.error ?? "未知错误"}`);
78
+ for (const [key, value] of [
79
+ ...Object.entries(ecagent).map(([key, value]) => [`baseagents.ecagent.${key}`, value]),
80
+ ["active_baseagent", "ecagent"],
81
+ ]) {
82
+ const result = await run(["agent", "set", aid, key, value, "--format", "json"], home);
83
+ if (!result?.ok)
84
+ throw new Error(`配置 ${key} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
85
+ }
86
+ const result = await run(["agent", "reload", aid, "--format", "json"], home);
38
87
  if (!result?.ok)
39
- throw new Error(`配置 AID ${aid} 失败:${key};${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
88
+ throw new Error(`重载失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
89
+ return "ec";
90
+ }
91
+ catch (commandError) {
92
+ const file = (0, node_path_1.join)(home, "agents", aid, "config.json");
93
+ const config = JSON.parse(await (0, promises_1.readFile)(file, "utf8"));
94
+ if (!config || typeof config !== "object" || Array.isArray(config) || config.baseagents && (typeof config.baseagents !== "object" || Array.isArray(config.baseagents)))
95
+ throw new Error(`AID ${aid} 原配置格式无效,未覆盖文件`);
96
+ config.enabled = false;
97
+ config.active_baseagent = "ecagent";
98
+ config.baseagents = { ...config.baseagents, ecagent: { ...config.baseagents?.ecagent, ...ecagent } };
99
+ const temporary = `${file}.mab-${(0, node_crypto_1.randomUUID)()}.tmp`;
100
+ try {
101
+ await (0, promises_1.writeFile)(temporary, `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
102
+ await (0, promises_1.rename)(temporary, file);
103
+ }
104
+ finally {
105
+ await (0, promises_1.rm)(temporary, { force: true });
106
+ }
107
+ // 写盘不能关闭运行中的旧 Agent;存在活跃实例时必须确认重载成功。
108
+ if (await ecIsRunning(home)) {
109
+ try {
110
+ const result = await run(["agent", "reload", aid, "--format", "json"], home);
111
+ if (!result?.ok)
112
+ throw new Error(result?.error ?? "重载失败");
113
+ }
114
+ catch (error) {
115
+ throw new AggregateError([commandError, error], `AID ${aid} 配置已保存,但 EvolCore 仍在运行,原 Agent 未确认停用:${error instanceof Error ? error.message : error}`);
116
+ }
117
+ }
118
+ return "json";
40
119
  }
41
- const result = await run(["agent", "reload", aid, "--format", "json"]);
42
- if (!result?.ok)
43
- throw new Error(`重载 AID ${aid} 失败:${result?.code ?? ""} ${result?.error ?? "未知错误"}`);
44
120
  }
@@ -9,10 +9,11 @@ const node_path_1 = require("node:path");
9
9
  const node_os_1 = require("node:os");
10
10
  const node_url_1 = require("node:url");
11
11
  const agent_1 = require("../../agent");
12
+ const evolcore_1 = require("./evolcore");
12
13
  const messages_1 = require("./messages");
13
14
  const attachments_1 = require("./attachments");
14
15
  const manifest = {
15
- id: "aun", version: "1.0.34", type: "channel", displayName: "AUN 通道",
16
+ id: "aun", version: "1.0.35", type: "channel", displayName: "AUN 通道",
16
17
  entry: "dist/src-ts/plugins/aun/index.js", apiVersion: "1", process: "in-process",
17
18
  capabilities: ["channel", "channel.identity.create", "channel.receive", "channel.send", "health", "cli"],
18
19
  configSchema: "config.schema.json",
@@ -80,6 +81,60 @@ class AunPlugin {
80
81
  this.sdk = options.sdk;
81
82
  this.ingress = options.ingress;
82
83
  }
84
+ aunManagement = {
85
+ list: async () => {
86
+ const groups = await this.identity.list();
87
+ const aids = [...new Set([...groups.evolcore, ...groups.muAgentBridge, ...(groups.localMuAgentBridge ?? []), ...this.listStoreAids()])]
88
+ .filter((aid) => aidPattern.test(aid)).sort();
89
+ const bindings = Object.fromEntries(Object.entries(this.bindings)
90
+ .filter(([aid, agentId]) => aidPattern.test(aid) && /^[a-z][a-z0-9-]{1,63}$/.test(agentId) && agentId !== "aun" && agentId !== "abweb")
91
+ .sort(([left], [right]) => left.localeCompare(right)));
92
+ return { aids, bindings };
93
+ },
94
+ create: async ({ aid, ownerAid }) => {
95
+ if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
96
+ throw new AunCommandError("AID 格式无效");
97
+ const validAid = aid.trim();
98
+ const owner = typeof ownerAid === "string" && ownerAid.trim()
99
+ ? ownerAid.trim()
100
+ : typeof this.config.owner_aid === "string" ? this.config.owner_aid.trim() : "";
101
+ if (!owner)
102
+ throw new AunCommandError("未提供 Owner AID,且未配置默认 Owner");
103
+ if (!aidPattern.test(owner))
104
+ throw new AunCommandError("Owner AID 格式无效");
105
+ const result = await this.createIdentity(validAid, owner);
106
+ const connection = result.connection === "ready" ? "ready" : "degraded";
107
+ if (connection !== "ready")
108
+ throw new AunCommandError("AID 已创建但连接失败,请先查询 AID 列表确认状态,不要重试创建", 502, { created: true, aid: validAid, connection });
109
+ return { aid: validAid, status: result.status, connection };
110
+ },
111
+ bind: async ({ aid, agentId, replace }) => {
112
+ if (typeof aid !== "string" || !aidPattern.test(aid.trim()))
113
+ throw new AunCommandError("AID 格式无效");
114
+ if (typeof agentId !== "string" || !/^[a-z][a-z0-9-]{1,63}$/.test(agentId) || agentId === "aun" || agentId === "abweb")
115
+ throw new AunCommandError("Agent 插件 ID 无效");
116
+ if (typeof replace !== "boolean")
117
+ throw new AunCommandError("必须明确指定是否替换现有绑定");
118
+ const validAid = aid.trim();
119
+ const previousAgent = this.bindings[validAid];
120
+ const existing = Object.entries(this.bindings)
121
+ .filter(([, target]) => target === agentId)
122
+ .map(([boundAid]) => boundAid)
123
+ .filter((previousAid) => previousAid !== validAid)
124
+ .sort();
125
+ if (previousAgent && previousAgent !== agentId)
126
+ throw new AunCommandError("该 AID 已绑定到其他 Agent,不能通过替换转移");
127
+ if (existing.length && !replace)
128
+ throw new AunCommandError("目标 Agent 已有其他 AID 绑定;如需替换请明确设置 replace");
129
+ try {
130
+ await this.patchAgentAid(agentId, validAid);
131
+ }
132
+ catch {
133
+ throw new AunCommandError("AUN 身份关联失败", 502);
134
+ }
135
+ return { aid: validAid, agent: agentId, replacedAids: existing };
136
+ },
137
+ };
83
138
  identity = {
84
139
  list: async () => {
85
140
  const own = new Set([...this.createdAids, ...this.legacyCreatedAids]);
@@ -124,7 +179,7 @@ class AunPlugin {
124
179
  this.creating = false;
125
180
  }
126
181
  },
127
- bind: async (pluginId, aid, _port) => {
182
+ bind: async (pluginId, aid, port) => {
128
183
  if (!aid || !aidPattern.test(aid))
129
184
  throw new Error("AID 格式无效,必须指定要关联的 AID");
130
185
  if (pluginId === "aun" || pluginId === "abweb")
@@ -140,6 +195,14 @@ class AunPlugin {
140
195
  const loaded = this.store.load(aid);
141
196
  if (!loaded.ok || !loaded.data)
142
197
  throw new Error("AID 不在本地密钥库中,无法关联");
198
+ // 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
199
+ if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
200
+ const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
201
+ if (method === "json")
202
+ this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
203
+ else
204
+ this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
205
+ }
143
206
  this.identities.add(aid);
144
207
  if (!connection)
145
208
  await this.connectAid(aid);
@@ -663,16 +726,7 @@ class AunPlugin {
663
726
  this.validatePluginId(agent);
664
727
  await this.requireAgent(agent);
665
728
  }
666
- let created;
667
- try {
668
- created = await this.identity.create(aid, owner);
669
- }
670
- catch (error) {
671
- const exists = this.identities.has(aid) || this.listStoreAids().includes(aid);
672
- if (exists)
673
- throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
674
- throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
675
- }
729
+ const created = await this.createIdentity(aid, owner);
676
730
  const data = { created: true, aid, connection: created.connection ?? "ready" };
677
731
  if (created.connection !== "ready")
678
732
  throw new AunCommandError("AID 已创建,但连接失败;请先执行 mab aun list 确认,不要重试创建", 502, { ...data, connectionError: created.connectionError });
@@ -690,6 +744,17 @@ class AunPlugin {
690
744
  data: { ...data, ...(agent ? { agent } : {}) },
691
745
  };
692
746
  }
747
+ async createIdentity(aid, owner) {
748
+ try {
749
+ return await this.identity.create(aid, owner);
750
+ }
751
+ catch (error) {
752
+ if (this.identities.has(aid) || this.listStoreAids().includes(aid)) {
753
+ throw new AunCommandError("AID 可能已创建,但后续连接或保存失败;请先执行 mab aun list 确认,不要重试创建", 502, { created: true, aid });
754
+ }
755
+ throw new AunCommandError(`AID 创建失败:${safeError(error).replaceAll(owner, "[已隐藏]")}`, 502);
756
+ }
757
+ }
693
758
  async agents(readyOnly = true) {
694
759
  const payload = await this.controlRequest("/api/plugins");
695
760
  if (!Array.isArray(payload.plugins))
@@ -13,6 +13,9 @@ function normalizeAunMessage(payload, selfAid, allowFrom) {
13
13
  if (!peerId || peerId === selfAid || (rules.length > 0 && !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;
@@ -170,9 +170,11 @@ class ChatgptCdpClient {
170
170
  missingReasons.push("仅包含当前页面已加载的可验证正文,页面虚拟化或未加载历史导致完整性无法确认");
171
171
  return { supported: true, turns: result.turns, complete: false, missingReasons };
172
172
  }
173
- async switchSession(session) {
174
- await this.waitForGeneration();
175
- await this.recoverDraft();
173
+ async switchSession(session, preserveDraft = false) {
174
+ if (!preserveDraft) {
175
+ await this.waitForGeneration();
176
+ await this.recoverDraft();
177
+ }
176
178
  const locate = `[...document.querySelectorAll(${JSON.stringify(sidebarSelector)})].find(item =>
177
179
  item.getAttribute('data-app-action-sidebar-thread-id') === ${JSON.stringify(session.id)}
178
180
  && item.getAttribute('data-app-action-sidebar-thread-host-id') === ${JSON.stringify(session.host)})`;
@@ -194,15 +196,18 @@ class ChatgptCdpClient {
194
196
  && Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}));
195
197
  })()`, "ChatGPT 会话切换未确认,未发送消息");
196
198
  }
197
- async createSession(_name) {
198
- await this.waitForGeneration();
199
- await this.recoverDraft();
199
+ async createSession(_name, preserveDraft = false) {
200
+ if (!preserveDraft) {
201
+ await this.waitForGeneration();
202
+ await this.recoverDraft();
203
+ }
200
204
  const before = await this.activeSession();
201
205
  const state = await this.evaluate(`JSON.stringify({ editor: document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText ?? null, turns: document.querySelectorAll(${JSON.stringify(exports.turnSelector)}).length, generating: Boolean(document.querySelector(${JSON.stringify(stopButtonSelector)})) })`);
202
206
  const initial = JSON.parse(String(state));
203
207
  if (initial.editor?.trim() || initial.generating)
204
208
  throw new Error("ChatGPT 当前有未发送内容或正在生成回复,不能新建会话");
205
209
  const clicked = await this.evaluate(`(() => {
210
+ if (document.querySelector(${JSON.stringify(exports.editorSelector)})?.innerText.trim() || document.querySelector(${JSON.stringify(stopButtonSelector)})) throw new Error('ChatGPT 当前有未发送内容或正在生成回复,不能新建会话');
206
211
  const labels = /^(?:新聊天|新对话|new chat|new conversation)$/i;
207
212
  const item = [...document.querySelectorAll(${JSON.stringify(newSessionSelector)})].find((node) => {
208
213
  const label = [node.getAttribute('aria-label'), node.getAttribute('title'), node.textContent].filter(Boolean).join(' ').trim();
@@ -304,7 +309,7 @@ class ChatgptCdpClient {
304
309
  this.tunnel = undefined;
305
310
  this.rejectPending(new Error("ChatGPT CDP 已关闭"));
306
311
  }
307
- async direct(text, timeoutMs = 240_000, attachments) {
312
+ async direct(text, timeoutMs = 240_000, attachments, signal, preserveDraft = false) {
308
313
  (0, attachments_1.validateAttachments)(attachments);
309
314
  const slot = `__agentBridgeChatgpt_${(0, node_crypto_1.randomUUID)().replaceAll("-", "")}`;
310
315
  const outputDirectory = `agent-bridge-chatgpt-${(0, node_crypto_1.randomUUID)()}`;
@@ -312,7 +317,7 @@ class ChatgptCdpClient {
312
317
  const instruction = `附件回传约定:如果本轮需要返回文件、图片或视频,请把要返回的文件副本写入本轮新建的临时目录(Linux/macOS:/tmp/${outputDirectory};Windows:C:/Users/<当前用户>/AppData/Local/Temp/${outputDirectory}),并在最终回复给出文件链接。仅需文字时无需创建目录。`;
313
318
  let failure;
314
319
  try {
315
- return await this.directTurn(`${text} ${instruction}`, timeoutMs, attachments, slot, outputDirectory, startedAt);
320
+ return await this.directTurn(preserveDraft ? text : `${text} ${instruction}`, timeoutMs, attachments, slot, outputDirectory, startedAt, signal, preserveDraft);
316
321
  }
317
322
  catch (error) {
318
323
  failure = error;
@@ -338,10 +343,12 @@ class ChatgptCdpClient {
338
343
  }
339
344
  }
340
345
  }
341
- async directTurn(text, timeoutMs, attachments, slot, outputDirectory, startedAt) {
346
+ async directTurn(text, timeoutMs, attachments, slot, outputDirectory, startedAt, signal, preserveDraft = false) {
342
347
  await this.waitFor(`Boolean(document.querySelector(${JSON.stringify(exports.editorSelector)}))`, "ChatGPT 桌面端输入框未就绪");
343
- await this.waitForGeneration();
344
- await this.recoverDraft();
348
+ if (!preserveDraft) {
349
+ await this.waitForGeneration();
350
+ await this.recoverDraft();
351
+ }
345
352
  const beforeTurnsJson = await this.evaluate(`JSON.stringify([...document.querySelectorAll(${JSON.stringify(exports.turnSelector)})].map(turn => ({ id: turn.getAttribute(${JSON.stringify(turnIdAttribute)}), users: turn.querySelectorAll(${JSON.stringify(exports.userBubbleSelector)}).length })))`);
346
353
  const beforeTurns = JSON.parse(String(beforeTurnsJson || "[]"));
347
354
  const beforeAssistantCount = Number(await this.evaluate(`document.querySelectorAll(${JSON.stringify(assistantMessageSelector)}).length`));
@@ -393,7 +400,9 @@ class ChatgptCdpClient {
393
400
  const deadline = Date.now() + timeoutMs;
394
401
  let previous = "";
395
402
  let stable = 0;
396
- while (Date.now() < deadline) {
403
+ let seenActive = false;
404
+ let inactivePolls = 0;
405
+ while (Date.now() < deadline || signal?.aborted) {
397
406
  await sleep(800);
398
407
  const raw = await this.evaluate(`(() => {
399
408
  const previous = new Set(${JSON.stringify(Array.isArray(beforeMessages) ? beforeMessages : [])}.map(item => JSON.stringify(item)));
@@ -439,6 +448,11 @@ class ChatgptCdpClient {
439
448
  })()`);
440
449
  const state = JSON.parse(String(raw));
441
450
  const reply = state.reply.trim();
451
+ seenActive ||= state.active === true;
452
+ inactivePolls = seenActive && state.submitted === true && state.active === false ? inactivePolls + 1 : 0;
453
+ if (signal?.aborted && inactivePolls >= 2 && state.artifacts?.length && !reply && !state.hasArtifacts) {
454
+ throw new Error("ChatGPT 请求已取消,已确认本轮桌面生成结束,未返回正文或附件");
455
+ }
442
456
  const signature = JSON.stringify([reply, state.artifacts, state.hasArtifacts]);
443
457
  stable = (reply || state.hasArtifacts) && signature === previous ? stable + 1 : 0;
444
458
  previous = signature;