@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.1
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 +86 -33
- package/package.json +62 -9
- package/skills/boss-chat/SKILL.md +5 -4
- package/skills/boss-recommend-pipeline/SKILL.md +21 -31
- package/skills/boss-recruit-pipeline/README.md +17 -0
- package/skills/boss-recruit-pipeline/SKILL.md +55 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1254 -225
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +66 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +67 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
package/src/boss-chat.js
DELETED
|
@@ -1,1037 +0,0 @@
|
|
|
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";
|
|
7
|
-
|
|
8
|
-
import { getScreenConfigResolution, resolveSharedLlmTransportConfig } from "./adapters.js";
|
|
9
|
-
|
|
10
|
-
const currentFilePath = fileURLToPath(import.meta.url);
|
|
11
|
-
const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
|
|
12
|
-
const VENDORED_BOSS_CHAT_DIR = path.join(packageRoot, "vendor", "boss-chat-cli");
|
|
13
|
-
const DEFAULT_BOSS_CHAT_POLL_MS = 1500;
|
|
14
|
-
const PREPARE_BOSS_CHAT_MAX_ATTEMPTS = 3;
|
|
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"];
|
|
23
|
-
const LLM_THINKING_LEVEL_FIELDS = [
|
|
24
|
-
"llmThinkingLevel",
|
|
25
|
-
"thinkingLevel",
|
|
26
|
-
"reasoningEffort",
|
|
27
|
-
"reasoning_effort"
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
function normalizeText(value) {
|
|
31
|
-
return String(value || "").replace(/\s+/g, " ").trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function pathExists(targetPath) {
|
|
35
|
-
try {
|
|
36
|
-
return fs.existsSync(targetPath);
|
|
37
|
-
} catch {
|
|
38
|
-
return false;
|
|
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
|
-
}
|
|
238
|
-
|
|
239
|
-
function parsePositiveInteger(value, fallback = null) {
|
|
240
|
-
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
241
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function parseBooleanValue(value) {
|
|
245
|
-
if (typeof value === "boolean") return value;
|
|
246
|
-
const normalized = normalizeText(value).toLowerCase();
|
|
247
|
-
if (!normalized) return null;
|
|
248
|
-
if (["1", "true", "yes", "y", "on", "是"].includes(normalized)) return true;
|
|
249
|
-
if (["0", "false", "no", "n", "off", "否"].includes(normalized)) return false;
|
|
250
|
-
return null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function resolveHumanRestEnabled(config = {}) {
|
|
254
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) return false;
|
|
255
|
-
const candidates = [
|
|
256
|
-
config.humanRestEnabled,
|
|
257
|
-
config.human_rest_enabled,
|
|
258
|
-
config.humanLikeRestEnabled,
|
|
259
|
-
config.human_like_rest_enabled
|
|
260
|
-
];
|
|
261
|
-
for (const candidate of candidates) {
|
|
262
|
-
const parsed = parseBooleanValue(candidate);
|
|
263
|
-
if (typeof parsed === "boolean") return parsed;
|
|
264
|
-
}
|
|
265
|
-
return false;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
function isUnlimitedTargetCountToken(value) {
|
|
269
|
-
const token = normalizeText(value).toLowerCase();
|
|
270
|
-
if (!token) return false;
|
|
271
|
-
const compact = token.replace(/\s+/g, "");
|
|
272
|
-
const withoutAnnotation = compact.replace(/[((【[].*?[))】\]]/gu, "");
|
|
273
|
-
const knownTokens = new Set([
|
|
274
|
-
"all",
|
|
275
|
-
"unlimited",
|
|
276
|
-
"infinity",
|
|
277
|
-
"inf",
|
|
278
|
-
"max",
|
|
279
|
-
"full",
|
|
280
|
-
"allcandidates",
|
|
281
|
-
"全部",
|
|
282
|
-
"全量",
|
|
283
|
-
"不限",
|
|
284
|
-
"扫到底",
|
|
285
|
-
"全部候选人",
|
|
286
|
-
"所有候选人",
|
|
287
|
-
"全部人选",
|
|
288
|
-
"所有人选",
|
|
289
|
-
"直到完成所有人选"
|
|
290
|
-
]);
|
|
291
|
-
if (knownTokens.has(token) || knownTokens.has(compact) || knownTokens.has(withoutAnnotation)) return true;
|
|
292
|
-
if (/^(?:all|unlimited|infinity|inf|max|full)(?:candidate|candidates)?$/i.test(compact)) return true;
|
|
293
|
-
if (/^(?:all|unlimited|infinity|inf|max|full)(?:候选人|人选|牛人|人才|人员)?$/iu.test(withoutAnnotation)) return true;
|
|
294
|
-
if (/^(?:全部|所有|全量|不限)(?:候选人|人选|牛人|人才|人员)?$/u.test(compact)) return true;
|
|
295
|
-
if (!/\d/.test(compact) && /(?:扫到底|全部候选人|所有候选人|全部人选|所有人选)/u.test(compact)) return true;
|
|
296
|
-
return false;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
function getWrappedTargetCountValue(value) {
|
|
300
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return value;
|
|
301
|
-
for (const key of TARGET_COUNT_WRAPPER_KEYS) {
|
|
302
|
-
if (Object.prototype.hasOwnProperty.call(value, key)) {
|
|
303
|
-
return value[key];
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
return value;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
export function getBossChatTargetCountValue(input = {}) {
|
|
310
|
-
if (!input || typeof input !== "object" || Array.isArray(input)) return undefined;
|
|
311
|
-
if (Object.prototype.hasOwnProperty.call(input, "target_count") && input.target_count !== undefined && input.target_count !== null) {
|
|
312
|
-
return input.target_count;
|
|
313
|
-
}
|
|
314
|
-
if (Object.prototype.hasOwnProperty.call(input, "targetCount") && input.targetCount !== undefined && input.targetCount !== null) {
|
|
315
|
-
return input.targetCount;
|
|
316
|
-
}
|
|
317
|
-
if (Object.prototype.hasOwnProperty.call(input, "target_count")) return input.target_count;
|
|
318
|
-
if (Object.prototype.hasOwnProperty.call(input, "targetCount")) return input.targetCount;
|
|
319
|
-
return undefined;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
function cloneForDiagnostics(value) {
|
|
323
|
-
if (value === undefined) return undefined;
|
|
324
|
-
if (value === null || ["string", "number", "boolean"].includes(typeof value)) return value;
|
|
325
|
-
try {
|
|
326
|
-
return JSON.parse(JSON.stringify(value));
|
|
327
|
-
} catch {
|
|
328
|
-
return String(value);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
export function buildTargetCountCompatibilityHints({
|
|
333
|
-
argumentName = "target_count",
|
|
334
|
-
recommendedArgumentPatch = { target_count: TARGET_COUNT_CANONICAL_ALL },
|
|
335
|
-
includeOptions = true
|
|
336
|
-
} = {}) {
|
|
337
|
-
const normalizedArgumentName = normalizeText(argumentName) || "target_count";
|
|
338
|
-
const clonedRecommendedPatch = cloneForDiagnostics(recommendedArgumentPatch)
|
|
339
|
-
|| { target_count: TARGET_COUNT_CANONICAL_ALL };
|
|
340
|
-
const literal = `${normalizedArgumentName}="${TARGET_COUNT_CANONICAL_ALL}"`;
|
|
341
|
-
const base = {
|
|
342
|
-
argument_name: normalizedArgumentName,
|
|
343
|
-
answer_format: `${normalizedArgumentName} = 正整数 | "${TARGET_COUNT_CANONICAL_ALL}"`,
|
|
344
|
-
canonical_unlimited_value: TARGET_COUNT_CANONICAL_ALL,
|
|
345
|
-
recommended_value: TARGET_COUNT_CANONICAL_ALL,
|
|
346
|
-
recommended_argument_patch: clonedRecommendedPatch,
|
|
347
|
-
accepted_examples: TARGET_COUNT_ACCEPTED_EXAMPLES.slice()
|
|
348
|
-
};
|
|
349
|
-
if (!includeOptions) return base;
|
|
350
|
-
return {
|
|
351
|
-
...base,
|
|
352
|
-
options: [
|
|
353
|
-
{
|
|
354
|
-
label: `扫到底(必须传 ${literal},推荐)`,
|
|
355
|
-
value: TARGET_COUNT_CANONICAL_ALL,
|
|
356
|
-
canonical_value: TARGET_COUNT_CANONICAL_ALL,
|
|
357
|
-
argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
label: `不限(等价于 ${literal})`,
|
|
361
|
-
value: "unlimited",
|
|
362
|
-
canonical_value: TARGET_COUNT_CANONICAL_ALL,
|
|
363
|
-
argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
|
|
364
|
-
},
|
|
365
|
-
{
|
|
366
|
-
label: `全部候选人(等价于 ${literal})`,
|
|
367
|
-
value: "全部候选人",
|
|
368
|
-
canonical_value: TARGET_COUNT_CANONICAL_ALL,
|
|
369
|
-
argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
|
|
370
|
-
},
|
|
371
|
-
{
|
|
372
|
-
label: `所有候选人(等价于 ${literal})`,
|
|
373
|
-
value: "所有候选人",
|
|
374
|
-
canonical_value: TARGET_COUNT_CANONICAL_ALL,
|
|
375
|
-
argument_patch: cloneForDiagnostics(clonedRecommendedPatch)
|
|
376
|
-
}
|
|
377
|
-
]
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export function normalizeTargetCountInput(value) {
|
|
382
|
-
if (value === undefined || value === null) {
|
|
383
|
-
return {
|
|
384
|
-
provided: false,
|
|
385
|
-
targetCount: null,
|
|
386
|
-
cliArg: null,
|
|
387
|
-
publicValue: null,
|
|
388
|
-
rawValue: value,
|
|
389
|
-
parseError: null
|
|
390
|
-
};
|
|
391
|
-
}
|
|
392
|
-
const unwrapped = getWrappedTargetCountValue(value);
|
|
393
|
-
if (unwrapped !== value) {
|
|
394
|
-
return normalizeTargetCountInput(unwrapped);
|
|
395
|
-
}
|
|
396
|
-
const raw = normalizeText(unwrapped);
|
|
397
|
-
if (!raw) {
|
|
398
|
-
return {
|
|
399
|
-
provided: false,
|
|
400
|
-
targetCount: null,
|
|
401
|
-
cliArg: null,
|
|
402
|
-
publicValue: null,
|
|
403
|
-
rawValue: value,
|
|
404
|
-
parseError: null
|
|
405
|
-
};
|
|
406
|
-
}
|
|
407
|
-
if (isUnlimitedTargetCountToken(raw)) {
|
|
408
|
-
return {
|
|
409
|
-
provided: true,
|
|
410
|
-
targetCount: null,
|
|
411
|
-
cliArg: "-1",
|
|
412
|
-
publicValue: "all",
|
|
413
|
-
rawValue: cloneForDiagnostics(value),
|
|
414
|
-
parseError: null
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
const parsed = Number.parseInt(String(raw), 10);
|
|
418
|
-
if (Number.isFinite(parsed) && parsed === -1) {
|
|
419
|
-
return {
|
|
420
|
-
provided: true,
|
|
421
|
-
targetCount: null,
|
|
422
|
-
cliArg: "-1",
|
|
423
|
-
publicValue: "all",
|
|
424
|
-
rawValue: cloneForDiagnostics(value),
|
|
425
|
-
parseError: null
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
if (Number.isFinite(parsed) && parsed > 0) {
|
|
429
|
-
return {
|
|
430
|
-
provided: true,
|
|
431
|
-
targetCount: parsed,
|
|
432
|
-
cliArg: String(parsed),
|
|
433
|
-
publicValue: parsed,
|
|
434
|
-
rawValue: cloneForDiagnostics(value),
|
|
435
|
-
parseError: null
|
|
436
|
-
};
|
|
437
|
-
}
|
|
438
|
-
return {
|
|
439
|
-
provided: false,
|
|
440
|
-
targetCount: null,
|
|
441
|
-
cliArg: null,
|
|
442
|
-
publicValue: null,
|
|
443
|
-
rawValue: cloneForDiagnostics(value),
|
|
444
|
-
parseError: "target_count must be a positive integer, -1, or one of: all, unlimited, 全部, 不限, 扫到底, 全量, 全部候选人, 所有候选人"
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
function parseJsonOutput(text) {
|
|
449
|
-
const trimmed = String(text || "").trim();
|
|
450
|
-
if (!trimmed) return null;
|
|
451
|
-
try {
|
|
452
|
-
return JSON.parse(trimmed);
|
|
453
|
-
} catch {}
|
|
454
|
-
const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
455
|
-
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
|
456
|
-
try {
|
|
457
|
-
return JSON.parse(lines[index]);
|
|
458
|
-
} catch {
|
|
459
|
-
continue;
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function sleep(ms) {
|
|
466
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
function resolveBossChatCliDir(workspaceRoot) {
|
|
470
|
-
const localDir = path.join(path.resolve(String(workspaceRoot || process.cwd())), "boss-chat-cli");
|
|
471
|
-
if (pathExists(localDir)) return localDir;
|
|
472
|
-
return pathExists(VENDORED_BOSS_CHAT_DIR) ? VENDORED_BOSS_CHAT_DIR : null;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
function resolveBossChatCliPath(workspaceRoot) {
|
|
476
|
-
const cliDir = resolveBossChatCliDir(workspaceRoot);
|
|
477
|
-
if (!cliDir) return null;
|
|
478
|
-
const cliPath = path.join(cliDir, "src", "cli.js");
|
|
479
|
-
return pathExists(cliPath) ? cliPath : null;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
function validateRecommendScreenConfig(config) {
|
|
483
|
-
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
|
484
|
-
return {
|
|
485
|
-
ok: false,
|
|
486
|
-
message: "screening-config.json 缺失或格式无效。请填写 baseUrl、apiKey、model。"
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
const baseUrl = normalizeText(config.baseUrl).replace(/\/+$/, "");
|
|
490
|
-
const apiKey = normalizeText(config.apiKey);
|
|
491
|
-
const model = normalizeText(config.model);
|
|
492
|
-
const missing = [];
|
|
493
|
-
if (!baseUrl) missing.push("baseUrl");
|
|
494
|
-
if (!apiKey) missing.push("apiKey");
|
|
495
|
-
if (!model) missing.push("model");
|
|
496
|
-
if (missing.length > 0) {
|
|
497
|
-
return {
|
|
498
|
-
ok: false,
|
|
499
|
-
message: `screening-config.json 缺少必填字段:${missing.join(", ")}。`
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
if (/^replace-with/i.test(apiKey)) {
|
|
503
|
-
return {
|
|
504
|
-
ok: false,
|
|
505
|
-
message: "screening-config.json 的 apiKey 仍是模板占位符,请填写真实 API Key。"
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
return { ok: true };
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function resolveLlmThinkingLevel(config = {}) {
|
|
512
|
-
if (!config || typeof config !== "object") return "";
|
|
513
|
-
for (const field of LLM_THINKING_LEVEL_FIELDS) {
|
|
514
|
-
const value = normalizeText(config[field]);
|
|
515
|
-
if (value) return value;
|
|
516
|
-
}
|
|
517
|
-
return "";
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
function resolveBossChatScreenConfig(workspaceRoot) {
|
|
521
|
-
const resolution = getScreenConfigResolution(workspaceRoot);
|
|
522
|
-
const configPath = resolution.resolved_path || resolution.writable_path || resolution.legacy_path || null;
|
|
523
|
-
if (!configPath || !pathExists(configPath)) {
|
|
524
|
-
return {
|
|
525
|
-
ok: false,
|
|
526
|
-
error: {
|
|
527
|
-
code: "SCREEN_CONFIG_ERROR",
|
|
528
|
-
message: `screening-config.json 不存在。请先完成 recommend 配置。${configPath ? ` (path: ${configPath})` : ""}`
|
|
529
|
-
},
|
|
530
|
-
config_path: configPath,
|
|
531
|
-
config_dir: configPath ? path.dirname(configPath) : null
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
let parsed = null;
|
|
535
|
-
try {
|
|
536
|
-
parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
|
537
|
-
} catch (error) {
|
|
538
|
-
return {
|
|
539
|
-
ok: false,
|
|
540
|
-
error: {
|
|
541
|
-
code: "SCREEN_CONFIG_ERROR",
|
|
542
|
-
message: `screening-config.json 解析失败:${error.message || "unknown error"} (path: ${configPath})`
|
|
543
|
-
},
|
|
544
|
-
config_path: configPath,
|
|
545
|
-
config_dir: path.dirname(configPath)
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
const validation = validateRecommendScreenConfig(parsed);
|
|
549
|
-
if (!validation.ok) {
|
|
550
|
-
return {
|
|
551
|
-
ok: false,
|
|
552
|
-
error: {
|
|
553
|
-
code: "SCREEN_CONFIG_ERROR",
|
|
554
|
-
message: `${validation.message} (path: ${configPath})`
|
|
555
|
-
},
|
|
556
|
-
config_path: configPath,
|
|
557
|
-
config_dir: path.dirname(configPath)
|
|
558
|
-
};
|
|
559
|
-
}
|
|
560
|
-
return {
|
|
561
|
-
ok: true,
|
|
562
|
-
config: {
|
|
563
|
-
baseUrl: normalizeText(parsed.baseUrl).replace(/\/+$/, ""),
|
|
564
|
-
apiKey: normalizeText(parsed.apiKey),
|
|
565
|
-
model: normalizeText(parsed.model),
|
|
566
|
-
llmThinkingLevel: resolveLlmThinkingLevel(parsed),
|
|
567
|
-
...resolveSharedLlmTransportConfig(parsed),
|
|
568
|
-
debugPort: parsePositiveInteger(parsed.debugPort, 9222),
|
|
569
|
-
humanRestEnabled: resolveHumanRestEnabled(parsed)
|
|
570
|
-
},
|
|
571
|
-
config_path: configPath,
|
|
572
|
-
config_dir: path.dirname(configPath)
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function normalizeBossChatStartInput(input = {}) {
|
|
577
|
-
const profile = normalizeText(input.profile) || "default";
|
|
578
|
-
const job = normalizeText(input.job);
|
|
579
|
-
const startFromRaw = normalizeText(input.startFrom || input.start_from).toLowerCase();
|
|
580
|
-
const startFrom = startFromRaw === "all" ? "all" : startFromRaw === "unread" ? "unread" : "";
|
|
581
|
-
const criteria = normalizeText(input.criteria);
|
|
582
|
-
const greetingText = normalizeText(input.greeting_text || input.greetingText || input.greeting);
|
|
583
|
-
const parsedTarget = normalizeTargetCountInput(getBossChatTargetCountValue(input));
|
|
584
|
-
const port = parsePositiveInteger(input.port);
|
|
585
|
-
return {
|
|
586
|
-
profile,
|
|
587
|
-
job,
|
|
588
|
-
startFrom,
|
|
589
|
-
criteria,
|
|
590
|
-
greetingText,
|
|
591
|
-
targetCount: parsedTarget.targetCount,
|
|
592
|
-
targetCountArg: parsedTarget.cliArg,
|
|
593
|
-
targetCountProvided: parsedTarget.provided,
|
|
594
|
-
targetCountPublicValue: parsedTarget.publicValue,
|
|
595
|
-
targetCountRawValue: parsedTarget.rawValue,
|
|
596
|
-
targetCountParseError: parsedTarget.parseError,
|
|
597
|
-
port,
|
|
598
|
-
dryRun: input.dryRun === true || input.dry_run === true,
|
|
599
|
-
noState: input.noState === true || input.no_state === true,
|
|
600
|
-
safePacing: typeof input.safePacing === "boolean" ? input.safePacing : (
|
|
601
|
-
typeof input.safe_pacing === "boolean" ? input.safe_pacing : undefined
|
|
602
|
-
),
|
|
603
|
-
batchRestEnabled: typeof input.batchRestEnabled === "boolean" ? input.batchRestEnabled : (
|
|
604
|
-
typeof input.batch_rest_enabled === "boolean" ? input.batch_rest_enabled : undefined
|
|
605
|
-
)
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
function normalizeBossChatRunId(input = {}) {
|
|
610
|
-
return normalizeText(input.runId || input.run_id);
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
function getMissingBossChatStartFields(input = {}) {
|
|
614
|
-
const normalized = normalizeBossChatStartInput(input);
|
|
615
|
-
const missing = [];
|
|
616
|
-
if (!normalized.job) missing.push("job");
|
|
617
|
-
if (!normalized.startFrom) missing.push("start_from");
|
|
618
|
-
if (!normalized.targetCountProvided) missing.push("target_count");
|
|
619
|
-
if (!normalized.criteria) missing.push("criteria");
|
|
620
|
-
return missing;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
function buildTargetCountQuestionHint(item = {}) {
|
|
624
|
-
const next = { ...item };
|
|
625
|
-
const hints = buildTargetCountCompatibilityHints({
|
|
626
|
-
argumentName: "target_count",
|
|
627
|
-
recommendedArgumentPatch: { target_count: TARGET_COUNT_CANONICAL_ALL }
|
|
628
|
-
});
|
|
629
|
-
return {
|
|
630
|
-
...next,
|
|
631
|
-
...hints,
|
|
632
|
-
question: `请输入 target_count:正整数,或直接填写 "${TARGET_COUNT_CANONICAL_ALL}"(扫到底)。`,
|
|
633
|
-
examples: TARGET_COUNT_ACCEPTED_EXAMPLES.slice()
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function normalizePendingQuestions(pendingQuestions = []) {
|
|
638
|
-
return pendingQuestions.map((item) => {
|
|
639
|
-
if (String(item?.field || "") !== "target_count") return item;
|
|
640
|
-
return buildTargetCountQuestionHint(item);
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
function buildNextCallExample(input = {}, missingFields = []) {
|
|
645
|
-
if (!Array.isArray(missingFields) || missingFields.length === 0) return null;
|
|
646
|
-
const normalized = normalizeBossChatStartInput(input);
|
|
647
|
-
const sample = {};
|
|
648
|
-
if (normalized.job) sample.job = normalized.job;
|
|
649
|
-
if (normalized.startFrom) sample.start_from = normalized.startFrom;
|
|
650
|
-
if (normalized.criteria) sample.criteria = normalized.criteria;
|
|
651
|
-
if (normalized.greetingText) sample.greeting_text = normalized.greetingText;
|
|
652
|
-
if (normalized.targetCountProvided) {
|
|
653
|
-
sample.target_count = normalized.targetCountPublicValue || (normalized.targetCountArg === "-1" ? "all" : normalized.targetCount);
|
|
654
|
-
} else if (missingFields.includes("target_count")) {
|
|
655
|
-
sample.target_count = "all";
|
|
656
|
-
}
|
|
657
|
-
return Object.keys(sample).length > 0 ? sample : null;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
function buildTargetCountNeedInputDiagnostics(input = {}, missingFields = []) {
|
|
661
|
-
if (!Array.isArray(missingFields) || !missingFields.includes("target_count")) return {};
|
|
662
|
-
const normalized = normalizeBossChatStartInput(input);
|
|
663
|
-
const hints = buildTargetCountCompatibilityHints({
|
|
664
|
-
argumentName: "target_count",
|
|
665
|
-
recommendedArgumentPatch: { target_count: TARGET_COUNT_CANONICAL_ALL },
|
|
666
|
-
includeOptions: false
|
|
667
|
-
});
|
|
668
|
-
return {
|
|
669
|
-
...hints,
|
|
670
|
-
...(normalized.targetCountRawValue !== undefined ? { received_target_count: normalized.targetCountRawValue } : {}),
|
|
671
|
-
...(normalized.targetCountParseError ? { target_count_parse_error: normalized.targetCountParseError } : {})
|
|
672
|
-
};
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = null) {
|
|
676
|
-
const args = [command, "--json"];
|
|
677
|
-
if (runtimeLayout?.data_dir) {
|
|
678
|
-
args.push("--data-dir", runtimeLayout.data_dir);
|
|
679
|
-
}
|
|
680
|
-
if (command === "prepare-run") {
|
|
681
|
-
const normalized = normalizeBossChatStartInput(input);
|
|
682
|
-
args.push("--profile", normalized.profile);
|
|
683
|
-
if (normalized.job) args.push("--job", normalized.job);
|
|
684
|
-
if (normalized.startFrom) args.push("--start-from", normalized.startFrom);
|
|
685
|
-
if (normalized.criteria) args.push("--criteria", normalized.criteria);
|
|
686
|
-
if (normalized.greetingText) args.push("--greeting", normalized.greetingText);
|
|
687
|
-
if (normalized.targetCountArg) args.push("--targetCount", normalized.targetCountArg);
|
|
688
|
-
args.push("--port", String(normalized.port || resolvedConfig.debugPort || 9222));
|
|
689
|
-
args.push("--baseurl", resolvedConfig.baseUrl);
|
|
690
|
-
args.push("--apikey", resolvedConfig.apiKey);
|
|
691
|
-
args.push("--model", resolvedConfig.model);
|
|
692
|
-
if (resolvedConfig.llmThinkingLevel) {
|
|
693
|
-
args.push("--thinking-level", resolvedConfig.llmThinkingLevel);
|
|
694
|
-
}
|
|
695
|
-
if (resolvedConfig.llmTimeoutMs) {
|
|
696
|
-
args.push("--llm-timeout-ms", String(resolvedConfig.llmTimeoutMs));
|
|
697
|
-
}
|
|
698
|
-
if (resolvedConfig.llmMaxRetries) {
|
|
699
|
-
args.push("--llm-max-retries", String(resolvedConfig.llmMaxRetries));
|
|
700
|
-
}
|
|
701
|
-
return args;
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
if (command === "start-run") {
|
|
705
|
-
const normalized = normalizeBossChatStartInput(input);
|
|
706
|
-
args.push("--profile", normalized.profile);
|
|
707
|
-
if (normalized.dryRun) args.push("--dry-run");
|
|
708
|
-
if (normalized.noState) args.push("--no-state");
|
|
709
|
-
args.push("--job", normalized.job);
|
|
710
|
-
args.push("--start-from", normalized.startFrom);
|
|
711
|
-
args.push("--criteria", normalized.criteria);
|
|
712
|
-
if (normalized.greetingText) args.push("--greeting", normalized.greetingText);
|
|
713
|
-
if (normalized.targetCountArg) {
|
|
714
|
-
args.push("--targetCount", normalized.targetCountArg);
|
|
715
|
-
}
|
|
716
|
-
args.push("--baseurl", resolvedConfig.baseUrl);
|
|
717
|
-
args.push("--apikey", resolvedConfig.apiKey);
|
|
718
|
-
args.push("--model", resolvedConfig.model);
|
|
719
|
-
if (resolvedConfig.llmThinkingLevel) {
|
|
720
|
-
args.push("--thinking-level", resolvedConfig.llmThinkingLevel);
|
|
721
|
-
}
|
|
722
|
-
if (resolvedConfig.llmTimeoutMs) {
|
|
723
|
-
args.push("--llm-timeout-ms", String(resolvedConfig.llmTimeoutMs));
|
|
724
|
-
}
|
|
725
|
-
if (resolvedConfig.llmMaxRetries) {
|
|
726
|
-
args.push("--llm-max-retries", String(resolvedConfig.llmMaxRetries));
|
|
727
|
-
}
|
|
728
|
-
args.push("--port", String(normalized.port || resolvedConfig.debugPort || 9222));
|
|
729
|
-
if (typeof normalized.safePacing === "boolean") {
|
|
730
|
-
args.push("--safe-pacing", String(normalized.safePacing));
|
|
731
|
-
}
|
|
732
|
-
if (typeof normalized.batchRestEnabled === "boolean") {
|
|
733
|
-
args.push("--batch-rest", String(normalized.batchRestEnabled));
|
|
734
|
-
} else if (typeof resolvedConfig?.humanRestEnabled === "boolean") {
|
|
735
|
-
args.push("--batch-rest", String(resolvedConfig.humanRestEnabled));
|
|
736
|
-
}
|
|
737
|
-
return args;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
const runId = normalizeBossChatRunId(input);
|
|
741
|
-
args.push("--profile", normalizeText(input.profile) || "default");
|
|
742
|
-
args.push("--run-id", runId);
|
|
743
|
-
return args;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
function withRuntimeDiagnostics(payload, runtimeLayout) {
|
|
747
|
-
if (!payload || typeof payload !== "object") return payload;
|
|
748
|
-
return {
|
|
749
|
-
...payload,
|
|
750
|
-
data_dir: runtimeLayout?.data_dir || null,
|
|
751
|
-
data_dir_source: runtimeLayout?.data_dir_source || null
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
756
|
-
const runtimeLayout = ensureBossChatRuntimeReady(workspaceRoot);
|
|
757
|
-
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
758
|
-
if (!cliPath) {
|
|
759
|
-
return {
|
|
760
|
-
ok: false,
|
|
761
|
-
exitCode: -1,
|
|
762
|
-
stdout: "",
|
|
763
|
-
stderr: "",
|
|
764
|
-
payload: {
|
|
765
|
-
status: "FAILED",
|
|
766
|
-
error: {
|
|
767
|
-
code: "BOSS_CHAT_CLI_MISSING",
|
|
768
|
-
message: "未找到 vendored boss-chat CLI。"
|
|
769
|
-
},
|
|
770
|
-
data_dir: runtimeLayout?.data_dir || null,
|
|
771
|
-
data_dir_source: runtimeLayout?.data_dir_source || null
|
|
772
|
-
}
|
|
773
|
-
};
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
const runtimeInitFailed = runtimeLayout.failed.some((item) => item.path === runtimeLayout.data_dir)
|
|
777
|
-
&& !pathExists(runtimeLayout.data_dir);
|
|
778
|
-
if (runtimeInitFailed) {
|
|
779
|
-
return {
|
|
780
|
-
ok: false,
|
|
781
|
-
exitCode: 1,
|
|
782
|
-
stdout: "",
|
|
783
|
-
stderr: "",
|
|
784
|
-
payload: {
|
|
785
|
-
status: "FAILED",
|
|
786
|
-
error: {
|
|
787
|
-
code: "BOSS_CHAT_RUNTIME_INIT_FAILED",
|
|
788
|
-
message: runtimeLayout.failed
|
|
789
|
-
.filter((item) => item.path === runtimeLayout.data_dir)
|
|
790
|
-
.map((item) => item.message)
|
|
791
|
-
.join("; ") || "无法初始化 boss-chat runtime 目录。"
|
|
792
|
-
},
|
|
793
|
-
data_dir: runtimeLayout.data_dir,
|
|
794
|
-
data_dir_source: runtimeLayout.data_dir_source,
|
|
795
|
-
migration: runtimeLayout.migration
|
|
796
|
-
}
|
|
797
|
-
};
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
let configResolution = null;
|
|
801
|
-
if (command === "start-run" || command === "prepare-run") {
|
|
802
|
-
configResolution = resolveBossChatScreenConfig(workspaceRoot);
|
|
803
|
-
if (!configResolution.ok) {
|
|
804
|
-
return {
|
|
805
|
-
ok: false,
|
|
806
|
-
exitCode: 1,
|
|
807
|
-
stdout: "",
|
|
808
|
-
stderr: "",
|
|
809
|
-
payload: {
|
|
810
|
-
status: "FAILED",
|
|
811
|
-
error: configResolution.error,
|
|
812
|
-
config_path: configResolution.config_path,
|
|
813
|
-
config_dir: configResolution.config_dir,
|
|
814
|
-
data_dir: runtimeLayout.data_dir,
|
|
815
|
-
data_dir_source: runtimeLayout.data_dir_source
|
|
816
|
-
}
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
const args = [cliPath, ...buildBossChatCliArgs(command, input, configResolution?.config || {}, runtimeLayout)];
|
|
822
|
-
const cwd = path.resolve(String(workspaceRoot || process.cwd()));
|
|
823
|
-
return new Promise((resolve) => {
|
|
824
|
-
const child = spawn(process.execPath, args, {
|
|
825
|
-
cwd,
|
|
826
|
-
env: {
|
|
827
|
-
...process.env,
|
|
828
|
-
BOSS_CHAT_HOME: runtimeLayout.data_dir
|
|
829
|
-
},
|
|
830
|
-
windowsHide: true,
|
|
831
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
832
|
-
});
|
|
833
|
-
|
|
834
|
-
let stdout = "";
|
|
835
|
-
let stderr = "";
|
|
836
|
-
child.stdout.on("data", (chunk) => {
|
|
837
|
-
stdout += String(chunk);
|
|
838
|
-
});
|
|
839
|
-
child.stderr.on("data", (chunk) => {
|
|
840
|
-
stderr += String(chunk);
|
|
841
|
-
});
|
|
842
|
-
child.on("error", (error) => {
|
|
843
|
-
resolve({
|
|
844
|
-
ok: false,
|
|
845
|
-
exitCode: -1,
|
|
846
|
-
stdout,
|
|
847
|
-
stderr,
|
|
848
|
-
payload: {
|
|
849
|
-
status: "FAILED",
|
|
850
|
-
error: {
|
|
851
|
-
code: "BOSS_CHAT_CLI_SPAWN_FAILED",
|
|
852
|
-
message: error?.message || "无法启动 vendored boss-chat CLI。"
|
|
853
|
-
},
|
|
854
|
-
data_dir: runtimeLayout.data_dir,
|
|
855
|
-
data_dir_source: runtimeLayout.data_dir_source
|
|
856
|
-
}
|
|
857
|
-
});
|
|
858
|
-
});
|
|
859
|
-
child.on("close", (code) => {
|
|
860
|
-
const parsed = parseJsonOutput(stdout) || parseJsonOutput(stderr);
|
|
861
|
-
if (parsed && typeof parsed === "object") {
|
|
862
|
-
resolve({
|
|
863
|
-
ok: Number(code) === 0 && String(parsed.status || "").toUpperCase() !== "FAILED",
|
|
864
|
-
exitCode: Number.isInteger(code) ? code : 1,
|
|
865
|
-
stdout,
|
|
866
|
-
stderr,
|
|
867
|
-
payload: withRuntimeDiagnostics(parsed, runtimeLayout)
|
|
868
|
-
});
|
|
869
|
-
return;
|
|
870
|
-
}
|
|
871
|
-
resolve({
|
|
872
|
-
ok: Number(code) === 0,
|
|
873
|
-
exitCode: Number.isInteger(code) ? code : 1,
|
|
874
|
-
stdout,
|
|
875
|
-
stderr,
|
|
876
|
-
payload: withRuntimeDiagnostics(
|
|
877
|
-
Number(code) === 0
|
|
878
|
-
? {
|
|
879
|
-
status: "OK",
|
|
880
|
-
message: normalizeText(stdout) || `${command} 执行成功。`
|
|
881
|
-
}
|
|
882
|
-
: {
|
|
883
|
-
status: "FAILED",
|
|
884
|
-
error: {
|
|
885
|
-
code: "BOSS_CHAT_CLI_EXECUTION_FAILED",
|
|
886
|
-
message: normalizeText(stderr || stdout) || `${command} 执行失败。`
|
|
887
|
-
}
|
|
888
|
-
},
|
|
889
|
-
runtimeLayout
|
|
890
|
-
)
|
|
891
|
-
});
|
|
892
|
-
});
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
897
|
-
const cliDir = resolveBossChatCliDir(workspaceRoot);
|
|
898
|
-
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
899
|
-
const configResolution = resolveBossChatScreenConfig(workspaceRoot);
|
|
900
|
-
const runtimeLayout = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
901
|
-
const resolvedPort = parsePositiveInteger(input.port)
|
|
902
|
-
|| (configResolution.ok ? configResolution.config.debugPort : 9222);
|
|
903
|
-
if (!cliDir || !cliPath) {
|
|
904
|
-
return {
|
|
905
|
-
status: "FAILED",
|
|
906
|
-
error: {
|
|
907
|
-
code: "BOSS_CHAT_CLI_MISSING",
|
|
908
|
-
message: "未找到 vendored boss-chat CLI。"
|
|
909
|
-
},
|
|
910
|
-
data_dir: runtimeLayout.data_dir,
|
|
911
|
-
data_dir_source: runtimeLayout.data_dir_source,
|
|
912
|
-
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
913
|
-
migration_pending: runtimeLayout.migration_pending
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
if (!configResolution.ok) {
|
|
917
|
-
return {
|
|
918
|
-
status: "FAILED",
|
|
919
|
-
error: configResolution.error,
|
|
920
|
-
config_path: configResolution.config_path,
|
|
921
|
-
config_dir: configResolution.config_dir,
|
|
922
|
-
cli_dir: cliDir,
|
|
923
|
-
cli_path: cliPath,
|
|
924
|
-
data_dir: runtimeLayout.data_dir,
|
|
925
|
-
data_dir_source: runtimeLayout.data_dir_source,
|
|
926
|
-
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
927
|
-
migration_pending: runtimeLayout.migration_pending
|
|
928
|
-
};
|
|
929
|
-
}
|
|
930
|
-
return {
|
|
931
|
-
status: "OK",
|
|
932
|
-
server: "boss-chat",
|
|
933
|
-
cli_dir: cliDir,
|
|
934
|
-
cli_path: cliPath,
|
|
935
|
-
config_path: configResolution.config_path,
|
|
936
|
-
debug_port: resolvedPort,
|
|
937
|
-
shared_llm_config: true,
|
|
938
|
-
data_dir: runtimeLayout.data_dir,
|
|
939
|
-
data_dir_source: runtimeLayout.data_dir_source,
|
|
940
|
-
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
941
|
-
migration_source_dir: runtimeLayout.migration_source_dir,
|
|
942
|
-
migration_pending: runtimeLayout.migration_pending
|
|
943
|
-
};
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
export async function startBossChatRun({ workspaceRoot, input = {} }) {
|
|
947
|
-
const missingFields = getMissingBossChatStartFields(input);
|
|
948
|
-
if (missingFields.length > 0) {
|
|
949
|
-
const prepared = await prepareBossChatRun({ workspaceRoot, input });
|
|
950
|
-
if (prepared?.status === "FAILED") return prepared;
|
|
951
|
-
const pendingQuestions = Array.isArray(prepared?.pending_questions)
|
|
952
|
-
? prepared.pending_questions.filter((item) => missingFields.includes(String(item?.field || "")))
|
|
953
|
-
: [];
|
|
954
|
-
const normalizedPendingQuestions = normalizePendingQuestions(pendingQuestions);
|
|
955
|
-
const nextCallExample = buildNextCallExample(input, missingFields);
|
|
956
|
-
const targetCountDiagnostics = buildTargetCountNeedInputDiagnostics(input, missingFields);
|
|
957
|
-
return {
|
|
958
|
-
...prepared,
|
|
959
|
-
status: "NEED_INPUT",
|
|
960
|
-
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
961
|
-
missing_fields: missingFields,
|
|
962
|
-
pending_questions: normalizedPendingQuestions,
|
|
963
|
-
...targetCountDiagnostics,
|
|
964
|
-
...(nextCallExample ? { next_call_example: nextCallExample } : {}),
|
|
965
|
-
message: prepared?.message
|
|
966
|
-
|| "已获取 Boss 聊天页岗位列表,请先补齐 job / start_from / target_count / criteria。"
|
|
967
|
-
};
|
|
968
|
-
}
|
|
969
|
-
return (await spawnBossChatCli({ workspaceRoot, command: "start-run", input })).payload;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
export async function prepareBossChatRun({ workspaceRoot, input = {} }) {
|
|
973
|
-
let payload = null;
|
|
974
|
-
for (let attempt = 1; attempt <= PREPARE_BOSS_CHAT_MAX_ATTEMPTS; attempt += 1) {
|
|
975
|
-
payload = (await spawnBossChatCli({ workspaceRoot, command: "prepare-run", input })).payload;
|
|
976
|
-
if (payload?.status !== "FAILED") break;
|
|
977
|
-
if (attempt >= PREPARE_BOSS_CHAT_MAX_ATTEMPTS) break;
|
|
978
|
-
await sleep(PREPARE_BOSS_CHAT_RETRY_DELAY_MS);
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
if (payload?.status !== "NEED_INPUT") return payload;
|
|
982
|
-
|
|
983
|
-
const missingFields = getMissingBossChatStartFields(input);
|
|
984
|
-
const pendingQuestions = Array.isArray(payload?.pending_questions)
|
|
985
|
-
? payload.pending_questions.filter((item) => (
|
|
986
|
-
missingFields.length === 0 || missingFields.includes(String(item?.field || ""))
|
|
987
|
-
))
|
|
988
|
-
: [];
|
|
989
|
-
const nextCallExample = buildNextCallExample(input, missingFields);
|
|
990
|
-
const targetCountDiagnostics = buildTargetCountNeedInputDiagnostics(input, missingFields);
|
|
991
|
-
return {
|
|
992
|
-
...payload,
|
|
993
|
-
required_fields: CHAT_REQUIRED_FIELDS.slice(),
|
|
994
|
-
missing_fields: missingFields,
|
|
995
|
-
pending_questions: normalizePendingQuestions(pendingQuestions),
|
|
996
|
-
...targetCountDiagnostics,
|
|
997
|
-
...(nextCallExample ? { next_call_example: nextCallExample } : {})
|
|
998
|
-
};
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
export async function getBossChatRun({ workspaceRoot, input = {} }) {
|
|
1002
|
-
return (await spawnBossChatCli({ workspaceRoot, command: "get-run", input })).payload;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
export async function pauseBossChatRun({ workspaceRoot, input = {} }) {
|
|
1006
|
-
return (await spawnBossChatCli({ workspaceRoot, command: "pause-run", input })).payload;
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
export async function resumeBossChatRun({ workspaceRoot, input = {} }) {
|
|
1010
|
-
return (await spawnBossChatCli({ workspaceRoot, command: "resume-run", input })).payload;
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
export async function cancelBossChatRun({ workspaceRoot, input = {} }) {
|
|
1014
|
-
return (await spawnBossChatCli({ workspaceRoot, command: "cancel-run", input })).payload;
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
export async function runBossChatSync({ workspaceRoot, input = {}, pollMs = DEFAULT_BOSS_CHAT_POLL_MS }) {
|
|
1018
|
-
const accepted = await startBossChatRun({ workspaceRoot, input });
|
|
1019
|
-
if (accepted?.status !== "ACCEPTED" || !normalizeText(accepted.run_id)) {
|
|
1020
|
-
return accepted;
|
|
1021
|
-
}
|
|
1022
|
-
const runId = normalizeText(accepted.run_id);
|
|
1023
|
-
while (true) {
|
|
1024
|
-
await sleep(pollMs);
|
|
1025
|
-
const statusPayload = await getBossChatRun({
|
|
1026
|
-
workspaceRoot,
|
|
1027
|
-
input: {
|
|
1028
|
-
profile: input.profile,
|
|
1029
|
-
runId
|
|
1030
|
-
}
|
|
1031
|
-
});
|
|
1032
|
-
const runState = normalizeText(statusPayload?.run?.state).toLowerCase();
|
|
1033
|
-
if (BOSS_CHAT_TERMINAL_STATES.has(runState)) {
|
|
1034
|
-
return statusPayload;
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
|
-
}
|