mu-agent-bridge 1.0.69 → 1.0.71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src-ts/cli.js +1 -1
- package/dist/src-ts/core-updater.js +1 -1
- package/dist/src-ts/dsh-agent.js +6 -3
- package/dist/src-ts/dsh-paths.js +7 -7
- package/dist/src-ts/plugin-manager.js +3 -3
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/aun/evolcore.js +93 -17
- package/dist/src-ts/plugins/aun/index.js +11 -2
- package/dist/src-ts/plugins/doubao/index.js +1 -1
- package/dist/src-ts/plugins/dsh/index.js +5 -2
- package/dist/src-ts/version-manager.js +1 -1
- package/dist/tests-ts/aun-plugin.test.js +64 -0
- package/dist/tests-ts/bundled-package.test.js +3 -1
- package/dist/tests-ts/control-server.test.js +52 -2
- package/dist/tests-ts/dsh-agent.test.js +38 -8
- package/dist/tests-ts/installer-assets.test.js +3 -3
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/dist/src-ts/plugins/aun/evolcore.js +93 -17
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +11 -2
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +1 -1
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +1 -1
- package/plugins/dsh/config.schema.json +1 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +6 -3
- package/plugins/dsh/dist/src-ts/dsh-paths.js +7 -7
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +197 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +5 -2
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +1 -1
package/dist/src-ts/cli.js
CHANGED
|
@@ -119,7 +119,7 @@ async function update() {
|
|
|
119
119
|
return 2;
|
|
120
120
|
}
|
|
121
121
|
await stopDaemon();
|
|
122
|
-
await execFileAsync("npm.cmd", ["install", "-g", "mu-agent-bridge@latest", "--no-fund", "--no-audit"], { timeout: 120_000, shell: true });
|
|
122
|
+
await execFileAsync("npm.cmd", ["install", "-g", "mu-agent-bridge@latest", "--no-fund", "--no-audit"], { timeout: 120_000, shell: true, windowsHide: true });
|
|
123
123
|
await startDaemon();
|
|
124
124
|
console.log("核心已更新,旧 daemon 已结束并启动新版本");
|
|
125
125
|
return 0;
|
|
@@ -21,7 +21,7 @@ async function sameDirectory(left, right) {
|
|
|
21
21
|
}
|
|
22
22
|
async function runNpm(args) {
|
|
23
23
|
const command = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
24
|
-
const { stdout } = await execFileAsync(command, args, { timeout: 180_000, maxBuffer: 4 * 1024 * 1024, ...(process.platform === "win32" ? { shell: true } : {}) });
|
|
24
|
+
const { stdout } = await execFileAsync(command, args, { timeout: 180_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true, ...(process.platform === "win32" ? { shell: true } : {}) });
|
|
25
25
|
return stdout.trim();
|
|
26
26
|
}
|
|
27
27
|
async function updateCoreAtStartup(options) {
|
package/dist/src-ts/dsh-agent.js
CHANGED
|
@@ -61,12 +61,14 @@ async function readPresentedFiles(directory, paths, cwd, inputs, returnPaths) {
|
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
63
|
class DshAgent {
|
|
64
|
+
options;
|
|
64
65
|
name = "dsh";
|
|
65
66
|
paths;
|
|
66
67
|
spawnProcess;
|
|
67
68
|
defaultTimeoutMs;
|
|
68
69
|
constructor(options) {
|
|
69
|
-
this.
|
|
70
|
+
this.options = options;
|
|
71
|
+
this.paths = options.paths;
|
|
70
72
|
this.spawnProcess = options.spawn ?? node_child_process_1.spawn;
|
|
71
73
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300_000;
|
|
72
74
|
}
|
|
@@ -74,6 +76,7 @@ class DshAgent {
|
|
|
74
76
|
if (!request.task.trim())
|
|
75
77
|
throw new agent_1.AgentError("DSH task must not be empty");
|
|
76
78
|
(0, attachments_1.validateAttachments)(request.attachments);
|
|
79
|
+
const paths = this.paths ?? await (0, dsh_paths_1.resolveDshPaths)(this.options.patchPath);
|
|
77
80
|
const workDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-dsh-"));
|
|
78
81
|
let failed = false;
|
|
79
82
|
try {
|
|
@@ -85,11 +88,11 @@ class DshAgent {
|
|
|
85
88
|
for (const [index, item] of (request.attachments ?? []).entries())
|
|
86
89
|
await (0, promises_1.writeFile)(inputPaths[index].returnPath, item.data, { mode: 0o600 });
|
|
87
90
|
const task = `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
|
|
88
|
-
const args = ["--expose-internals",
|
|
91
|
+
const args = ["--expose-internals", paths.cli, "--profile", "headless", "--patch", paths.patch, "--json"];
|
|
89
92
|
if (request.sessionId && !request.newSession)
|
|
90
93
|
args.push("--session-id", request.sessionId);
|
|
91
94
|
args.push("-");
|
|
92
|
-
const child = this.spawnProcess(
|
|
95
|
+
const child = this.spawnProcess(paths.executable, args, {
|
|
93
96
|
cwd: request.cwd,
|
|
94
97
|
env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
|
|
95
98
|
stdio: ["pipe", "pipe", "pipe"],
|
package/dist/src-ts/dsh-paths.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveDshPaths = resolveDshPaths;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
4
|
const node_path_1 = require("node:path");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
5
|
+
const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
6
|
+
async function resolveDshPaths(patch, configured) {
|
|
7
|
+
const executable = await (0, desktop_launch_1.findWindowsExecutable)("DeepSeek Harness.exe", configured ?? (process.env.DSH_INSTALL_DIR ? (0, node_path_1.join)(process.env.DSH_INSTALL_DIR, "DeepSeek Harness.exe") : undefined));
|
|
8
|
+
if (!executable)
|
|
9
|
+
throw new Error("未找到 DeepSeek Harness 可执行文件,请先安装 DSH 或重新检测客户端路径");
|
|
10
10
|
return {
|
|
11
|
-
executable
|
|
12
|
-
cli: (0, node_path_1.join)(
|
|
11
|
+
executable,
|
|
12
|
+
cli: (0, node_path_1.join)((0, node_path_1.dirname)(executable), "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"),
|
|
13
13
|
patch,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -102,7 +102,7 @@ class PluginManager {
|
|
|
102
102
|
try {
|
|
103
103
|
const spec = version ? `${packageName}@${version}` : packageName;
|
|
104
104
|
if (process.platform === "win32")
|
|
105
|
-
await execFileAsync("npm.cmd", ["install", "--prefix", target, "--no-save", "--ignore-scripts", spec], { shell: true });
|
|
105
|
+
await execFileAsync("npm.cmd", ["install", "--prefix", target, "--no-save", "--ignore-scripts", spec], { shell: true, windowsHide: true });
|
|
106
106
|
else
|
|
107
107
|
await execFileAsync("npm", ["install", "--prefix", target, "--no-save", "--ignore-scripts", spec]);
|
|
108
108
|
const candidate = (0, node_path_1.join)(target, "node_modules", packageName);
|
|
@@ -276,7 +276,7 @@ class PluginManager {
|
|
|
276
276
|
this.log("info", `开始安装插件:${packageName}`, undefined, "插件安装");
|
|
277
277
|
try {
|
|
278
278
|
if (process.platform === "win32")
|
|
279
|
-
await execFileAsync("npm.cmd", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName], { shell: true });
|
|
279
|
+
await execFileAsync("npm.cmd", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName], { shell: true, windowsHide: true });
|
|
280
280
|
else
|
|
281
281
|
await execFileAsync("npm", ["install", "--prefix", prefix, "--no-save", "--ignore-scripts", packageName]);
|
|
282
282
|
const directory = (0, node_path_1.join)(prefix, "node_modules", name);
|
|
@@ -765,7 +765,7 @@ class PluginManager {
|
|
|
765
765
|
const req = (0, node_module_1.createRequire)((0, node_path_1.resolve)(process.cwd(), "package.json"));
|
|
766
766
|
const roots = [process.cwd()];
|
|
767
767
|
try {
|
|
768
|
-
roots.push((await execFileAsync("npm", ["root", "-g"])).stdout.trim());
|
|
768
|
+
roots.push((await execFileAsync("npm", ["root", "-g"], { windowsHide: true })).stdout.trim());
|
|
769
769
|
}
|
|
770
770
|
catch { /* npm 不可用时仍扫描本地依赖 */ }
|
|
771
771
|
for (const name of names) {
|
|
@@ -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:
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
["
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
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(
|
|
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.
|
|
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",
|
|
@@ -124,7 +125,7 @@ class AunPlugin {
|
|
|
124
125
|
this.creating = false;
|
|
125
126
|
}
|
|
126
127
|
},
|
|
127
|
-
bind: async (pluginId, aid,
|
|
128
|
+
bind: async (pluginId, aid, port) => {
|
|
128
129
|
if (!aid || !aidPattern.test(aid))
|
|
129
130
|
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
130
131
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
@@ -140,6 +141,14 @@ class AunPlugin {
|
|
|
140
141
|
const loaded = this.store.load(aid);
|
|
141
142
|
if (!loaded.ok || !loaded.data)
|
|
142
143
|
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
144
|
+
// 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
|
|
145
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
|
|
146
|
+
const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
|
|
147
|
+
if (method === "json")
|
|
148
|
+
this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
|
|
149
|
+
else
|
|
150
|
+
this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
|
|
151
|
+
}
|
|
143
152
|
this.identities.add(aid);
|
|
144
153
|
if (!connection)
|
|
145
154
|
await this.connectAid(aid);
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DshPlugin = exports.dshManifest = void 0;
|
|
4
4
|
const node_path_1 = require("node:path");
|
|
5
5
|
const dsh_agent_1 = require("../../dsh-agent");
|
|
6
|
+
const dsh_paths_1 = require("../../dsh-paths");
|
|
6
7
|
const openai_adapter_1 = require("../qwenwork/openai-adapter");
|
|
7
8
|
const session_history_1 = require("./session-history");
|
|
8
9
|
exports.dshManifest = {
|
|
9
10
|
id: "dsh",
|
|
10
|
-
version: "1.0.
|
|
11
|
+
version: "1.0.11",
|
|
11
12
|
displayName: "DeepSeek Harness",
|
|
12
13
|
entry: "dist/index.js",
|
|
13
14
|
apiVersion: "1",
|
|
@@ -31,7 +32,9 @@ class DshPlugin {
|
|
|
31
32
|
const patchPath = typeof configured === "string" && configured.trim()
|
|
32
33
|
? configured
|
|
33
34
|
: (0, node_path_1.resolve)(__dirname, "..", "..", "headless-account.patch.yml");
|
|
34
|
-
|
|
35
|
+
const paths = await (0, dsh_paths_1.resolveDshPaths)(patchPath, typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
|
|
36
|
+
this.agent = new dsh_agent_1.DshAgent({ patchPath, paths });
|
|
37
|
+
context.logger?.info?.("DSH 客户端路径已确认", { executablePath: paths.executable });
|
|
35
38
|
this.sessionHistory = new session_history_1.DshSessionHistoryProvider({
|
|
36
39
|
sessionsRoot: typeof context.config.sessionsRoot === "string" ? context.config.sessionsRoot : undefined,
|
|
37
40
|
unavailableReason: () => this.status !== "ready" ? "DSH 插件尚未就绪" : this.activeRequests ? "DSH 正在处理任务,暂不读取持久化历史" : "",
|
|
@@ -76,6 +76,6 @@ class VersionManager {
|
|
|
76
76
|
this.saveTask = this.saveTask.catch(() => undefined).then(async () => { await (0, promises_1.mkdir)(this.options.stateDir, { recursive: true }); await (0, promises_1.writeFile)((0, node_path_1.join)(this.options.stateDir, "versions.json"), JSON.stringify(this.list(), null, 2)); });
|
|
77
77
|
await this.saveTask;
|
|
78
78
|
}
|
|
79
|
-
async queryNpm(args) { return (await execFileAsync(process.platform === "win32" ? "npm.cmd" : "npm", args, { timeout: 30_000, maxBuffer: 1024 * 1024, ...(process.platform === "win32" ? { shell: true } : {}) })).stdout; }
|
|
79
|
+
async queryNpm(args) { return (await execFileAsync(process.platform === "win32" ? "npm.cmd" : "npm", args, { timeout: 30_000, maxBuffer: 1024 * 1024, windowsHide: true, ...(process.platform === "win32" ? { shell: true } : {}) })).stdout; }
|
|
80
80
|
}
|
|
81
81
|
exports.VersionManager = VersionManager;
|
|
@@ -1497,3 +1497,67 @@ async function withAunAttachmentPlugin(run, publish, config = { allow_from: "pee
|
|
|
1497
1497
|
await message.reply("任务最终回复");
|
|
1498
1498
|
});
|
|
1499
1499
|
});
|
|
1500
|
+
(0, node_test_1.default)("AUN 显式关联先停用所选 EvolCore Agent 并切换 ecagent,不改其它身份或纯密钥库身份", async () => {
|
|
1501
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "aun-ec-handoff-"));
|
|
1502
|
+
const selected = "selected.agentid.cn";
|
|
1503
|
+
const other = "other.agentid.cn";
|
|
1504
|
+
const local = "local.agentid.cn";
|
|
1505
|
+
const calls = [];
|
|
1506
|
+
const events = [];
|
|
1507
|
+
let fail = false;
|
|
1508
|
+
const sdk = {
|
|
1509
|
+
AIDStore: class {
|
|
1510
|
+
list() { return { ok: true, data: { identities: [selected, other, local].map(aid => ({ aid })) } }; }
|
|
1511
|
+
load(aid) { return { ok: true, data: { aid } }; }
|
|
1512
|
+
close() { }
|
|
1513
|
+
},
|
|
1514
|
+
AUNClient: class {
|
|
1515
|
+
aid;
|
|
1516
|
+
constructor(aid) {
|
|
1517
|
+
this.aid = aid;
|
|
1518
|
+
}
|
|
1519
|
+
on() { return { unsubscribe() { } }; }
|
|
1520
|
+
async connect() { events.push(`connect:${this.aid}`); }
|
|
1521
|
+
async close() { }
|
|
1522
|
+
},
|
|
1523
|
+
};
|
|
1524
|
+
const run = async (args, home) => {
|
|
1525
|
+
strict_1.default.equal(home, root);
|
|
1526
|
+
calls.push(args);
|
|
1527
|
+
events.push(args[1]);
|
|
1528
|
+
return fail ? { ok: false, error: "停用失败" } : { ok: true };
|
|
1529
|
+
};
|
|
1530
|
+
const plugin = new aun_1.AunPlugin({ sdk, runEc: run, ingress: { publish: async () => undefined } });
|
|
1531
|
+
const original = JSON.stringify({ enabled: true, active_baseagent: "claude" });
|
|
1532
|
+
try {
|
|
1533
|
+
for (const aid of [selected, other]) {
|
|
1534
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "agents", aid), { recursive: true });
|
|
1535
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "agents", aid, "config.json"), original);
|
|
1536
|
+
}
|
|
1537
|
+
await plugin.activate({ dataDir: (0, node_path_1.join)(root, "state"), config: { aun_path: root }, logger: { info() { }, warn() { }, error() { } }, callPlugin: async () => ({ text: "" }), pluginStatus: () => "ready", http: { register() { }, unregister() { } } });
|
|
1538
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "data", "instance"), { recursive: true });
|
|
1539
|
+
const instance = (0, node_path_1.join)(root, "data", "instance", `main-${process.pid}.json`);
|
|
1540
|
+
await (0, promises_1.writeFile)(instance, JSON.stringify({ pid: process.pid }));
|
|
1541
|
+
fail = true;
|
|
1542
|
+
await strict_1.default.rejects(plugin.identity.bind("workbuddy", selected, 26107), /仍在运行.*未确认停用/);
|
|
1543
|
+
await (0, promises_1.rm)(instance);
|
|
1544
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, {});
|
|
1545
|
+
strict_1.default.equal(events.some(value => value.startsWith("connect:")), false);
|
|
1546
|
+
calls.length = 0;
|
|
1547
|
+
events.length = 0;
|
|
1548
|
+
fail = false;
|
|
1549
|
+
await plugin.identity.bind("workbuddy", selected, 26107);
|
|
1550
|
+
strict_1.default.deepEqual(events, ["disable", "set", "set", "set", "set", "reload", `connect:${selected}`]);
|
|
1551
|
+
strict_1.default.ok(calls.every(args => args[2] === selected));
|
|
1552
|
+
strict_1.default.equal(calls[1][4], "http://127.0.0.1:26107/api/agents/workbuddy/v1");
|
|
1553
|
+
strict_1.default.equal(calls[4][4], "ecagent");
|
|
1554
|
+
strict_1.default.equal(await (0, promises_1.readFile)((0, node_path_1.join)(root, "agents", other, "config.json"), "utf8"), original);
|
|
1555
|
+
await plugin.identity.bind("dsh", local, 26107);
|
|
1556
|
+
strict_1.default.equal(calls.length, 6);
|
|
1557
|
+
strict_1.default.deepEqual((await plugin.health()).details?.bindings, { [selected]: "workbuddy", [local]: "dsh" });
|
|
1558
|
+
}
|
|
1559
|
+
finally {
|
|
1560
|
+
await plugin.deactivate("stop");
|
|
1561
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
@@ -9,13 +9,15 @@ const node_path_1 = require("node:path");
|
|
|
9
9
|
const node_module_1 = require("node:module");
|
|
10
10
|
const node_test_1 = __importDefault(require("node:test"));
|
|
11
11
|
const ids = ["abweb", "aun", "chatgpt", "claude-cli", "codex-cli", "doubao", "dsh", "feishu", "qwenwork", "workbuddy"];
|
|
12
|
-
(0, node_test_1.default)("
|
|
12
|
+
(0, node_test_1.default)("独立包和默认插件的运行时依赖可加载且代码版本一致", async () => {
|
|
13
13
|
const load = (0, node_module_1.createRequire)(__filename);
|
|
14
14
|
for (const id of ids) {
|
|
15
15
|
for (const root of ["packages", "plugins"]) {
|
|
16
16
|
const directory = (0, node_path_1.join)(process.cwd(), root, id);
|
|
17
17
|
const manifest = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(directory, "plugin.json"), "utf8"));
|
|
18
18
|
strict_1.default.doesNotThrow(() => load((0, node_path_1.join)(directory, manifest.entry)), `${root}/${id} 的运行时依赖必须完整`);
|
|
19
|
+
const instance = new (load((0, node_path_1.join)(directory, manifest.entry)).default)();
|
|
20
|
+
strict_1.default.equal(instance.manifest.version, manifest.version, `${root}/${id} 的运行时代码版本必须与包声明一致`);
|
|
19
21
|
}
|
|
20
22
|
}
|
|
21
23
|
});
|
|
@@ -72,16 +72,17 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
72
72
|
const calls = [];
|
|
73
73
|
await (0, evolcore_1.configureAidForPlugin)("dsh", "test.agentid.cn", 26107, async (args) => { calls.push(args); return { ok: true }; });
|
|
74
74
|
strict_1.default.deepEqual(calls.map((args) => args.slice(0, 4)), [
|
|
75
|
+
["agent", "disable", "test.agentid.cn", "--format"],
|
|
75
76
|
["agent", "set", "test.agentid.cn", "baseagents.ecagent.baseUrl"],
|
|
76
77
|
["agent", "set", "test.agentid.cn", "baseagents.ecagent.apiKey"],
|
|
77
78
|
["agent", "set", "test.agentid.cn", "baseagents.ecagent.model"],
|
|
78
79
|
["agent", "set", "test.agentid.cn", "active_baseagent"],
|
|
79
80
|
["agent", "reload", "test.agentid.cn", "--format"],
|
|
80
81
|
]);
|
|
81
|
-
strict_1.default.equal(calls[
|
|
82
|
+
strict_1.default.equal(calls[1][4], "http://127.0.0.1:26107/dsh/v1");
|
|
82
83
|
calls.length = 0;
|
|
83
84
|
await (0, evolcore_1.configureAidForPlugin)("qwenwork", "qwen.agentid.cn", 26107, async (args) => { calls.push(args); return { ok: true }; });
|
|
84
|
-
strict_1.default.equal(calls[
|
|
85
|
+
strict_1.default.equal(calls[1][4], "http://127.0.0.1:26107/api/agents/qwenwork/v1");
|
|
85
86
|
});
|
|
86
87
|
(0, node_test_1.default)("通用 Agent 路由调用插件且关联失败进入日志", async () => {
|
|
87
88
|
const logs = [];
|
|
@@ -522,3 +523,52 @@ const plugin_manager_1 = require("../src-ts/plugin-manager");
|
|
|
522
523
|
await anonymousServer.handle({ method: "DELETE", url: "/api/session-history", headers: {}, socket: { remoteAddress: "192.168.31.10" } }, response);
|
|
523
524
|
strict_1.default.equal(response.statusCode, 401);
|
|
524
525
|
});
|
|
526
|
+
(0, node_test_1.default)("ec 命令缺失或失败且 EvolCore 未运行时,仅以 JSON 接管指定 AID", async () => {
|
|
527
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "ec-fallback-"));
|
|
528
|
+
const aid = "selected.agentid.cn";
|
|
529
|
+
const configPath = (0, node_path_1.join)(root, "agents", aid, "config.json");
|
|
530
|
+
const other = (0, node_path_1.join)(root, "agents", "other.agentid.cn", "config.json");
|
|
531
|
+
const original = { enabled: true, active_baseagent: "claude", owner: "owner.agentid.cn", baseagents: { claude: { model: "原模型" }, ecagent: { effort: "high" } } };
|
|
532
|
+
try {
|
|
533
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "agents", aid), { recursive: true });
|
|
534
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "agents", "other.agentid.cn"), { recursive: true });
|
|
535
|
+
await (0, promises_1.writeFile)(other, JSON.stringify(original));
|
|
536
|
+
for (const mode of ["missing", "disable", "reload"]) {
|
|
537
|
+
await (0, promises_1.writeFile)(configPath, JSON.stringify(original));
|
|
538
|
+
const run = async (args) => {
|
|
539
|
+
if (mode === "missing")
|
|
540
|
+
throw Object.assign(new Error("ec 不存在"), { code: "ENOENT" });
|
|
541
|
+
return { ok: mode === "reload" && args[1] !== "reload", error: "ec 未运行" };
|
|
542
|
+
};
|
|
543
|
+
const result = await (0, evolcore_1.configureAidForPlugin)("workbuddy", aid, 26107, run, root);
|
|
544
|
+
strict_1.default.equal(result, "json");
|
|
545
|
+
strict_1.default.deepEqual(JSON.parse(await (0, promises_1.readFile)(configPath, "utf8")), { ...original, enabled: false, active_baseagent: "ecagent", baseagents: { ...original.baseagents, ecagent: { effort: "high", baseUrl: "http://127.0.0.1:26107/api/agents/workbuddy/v1", apiKey: "mab-local", model: "workbuddy" } } });
|
|
546
|
+
strict_1.default.equal(await (0, promises_1.readFile)(other, "utf8"), JSON.stringify(original));
|
|
547
|
+
}
|
|
548
|
+
await (0, promises_1.writeFile)(configPath, "无效 JSON");
|
|
549
|
+
await strict_1.default.rejects((0, evolcore_1.configureAidForPlugin)("workbuddy", aid, 26107, async () => ({ ok: false }), root));
|
|
550
|
+
strict_1.default.equal(await (0, promises_1.readFile)(configPath, "utf8"), "无效 JSON");
|
|
551
|
+
}
|
|
552
|
+
finally {
|
|
553
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
(0, node_test_1.default)("ec 仍在运行时 JSON 兜底后必须重载成功,不能误报原 Agent 已停用", async () => {
|
|
557
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "ec-live-fallback-"));
|
|
558
|
+
const aid = "selected.agentid.cn";
|
|
559
|
+
const configPath = (0, node_path_1.join)(root, "agents", aid, "config.json");
|
|
560
|
+
try {
|
|
561
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "agents", aid), { recursive: true });
|
|
562
|
+
await (0, promises_1.writeFile)(configPath, JSON.stringify({ enabled: true, active_baseagent: "claude" }));
|
|
563
|
+
await (0, promises_1.mkdir)((0, node_path_1.join)(root, "data", "instance"), { recursive: true });
|
|
564
|
+
await (0, promises_1.writeFile)((0, node_path_1.join)(root, "data", "instance", `main-${process.pid}.json`), JSON.stringify({ pid: process.pid }));
|
|
565
|
+
await strict_1.default.rejects((0, evolcore_1.configureAidForPlugin)("workbuddy", aid, 26107, async () => ({ ok: false, error: "停用失败" }), root), /仍在运行.*未确认停用/);
|
|
566
|
+
strict_1.default.equal(JSON.parse(await (0, promises_1.readFile)(configPath, "utf8")).enabled, false);
|
|
567
|
+
const calls = [];
|
|
568
|
+
strict_1.default.equal(await (0, evolcore_1.configureAidForPlugin)("workbuddy", aid, 26107, async (args) => { calls.push(args); return { ok: args[1] === "reload" }; }, root), "json");
|
|
569
|
+
strict_1.default.equal(calls.at(-1)?.[1], "reload");
|
|
570
|
+
}
|
|
571
|
+
finally {
|
|
572
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
573
|
+
}
|
|
574
|
+
});
|
|
@@ -40,6 +40,9 @@ const strict_1 = __importDefault(require("node:assert/strict"));
|
|
|
40
40
|
const node_events_1 = require("node:events");
|
|
41
41
|
const node_test_1 = __importDefault(require("node:test"));
|
|
42
42
|
const node_path_1 = require("node:path");
|
|
43
|
+
const promises_1 = require("node:fs/promises");
|
|
44
|
+
const node_os_1 = require("node:os");
|
|
45
|
+
const dsh_paths_1 = require("../src-ts/dsh-paths");
|
|
43
46
|
const dsh_agent_1 = require("../src-ts/dsh-agent");
|
|
44
47
|
const dsh_1 = require("../src-ts/plugins/dsh");
|
|
45
48
|
(0, node_test_1.default)("DSH 插件声明由通用层处理的新会话能力", () => {
|
|
@@ -115,7 +118,7 @@ function fakeSpawn(_file, _args, _options) {
|
|
|
115
118
|
});
|
|
116
119
|
(0, node_test_1.default)("DSH 无效附件在启动进程前拒绝", async () => {
|
|
117
120
|
let called = false;
|
|
118
|
-
const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn: (...args) => { called = true; return fakeSpawn(...args); } });
|
|
121
|
+
const agent = new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn: (...args) => { called = true; return fakeSpawn(...args); } });
|
|
119
122
|
await strict_1.default.rejects(agent.send({ task: "读取", attachments: [{ kind: "file", filename: "x", contentType: "text/plain", data: new Uint8Array() }] }), /附件内容不能为空/);
|
|
120
123
|
strict_1.default.equal(called, false);
|
|
121
124
|
});
|
|
@@ -170,9 +173,9 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
170
173
|
strict_1.default.deepEqual(await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).readFile(block.inputs[0].returnPath), input.data);
|
|
171
174
|
return [block.inputs[0].returnPath];
|
|
172
175
|
});
|
|
173
|
-
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "原样回传", attachments: [input] });
|
|
176
|
+
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "原样回传", attachments: [input] });
|
|
174
177
|
strict_1.default.deepEqual(response.attachments, [{ ...input, data: new Uint8Array(input.data) }]);
|
|
175
|
-
const withoutPresent = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn: fakeSpawn }).send({ task: "只读取", attachments: [input] });
|
|
178
|
+
const withoutPresent = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn: fakeSpawn }).send({ task: "只读取", attachments: [input] });
|
|
176
179
|
strict_1.default.equal(withoutPresent.attachments, undefined);
|
|
177
180
|
});
|
|
178
181
|
(0, node_test_1.default)("DSH 无输入附件时仍可生成并回传文件", async () => {
|
|
@@ -181,7 +184,7 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
181
184
|
await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).writeFile(path, "生成文件");
|
|
182
185
|
return [path];
|
|
183
186
|
});
|
|
184
|
-
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "创建文本文件并回传" });
|
|
187
|
+
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "创建文本文件并回传" });
|
|
185
188
|
strict_1.default.equal(Buffer.from(response.attachments[0].data).toString(), "生成文件");
|
|
186
189
|
});
|
|
187
190
|
(0, node_test_1.default)("DSH 允许只有成功交付附件的空正文回复", async () => {
|
|
@@ -190,13 +193,13 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
190
193
|
await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).writeFile(path, "附件正文");
|
|
191
194
|
return [path];
|
|
192
195
|
}, "completed", "");
|
|
193
|
-
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "只回传文件" });
|
|
196
|
+
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "只回传文件" });
|
|
194
197
|
strict_1.default.equal(response.text, "");
|
|
195
198
|
strict_1.default.equal(response.attachments?.length, 1);
|
|
196
199
|
});
|
|
197
200
|
(0, node_test_1.default)("DSH 不回传失败 present 调用的文件", async () => {
|
|
198
201
|
const spawn = presentSpawn(async () => ["/不存在的路径"], "error");
|
|
199
|
-
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "创建文件", attachments: [{ kind: "file", data: Buffer.from("x"), filename: "x.txt", contentType: "text/plain" }] });
|
|
202
|
+
const response = await new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "创建文件", attachments: [{ kind: "file", data: Buffer.from("x"), filename: "x.txt", contentType: "text/plain" }] });
|
|
200
203
|
strict_1.default.equal(response.attachments, undefined);
|
|
201
204
|
});
|
|
202
205
|
(0, node_test_1.default)("DSH 拒绝回传输出目录外的文件与符号链接", async () => {
|
|
@@ -209,7 +212,7 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
209
212
|
await (await Promise.resolve().then(() => __importStar(require("node:fs/promises")))).symlink(source, path);
|
|
210
213
|
return [path];
|
|
211
214
|
});
|
|
212
|
-
await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "回传文件", attachments: [{ kind: "file", data: Buffer.from("x"), filename: "x.txt", contentType: "text/plain" }] }), /输出目录|普通文件/);
|
|
215
|
+
await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "回传文件", attachments: [{ kind: "file", data: Buffer.from("x"), filename: "x.txt", contentType: "text/plain" }] }), /输出目录|普通文件/);
|
|
213
216
|
}
|
|
214
217
|
});
|
|
215
218
|
(0, node_test_1.default)("DSH 拒绝回传超量和超大附件", async () => {
|
|
@@ -222,6 +225,33 @@ function presentSpawn(prepare, status = "completed", text = "已完成") {
|
|
|
222
225
|
await truncate(path, 32 * 1024 * 1024 + 1);
|
|
223
226
|
return limit === "count" ? Array.from({ length: 9 }, (_, index) => (0, node_path_1.join)(block.outputDirectory, `${index}.txt`)) : [path];
|
|
224
227
|
});
|
|
225
|
-
await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", spawn }).send({ task: "回传文件" }), /最多|大小/);
|
|
228
|
+
await strict_1.default.rejects(new dsh_agent_1.DshAgent({ patchPath: "patch.yml", paths: { executable: "dsh.exe", cli: "cli.js", patch: "patch.yml" }, spawn }).send({ task: "回传文件" }), /最多|大小/);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
(0, node_test_1.default)("DSH 使用发现的真实安装路径构建 CLI 路径,启动前拒绝不存在的程序", async () => {
|
|
232
|
+
const root = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "dsh-install-"));
|
|
233
|
+
const executable = (0, node_path_1.join)(root, "DeepSeek Harness.exe");
|
|
234
|
+
const before = process.env.DSH_INSTALL_DIR;
|
|
235
|
+
try {
|
|
236
|
+
await (0, promises_1.writeFile)(executable, "");
|
|
237
|
+
const paths = await (0, dsh_paths_1.resolveDshPaths)("patch.yml", executable);
|
|
238
|
+
strict_1.default.equal(paths.executable, executable);
|
|
239
|
+
strict_1.default.equal(paths.cli, (0, node_path_1.join)(root, "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"));
|
|
240
|
+
const plugin = new dsh_1.DshPlugin();
|
|
241
|
+
await plugin.activate({ config: { executablePath: executable } });
|
|
242
|
+
strict_1.default.equal(plugin.agent.paths.executable, executable);
|
|
243
|
+
await plugin.deactivate();
|
|
244
|
+
process.env.DSH_INSTALL_DIR = root;
|
|
245
|
+
strict_1.default.equal((await (0, dsh_paths_1.resolveDshPaths)("patch.yml")).executable, executable);
|
|
246
|
+
await (0, promises_1.rm)(executable);
|
|
247
|
+
if (process.platform !== "win32")
|
|
248
|
+
await strict_1.default.rejects((0, dsh_paths_1.resolveDshPaths)("patch.yml", executable), /未找到.*DeepSeek Harness/);
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
if (before === undefined)
|
|
252
|
+
delete process.env.DSH_INSTALL_DIR;
|
|
253
|
+
else
|
|
254
|
+
process.env.DSH_INSTALL_DIR = before;
|
|
255
|
+
await (0, promises_1.rm)(root, { recursive: true, force: true });
|
|
226
256
|
}
|
|
227
257
|
});
|
|
@@ -8,7 +8,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
8
8
|
const node_test_1 = __importDefault(require("node:test"));
|
|
9
9
|
const node_child_process_1 = require("node:child_process");
|
|
10
10
|
const node_util_1 = require("node:util");
|
|
11
|
-
(0, node_test_1.default)("Windows
|
|
11
|
+
(0, node_test_1.default)("Windows 向导识别自定义安装目录并保存 DSH 真实路径", { skip: process.platform !== "win32" }, async () => {
|
|
12
12
|
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "tests/windows-installer-detection.test.ps1"]);
|
|
13
13
|
strict_1.default.match(stdout, /自定义安装目录检测验证通过/);
|
|
14
14
|
strict_1.default.match(stdout, /DSH GUI 配置验证通过/);
|
|
@@ -78,7 +78,7 @@ const node_util_1 = require("node:util");
|
|
|
78
78
|
strict_1.default.match(setup, /环境与桥接服务/);
|
|
79
79
|
strict_1.default.match(setup, /Get-ChildItem -LiteralPath \$root -Filter \$fileName -File -Recurse/);
|
|
80
80
|
strict_1.default.match(setup, /api\/plugins\/\$agent\/config/);
|
|
81
|
-
strict_1.default.
|
|
81
|
+
strict_1.default.doesNotMatch(setup, /TopMost = \$true/);
|
|
82
82
|
strict_1.default.match(setup, /BringToFront\(\)/);
|
|
83
83
|
strict_1.default.match(setup, /Invoke-AunCommand @\('list'\)/);
|
|
84
84
|
strict_1.default.match(setup, /自动解除未安装千问办公的残留绑定/);
|
|
@@ -109,7 +109,7 @@ const node_util_1 = require("node:util");
|
|
|
109
109
|
strict_1.default.match(setup, /AddSeconds\(180\)/);
|
|
110
110
|
strict_1.default.match(setup, /System.Windows.Forms.LinkLabel/);
|
|
111
111
|
strict_1.default.match(setup, /重新检测/);
|
|
112
|
-
for (const domain of ["qwenwork.cn/download", "www.doubao.com", "www.workbuddy.ai", "
|
|
112
|
+
for (const domain of ["qwenwork.cn/download", "www.doubao.com", "www.workbuddy.ai", "download.deepseek.com/desktop/dsh-latest-windows-x64.exe"])
|
|
113
113
|
strict_1.default.ok(setup.includes(domain));
|
|
114
114
|
});
|
|
115
115
|
(0, node_test_1.default)("先检测并显示缺失 App,再配置 AID,最后单独绑定 Agent", async () => {
|
package/package.json
CHANGED
|
@@ -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:
|
|
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
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
["
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
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(
|
|
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.
|
|
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",
|
|
@@ -124,7 +125,7 @@ class AunPlugin {
|
|
|
124
125
|
this.creating = false;
|
|
125
126
|
}
|
|
126
127
|
},
|
|
127
|
-
bind: async (pluginId, aid,
|
|
128
|
+
bind: async (pluginId, aid, port) => {
|
|
128
129
|
if (!aid || !aidPattern.test(aid))
|
|
129
130
|
throw new Error("AID 格式无效,必须指定要关联的 AID");
|
|
130
131
|
if (pluginId === "aun" || pluginId === "abweb")
|
|
@@ -140,6 +141,14 @@ class AunPlugin {
|
|
|
140
141
|
const loaded = this.store.load(aid);
|
|
141
142
|
if (!loaded.ok || !loaded.data)
|
|
142
143
|
throw new Error("AID 不在本地密钥库中,无法关联");
|
|
144
|
+
// 仅接管用户本次选中的 EvolCore Agent;纯密钥库身份不调用 ec。
|
|
145
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(this.path(), "agents", aid, "config.json"))) {
|
|
146
|
+
const method = await (0, evolcore_1.configureAidForPlugin)(pluginId, aid, port, this.options.runEc ?? evolcore_1.runEc, this.path());
|
|
147
|
+
if (method === "json")
|
|
148
|
+
this.context?.logger.warn("ec 命令不可用或失败,已通过 JSON 停用原 Agent 并配置 ecagent", { aid, pluginId });
|
|
149
|
+
else
|
|
150
|
+
this.context?.logger.info("已通过 ec 命令停用原 Agent 并切换为 ecagent", { aid, pluginId });
|
|
151
|
+
}
|
|
143
152
|
this.identities.add(aid);
|
|
144
153
|
if (!connection)
|
|
145
154
|
await this.connectAid(aid);
|
package/plugins/aun/package.json
CHANGED
package/plugins/aun/plugin.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "object",
|
|
3
3
|
"properties": {
|
|
4
|
+
"executablePath": { "type": "string", "description": "DeepSeek Harness 客户端路径;升级后自动重新检测" },
|
|
4
5
|
"aid": { "type": "string", "description": "复用的 EvolCore 已有 AID" },
|
|
5
6
|
"patchPath": { "type": "string", "description": "DeepSeek Harness patch 文件路径" },
|
|
6
7
|
"sessionsRoot": { "type": "string", "description": "DSH 官方持久化会话目录;默认使用 DSH_HOME/sessions 或用户目录下的 .dsh/sessions" }
|
|
@@ -61,12 +61,14 @@ async function readPresentedFiles(directory, paths, cwd, inputs, returnPaths) {
|
|
|
61
61
|
return result;
|
|
62
62
|
}
|
|
63
63
|
class DshAgent {
|
|
64
|
+
options;
|
|
64
65
|
name = "dsh";
|
|
65
66
|
paths;
|
|
66
67
|
spawnProcess;
|
|
67
68
|
defaultTimeoutMs;
|
|
68
69
|
constructor(options) {
|
|
69
|
-
this.
|
|
70
|
+
this.options = options;
|
|
71
|
+
this.paths = options.paths;
|
|
70
72
|
this.spawnProcess = options.spawn ?? node_child_process_1.spawn;
|
|
71
73
|
this.defaultTimeoutMs = options.defaultTimeoutMs ?? 300_000;
|
|
72
74
|
}
|
|
@@ -74,6 +76,7 @@ class DshAgent {
|
|
|
74
76
|
if (!request.task.trim())
|
|
75
77
|
throw new agent_1.AgentError("DSH task must not be empty");
|
|
76
78
|
(0, attachments_1.validateAttachments)(request.attachments);
|
|
79
|
+
const paths = this.paths ?? await (0, dsh_paths_1.resolveDshPaths)(this.options.patchPath);
|
|
77
80
|
const workDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "agent-bridge-dsh-"));
|
|
78
81
|
let failed = false;
|
|
79
82
|
try {
|
|
@@ -85,11 +88,11 @@ class DshAgent {
|
|
|
85
88
|
for (const [index, item] of (request.attachments ?? []).entries())
|
|
86
89
|
await (0, promises_1.writeFile)(inputPaths[index].returnPath, item.data, { mode: 0o600 });
|
|
87
90
|
const task = `${request.task}\n\n请使用真实文件工具读取输入附件;生成的回传文件必须写入 outputDirectory 的直接子文件,并调用 present 工具声明文件。原附件已准备 returnPath 副本,原样回传时直接 present 此路径,无需 shell 复制。图片或视频无法理解时请明确说明,仍可原样回传。\n附件传输:\n${JSON.stringify({ inputs: inputPaths, outputDirectory })}`;
|
|
88
|
-
const args = ["--expose-internals",
|
|
91
|
+
const args = ["--expose-internals", paths.cli, "--profile", "headless", "--patch", paths.patch, "--json"];
|
|
89
92
|
if (request.sessionId && !request.newSession)
|
|
90
93
|
args.push("--session-id", request.sessionId);
|
|
91
94
|
args.push("-");
|
|
92
|
-
const child = this.spawnProcess(
|
|
95
|
+
const child = this.spawnProcess(paths.executable, args, {
|
|
93
96
|
cwd: request.cwd,
|
|
94
97
|
env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" },
|
|
95
98
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.resolveDshPaths = resolveDshPaths;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
4
|
const node_path_1 = require("node:path");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
5
|
+
const desktop_launch_1 = require("./plugins/desktop-launch");
|
|
6
|
+
async function resolveDshPaths(patch, configured) {
|
|
7
|
+
const executable = await (0, desktop_launch_1.findWindowsExecutable)("DeepSeek Harness.exe", configured ?? (process.env.DSH_INSTALL_DIR ? (0, node_path_1.join)(process.env.DSH_INSTALL_DIR, "DeepSeek Harness.exe") : undefined));
|
|
8
|
+
if (!executable)
|
|
9
|
+
throw new Error("未找到 DeepSeek Harness 可执行文件,请先安装 DSH 或重新检测客户端路径");
|
|
10
10
|
return {
|
|
11
|
-
executable
|
|
12
|
-
cli: (0, node_path_1.join)(
|
|
11
|
+
executable,
|
|
12
|
+
cli: (0, node_path_1.join)((0, node_path_1.dirname)(executable), "resources", "app.asar", "dsh", "node_modules", "@deepseek-ai", "dsh-desktop-host", "lib", "cli.js"),
|
|
13
13
|
patch,
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findWindowsExecutable = findWindowsExecutable;
|
|
4
|
+
exports.findExecutableInTree = findExecutableInTree;
|
|
5
|
+
exports.launchAndWait = launchAndWait;
|
|
6
|
+
exports.launchManagedAndWait = launchManagedAndWait;
|
|
7
|
+
exports.restartOwnedAndWait = restartOwnedAndWait;
|
|
8
|
+
exports.terminateWindowsDesktop = terminateWindowsDesktop;
|
|
9
|
+
exports.terminateOwnedProcess = terminateOwnedProcess;
|
|
10
|
+
exports.findChatgptExecutable = findChatgptExecutable;
|
|
11
|
+
exports.waitFor = waitFor;
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const promises_1 = require("node:fs/promises");
|
|
14
|
+
const node_fs_1 = require("node:fs");
|
|
15
|
+
const node_path_1 = require("node:path");
|
|
16
|
+
const node_util_1 = require("node:util");
|
|
17
|
+
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
|
+
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
19
|
+
/** 在 Windows 常见安装目录中查找桌面程序,避免依赖单一盘符。 */
|
|
20
|
+
async function findWindowsExecutable(fileName, configured) {
|
|
21
|
+
if (configured?.trim()) {
|
|
22
|
+
const configuredPath = (0, node_path_1.resolve)(configured);
|
|
23
|
+
if (fileName.toLowerCase() === "doubao.exe") {
|
|
24
|
+
let root = (0, node_path_1.dirname)(configuredPath);
|
|
25
|
+
while (/^(?:app|launcher|doubao_update)$/i.test((0, node_path_1.basename)(root)) || versionDirectory.test((0, node_path_1.basename)(root)))
|
|
26
|
+
root = (0, node_path_1.dirname)(root);
|
|
27
|
+
const updated = (0, node_path_1.join)(root, "Doubao_update", fileName);
|
|
28
|
+
if ((0, node_fs_1.existsSync)(updated))
|
|
29
|
+
return updated;
|
|
30
|
+
const latest = await findExecutableInTree(root, fileName, /^/, 3);
|
|
31
|
+
if (latest)
|
|
32
|
+
return latest;
|
|
33
|
+
}
|
|
34
|
+
// 千问升级会保留或删除旧版本目录,从同一安装位置重新定位新版。
|
|
35
|
+
if (fileName.toLowerCase() === "qwenworkcn.exe" && /^v?\d+(?:[.-]\d+)+$/i.test((0, node_path_1.basename)((0, node_path_1.dirname)(configuredPath)))) {
|
|
36
|
+
const latest = await findExecutableInTree((0, node_path_1.dirname)((0, node_path_1.dirname)(configuredPath)), fileName, /^/, 1);
|
|
37
|
+
if (latest)
|
|
38
|
+
return latest;
|
|
39
|
+
}
|
|
40
|
+
if ((0, node_fs_1.existsSync)(configuredPath))
|
|
41
|
+
return configuredPath;
|
|
42
|
+
}
|
|
43
|
+
if (process.platform !== "win32")
|
|
44
|
+
return undefined;
|
|
45
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh/i;
|
|
46
|
+
const roots = [...new Set([
|
|
47
|
+
process.env.DSH_INSTALL_DIR,
|
|
48
|
+
"D:\\Programs\\DeepSeek Harness",
|
|
49
|
+
"D:\\DSH",
|
|
50
|
+
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
51
|
+
process.env.ProgramFiles,
|
|
52
|
+
process.env["ProgramFiles(x86)"],
|
|
53
|
+
...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
|
|
54
|
+
].filter((value) => typeof value === "string" && (0, node_fs_1.existsSync)(value)))];
|
|
55
|
+
for (const root of roots) {
|
|
56
|
+
const found = await findExecutableInTree(root, fileName, pattern, 6);
|
|
57
|
+
if (found)
|
|
58
|
+
return found;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
async function findExecutableInTree(directory, fileName, pattern, depth) {
|
|
63
|
+
return search(directory, fileName, pattern, depth, pattern.test(directory));
|
|
64
|
+
}
|
|
65
|
+
async function search(directory, fileName, pattern, depth, insideProduct) {
|
|
66
|
+
try {
|
|
67
|
+
const entries = await (0, promises_1.readdir)(directory, { withFileTypes: true });
|
|
68
|
+
// 豆包上层文件是启动器,优先查找新版目录或 app 中的实际程序。
|
|
69
|
+
if (insideProduct && depth > 0 && fileName.toLowerCase() === "doubao.exe") {
|
|
70
|
+
const versions = entries.filter(entry => entry.isDirectory() && (versionDirectory.test(entry.name) || /^(?:app|doubao_update)$/i.test(entry.name)));
|
|
71
|
+
versions.sort((left, right) => {
|
|
72
|
+
const rank = (name) => /^doubao_update$/i.test(name) ? 2 : /^app$/i.test(name) ? 0 : 1;
|
|
73
|
+
return rank(right.name) - rank(left.name) || right.name.replace(/^\D+/, "").localeCompare(left.name.replace(/^\D+/, ""), "en", { numeric: true });
|
|
74
|
+
});
|
|
75
|
+
for (const entry of versions) {
|
|
76
|
+
const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, true);
|
|
77
|
+
if (found)
|
|
78
|
+
return found;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const entry of entries)
|
|
82
|
+
if (entry.isFile() && insideProduct && entry.name.toLowerCase() === fileName.toLowerCase() && !(fileName.toLowerCase() === "doubao.exe" && /^(?:launcher|doubao)$/i.test((0, node_path_1.basename)(directory))))
|
|
83
|
+
return (0, node_path_1.join)(directory, entry.name);
|
|
84
|
+
if (depth <= 0)
|
|
85
|
+
return undefined;
|
|
86
|
+
// 按数字比较版本目录,避免 1.2.5 排在 1.2.10 之前。
|
|
87
|
+
entries.sort((left, right) => right.name.localeCompare(left.name, "en", { numeric: true }));
|
|
88
|
+
for (const entry of entries)
|
|
89
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && (insideProduct || pattern.test(entry.name) || /^(?:Program Files(?: \(x86\))?|Programs)$/i.test(entry.name))) {
|
|
90
|
+
const found = await search((0, node_path_1.join)(directory, entry.name), fileName, pattern, depth - 1, insideProduct || pattern.test(entry.name));
|
|
91
|
+
if (found)
|
|
92
|
+
return found;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch { /* 无权限或目录在扫描期间消失时继续查找 */ }
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
async function launchAndWait(executable, args, connect, timeoutMs, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
99
|
+
const child = launch(executable, args);
|
|
100
|
+
child.unref();
|
|
101
|
+
return waitFor(connect, timeoutMs, 250);
|
|
102
|
+
}
|
|
103
|
+
async function launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess, launch = (file, argv) => (0, node_child_process_1.spawn)(file, argv, { detached: true, stdio: "ignore", windowsHide: false })) {
|
|
104
|
+
if (profileDirectory)
|
|
105
|
+
await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
|
|
106
|
+
const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
|
|
107
|
+
child.unref();
|
|
108
|
+
onProcess(child);
|
|
109
|
+
try {
|
|
110
|
+
return await waitFor(connect, timeoutMs, 250);
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
await terminateOwnedProcess(child).catch(() => undefined);
|
|
114
|
+
onProcess(undefined);
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function restartOwnedAndWait(child, executable, args, profileDirectory, connect, timeoutMs, onProcess) {
|
|
119
|
+
await terminateOwnedProcess(child);
|
|
120
|
+
onProcess(undefined);
|
|
121
|
+
return launchManagedAndWait(executable, args, profileDirectory, connect, timeoutMs, onProcess);
|
|
122
|
+
}
|
|
123
|
+
/** 仅在用户确认重启后清理同一用户、同一安装路径的桌面进程。 */
|
|
124
|
+
async function terminateWindowsDesktop(executable) {
|
|
125
|
+
if (process.platform !== "win32")
|
|
126
|
+
return [];
|
|
127
|
+
const script = `
|
|
128
|
+
$ErrorActionPreference = 'Stop'
|
|
129
|
+
$target = '${(0, node_path_1.resolve)(executable).replace(/'/g, "''")}'
|
|
130
|
+
$sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
|
|
131
|
+
$processes = @(Get-CimInstance Win32_Process | Where-Object { $_.ExecutablePath -ieq $target } | Where-Object { (Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid).Sid -eq $sid })
|
|
132
|
+
$ids = @($processes.ProcessId)
|
|
133
|
+
$roots = @($processes | Where-Object { $ids -notcontains $_.ParentProcessId })
|
|
134
|
+
foreach ($item in $roots) {
|
|
135
|
+
$current = Get-CimInstance Win32_Process -Filter "ProcessId=$($item.ProcessId)"
|
|
136
|
+
if (-not $current -or $current.CreationDate -ne $item.CreationDate) { continue }
|
|
137
|
+
& taskkill.exe /PID $item.ProcessId /T /F | Out-Null
|
|
138
|
+
if ($LASTEXITCODE -ne 0 -and (Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue)) { throw "无法结束桌面进程 $($item.ProcessId)" }
|
|
139
|
+
$remaining = Get-Process -Id $item.ProcessId -ErrorAction SilentlyContinue
|
|
140
|
+
if ($remaining -and -not $remaining.WaitForExit(10000)) { throw "等待桌面进程退出超时 $($item.ProcessId)" }
|
|
141
|
+
}
|
|
142
|
+
ConvertTo-Json -Compress -InputObject @($roots | ForEach-Object { [int]$_.ProcessId })
|
|
143
|
+
`;
|
|
144
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 30_000, windowsHide: true });
|
|
145
|
+
return JSON.parse(stdout.trim());
|
|
146
|
+
}
|
|
147
|
+
async function terminateOwnedProcess(child, platform = process.platform, run = async (file, args) => execFileAsync(file, args, { timeout: 10_000 })) {
|
|
148
|
+
const pid = child?.pid;
|
|
149
|
+
if (!Number.isSafeInteger(pid) || !pid || child?.exitCode !== null && child?.exitCode !== undefined || child?.signalCode)
|
|
150
|
+
return;
|
|
151
|
+
if (platform === "win32") {
|
|
152
|
+
try {
|
|
153
|
+
await run("taskkill.exe", ["/PID", String(pid), "/T", "/F"]);
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
if (!child.exitCode && !child.signalCode && error.code !== "ESRCH")
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
try {
|
|
162
|
+
process.kill(pid, "SIGTERM");
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error.code !== "ESRCH")
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async function findChatgptExecutable(configured) {
|
|
171
|
+
if (configured?.trim() && (0, node_fs_1.existsSync)((0, node_path_1.resolve)(configured)))
|
|
172
|
+
return (0, node_path_1.resolve)(configured);
|
|
173
|
+
if (process.platform !== "win32")
|
|
174
|
+
return undefined;
|
|
175
|
+
const script = "$candidates = foreach ($package in (Get-AppxPackage | Where-Object { $_.Name -like 'OpenAI.Codex*' })) { $appDir = Join-Path $package.InstallLocation 'app'; $path = Join-Path $appDir 'ChatGPT.exe'; if (-not (Test-Path -LiteralPath $path)) { $path = Get-ChildItem -LiteralPath $appDir -Filter 'ChatGPT*.exe' -File -ErrorAction SilentlyContinue | Select-Object -First 1 -ExpandProperty FullName }; if ($path) { [pscustomobject]@{ Version = [version]$package.Version; Executable = $path } } }; $candidate = $candidates | Sort-Object Version -Descending | Select-Object -First 1; if ($candidate) { $candidate.Executable }";
|
|
176
|
+
try {
|
|
177
|
+
const { stdout } = await execFileAsync("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", script], { timeout: 10_000 });
|
|
178
|
+
return stdout.trim().split(/\r?\n/).at(-1)?.trim() || undefined;
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function waitFor(action, timeoutMs, intervalMs = 500) {
|
|
185
|
+
const deadline = Date.now() + timeoutMs;
|
|
186
|
+
let last;
|
|
187
|
+
while (Date.now() < deadline) {
|
|
188
|
+
try {
|
|
189
|
+
return await action();
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
last = error;
|
|
193
|
+
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
throw last instanceof Error ? last : new Error("桌面程序启动超时");
|
|
197
|
+
}
|
|
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.DshPlugin = exports.dshManifest = void 0;
|
|
4
4
|
const node_path_1 = require("node:path");
|
|
5
5
|
const dsh_agent_1 = require("../../dsh-agent");
|
|
6
|
+
const dsh_paths_1 = require("../../dsh-paths");
|
|
6
7
|
const openai_adapter_1 = require("../qwenwork/openai-adapter");
|
|
7
8
|
const session_history_1 = require("./session-history");
|
|
8
9
|
exports.dshManifest = {
|
|
9
10
|
id: "dsh",
|
|
10
|
-
version: "1.0.
|
|
11
|
+
version: "1.0.11",
|
|
11
12
|
displayName: "DeepSeek Harness",
|
|
12
13
|
entry: "dist/index.js",
|
|
13
14
|
apiVersion: "1",
|
|
@@ -31,7 +32,9 @@ class DshPlugin {
|
|
|
31
32
|
const patchPath = typeof configured === "string" && configured.trim()
|
|
32
33
|
? configured
|
|
33
34
|
: (0, node_path_1.resolve)(__dirname, "..", "..", "headless-account.patch.yml");
|
|
34
|
-
|
|
35
|
+
const paths = await (0, dsh_paths_1.resolveDshPaths)(patchPath, typeof context.config.executablePath === "string" ? context.config.executablePath : undefined);
|
|
36
|
+
this.agent = new dsh_agent_1.DshAgent({ patchPath, paths });
|
|
37
|
+
context.logger?.info?.("DSH 客户端路径已确认", { executablePath: paths.executable });
|
|
35
38
|
this.sessionHistory = new session_history_1.DshSessionHistoryProvider({
|
|
36
39
|
sessionsRoot: typeof context.config.sessionsRoot === "string" ? context.config.sessionsRoot : undefined,
|
|
37
40
|
unavailableReason: () => this.status !== "ready" ? "DSH 插件尚未就绪" : this.activeRequests ? "DSH 正在处理任务,暂不读取持久化历史" : "",
|
package/plugins/dsh/package.json
CHANGED