mu-agent-bridge 1.0.71 → 1.0.78
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/src-ts/agent.js +35 -0
- package/dist/src-ts/channel-sessions.js +3 -2
- package/dist/src-ts/cli.js +67 -37
- package/dist/src-ts/control-server.js +12 -30
- package/dist/src-ts/core-updater.js +141 -33
- package/dist/src-ts/daemon-process.js +2 -1
- package/dist/src-ts/dsh-agent.js +31 -13
- package/dist/src-ts/http-agent.js +36 -0
- package/dist/src-ts/plugin-catalog.js +11 -2
- package/dist/src-ts/plugin-manager.js +351 -50
- package/dist/src-ts/plugins/abweb/index.js +1 -1
- package/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/dist/src-ts/plugins/aun/index.js +155 -21
- package/dist/src-ts/plugins/aun/messages.js +4 -1
- package/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/dist/src-ts/plugins/doubao/index.js +152 -8
- package/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/dist/src-ts/plugins/dsh/index.js +172 -21
- package/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/dist/src-ts/plugins/feishu/index.js +1 -1
- package/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/dist/src-ts/plugins/zcode/index.js +97 -0
- package/dist/src-ts/session-management.js +132 -14
- package/dist/src-ts/version-manager.js +6 -3
- package/dist/tests-ts/abweb-cli.test.js +10 -2
- package/dist/tests-ts/abweb-model-config.test.js +73 -0
- package/dist/tests-ts/agent.test.js +27 -0
- package/dist/tests-ts/aun-attachments.test.js +19 -17
- package/dist/tests-ts/aun-default-owner-page.test.js +2 -1
- package/dist/tests-ts/aun-messages.test.js +20 -0
- package/dist/tests-ts/aun-plugin.test.js +292 -19
- package/dist/tests-ts/bundled-package.test.js +12 -2
- package/dist/tests-ts/cdp-draft-recovery.test.js +182 -3
- package/dist/tests-ts/channel-sessions.test.js +27 -0
- package/dist/tests-ts/chatgpt-plugin.test.js +118 -0
- package/dist/tests-ts/control-server.test.js +56 -0
- package/dist/tests-ts/core-updater.test.js +215 -46
- package/dist/tests-ts/daemon-process.test.js +126 -1
- package/dist/tests-ts/desktop-launch.test.js +73 -0
- package/dist/tests-ts/doubao-native-task.test.js +237 -0
- package/dist/tests-ts/doubao-plugin.test.js +773 -12
- package/dist/tests-ts/dsh-agent.test.js +157 -8
- package/dist/tests-ts/dsh-cdp.test.js +148 -0
- package/dist/tests-ts/dsh-history.test.js +37 -0
- package/dist/tests-ts/feishu-plugin.test.js +1 -1
- package/dist/tests-ts/http-agent.test.js +18 -0
- package/dist/tests-ts/installer-assets.test.js +1 -1
- package/dist/tests-ts/local-cli.test.js +146 -1
- package/dist/tests-ts/model-provider-cancellation.test.js +152 -0
- package/dist/tests-ts/native-session-management.test.js +8 -7
- package/dist/tests-ts/personal-assistant-coordination.test.js +92 -0
- package/dist/tests-ts/personal-assistant-extensions.test.js +35 -0
- package/dist/tests-ts/personal-assistant-memory.test.js +117 -0
- package/dist/tests-ts/personal-assistant-owner-context.test.js +107 -0
- package/dist/tests-ts/personal-assistant-remote-ability.test.js +123 -0
- package/dist/tests-ts/personal-assistant.test.js +663 -0
- package/dist/tests-ts/platform-integration.test.js +183 -1
- package/dist/tests-ts/plugin-catalog.test.js +12 -2
- package/dist/tests-ts/plugin-manager.test.js +356 -4
- package/dist/tests-ts/plugin-upgrade-deadline.test.js +354 -0
- package/dist/tests-ts/publish-packages.test.js +42 -0
- package/dist/tests-ts/qwenwork-plugin.test.js +135 -1
- package/dist/tests-ts/qwenwork-reconnect.test.js +195 -0
- package/dist/tests-ts/qwenwork-sse.test.js +33 -0
- package/dist/tests-ts/session-access-defaults.test.js +112 -0
- package/dist/tests-ts/session-management.test.js +159 -4
- package/dist/tests-ts/version-manager.test.js +40 -0
- package/dist/tests-ts/workbuddy-cdp-recovery.test.js +311 -0
- package/dist/tests-ts/workbuddy-plugin.test.js +499 -4
- package/dist/tests-ts/zcode-cdp.test.js +894 -0
- package/dist/tests-ts/zcode-plugin.test.js +396 -0
- package/package.json +1 -1
- package/plugins/abweb/dist/src-ts/agent.js +35 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/index.js +1 -1
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.js +73 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/app.ts +57 -5
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/index.html +15 -0
- package/plugins/abweb/dist/src-ts/plugins/abweb/public/style.css +4 -0
- package/plugins/abweb/package.json +1 -1
- package/plugins/abweb/plugin.json +1 -1
- package/plugins/aun/config.schema.json +1 -1
- package/plugins/aun/dist/src-ts/agent.js +35 -0
- package/plugins/aun/dist/src-ts/plugins/aun/attachments.js +3 -21
- package/plugins/aun/dist/src-ts/plugins/aun/index.js +155 -21
- package/plugins/aun/dist/src-ts/plugins/aun/messages.js +4 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/app.js +2 -1
- package/plugins/aun/dist/src-ts/plugins/aun/public/index.html +1 -1
- package/plugins/aun/package.json +1 -1
- package/plugins/aun/plugin.json +1 -1
- package/plugins/chatgpt/dist/src-ts/agent.js +35 -0
- package/plugins/chatgpt/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/cdp-client.js +26 -12
- package/plugins/chatgpt/dist/src-ts/plugins/chatgpt/index.js +80 -19
- package/plugins/chatgpt/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/chatgpt/package.json +1 -1
- package/plugins/chatgpt/plugin.json +1 -1
- package/plugins/claude-cli/dist/src-ts/agent.js +35 -0
- package/plugins/claude-cli/dist/src-ts/plugins/claude-cli/index.js +19 -2
- package/plugins/claude-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/claude-cli/package.json +1 -1
- package/plugins/claude-cli/plugin.json +3 -2
- package/plugins/codex-cli/dist/src-ts/agent.js +35 -0
- package/plugins/codex-cli/dist/src-ts/plugins/codex-cli/index.js +19 -2
- package/plugins/codex-cli/dist/src-ts/plugins/local-cli/agent.js +74 -9
- package/plugins/codex-cli/package.json +1 -1
- package/plugins/codex-cli/plugin.json +3 -2
- package/plugins/doubao/config.schema.json +2 -1
- package/plugins/doubao/dist/src-ts/agent.js +35 -0
- package/plugins/doubao/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/doubao/dist/src-ts/plugins/doubao/cdp-client.js +234 -45
- package/plugins/doubao/dist/src-ts/plugins/doubao/index.js +152 -8
- package/plugins/doubao/dist/src-ts/plugins/doubao/native-task.js +134 -0
- package/plugins/doubao/package.json +1 -1
- package/plugins/doubao/plugin.json +3 -2
- package/plugins/dsh/config.schema.json +5 -2
- package/plugins/dsh/dist/src-ts/agent.js +35 -0
- package/plugins/dsh/dist/src-ts/dsh-agent.js +31 -13
- package/plugins/dsh/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/dsh/dist/src-ts/plugins/dsh/cdp-client.js +334 -0
- package/plugins/dsh/dist/src-ts/plugins/dsh/index.js +172 -21
- package/plugins/dsh/dist/src-ts/plugins/dsh/session-history.js +31 -3
- package/plugins/dsh/package.json +1 -1
- package/plugins/dsh/plugin.json +3 -2
- package/plugins/feishu/dist/src-ts/agent.js +35 -0
- package/plugins/feishu/dist/src-ts/plugins/feishu/index.js +1 -1
- package/plugins/feishu/package.json +1 -1
- package/plugins/feishu/plugin.json +1 -1
- package/plugins/personal-assistant/config.schema.json +23 -0
- package/plugins/personal-assistant/dist/src-ts/agent.js +69 -0
- package/plugins/personal-assistant/dist/src-ts/attachments.js +62 -0
- package/plugins/personal-assistant/dist/src-ts/plugin.js +13 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/claude-cli-model.js +30 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/coordination.js +57 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/index.js +263 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/model-client.js +181 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/owner-context.js +56 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/remote-ability.js +176 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/session-memory.js +124 -0
- package/plugins/personal-assistant/dist/src-ts/plugins/personal-assistant/tools.js +167 -0
- package/plugins/personal-assistant/package.json +39 -0
- package/plugins/personal-assistant/plugin.json +15 -0
- package/plugins/qwenwork/dist/src-ts/agent.js +35 -0
- package/plugins/qwenwork/dist/src-ts/plugins/cdp-draft.js +5 -2
- package/plugins/qwenwork/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/cdp-client.js +90 -18
- package/plugins/qwenwork/dist/src-ts/plugins/qwenwork/index.js +139 -24
- package/plugins/qwenwork/package.json +1 -1
- package/plugins/qwenwork/plugin.json +3 -2
- package/plugins/workbuddy/config.schema.json +1 -1
- package/plugins/workbuddy/dist/src-ts/agent.js +35 -0
- package/plugins/workbuddy/dist/src-ts/plugins/desktop-launch.js +44 -3
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/cdp-client.js +353 -82
- package/plugins/workbuddy/dist/src-ts/plugins/workbuddy/index.js +124 -41
- package/plugins/workbuddy/package.json +1 -1
- package/plugins/workbuddy/plugin.json +3 -2
- package/plugins/zcode/config.schema.json +13 -0
- package/plugins/zcode/dist/src-ts/agent.js +69 -0
- package/plugins/zcode/dist/src-ts/attachments.js +62 -0
- package/plugins/zcode/dist/src-ts/plugin.js +13 -0
- package/plugins/zcode/dist/src-ts/plugins/desktop-launch.js +238 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cdp-client.js +896 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/cli-client.js +39 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/desktop-launch.js +10 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/index.js +567 -0
- package/plugins/zcode/dist/src-ts/plugins/workbuddy/session-history.js +121 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/cdp-client.js +500 -0
- package/plugins/zcode/dist/src-ts/plugins/zcode/index.js +97 -0
- package/plugins/zcode/package.json +16 -0
- package/plugins/zcode/plugin.json +11 -0
|
@@ -6,6 +6,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const promises_1 = require("node:fs/promises");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
9
10
|
const node_crypto_1 = require("node:crypto");
|
|
10
11
|
const nativeId = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
11
12
|
const maximumOutput = 4 * 1024 * 1024;
|
|
@@ -22,6 +23,21 @@ function diagnostic(value) {
|
|
|
22
23
|
} })
|
|
23
24
|
.slice(0, 2000);
|
|
24
25
|
}
|
|
26
|
+
function modelDiagnostic(error) {
|
|
27
|
+
const message = String(error);
|
|
28
|
+
const code = /CLI 退出码 (?:\d+|[A-Z][A-Z0-9_]+)/.exec(message)?.[0];
|
|
29
|
+
if (code)
|
|
30
|
+
return `${code}:请检查 CLI 登录、权限和网络`;
|
|
31
|
+
if (message.includes("CLI 调用超时"))
|
|
32
|
+
return "CLI 模型调用超时,请检查登录、网络或权限审批";
|
|
33
|
+
if (message.includes("CLI 请求已取消"))
|
|
34
|
+
return "CLI 模型请求已取消";
|
|
35
|
+
if (message.includes("插件已停止"))
|
|
36
|
+
return "插件已停止,当前 CLI 模型任务已终止";
|
|
37
|
+
if (message.includes("CLI 输出超过"))
|
|
38
|
+
return "CLI 模型输出超过 4MB 限制";
|
|
39
|
+
return "CLI 模型调用或结构化输出失败,请检查 CLI 状态";
|
|
40
|
+
}
|
|
25
41
|
async function resolveWindowsCliShim(file, provider) {
|
|
26
42
|
const modules = (0, node_path_1.basename)((0, node_path_1.dirname)(file)) === ".bin" ? (0, node_path_1.dirname)((0, node_path_1.dirname)(file)) : (0, node_path_1.join)((0, node_path_1.dirname)(file), "node_modules");
|
|
27
43
|
const root = (0, node_path_1.join)(modules, ...(provider === "codex" ? ["@openai", "codex"] : ["@anthropic-ai", "claude-code"]));
|
|
@@ -76,6 +92,8 @@ class LocalCliAgent {
|
|
|
76
92
|
ready = false;
|
|
77
93
|
message = "插件尚未启动";
|
|
78
94
|
version = "";
|
|
95
|
+
modelModeSupported = false;
|
|
96
|
+
get modelInput() { return this.ready && this.modelModeSupported; }
|
|
79
97
|
sessions = new Map();
|
|
80
98
|
writes = Promise.resolve();
|
|
81
99
|
pending = new Map();
|
|
@@ -108,6 +126,9 @@ class LocalCliAgent {
|
|
|
108
126
|
const help = (await this.run(this.provider === "codex" ? ["exec", "--help"] : ["--help"], "", 10000)).stdout;
|
|
109
127
|
if (this.provider === "codex" ? !help.includes("--json") || !help.includes("resume") : !help.includes("--output-format") || !help.includes("--resume"))
|
|
110
128
|
throw new Error("CLI 版本不支持所需的结构化输出和会话恢复,请升级 CLI");
|
|
129
|
+
this.modelModeSupported = this.provider === "claude"
|
|
130
|
+
? ["--json-schema", "--tools", "--strict-mcp-config", "--mcp-config", "--no-session-persistence", "--settings", "--setting-sources"].every(flag => help.includes(flag))
|
|
131
|
+
: ["--output-schema", "--ephemeral", "--sandbox", "--disable", "--config", "--ignore-rules"].every(flag => help.includes(flag));
|
|
111
132
|
this.sessions = new Map();
|
|
112
133
|
try {
|
|
113
134
|
const data = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json"), "utf8"));
|
|
@@ -128,7 +149,9 @@ class LocalCliAgent {
|
|
|
128
149
|
throw new Error(this.message);
|
|
129
150
|
}
|
|
130
151
|
}
|
|
131
|
-
run(args, input, timeout) {
|
|
152
|
+
run(args, input, timeout, signal) {
|
|
153
|
+
if (signal?.aborted)
|
|
154
|
+
return Promise.reject(new Error("CLI 请求已取消"));
|
|
132
155
|
const command = this.command;
|
|
133
156
|
let child;
|
|
134
157
|
let stop;
|
|
@@ -153,8 +176,10 @@ class LocalCliAgent {
|
|
|
153
176
|
}
|
|
154
177
|
};
|
|
155
178
|
stop = () => terminate("插件已停止,当前 CLI 任务已终止");
|
|
179
|
+
const abort = () => terminate("CLI 请求已取消");
|
|
180
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
156
181
|
const timer = setTimeout(() => terminate(`CLI 调用超时(${timeout}ms),请检查登录、网络或权限审批`), timeout);
|
|
157
|
-
const cleanup = () => { clearTimeout(timer); this.children.delete(child); };
|
|
182
|
+
const cleanup = () => { clearTimeout(timer); signal?.removeEventListener("abort", abort); this.children.delete(child); };
|
|
158
183
|
child.stdout.setEncoding("utf8");
|
|
159
184
|
child.stderr.setEncoding("utf8");
|
|
160
185
|
const collect = (value, error) => {
|
|
@@ -227,35 +252,70 @@ class LocalCliAgent {
|
|
|
227
252
|
throw new Error(`${this.manifest.displayName} 本期不支持附件输入`);
|
|
228
253
|
if (!request.task?.trim())
|
|
229
254
|
throw new Error("CLI 任务文本不能为空");
|
|
255
|
+
return this.executeCli(request, request.modelResponseSchema);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 模型来源的独立入口。调用方只提供最小模型契约,不能通过普通 Agent handle 进入管理路径。
|
|
259
|
+
*/
|
|
260
|
+
async completeStructuredModel(request, responseSchema) {
|
|
261
|
+
if (!request.prompt?.trim())
|
|
262
|
+
throw new Error("模型请求文本不能为空");
|
|
263
|
+
if (!request.sessionId?.trim())
|
|
264
|
+
throw new Error("模型请求会话标识不能为空");
|
|
265
|
+
const response = await this.executeCli({ task: request.prompt, sessionId: request.sessionId, timeoutMs: request.timeoutMs, signal: request.signal }, responseSchema);
|
|
266
|
+
return { text: response.text };
|
|
267
|
+
}
|
|
268
|
+
async executeCli(request, modelSchema) {
|
|
269
|
+
const modelMode = modelSchema !== undefined;
|
|
270
|
+
if (modelMode && !this.modelModeSupported)
|
|
271
|
+
throw new Error("当前 CLI 不支持无工具结构化模型请求,请升级 CLI");
|
|
272
|
+
const schema = modelMode ? JSON.stringify(modelSchema) : undefined;
|
|
273
|
+
if (modelMode && (!modelSchema || typeof modelSchema !== "object" || Array.isArray(modelSchema) || !schema || Buffer.byteLength(schema) > 64_000))
|
|
274
|
+
throw new Error("模型输出 Schema 无效或过大");
|
|
230
275
|
const logical = request.sessionId || (0, node_crypto_1.randomUUID)();
|
|
231
276
|
return this.serial(logical, async () => {
|
|
232
277
|
this.requireReady();
|
|
233
|
-
const id = request.newSession ? undefined : this.sessions.get(logical);
|
|
278
|
+
const id = modelMode || request.newSession ? undefined : this.sessions.get(logical);
|
|
279
|
+
let schemaFile;
|
|
280
|
+
let schemaDirectory;
|
|
234
281
|
const args = this.provider === "codex"
|
|
235
282
|
? ["exec", ...(id ? ["resume", id] : []), "--json", "-"]
|
|
236
283
|
: ["--print", "--output-format", "json", ...(id ? ["--resume", id] : [])];
|
|
284
|
+
if (modelMode && this.provider === "claude")
|
|
285
|
+
args.push("--json-schema", schema, "--tools", "", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--settings", '{"disableAllHooks":true}', "--setting-sources", "user", "--no-session-persistence");
|
|
237
286
|
const timeout = request.timeoutMs ?? this.timeoutMs;
|
|
238
287
|
if (!Number.isInteger(timeout) || timeout <= 0 || timeout > 2147483647)
|
|
239
288
|
throw new Error("任务超时必须是有效的正整数毫秒数");
|
|
240
289
|
const started = Date.now();
|
|
241
290
|
this.context.logger.info("本地 CLI 任务开始", { plugin: this.manifest.id, sessionId: logical, resume: Boolean(id) });
|
|
242
291
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
292
|
+
if (modelMode && this.provider === "codex") {
|
|
293
|
+
schemaDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-codex-model-"));
|
|
294
|
+
schemaFile = (0, node_path_1.join)(schemaDirectory, "response-schema.json");
|
|
295
|
+
await (0, promises_1.writeFile)(schemaFile, schema, { flag: "wx", mode: 0o600 });
|
|
296
|
+
args.splice(-1, 0, "--ephemeral", "--sandbox", "read-only", "--output-schema", schemaFile, "--disable", "shell_tool", "--disable", "apps", "--disable", "plugins", "--disable", "hooks", "--ignore-rules", "--config", "mcp_servers={}");
|
|
297
|
+
}
|
|
298
|
+
const output = await this.run(args, request.task, timeout, request.signal);
|
|
299
|
+
const response = this.parse(output.stdout, modelMode);
|
|
245
300
|
if (id && response.id !== id)
|
|
246
301
|
throw new Error("CLI 恢复返回的会话 ID 与指定会话不一致,保留原会话绑定");
|
|
247
|
-
|
|
302
|
+
if (!modelMode)
|
|
303
|
+
await this.save(logical, response.id);
|
|
248
304
|
this.context.logger.info("本地 CLI 任务完成", { plugin: this.manifest.id, sessionId: logical, durationMs: Date.now() - started, exitCode: 0 });
|
|
249
|
-
return { text: response.text, sessionId: logical };
|
|
305
|
+
return { text: response.text, ...(!modelMode ? { sessionId: logical } : {}) };
|
|
250
306
|
}
|
|
251
307
|
catch (error) {
|
|
252
|
-
const detail = diagnostic(String(error));
|
|
308
|
+
const detail = modelMode ? modelDiagnostic(error) : diagnostic(String(error));
|
|
253
309
|
this.context.logger.error("本地 CLI 任务失败", { plugin: this.manifest.id, sessionId: logical, durationMs: Date.now() - started, error: detail });
|
|
254
310
|
throw new Error(`${this.manifest.displayName}:${detail}`);
|
|
255
311
|
}
|
|
312
|
+
finally {
|
|
313
|
+
if (schemaFile && schemaDirectory)
|
|
314
|
+
await (0, promises_1.rm)(schemaDirectory, { recursive: true, force: true });
|
|
315
|
+
}
|
|
256
316
|
});
|
|
257
317
|
}
|
|
258
|
-
parse(stdout) {
|
|
318
|
+
parse(stdout, modelMode = false) {
|
|
259
319
|
let id = "", text = "";
|
|
260
320
|
try {
|
|
261
321
|
if (this.provider === "codex") {
|
|
@@ -287,6 +347,11 @@ class LocalCliAgent {
|
|
|
287
347
|
throw new Error(JSON.stringify(event.errors ?? event));
|
|
288
348
|
id = event.session_id;
|
|
289
349
|
text = event.result;
|
|
350
|
+
if (modelMode) {
|
|
351
|
+
if (!event.structured_output || typeof event.structured_output !== "object" || Array.isArray(event.structured_output))
|
|
352
|
+
throw new Error("缺少有效结构化模型输出");
|
|
353
|
+
text = JSON.stringify(event.structured_output);
|
|
354
|
+
}
|
|
290
355
|
}
|
|
291
356
|
if (typeof id !== "string" || !nativeId.test(id))
|
|
292
357
|
throw new Error("缺少有效原生会话 ID");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "claude-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"type": "agent",
|
|
5
5
|
"executionMode": "cli",
|
|
6
6
|
"displayName": "Claude Code CLI",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"process": "in-process",
|
|
10
10
|
"capabilities": [
|
|
11
11
|
"chat",
|
|
12
|
-
"health"
|
|
12
|
+
"health",
|
|
13
|
+
"model-provider"
|
|
13
14
|
],
|
|
14
15
|
"configSchema": "config.schema.json"
|
|
15
16
|
}
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentOperationError = exports.AgentError = void 0;
|
|
4
|
+
exports.abortable = abortable;
|
|
4
5
|
exports.errorDetails = errorDetails;
|
|
6
|
+
async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
|
|
11
|
+
void promise.catch(() => undefined);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
17
|
+
const abort = () => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
cleanup();
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
25
|
+
promise.then((value) => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
cleanup();
|
|
30
|
+
resolve(value);
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
class AgentError extends Error {
|
|
6
41
|
constructor(message, options) {
|
|
7
42
|
super(message, options);
|
|
@@ -3,12 +3,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CodexCliPlugin = exports.manifest = void 0;
|
|
4
4
|
const agent_1 = require("../local-cli/agent");
|
|
5
5
|
exports.manifest = {
|
|
6
|
-
id: "codex-cli", version: "1.0.
|
|
6
|
+
id: "codex-cli", version: "1.0.2", displayName: "Codex CLI", type: "agent", executionMode: "cli",
|
|
7
7
|
entry: "dist/src-ts/plugins/codex-cli/index.js", apiVersion: "1", process: "in-process",
|
|
8
|
-
capabilities: ["chat", "health"], configSchema: "config.schema.json",
|
|
8
|
+
capabilities: ["chat", "health", "model-provider"], configSchema: "config.schema.json",
|
|
9
9
|
};
|
|
10
10
|
class CodexCliPlugin extends agent_1.LocalCliAgent {
|
|
11
11
|
constructor() { super(exports.manifest, "codex"); }
|
|
12
|
+
get modelProvider() {
|
|
13
|
+
if (!this.modelInput)
|
|
14
|
+
return undefined;
|
|
15
|
+
return { complete: (request) => this.completeModelProvider(request) };
|
|
16
|
+
}
|
|
17
|
+
async completeModelProvider(request) {
|
|
18
|
+
let responseSchema;
|
|
19
|
+
try {
|
|
20
|
+
responseSchema = JSON.parse(request.prompt)?.responseSchema;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error("Codex CLI 模型请求必须是包含 responseSchema 的 JSON 对象");
|
|
24
|
+
}
|
|
25
|
+
if (!responseSchema || typeof responseSchema !== "object" || Array.isArray(responseSchema))
|
|
26
|
+
throw new Error("Codex CLI 模型请求缺少有效 responseSchema");
|
|
27
|
+
return this.completeStructuredModel(request, responseSchema);
|
|
28
|
+
}
|
|
12
29
|
}
|
|
13
30
|
exports.CodexCliPlugin = CodexCliPlugin;
|
|
14
31
|
exports.default = CodexCliPlugin;
|
|
@@ -6,6 +6,7 @@ const node_child_process_1 = require("node:child_process");
|
|
|
6
6
|
const node_fs_1 = require("node:fs");
|
|
7
7
|
const promises_1 = require("node:fs/promises");
|
|
8
8
|
const node_path_1 = require("node:path");
|
|
9
|
+
const node_os_1 = require("node:os");
|
|
9
10
|
const node_crypto_1 = require("node:crypto");
|
|
10
11
|
const nativeId = /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i;
|
|
11
12
|
const maximumOutput = 4 * 1024 * 1024;
|
|
@@ -22,6 +23,21 @@ function diagnostic(value) {
|
|
|
22
23
|
} })
|
|
23
24
|
.slice(0, 2000);
|
|
24
25
|
}
|
|
26
|
+
function modelDiagnostic(error) {
|
|
27
|
+
const message = String(error);
|
|
28
|
+
const code = /CLI 退出码 (?:\d+|[A-Z][A-Z0-9_]+)/.exec(message)?.[0];
|
|
29
|
+
if (code)
|
|
30
|
+
return `${code}:请检查 CLI 登录、权限和网络`;
|
|
31
|
+
if (message.includes("CLI 调用超时"))
|
|
32
|
+
return "CLI 模型调用超时,请检查登录、网络或权限审批";
|
|
33
|
+
if (message.includes("CLI 请求已取消"))
|
|
34
|
+
return "CLI 模型请求已取消";
|
|
35
|
+
if (message.includes("插件已停止"))
|
|
36
|
+
return "插件已停止,当前 CLI 模型任务已终止";
|
|
37
|
+
if (message.includes("CLI 输出超过"))
|
|
38
|
+
return "CLI 模型输出超过 4MB 限制";
|
|
39
|
+
return "CLI 模型调用或结构化输出失败,请检查 CLI 状态";
|
|
40
|
+
}
|
|
25
41
|
async function resolveWindowsCliShim(file, provider) {
|
|
26
42
|
const modules = (0, node_path_1.basename)((0, node_path_1.dirname)(file)) === ".bin" ? (0, node_path_1.dirname)((0, node_path_1.dirname)(file)) : (0, node_path_1.join)((0, node_path_1.dirname)(file), "node_modules");
|
|
27
43
|
const root = (0, node_path_1.join)(modules, ...(provider === "codex" ? ["@openai", "codex"] : ["@anthropic-ai", "claude-code"]));
|
|
@@ -76,6 +92,8 @@ class LocalCliAgent {
|
|
|
76
92
|
ready = false;
|
|
77
93
|
message = "插件尚未启动";
|
|
78
94
|
version = "";
|
|
95
|
+
modelModeSupported = false;
|
|
96
|
+
get modelInput() { return this.ready && this.modelModeSupported; }
|
|
79
97
|
sessions = new Map();
|
|
80
98
|
writes = Promise.resolve();
|
|
81
99
|
pending = new Map();
|
|
@@ -108,6 +126,9 @@ class LocalCliAgent {
|
|
|
108
126
|
const help = (await this.run(this.provider === "codex" ? ["exec", "--help"] : ["--help"], "", 10000)).stdout;
|
|
109
127
|
if (this.provider === "codex" ? !help.includes("--json") || !help.includes("resume") : !help.includes("--output-format") || !help.includes("--resume"))
|
|
110
128
|
throw new Error("CLI 版本不支持所需的结构化输出和会话恢复,请升级 CLI");
|
|
129
|
+
this.modelModeSupported = this.provider === "claude"
|
|
130
|
+
? ["--json-schema", "--tools", "--strict-mcp-config", "--mcp-config", "--no-session-persistence", "--settings", "--setting-sources"].every(flag => help.includes(flag))
|
|
131
|
+
: ["--output-schema", "--ephemeral", "--sandbox", "--disable", "--config", "--ignore-rules"].every(flag => help.includes(flag));
|
|
111
132
|
this.sessions = new Map();
|
|
112
133
|
try {
|
|
113
134
|
const data = JSON.parse(await (0, promises_1.readFile)((0, node_path_1.join)(context.dataDir, "cli-sessions.json"), "utf8"));
|
|
@@ -128,7 +149,9 @@ class LocalCliAgent {
|
|
|
128
149
|
throw new Error(this.message);
|
|
129
150
|
}
|
|
130
151
|
}
|
|
131
|
-
run(args, input, timeout) {
|
|
152
|
+
run(args, input, timeout, signal) {
|
|
153
|
+
if (signal?.aborted)
|
|
154
|
+
return Promise.reject(new Error("CLI 请求已取消"));
|
|
132
155
|
const command = this.command;
|
|
133
156
|
let child;
|
|
134
157
|
let stop;
|
|
@@ -153,8 +176,10 @@ class LocalCliAgent {
|
|
|
153
176
|
}
|
|
154
177
|
};
|
|
155
178
|
stop = () => terminate("插件已停止,当前 CLI 任务已终止");
|
|
179
|
+
const abort = () => terminate("CLI 请求已取消");
|
|
180
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
156
181
|
const timer = setTimeout(() => terminate(`CLI 调用超时(${timeout}ms),请检查登录、网络或权限审批`), timeout);
|
|
157
|
-
const cleanup = () => { clearTimeout(timer); this.children.delete(child); };
|
|
182
|
+
const cleanup = () => { clearTimeout(timer); signal?.removeEventListener("abort", abort); this.children.delete(child); };
|
|
158
183
|
child.stdout.setEncoding("utf8");
|
|
159
184
|
child.stderr.setEncoding("utf8");
|
|
160
185
|
const collect = (value, error) => {
|
|
@@ -227,35 +252,70 @@ class LocalCliAgent {
|
|
|
227
252
|
throw new Error(`${this.manifest.displayName} 本期不支持附件输入`);
|
|
228
253
|
if (!request.task?.trim())
|
|
229
254
|
throw new Error("CLI 任务文本不能为空");
|
|
255
|
+
return this.executeCli(request, request.modelResponseSchema);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 模型来源的独立入口。调用方只提供最小模型契约,不能通过普通 Agent handle 进入管理路径。
|
|
259
|
+
*/
|
|
260
|
+
async completeStructuredModel(request, responseSchema) {
|
|
261
|
+
if (!request.prompt?.trim())
|
|
262
|
+
throw new Error("模型请求文本不能为空");
|
|
263
|
+
if (!request.sessionId?.trim())
|
|
264
|
+
throw new Error("模型请求会话标识不能为空");
|
|
265
|
+
const response = await this.executeCli({ task: request.prompt, sessionId: request.sessionId, timeoutMs: request.timeoutMs, signal: request.signal }, responseSchema);
|
|
266
|
+
return { text: response.text };
|
|
267
|
+
}
|
|
268
|
+
async executeCli(request, modelSchema) {
|
|
269
|
+
const modelMode = modelSchema !== undefined;
|
|
270
|
+
if (modelMode && !this.modelModeSupported)
|
|
271
|
+
throw new Error("当前 CLI 不支持无工具结构化模型请求,请升级 CLI");
|
|
272
|
+
const schema = modelMode ? JSON.stringify(modelSchema) : undefined;
|
|
273
|
+
if (modelMode && (!modelSchema || typeof modelSchema !== "object" || Array.isArray(modelSchema) || !schema || Buffer.byteLength(schema) > 64_000))
|
|
274
|
+
throw new Error("模型输出 Schema 无效或过大");
|
|
230
275
|
const logical = request.sessionId || (0, node_crypto_1.randomUUID)();
|
|
231
276
|
return this.serial(logical, async () => {
|
|
232
277
|
this.requireReady();
|
|
233
|
-
const id = request.newSession ? undefined : this.sessions.get(logical);
|
|
278
|
+
const id = modelMode || request.newSession ? undefined : this.sessions.get(logical);
|
|
279
|
+
let schemaFile;
|
|
280
|
+
let schemaDirectory;
|
|
234
281
|
const args = this.provider === "codex"
|
|
235
282
|
? ["exec", ...(id ? ["resume", id] : []), "--json", "-"]
|
|
236
283
|
: ["--print", "--output-format", "json", ...(id ? ["--resume", id] : [])];
|
|
284
|
+
if (modelMode && this.provider === "claude")
|
|
285
|
+
args.push("--json-schema", schema, "--tools", "", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}', "--settings", '{"disableAllHooks":true}', "--setting-sources", "user", "--no-session-persistence");
|
|
237
286
|
const timeout = request.timeoutMs ?? this.timeoutMs;
|
|
238
287
|
if (!Number.isInteger(timeout) || timeout <= 0 || timeout > 2147483647)
|
|
239
288
|
throw new Error("任务超时必须是有效的正整数毫秒数");
|
|
240
289
|
const started = Date.now();
|
|
241
290
|
this.context.logger.info("本地 CLI 任务开始", { plugin: this.manifest.id, sessionId: logical, resume: Boolean(id) });
|
|
242
291
|
try {
|
|
243
|
-
|
|
244
|
-
|
|
292
|
+
if (modelMode && this.provider === "codex") {
|
|
293
|
+
schemaDirectory = await (0, promises_1.mkdtemp)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "mab-codex-model-"));
|
|
294
|
+
schemaFile = (0, node_path_1.join)(schemaDirectory, "response-schema.json");
|
|
295
|
+
await (0, promises_1.writeFile)(schemaFile, schema, { flag: "wx", mode: 0o600 });
|
|
296
|
+
args.splice(-1, 0, "--ephemeral", "--sandbox", "read-only", "--output-schema", schemaFile, "--disable", "shell_tool", "--disable", "apps", "--disable", "plugins", "--disable", "hooks", "--ignore-rules", "--config", "mcp_servers={}");
|
|
297
|
+
}
|
|
298
|
+
const output = await this.run(args, request.task, timeout, request.signal);
|
|
299
|
+
const response = this.parse(output.stdout, modelMode);
|
|
245
300
|
if (id && response.id !== id)
|
|
246
301
|
throw new Error("CLI 恢复返回的会话 ID 与指定会话不一致,保留原会话绑定");
|
|
247
|
-
|
|
302
|
+
if (!modelMode)
|
|
303
|
+
await this.save(logical, response.id);
|
|
248
304
|
this.context.logger.info("本地 CLI 任务完成", { plugin: this.manifest.id, sessionId: logical, durationMs: Date.now() - started, exitCode: 0 });
|
|
249
|
-
return { text: response.text, sessionId: logical };
|
|
305
|
+
return { text: response.text, ...(!modelMode ? { sessionId: logical } : {}) };
|
|
250
306
|
}
|
|
251
307
|
catch (error) {
|
|
252
|
-
const detail = diagnostic(String(error));
|
|
308
|
+
const detail = modelMode ? modelDiagnostic(error) : diagnostic(String(error));
|
|
253
309
|
this.context.logger.error("本地 CLI 任务失败", { plugin: this.manifest.id, sessionId: logical, durationMs: Date.now() - started, error: detail });
|
|
254
310
|
throw new Error(`${this.manifest.displayName}:${detail}`);
|
|
255
311
|
}
|
|
312
|
+
finally {
|
|
313
|
+
if (schemaFile && schemaDirectory)
|
|
314
|
+
await (0, promises_1.rm)(schemaDirectory, { recursive: true, force: true });
|
|
315
|
+
}
|
|
256
316
|
});
|
|
257
317
|
}
|
|
258
|
-
parse(stdout) {
|
|
318
|
+
parse(stdout, modelMode = false) {
|
|
259
319
|
let id = "", text = "";
|
|
260
320
|
try {
|
|
261
321
|
if (this.provider === "codex") {
|
|
@@ -287,6 +347,11 @@ class LocalCliAgent {
|
|
|
287
347
|
throw new Error(JSON.stringify(event.errors ?? event));
|
|
288
348
|
id = event.session_id;
|
|
289
349
|
text = event.result;
|
|
350
|
+
if (modelMode) {
|
|
351
|
+
if (!event.structured_output || typeof event.structured_output !== "object" || Array.isArray(event.structured_output))
|
|
352
|
+
throw new Error("缺少有效结构化模型输出");
|
|
353
|
+
text = JSON.stringify(event.structured_output);
|
|
354
|
+
}
|
|
290
355
|
}
|
|
291
356
|
if (typeof id !== "string" || !nativeId.test(id))
|
|
292
357
|
throw new Error("缺少有效原生会话 ID");
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "codex-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"type": "agent",
|
|
5
5
|
"executionMode": "cli",
|
|
6
6
|
"displayName": "Codex CLI",
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"process": "in-process",
|
|
10
10
|
"capabilities": [
|
|
11
11
|
"chat",
|
|
12
|
-
"health"
|
|
12
|
+
"health",
|
|
13
|
+
"model-provider"
|
|
13
14
|
],
|
|
14
15
|
"configSchema": "config.schema.json"
|
|
15
16
|
}
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
"startupTimeoutMs": { "type": "integer", "minimum": 1000, "default": 30000, "description": "等待豆包启动的超时时间(毫秒)" },
|
|
9
9
|
"conversationId": { "type": "string", "description": "豆包会话 ID" },
|
|
10
10
|
"botId": { "type": "string", "description": "豆包 bot ID" },
|
|
11
|
+
"mode": { "type": "string", "enum": ["local-computer", "chat"], "default": "local-computer", "description": "默认使用本地电脑任务模式,继承当前桌面登录态的工作区和授权;chat 为纯聊天" },
|
|
11
12
|
"pluginList": { "type": "array", "items": { "type": "string" }, "description": "请求时显式启用的豆包伙伴标识,例如飞书伙伴" },
|
|
12
|
-
"timeoutMs": { "type": "integer", "minimum": 1000, "description": "
|
|
13
|
+
"timeoutMs": { "type": "integer", "minimum": 1000, "description": "等待回复超时时间(毫秒),默认 180000;本地电脑模式按本轮新流事件续期" },
|
|
13
14
|
"token": { "type": "string", "secret": true }
|
|
14
15
|
},
|
|
15
16
|
"additionalProperties": false
|
|
@@ -1,7 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AgentOperationError = exports.AgentError = void 0;
|
|
4
|
+
exports.abortable = abortable;
|
|
4
5
|
exports.errorDetails = errorDetails;
|
|
6
|
+
async function abortable(promise, signal) {
|
|
7
|
+
if (!signal)
|
|
8
|
+
return promise;
|
|
9
|
+
if (signal.aborted) {
|
|
10
|
+
// promise 已在调用方启动,先观察其拒绝,避免预取消时产生未处理拒绝。
|
|
11
|
+
void promise.catch(() => undefined);
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
}
|
|
14
|
+
return new Promise((resolve, reject) => {
|
|
15
|
+
let settled = false;
|
|
16
|
+
const cleanup = () => signal.removeEventListener("abort", abort);
|
|
17
|
+
const abort = () => {
|
|
18
|
+
if (settled)
|
|
19
|
+
return;
|
|
20
|
+
settled = true;
|
|
21
|
+
cleanup();
|
|
22
|
+
reject(signal.reason);
|
|
23
|
+
};
|
|
24
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
25
|
+
promise.then((value) => {
|
|
26
|
+
if (settled)
|
|
27
|
+
return;
|
|
28
|
+
settled = true;
|
|
29
|
+
cleanup();
|
|
30
|
+
resolve(value);
|
|
31
|
+
}, (error) => {
|
|
32
|
+
if (settled)
|
|
33
|
+
return;
|
|
34
|
+
settled = true;
|
|
35
|
+
cleanup();
|
|
36
|
+
reject(error);
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
5
40
|
class AgentError extends Error {
|
|
6
41
|
constructor(message, options) {
|
|
7
42
|
super(message, options);
|
|
@@ -16,8 +16,8 @@ const node_path_1 = require("node:path");
|
|
|
16
16
|
const node_util_1 = require("node:util");
|
|
17
17
|
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
|
18
18
|
const versionDirectory = /^(?:app[-_ ]?)?v?\d+(?:[.-]\d+)+$/i;
|
|
19
|
-
/**
|
|
20
|
-
async function findWindowsExecutable(fileName, configured) {
|
|
19
|
+
/** 先从 Windows 注册信息和开始菜单解析,失败时再查常见安装目录。 */
|
|
20
|
+
async function findWindowsExecutable(fileName, configured, run = execFileAsync) {
|
|
21
21
|
if (configured?.trim()) {
|
|
22
22
|
const configuredPath = (0, node_path_1.resolve)(configured);
|
|
23
23
|
if (fileName.toLowerCase() === "doubao.exe") {
|
|
@@ -42,12 +42,16 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
42
42
|
}
|
|
43
43
|
if (process.platform !== "win32")
|
|
44
44
|
return undefined;
|
|
45
|
-
const
|
|
45
|
+
const registered = await findRegisteredWindowsExecutable(fileName, run);
|
|
46
|
+
if (registered)
|
|
47
|
+
return registered;
|
|
48
|
+
const pattern = /doubao|豆包|qwen|qwwork|千问|deepseek|harness|dsh|zcode/i;
|
|
46
49
|
const roots = [...new Set([
|
|
47
50
|
process.env.DSH_INSTALL_DIR,
|
|
48
51
|
"D:\\Programs\\DeepSeek Harness",
|
|
49
52
|
"D:\\DSH",
|
|
50
53
|
process.env.LOCALAPPDATA && (0, node_path_1.join)(process.env.LOCALAPPDATA, "Programs"),
|
|
54
|
+
process.env.LOCALAPPDATA,
|
|
51
55
|
process.env.ProgramFiles,
|
|
52
56
|
process.env["ProgramFiles(x86)"],
|
|
53
57
|
...Array.from({ length: 24 }, (_, index) => `${String.fromCharCode(67 + index)}:\\`),
|
|
@@ -59,6 +63,36 @@ async function findWindowsExecutable(fileName, configured) {
|
|
|
59
63
|
}
|
|
60
64
|
return undefined;
|
|
61
65
|
}
|
|
66
|
+
async function findRegisteredWindowsExecutable(fileName, run) {
|
|
67
|
+
const target = fileName.replace(/'/g, "''");
|
|
68
|
+
const script = `$ErrorActionPreference='SilentlyContinue';[Console]::OutputEncoding=[Text.Encoding]::UTF8;$target='${target}'
|
|
69
|
+
function Emit-Path($value){if(-not $value){return};$value=[Environment]::ExpandEnvironmentVariables(([string]$value).Trim());if($value.StartsWith('"')){$end=$value.IndexOf('"',1);if($end -gt 1){$value=$value.Substring(1,$end-1)}}elseif($value -match '^(\S+?\.exe)(?:\s|$)'){$value=$Matches[1]};if($value -match '(?i)\.exe$'){[Console]::WriteLine($value)}}
|
|
70
|
+
$keys=@("HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKLM:\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\$target","HKCU:\\Software\\Classes\\Applications\\$target\\shell\\open\\command","HKLM:\\Software\\Classes\\Applications\\$target\\shell\\open\\command")
|
|
71
|
+
foreach($key in $keys){$item=Get-Item $key -ErrorAction SilentlyContinue;if($item){Emit-Path $item.GetValue('')}}
|
|
72
|
+
$dirs=@();if($env:APPDATA){$dirs+=[IO.Path]::Combine($env:APPDATA,'Microsoft\\Windows\\Start Menu\\Programs')};if($env:ProgramData){$dirs+=[IO.Path]::Combine($env:ProgramData,'Microsoft\\Windows\\Start Menu\\Programs')};$shell=New-Object -ComObject WScript.Shell
|
|
73
|
+
foreach($dir in $dirs){Get-ChildItem $dir -Filter '*.lnk' -File -Recurse -ErrorAction SilentlyContinue|ForEach-Object{$link=$shell.CreateShortcut($_.FullName);if($link.TargetPath -and [IO.Path]::GetFileName($link.TargetPath) -ieq $target){Emit-Path $link.TargetPath}}}`;
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await run("powershell.exe", ["-NoProfile", "-NonInteractive", "-EncodedCommand", Buffer.from(script, "utf16le").toString("base64")], { timeout: 10_000, windowsHide: true });
|
|
76
|
+
for (const candidate of stdout.split(/\r?\n/).map(value => value.trim()).filter(Boolean)) {
|
|
77
|
+
const found = await resolveRegisteredCandidate(fileName, candidate);
|
|
78
|
+
if (found)
|
|
79
|
+
return found;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch { /* 注册表或开始菜单不可读时继续使用目录兜底 */ }
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
async function resolveRegisteredCandidate(fileName, candidate) {
|
|
86
|
+
const candidatePath = (0, node_path_1.resolve)(candidate.replace(/^"|"$/g, ""));
|
|
87
|
+
if (fileName.toLowerCase() === "doubao.exe" && (0, node_path_1.basename)((0, node_path_1.dirname)(candidatePath)).toLowerCase() === "application") {
|
|
88
|
+
const client = (0, node_path_1.join)((0, node_path_1.dirname)(candidatePath), "app", fileName);
|
|
89
|
+
if ((0, node_fs_1.existsSync)(client))
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
if ((0, node_fs_1.existsSync)(candidatePath) && (0, node_path_1.basename)(candidatePath).toLowerCase() === fileName.toLowerCase())
|
|
93
|
+
return candidatePath;
|
|
94
|
+
return findExecutableInTree((0, node_path_1.dirname)(candidatePath), fileName, /^/, 3);
|
|
95
|
+
}
|
|
62
96
|
async function findExecutableInTree(directory, fileName, pattern, depth) {
|
|
63
97
|
return search(directory, fileName, pattern, depth, pattern.test(directory));
|
|
64
98
|
}
|
|
@@ -104,9 +138,16 @@ async function launchManagedAndWait(executable, args, profileDirectory, connect,
|
|
|
104
138
|
if (profileDirectory)
|
|
105
139
|
await (0, promises_1.mkdir)(profileDirectory, { recursive: true });
|
|
106
140
|
const child = launch(executable, profileDirectory ? [...args, `--user-data-dir=${profileDirectory}`] : args);
|
|
141
|
+
// 先等待 spawn,避免异步 error 终止 daemon 或在启动失败后继续轮询。
|
|
142
|
+
const events = child;
|
|
143
|
+
const started = typeof events.once === "function" ? new Promise((resolve, reject) => {
|
|
144
|
+
events.once("error", reject);
|
|
145
|
+
events.once("spawn", resolve);
|
|
146
|
+
}) : Promise.resolve();
|
|
107
147
|
child.unref();
|
|
108
148
|
onProcess(child);
|
|
109
149
|
try {
|
|
150
|
+
await started;
|
|
110
151
|
return await waitFor(connect, timeoutMs, 250);
|
|
111
152
|
}
|
|
112
153
|
catch (error) {
|