@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/cli.js
CHANGED
|
@@ -2,54 +2,64 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import process from "node:process";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
5
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { fileURLToPath } from "node:url";
|
|
8
|
-
import { startServer } from "./index.js";
|
|
9
8
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
assertNoForbiddenCdpCalls,
|
|
10
|
+
bringPageToFront,
|
|
11
|
+
connectToChromeTarget,
|
|
12
|
+
enableDomains,
|
|
13
|
+
getDocumentRoot,
|
|
14
|
+
querySelector,
|
|
15
|
+
sleep as sleepMs
|
|
16
|
+
} from "./core/browser/index.js";
|
|
17
|
+
import {
|
|
18
|
+
bossChatHealthCheckTool,
|
|
19
|
+
cancelBossChatRunTool,
|
|
20
|
+
getBossChatRunTool,
|
|
21
|
+
pauseBossChatRunTool,
|
|
22
|
+
prepareBossChatRunTool,
|
|
23
|
+
resumeBossChatRunTool
|
|
24
|
+
} from "./chat-mcp.js";
|
|
25
|
+
import { listRecommendJobsTool } from "./recommend-mcp.js";
|
|
19
26
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
prepareBossChatRun,
|
|
26
|
-
resolveBossChatRuntimeLayout,
|
|
27
|
-
resumeBossChatRun,
|
|
28
|
-
startBossChatRun
|
|
29
|
-
} from "./boss-chat.js";
|
|
30
|
-
import { runRecommendPipeline } from "./pipeline.js";
|
|
27
|
+
getBossScreenConfigResolution,
|
|
28
|
+
resolveBossChatRuntimeLayout as resolveCdpBossChatRuntimeLayout,
|
|
29
|
+
resolveBossScreeningConfig
|
|
30
|
+
} from "./chat-runtime-config.js";
|
|
31
|
+
import { startServer } from "./index.js";
|
|
31
32
|
|
|
32
33
|
const require = createRequire(import.meta.url);
|
|
33
34
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
34
35
|
const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
|
|
35
36
|
const packageJsonPath = path.join(packageRoot, "package.json");
|
|
36
37
|
const skillName = "boss-recommend-pipeline";
|
|
37
|
-
const
|
|
38
|
+
const recruitSkillName = "boss-recruit-pipeline";
|
|
39
|
+
const chatSkillName = "boss-chat";
|
|
40
|
+
const bundledSkillNames = [skillName, recruitSkillName, chatSkillName];
|
|
38
41
|
const exampleConfigPath = path.join(packageRoot, "config", "screening-config.example.json");
|
|
39
42
|
const bossUrl = "https://www.zhipin.com/web/chat/recommend";
|
|
43
|
+
const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
|
|
40
44
|
const chromeOnboardingUrlPattern = /^chrome:\/\/(welcome|intro|newtab|signin|history-sync|settings\/syncSetup)/i;
|
|
45
|
+
const bossLoginUrlPattern = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com)/i;
|
|
46
|
+
const bossLoginTitlePattern = /登录|signin|扫码登录|BOSS直聘登录/i;
|
|
41
47
|
const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw"];
|
|
42
48
|
const defaultMcpServerName = "boss-recommend";
|
|
43
49
|
const defaultMcpCommand = "npx";
|
|
44
50
|
const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
|
|
45
51
|
const recommendMcpBinaryName = "boss-recommend-mcp";
|
|
46
|
-
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h"]);
|
|
52
|
+
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
|
|
47
53
|
const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
48
54
|
const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
|
|
49
55
|
const installConfigDefaults = Object.freeze({
|
|
50
56
|
llmThinkingLevel: "low",
|
|
51
57
|
humanRestEnabled: false
|
|
52
58
|
});
|
|
59
|
+
const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
60
|
+
const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
|
|
61
|
+
const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
|
|
62
|
+
const recommendCliRunUnsupportedCode = "RECOMMEND_CLI_RUN_UNSUPPORTED_CDP_ONLY";
|
|
53
63
|
|
|
54
64
|
function getSkillSourceDir(name = skillName) {
|
|
55
65
|
return path.join(packageRoot, "skills", name);
|
|
@@ -113,6 +123,125 @@ function pathExists(targetPath) {
|
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
125
|
|
|
126
|
+
function normalizeText(value) {
|
|
127
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isUnsafeRuntimeDirectory(targetPath) {
|
|
131
|
+
const resolved = path.resolve(String(targetPath || ""));
|
|
132
|
+
if (!resolved) return true;
|
|
133
|
+
if (path.parse(resolved).root.toLowerCase() === resolved.toLowerCase()) return true;
|
|
134
|
+
const normalized = resolved.replace(/\\/g, "/").toLowerCase();
|
|
135
|
+
if (process.platform === "win32") {
|
|
136
|
+
return (
|
|
137
|
+
normalized.endsWith("/windows")
|
|
138
|
+
|| normalized.endsWith("/windows/system32")
|
|
139
|
+
|| normalized.endsWith("/windows/syswow64")
|
|
140
|
+
|| normalized.endsWith("/program files")
|
|
141
|
+
|| normalized.endsWith("/program files (x86)")
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return ["/system", "/usr", "/bin", "/sbin"].some((prefix) => (
|
|
145
|
+
normalized === prefix || normalized.startsWith(`${prefix}/`)
|
|
146
|
+
));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getBossChatRuntimeDirectories(runtime) {
|
|
150
|
+
return [
|
|
151
|
+
runtime.data_dir,
|
|
152
|
+
...bossChatRuntimeChildDirs.map((name) => path.join(runtime.data_dir, name))
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function ensureBossChatRuntimeReadyLocal(workspaceRoot) {
|
|
157
|
+
const runtime = resolveCdpBossChatRuntimeLayout(workspaceRoot);
|
|
158
|
+
const runtimeDirectories = getBossChatRuntimeDirectories(runtime);
|
|
159
|
+
const created = [];
|
|
160
|
+
const existed = [];
|
|
161
|
+
const failed = [];
|
|
162
|
+
let migration = {
|
|
163
|
+
attempted: false,
|
|
164
|
+
performed: false,
|
|
165
|
+
source: runtime.migration_source_dir,
|
|
166
|
+
target: runtime.data_dir,
|
|
167
|
+
message: runtime.migration_source_dir
|
|
168
|
+
? `Pending legacy boss-chat migration from ${runtime.migration_source_dir}`
|
|
169
|
+
: ""
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (isUnsafeRuntimeDirectory(runtime.data_dir)) {
|
|
173
|
+
return {
|
|
174
|
+
...runtime,
|
|
175
|
+
directories: runtimeDirectories,
|
|
176
|
+
created,
|
|
177
|
+
existed,
|
|
178
|
+
failed: [
|
|
179
|
+
{
|
|
180
|
+
path: runtime.data_dir,
|
|
181
|
+
message: `Refusing unsafe boss-chat runtime path: ${runtime.data_dir}. Please use BOSS_CHAT_HOME in a writable user directory.`
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
migration,
|
|
185
|
+
blocked_reason: "UNSAFE_DATA_DIR"
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (runtime.migration_source_dir) {
|
|
190
|
+
try {
|
|
191
|
+
fs.cpSync(runtime.migration_source_dir, runtime.data_dir, {
|
|
192
|
+
recursive: true,
|
|
193
|
+
force: false,
|
|
194
|
+
errorOnExist: false
|
|
195
|
+
});
|
|
196
|
+
migration = {
|
|
197
|
+
attempted: true,
|
|
198
|
+
performed: true,
|
|
199
|
+
source: runtime.migration_source_dir,
|
|
200
|
+
target: runtime.data_dir,
|
|
201
|
+
message: `Migrated legacy boss-chat runtime from ${runtime.migration_source_dir} to ${runtime.data_dir}. Legacy source was preserved.`
|
|
202
|
+
};
|
|
203
|
+
} catch (error) {
|
|
204
|
+
migration = {
|
|
205
|
+
attempted: true,
|
|
206
|
+
performed: false,
|
|
207
|
+
source: runtime.migration_source_dir,
|
|
208
|
+
target: runtime.data_dir,
|
|
209
|
+
message: error?.message || "Legacy boss-chat migration failed."
|
|
210
|
+
};
|
|
211
|
+
failed.push({
|
|
212
|
+
path: runtime.data_dir,
|
|
213
|
+
message: `Legacy migration failed: ${migration.message}`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
for (const directory of runtimeDirectories) {
|
|
219
|
+
try {
|
|
220
|
+
const existedBefore = pathExists(directory);
|
|
221
|
+
ensureDir(directory);
|
|
222
|
+
if (existedBefore) {
|
|
223
|
+
existed.push(directory);
|
|
224
|
+
} else {
|
|
225
|
+
created.push(directory);
|
|
226
|
+
}
|
|
227
|
+
} catch (error) {
|
|
228
|
+
failed.push({
|
|
229
|
+
path: directory,
|
|
230
|
+
message: error?.message || String(error)
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
...runtime,
|
|
237
|
+
directories: runtimeDirectories,
|
|
238
|
+
created,
|
|
239
|
+
existed,
|
|
240
|
+
failed,
|
|
241
|
+
migration
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
116
245
|
function readJsonObjectFileSafe(filePath) {
|
|
117
246
|
if (!pathExists(filePath)) return {};
|
|
118
247
|
try {
|
|
@@ -178,6 +307,235 @@ function getLegacyUserConfigPath() {
|
|
|
178
307
|
return path.join(getCodexHome(), "boss-recommend-mcp", "screening-config.json");
|
|
179
308
|
}
|
|
180
309
|
|
|
310
|
+
function getUserCalibrationPath() {
|
|
311
|
+
return path.join(getCodexHome(), "boss-recommend-mcp", "favorite-calibration.json");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function isUsableCalibrationFile(filePath) {
|
|
315
|
+
if (!filePath || !pathExists(filePath)) return false;
|
|
316
|
+
const parsed = readJsonObjectFileSafe(filePath);
|
|
317
|
+
return Boolean(
|
|
318
|
+
parsed
|
|
319
|
+
&& parsed.favoritePosition
|
|
320
|
+
&& Number.isFinite(parsed.favoritePosition.pageX)
|
|
321
|
+
&& Number.isFinite(parsed.favoritePosition.pageY)
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function resolveFavoriteCalibrationPath(workspaceRoot) {
|
|
326
|
+
const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_CALIBRATION_FILE || "");
|
|
327
|
+
if (fromEnv) return path.resolve(fromEnv);
|
|
328
|
+
|
|
329
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
330
|
+
const screenConfigPath = configResolution.config_path || getUserConfigPath();
|
|
331
|
+
const screenConfig = readJsonObjectFileSafe(screenConfigPath);
|
|
332
|
+
const calibrationFile = normalizeText(screenConfig?.calibrationFile || "");
|
|
333
|
+
if (calibrationFile && screenConfigPath) {
|
|
334
|
+
return path.resolve(path.dirname(screenConfigPath), calibrationFile);
|
|
335
|
+
}
|
|
336
|
+
return getUserCalibrationPath();
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function resolveRecruitCalibrationScriptPath(workspaceRoot) {
|
|
340
|
+
const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_RECRUIT_CALIBRATION_SCRIPT || "");
|
|
341
|
+
const workspaceResolved = path.resolve(String(workspaceRoot || process.cwd()));
|
|
342
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
343
|
+
const candidates = [
|
|
344
|
+
fromEnv,
|
|
345
|
+
path.join(workspaceResolved, "..", "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
346
|
+
path.join(packageRoot, "..", "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
347
|
+
path.join(appData, "npm", "node_modules", "@reconcrap", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
348
|
+
path.join(workspaceResolved, "..", "boss-recruit-mcp-main", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
349
|
+
path.join(packageRoot, "..", "boss-recruit-mcp-main", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs")
|
|
350
|
+
].filter(Boolean).map((item) => path.resolve(item));
|
|
351
|
+
|
|
352
|
+
for (const candidate of new Set(candidates)) {
|
|
353
|
+
if (pathExists(candidate)) return candidate;
|
|
354
|
+
}
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getFeaturedCalibrationResolutionLocal(workspaceRoot) {
|
|
359
|
+
const calibrationPath = resolveFavoriteCalibrationPath(workspaceRoot);
|
|
360
|
+
return {
|
|
361
|
+
calibration_path: calibrationPath,
|
|
362
|
+
calibration_exists: pathExists(calibrationPath),
|
|
363
|
+
calibration_usable: isUsableCalibrationFile(calibrationPath),
|
|
364
|
+
calibration_script_path: resolveRecruitCalibrationScriptPath(workspaceRoot)
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
function runProcessSyncLocal({ command, args = [], cwd = process.cwd() } = {}) {
|
|
369
|
+
try {
|
|
370
|
+
const result = spawnSync(command, args, {
|
|
371
|
+
cwd,
|
|
372
|
+
encoding: "utf8",
|
|
373
|
+
env: process.env,
|
|
374
|
+
shell: false,
|
|
375
|
+
windowsHide: true
|
|
376
|
+
});
|
|
377
|
+
const stdout = String(result.stdout || "").trim();
|
|
378
|
+
const stderr = String(result.stderr || "").trim();
|
|
379
|
+
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
380
|
+
return {
|
|
381
|
+
ok: result.status === 0 && !result.error,
|
|
382
|
+
status: Number.isInteger(result.status) ? result.status : -1,
|
|
383
|
+
stdout,
|
|
384
|
+
stderr,
|
|
385
|
+
output,
|
|
386
|
+
error_code: result.error?.code || null,
|
|
387
|
+
error_message: result.error?.message || ""
|
|
388
|
+
};
|
|
389
|
+
} catch (error) {
|
|
390
|
+
return {
|
|
391
|
+
ok: false,
|
|
392
|
+
status: -1,
|
|
393
|
+
stdout: "",
|
|
394
|
+
stderr: "",
|
|
395
|
+
output: "",
|
|
396
|
+
error_code: error.code || "SPAWN_FAILED",
|
|
397
|
+
error_message: error.message || String(error)
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function parseMajorVersion(raw) {
|
|
403
|
+
const match = String(raw || "").match(/v?(\d+)(?:\.\d+){0,2}/);
|
|
404
|
+
if (!match) return null;
|
|
405
|
+
const major = Number.parseInt(match[1], 10);
|
|
406
|
+
return Number.isFinite(major) ? major : null;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function buildNodeCommandCheckLocal() {
|
|
410
|
+
const probe = runProcessSyncLocal({
|
|
411
|
+
command: "node",
|
|
412
|
+
args: ["--version"]
|
|
413
|
+
});
|
|
414
|
+
const major = parseMajorVersion(probe.output);
|
|
415
|
+
const versionOk = Number.isInteger(major) && major >= 18;
|
|
416
|
+
return {
|
|
417
|
+
key: "node_cli",
|
|
418
|
+
ok: probe.ok && versionOk,
|
|
419
|
+
path: "node --version",
|
|
420
|
+
message: probe.ok
|
|
421
|
+
? (versionOk
|
|
422
|
+
? `Node 命令可用 (${probe.output || "unknown version"})`
|
|
423
|
+
: `Node 版本过低 (${probe.output || "unknown version"}),要求 >= 18`)
|
|
424
|
+
: `未找到 node 命令,请先安装 Node.js >= 18。${probe.error_message ? ` (${probe.error_message})` : ""}`
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function buildNodePackageCheckLocal({ key, moduleName, cwd, missingMessage }) {
|
|
429
|
+
if (!cwd || !pathExists(cwd)) {
|
|
430
|
+
return {
|
|
431
|
+
key,
|
|
432
|
+
ok: false,
|
|
433
|
+
path: moduleName,
|
|
434
|
+
module: moduleName,
|
|
435
|
+
install_cwd: null,
|
|
436
|
+
message: missingMessage
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
const probe = runProcessSyncLocal({
|
|
440
|
+
command: "node",
|
|
441
|
+
args: ["-e", `require.resolve(${JSON.stringify(moduleName)});`],
|
|
442
|
+
cwd
|
|
443
|
+
});
|
|
444
|
+
return {
|
|
445
|
+
key,
|
|
446
|
+
ok: probe.ok,
|
|
447
|
+
path: moduleName,
|
|
448
|
+
module: moduleName,
|
|
449
|
+
install_cwd: cwd,
|
|
450
|
+
message: probe.ok
|
|
451
|
+
? `${moduleName} npm 依赖可用`
|
|
452
|
+
: `缺少 npm 依赖 ${moduleName},请在 boss-recommend-mcp 目录执行 npm install。`
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function buildRuntimeDependencyChecksLocal({ dependencyDir = packageRoot } = {}) {
|
|
457
|
+
return [
|
|
458
|
+
buildNodeCommandCheckLocal(),
|
|
459
|
+
buildNodePackageCheckLocal({
|
|
460
|
+
key: "npm_dep_chrome_remote_interface",
|
|
461
|
+
moduleName: "chrome-remote-interface",
|
|
462
|
+
cwd: dependencyDir,
|
|
463
|
+
missingMessage: "无法校验 chrome-remote-interface:boss-recommend-mcp package 目录不存在。"
|
|
464
|
+
}),
|
|
465
|
+
buildNodePackageCheckLocal({
|
|
466
|
+
key: "npm_dep_ws",
|
|
467
|
+
moduleName: "ws",
|
|
468
|
+
cwd: dependencyDir,
|
|
469
|
+
missingMessage: "无法校验 ws:boss-recommend-mcp package 目录不存在。"
|
|
470
|
+
}),
|
|
471
|
+
buildNodePackageCheckLocal({
|
|
472
|
+
key: "npm_dep_sharp",
|
|
473
|
+
moduleName: "sharp",
|
|
474
|
+
cwd: dependencyDir,
|
|
475
|
+
missingMessage: "无法校验 sharp:boss-recommend-mcp package 目录不存在。"
|
|
476
|
+
})
|
|
477
|
+
];
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function resolveWorkspaceDebugPortLocal(workspaceRoot) {
|
|
481
|
+
const fromEnv = parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT);
|
|
482
|
+
if (fromEnv) return fromEnv;
|
|
483
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
484
|
+
const config = readJsonObjectFileSafe(configResolution.resolved_path);
|
|
485
|
+
return parsePositivePort(config?.debugPort) || 9222;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function buildScreenConfigCheckLocal(workspaceRoot, configResolution) {
|
|
489
|
+
const screenConfig = resolveBossScreeningConfig(workspaceRoot);
|
|
490
|
+
const pathForMessage = screenConfig.config_path || configResolution.resolved_path || configResolution.writable_path;
|
|
491
|
+
return {
|
|
492
|
+
key: "screen_config",
|
|
493
|
+
ok: screenConfig.ok,
|
|
494
|
+
path: pathForMessage,
|
|
495
|
+
reason: screenConfig.ok ? "OK" : (screenConfig.error?.code || "SCREEN_CONFIG_ERROR"),
|
|
496
|
+
message: screenConfig.ok ? "screening-config.json 可用" : (screenConfig.error?.message || "screening-config.json 不可用")
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function runPipelinePreflightLocal(workspaceRoot, options = {}) {
|
|
501
|
+
const pageScope = normalizePageScope(options.pageScope) || "recommend";
|
|
502
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
503
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(workspaceRoot);
|
|
504
|
+
const checks = [
|
|
505
|
+
buildScreenConfigCheckLocal(workspaceRoot, configResolution),
|
|
506
|
+
{
|
|
507
|
+
key: "favorite_calibration",
|
|
508
|
+
ok: calibrationResolution.calibration_usable,
|
|
509
|
+
path: calibrationResolution.calibration_path,
|
|
510
|
+
optional: pageScope !== "featured",
|
|
511
|
+
message: calibrationResolution.calibration_usable
|
|
512
|
+
? "favorite-calibration.json 可用"
|
|
513
|
+
: "favorite-calibration.json 不存在或无效(精选页收藏仅支持校准坐标点击)"
|
|
514
|
+
}
|
|
515
|
+
];
|
|
516
|
+
checks.push(...buildRuntimeDependencyChecksLocal({ dependencyDir: packageRoot }));
|
|
517
|
+
|
|
518
|
+
const requiredCheckKeys = new Set([
|
|
519
|
+
"screen_config",
|
|
520
|
+
"node_cli",
|
|
521
|
+
"npm_dep_chrome_remote_interface",
|
|
522
|
+
"npm_dep_ws",
|
|
523
|
+
"npm_dep_sharp"
|
|
524
|
+
]);
|
|
525
|
+
if (pageScope === "featured") {
|
|
526
|
+
requiredCheckKeys.add("favorite_calibration");
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
ok: checks.every((item) => !requiredCheckKeys.has(item.key) || item.ok),
|
|
531
|
+
checks,
|
|
532
|
+
debug_port: resolveWorkspaceDebugPortLocal(workspaceRoot),
|
|
533
|
+
config_resolution: configResolution,
|
|
534
|
+
calibration_path: calibrationResolution.calibration_path,
|
|
535
|
+
page_scope: pageScope
|
|
536
|
+
};
|
|
537
|
+
}
|
|
538
|
+
|
|
181
539
|
function getSkillTargetDir(name = skillName) {
|
|
182
540
|
return path.join(getCodexHome(), "skills", name);
|
|
183
541
|
}
|
|
@@ -224,6 +582,12 @@ function parsePositivePort(raw) {
|
|
|
224
582
|
return Number.isFinite(port) && port > 0 ? port : null;
|
|
225
583
|
}
|
|
226
584
|
|
|
585
|
+
function parseNonNegativeInteger(raw, fallback = undefined) {
|
|
586
|
+
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
587
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
588
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
589
|
+
}
|
|
590
|
+
|
|
227
591
|
function parseBossChatTargetCountOption(raw) {
|
|
228
592
|
if (raw === undefined || raw === null) return undefined;
|
|
229
593
|
const text = String(raw).trim();
|
|
@@ -362,7 +726,7 @@ function buildMcpLaunchConfig(options = {}) {
|
|
|
362
726
|
? args
|
|
363
727
|
: command === "boss-recommend-mcp"
|
|
364
728
|
? ["start"]
|
|
365
|
-
: buildDefaultMcpArgs();
|
|
729
|
+
: buildDefaultMcpArgs(options);
|
|
366
730
|
const launchConfig = { command, args: launchArgs };
|
|
367
731
|
if (env && typeof env === "object" && !Array.isArray(env) && Object.keys(env).length > 0) {
|
|
368
732
|
launchConfig.env = env;
|
|
@@ -431,24 +795,56 @@ function parseAgentTargets(rawValue) {
|
|
|
431
795
|
return unique;
|
|
432
796
|
}
|
|
433
797
|
|
|
798
|
+
function getExternalAppSupportBaseDirs() {
|
|
799
|
+
const home = os.homedir();
|
|
800
|
+
if (process.platform === "win32") {
|
|
801
|
+
return dedupePaths([
|
|
802
|
+
process.env.APPDATA || "",
|
|
803
|
+
path.join(home, "AppData", "Roaming")
|
|
804
|
+
]);
|
|
805
|
+
}
|
|
806
|
+
if (process.platform === "darwin") {
|
|
807
|
+
return dedupePaths([
|
|
808
|
+
path.join(home, "Library", "Application Support")
|
|
809
|
+
]);
|
|
810
|
+
}
|
|
811
|
+
return dedupePaths([
|
|
812
|
+
process.env.XDG_CONFIG_HOME || "",
|
|
813
|
+
path.join(home, ".config")
|
|
814
|
+
]);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function buildAppUserPaths({ dirNames = [], tail = [] } = {}) {
|
|
818
|
+
const paths = [];
|
|
819
|
+
for (const baseDir of getExternalAppSupportBaseDirs()) {
|
|
820
|
+
const discovered = discoverAppDataDirsByPattern(baseDir, /^trae(?:[\s\-_]?cn)?$/i);
|
|
821
|
+
const names = dedupeLower([...dirNames, ...discovered]);
|
|
822
|
+
for (const dirName of names) {
|
|
823
|
+
paths.push(path.join(baseDir, dirName, "User", ...tail));
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
return dedupePaths(paths);
|
|
827
|
+
}
|
|
828
|
+
|
|
434
829
|
function getKnownExternalMcpConfigPathsByAgent() {
|
|
435
830
|
const home = os.homedir();
|
|
436
|
-
const
|
|
437
|
-
const traeDirNames =
|
|
831
|
+
const appBases = getExternalAppSupportBaseDirs();
|
|
832
|
+
const traeDirNames = [
|
|
438
833
|
"Trae",
|
|
439
834
|
"Trae CN",
|
|
440
835
|
"TraeCN",
|
|
441
836
|
"trae-cn",
|
|
442
|
-
"trae_cn"
|
|
443
|
-
|
|
444
|
-
]);
|
|
445
|
-
const
|
|
837
|
+
"trae_cn"
|
|
838
|
+
];
|
|
839
|
+
const traeConfigPaths = buildAppUserPaths({ dirNames: traeDirNames, tail: ["mcp.json"] });
|
|
840
|
+
const cursorConfigPaths = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "mcp.json"));
|
|
841
|
+
const openClawConfigPaths = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "mcp.json"));
|
|
446
842
|
return {
|
|
447
|
-
cursor: [
|
|
843
|
+
cursor: [...cursorConfigPaths, path.join(home, ".cursor", "mcp.json")],
|
|
448
844
|
trae: [...traeConfigPaths, path.join(home, ".trae", "mcp.json"), path.join(home, ".trae-cn", "mcp.json")],
|
|
449
845
|
"trae-cn": [...traeConfigPaths, path.join(home, ".trae-cn", "mcp.json"), path.join(home, ".trae", "mcp.json")],
|
|
450
846
|
claude: [path.join(home, ".claude", "mcp.json")],
|
|
451
|
-
openclaw: [path.join(home, ".openclaw", "mcp.json")]
|
|
847
|
+
openclaw: [path.join(home, ".openclaw", "mcp.json"), ...openClawConfigPaths]
|
|
452
848
|
};
|
|
453
849
|
}
|
|
454
850
|
|
|
@@ -475,21 +871,40 @@ function mergeMcpServerConfigFile(filePath, options = {}) {
|
|
|
475
871
|
? current.mcpServers
|
|
476
872
|
: {};
|
|
477
873
|
const existingEntry = existingServers[serverName];
|
|
874
|
+
const retainedServers = {};
|
|
875
|
+
const migratedLegacyServers = [];
|
|
876
|
+
for (const [name, config] of Object.entries(existingServers)) {
|
|
877
|
+
if (name === serverName) continue;
|
|
878
|
+
if (isBossMcpServerEntry(name, config)) {
|
|
879
|
+
migratedLegacyServers.push(name);
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
retainedServers[name] = config;
|
|
883
|
+
}
|
|
478
884
|
const merged = {
|
|
479
885
|
...current,
|
|
480
886
|
mcpServers: {
|
|
481
|
-
...
|
|
887
|
+
...retainedServers,
|
|
482
888
|
[serverName]: launchConfig
|
|
483
889
|
}
|
|
484
890
|
};
|
|
485
891
|
|
|
486
892
|
ensureDir(path.dirname(filePath));
|
|
893
|
+
const before = pathExists(filePath) ? fs.readFileSync(filePath, "utf8") : "";
|
|
894
|
+
const next = JSON.stringify(merged, null, 2);
|
|
895
|
+
let backupFile = null;
|
|
896
|
+
if (before && before.trim() !== next.trim()) {
|
|
897
|
+
backupFile = `${filePath}.boss-mcp-migration-${new Date().toISOString().replace(/[:.]/g, "-")}.bak`;
|
|
898
|
+
fs.writeFileSync(backupFile, before, "utf8");
|
|
899
|
+
}
|
|
487
900
|
fs.writeFileSync(filePath, JSON.stringify(merged, null, 2), "utf8");
|
|
488
|
-
const updated = JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
|
|
901
|
+
const updated = before.trim() !== next.trim() || JSON.stringify(existingEntry || null) !== JSON.stringify(launchConfig);
|
|
489
902
|
return {
|
|
490
903
|
file: filePath,
|
|
491
904
|
server: serverName,
|
|
492
|
-
updated
|
|
905
|
+
updated,
|
|
906
|
+
migrated_legacy_servers: migratedLegacyServers,
|
|
907
|
+
backup_file: backupFile
|
|
493
908
|
};
|
|
494
909
|
}
|
|
495
910
|
|
|
@@ -505,7 +920,9 @@ function installExternalMcpConfigs(options = {}) {
|
|
|
505
920
|
file: target,
|
|
506
921
|
server: merged.server,
|
|
507
922
|
created: !existed,
|
|
508
|
-
updated: merged.updated
|
|
923
|
+
updated: merged.updated,
|
|
924
|
+
migrated_legacy_servers: merged.migrated_legacy_servers,
|
|
925
|
+
backup_file: merged.backup_file
|
|
509
926
|
});
|
|
510
927
|
} catch (error) {
|
|
511
928
|
skipped.push({
|
|
@@ -519,25 +936,30 @@ function installExternalMcpConfigs(options = {}) {
|
|
|
519
936
|
|
|
520
937
|
function getKnownExternalSkillBaseDirsByAgent() {
|
|
521
938
|
const home = os.homedir();
|
|
522
|
-
const
|
|
523
|
-
const traeDirNames =
|
|
939
|
+
const appBases = getExternalAppSupportBaseDirs();
|
|
940
|
+
const traeDirNames = [
|
|
524
941
|
"Trae",
|
|
525
942
|
"Trae CN",
|
|
526
943
|
"TraeCN",
|
|
527
944
|
"trae-cn",
|
|
528
|
-
"trae_cn"
|
|
529
|
-
|
|
530
|
-
]);
|
|
531
|
-
const
|
|
945
|
+
"trae_cn"
|
|
946
|
+
];
|
|
947
|
+
const traeSkillDirs = buildAppUserPaths({ dirNames: traeDirNames, tail: ["skills"] });
|
|
948
|
+
const cursorSkillDirs = appBases.map((baseDir) => path.join(baseDir, "Cursor", "User", "skills"));
|
|
949
|
+
const openClawSkillDirs = appBases.map((baseDir) => path.join(baseDir, "OpenClaw", "User", "skills"));
|
|
532
950
|
return {
|
|
533
|
-
cursor: [path.join(home, ".cursor", "skills"),
|
|
951
|
+
cursor: [path.join(home, ".cursor", "skills"), ...cursorSkillDirs],
|
|
534
952
|
trae: [path.join(home, ".trae", "skills"), path.join(home, ".trae-cn", "skills"), ...traeSkillDirs],
|
|
535
953
|
"trae-cn": [path.join(home, ".trae-cn", "skills"), path.join(home, ".trae", "skills"), ...traeSkillDirs],
|
|
536
954
|
claude: [path.join(home, ".claude", "skills")],
|
|
537
|
-
openclaw: [path.join(home, ".openclaw", "skills"),
|
|
955
|
+
openclaw: [path.join(home, ".openclaw", "skills"), ...openClawSkillDirs]
|
|
538
956
|
};
|
|
539
957
|
}
|
|
540
958
|
|
|
959
|
+
function serializeMcpLaunchConfig(launchConfig) {
|
|
960
|
+
return JSON.stringify(launchConfig || {}).toLowerCase().replace(/\\/g, "/");
|
|
961
|
+
}
|
|
962
|
+
|
|
541
963
|
function isRecommendMcpLaunchConfig(launchConfig) {
|
|
542
964
|
if (!launchConfig || typeof launchConfig !== "object") return false;
|
|
543
965
|
const command = String(launchConfig.command || "").toLowerCase();
|
|
@@ -551,14 +973,33 @@ function isRecommendMcpLaunchConfig(launchConfig) {
|
|
|
551
973
|
);
|
|
552
974
|
}
|
|
553
975
|
|
|
976
|
+
function isBossMcpServerEntry(name, launchConfig) {
|
|
977
|
+
const lowerName = String(name || "").toLowerCase();
|
|
978
|
+
const serialized = serializeMcpLaunchConfig(launchConfig);
|
|
979
|
+
return (
|
|
980
|
+
/boss[-_\s]?(recommend|recruit|chat)/i.test(lowerName)
|
|
981
|
+
|| serialized.includes(recommendMcpPackageName.toLowerCase())
|
|
982
|
+
|| serialized.includes("@reconcrap/boss-recruit-mcp")
|
|
983
|
+
|| serialized.includes("@reconcrap/boss-chat")
|
|
984
|
+
|| serialized.includes("boss-recommend-mcp")
|
|
985
|
+
|| serialized.includes("boss-recruit-mcp")
|
|
986
|
+
|| serialized.includes("boss-chat")
|
|
987
|
+
|| serialized.includes("boss recommend pipeline")
|
|
988
|
+
|| serialized.includes("boss recruit pipeline")
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
|
|
554
992
|
function inspectMcpServerEntries(filePath) {
|
|
555
993
|
if (!pathExists(filePath)) {
|
|
556
994
|
return {
|
|
557
995
|
exists: false,
|
|
558
996
|
has_boss_recommend: false,
|
|
559
997
|
has_boss_recruit: false,
|
|
998
|
+
has_boss_chat: false,
|
|
560
999
|
recommend_server_names: [],
|
|
561
|
-
recruit_server_names: []
|
|
1000
|
+
recruit_server_names: [],
|
|
1001
|
+
chat_server_names: [],
|
|
1002
|
+
boss_server_names: []
|
|
562
1003
|
};
|
|
563
1004
|
}
|
|
564
1005
|
const parsed = readJsonObjectFileSafe(filePath);
|
|
@@ -567,8 +1008,13 @@ function inspectMcpServerEntries(filePath) {
|
|
|
567
1008
|
: {};
|
|
568
1009
|
const recommendNames = [];
|
|
569
1010
|
const recruitNames = [];
|
|
1011
|
+
const chatNames = [];
|
|
1012
|
+
const bossNames = [];
|
|
570
1013
|
for (const [name, config] of Object.entries(servers)) {
|
|
571
1014
|
const lowerName = String(name || "").toLowerCase();
|
|
1015
|
+
if (isBossMcpServerEntry(name, config)) {
|
|
1016
|
+
bossNames.push(name);
|
|
1017
|
+
}
|
|
572
1018
|
if (isRecommendMcpLaunchConfig(config) || lowerName.includes("boss-recommend")) {
|
|
573
1019
|
recommendNames.push(name);
|
|
574
1020
|
}
|
|
@@ -580,13 +1026,23 @@ function inspectMcpServerEntries(filePath) {
|
|
|
580
1026
|
) {
|
|
581
1027
|
recruitNames.push(name);
|
|
582
1028
|
}
|
|
1029
|
+
if (
|
|
1030
|
+
lowerName.includes("boss-chat")
|
|
1031
|
+
|| serialized.includes("@reconcrap/boss-chat")
|
|
1032
|
+
|| serialized.includes("boss-chat")
|
|
1033
|
+
) {
|
|
1034
|
+
chatNames.push(name);
|
|
1035
|
+
}
|
|
583
1036
|
}
|
|
584
1037
|
return {
|
|
585
1038
|
exists: true,
|
|
586
1039
|
has_boss_recommend: recommendNames.length > 0,
|
|
587
1040
|
has_boss_recruit: recruitNames.length > 0,
|
|
1041
|
+
has_boss_chat: chatNames.length > 0,
|
|
588
1042
|
recommend_server_names: recommendNames,
|
|
589
|
-
recruit_server_names: recruitNames
|
|
1043
|
+
recruit_server_names: recruitNames,
|
|
1044
|
+
chat_server_names: chatNames,
|
|
1045
|
+
boss_server_names: bossNames
|
|
590
1046
|
};
|
|
591
1047
|
}
|
|
592
1048
|
|
|
@@ -610,9 +1066,16 @@ function mirrorSkillToExternalDirs(options = {}) {
|
|
|
610
1066
|
for (const bundledSkillName of bundledSkillNames) {
|
|
611
1067
|
try {
|
|
612
1068
|
const targetDir = path.join(baseDir, bundledSkillName);
|
|
1069
|
+
const legacyBeforeCopy = isLegacyBossSkillDir(targetDir);
|
|
613
1070
|
ensureDir(path.dirname(targetDir));
|
|
614
1071
|
fs.cpSync(getSkillSourceDir(bundledSkillName), targetDir, { recursive: true, force: true });
|
|
615
|
-
|
|
1072
|
+
fs.writeFileSync(path.join(targetDir, ".installed-version"), `${packageVersion}\n`, "utf8");
|
|
1073
|
+
mirrored.push({
|
|
1074
|
+
base_dir: baseDir,
|
|
1075
|
+
target_dir: targetDir,
|
|
1076
|
+
skill: bundledSkillName,
|
|
1077
|
+
replaced_legacy: legacyBeforeCopy
|
|
1078
|
+
});
|
|
616
1079
|
} catch (error) {
|
|
617
1080
|
skipped.push({ base_dir: baseDir, skill: bundledSkillName, reason: error.message });
|
|
618
1081
|
}
|
|
@@ -621,6 +1084,23 @@ function mirrorSkillToExternalDirs(options = {}) {
|
|
|
621
1084
|
return { baseDirs, mirrored, skipped };
|
|
622
1085
|
}
|
|
623
1086
|
|
|
1087
|
+
function isLegacyBossSkillDir(targetDir) {
|
|
1088
|
+
const skillFile = path.join(targetDir, "SKILL.md");
|
|
1089
|
+
if (!pathExists(skillFile)) return false;
|
|
1090
|
+
try {
|
|
1091
|
+
const content = fs.readFileSync(skillFile, "utf8").toLowerCase();
|
|
1092
|
+
return (
|
|
1093
|
+
content.includes("@reconcrap/boss-recruit-mcp")
|
|
1094
|
+
|| content.includes("@reconcrap/boss-chat")
|
|
1095
|
+
|| content.includes("boss-screen-cli")
|
|
1096
|
+
|| content.includes(`runtime.${"evaluate"}`)
|
|
1097
|
+
|| content.includes("page js")
|
|
1098
|
+
);
|
|
1099
|
+
} catch {
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
624
1104
|
function syncSkillAssets(options = {}) {
|
|
625
1105
|
const force = options.force === true;
|
|
626
1106
|
const results = [];
|
|
@@ -670,9 +1150,9 @@ function pathStartsWith(filePath, rootPath) {
|
|
|
670
1150
|
return file.startsWith(root);
|
|
671
1151
|
}
|
|
672
1152
|
|
|
673
|
-
function resolveCliConfigTarget(options = {}) {
|
|
1153
|
+
async function resolveCliConfigTarget(options = {}) {
|
|
674
1154
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
675
|
-
const resolution =
|
|
1155
|
+
const resolution = getBossScreenConfigResolution(workspaceRoot);
|
|
676
1156
|
const workspacePreferred = (resolution.candidate_paths || []).find((item) => pathStartsWith(item, workspaceRoot)) || null;
|
|
677
1157
|
const configPath = resolution.writable_path || resolution.resolved_path || workspacePreferred || getUserConfigPath();
|
|
678
1158
|
return {
|
|
@@ -698,8 +1178,8 @@ function applyMissingInstallConfigDefaults(config = {}) {
|
|
|
698
1178
|
};
|
|
699
1179
|
}
|
|
700
1180
|
|
|
701
|
-
function ensureUserConfig(options = {}) {
|
|
702
|
-
const { configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1181
|
+
async function ensureUserConfig(options = {}) {
|
|
1182
|
+
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
703
1183
|
const writeTargets = dedupePaths([configPath, workspacePreferred]).filter(Boolean);
|
|
704
1184
|
let lastError = null;
|
|
705
1185
|
for (const targetPath of writeTargets) {
|
|
@@ -744,16 +1224,16 @@ function ensureUserConfig(options = {}) {
|
|
|
744
1224
|
throw lastError || new Error("No writable target for screening-config.json");
|
|
745
1225
|
}
|
|
746
1226
|
|
|
747
|
-
function collectRuntimeDirectories(options = {}) {
|
|
1227
|
+
async function collectRuntimeDirectories(options = {}) {
|
|
748
1228
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
749
1229
|
const stateHome = getStateHome();
|
|
750
|
-
const runtime =
|
|
1230
|
+
const runtime = resolveCdpBossChatRuntimeLayout(workspaceRoot);
|
|
751
1231
|
const bossChatRoot = runtime.data_dir;
|
|
752
1232
|
const recommendRuntimeDirs = [
|
|
753
1233
|
stateHome,
|
|
754
1234
|
path.join(stateHome, "runs")
|
|
755
1235
|
];
|
|
756
|
-
const bossChatRuntimeDirs = runtime
|
|
1236
|
+
const bossChatRuntimeDirs = getBossChatRuntimeDirectories(runtime);
|
|
757
1237
|
return {
|
|
758
1238
|
workspaceRoot,
|
|
759
1239
|
stateHome,
|
|
@@ -767,9 +1247,9 @@ function collectRuntimeDirectories(options = {}) {
|
|
|
767
1247
|
};
|
|
768
1248
|
}
|
|
769
1249
|
|
|
770
|
-
function ensureRuntimeDirectories(options = {}) {
|
|
771
|
-
const { workspaceRoot, stateHome } = collectRuntimeDirectories(options);
|
|
772
|
-
const runtime =
|
|
1250
|
+
async function ensureRuntimeDirectories(options = {}) {
|
|
1251
|
+
const { workspaceRoot, stateHome } = await collectRuntimeDirectories(options);
|
|
1252
|
+
const runtime = ensureBossChatRuntimeReadyLocal(workspaceRoot);
|
|
773
1253
|
const recommendCreated = [];
|
|
774
1254
|
const recommendExisted = [];
|
|
775
1255
|
const failed = [...runtime.failed];
|
|
@@ -813,8 +1293,8 @@ function readJsonObjectFile(filePath) {
|
|
|
813
1293
|
return parsed;
|
|
814
1294
|
}
|
|
815
1295
|
|
|
816
|
-
function loadBestExistingUserConfig(options = {}) {
|
|
817
|
-
const { resolution, configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1296
|
+
async function loadBestExistingUserConfig(options = {}) {
|
|
1297
|
+
const { resolution, configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
818
1298
|
const candidates = dedupePaths([
|
|
819
1299
|
...(resolution.candidate_paths || []),
|
|
820
1300
|
configPath,
|
|
@@ -837,8 +1317,8 @@ function loadBestExistingUserConfig(options = {}) {
|
|
|
837
1317
|
return { path: configPath, config: {} };
|
|
838
1318
|
}
|
|
839
1319
|
|
|
840
|
-
function writeConfigWithFallback(nextConfig, options = {}) {
|
|
841
|
-
const { configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1320
|
+
async function writeConfigWithFallback(nextConfig, options = {}) {
|
|
1321
|
+
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
842
1322
|
const targets = dedupePaths([configPath, workspacePreferred]).filter(Boolean);
|
|
843
1323
|
let lastError = null;
|
|
844
1324
|
for (const target of targets) {
|
|
@@ -860,14 +1340,14 @@ function writeConfigWithFallback(nextConfig, options = {}) {
|
|
|
860
1340
|
throw lastError || new Error("No writable target for screening-config.json");
|
|
861
1341
|
}
|
|
862
1342
|
|
|
863
|
-
function persistDebugPortSelection(port, options = {}) {
|
|
864
|
-
const { config } = loadBestExistingUserConfig(options);
|
|
1343
|
+
async function persistDebugPortSelection(port, options = {}) {
|
|
1344
|
+
const { config } = await loadBestExistingUserConfig(options);
|
|
865
1345
|
config.debugPort = port;
|
|
866
|
-
const configPath = writeConfigWithFallback(config, options);
|
|
1346
|
+
const configPath = await writeConfigWithFallback(config, options);
|
|
867
1347
|
return { port, configPath };
|
|
868
1348
|
}
|
|
869
1349
|
|
|
870
|
-
function setDebugPort(options = {}) {
|
|
1350
|
+
async function setDebugPort(options = {}) {
|
|
871
1351
|
const selected = parsePositivePort(options.port);
|
|
872
1352
|
if (!selected) {
|
|
873
1353
|
throw new Error("Missing required --port <number> for set-port.");
|
|
@@ -876,7 +1356,7 @@ function setDebugPort(options = {}) {
|
|
|
876
1356
|
return persistDebugPortSelection(selected, options);
|
|
877
1357
|
}
|
|
878
1358
|
|
|
879
|
-
function setScreeningConfig(options = {}) {
|
|
1359
|
+
async function setScreeningConfig(options = {}) {
|
|
880
1360
|
const baseUrl = String(options["base-url"] || options.baseUrl || "").trim();
|
|
881
1361
|
const apiKey = String(options["api-key"] || options.apiKey || "").trim();
|
|
882
1362
|
const model = String(options.model || "").trim();
|
|
@@ -884,7 +1364,7 @@ function setScreeningConfig(options = {}) {
|
|
|
884
1364
|
throw new Error("Missing required fields: --base-url, --api-key, --model");
|
|
885
1365
|
}
|
|
886
1366
|
|
|
887
|
-
const { config: existing } = loadBestExistingUserConfig(options);
|
|
1367
|
+
const { config: existing } = await loadBestExistingUserConfig(options);
|
|
888
1368
|
const nextConfig = {
|
|
889
1369
|
...existing,
|
|
890
1370
|
baseUrl,
|
|
@@ -909,7 +1389,7 @@ function setScreeningConfig(options = {}) {
|
|
|
909
1389
|
if (debugPort) {
|
|
910
1390
|
nextConfig.debugPort = debugPort;
|
|
911
1391
|
}
|
|
912
|
-
const configPath = writeConfigWithFallback(nextConfig, options);
|
|
1392
|
+
const configPath = await writeConfigWithFallback(nextConfig, options);
|
|
913
1393
|
return { path: configPath, updated: true };
|
|
914
1394
|
}
|
|
915
1395
|
|
|
@@ -926,6 +1406,20 @@ async function listChromeTabs(port) {
|
|
|
926
1406
|
return Array.isArray(data) ? data : [];
|
|
927
1407
|
}
|
|
928
1408
|
|
|
1409
|
+
function buildBossPageState(payload) {
|
|
1410
|
+
return {
|
|
1411
|
+
key: "boss_page_state",
|
|
1412
|
+
...payload
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
function extractSampleUrls(tabs, limit = 5) {
|
|
1417
|
+
return tabs
|
|
1418
|
+
.map((tab) => tab?.url)
|
|
1419
|
+
.filter(Boolean)
|
|
1420
|
+
.slice(0, limit);
|
|
1421
|
+
}
|
|
1422
|
+
|
|
929
1423
|
function findChromeOnboardingUrl(tabs) {
|
|
930
1424
|
for (const tab of tabs) {
|
|
931
1425
|
if (typeof tab?.url === "string" && chromeOnboardingUrlPattern.test(tab.url)) {
|
|
@@ -935,6 +1429,383 @@ function findChromeOnboardingUrl(tabs) {
|
|
|
935
1429
|
return null;
|
|
936
1430
|
}
|
|
937
1431
|
|
|
1432
|
+
function isBossRecommendTab(tab) {
|
|
1433
|
+
return typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend");
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
function findBossRecommendTab(tabs = []) {
|
|
1437
|
+
return tabs.find((tab) => isBossRecommendTab(tab)) || null;
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
function isBossLoginTab(tab) {
|
|
1441
|
+
const url = String(tab?.url || "");
|
|
1442
|
+
const title = String(tab?.title || "");
|
|
1443
|
+
return (
|
|
1444
|
+
url === bossLoginUrl
|
|
1445
|
+
|| bossLoginUrlPattern.test(url)
|
|
1446
|
+
|| bossLoginTitlePattern.test(title)
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
function findBossPageTab(tabs = []) {
|
|
1451
|
+
return tabs.find((tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")) || null;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
function getNodeAttribute(node, name) {
|
|
1455
|
+
const attributes = node?.attributes || [];
|
|
1456
|
+
for (let index = 0; index < attributes.length; index += 2) {
|
|
1457
|
+
if (attributes[index] === name) return attributes[index + 1] || "";
|
|
1458
|
+
}
|
|
1459
|
+
return "";
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function uniqueMethodNames(methodLog = []) {
|
|
1463
|
+
return Array.from(new Set(methodLog.map((entry) => entry?.method).filter(Boolean)));
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
async function inspectBossRecommendPageStateCdp(port, options = {}) {
|
|
1467
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
|
|
1468
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 1000;
|
|
1469
|
+
const expectedUrl = options.expectedUrl || bossUrl;
|
|
1470
|
+
const deadline = Date.now() + timeoutMs;
|
|
1471
|
+
let lastError = null;
|
|
1472
|
+
let lastTabs = [];
|
|
1473
|
+
|
|
1474
|
+
while (Date.now() <= deadline) {
|
|
1475
|
+
try {
|
|
1476
|
+
const tabs = await listChromeTabs(port);
|
|
1477
|
+
lastTabs = tabs;
|
|
1478
|
+
const recommendTab = findBossRecommendTab(tabs);
|
|
1479
|
+
if (recommendTab) {
|
|
1480
|
+
if (isBossLoginTab(recommendTab)) {
|
|
1481
|
+
return buildBossPageState({
|
|
1482
|
+
ok: false,
|
|
1483
|
+
state: "LOGIN_REQUIRED",
|
|
1484
|
+
path: recommendTab.url || bossLoginUrl,
|
|
1485
|
+
current_url: recommendTab.url || bossLoginUrl,
|
|
1486
|
+
title: recommendTab.title || null,
|
|
1487
|
+
requires_login: true,
|
|
1488
|
+
expected_url: expectedUrl,
|
|
1489
|
+
login_url: bossLoginUrl,
|
|
1490
|
+
message: "当前标签页虽在 recommend 路径,但检测到登录态页面特征,请先完成 Boss 登录。"
|
|
1491
|
+
});
|
|
1492
|
+
}
|
|
1493
|
+
return buildBossPageState({
|
|
1494
|
+
ok: true,
|
|
1495
|
+
state: "RECOMMEND_READY",
|
|
1496
|
+
path: recommendTab.url,
|
|
1497
|
+
current_url: recommendTab.url,
|
|
1498
|
+
title: recommendTab.title || null,
|
|
1499
|
+
requires_login: false,
|
|
1500
|
+
expected_url: expectedUrl,
|
|
1501
|
+
message: "Boss 推荐页已打开,且当前仍停留在 recommend 页面。"
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
const loginTab = tabs.find((tab) => isBossLoginTab(tab));
|
|
1506
|
+
if (loginTab) {
|
|
1507
|
+
return buildBossPageState({
|
|
1508
|
+
ok: false,
|
|
1509
|
+
state: "LOGIN_REQUIRED",
|
|
1510
|
+
path: loginTab.url || bossLoginUrl,
|
|
1511
|
+
current_url: loginTab.url || bossLoginUrl,
|
|
1512
|
+
title: loginTab.title || null,
|
|
1513
|
+
requires_login: true,
|
|
1514
|
+
expected_url: expectedUrl,
|
|
1515
|
+
login_url: bossLoginUrl,
|
|
1516
|
+
message: "Boss 页面未登录,需先完成登录后再进入 recommend 页面。"
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const bossTab = findBossPageTab(tabs);
|
|
1521
|
+
if (bossTab) {
|
|
1522
|
+
const requiresLogin = bossLoginUrlPattern.test(bossTab.url);
|
|
1523
|
+
return buildBossPageState({
|
|
1524
|
+
ok: false,
|
|
1525
|
+
state: requiresLogin ? "LOGIN_REQUIRED" : "BOSS_NOT_ON_RECOMMEND",
|
|
1526
|
+
path: bossTab.url,
|
|
1527
|
+
current_url: bossTab.url,
|
|
1528
|
+
title: bossTab.title || null,
|
|
1529
|
+
requires_login: requiresLogin,
|
|
1530
|
+
expected_url: expectedUrl,
|
|
1531
|
+
login_url: requiresLogin ? bossLoginUrl : undefined,
|
|
1532
|
+
message: requiresLogin
|
|
1533
|
+
? "Boss 页面未登录,需先完成登录后再进入 recommend 页面。"
|
|
1534
|
+
: "Boss 已登录但当前不在 recommend 页面,将尝试自动跳转。"
|
|
1535
|
+
});
|
|
1536
|
+
}
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
lastError = error;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
await sleepMs(pollMs);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
if (lastError) {
|
|
1545
|
+
return buildBossPageState({
|
|
1546
|
+
ok: false,
|
|
1547
|
+
state: "DEBUG_PORT_UNREACHABLE",
|
|
1548
|
+
path: `http://127.0.0.1:${port}`,
|
|
1549
|
+
current_url: null,
|
|
1550
|
+
title: null,
|
|
1551
|
+
requires_login: false,
|
|
1552
|
+
expected_url: expectedUrl,
|
|
1553
|
+
message: `无法连接到 Chrome DevTools 端口 ${port}。请确认 Chrome 已以远程调试模式启动。`,
|
|
1554
|
+
error: lastError.message
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
const onboardingUrl = findChromeOnboardingUrl(lastTabs);
|
|
1559
|
+
if (onboardingUrl) {
|
|
1560
|
+
return buildBossPageState({
|
|
1561
|
+
ok: false,
|
|
1562
|
+
state: "CHROME_ONBOARDING_INTERCEPTED",
|
|
1563
|
+
path: onboardingUrl,
|
|
1564
|
+
current_url: onboardingUrl,
|
|
1565
|
+
title: null,
|
|
1566
|
+
requires_login: false,
|
|
1567
|
+
expected_url: expectedUrl,
|
|
1568
|
+
message: "Chrome 当前停留在登录或引导页,尚未稳定到 Boss 推荐页。",
|
|
1569
|
+
sample_urls: extractSampleUrls(lastTabs)
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
return buildBossPageState({
|
|
1574
|
+
ok: false,
|
|
1575
|
+
state: "BOSS_TAB_NOT_FOUND",
|
|
1576
|
+
path: expectedUrl,
|
|
1577
|
+
current_url: null,
|
|
1578
|
+
title: null,
|
|
1579
|
+
requires_login: false,
|
|
1580
|
+
expected_url: expectedUrl,
|
|
1581
|
+
message: "未检测到 Boss 推荐页标签页。",
|
|
1582
|
+
sample_urls: extractSampleUrls(lastTabs)
|
|
1583
|
+
});
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function withRecommendTargetCdp(port, callback) {
|
|
1587
|
+
const connection = await connectToChromeTarget({
|
|
1588
|
+
port,
|
|
1589
|
+
targetPredicate: (target) => isBossRecommendTab(target)
|
|
1590
|
+
});
|
|
1591
|
+
try {
|
|
1592
|
+
return await callback(connection);
|
|
1593
|
+
} finally {
|
|
1594
|
+
await connection.close();
|
|
1595
|
+
}
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
async function bringBossRecommendTabToFrontCdp(port) {
|
|
1599
|
+
try {
|
|
1600
|
+
return await withRecommendTargetCdp(port, async ({ client, methodLog }) => {
|
|
1601
|
+
await enableDomains(client, ["Page"]);
|
|
1602
|
+
await bringPageToFront(client);
|
|
1603
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1604
|
+
return {
|
|
1605
|
+
ok: true,
|
|
1606
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1607
|
+
};
|
|
1608
|
+
});
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
return {
|
|
1611
|
+
ok: false,
|
|
1612
|
+
error: error.message || String(error)
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
async function probeRecommendIframeStateCdp(port, options = {}) {
|
|
1618
|
+
const expectedUrl = options.expectedUrl || bossUrl;
|
|
1619
|
+
try {
|
|
1620
|
+
return await withRecommendTargetCdp(port, async ({ client, target, methodLog }) => {
|
|
1621
|
+
await enableDomains(client, ["Page", "DOM"]);
|
|
1622
|
+
const root = await getDocumentRoot(client, { depth: 1, pierce: true });
|
|
1623
|
+
const iframeNodeId = await querySelector(client, root.nodeId, 'iframe[name="recommendFrame"]');
|
|
1624
|
+
if (!iframeNodeId) {
|
|
1625
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1626
|
+
return buildBossPageState({
|
|
1627
|
+
ok: false,
|
|
1628
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1629
|
+
path: target.url || expectedUrl,
|
|
1630
|
+
current_url: target.url || null,
|
|
1631
|
+
title: target.title || null,
|
|
1632
|
+
expected_url: expectedUrl,
|
|
1633
|
+
message: "recommend iframe 尚未挂载。",
|
|
1634
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1635
|
+
});
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
const described = await client.DOM.describeNode({
|
|
1639
|
+
nodeId: iframeNodeId,
|
|
1640
|
+
depth: 1,
|
|
1641
|
+
pierce: true
|
|
1642
|
+
});
|
|
1643
|
+
const iframeNode = described.node || {};
|
|
1644
|
+
const frameDocument = iframeNode.contentDocument || null;
|
|
1645
|
+
const frameUrl = frameDocument?.documentURL || getNodeAttribute(iframeNode, "src") || null;
|
|
1646
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1647
|
+
if (!frameDocument?.nodeId) {
|
|
1648
|
+
return buildBossPageState({
|
|
1649
|
+
ok: false,
|
|
1650
|
+
state: "RECOMMEND_IFRAME_DOCUMENT_PENDING",
|
|
1651
|
+
path: target.url || expectedUrl,
|
|
1652
|
+
current_url: target.url || null,
|
|
1653
|
+
title: target.title || null,
|
|
1654
|
+
expected_url: expectedUrl,
|
|
1655
|
+
frame_url: frameUrl,
|
|
1656
|
+
message: "recommend iframe 已挂载但文档尚未就绪。",
|
|
1657
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
return buildBossPageState({
|
|
1662
|
+
ok: true,
|
|
1663
|
+
state: "RECOMMEND_IFRAME_READY",
|
|
1664
|
+
path: target.url || expectedUrl,
|
|
1665
|
+
current_url: target.url || null,
|
|
1666
|
+
title: target.title || null,
|
|
1667
|
+
expected_url: expectedUrl,
|
|
1668
|
+
frame_url: frameUrl,
|
|
1669
|
+
iframe_node_id: iframeNodeId,
|
|
1670
|
+
frame_document_node_id: frameDocument.nodeId,
|
|
1671
|
+
message: "recommend iframe 已通过 CDP DOM 检测就绪。",
|
|
1672
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1673
|
+
});
|
|
1674
|
+
});
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
return buildBossPageState({
|
|
1677
|
+
ok: false,
|
|
1678
|
+
state: "RECOMMEND_IFRAME_PROBE_FAILED",
|
|
1679
|
+
path: expectedUrl,
|
|
1680
|
+
current_url: null,
|
|
1681
|
+
title: null,
|
|
1682
|
+
expected_url: expectedUrl,
|
|
1683
|
+
message: "recommend iframe CDP DOM 检测失败。",
|
|
1684
|
+
error: error.message || String(error)
|
|
1685
|
+
});
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
async function waitForRecommendIframeReadyCdp(port, options = {}) {
|
|
1690
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
|
|
1691
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1692
|
+
const deadline = Date.now() + timeoutMs;
|
|
1693
|
+
let lastState = null;
|
|
1694
|
+
|
|
1695
|
+
while (Date.now() <= deadline) {
|
|
1696
|
+
lastState = await probeRecommendIframeStateCdp(port, options);
|
|
1697
|
+
if (lastState?.state === "RECOMMEND_IFRAME_READY") return lastState;
|
|
1698
|
+
await sleepMs(pollMs);
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
return lastState || buildBossPageState({
|
|
1702
|
+
ok: false,
|
|
1703
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1704
|
+
path: options.expectedUrl || bossUrl,
|
|
1705
|
+
current_url: null,
|
|
1706
|
+
title: null,
|
|
1707
|
+
expected_url: options.expectedUrl || bossUrl,
|
|
1708
|
+
message: "recommend iframe 尚未就绪。"
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
async function verifyRecommendPageStableCdp(port, options = {}) {
|
|
1713
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1000;
|
|
1714
|
+
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 6000;
|
|
1715
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1716
|
+
|
|
1717
|
+
await sleepMs(settleMs);
|
|
1718
|
+
const recheck = await inspectBossRecommendPageStateCdp(port, {
|
|
1719
|
+
timeoutMs: recheckTimeoutMs,
|
|
1720
|
+
pollMs
|
|
1721
|
+
});
|
|
1722
|
+
if (recheck.state !== "RECOMMEND_READY") return recheck;
|
|
1723
|
+
|
|
1724
|
+
const iframeState = await waitForRecommendIframeReadyCdp(port, {
|
|
1725
|
+
timeoutMs: recheckTimeoutMs,
|
|
1726
|
+
pollMs
|
|
1727
|
+
});
|
|
1728
|
+
if (iframeState.state === "RECOMMEND_IFRAME_READY") {
|
|
1729
|
+
return buildBossPageState({
|
|
1730
|
+
...recheck,
|
|
1731
|
+
ok: true,
|
|
1732
|
+
state: "RECOMMEND_READY",
|
|
1733
|
+
frame_url: iframeState.frame_url || null,
|
|
1734
|
+
iframe_state: iframeState,
|
|
1735
|
+
method_log: iframeState.method_log || []
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return buildBossPageState({
|
|
1740
|
+
...iframeState,
|
|
1741
|
+
state: iframeState.state || "NO_RECOMMEND_IFRAME",
|
|
1742
|
+
message: iframeState.message || "Boss recommend 页面已打开,但 iframe 尚未就绪。"
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
async function navigateExistingTargetToBossRecommendCdp(port) {
|
|
1747
|
+
let connection = null;
|
|
1748
|
+
try {
|
|
1749
|
+
connection = await connectToChromeTarget({
|
|
1750
|
+
port,
|
|
1751
|
+
targetPredicate: (target) => target?.type === "page"
|
|
1752
|
+
});
|
|
1753
|
+
await enableDomains(connection.client, ["Page"]);
|
|
1754
|
+
await connection.client.Page.navigate({ url: bossUrl });
|
|
1755
|
+
assertNoForbiddenCdpCalls(connection.methodLog);
|
|
1756
|
+
return {
|
|
1757
|
+
ok: true,
|
|
1758
|
+
via: "cdp_page_navigate",
|
|
1759
|
+
target_id: connection.target?.id || null,
|
|
1760
|
+
method_log: uniqueMethodNames(connection.methodLog)
|
|
1761
|
+
};
|
|
1762
|
+
} catch (error) {
|
|
1763
|
+
return {
|
|
1764
|
+
ok: false,
|
|
1765
|
+
via: "cdp_page_navigate",
|
|
1766
|
+
error: error.message || String(error)
|
|
1767
|
+
};
|
|
1768
|
+
} finally {
|
|
1769
|
+
if (connection) await connection.close();
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
async function openBossRecommendTabCdp(port) {
|
|
1774
|
+
const endpoint = `http://127.0.0.1:${port}/json/new?${encodeURIComponent(bossUrl)}`;
|
|
1775
|
+
const attempts = ["PUT", "GET"];
|
|
1776
|
+
let lastError = null;
|
|
1777
|
+
|
|
1778
|
+
for (const method of attempts) {
|
|
1779
|
+
try {
|
|
1780
|
+
const response = await fetch(endpoint, { method });
|
|
1781
|
+
if (response.ok) {
|
|
1782
|
+
let payload = null;
|
|
1783
|
+
try {
|
|
1784
|
+
payload = await response.json();
|
|
1785
|
+
} catch {}
|
|
1786
|
+
return {
|
|
1787
|
+
ok: true,
|
|
1788
|
+
via: "devtools_http_new_tab",
|
|
1789
|
+
method,
|
|
1790
|
+
target_id: payload?.id || null,
|
|
1791
|
+
current_url: payload?.url || bossUrl
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
lastError = new Error(`DevTools /json/new returned ${response.status}`);
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
lastError = error;
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
const fallback = await navigateExistingTargetToBossRecommendCdp(port);
|
|
1801
|
+
if (fallback.ok) return fallback;
|
|
1802
|
+
return {
|
|
1803
|
+
ok: false,
|
|
1804
|
+
via: "devtools_http_new_tab",
|
|
1805
|
+
error: lastError?.message || fallback.error || "Failed to open Boss recommend tab via DevTools"
|
|
1806
|
+
};
|
|
1807
|
+
}
|
|
1808
|
+
|
|
938
1809
|
function getDefaultChromeExecutableCandidates() {
|
|
939
1810
|
const candidates = [process.env.BOSS_RECOMMEND_CHROME_PATH].filter(Boolean);
|
|
940
1811
|
if (process.platform === "win32") {
|
|
@@ -997,16 +1868,118 @@ function resolveDefaultChromeUserDataDir(port) {
|
|
|
997
1868
|
return legacyExisting || sharedPath;
|
|
998
1869
|
}
|
|
999
1870
|
|
|
1871
|
+
function getLaunchChromeTiming(options = {}) {
|
|
1872
|
+
if (options["slow-live"] || options.slowLive) {
|
|
1873
|
+
return {
|
|
1874
|
+
initialTimeoutMs: 5000,
|
|
1875
|
+
inspectTimeoutMs: 20000,
|
|
1876
|
+
pollMs: 1000,
|
|
1877
|
+
settleMs: 2000
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
return {
|
|
1881
|
+
initialTimeoutMs: 1500,
|
|
1882
|
+
inspectTimeoutMs: 6000,
|
|
1883
|
+
pollMs: 800,
|
|
1884
|
+
settleMs: 1000
|
|
1885
|
+
};
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
async function ensureBossRecommendPageReadyCdp(port, options = {}) {
|
|
1889
|
+
const attempts = Number.isFinite(options.attempts) ? Math.max(0, options.attempts) : 3;
|
|
1890
|
+
const inspectTimeoutMs = Number.isFinite(options.inspectTimeoutMs) ? options.inspectTimeoutMs : 6000;
|
|
1891
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1892
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1000;
|
|
1893
|
+
|
|
1894
|
+
let pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
1895
|
+
timeoutMs: inspectTimeoutMs,
|
|
1896
|
+
pollMs
|
|
1897
|
+
});
|
|
1898
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
1899
|
+
const stableState = await verifyRecommendPageStableCdp(port, {
|
|
1900
|
+
settleMs,
|
|
1901
|
+
recheckTimeoutMs: inspectTimeoutMs,
|
|
1902
|
+
pollMs
|
|
1903
|
+
});
|
|
1904
|
+
return {
|
|
1905
|
+
ok: stableState.state === "RECOMMEND_READY",
|
|
1906
|
+
debug_port: port,
|
|
1907
|
+
state: stableState.state,
|
|
1908
|
+
page_state: stableState
|
|
1909
|
+
};
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
if (pageState.state === "LOGIN_REQUIRED") {
|
|
1913
|
+
return {
|
|
1914
|
+
ok: false,
|
|
1915
|
+
debug_port: port,
|
|
1916
|
+
state: pageState.state,
|
|
1917
|
+
page_state: pageState
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
let openAttempt = null;
|
|
1922
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1923
|
+
if (pageState.state === "DEBUG_PORT_UNREACHABLE" || pageState.state === "LOGIN_REQUIRED") break;
|
|
1924
|
+
openAttempt = await openBossRecommendTabCdp(port);
|
|
1925
|
+
await sleepMs(settleMs);
|
|
1926
|
+
pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
1927
|
+
timeoutMs: inspectTimeoutMs,
|
|
1928
|
+
pollMs
|
|
1929
|
+
});
|
|
1930
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
1931
|
+
const stableState = await verifyRecommendPageStableCdp(port, {
|
|
1932
|
+
settleMs,
|
|
1933
|
+
recheckTimeoutMs: inspectTimeoutMs,
|
|
1934
|
+
pollMs
|
|
1935
|
+
});
|
|
1936
|
+
return {
|
|
1937
|
+
ok: stableState.state === "RECOMMEND_READY",
|
|
1938
|
+
debug_port: port,
|
|
1939
|
+
state: stableState.state,
|
|
1940
|
+
page_state: {
|
|
1941
|
+
...stableState,
|
|
1942
|
+
open_attempt: openAttempt
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
if (pageState.state === "LOGIN_REQUIRED") break;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
return {
|
|
1950
|
+
ok: false,
|
|
1951
|
+
debug_port: port,
|
|
1952
|
+
state: pageState.state || "UNKNOWN",
|
|
1953
|
+
page_state: {
|
|
1954
|
+
...pageState,
|
|
1955
|
+
open_attempt: openAttempt
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1000
1960
|
async function launchChrome(options = {}) {
|
|
1001
1961
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1002
1962
|
process.env.BOSS_RECOMMEND_CHROME_PORT = String(port);
|
|
1963
|
+
const timing = getLaunchChromeTiming(options);
|
|
1003
1964
|
|
|
1004
|
-
const initialState = await
|
|
1965
|
+
const initialState = await inspectBossRecommendPageStateCdp(port, {
|
|
1966
|
+
timeoutMs: timing.initialTimeoutMs,
|
|
1967
|
+
pollMs: timing.pollMs
|
|
1968
|
+
});
|
|
1005
1969
|
if (initialState.state !== "DEBUG_PORT_UNREACHABLE") {
|
|
1006
1970
|
console.log(`Reusing existing Chrome debug instance on port ${port}`);
|
|
1007
|
-
const pageState = await
|
|
1971
|
+
const pageState = await ensureBossRecommendPageReadyCdp(port, {
|
|
1972
|
+
attempts: 2,
|
|
1973
|
+
inspectTimeoutMs: timing.inspectTimeoutMs,
|
|
1974
|
+
pollMs: timing.pollMs,
|
|
1975
|
+
settleMs: timing.settleMs
|
|
1976
|
+
});
|
|
1008
1977
|
if (pageState.ok) {
|
|
1009
1978
|
console.log("Boss recommend page is ready.");
|
|
1979
|
+
const frontResult = await bringBossRecommendTabToFrontCdp(port);
|
|
1980
|
+
if (frontResult.ok) {
|
|
1981
|
+
console.log(`CDP methods: ${frontResult.method_log.join(", ") || "none"}`);
|
|
1982
|
+
}
|
|
1010
1983
|
} else {
|
|
1011
1984
|
console.log(pageState.page_state?.message || "Boss recommend page is not ready.");
|
|
1012
1985
|
}
|
|
@@ -1037,9 +2010,19 @@ async function launchChrome(options = {}) {
|
|
|
1037
2010
|
child.unref();
|
|
1038
2011
|
console.log(`Chrome launched with remote debugging port ${port}`);
|
|
1039
2012
|
console.log(`User data dir: ${userDataDir}`);
|
|
1040
|
-
|
|
2013
|
+
await sleepMs(timing.settleMs + 1200);
|
|
2014
|
+
const pageState = await ensureBossRecommendPageReadyCdp(port, {
|
|
2015
|
+
attempts: 6,
|
|
2016
|
+
inspectTimeoutMs: timing.inspectTimeoutMs,
|
|
2017
|
+
pollMs: timing.pollMs,
|
|
2018
|
+
settleMs: timing.settleMs
|
|
2019
|
+
});
|
|
1041
2020
|
if (pageState.ok) {
|
|
1042
2021
|
console.log("Boss recommend page is ready.");
|
|
2022
|
+
const frontResult = await bringBossRecommendTabToFrontCdp(port);
|
|
2023
|
+
if (frontResult.ok) {
|
|
2024
|
+
console.log(`CDP methods: ${frontResult.method_log.join(", ") || "none"}`);
|
|
2025
|
+
}
|
|
1043
2026
|
} else {
|
|
1044
2027
|
console.log(pageState.page_state?.message || "Boss recommend page is not ready.");
|
|
1045
2028
|
}
|
|
@@ -1051,99 +2034,37 @@ function getCalibrationTimeoutMs(options = {}) {
|
|
|
1051
2034
|
return Math.max(5000, parsed);
|
|
1052
2035
|
}
|
|
1053
2036
|
|
|
1054
|
-
|
|
2037
|
+
function buildUnsupportedCalibrateResponse(options = {}) {
|
|
1055
2038
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1056
2039
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1057
|
-
process.env.BOSS_RECOMMEND_CHROME_PORT = String(port);
|
|
1058
|
-
persistDebugPortSelection(port, options);
|
|
1059
2040
|
const timeoutMs = getCalibrationTimeoutMs(options);
|
|
1060
2041
|
const outputPath = String(options.output || "").trim()
|
|
1061
2042
|
? path.resolve(String(options.output))
|
|
1062
2043
|
: null;
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
if (preState.state === "DEBUG_PORT_UNREACHABLE") {
|
|
1075
|
-
await launchChrome({ ...options, port: String(port) });
|
|
1076
|
-
if (process.exitCode && process.exitCode !== 0) {
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
console.log(`Detected existing Chrome debug instance on port ${port}; calibration will reuse it.`);
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
const pageReady = await ensureBossRecommendPageReady(workspaceRoot, {
|
|
1084
|
-
port,
|
|
1085
|
-
attempts: 4
|
|
1086
|
-
});
|
|
1087
|
-
if (pageReady.ok) {
|
|
1088
|
-
const switchResult = await switchRecommendTab(workspaceRoot, {
|
|
1089
|
-
port,
|
|
1090
|
-
target_status: "3"
|
|
1091
|
-
});
|
|
1092
|
-
if (switchResult?.ok) {
|
|
1093
|
-
console.log("已自动切换到推荐页“精选”tab,请直接在当前页面打开人选详情并完成收藏/取消收藏。");
|
|
1094
|
-
} else {
|
|
1095
|
-
console.log("未能自动切换到“精选”tab,请手动切换到精选后再执行收藏/取消收藏。");
|
|
1096
|
-
}
|
|
1097
|
-
} else {
|
|
1098
|
-
console.log("未能确认 recommend 页面就绪,请手动进入推荐页并切换到精选 tab 后再继续校准操作。");
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
console.log(`等待你打开“精选”候选人详情页(最多 ${Math.round(timeoutMs / 1000)} 秒),检测到后自动开始校准监听...`);
|
|
1102
|
-
const detailReady = await waitRecommendFeaturedDetailReady(workspaceRoot, {
|
|
1103
|
-
port,
|
|
1104
|
-
timeoutMs,
|
|
1105
|
-
pollMs: 400
|
|
1106
|
-
});
|
|
1107
|
-
if (!detailReady.ok) {
|
|
1108
|
-
console.error(detailReady.message || "未检测到可校准的精选详情页。");
|
|
1109
|
-
console.error("请先打开任意精选候选人详情页并保持在前台,然后重新运行 calibrate。");
|
|
1110
|
-
process.exitCode = 1;
|
|
1111
|
-
return;
|
|
1112
|
-
}
|
|
1113
|
-
const detailSource = detailReady.detail_state?.source || "unknown";
|
|
1114
|
-
const detailSelector = detailReady.detail_state?.selector || "unknown";
|
|
1115
|
-
console.log(`已检测到详情页(source=${detailSource}, selector=${detailSelector}),即将启动校准脚本。`);
|
|
1116
|
-
await new Promise((resolve) => setTimeout(resolve, 600));
|
|
1117
|
-
|
|
1118
|
-
const result = await runRecommendCalibration(workspaceRoot, {
|
|
2044
|
+
return {
|
|
2045
|
+
status: "FAILED",
|
|
2046
|
+
error: {
|
|
2047
|
+
code: calibrateUnsupportedCode,
|
|
2048
|
+
message: "boss-recommend-mcp calibrate is fenced during the CDP-only rewrite because the old calibration route delegated to page-JS/Runtime-based adapter behavior and an external calibration script. A replacement must use CDP DOM/Input only and pass a live safe calibration gate before this command is re-enabled.",
|
|
2049
|
+
retryable: false
|
|
2050
|
+
},
|
|
2051
|
+
cdp_only: true,
|
|
2052
|
+
runtime_evaluate_used: false,
|
|
2053
|
+
method_summary: {},
|
|
2054
|
+
method_log: [],
|
|
1119
2055
|
port,
|
|
2056
|
+
timeout_ms: timeoutMs,
|
|
1120
2057
|
output: outputPath,
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
if (!text) return;
|
|
1126
|
-
if (event?.stream === "stderr") {
|
|
1127
|
-
process.stderr.write(text);
|
|
1128
|
-
} else {
|
|
1129
|
-
process.stdout.write(text);
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
2058
|
+
calibration_resolution: getFeaturedCalibrationResolutionLocal(workspaceRoot),
|
|
2059
|
+
guidance: {
|
|
2060
|
+
current_workaround: "Use an existing favorite-calibration.json if present; `doctor --page-scope featured` will report whether it is usable.",
|
|
2061
|
+
next_development_task: "Implement CDP-only featured detail/action discovery and a user-approved live calibration gate before restoring this command."
|
|
1132
2062
|
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
console.log(`Calibration saved: ${result.calibration_path}`);
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
1138
2065
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
if (result.calibration_script_path) {
|
|
1142
|
-
console.error(`Calibration script: ${result.calibration_script_path}`);
|
|
1143
|
-
}
|
|
1144
|
-
if (result.calibration_path) {
|
|
1145
|
-
console.error(`Calibration target: ${result.calibration_path}`);
|
|
1146
|
-
}
|
|
2066
|
+
async function calibrate(options = {}) {
|
|
2067
|
+
printJson(buildUnsupportedCalibrateResponse(options));
|
|
1147
2068
|
process.exitCode = 1;
|
|
1148
2069
|
}
|
|
1149
2070
|
|
|
@@ -1215,11 +2136,22 @@ async function printDoctor(options = {}) {
|
|
|
1215
2136
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1216
2137
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1217
2138
|
const pageScope = normalizePageScope(options["page-scope"] || options.pageScope) || "recommend";
|
|
1218
|
-
const preflight =
|
|
2139
|
+
const preflight = runPipelinePreflightLocal(workspaceRoot, { pageScope });
|
|
1219
2140
|
const checks = preflight.checks.slice();
|
|
1220
|
-
const configResolution =
|
|
1221
|
-
const calibrationResolution =
|
|
1222
|
-
const
|
|
2141
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
2142
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(workspaceRoot);
|
|
2143
|
+
const timing = getLaunchChromeTiming(options);
|
|
2144
|
+
let pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
2145
|
+
timeoutMs: options["slow-live"] || options.slowLive ? timing.initialTimeoutMs : 2000,
|
|
2146
|
+
pollMs: options["slow-live"] || options.slowLive ? timing.pollMs : 500
|
|
2147
|
+
});
|
|
2148
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
2149
|
+
pageState = await verifyRecommendPageStableCdp(port, {
|
|
2150
|
+
settleMs: options["slow-live"] || options.slowLive ? timing.settleMs : 800,
|
|
2151
|
+
recheckTimeoutMs: options["slow-live"] || options.slowLive ? timing.inspectTimeoutMs : 3000,
|
|
2152
|
+
pollMs: options["slow-live"] || options.slowLive ? timing.pollMs : 500
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
1223
2155
|
const resolvedConfigPath = configResolution.resolved_path || configResolution.writable_path;
|
|
1224
2156
|
const userConfigExists = (
|
|
1225
2157
|
(resolvedConfigPath && fs.existsSync(resolvedConfigPath))
|
|
@@ -1245,10 +2177,11 @@ async function printDoctor(options = {}) {
|
|
|
1245
2177
|
checks.push({
|
|
1246
2178
|
key: "featured_calibration_script",
|
|
1247
2179
|
ok: Boolean(calibrationResolution.calibration_script_path),
|
|
2180
|
+
optional: true,
|
|
1248
2181
|
path: calibrationResolution.calibration_script_path,
|
|
1249
2182
|
message: calibrationResolution.calibration_script_path
|
|
1250
2183
|
? "已检测到 boss-recruit-mcp 校准脚本。"
|
|
1251
|
-
: "未检测到 boss-recruit-mcp
|
|
2184
|
+
: "未检测到 boss-recruit-mcp 校准脚本;CDP-only package 已禁用旧精选页自动校准。"
|
|
1252
2185
|
});
|
|
1253
2186
|
checks.push({
|
|
1254
2187
|
key: "featured_calibration_file",
|
|
@@ -1322,7 +2255,7 @@ async function printDoctor(options = {}) {
|
|
|
1322
2255
|
}
|
|
1323
2256
|
|
|
1324
2257
|
printJson({
|
|
1325
|
-
ok: checks.every((item) => item.ok),
|
|
2258
|
+
ok: checks.every((item) => item.ok || item.optional),
|
|
1326
2259
|
port,
|
|
1327
2260
|
checks,
|
|
1328
2261
|
config_resolution: configResolution,
|
|
@@ -1340,8 +2273,8 @@ async function printDoctor(options = {}) {
|
|
|
1340
2273
|
function printPaths() {
|
|
1341
2274
|
const codexHome = getCodexHome();
|
|
1342
2275
|
const stateHome = getStateHome();
|
|
1343
|
-
const calibrationResolution =
|
|
1344
|
-
const bossChatRuntime =
|
|
2276
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(process.cwd());
|
|
2277
|
+
const bossChatRuntime = resolveCdpBossChatRuntimeLayout(getWorkspaceRoot({}));
|
|
1345
2278
|
console.log(`package_root=${packageRoot}`);
|
|
1346
2279
|
console.log(`skill_sources=${bundledSkillNames.map((name) => getSkillSourceDir(name)).join(" | ")}`);
|
|
1347
2280
|
console.log(`codex_home=${codexHome}`);
|
|
@@ -1362,26 +2295,28 @@ function printHelp() {
|
|
|
1362
2295
|
console.log("Usage:");
|
|
1363
2296
|
console.log(" boss-recommend-mcp Start the MCP server");
|
|
1364
2297
|
console.log(" boss-recommend-mcp start Start the MCP server");
|
|
1365
|
-
console.log(" boss-recommend-mcp run
|
|
1366
|
-
console.log(" boss-recommend-mcp
|
|
1367
|
-
console.log(" boss-recommend-mcp
|
|
1368
|
-
console.log(" boss-recommend-mcp install
|
|
2298
|
+
console.log(" boss-recommend-mcp run Disabled until the one-shot CLI has a CDP-only async replacement");
|
|
2299
|
+
console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
|
|
2300
|
+
console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
|
|
2301
|
+
console.log(" boss-recommend-mcp install Install/migrate skills and MCP configs; replaces legacy Boss MCP routes (supports --agent trae-cn/openclaw/...)");
|
|
2302
|
+
console.log(" boss-recommend-mcp install-skill Install bundled Codex skills (recommend/recruit/chat)");
|
|
1369
2303
|
console.log(" boss-recommend-mcp init-config Create screening-config.json if missing (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
|
|
1370
2304
|
console.log(" boss-recommend-mcp config set Write baseUrl/apiKey/model (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
|
|
1371
2305
|
console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
|
|
1372
2306
|
console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw");
|
|
1373
2307
|
console.log(" boss-recommend-mcp doctor Check config/runtime/calibration prerequisites (supports --agent trae-cn/cursor/...)");
|
|
1374
|
-
console.log(" boss-recommend-mcp calibrate
|
|
2308
|
+
console.log(" boss-recommend-mcp calibrate Disabled until CDP-only featured calibration is live-verified");
|
|
1375
2309
|
console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
|
|
1376
2310
|
console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
|
|
1377
2311
|
console.log("");
|
|
1378
2312
|
console.log("Run command:");
|
|
1379
|
-
console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\"
|
|
1380
|
-
console.log(" boss-recommend-mcp
|
|
2313
|
+
console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" # returns RECOMMEND_CLI_RUN_UNSUPPORTED_CDP_ONLY during rewrite; use MCP start_recommend_pipeline_run");
|
|
2314
|
+
console.log(" boss-recommend-mcp list-jobs --slow-live --port 9222");
|
|
2315
|
+
console.log(" boss-recommend-mcp chat prepare-run --slow-live --port 9222 # CDP-only preflight; start runs through MCP start_boss_chat_run");
|
|
1381
2316
|
console.log(" boss-recommend-mcp config set --base-url <url> --api-key <key> --model <model> [--thinking-level off|low|medium|high|current] [--openai-organization <id>] [--openai-project <id>]");
|
|
1382
2317
|
console.log(" boss-recommend-mcp install --agent trae-cn");
|
|
1383
2318
|
console.log(" boss-recommend-mcp doctor --agent trae-cn --page-scope featured");
|
|
1384
|
-
console.log(" boss-recommend-mcp calibrate --port 9222
|
|
2319
|
+
console.log(" boss-recommend-mcp calibrate --port 9222 # returns CALIBRATE_UNSUPPORTED_CDP_ONLY during rewrite");
|
|
1385
2320
|
}
|
|
1386
2321
|
|
|
1387
2322
|
function printMcpConfig(options = {}) {
|
|
@@ -1397,10 +2332,10 @@ function printMcpConfig(options = {}) {
|
|
|
1397
2332
|
}
|
|
1398
2333
|
}
|
|
1399
2334
|
|
|
1400
|
-
function installAll(options = {}) {
|
|
1401
|
-
const runtimeDirsResult = ensureRuntimeDirectories(options);
|
|
2335
|
+
async function installAll(options = {}) {
|
|
2336
|
+
const runtimeDirsResult = await ensureRuntimeDirectories(options);
|
|
1402
2337
|
const skillResults = installSkill();
|
|
1403
|
-
const configResult = ensureUserConfig(options);
|
|
2338
|
+
const configResult = await ensureUserConfig(options);
|
|
1404
2339
|
const mcpTemplateResult = writeMcpConfigFiles({ client: "all" });
|
|
1405
2340
|
const externalMcpResult = installExternalMcpConfigs(options);
|
|
1406
2341
|
const externalSkillResult = mirrorSkillToExternalDirs(options);
|
|
@@ -1440,7 +2375,14 @@ function installAll(options = {}) {
|
|
|
1440
2375
|
console.log(`Auto-configured external MCP files: ${externalMcpResult.applied.length}`);
|
|
1441
2376
|
for (const item of externalMcpResult.applied) {
|
|
1442
2377
|
const action = item.created ? "created" : item.updated ? "updated" : "unchanged";
|
|
1443
|
-
|
|
2378
|
+
const migrated = Array.isArray(item.migrated_legacy_servers) && item.migrated_legacy_servers.length > 0
|
|
2379
|
+
? `; migrated legacy servers: ${item.migrated_legacy_servers.join(", ")}`
|
|
2380
|
+
: "";
|
|
2381
|
+
const backup = item.backup_file ? `; backup: ${item.backup_file}` : "";
|
|
2382
|
+
console.log(`- ${item.file} (${action}${migrated}${backup})`);
|
|
2383
|
+
}
|
|
2384
|
+
for (const item of externalMcpResult.skipped) {
|
|
2385
|
+
console.warn(`External MCP warning: ${item.file} -> ${item.reason}`);
|
|
1444
2386
|
}
|
|
1445
2387
|
} else {
|
|
1446
2388
|
console.log("No external MCP config target detected. Set BOSS_RECOMMEND_MCP_CONFIG_TARGETS to auto-configure custom agents.");
|
|
@@ -1448,7 +2390,10 @@ function installAll(options = {}) {
|
|
|
1448
2390
|
if (externalSkillResult.baseDirs.length > 0) {
|
|
1449
2391
|
console.log(`Mirrored skill to external dirs: ${externalSkillResult.mirrored.length}`);
|
|
1450
2392
|
for (const item of externalSkillResult.mirrored) {
|
|
1451
|
-
console.log(`- ${item.target_dir}`);
|
|
2393
|
+
console.log(`- ${item.target_dir}${item.replaced_legacy ? " (replaced legacy skill)" : ""}`);
|
|
2394
|
+
}
|
|
2395
|
+
for (const item of externalSkillResult.skipped) {
|
|
2396
|
+
console.warn(`External skill warning: ${item.base_dir} / ${item.skill} -> ${item.reason}`);
|
|
1452
2397
|
}
|
|
1453
2398
|
} else {
|
|
1454
2399
|
console.log("No external skill dir detected. Set BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS to mirror skill for non-Codex agents.");
|
|
@@ -1458,26 +2403,78 @@ function installAll(options = {}) {
|
|
|
1458
2403
|
}
|
|
1459
2404
|
}
|
|
1460
2405
|
|
|
1461
|
-
|
|
2406
|
+
function buildUnsupportedRecommendCliRunResponse({
|
|
2407
|
+
instruction,
|
|
2408
|
+
confirmation,
|
|
2409
|
+
overrides,
|
|
2410
|
+
followUp,
|
|
2411
|
+
workspaceRoot,
|
|
2412
|
+
port
|
|
2413
|
+
} = {}) {
|
|
2414
|
+
return {
|
|
2415
|
+
status: "FAILED",
|
|
2416
|
+
error: {
|
|
2417
|
+
code: recommendCliRunUnsupportedCode,
|
|
2418
|
+
message: "boss-recommend-mcp run is fenced during the CDP-only rewrite because the old one-shot CLI route can reach page-JS/Runtime-based orchestration. Use the MCP tool start_recommend_pipeline_run for CDP-only recommend runs until a live-verified one-shot CLI replacement exists.",
|
|
2419
|
+
retryable: false
|
|
2420
|
+
},
|
|
2421
|
+
cdp_only: true,
|
|
2422
|
+
runtime_evaluate_used: false,
|
|
2423
|
+
method_summary: {},
|
|
2424
|
+
method_log: [],
|
|
2425
|
+
run_mode: "mcp_async_required",
|
|
2426
|
+
port,
|
|
2427
|
+
target_url: bossUrl,
|
|
2428
|
+
input: {
|
|
2429
|
+
workspace_root: workspaceRoot,
|
|
2430
|
+
instruction,
|
|
2431
|
+
confirmation: confirmation ?? null,
|
|
2432
|
+
overrides: overrides ?? null,
|
|
2433
|
+
follow_up: followUp ?? null
|
|
2434
|
+
},
|
|
2435
|
+
guidance: {
|
|
2436
|
+
recommended_tool: "start_recommend_pipeline_run",
|
|
2437
|
+
next_development_task: "Implement a CDP-only CLI wrapper that starts a durable shared run-service session, persists run state, and exits only after its live gate proves no Runtime.* methods are reachable."
|
|
2438
|
+
}
|
|
2439
|
+
};
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
async function runPipelineOnce(options = {}) {
|
|
1462
2443
|
const instruction = getRunInstruction(options);
|
|
1463
2444
|
const confirmation = getRunConfirmation(options);
|
|
1464
2445
|
const overrides = getRunOverrides(options);
|
|
1465
2446
|
const followUp = getRunFollowUp(options);
|
|
1466
2447
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1467
|
-
const
|
|
1468
|
-
if (explicitPort) {
|
|
1469
|
-
process.env.BOSS_RECOMMEND_CHROME_PORT = String(explicitPort);
|
|
1470
|
-
persistDebugPortSelection(explicitPort, options);
|
|
1471
|
-
}
|
|
2448
|
+
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1472
2449
|
|
|
1473
|
-
|
|
2450
|
+
printJson(buildUnsupportedRecommendCliRunResponse({
|
|
1474
2451
|
workspaceRoot,
|
|
1475
2452
|
instruction,
|
|
1476
2453
|
confirmation,
|
|
1477
2454
|
overrides,
|
|
1478
|
-
followUp
|
|
1479
|
-
|
|
1480
|
-
|
|
2455
|
+
followUp,
|
|
2456
|
+
port
|
|
2457
|
+
}));
|
|
2458
|
+
process.exitCode = 1;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2461
|
+
function buildRecommendJobListCliInput(options = {}) {
|
|
2462
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2463
|
+
const host = String(options.host || "").trim();
|
|
2464
|
+
return {
|
|
2465
|
+
host: host || undefined,
|
|
2466
|
+
port: parsePositivePort(options.port),
|
|
2467
|
+
target_url_includes: targetUrlIncludes || undefined,
|
|
2468
|
+
allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
|
|
2469
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2473
|
+
async function listRecommendJobsCli(options = {}) {
|
|
2474
|
+
printJson(await listRecommendJobsTool({
|
|
2475
|
+
workspaceRoot: getWorkspaceRoot(options),
|
|
2476
|
+
args: buildRecommendJobListCliInput(options)
|
|
2477
|
+
}));
|
|
1481
2478
|
}
|
|
1482
2479
|
|
|
1483
2480
|
function buildBossChatCliInput(options = {}) {
|
|
@@ -1487,6 +2484,8 @@ function buildBossChatCliInput(options = {}) {
|
|
|
1487
2484
|
?? options.greetingText
|
|
1488
2485
|
?? options.greeting;
|
|
1489
2486
|
const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
|
|
2487
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2488
|
+
const host = String(options.host || "").trim();
|
|
1490
2489
|
return {
|
|
1491
2490
|
profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
|
|
1492
2491
|
job: typeof options.job === "string" ? options.job.trim() : undefined,
|
|
@@ -1494,7 +2493,14 @@ function buildBossChatCliInput(options = {}) {
|
|
|
1494
2493
|
criteria: typeof options.criteria === "string" ? options.criteria.trim() : undefined,
|
|
1495
2494
|
greeting_text: greetingText || undefined,
|
|
1496
2495
|
target_count: parseBossChatTargetCountOption(options.targetCount || options["target-count"] || options.target_count),
|
|
2496
|
+
host: host || undefined,
|
|
1497
2497
|
port: parsePositivePort(options.port),
|
|
2498
|
+
target_url_includes: targetUrlIncludes || undefined,
|
|
2499
|
+
allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
|
|
2500
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true,
|
|
2501
|
+
detail_limit: parseNonNegativeInteger(options["detail-limit"] ?? options.detail_limit),
|
|
2502
|
+
delay_ms: parseNonNegativeInteger(options["delay-ms"] ?? options.delay_ms),
|
|
2503
|
+
max_candidates: parseNonNegativeInteger(options["max-candidates"] ?? options.max_candidates),
|
|
1498
2504
|
dry_run: options["dry-run"] === true || options.dryRun === true,
|
|
1499
2505
|
no_state: options["no-state"] === true || options.noState === true,
|
|
1500
2506
|
safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
|
|
@@ -1509,67 +2515,69 @@ function getBossChatCliRunTarget(options = {}) {
|
|
|
1509
2515
|
};
|
|
1510
2516
|
}
|
|
1511
2517
|
|
|
2518
|
+
function buildUnsupportedBossChatCliStartResponse(subcommand) {
|
|
2519
|
+
return {
|
|
2520
|
+
status: "FAILED",
|
|
2521
|
+
error: {
|
|
2522
|
+
code: bossChatCliUnsupportedStartCode,
|
|
2523
|
+
message: `boss-recommend-mcp chat ${subcommand} is fenced during the CDP-only rewrite because a one-shot CLI process cannot keep the live CDP session and run lifecycle alive after it exits. Use the MCP tool start_boss_chat_run, or the live chat harness, for CDP-only chat runs.`,
|
|
2524
|
+
retryable: false
|
|
2525
|
+
},
|
|
2526
|
+
cdp_only: true,
|
|
2527
|
+
runtime_evaluate_used: false,
|
|
2528
|
+
method_summary: {},
|
|
2529
|
+
method_log: []
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
|
|
1512
2533
|
async function runBossChatCliCommand(subcommand, options = {}) {
|
|
1513
2534
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
2535
|
+
const input = buildBossChatCliInput(options);
|
|
1514
2536
|
if (subcommand === "health-check") {
|
|
1515
|
-
printJson(
|
|
1516
|
-
port: parsePositivePort(options.port)
|
|
1517
|
-
}));
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
if (subcommand === "prepare-run") {
|
|
1522
|
-
printJson(await prepareBossChatRun({
|
|
2537
|
+
printJson(await bossChatHealthCheckTool({
|
|
1523
2538
|
workspaceRoot,
|
|
1524
|
-
|
|
2539
|
+
args: input
|
|
1525
2540
|
}));
|
|
1526
2541
|
return;
|
|
1527
2542
|
}
|
|
1528
2543
|
|
|
1529
|
-
if (subcommand === "run") {
|
|
1530
|
-
printJson(await
|
|
2544
|
+
if (subcommand === "prepare-run") {
|
|
2545
|
+
printJson(await prepareBossChatRunTool({
|
|
1531
2546
|
workspaceRoot,
|
|
1532
|
-
|
|
2547
|
+
args: input
|
|
1533
2548
|
}));
|
|
1534
2549
|
return;
|
|
1535
2550
|
}
|
|
1536
2551
|
|
|
1537
|
-
if (subcommand === "start-run") {
|
|
1538
|
-
printJson(
|
|
1539
|
-
workspaceRoot,
|
|
1540
|
-
input: buildBossChatCliInput(options)
|
|
1541
|
-
}));
|
|
2552
|
+
if (subcommand === "run" || subcommand === "start-run") {
|
|
2553
|
+
printJson(buildUnsupportedBossChatCliStartResponse(subcommand));
|
|
1542
2554
|
return;
|
|
1543
2555
|
}
|
|
1544
2556
|
|
|
1545
2557
|
if (subcommand === "get-run") {
|
|
1546
|
-
printJson(
|
|
1547
|
-
|
|
1548
|
-
input: getBossChatCliRunTarget(options)
|
|
2558
|
+
printJson(getBossChatRunTool({
|
|
2559
|
+
args: getBossChatCliRunTarget(options)
|
|
1549
2560
|
}));
|
|
1550
2561
|
return;
|
|
1551
2562
|
}
|
|
1552
2563
|
|
|
1553
2564
|
if (subcommand === "pause-run") {
|
|
1554
|
-
printJson(
|
|
1555
|
-
|
|
1556
|
-
input: getBossChatCliRunTarget(options)
|
|
2565
|
+
printJson(pauseBossChatRunTool({
|
|
2566
|
+
args: getBossChatCliRunTarget(options)
|
|
1557
2567
|
}));
|
|
1558
2568
|
return;
|
|
1559
2569
|
}
|
|
1560
2570
|
|
|
1561
2571
|
if (subcommand === "resume-run") {
|
|
1562
|
-
printJson(
|
|
1563
|
-
|
|
1564
|
-
input: getBossChatCliRunTarget(options)
|
|
2572
|
+
printJson(resumeBossChatRunTool({
|
|
2573
|
+
args: getBossChatCliRunTarget(options)
|
|
1565
2574
|
}));
|
|
1566
2575
|
return;
|
|
1567
2576
|
}
|
|
1568
2577
|
|
|
1569
2578
|
if (subcommand === "cancel-run") {
|
|
1570
|
-
printJson(
|
|
1571
|
-
|
|
1572
|
-
input: getBossChatCliRunTarget(options)
|
|
2579
|
+
printJson(cancelBossChatRunTool({
|
|
2580
|
+
args: getBossChatCliRunTarget(options)
|
|
1573
2581
|
}));
|
|
1574
2582
|
return;
|
|
1575
2583
|
}
|
|
@@ -1601,6 +2609,23 @@ export async function runCli(argv = process.argv) {
|
|
|
1601
2609
|
process.exitCode = 1;
|
|
1602
2610
|
}
|
|
1603
2611
|
break;
|
|
2612
|
+
case "list-jobs":
|
|
2613
|
+
case "jobs":
|
|
2614
|
+
case "recommend-jobs":
|
|
2615
|
+
try {
|
|
2616
|
+
await listRecommendJobsCli(options);
|
|
2617
|
+
} catch (error) {
|
|
2618
|
+
printJson({
|
|
2619
|
+
status: "FAILED",
|
|
2620
|
+
error: {
|
|
2621
|
+
code: "RECOMMEND_JOB_LIST_CLI_FAILED",
|
|
2622
|
+
message: error.message || "Failed to list recommend jobs",
|
|
2623
|
+
retryable: true
|
|
2624
|
+
}
|
|
2625
|
+
});
|
|
2626
|
+
process.exitCode = 1;
|
|
2627
|
+
}
|
|
2628
|
+
break;
|
|
1604
2629
|
case "chat":
|
|
1605
2630
|
try {
|
|
1606
2631
|
const chatSubcommand = String(argv[3] || "").trim().toLowerCase();
|
|
@@ -1620,7 +2645,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1620
2645
|
break;
|
|
1621
2646
|
case "install":
|
|
1622
2647
|
try {
|
|
1623
|
-
installAll(options);
|
|
2648
|
+
await installAll(options);
|
|
1624
2649
|
} catch (error) {
|
|
1625
2650
|
console.error(error.message || "Install failed.");
|
|
1626
2651
|
process.exitCode = 1;
|
|
@@ -1632,8 +2657,8 @@ export async function runCli(argv = process.argv) {
|
|
|
1632
2657
|
}
|
|
1633
2658
|
break;
|
|
1634
2659
|
case "init-config": {
|
|
1635
|
-
const runtimeDirsResult = ensureRuntimeDirectories(options);
|
|
1636
|
-
const result = ensureUserConfig(options);
|
|
2660
|
+
const runtimeDirsResult = await ensureRuntimeDirectories(options);
|
|
2661
|
+
const result = await ensureUserConfig(options);
|
|
1637
2662
|
console.log(
|
|
1638
2663
|
`Runtime directories prepared: created=${runtimeDirsResult.created.length}, existing=${runtimeDirsResult.existed.length}, failed=${runtimeDirsResult.failed.length}`
|
|
1639
2664
|
);
|
|
@@ -1657,7 +2682,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1657
2682
|
}
|
|
1658
2683
|
case "set-port": {
|
|
1659
2684
|
try {
|
|
1660
|
-
const result = setDebugPort(options);
|
|
2685
|
+
const result = await setDebugPort(options);
|
|
1661
2686
|
console.log(`Preferred debug port saved: ${result.port}`);
|
|
1662
2687
|
console.log(`Updated config: ${result.configPath}`);
|
|
1663
2688
|
console.log("Port priority for runtime commands: --port > BOSS_RECOMMEND_CHROME_PORT > screening-config.json.debugPort > 9222");
|
|
@@ -1668,8 +2693,8 @@ export async function runCli(argv = process.argv) {
|
|
|
1668
2693
|
break;
|
|
1669
2694
|
}
|
|
1670
2695
|
case "set-config": {
|
|
1671
|
-
|
|
1672
|
-
|
|
2696
|
+
try {
|
|
2697
|
+
const result = await setScreeningConfig(options);
|
|
1673
2698
|
console.log(`screening-config.json updated: ${result.path}`);
|
|
1674
2699
|
} catch (error) {
|
|
1675
2700
|
console.error(error.message || "Failed to write screening-config.json.");
|
|
@@ -1682,7 +2707,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1682
2707
|
if (!sub || sub.startsWith("--") || sub === "set") {
|
|
1683
2708
|
const configOptions = sub === "set" ? parseOptions(argv.slice(4)) : options;
|
|
1684
2709
|
try {
|
|
1685
|
-
const result = setScreeningConfig(configOptions);
|
|
2710
|
+
const result = await setScreeningConfig(configOptions);
|
|
1686
2711
|
console.log(`screening-config.json updated: ${result.path}`);
|
|
1687
2712
|
} catch (error) {
|
|
1688
2713
|
console.error(error.message || "Failed to write screening-config.json.");
|
|
@@ -1712,7 +2737,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1712
2737
|
await launchChrome(options);
|
|
1713
2738
|
break;
|
|
1714
2739
|
case "where":
|
|
1715
|
-
printPaths();
|
|
2740
|
+
await printPaths();
|
|
1716
2741
|
break;
|
|
1717
2742
|
case "help":
|
|
1718
2743
|
case "--help":
|
|
@@ -1727,18 +2752,22 @@ export async function runCli(argv = process.argv) {
|
|
|
1727
2752
|
}
|
|
1728
2753
|
|
|
1729
2754
|
export const __testables = {
|
|
2755
|
+
buildRecommendJobListCliInput,
|
|
1730
2756
|
buildBossChatCliInput,
|
|
1731
2757
|
buildDefaultMcpArgs,
|
|
1732
2758
|
buildMcpLaunchConfig,
|
|
2759
|
+
buildUnsupportedRecommendCliRunResponse,
|
|
1733
2760
|
collectRuntimeDirectories,
|
|
1734
|
-
ensureBossChatRuntimeReady,
|
|
2761
|
+
ensureBossChatRuntimeReady: ensureBossChatRuntimeReadyLocal,
|
|
1735
2762
|
ensureRuntimeDirectories,
|
|
1736
2763
|
getBossChatCliRunTarget,
|
|
1737
2764
|
getDefaultMcpPackageSpecifier,
|
|
1738
2765
|
getRunFollowUp,
|
|
2766
|
+
inspectMcpServerEntries,
|
|
1739
2767
|
installSkill,
|
|
1740
2768
|
isInstalledPackageRoot,
|
|
1741
|
-
|
|
2769
|
+
mergeMcpServerConfigFile,
|
|
2770
|
+
resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
|
|
1742
2771
|
runBossChatCliCommand,
|
|
1743
2772
|
runPipelineOnce
|
|
1744
2773
|
};
|