@reconcrap/boss-recommend-mcp 1.3.34 → 1.3.36
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 +14 -7
- package/package.json +1 -1
- package/src/boss-chat.js +383 -120
- package/src/cli.js +34 -24
- package/src/test-boss-chat.js +242 -84
- package/vendor/boss-chat-cli/src/cli.js +160 -45
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Boss 推荐页自动化流水线 MCP(stdio)服务。
|
|
|
16
16
|
|
|
17
17
|
- 单独运行 chat-only 任务,不需要再单独安装 `boss-chat`
|
|
18
18
|
- 在 recommend screen 完成后,通过同一个父 run 自动进入 `chat_followup`
|
|
19
|
-
-
|
|
19
|
+
- 继续把聊天页状态保存在用户目录下的 `~/.boss-recommend-mcp/boss-chat/`(可通过 `BOSS_CHAT_HOME` 覆盖)
|
|
20
20
|
|
|
21
21
|
MCP 工具:
|
|
22
22
|
|
|
@@ -68,7 +68,7 @@ MCP 工具:
|
|
|
68
68
|
- 在真正开始 search/screen 前,会进行最后一轮全参数总确认(岗位 + 全部筛选参数 + criteria + target_count + post_action + max_greet_count)
|
|
69
69
|
- npm 全局安装后会自动执行 install:生成 skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
|
|
70
70
|
- npm / npx 安装后会自动初始化 `screening-config.json` 模板(优先写入 workspace 的 `config/`,不可写时回退到用户目录)
|
|
71
|
-
- npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs
|
|
71
|
+
- npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`~/.boss-recommend-mcp/boss-chat` 及其 `logs/runs/profiles/reports/artifacts/state`
|
|
72
72
|
- `post_action` 必须在每次完整运行开始时确认一次
|
|
73
73
|
- `target_count` 会在每次运行开始时询问一次(可留空,不设上限)
|
|
74
74
|
- 当 `post_action=greet` 时,必须在运行开始时确认 `max_greet_count`
|
|
@@ -111,6 +111,7 @@ node src/cli.js start
|
|
|
111
111
|
|
|
112
112
|
```bash
|
|
113
113
|
BOSS_RECOMMEND_HOME # 统一状态目录,默认 ~/.boss-recommend-mcp
|
|
114
|
+
BOSS_CHAT_HOME # 覆盖 boss-chat 运行态目录;默认 ~/.boss-recommend-mcp/boss-chat
|
|
114
115
|
BOSS_RECOMMEND_SCREEN_CONFIG # 显式指定 screening-config.json 路径(最高优先级)
|
|
115
116
|
BOSS_RECOMMEND_MCP_CONFIG_TARGETS # JSON 数组或系统 path 分隔路径列表,指定额外 mcp.json 目标文件
|
|
116
117
|
BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS # JSON 数组或系统 path 分隔路径列表,指定额外 skills 根目录
|
|
@@ -149,6 +150,8 @@ node src/cli.js run --instruction "推荐页筛选985男生,近14天没有,
|
|
|
149
150
|
- `doctor` / `run` 默认优先读取 `~/.boss-recommend-mcp/screening-config.json`;如需强制其它路径,请设置 `BOSS_RECOMMEND_SCREEN_CONFIG`
|
|
150
151
|
- 首次运行时,若仍检测到默认占位词(如 `replace-with-openai-api-key`),pipeline 会返回配置目录并要求用户修改后确认“已修改完成”再继续
|
|
151
152
|
- 在 `npx` 临时目录(如 `AppData\\Local\\npm-cache\\_npx\\...`)执行时,不会再把该临时目录当作 `screening-config.json` 目标路径
|
|
153
|
+
- `boss-chat` 运行态默认固定写入 `~/.boss-recommend-mcp/boss-chat`;即使宿主把 MCP 进程启动在系统根目录,也不会再尝试写入 `/.boss-chat`
|
|
154
|
+
- 若当前工作区存在历史 `.boss-chat` 且新用户目录尚未初始化,首次运行会自动把 `logs/runs/profiles/reports/artifacts/state` 迁移到新目录,并保留旧目录作为只读历史来源
|
|
152
155
|
|
|
153
156
|
配置样例见:
|
|
154
157
|
|
|
@@ -217,6 +220,7 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
|
|
|
217
220
|
- `baseUrl` / `apiKey` / `model` 不再单独传入,固定复用 recommend 的 `screening-config.json`
|
|
218
221
|
- 若缺少 `follow_up.chat` 必填项,pipeline 会返回 `NEED_INPUT`
|
|
219
222
|
- recommend 成功后,父 run 继续存活并进入 `chat_followup`;chat 结束后父 run 才会进入最终终态
|
|
223
|
+
- `boss-chat` 子任务状态统一写入 `~/.boss-recommend-mcp/boss-chat`(或 `BOSS_CHAT_HOME` 指定目录),不再依赖工作区 `cwd`
|
|
220
224
|
|
|
221
225
|
## Chat-only
|
|
222
226
|
|
|
@@ -230,11 +234,14 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
|
|
|
230
234
|
- MCP:
|
|
231
235
|
- `boss_chat_health_check`
|
|
232
236
|
- `prepare_boss_chat_run`
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
237
|
+
- `start_boss_chat_run`
|
|
238
|
+
- `get_boss_chat_run`
|
|
239
|
+
- `pause_boss_chat_run`
|
|
240
|
+
- `resume_boss_chat_run`
|
|
241
|
+
- `cancel_boss_chat_run`
|
|
242
|
+
- vendored `boss-chat` CLI 还支持 `--data-dir <path>` 与 `BOSS_CHAT_HOME`,默认目录为 `~/.boss-recommend-mcp/boss-chat`(若设置 `BOSS_RECOMMEND_HOME`,则默认 `<BOSS_RECOMMEND_HOME>/boss-chat`)
|
|
243
|
+
- 对 `/.boss-chat`、系统目录等危险运行目录会主动拒绝启动并返回 `UNSAFE_DATA_DIR`,避免在 harness 丢参时误写根目录
|
|
244
|
+
- `boss_chat_health_check` 与 chat run 返回结果会包含 `data_dir` 与 `data_dir_source`,便于定位是参数/环境变量/默认路径生效
|
|
238
245
|
|
|
239
246
|
chat-only 交互建议:
|
|
240
247
|
|
package/package.json
CHANGED
package/src/boss-chat.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
8
|
import { getScreenConfigResolution, resolveSharedLlmTransportConfig } from "./adapters.js";
|
|
8
9
|
|
|
@@ -11,12 +12,14 @@ const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
|
|
|
11
12
|
const VENDORED_BOSS_CHAT_DIR = path.join(packageRoot, "vendor", "boss-chat-cli");
|
|
12
13
|
const DEFAULT_BOSS_CHAT_POLL_MS = 1500;
|
|
13
14
|
const PREPARE_BOSS_CHAT_MAX_ATTEMPTS = 3;
|
|
14
|
-
const PREPARE_BOSS_CHAT_RETRY_DELAY_MS = 1200;
|
|
15
|
-
const BOSS_CHAT_TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
|
|
16
|
-
const CHAT_REQUIRED_FIELDS = ["job", "start_from", "target_count", "criteria"];
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
15
|
+
const PREPARE_BOSS_CHAT_RETRY_DELAY_MS = 1200;
|
|
16
|
+
const BOSS_CHAT_TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
|
|
17
|
+
const CHAT_REQUIRED_FIELDS = ["job", "start_from", "target_count", "criteria"];
|
|
18
|
+
const BOSS_CHAT_RUNTIME_SUBDIR = "boss-chat";
|
|
19
|
+
const BOSS_CHAT_RUNTIME_CHILD_DIRS = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
20
|
+
export const TARGET_COUNT_CANONICAL_ALL = "all";
|
|
21
|
+
export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
|
|
22
|
+
const TARGET_COUNT_WRAPPER_KEYS = ["target_count", "targetCount", "value", "count", "limit"];
|
|
20
23
|
const LLM_THINKING_LEVEL_FIELDS = [
|
|
21
24
|
"llmThinkingLevel",
|
|
22
25
|
"thinkingLevel",
|
|
@@ -28,13 +31,210 @@ function normalizeText(value) {
|
|
|
28
31
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
function pathExists(targetPath) {
|
|
32
|
-
try {
|
|
33
|
-
return fs.existsSync(targetPath);
|
|
34
|
+
function pathExists(targetPath) {
|
|
35
|
+
try {
|
|
36
|
+
return fs.existsSync(targetPath);
|
|
34
37
|
} catch {
|
|
35
38
|
return false;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getStateHome() {
|
|
43
|
+
return process.env.BOSS_RECOMMEND_HOME
|
|
44
|
+
? path.resolve(process.env.BOSS_RECOMMEND_HOME)
|
|
45
|
+
: path.join(os.homedir(), ".boss-recommend-mcp");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isRootDirectory(targetPath) {
|
|
49
|
+
const resolved = path.resolve(String(targetPath || ""));
|
|
50
|
+
const parsed = path.parse(resolved);
|
|
51
|
+
return resolved.toLowerCase() === String(parsed.root || "").toLowerCase();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isSystemDirectoryWorkspaceRoot(workspaceRoot) {
|
|
55
|
+
const root = path.resolve(String(workspaceRoot || ""));
|
|
56
|
+
const normalized = root.replace(/\\/g, "/").toLowerCase();
|
|
57
|
+
if (process.platform === "win32") {
|
|
58
|
+
return (
|
|
59
|
+
normalized.endsWith("/windows")
|
|
60
|
+
|| normalized.endsWith("/windows/system32")
|
|
61
|
+
|| normalized.endsWith("/windows/syswow64")
|
|
62
|
+
|| normalized.endsWith("/program files")
|
|
63
|
+
|| normalized.endsWith("/program files (x86)")
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return (
|
|
67
|
+
normalized === "/system"
|
|
68
|
+
|| normalized.startsWith("/system/")
|
|
69
|
+
|| normalized === "/usr"
|
|
70
|
+
|| normalized.startsWith("/usr/")
|
|
71
|
+
|| normalized === "/bin"
|
|
72
|
+
|| normalized.startsWith("/bin/")
|
|
73
|
+
|| normalized === "/sbin"
|
|
74
|
+
|| normalized.startsWith("/sbin/")
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isEphemeralWorkspaceRoot(workspaceRoot) {
|
|
79
|
+
const normalized = path.resolve(String(workspaceRoot || ""))
|
|
80
|
+
.replace(/\\/g, "/")
|
|
81
|
+
.toLowerCase();
|
|
82
|
+
return (
|
|
83
|
+
normalized.includes("/appdata/local/npm-cache/_npx/")
|
|
84
|
+
|| normalized.includes("/node_modules/@reconcrap/boss-recommend-mcp")
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function isSafeBossChatLegacyWorkspaceRoot(workspaceRoot) {
|
|
89
|
+
const root = path.resolve(String(workspaceRoot || ""));
|
|
90
|
+
if (!root) return false;
|
|
91
|
+
const home = path.resolve(os.homedir());
|
|
92
|
+
return !(
|
|
93
|
+
isEphemeralWorkspaceRoot(root)
|
|
94
|
+
|| isRootDirectory(root)
|
|
95
|
+
|| root.toLowerCase() === home.toLowerCase()
|
|
96
|
+
|| isSystemDirectoryWorkspaceRoot(root)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isUnsafeBossChatDataDir(targetPath) {
|
|
101
|
+
const resolved = path.resolve(String(targetPath || ""));
|
|
102
|
+
return isRootDirectory(resolved) || isSystemDirectoryWorkspaceRoot(resolved);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveBossChatDataDir() {
|
|
106
|
+
if (process.env.BOSS_CHAT_HOME) {
|
|
107
|
+
return {
|
|
108
|
+
data_dir: path.resolve(process.env.BOSS_CHAT_HOME),
|
|
109
|
+
data_dir_source: "env:BOSS_CHAT_HOME"
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const stateHome = getStateHome();
|
|
113
|
+
const source = process.env.BOSS_RECOMMEND_HOME
|
|
114
|
+
? "default:env:BOSS_RECOMMEND_HOME"
|
|
115
|
+
: "default:user_home";
|
|
116
|
+
return {
|
|
117
|
+
data_dir: path.join(stateHome, BOSS_CHAT_RUNTIME_SUBDIR),
|
|
118
|
+
data_dir_source: source
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getBossChatDataDir() {
|
|
123
|
+
return resolveBossChatDataDir().data_dir;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function getLegacyBossChatWorkspaceDataDir(workspaceRoot) {
|
|
127
|
+
if (!isSafeBossChatLegacyWorkspaceRoot(workspaceRoot)) return null;
|
|
128
|
+
return path.join(path.resolve(String(workspaceRoot)), ".boss-chat");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveBossChatRuntimeLayout(workspaceRoot) {
|
|
132
|
+
const resolvedDataDir = resolveBossChatDataDir();
|
|
133
|
+
const dataDir = resolvedDataDir.data_dir;
|
|
134
|
+
const legacyWorkspaceDir = getLegacyBossChatWorkspaceDataDir(workspaceRoot);
|
|
135
|
+
const migrationSourceDir =
|
|
136
|
+
legacyWorkspaceDir && pathExists(legacyWorkspaceDir) && !pathExists(dataDir)
|
|
137
|
+
? legacyWorkspaceDir
|
|
138
|
+
: null;
|
|
139
|
+
return {
|
|
140
|
+
workspace_root: workspaceRoot ? path.resolve(String(workspaceRoot)) : null,
|
|
141
|
+
data_dir: dataDir,
|
|
142
|
+
data_dir_source: resolvedDataDir.data_dir_source,
|
|
143
|
+
legacy_workspace_dir: legacyWorkspaceDir,
|
|
144
|
+
migration_source_dir: migrationSourceDir,
|
|
145
|
+
migration_pending: Boolean(migrationSourceDir),
|
|
146
|
+
directories: [
|
|
147
|
+
dataDir,
|
|
148
|
+
...BOSS_CHAT_RUNTIME_CHILD_DIRS.map((name) => path.join(dataDir, name))
|
|
149
|
+
]
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function ensureBossChatRuntimeReady(workspaceRoot) {
|
|
154
|
+
const runtime = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
155
|
+
const created = [];
|
|
156
|
+
const existed = [];
|
|
157
|
+
const failed = [];
|
|
158
|
+
let migration = {
|
|
159
|
+
attempted: false,
|
|
160
|
+
performed: false,
|
|
161
|
+
source: runtime.migration_source_dir,
|
|
162
|
+
target: runtime.data_dir,
|
|
163
|
+
message: runtime.migration_source_dir
|
|
164
|
+
? `Pending legacy boss-chat migration from ${runtime.migration_source_dir}`
|
|
165
|
+
: ""
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
if (isUnsafeBossChatDataDir(runtime.data_dir)) {
|
|
169
|
+
return {
|
|
170
|
+
...runtime,
|
|
171
|
+
created,
|
|
172
|
+
existed,
|
|
173
|
+
failed: [
|
|
174
|
+
{
|
|
175
|
+
path: runtime.data_dir,
|
|
176
|
+
message: `Refusing unsafe boss-chat runtime path: ${runtime.data_dir}. Please use BOSS_CHAT_HOME in a writable user directory.`
|
|
177
|
+
}
|
|
178
|
+
],
|
|
179
|
+
migration,
|
|
180
|
+
blocked_reason: "UNSAFE_DATA_DIR"
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (runtime.migration_source_dir) {
|
|
185
|
+
try {
|
|
186
|
+
fs.cpSync(runtime.migration_source_dir, runtime.data_dir, {
|
|
187
|
+
recursive: true,
|
|
188
|
+
force: false,
|
|
189
|
+
errorOnExist: false
|
|
190
|
+
});
|
|
191
|
+
migration = {
|
|
192
|
+
attempted: true,
|
|
193
|
+
performed: true,
|
|
194
|
+
source: runtime.migration_source_dir,
|
|
195
|
+
target: runtime.data_dir,
|
|
196
|
+
message: `Migrated legacy boss-chat runtime from ${runtime.migration_source_dir} to ${runtime.data_dir}. Legacy source was preserved.`
|
|
197
|
+
};
|
|
198
|
+
} catch (error) {
|
|
199
|
+
migration = {
|
|
200
|
+
attempted: true,
|
|
201
|
+
performed: false,
|
|
202
|
+
source: runtime.migration_source_dir,
|
|
203
|
+
target: runtime.data_dir,
|
|
204
|
+
message: error?.message || "Legacy boss-chat migration failed."
|
|
205
|
+
};
|
|
206
|
+
failed.push({
|
|
207
|
+
path: runtime.data_dir,
|
|
208
|
+
message: `Legacy migration failed: ${migration.message}`
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
for (const directory of runtime.directories) {
|
|
214
|
+
try {
|
|
215
|
+
const existedBefore = pathExists(directory);
|
|
216
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
217
|
+
if (existedBefore) {
|
|
218
|
+
existed.push(directory);
|
|
219
|
+
} else {
|
|
220
|
+
created.push(directory);
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
failed.push({
|
|
224
|
+
path: directory,
|
|
225
|
+
message: error?.message || String(error)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
...runtime,
|
|
232
|
+
created,
|
|
233
|
+
existed,
|
|
234
|
+
failed,
|
|
235
|
+
migration
|
|
236
|
+
};
|
|
237
|
+
}
|
|
38
238
|
|
|
39
239
|
function parsePositiveInteger(value, fallback = null) {
|
|
40
240
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
@@ -469,9 +669,12 @@ function buildTargetCountNeedInputDiagnostics(input = {}, missingFields = []) {
|
|
|
469
669
|
};
|
|
470
670
|
}
|
|
471
671
|
|
|
472
|
-
function buildBossChatCliArgs(command, input, resolvedConfig) {
|
|
473
|
-
const args = [command, "--json"];
|
|
474
|
-
if (
|
|
672
|
+
function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = null) {
|
|
673
|
+
const args = [command, "--json"];
|
|
674
|
+
if (runtimeLayout?.data_dir) {
|
|
675
|
+
args.push("--data-dir", runtimeLayout.data_dir);
|
|
676
|
+
}
|
|
677
|
+
if (command === "prepare-run") {
|
|
475
678
|
const normalized = normalizeBossChatStartInput(input);
|
|
476
679
|
args.push("--profile", normalized.profile);
|
|
477
680
|
if (normalized.job) args.push("--job", normalized.job);
|
|
@@ -532,29 +735,65 @@ function buildBossChatCliArgs(command, input, resolvedConfig) {
|
|
|
532
735
|
const runId = normalizeBossChatRunId(input);
|
|
533
736
|
args.push("--profile", normalizeText(input.profile) || "default");
|
|
534
737
|
args.push("--run-id", runId);
|
|
535
|
-
return args;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
738
|
+
return args;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
function withRuntimeDiagnostics(payload, runtimeLayout) {
|
|
742
|
+
if (!payload || typeof payload !== "object") return payload;
|
|
743
|
+
return {
|
|
744
|
+
...payload,
|
|
745
|
+
data_dir: runtimeLayout?.data_dir || null,
|
|
746
|
+
data_dir_source: runtimeLayout?.data_dir_source || null
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
751
|
+
const runtimeLayout = ensureBossChatRuntimeReady(workspaceRoot);
|
|
752
|
+
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
753
|
+
if (!cliPath) {
|
|
754
|
+
return {
|
|
542
755
|
ok: false,
|
|
543
756
|
exitCode: -1,
|
|
544
757
|
stdout: "",
|
|
545
758
|
stderr: "",
|
|
546
|
-
payload: {
|
|
547
|
-
status: "FAILED",
|
|
548
|
-
error: {
|
|
549
|
-
code: "BOSS_CHAT_CLI_MISSING",
|
|
550
|
-
message: "未找到 vendored boss-chat CLI。"
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
759
|
+
payload: {
|
|
760
|
+
status: "FAILED",
|
|
761
|
+
error: {
|
|
762
|
+
code: "BOSS_CHAT_CLI_MISSING",
|
|
763
|
+
message: "未找到 vendored boss-chat CLI。"
|
|
764
|
+
},
|
|
765
|
+
data_dir: runtimeLayout?.data_dir || null,
|
|
766
|
+
data_dir_source: runtimeLayout?.data_dir_source || null
|
|
767
|
+
}
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
const runtimeInitFailed = runtimeLayout.failed.some((item) => item.path === runtimeLayout.data_dir)
|
|
772
|
+
&& !pathExists(runtimeLayout.data_dir);
|
|
773
|
+
if (runtimeInitFailed) {
|
|
774
|
+
return {
|
|
775
|
+
ok: false,
|
|
776
|
+
exitCode: 1,
|
|
777
|
+
stdout: "",
|
|
778
|
+
stderr: "",
|
|
779
|
+
payload: {
|
|
780
|
+
status: "FAILED",
|
|
781
|
+
error: {
|
|
782
|
+
code: "BOSS_CHAT_RUNTIME_INIT_FAILED",
|
|
783
|
+
message: runtimeLayout.failed
|
|
784
|
+
.filter((item) => item.path === runtimeLayout.data_dir)
|
|
785
|
+
.map((item) => item.message)
|
|
786
|
+
.join("; ") || "无法初始化 boss-chat runtime 目录。"
|
|
787
|
+
},
|
|
788
|
+
data_dir: runtimeLayout.data_dir,
|
|
789
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
790
|
+
migration: runtimeLayout.migration
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
let configResolution = null;
|
|
796
|
+
if (command === "start-run" || command === "prepare-run") {
|
|
558
797
|
configResolution = resolveBossChatScreenConfig(workspaceRoot);
|
|
559
798
|
if (!configResolution.ok) {
|
|
560
799
|
return {
|
|
@@ -562,25 +801,30 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
562
801
|
exitCode: 1,
|
|
563
802
|
stdout: "",
|
|
564
803
|
stderr: "",
|
|
565
|
-
payload: {
|
|
566
|
-
status: "FAILED",
|
|
567
|
-
error: configResolution.error,
|
|
568
|
-
config_path: configResolution.config_path,
|
|
569
|
-
config_dir: configResolution.config_dir
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
804
|
+
payload: {
|
|
805
|
+
status: "FAILED",
|
|
806
|
+
error: configResolution.error,
|
|
807
|
+
config_path: configResolution.config_path,
|
|
808
|
+
config_dir: configResolution.config_dir,
|
|
809
|
+
data_dir: runtimeLayout.data_dir,
|
|
810
|
+
data_dir_source: runtimeLayout.data_dir_source
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
const args = [cliPath, ...buildBossChatCliArgs(command, input, configResolution?.config || {}, runtimeLayout)];
|
|
817
|
+
const cwd = path.resolve(String(workspaceRoot || process.cwd()));
|
|
818
|
+
return new Promise((resolve) => {
|
|
819
|
+
const child = spawn(process.execPath, args, {
|
|
820
|
+
cwd,
|
|
821
|
+
env: {
|
|
822
|
+
...process.env,
|
|
823
|
+
BOSS_CHAT_HOME: runtimeLayout.data_dir
|
|
824
|
+
},
|
|
825
|
+
windowsHide: true,
|
|
826
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
827
|
+
});
|
|
584
828
|
|
|
585
829
|
let stdout = "";
|
|
586
830
|
let stderr = "";
|
|
@@ -596,84 +840,103 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
596
840
|
exitCode: -1,
|
|
597
841
|
stdout,
|
|
598
842
|
stderr,
|
|
599
|
-
payload: {
|
|
600
|
-
status: "FAILED",
|
|
601
|
-
error: {
|
|
602
|
-
code: "BOSS_CHAT_CLI_SPAWN_FAILED",
|
|
603
|
-
message: error?.message || "无法启动 vendored boss-chat CLI。"
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
843
|
+
payload: {
|
|
844
|
+
status: "FAILED",
|
|
845
|
+
error: {
|
|
846
|
+
code: "BOSS_CHAT_CLI_SPAWN_FAILED",
|
|
847
|
+
message: error?.message || "无法启动 vendored boss-chat CLI。"
|
|
848
|
+
},
|
|
849
|
+
data_dir: runtimeLayout.data_dir,
|
|
850
|
+
data_dir_source: runtimeLayout.data_dir_source
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
});
|
|
608
854
|
child.on("close", (code) => {
|
|
609
855
|
const parsed = parseJsonOutput(stdout) || parseJsonOutput(stderr);
|
|
610
856
|
if (parsed && typeof parsed === "object") {
|
|
611
857
|
resolve({
|
|
612
858
|
ok: Number(code) === 0 && String(parsed.status || "").toUpperCase() !== "FAILED",
|
|
613
|
-
exitCode: Number.isInteger(code) ? code : 1,
|
|
614
|
-
stdout,
|
|
615
|
-
stderr,
|
|
616
|
-
payload: parsed
|
|
617
|
-
});
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
resolve({
|
|
859
|
+
exitCode: Number.isInteger(code) ? code : 1,
|
|
860
|
+
stdout,
|
|
861
|
+
stderr,
|
|
862
|
+
payload: withRuntimeDiagnostics(parsed, runtimeLayout)
|
|
863
|
+
});
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
resolve({
|
|
621
867
|
ok: Number(code) === 0,
|
|
622
868
|
exitCode: Number.isInteger(code) ? code : 1,
|
|
623
869
|
stdout,
|
|
624
|
-
stderr,
|
|
625
|
-
payload:
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
870
|
+
stderr,
|
|
871
|
+
payload: withRuntimeDiagnostics(
|
|
872
|
+
Number(code) === 0
|
|
873
|
+
? {
|
|
874
|
+
status: "OK",
|
|
875
|
+
message: normalizeText(stdout) || `${command} 执行成功。`
|
|
876
|
+
}
|
|
877
|
+
: {
|
|
878
|
+
status: "FAILED",
|
|
879
|
+
error: {
|
|
880
|
+
code: "BOSS_CHAT_CLI_EXECUTION_FAILED",
|
|
881
|
+
message: normalizeText(stderr || stdout) || `${command} 执行失败。`
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
runtimeLayout
|
|
885
|
+
)
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
});
|
|
889
|
+
}
|
|
641
890
|
|
|
642
|
-
export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
643
|
-
const cliDir = resolveBossChatCliDir(workspaceRoot);
|
|
644
|
-
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
645
|
-
const configResolution = resolveBossChatScreenConfig(workspaceRoot);
|
|
646
|
-
const
|
|
647
|
-
|
|
648
|
-
|
|
891
|
+
export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
892
|
+
const cliDir = resolveBossChatCliDir(workspaceRoot);
|
|
893
|
+
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
894
|
+
const configResolution = resolveBossChatScreenConfig(workspaceRoot);
|
|
895
|
+
const runtimeLayout = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
896
|
+
const resolvedPort = parsePositiveInteger(input.port)
|
|
897
|
+
|| (configResolution.ok ? configResolution.config.debugPort : 9222);
|
|
898
|
+
if (!cliDir || !cliPath) {
|
|
649
899
|
return {
|
|
650
900
|
status: "FAILED",
|
|
651
|
-
error: {
|
|
652
|
-
code: "BOSS_CHAT_CLI_MISSING",
|
|
653
|
-
message: "未找到 vendored boss-chat CLI。"
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
901
|
+
error: {
|
|
902
|
+
code: "BOSS_CHAT_CLI_MISSING",
|
|
903
|
+
message: "未找到 vendored boss-chat CLI。"
|
|
904
|
+
},
|
|
905
|
+
data_dir: runtimeLayout.data_dir,
|
|
906
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
907
|
+
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
908
|
+
migration_pending: runtimeLayout.migration_pending
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
if (!configResolution.ok) {
|
|
658
912
|
return {
|
|
659
913
|
status: "FAILED",
|
|
660
|
-
error: configResolution.error,
|
|
661
|
-
config_path: configResolution.config_path,
|
|
662
|
-
config_dir: configResolution.config_dir,
|
|
663
|
-
cli_dir: cliDir,
|
|
664
|
-
cli_path: cliPath
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
914
|
+
error: configResolution.error,
|
|
915
|
+
config_path: configResolution.config_path,
|
|
916
|
+
config_dir: configResolution.config_dir,
|
|
917
|
+
cli_dir: cliDir,
|
|
918
|
+
cli_path: cliPath,
|
|
919
|
+
data_dir: runtimeLayout.data_dir,
|
|
920
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
921
|
+
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
922
|
+
migration_pending: runtimeLayout.migration_pending
|
|
923
|
+
};
|
|
924
|
+
}
|
|
925
|
+
return {
|
|
668
926
|
status: "OK",
|
|
669
927
|
server: "boss-chat",
|
|
670
|
-
cli_dir: cliDir,
|
|
671
|
-
cli_path: cliPath,
|
|
672
|
-
config_path: configResolution.config_path,
|
|
673
|
-
debug_port: resolvedPort,
|
|
674
|
-
shared_llm_config: true
|
|
675
|
-
|
|
676
|
-
|
|
928
|
+
cli_dir: cliDir,
|
|
929
|
+
cli_path: cliPath,
|
|
930
|
+
config_path: configResolution.config_path,
|
|
931
|
+
debug_port: resolvedPort,
|
|
932
|
+
shared_llm_config: true,
|
|
933
|
+
data_dir: runtimeLayout.data_dir,
|
|
934
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
935
|
+
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
936
|
+
migration_source_dir: runtimeLayout.migration_source_dir,
|
|
937
|
+
migration_pending: runtimeLayout.migration_pending
|
|
938
|
+
};
|
|
939
|
+
}
|
|
677
940
|
|
|
678
941
|
export async function startBossChatRun({ workspaceRoot, input = {} }) {
|
|
679
942
|
const missingFields = getMissingBossChatStartFields(input);
|