@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.0
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 +53 -33
- package/package.json +61 -9
- package/skills/boss-recommend-pipeline/SKILL.md +4 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1095 -196
- 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 +67 -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 +68 -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,32 +2,33 @@ 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);
|
|
@@ -37,19 +38,26 @@ const skillName = "boss-recommend-pipeline";
|
|
|
37
38
|
const bundledSkillNames = [skillName, "boss-chat"];
|
|
38
39
|
const exampleConfigPath = path.join(packageRoot, "config", "screening-config.example.json");
|
|
39
40
|
const bossUrl = "https://www.zhipin.com/web/chat/recommend";
|
|
41
|
+
const bossLoginUrl = "https://www.zhipin.com/web/user/?ka=bticket";
|
|
40
42
|
const chromeOnboardingUrlPattern = /^chrome:\/\/(welcome|intro|newtab|signin|history-sync|settings\/syncSetup)/i;
|
|
43
|
+
const bossLoginUrlPattern = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com)/i;
|
|
44
|
+
const bossLoginTitlePattern = /登录|signin|扫码登录|BOSS直聘登录/i;
|
|
41
45
|
const supportedMcpClients = ["generic", "cursor", "trae", "claudecode", "openclaw"];
|
|
42
46
|
const defaultMcpServerName = "boss-recommend";
|
|
43
47
|
const defaultMcpCommand = "npx";
|
|
44
48
|
const recommendMcpPackageName = "@reconcrap/boss-recommend-mcp";
|
|
45
49
|
const recommendMcpBinaryName = "boss-recommend-mcp";
|
|
46
|
-
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h"]);
|
|
50
|
+
const autoSyncSkipCommands = new Set(["install", "install-skill", "where", "help", "--help", "-h", "list-jobs", "jobs", "recommend-jobs"]);
|
|
47
51
|
const externalMcpTargetsEnv = "BOSS_RECOMMEND_MCP_CONFIG_TARGETS";
|
|
48
52
|
const externalSkillDirsEnv = "BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS";
|
|
49
53
|
const installConfigDefaults = Object.freeze({
|
|
50
54
|
llmThinkingLevel: "low",
|
|
51
55
|
humanRestEnabled: false
|
|
52
56
|
});
|
|
57
|
+
const bossChatRuntimeChildDirs = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
|
|
58
|
+
const bossChatCliUnsupportedStartCode = "CHAT_CLI_ASYNC_UNSUPPORTED_CDP_ONLY";
|
|
59
|
+
const calibrateUnsupportedCode = "CALIBRATE_UNSUPPORTED_CDP_ONLY";
|
|
60
|
+
const recommendCliRunUnsupportedCode = "RECOMMEND_CLI_RUN_UNSUPPORTED_CDP_ONLY";
|
|
53
61
|
|
|
54
62
|
function getSkillSourceDir(name = skillName) {
|
|
55
63
|
return path.join(packageRoot, "skills", name);
|
|
@@ -113,6 +121,125 @@ function pathExists(targetPath) {
|
|
|
113
121
|
}
|
|
114
122
|
}
|
|
115
123
|
|
|
124
|
+
function normalizeText(value) {
|
|
125
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isUnsafeRuntimeDirectory(targetPath) {
|
|
129
|
+
const resolved = path.resolve(String(targetPath || ""));
|
|
130
|
+
if (!resolved) return true;
|
|
131
|
+
if (path.parse(resolved).root.toLowerCase() === resolved.toLowerCase()) return true;
|
|
132
|
+
const normalized = resolved.replace(/\\/g, "/").toLowerCase();
|
|
133
|
+
if (process.platform === "win32") {
|
|
134
|
+
return (
|
|
135
|
+
normalized.endsWith("/windows")
|
|
136
|
+
|| normalized.endsWith("/windows/system32")
|
|
137
|
+
|| normalized.endsWith("/windows/syswow64")
|
|
138
|
+
|| normalized.endsWith("/program files")
|
|
139
|
+
|| normalized.endsWith("/program files (x86)")
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return ["/system", "/usr", "/bin", "/sbin"].some((prefix) => (
|
|
143
|
+
normalized === prefix || normalized.startsWith(`${prefix}/`)
|
|
144
|
+
));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function getBossChatRuntimeDirectories(runtime) {
|
|
148
|
+
return [
|
|
149
|
+
runtime.data_dir,
|
|
150
|
+
...bossChatRuntimeChildDirs.map((name) => path.join(runtime.data_dir, name))
|
|
151
|
+
];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function ensureBossChatRuntimeReadyLocal(workspaceRoot) {
|
|
155
|
+
const runtime = resolveCdpBossChatRuntimeLayout(workspaceRoot);
|
|
156
|
+
const runtimeDirectories = getBossChatRuntimeDirectories(runtime);
|
|
157
|
+
const created = [];
|
|
158
|
+
const existed = [];
|
|
159
|
+
const failed = [];
|
|
160
|
+
let migration = {
|
|
161
|
+
attempted: false,
|
|
162
|
+
performed: false,
|
|
163
|
+
source: runtime.migration_source_dir,
|
|
164
|
+
target: runtime.data_dir,
|
|
165
|
+
message: runtime.migration_source_dir
|
|
166
|
+
? `Pending legacy boss-chat migration from ${runtime.migration_source_dir}`
|
|
167
|
+
: ""
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (isUnsafeRuntimeDirectory(runtime.data_dir)) {
|
|
171
|
+
return {
|
|
172
|
+
...runtime,
|
|
173
|
+
directories: runtimeDirectories,
|
|
174
|
+
created,
|
|
175
|
+
existed,
|
|
176
|
+
failed: [
|
|
177
|
+
{
|
|
178
|
+
path: runtime.data_dir,
|
|
179
|
+
message: `Refusing unsafe boss-chat runtime path: ${runtime.data_dir}. Please use BOSS_CHAT_HOME in a writable user directory.`
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
migration,
|
|
183
|
+
blocked_reason: "UNSAFE_DATA_DIR"
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (runtime.migration_source_dir) {
|
|
188
|
+
try {
|
|
189
|
+
fs.cpSync(runtime.migration_source_dir, runtime.data_dir, {
|
|
190
|
+
recursive: true,
|
|
191
|
+
force: false,
|
|
192
|
+
errorOnExist: false
|
|
193
|
+
});
|
|
194
|
+
migration = {
|
|
195
|
+
attempted: true,
|
|
196
|
+
performed: true,
|
|
197
|
+
source: runtime.migration_source_dir,
|
|
198
|
+
target: runtime.data_dir,
|
|
199
|
+
message: `Migrated legacy boss-chat runtime from ${runtime.migration_source_dir} to ${runtime.data_dir}. Legacy source was preserved.`
|
|
200
|
+
};
|
|
201
|
+
} catch (error) {
|
|
202
|
+
migration = {
|
|
203
|
+
attempted: true,
|
|
204
|
+
performed: false,
|
|
205
|
+
source: runtime.migration_source_dir,
|
|
206
|
+
target: runtime.data_dir,
|
|
207
|
+
message: error?.message || "Legacy boss-chat migration failed."
|
|
208
|
+
};
|
|
209
|
+
failed.push({
|
|
210
|
+
path: runtime.data_dir,
|
|
211
|
+
message: `Legacy migration failed: ${migration.message}`
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
for (const directory of runtimeDirectories) {
|
|
217
|
+
try {
|
|
218
|
+
const existedBefore = pathExists(directory);
|
|
219
|
+
ensureDir(directory);
|
|
220
|
+
if (existedBefore) {
|
|
221
|
+
existed.push(directory);
|
|
222
|
+
} else {
|
|
223
|
+
created.push(directory);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
failed.push({
|
|
227
|
+
path: directory,
|
|
228
|
+
message: error?.message || String(error)
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
...runtime,
|
|
235
|
+
directories: runtimeDirectories,
|
|
236
|
+
created,
|
|
237
|
+
existed,
|
|
238
|
+
failed,
|
|
239
|
+
migration
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
116
243
|
function readJsonObjectFileSafe(filePath) {
|
|
117
244
|
if (!pathExists(filePath)) return {};
|
|
118
245
|
try {
|
|
@@ -178,6 +305,235 @@ function getLegacyUserConfigPath() {
|
|
|
178
305
|
return path.join(getCodexHome(), "boss-recommend-mcp", "screening-config.json");
|
|
179
306
|
}
|
|
180
307
|
|
|
308
|
+
function getUserCalibrationPath() {
|
|
309
|
+
return path.join(getCodexHome(), "boss-recommend-mcp", "favorite-calibration.json");
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function isUsableCalibrationFile(filePath) {
|
|
313
|
+
if (!filePath || !pathExists(filePath)) return false;
|
|
314
|
+
const parsed = readJsonObjectFileSafe(filePath);
|
|
315
|
+
return Boolean(
|
|
316
|
+
parsed
|
|
317
|
+
&& parsed.favoritePosition
|
|
318
|
+
&& Number.isFinite(parsed.favoritePosition.pageX)
|
|
319
|
+
&& Number.isFinite(parsed.favoritePosition.pageY)
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function resolveFavoriteCalibrationPath(workspaceRoot) {
|
|
324
|
+
const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_CALIBRATION_FILE || "");
|
|
325
|
+
if (fromEnv) return path.resolve(fromEnv);
|
|
326
|
+
|
|
327
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
328
|
+
const screenConfigPath = configResolution.config_path || getUserConfigPath();
|
|
329
|
+
const screenConfig = readJsonObjectFileSafe(screenConfigPath);
|
|
330
|
+
const calibrationFile = normalizeText(screenConfig?.calibrationFile || "");
|
|
331
|
+
if (calibrationFile && screenConfigPath) {
|
|
332
|
+
return path.resolve(path.dirname(screenConfigPath), calibrationFile);
|
|
333
|
+
}
|
|
334
|
+
return getUserCalibrationPath();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function resolveRecruitCalibrationScriptPath(workspaceRoot) {
|
|
338
|
+
const fromEnv = normalizeText(process.env.BOSS_RECOMMEND_RECRUIT_CALIBRATION_SCRIPT || "");
|
|
339
|
+
const workspaceResolved = path.resolve(String(workspaceRoot || process.cwd()));
|
|
340
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
341
|
+
const candidates = [
|
|
342
|
+
fromEnv,
|
|
343
|
+
path.join(workspaceResolved, "..", "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
344
|
+
path.join(packageRoot, "..", "..", "boss recruit pipeline", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
345
|
+
path.join(appData, "npm", "node_modules", "@reconcrap", "boss-recruit-mcp", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
346
|
+
path.join(workspaceResolved, "..", "boss-recruit-mcp-main", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs"),
|
|
347
|
+
path.join(packageRoot, "..", "boss-recruit-mcp-main", "vendor", "boss-screen-cli", "calibrate-favorite-position-v2.cjs")
|
|
348
|
+
].filter(Boolean).map((item) => path.resolve(item));
|
|
349
|
+
|
|
350
|
+
for (const candidate of new Set(candidates)) {
|
|
351
|
+
if (pathExists(candidate)) return candidate;
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function getFeaturedCalibrationResolutionLocal(workspaceRoot) {
|
|
357
|
+
const calibrationPath = resolveFavoriteCalibrationPath(workspaceRoot);
|
|
358
|
+
return {
|
|
359
|
+
calibration_path: calibrationPath,
|
|
360
|
+
calibration_exists: pathExists(calibrationPath),
|
|
361
|
+
calibration_usable: isUsableCalibrationFile(calibrationPath),
|
|
362
|
+
calibration_script_path: resolveRecruitCalibrationScriptPath(workspaceRoot)
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function runProcessSyncLocal({ command, args = [], cwd = process.cwd() } = {}) {
|
|
367
|
+
try {
|
|
368
|
+
const result = spawnSync(command, args, {
|
|
369
|
+
cwd,
|
|
370
|
+
encoding: "utf8",
|
|
371
|
+
env: process.env,
|
|
372
|
+
shell: false,
|
|
373
|
+
windowsHide: true
|
|
374
|
+
});
|
|
375
|
+
const stdout = String(result.stdout || "").trim();
|
|
376
|
+
const stderr = String(result.stderr || "").trim();
|
|
377
|
+
const output = [stdout, stderr].filter(Boolean).join("\n").trim();
|
|
378
|
+
return {
|
|
379
|
+
ok: result.status === 0 && !result.error,
|
|
380
|
+
status: Number.isInteger(result.status) ? result.status : -1,
|
|
381
|
+
stdout,
|
|
382
|
+
stderr,
|
|
383
|
+
output,
|
|
384
|
+
error_code: result.error?.code || null,
|
|
385
|
+
error_message: result.error?.message || ""
|
|
386
|
+
};
|
|
387
|
+
} catch (error) {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
status: -1,
|
|
391
|
+
stdout: "",
|
|
392
|
+
stderr: "",
|
|
393
|
+
output: "",
|
|
394
|
+
error_code: error.code || "SPAWN_FAILED",
|
|
395
|
+
error_message: error.message || String(error)
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function parseMajorVersion(raw) {
|
|
401
|
+
const match = String(raw || "").match(/v?(\d+)(?:\.\d+){0,2}/);
|
|
402
|
+
if (!match) return null;
|
|
403
|
+
const major = Number.parseInt(match[1], 10);
|
|
404
|
+
return Number.isFinite(major) ? major : null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function buildNodeCommandCheckLocal() {
|
|
408
|
+
const probe = runProcessSyncLocal({
|
|
409
|
+
command: "node",
|
|
410
|
+
args: ["--version"]
|
|
411
|
+
});
|
|
412
|
+
const major = parseMajorVersion(probe.output);
|
|
413
|
+
const versionOk = Number.isInteger(major) && major >= 18;
|
|
414
|
+
return {
|
|
415
|
+
key: "node_cli",
|
|
416
|
+
ok: probe.ok && versionOk,
|
|
417
|
+
path: "node --version",
|
|
418
|
+
message: probe.ok
|
|
419
|
+
? (versionOk
|
|
420
|
+
? `Node 命令可用 (${probe.output || "unknown version"})`
|
|
421
|
+
: `Node 版本过低 (${probe.output || "unknown version"}),要求 >= 18`)
|
|
422
|
+
: `未找到 node 命令,请先安装 Node.js >= 18。${probe.error_message ? ` (${probe.error_message})` : ""}`
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function buildNodePackageCheckLocal({ key, moduleName, cwd, missingMessage }) {
|
|
427
|
+
if (!cwd || !pathExists(cwd)) {
|
|
428
|
+
return {
|
|
429
|
+
key,
|
|
430
|
+
ok: false,
|
|
431
|
+
path: moduleName,
|
|
432
|
+
module: moduleName,
|
|
433
|
+
install_cwd: null,
|
|
434
|
+
message: missingMessage
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
const probe = runProcessSyncLocal({
|
|
438
|
+
command: "node",
|
|
439
|
+
args: ["-e", `require.resolve(${JSON.stringify(moduleName)});`],
|
|
440
|
+
cwd
|
|
441
|
+
});
|
|
442
|
+
return {
|
|
443
|
+
key,
|
|
444
|
+
ok: probe.ok,
|
|
445
|
+
path: moduleName,
|
|
446
|
+
module: moduleName,
|
|
447
|
+
install_cwd: cwd,
|
|
448
|
+
message: probe.ok
|
|
449
|
+
? `${moduleName} npm 依赖可用`
|
|
450
|
+
: `缺少 npm 依赖 ${moduleName},请在 boss-recommend-mcp 目录执行 npm install。`
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function buildRuntimeDependencyChecksLocal({ dependencyDir = packageRoot } = {}) {
|
|
455
|
+
return [
|
|
456
|
+
buildNodeCommandCheckLocal(),
|
|
457
|
+
buildNodePackageCheckLocal({
|
|
458
|
+
key: "npm_dep_chrome_remote_interface",
|
|
459
|
+
moduleName: "chrome-remote-interface",
|
|
460
|
+
cwd: dependencyDir,
|
|
461
|
+
missingMessage: "无法校验 chrome-remote-interface:boss-recommend-mcp package 目录不存在。"
|
|
462
|
+
}),
|
|
463
|
+
buildNodePackageCheckLocal({
|
|
464
|
+
key: "npm_dep_ws",
|
|
465
|
+
moduleName: "ws",
|
|
466
|
+
cwd: dependencyDir,
|
|
467
|
+
missingMessage: "无法校验 ws:boss-recommend-mcp package 目录不存在。"
|
|
468
|
+
}),
|
|
469
|
+
buildNodePackageCheckLocal({
|
|
470
|
+
key: "npm_dep_sharp",
|
|
471
|
+
moduleName: "sharp",
|
|
472
|
+
cwd: dependencyDir,
|
|
473
|
+
missingMessage: "无法校验 sharp:boss-recommend-mcp package 目录不存在。"
|
|
474
|
+
})
|
|
475
|
+
];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
function resolveWorkspaceDebugPortLocal(workspaceRoot) {
|
|
479
|
+
const fromEnv = parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT);
|
|
480
|
+
if (fromEnv) return fromEnv;
|
|
481
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
482
|
+
const config = readJsonObjectFileSafe(configResolution.resolved_path);
|
|
483
|
+
return parsePositivePort(config?.debugPort) || 9222;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function buildScreenConfigCheckLocal(workspaceRoot, configResolution) {
|
|
487
|
+
const screenConfig = resolveBossScreeningConfig(workspaceRoot);
|
|
488
|
+
const pathForMessage = screenConfig.config_path || configResolution.resolved_path || configResolution.writable_path;
|
|
489
|
+
return {
|
|
490
|
+
key: "screen_config",
|
|
491
|
+
ok: screenConfig.ok,
|
|
492
|
+
path: pathForMessage,
|
|
493
|
+
reason: screenConfig.ok ? "OK" : (screenConfig.error?.code || "SCREEN_CONFIG_ERROR"),
|
|
494
|
+
message: screenConfig.ok ? "screening-config.json 可用" : (screenConfig.error?.message || "screening-config.json 不可用")
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function runPipelinePreflightLocal(workspaceRoot, options = {}) {
|
|
499
|
+
const pageScope = normalizePageScope(options.pageScope) || "recommend";
|
|
500
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
501
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(workspaceRoot);
|
|
502
|
+
const checks = [
|
|
503
|
+
buildScreenConfigCheckLocal(workspaceRoot, configResolution),
|
|
504
|
+
{
|
|
505
|
+
key: "favorite_calibration",
|
|
506
|
+
ok: calibrationResolution.calibration_usable,
|
|
507
|
+
path: calibrationResolution.calibration_path,
|
|
508
|
+
optional: pageScope !== "featured",
|
|
509
|
+
message: calibrationResolution.calibration_usable
|
|
510
|
+
? "favorite-calibration.json 可用"
|
|
511
|
+
: "favorite-calibration.json 不存在或无效(精选页收藏仅支持校准坐标点击)"
|
|
512
|
+
}
|
|
513
|
+
];
|
|
514
|
+
checks.push(...buildRuntimeDependencyChecksLocal({ dependencyDir: packageRoot }));
|
|
515
|
+
|
|
516
|
+
const requiredCheckKeys = new Set([
|
|
517
|
+
"screen_config",
|
|
518
|
+
"node_cli",
|
|
519
|
+
"npm_dep_chrome_remote_interface",
|
|
520
|
+
"npm_dep_ws",
|
|
521
|
+
"npm_dep_sharp"
|
|
522
|
+
]);
|
|
523
|
+
if (pageScope === "featured") {
|
|
524
|
+
requiredCheckKeys.add("favorite_calibration");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
return {
|
|
528
|
+
ok: checks.every((item) => !requiredCheckKeys.has(item.key) || item.ok),
|
|
529
|
+
checks,
|
|
530
|
+
debug_port: resolveWorkspaceDebugPortLocal(workspaceRoot),
|
|
531
|
+
config_resolution: configResolution,
|
|
532
|
+
calibration_path: calibrationResolution.calibration_path,
|
|
533
|
+
page_scope: pageScope
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
181
537
|
function getSkillTargetDir(name = skillName) {
|
|
182
538
|
return path.join(getCodexHome(), "skills", name);
|
|
183
539
|
}
|
|
@@ -224,6 +580,12 @@ function parsePositivePort(raw) {
|
|
|
224
580
|
return Number.isFinite(port) && port > 0 ? port : null;
|
|
225
581
|
}
|
|
226
582
|
|
|
583
|
+
function parseNonNegativeInteger(raw, fallback = undefined) {
|
|
584
|
+
if (raw === undefined || raw === null || raw === "") return fallback;
|
|
585
|
+
const parsed = Number.parseInt(String(raw), 10);
|
|
586
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : fallback;
|
|
587
|
+
}
|
|
588
|
+
|
|
227
589
|
function parseBossChatTargetCountOption(raw) {
|
|
228
590
|
if (raw === undefined || raw === null) return undefined;
|
|
229
591
|
const text = String(raw).trim();
|
|
@@ -670,9 +1032,9 @@ function pathStartsWith(filePath, rootPath) {
|
|
|
670
1032
|
return file.startsWith(root);
|
|
671
1033
|
}
|
|
672
1034
|
|
|
673
|
-
function resolveCliConfigTarget(options = {}) {
|
|
1035
|
+
async function resolveCliConfigTarget(options = {}) {
|
|
674
1036
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
675
|
-
const resolution =
|
|
1037
|
+
const resolution = getBossScreenConfigResolution(workspaceRoot);
|
|
676
1038
|
const workspacePreferred = (resolution.candidate_paths || []).find((item) => pathStartsWith(item, workspaceRoot)) || null;
|
|
677
1039
|
const configPath = resolution.writable_path || resolution.resolved_path || workspacePreferred || getUserConfigPath();
|
|
678
1040
|
return {
|
|
@@ -698,8 +1060,8 @@ function applyMissingInstallConfigDefaults(config = {}) {
|
|
|
698
1060
|
};
|
|
699
1061
|
}
|
|
700
1062
|
|
|
701
|
-
function ensureUserConfig(options = {}) {
|
|
702
|
-
const { configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1063
|
+
async function ensureUserConfig(options = {}) {
|
|
1064
|
+
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
703
1065
|
const writeTargets = dedupePaths([configPath, workspacePreferred]).filter(Boolean);
|
|
704
1066
|
let lastError = null;
|
|
705
1067
|
for (const targetPath of writeTargets) {
|
|
@@ -744,16 +1106,16 @@ function ensureUserConfig(options = {}) {
|
|
|
744
1106
|
throw lastError || new Error("No writable target for screening-config.json");
|
|
745
1107
|
}
|
|
746
1108
|
|
|
747
|
-
function collectRuntimeDirectories(options = {}) {
|
|
1109
|
+
async function collectRuntimeDirectories(options = {}) {
|
|
748
1110
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
749
1111
|
const stateHome = getStateHome();
|
|
750
|
-
const runtime =
|
|
1112
|
+
const runtime = resolveCdpBossChatRuntimeLayout(workspaceRoot);
|
|
751
1113
|
const bossChatRoot = runtime.data_dir;
|
|
752
1114
|
const recommendRuntimeDirs = [
|
|
753
1115
|
stateHome,
|
|
754
1116
|
path.join(stateHome, "runs")
|
|
755
1117
|
];
|
|
756
|
-
const bossChatRuntimeDirs = runtime
|
|
1118
|
+
const bossChatRuntimeDirs = getBossChatRuntimeDirectories(runtime);
|
|
757
1119
|
return {
|
|
758
1120
|
workspaceRoot,
|
|
759
1121
|
stateHome,
|
|
@@ -767,9 +1129,9 @@ function collectRuntimeDirectories(options = {}) {
|
|
|
767
1129
|
};
|
|
768
1130
|
}
|
|
769
1131
|
|
|
770
|
-
function ensureRuntimeDirectories(options = {}) {
|
|
771
|
-
const { workspaceRoot, stateHome } = collectRuntimeDirectories(options);
|
|
772
|
-
const runtime =
|
|
1132
|
+
async function ensureRuntimeDirectories(options = {}) {
|
|
1133
|
+
const { workspaceRoot, stateHome } = await collectRuntimeDirectories(options);
|
|
1134
|
+
const runtime = ensureBossChatRuntimeReadyLocal(workspaceRoot);
|
|
773
1135
|
const recommendCreated = [];
|
|
774
1136
|
const recommendExisted = [];
|
|
775
1137
|
const failed = [...runtime.failed];
|
|
@@ -813,8 +1175,8 @@ function readJsonObjectFile(filePath) {
|
|
|
813
1175
|
return parsed;
|
|
814
1176
|
}
|
|
815
1177
|
|
|
816
|
-
function loadBestExistingUserConfig(options = {}) {
|
|
817
|
-
const { resolution, configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1178
|
+
async function loadBestExistingUserConfig(options = {}) {
|
|
1179
|
+
const { resolution, configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
818
1180
|
const candidates = dedupePaths([
|
|
819
1181
|
...(resolution.candidate_paths || []),
|
|
820
1182
|
configPath,
|
|
@@ -837,8 +1199,8 @@ function loadBestExistingUserConfig(options = {}) {
|
|
|
837
1199
|
return { path: configPath, config: {} };
|
|
838
1200
|
}
|
|
839
1201
|
|
|
840
|
-
function writeConfigWithFallback(nextConfig, options = {}) {
|
|
841
|
-
const { configPath, workspacePreferred } = resolveCliConfigTarget(options);
|
|
1202
|
+
async function writeConfigWithFallback(nextConfig, options = {}) {
|
|
1203
|
+
const { configPath, workspacePreferred } = await resolveCliConfigTarget(options);
|
|
842
1204
|
const targets = dedupePaths([configPath, workspacePreferred]).filter(Boolean);
|
|
843
1205
|
let lastError = null;
|
|
844
1206
|
for (const target of targets) {
|
|
@@ -860,14 +1222,14 @@ function writeConfigWithFallback(nextConfig, options = {}) {
|
|
|
860
1222
|
throw lastError || new Error("No writable target for screening-config.json");
|
|
861
1223
|
}
|
|
862
1224
|
|
|
863
|
-
function persistDebugPortSelection(port, options = {}) {
|
|
864
|
-
const { config } = loadBestExistingUserConfig(options);
|
|
1225
|
+
async function persistDebugPortSelection(port, options = {}) {
|
|
1226
|
+
const { config } = await loadBestExistingUserConfig(options);
|
|
865
1227
|
config.debugPort = port;
|
|
866
|
-
const configPath = writeConfigWithFallback(config, options);
|
|
1228
|
+
const configPath = await writeConfigWithFallback(config, options);
|
|
867
1229
|
return { port, configPath };
|
|
868
1230
|
}
|
|
869
1231
|
|
|
870
|
-
function setDebugPort(options = {}) {
|
|
1232
|
+
async function setDebugPort(options = {}) {
|
|
871
1233
|
const selected = parsePositivePort(options.port);
|
|
872
1234
|
if (!selected) {
|
|
873
1235
|
throw new Error("Missing required --port <number> for set-port.");
|
|
@@ -876,7 +1238,7 @@ function setDebugPort(options = {}) {
|
|
|
876
1238
|
return persistDebugPortSelection(selected, options);
|
|
877
1239
|
}
|
|
878
1240
|
|
|
879
|
-
function setScreeningConfig(options = {}) {
|
|
1241
|
+
async function setScreeningConfig(options = {}) {
|
|
880
1242
|
const baseUrl = String(options["base-url"] || options.baseUrl || "").trim();
|
|
881
1243
|
const apiKey = String(options["api-key"] || options.apiKey || "").trim();
|
|
882
1244
|
const model = String(options.model || "").trim();
|
|
@@ -884,7 +1246,7 @@ function setScreeningConfig(options = {}) {
|
|
|
884
1246
|
throw new Error("Missing required fields: --base-url, --api-key, --model");
|
|
885
1247
|
}
|
|
886
1248
|
|
|
887
|
-
const { config: existing } = loadBestExistingUserConfig(options);
|
|
1249
|
+
const { config: existing } = await loadBestExistingUserConfig(options);
|
|
888
1250
|
const nextConfig = {
|
|
889
1251
|
...existing,
|
|
890
1252
|
baseUrl,
|
|
@@ -909,7 +1271,7 @@ function setScreeningConfig(options = {}) {
|
|
|
909
1271
|
if (debugPort) {
|
|
910
1272
|
nextConfig.debugPort = debugPort;
|
|
911
1273
|
}
|
|
912
|
-
const configPath = writeConfigWithFallback(nextConfig, options);
|
|
1274
|
+
const configPath = await writeConfigWithFallback(nextConfig, options);
|
|
913
1275
|
return { path: configPath, updated: true };
|
|
914
1276
|
}
|
|
915
1277
|
|
|
@@ -926,6 +1288,20 @@ async function listChromeTabs(port) {
|
|
|
926
1288
|
return Array.isArray(data) ? data : [];
|
|
927
1289
|
}
|
|
928
1290
|
|
|
1291
|
+
function buildBossPageState(payload) {
|
|
1292
|
+
return {
|
|
1293
|
+
key: "boss_page_state",
|
|
1294
|
+
...payload
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
function extractSampleUrls(tabs, limit = 5) {
|
|
1299
|
+
return tabs
|
|
1300
|
+
.map((tab) => tab?.url)
|
|
1301
|
+
.filter(Boolean)
|
|
1302
|
+
.slice(0, limit);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
929
1305
|
function findChromeOnboardingUrl(tabs) {
|
|
930
1306
|
for (const tab of tabs) {
|
|
931
1307
|
if (typeof tab?.url === "string" && chromeOnboardingUrlPattern.test(tab.url)) {
|
|
@@ -935,6 +1311,383 @@ function findChromeOnboardingUrl(tabs) {
|
|
|
935
1311
|
return null;
|
|
936
1312
|
}
|
|
937
1313
|
|
|
1314
|
+
function isBossRecommendTab(tab) {
|
|
1315
|
+
return typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend");
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
function findBossRecommendTab(tabs = []) {
|
|
1319
|
+
return tabs.find((tab) => isBossRecommendTab(tab)) || null;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
function isBossLoginTab(tab) {
|
|
1323
|
+
const url = String(tab?.url || "");
|
|
1324
|
+
const title = String(tab?.title || "");
|
|
1325
|
+
return (
|
|
1326
|
+
url === bossLoginUrl
|
|
1327
|
+
|| bossLoginUrlPattern.test(url)
|
|
1328
|
+
|| bossLoginTitlePattern.test(title)
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
function findBossPageTab(tabs = []) {
|
|
1333
|
+
return tabs.find((tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")) || null;
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
function getNodeAttribute(node, name) {
|
|
1337
|
+
const attributes = node?.attributes || [];
|
|
1338
|
+
for (let index = 0; index < attributes.length; index += 2) {
|
|
1339
|
+
if (attributes[index] === name) return attributes[index + 1] || "";
|
|
1340
|
+
}
|
|
1341
|
+
return "";
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function uniqueMethodNames(methodLog = []) {
|
|
1345
|
+
return Array.from(new Set(methodLog.map((entry) => entry?.method).filter(Boolean)));
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
async function inspectBossRecommendPageStateCdp(port, options = {}) {
|
|
1349
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
|
|
1350
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 1000;
|
|
1351
|
+
const expectedUrl = options.expectedUrl || bossUrl;
|
|
1352
|
+
const deadline = Date.now() + timeoutMs;
|
|
1353
|
+
let lastError = null;
|
|
1354
|
+
let lastTabs = [];
|
|
1355
|
+
|
|
1356
|
+
while (Date.now() <= deadline) {
|
|
1357
|
+
try {
|
|
1358
|
+
const tabs = await listChromeTabs(port);
|
|
1359
|
+
lastTabs = tabs;
|
|
1360
|
+
const recommendTab = findBossRecommendTab(tabs);
|
|
1361
|
+
if (recommendTab) {
|
|
1362
|
+
if (isBossLoginTab(recommendTab)) {
|
|
1363
|
+
return buildBossPageState({
|
|
1364
|
+
ok: false,
|
|
1365
|
+
state: "LOGIN_REQUIRED",
|
|
1366
|
+
path: recommendTab.url || bossLoginUrl,
|
|
1367
|
+
current_url: recommendTab.url || bossLoginUrl,
|
|
1368
|
+
title: recommendTab.title || null,
|
|
1369
|
+
requires_login: true,
|
|
1370
|
+
expected_url: expectedUrl,
|
|
1371
|
+
login_url: bossLoginUrl,
|
|
1372
|
+
message: "当前标签页虽在 recommend 路径,但检测到登录态页面特征,请先完成 Boss 登录。"
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
return buildBossPageState({
|
|
1376
|
+
ok: true,
|
|
1377
|
+
state: "RECOMMEND_READY",
|
|
1378
|
+
path: recommendTab.url,
|
|
1379
|
+
current_url: recommendTab.url,
|
|
1380
|
+
title: recommendTab.title || null,
|
|
1381
|
+
requires_login: false,
|
|
1382
|
+
expected_url: expectedUrl,
|
|
1383
|
+
message: "Boss 推荐页已打开,且当前仍停留在 recommend 页面。"
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
const loginTab = tabs.find((tab) => isBossLoginTab(tab));
|
|
1388
|
+
if (loginTab) {
|
|
1389
|
+
return buildBossPageState({
|
|
1390
|
+
ok: false,
|
|
1391
|
+
state: "LOGIN_REQUIRED",
|
|
1392
|
+
path: loginTab.url || bossLoginUrl,
|
|
1393
|
+
current_url: loginTab.url || bossLoginUrl,
|
|
1394
|
+
title: loginTab.title || null,
|
|
1395
|
+
requires_login: true,
|
|
1396
|
+
expected_url: expectedUrl,
|
|
1397
|
+
login_url: bossLoginUrl,
|
|
1398
|
+
message: "Boss 页面未登录,需先完成登录后再进入 recommend 页面。"
|
|
1399
|
+
});
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const bossTab = findBossPageTab(tabs);
|
|
1403
|
+
if (bossTab) {
|
|
1404
|
+
const requiresLogin = bossLoginUrlPattern.test(bossTab.url);
|
|
1405
|
+
return buildBossPageState({
|
|
1406
|
+
ok: false,
|
|
1407
|
+
state: requiresLogin ? "LOGIN_REQUIRED" : "BOSS_NOT_ON_RECOMMEND",
|
|
1408
|
+
path: bossTab.url,
|
|
1409
|
+
current_url: bossTab.url,
|
|
1410
|
+
title: bossTab.title || null,
|
|
1411
|
+
requires_login: requiresLogin,
|
|
1412
|
+
expected_url: expectedUrl,
|
|
1413
|
+
login_url: requiresLogin ? bossLoginUrl : undefined,
|
|
1414
|
+
message: requiresLogin
|
|
1415
|
+
? "Boss 页面未登录,需先完成登录后再进入 recommend 页面。"
|
|
1416
|
+
: "Boss 已登录但当前不在 recommend 页面,将尝试自动跳转。"
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
} catch (error) {
|
|
1420
|
+
lastError = error;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
await sleepMs(pollMs);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
if (lastError) {
|
|
1427
|
+
return buildBossPageState({
|
|
1428
|
+
ok: false,
|
|
1429
|
+
state: "DEBUG_PORT_UNREACHABLE",
|
|
1430
|
+
path: `http://127.0.0.1:${port}`,
|
|
1431
|
+
current_url: null,
|
|
1432
|
+
title: null,
|
|
1433
|
+
requires_login: false,
|
|
1434
|
+
expected_url: expectedUrl,
|
|
1435
|
+
message: `无法连接到 Chrome DevTools 端口 ${port}。请确认 Chrome 已以远程调试模式启动。`,
|
|
1436
|
+
error: lastError.message
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const onboardingUrl = findChromeOnboardingUrl(lastTabs);
|
|
1441
|
+
if (onboardingUrl) {
|
|
1442
|
+
return buildBossPageState({
|
|
1443
|
+
ok: false,
|
|
1444
|
+
state: "CHROME_ONBOARDING_INTERCEPTED",
|
|
1445
|
+
path: onboardingUrl,
|
|
1446
|
+
current_url: onboardingUrl,
|
|
1447
|
+
title: null,
|
|
1448
|
+
requires_login: false,
|
|
1449
|
+
expected_url: expectedUrl,
|
|
1450
|
+
message: "Chrome 当前停留在登录或引导页,尚未稳定到 Boss 推荐页。",
|
|
1451
|
+
sample_urls: extractSampleUrls(lastTabs)
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
return buildBossPageState({
|
|
1456
|
+
ok: false,
|
|
1457
|
+
state: "BOSS_TAB_NOT_FOUND",
|
|
1458
|
+
path: expectedUrl,
|
|
1459
|
+
current_url: null,
|
|
1460
|
+
title: null,
|
|
1461
|
+
requires_login: false,
|
|
1462
|
+
expected_url: expectedUrl,
|
|
1463
|
+
message: "未检测到 Boss 推荐页标签页。",
|
|
1464
|
+
sample_urls: extractSampleUrls(lastTabs)
|
|
1465
|
+
});
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
async function withRecommendTargetCdp(port, callback) {
|
|
1469
|
+
const connection = await connectToChromeTarget({
|
|
1470
|
+
port,
|
|
1471
|
+
targetPredicate: (target) => isBossRecommendTab(target)
|
|
1472
|
+
});
|
|
1473
|
+
try {
|
|
1474
|
+
return await callback(connection);
|
|
1475
|
+
} finally {
|
|
1476
|
+
await connection.close();
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
async function bringBossRecommendTabToFrontCdp(port) {
|
|
1481
|
+
try {
|
|
1482
|
+
return await withRecommendTargetCdp(port, async ({ client, methodLog }) => {
|
|
1483
|
+
await enableDomains(client, ["Page"]);
|
|
1484
|
+
await bringPageToFront(client);
|
|
1485
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1486
|
+
return {
|
|
1487
|
+
ok: true,
|
|
1488
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1489
|
+
};
|
|
1490
|
+
});
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
return {
|
|
1493
|
+
ok: false,
|
|
1494
|
+
error: error.message || String(error)
|
|
1495
|
+
};
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
async function probeRecommendIframeStateCdp(port, options = {}) {
|
|
1500
|
+
const expectedUrl = options.expectedUrl || bossUrl;
|
|
1501
|
+
try {
|
|
1502
|
+
return await withRecommendTargetCdp(port, async ({ client, target, methodLog }) => {
|
|
1503
|
+
await enableDomains(client, ["Page", "DOM"]);
|
|
1504
|
+
const root = await getDocumentRoot(client, { depth: 1, pierce: true });
|
|
1505
|
+
const iframeNodeId = await querySelector(client, root.nodeId, 'iframe[name="recommendFrame"]');
|
|
1506
|
+
if (!iframeNodeId) {
|
|
1507
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1508
|
+
return buildBossPageState({
|
|
1509
|
+
ok: false,
|
|
1510
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1511
|
+
path: target.url || expectedUrl,
|
|
1512
|
+
current_url: target.url || null,
|
|
1513
|
+
title: target.title || null,
|
|
1514
|
+
expected_url: expectedUrl,
|
|
1515
|
+
message: "recommend iframe 尚未挂载。",
|
|
1516
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
const described = await client.DOM.describeNode({
|
|
1521
|
+
nodeId: iframeNodeId,
|
|
1522
|
+
depth: 1,
|
|
1523
|
+
pierce: true
|
|
1524
|
+
});
|
|
1525
|
+
const iframeNode = described.node || {};
|
|
1526
|
+
const frameDocument = iframeNode.contentDocument || null;
|
|
1527
|
+
const frameUrl = frameDocument?.documentURL || getNodeAttribute(iframeNode, "src") || null;
|
|
1528
|
+
assertNoForbiddenCdpCalls(methodLog);
|
|
1529
|
+
if (!frameDocument?.nodeId) {
|
|
1530
|
+
return buildBossPageState({
|
|
1531
|
+
ok: false,
|
|
1532
|
+
state: "RECOMMEND_IFRAME_DOCUMENT_PENDING",
|
|
1533
|
+
path: target.url || expectedUrl,
|
|
1534
|
+
current_url: target.url || null,
|
|
1535
|
+
title: target.title || null,
|
|
1536
|
+
expected_url: expectedUrl,
|
|
1537
|
+
frame_url: frameUrl,
|
|
1538
|
+
message: "recommend iframe 已挂载但文档尚未就绪。",
|
|
1539
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
return buildBossPageState({
|
|
1544
|
+
ok: true,
|
|
1545
|
+
state: "RECOMMEND_IFRAME_READY",
|
|
1546
|
+
path: target.url || expectedUrl,
|
|
1547
|
+
current_url: target.url || null,
|
|
1548
|
+
title: target.title || null,
|
|
1549
|
+
expected_url: expectedUrl,
|
|
1550
|
+
frame_url: frameUrl,
|
|
1551
|
+
iframe_node_id: iframeNodeId,
|
|
1552
|
+
frame_document_node_id: frameDocument.nodeId,
|
|
1553
|
+
message: "recommend iframe 已通过 CDP DOM 检测就绪。",
|
|
1554
|
+
method_log: uniqueMethodNames(methodLog)
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
} catch (error) {
|
|
1558
|
+
return buildBossPageState({
|
|
1559
|
+
ok: false,
|
|
1560
|
+
state: "RECOMMEND_IFRAME_PROBE_FAILED",
|
|
1561
|
+
path: expectedUrl,
|
|
1562
|
+
current_url: null,
|
|
1563
|
+
title: null,
|
|
1564
|
+
expected_url: expectedUrl,
|
|
1565
|
+
message: "recommend iframe CDP DOM 检测失败。",
|
|
1566
|
+
error: error.message || String(error)
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
async function waitForRecommendIframeReadyCdp(port, options = {}) {
|
|
1572
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
|
|
1573
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1574
|
+
const deadline = Date.now() + timeoutMs;
|
|
1575
|
+
let lastState = null;
|
|
1576
|
+
|
|
1577
|
+
while (Date.now() <= deadline) {
|
|
1578
|
+
lastState = await probeRecommendIframeStateCdp(port, options);
|
|
1579
|
+
if (lastState?.state === "RECOMMEND_IFRAME_READY") return lastState;
|
|
1580
|
+
await sleepMs(pollMs);
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return lastState || buildBossPageState({
|
|
1584
|
+
ok: false,
|
|
1585
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1586
|
+
path: options.expectedUrl || bossUrl,
|
|
1587
|
+
current_url: null,
|
|
1588
|
+
title: null,
|
|
1589
|
+
expected_url: options.expectedUrl || bossUrl,
|
|
1590
|
+
message: "recommend iframe 尚未就绪。"
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
async function verifyRecommendPageStableCdp(port, options = {}) {
|
|
1595
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1000;
|
|
1596
|
+
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 6000;
|
|
1597
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1598
|
+
|
|
1599
|
+
await sleepMs(settleMs);
|
|
1600
|
+
const recheck = await inspectBossRecommendPageStateCdp(port, {
|
|
1601
|
+
timeoutMs: recheckTimeoutMs,
|
|
1602
|
+
pollMs
|
|
1603
|
+
});
|
|
1604
|
+
if (recheck.state !== "RECOMMEND_READY") return recheck;
|
|
1605
|
+
|
|
1606
|
+
const iframeState = await waitForRecommendIframeReadyCdp(port, {
|
|
1607
|
+
timeoutMs: recheckTimeoutMs,
|
|
1608
|
+
pollMs
|
|
1609
|
+
});
|
|
1610
|
+
if (iframeState.state === "RECOMMEND_IFRAME_READY") {
|
|
1611
|
+
return buildBossPageState({
|
|
1612
|
+
...recheck,
|
|
1613
|
+
ok: true,
|
|
1614
|
+
state: "RECOMMEND_READY",
|
|
1615
|
+
frame_url: iframeState.frame_url || null,
|
|
1616
|
+
iframe_state: iframeState,
|
|
1617
|
+
method_log: iframeState.method_log || []
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
return buildBossPageState({
|
|
1622
|
+
...iframeState,
|
|
1623
|
+
state: iframeState.state || "NO_RECOMMEND_IFRAME",
|
|
1624
|
+
message: iframeState.message || "Boss recommend 页面已打开,但 iframe 尚未就绪。"
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
async function navigateExistingTargetToBossRecommendCdp(port) {
|
|
1629
|
+
let connection = null;
|
|
1630
|
+
try {
|
|
1631
|
+
connection = await connectToChromeTarget({
|
|
1632
|
+
port,
|
|
1633
|
+
targetPredicate: (target) => target?.type === "page"
|
|
1634
|
+
});
|
|
1635
|
+
await enableDomains(connection.client, ["Page"]);
|
|
1636
|
+
await connection.client.Page.navigate({ url: bossUrl });
|
|
1637
|
+
assertNoForbiddenCdpCalls(connection.methodLog);
|
|
1638
|
+
return {
|
|
1639
|
+
ok: true,
|
|
1640
|
+
via: "cdp_page_navigate",
|
|
1641
|
+
target_id: connection.target?.id || null,
|
|
1642
|
+
method_log: uniqueMethodNames(connection.methodLog)
|
|
1643
|
+
};
|
|
1644
|
+
} catch (error) {
|
|
1645
|
+
return {
|
|
1646
|
+
ok: false,
|
|
1647
|
+
via: "cdp_page_navigate",
|
|
1648
|
+
error: error.message || String(error)
|
|
1649
|
+
};
|
|
1650
|
+
} finally {
|
|
1651
|
+
if (connection) await connection.close();
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1655
|
+
async function openBossRecommendTabCdp(port) {
|
|
1656
|
+
const endpoint = `http://127.0.0.1:${port}/json/new?${encodeURIComponent(bossUrl)}`;
|
|
1657
|
+
const attempts = ["PUT", "GET"];
|
|
1658
|
+
let lastError = null;
|
|
1659
|
+
|
|
1660
|
+
for (const method of attempts) {
|
|
1661
|
+
try {
|
|
1662
|
+
const response = await fetch(endpoint, { method });
|
|
1663
|
+
if (response.ok) {
|
|
1664
|
+
let payload = null;
|
|
1665
|
+
try {
|
|
1666
|
+
payload = await response.json();
|
|
1667
|
+
} catch {}
|
|
1668
|
+
return {
|
|
1669
|
+
ok: true,
|
|
1670
|
+
via: "devtools_http_new_tab",
|
|
1671
|
+
method,
|
|
1672
|
+
target_id: payload?.id || null,
|
|
1673
|
+
current_url: payload?.url || bossUrl
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
lastError = new Error(`DevTools /json/new returned ${response.status}`);
|
|
1677
|
+
} catch (error) {
|
|
1678
|
+
lastError = error;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
const fallback = await navigateExistingTargetToBossRecommendCdp(port);
|
|
1683
|
+
if (fallback.ok) return fallback;
|
|
1684
|
+
return {
|
|
1685
|
+
ok: false,
|
|
1686
|
+
via: "devtools_http_new_tab",
|
|
1687
|
+
error: lastError?.message || fallback.error || "Failed to open Boss recommend tab via DevTools"
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1690
|
+
|
|
938
1691
|
function getDefaultChromeExecutableCandidates() {
|
|
939
1692
|
const candidates = [process.env.BOSS_RECOMMEND_CHROME_PATH].filter(Boolean);
|
|
940
1693
|
if (process.platform === "win32") {
|
|
@@ -997,16 +1750,118 @@ function resolveDefaultChromeUserDataDir(port) {
|
|
|
997
1750
|
return legacyExisting || sharedPath;
|
|
998
1751
|
}
|
|
999
1752
|
|
|
1753
|
+
function getLaunchChromeTiming(options = {}) {
|
|
1754
|
+
if (options["slow-live"] || options.slowLive) {
|
|
1755
|
+
return {
|
|
1756
|
+
initialTimeoutMs: 5000,
|
|
1757
|
+
inspectTimeoutMs: 20000,
|
|
1758
|
+
pollMs: 1000,
|
|
1759
|
+
settleMs: 2000
|
|
1760
|
+
};
|
|
1761
|
+
}
|
|
1762
|
+
return {
|
|
1763
|
+
initialTimeoutMs: 1500,
|
|
1764
|
+
inspectTimeoutMs: 6000,
|
|
1765
|
+
pollMs: 800,
|
|
1766
|
+
settleMs: 1000
|
|
1767
|
+
};
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
async function ensureBossRecommendPageReadyCdp(port, options = {}) {
|
|
1771
|
+
const attempts = Number.isFinite(options.attempts) ? Math.max(0, options.attempts) : 3;
|
|
1772
|
+
const inspectTimeoutMs = Number.isFinite(options.inspectTimeoutMs) ? options.inspectTimeoutMs : 6000;
|
|
1773
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
|
|
1774
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1000;
|
|
1775
|
+
|
|
1776
|
+
let pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
1777
|
+
timeoutMs: inspectTimeoutMs,
|
|
1778
|
+
pollMs
|
|
1779
|
+
});
|
|
1780
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
1781
|
+
const stableState = await verifyRecommendPageStableCdp(port, {
|
|
1782
|
+
settleMs,
|
|
1783
|
+
recheckTimeoutMs: inspectTimeoutMs,
|
|
1784
|
+
pollMs
|
|
1785
|
+
});
|
|
1786
|
+
return {
|
|
1787
|
+
ok: stableState.state === "RECOMMEND_READY",
|
|
1788
|
+
debug_port: port,
|
|
1789
|
+
state: stableState.state,
|
|
1790
|
+
page_state: stableState
|
|
1791
|
+
};
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
if (pageState.state === "LOGIN_REQUIRED") {
|
|
1795
|
+
return {
|
|
1796
|
+
ok: false,
|
|
1797
|
+
debug_port: port,
|
|
1798
|
+
state: pageState.state,
|
|
1799
|
+
page_state: pageState
|
|
1800
|
+
};
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
let openAttempt = null;
|
|
1804
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
1805
|
+
if (pageState.state === "DEBUG_PORT_UNREACHABLE" || pageState.state === "LOGIN_REQUIRED") break;
|
|
1806
|
+
openAttempt = await openBossRecommendTabCdp(port);
|
|
1807
|
+
await sleepMs(settleMs);
|
|
1808
|
+
pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
1809
|
+
timeoutMs: inspectTimeoutMs,
|
|
1810
|
+
pollMs
|
|
1811
|
+
});
|
|
1812
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
1813
|
+
const stableState = await verifyRecommendPageStableCdp(port, {
|
|
1814
|
+
settleMs,
|
|
1815
|
+
recheckTimeoutMs: inspectTimeoutMs,
|
|
1816
|
+
pollMs
|
|
1817
|
+
});
|
|
1818
|
+
return {
|
|
1819
|
+
ok: stableState.state === "RECOMMEND_READY",
|
|
1820
|
+
debug_port: port,
|
|
1821
|
+
state: stableState.state,
|
|
1822
|
+
page_state: {
|
|
1823
|
+
...stableState,
|
|
1824
|
+
open_attempt: openAttempt
|
|
1825
|
+
}
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1828
|
+
if (pageState.state === "LOGIN_REQUIRED") break;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
return {
|
|
1832
|
+
ok: false,
|
|
1833
|
+
debug_port: port,
|
|
1834
|
+
state: pageState.state || "UNKNOWN",
|
|
1835
|
+
page_state: {
|
|
1836
|
+
...pageState,
|
|
1837
|
+
open_attempt: openAttempt
|
|
1838
|
+
}
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1000
1842
|
async function launchChrome(options = {}) {
|
|
1001
1843
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1002
1844
|
process.env.BOSS_RECOMMEND_CHROME_PORT = String(port);
|
|
1845
|
+
const timing = getLaunchChromeTiming(options);
|
|
1003
1846
|
|
|
1004
|
-
const initialState = await
|
|
1847
|
+
const initialState = await inspectBossRecommendPageStateCdp(port, {
|
|
1848
|
+
timeoutMs: timing.initialTimeoutMs,
|
|
1849
|
+
pollMs: timing.pollMs
|
|
1850
|
+
});
|
|
1005
1851
|
if (initialState.state !== "DEBUG_PORT_UNREACHABLE") {
|
|
1006
1852
|
console.log(`Reusing existing Chrome debug instance on port ${port}`);
|
|
1007
|
-
const pageState = await
|
|
1853
|
+
const pageState = await ensureBossRecommendPageReadyCdp(port, {
|
|
1854
|
+
attempts: 2,
|
|
1855
|
+
inspectTimeoutMs: timing.inspectTimeoutMs,
|
|
1856
|
+
pollMs: timing.pollMs,
|
|
1857
|
+
settleMs: timing.settleMs
|
|
1858
|
+
});
|
|
1008
1859
|
if (pageState.ok) {
|
|
1009
1860
|
console.log("Boss recommend page is ready.");
|
|
1861
|
+
const frontResult = await bringBossRecommendTabToFrontCdp(port);
|
|
1862
|
+
if (frontResult.ok) {
|
|
1863
|
+
console.log(`CDP methods: ${frontResult.method_log.join(", ") || "none"}`);
|
|
1864
|
+
}
|
|
1010
1865
|
} else {
|
|
1011
1866
|
console.log(pageState.page_state?.message || "Boss recommend page is not ready.");
|
|
1012
1867
|
}
|
|
@@ -1037,9 +1892,19 @@ async function launchChrome(options = {}) {
|
|
|
1037
1892
|
child.unref();
|
|
1038
1893
|
console.log(`Chrome launched with remote debugging port ${port}`);
|
|
1039
1894
|
console.log(`User data dir: ${userDataDir}`);
|
|
1040
|
-
|
|
1895
|
+
await sleepMs(timing.settleMs + 1200);
|
|
1896
|
+
const pageState = await ensureBossRecommendPageReadyCdp(port, {
|
|
1897
|
+
attempts: 6,
|
|
1898
|
+
inspectTimeoutMs: timing.inspectTimeoutMs,
|
|
1899
|
+
pollMs: timing.pollMs,
|
|
1900
|
+
settleMs: timing.settleMs
|
|
1901
|
+
});
|
|
1041
1902
|
if (pageState.ok) {
|
|
1042
1903
|
console.log("Boss recommend page is ready.");
|
|
1904
|
+
const frontResult = await bringBossRecommendTabToFrontCdp(port);
|
|
1905
|
+
if (frontResult.ok) {
|
|
1906
|
+
console.log(`CDP methods: ${frontResult.method_log.join(", ") || "none"}`);
|
|
1907
|
+
}
|
|
1043
1908
|
} else {
|
|
1044
1909
|
console.log(pageState.page_state?.message || "Boss recommend page is not ready.");
|
|
1045
1910
|
}
|
|
@@ -1051,99 +1916,37 @@ function getCalibrationTimeoutMs(options = {}) {
|
|
|
1051
1916
|
return Math.max(5000, parsed);
|
|
1052
1917
|
}
|
|
1053
1918
|
|
|
1054
|
-
|
|
1919
|
+
function buildUnsupportedCalibrateResponse(options = {}) {
|
|
1055
1920
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1056
1921
|
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
1922
|
const timeoutMs = getCalibrationTimeoutMs(options);
|
|
1060
1923
|
const outputPath = String(options.output || "").trim()
|
|
1061
1924
|
? path.resolve(String(options.output))
|
|
1062
1925
|
: 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, {
|
|
1926
|
+
return {
|
|
1927
|
+
status: "FAILED",
|
|
1928
|
+
error: {
|
|
1929
|
+
code: calibrateUnsupportedCode,
|
|
1930
|
+
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.",
|
|
1931
|
+
retryable: false
|
|
1932
|
+
},
|
|
1933
|
+
cdp_only: true,
|
|
1934
|
+
runtime_evaluate_used: false,
|
|
1935
|
+
method_summary: {},
|
|
1936
|
+
method_log: [],
|
|
1119
1937
|
port,
|
|
1938
|
+
timeout_ms: timeoutMs,
|
|
1120
1939
|
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
|
-
}
|
|
1940
|
+
calibration_resolution: getFeaturedCalibrationResolutionLocal(workspaceRoot),
|
|
1941
|
+
guidance: {
|
|
1942
|
+
current_workaround: "Use an existing favorite-calibration.json if present; `doctor --page-scope featured` will report whether it is usable.",
|
|
1943
|
+
next_development_task: "Implement CDP-only featured detail/action discovery and a user-approved live calibration gate before restoring this command."
|
|
1132
1944
|
}
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
console.log(`Calibration saved: ${result.calibration_path}`);
|
|
1136
|
-
return;
|
|
1137
|
-
}
|
|
1945
|
+
};
|
|
1946
|
+
}
|
|
1138
1947
|
|
|
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
|
-
}
|
|
1948
|
+
async function calibrate(options = {}) {
|
|
1949
|
+
printJson(buildUnsupportedCalibrateResponse(options));
|
|
1147
1950
|
process.exitCode = 1;
|
|
1148
1951
|
}
|
|
1149
1952
|
|
|
@@ -1215,11 +2018,22 @@ async function printDoctor(options = {}) {
|
|
|
1215
2018
|
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1216
2019
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1217
2020
|
const pageScope = normalizePageScope(options["page-scope"] || options.pageScope) || "recommend";
|
|
1218
|
-
const preflight =
|
|
2021
|
+
const preflight = runPipelinePreflightLocal(workspaceRoot, { pageScope });
|
|
1219
2022
|
const checks = preflight.checks.slice();
|
|
1220
|
-
const configResolution =
|
|
1221
|
-
const calibrationResolution =
|
|
1222
|
-
const
|
|
2023
|
+
const configResolution = getBossScreenConfigResolution(workspaceRoot);
|
|
2024
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(workspaceRoot);
|
|
2025
|
+
const timing = getLaunchChromeTiming(options);
|
|
2026
|
+
let pageState = await inspectBossRecommendPageStateCdp(port, {
|
|
2027
|
+
timeoutMs: options["slow-live"] || options.slowLive ? timing.initialTimeoutMs : 2000,
|
|
2028
|
+
pollMs: options["slow-live"] || options.slowLive ? timing.pollMs : 500
|
|
2029
|
+
});
|
|
2030
|
+
if (pageState.state === "RECOMMEND_READY") {
|
|
2031
|
+
pageState = await verifyRecommendPageStableCdp(port, {
|
|
2032
|
+
settleMs: options["slow-live"] || options.slowLive ? timing.settleMs : 800,
|
|
2033
|
+
recheckTimeoutMs: options["slow-live"] || options.slowLive ? timing.inspectTimeoutMs : 3000,
|
|
2034
|
+
pollMs: options["slow-live"] || options.slowLive ? timing.pollMs : 500
|
|
2035
|
+
});
|
|
2036
|
+
}
|
|
1223
2037
|
const resolvedConfigPath = configResolution.resolved_path || configResolution.writable_path;
|
|
1224
2038
|
const userConfigExists = (
|
|
1225
2039
|
(resolvedConfigPath && fs.existsSync(resolvedConfigPath))
|
|
@@ -1245,10 +2059,11 @@ async function printDoctor(options = {}) {
|
|
|
1245
2059
|
checks.push({
|
|
1246
2060
|
key: "featured_calibration_script",
|
|
1247
2061
|
ok: Boolean(calibrationResolution.calibration_script_path),
|
|
2062
|
+
optional: true,
|
|
1248
2063
|
path: calibrationResolution.calibration_script_path,
|
|
1249
2064
|
message: calibrationResolution.calibration_script_path
|
|
1250
2065
|
? "已检测到 boss-recruit-mcp 校准脚本。"
|
|
1251
|
-
: "未检测到 boss-recruit-mcp
|
|
2066
|
+
: "未检测到 boss-recruit-mcp 校准脚本;CDP-only package 已禁用旧精选页自动校准。"
|
|
1252
2067
|
});
|
|
1253
2068
|
checks.push({
|
|
1254
2069
|
key: "featured_calibration_file",
|
|
@@ -1322,7 +2137,7 @@ async function printDoctor(options = {}) {
|
|
|
1322
2137
|
}
|
|
1323
2138
|
|
|
1324
2139
|
printJson({
|
|
1325
|
-
ok: checks.every((item) => item.ok),
|
|
2140
|
+
ok: checks.every((item) => item.ok || item.optional),
|
|
1326
2141
|
port,
|
|
1327
2142
|
checks,
|
|
1328
2143
|
config_resolution: configResolution,
|
|
@@ -1340,8 +2155,8 @@ async function printDoctor(options = {}) {
|
|
|
1340
2155
|
function printPaths() {
|
|
1341
2156
|
const codexHome = getCodexHome();
|
|
1342
2157
|
const stateHome = getStateHome();
|
|
1343
|
-
const calibrationResolution =
|
|
1344
|
-
const bossChatRuntime =
|
|
2158
|
+
const calibrationResolution = getFeaturedCalibrationResolutionLocal(process.cwd());
|
|
2159
|
+
const bossChatRuntime = resolveCdpBossChatRuntimeLayout(getWorkspaceRoot({}));
|
|
1345
2160
|
console.log(`package_root=${packageRoot}`);
|
|
1346
2161
|
console.log(`skill_sources=${bundledSkillNames.map((name) => getSkillSourceDir(name)).join(" | ")}`);
|
|
1347
2162
|
console.log(`codex_home=${codexHome}`);
|
|
@@ -1362,8 +2177,9 @@ function printHelp() {
|
|
|
1362
2177
|
console.log("Usage:");
|
|
1363
2178
|
console.log(" boss-recommend-mcp Start the MCP server");
|
|
1364
2179
|
console.log(" boss-recommend-mcp start Start the MCP server");
|
|
1365
|
-
console.log(" boss-recommend-mcp run
|
|
1366
|
-
console.log(" boss-recommend-mcp
|
|
2180
|
+
console.log(" boss-recommend-mcp run Disabled until the one-shot CLI has a CDP-only async replacement");
|
|
2181
|
+
console.log(" boss-recommend-mcp list-jobs CDP-only list of exact recommend job names for cron/one-shot inputs");
|
|
2182
|
+
console.log(" boss-recommend-mcp chat <subcommand> Run CDP-only boss-chat health/prepare/status commands");
|
|
1367
2183
|
console.log(" boss-recommend-mcp install Install skill/MCP templates and auto-init screening-config.json (supports --agent trae-cn/cursor/...)");
|
|
1368
2184
|
console.log(" boss-recommend-mcp install-skill Install bundled Codex skills");
|
|
1369
2185
|
console.log(" boss-recommend-mcp init-config Create screening-config.json if missing (prefer workspace config/, fallback ~/.boss-recommend-mcp)");
|
|
@@ -1371,17 +2187,18 @@ function printHelp() {
|
|
|
1371
2187
|
console.log(" boss-recommend-mcp set-port Persist preferred Chrome debug port to screening-config.json");
|
|
1372
2188
|
console.log(" boss-recommend-mcp mcp-config Generate MCP config JSON for Cursor/Trae(含 trae-cn)/Claude Code/OpenClaw");
|
|
1373
2189
|
console.log(" boss-recommend-mcp doctor Check config/runtime/calibration prerequisites (supports --agent trae-cn/cursor/...)");
|
|
1374
|
-
console.log(" boss-recommend-mcp calibrate
|
|
2190
|
+
console.log(" boss-recommend-mcp calibrate Disabled until CDP-only featured calibration is live-verified");
|
|
1375
2191
|
console.log(" boss-recommend-mcp launch-chrome Launch or reuse Chrome debug instance and open Boss recommend page");
|
|
1376
2192
|
console.log(" boss-recommend-mcp where Print installed package, skill, and config paths");
|
|
1377
2193
|
console.log("");
|
|
1378
2194
|
console.log("Run command:");
|
|
1379
|
-
console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\"
|
|
1380
|
-
console.log(" boss-recommend-mcp
|
|
2195
|
+
console.log(" boss-recommend-mcp run --instruction \"推荐页上筛选211男生,近14天没有,有大模型平台经验\" # returns RECOMMEND_CLI_RUN_UNSUPPORTED_CDP_ONLY during rewrite; use MCP start_recommend_pipeline_run");
|
|
2196
|
+
console.log(" boss-recommend-mcp list-jobs --slow-live --port 9222");
|
|
2197
|
+
console.log(" boss-recommend-mcp chat prepare-run --slow-live --port 9222 # CDP-only preflight; start runs through MCP start_boss_chat_run");
|
|
1381
2198
|
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
2199
|
console.log(" boss-recommend-mcp install --agent trae-cn");
|
|
1383
2200
|
console.log(" boss-recommend-mcp doctor --agent trae-cn --page-scope featured");
|
|
1384
|
-
console.log(" boss-recommend-mcp calibrate --port 9222
|
|
2201
|
+
console.log(" boss-recommend-mcp calibrate --port 9222 # returns CALIBRATE_UNSUPPORTED_CDP_ONLY during rewrite");
|
|
1385
2202
|
}
|
|
1386
2203
|
|
|
1387
2204
|
function printMcpConfig(options = {}) {
|
|
@@ -1397,10 +2214,10 @@ function printMcpConfig(options = {}) {
|
|
|
1397
2214
|
}
|
|
1398
2215
|
}
|
|
1399
2216
|
|
|
1400
|
-
function installAll(options = {}) {
|
|
1401
|
-
const runtimeDirsResult = ensureRuntimeDirectories(options);
|
|
2217
|
+
async function installAll(options = {}) {
|
|
2218
|
+
const runtimeDirsResult = await ensureRuntimeDirectories(options);
|
|
1402
2219
|
const skillResults = installSkill();
|
|
1403
|
-
const configResult = ensureUserConfig(options);
|
|
2220
|
+
const configResult = await ensureUserConfig(options);
|
|
1404
2221
|
const mcpTemplateResult = writeMcpConfigFiles({ client: "all" });
|
|
1405
2222
|
const externalMcpResult = installExternalMcpConfigs(options);
|
|
1406
2223
|
const externalSkillResult = mirrorSkillToExternalDirs(options);
|
|
@@ -1458,26 +2275,78 @@ function installAll(options = {}) {
|
|
|
1458
2275
|
}
|
|
1459
2276
|
}
|
|
1460
2277
|
|
|
1461
|
-
|
|
2278
|
+
function buildUnsupportedRecommendCliRunResponse({
|
|
2279
|
+
instruction,
|
|
2280
|
+
confirmation,
|
|
2281
|
+
overrides,
|
|
2282
|
+
followUp,
|
|
2283
|
+
workspaceRoot,
|
|
2284
|
+
port
|
|
2285
|
+
} = {}) {
|
|
2286
|
+
return {
|
|
2287
|
+
status: "FAILED",
|
|
2288
|
+
error: {
|
|
2289
|
+
code: recommendCliRunUnsupportedCode,
|
|
2290
|
+
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.",
|
|
2291
|
+
retryable: false
|
|
2292
|
+
},
|
|
2293
|
+
cdp_only: true,
|
|
2294
|
+
runtime_evaluate_used: false,
|
|
2295
|
+
method_summary: {},
|
|
2296
|
+
method_log: [],
|
|
2297
|
+
run_mode: "mcp_async_required",
|
|
2298
|
+
port,
|
|
2299
|
+
target_url: bossUrl,
|
|
2300
|
+
input: {
|
|
2301
|
+
workspace_root: workspaceRoot,
|
|
2302
|
+
instruction,
|
|
2303
|
+
confirmation: confirmation ?? null,
|
|
2304
|
+
overrides: overrides ?? null,
|
|
2305
|
+
follow_up: followUp ?? null
|
|
2306
|
+
},
|
|
2307
|
+
guidance: {
|
|
2308
|
+
recommended_tool: "start_recommend_pipeline_run",
|
|
2309
|
+
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."
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
async function runPipelineOnce(options = {}) {
|
|
1462
2315
|
const instruction = getRunInstruction(options);
|
|
1463
2316
|
const confirmation = getRunConfirmation(options);
|
|
1464
2317
|
const overrides = getRunOverrides(options);
|
|
1465
2318
|
const followUp = getRunFollowUp(options);
|
|
1466
2319
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
1467
|
-
const
|
|
1468
|
-
if (explicitPort) {
|
|
1469
|
-
process.env.BOSS_RECOMMEND_CHROME_PORT = String(explicitPort);
|
|
1470
|
-
persistDebugPortSelection(explicitPort, options);
|
|
1471
|
-
}
|
|
2320
|
+
const port = parsePositivePort(options.port) || parsePositivePort(process.env.BOSS_RECOMMEND_CHROME_PORT) || 9222;
|
|
1472
2321
|
|
|
1473
|
-
|
|
2322
|
+
printJson(buildUnsupportedRecommendCliRunResponse({
|
|
1474
2323
|
workspaceRoot,
|
|
1475
2324
|
instruction,
|
|
1476
2325
|
confirmation,
|
|
1477
2326
|
overrides,
|
|
1478
|
-
followUp
|
|
1479
|
-
|
|
1480
|
-
|
|
2327
|
+
followUp,
|
|
2328
|
+
port
|
|
2329
|
+
}));
|
|
2330
|
+
process.exitCode = 1;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
function buildRecommendJobListCliInput(options = {}) {
|
|
2334
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2335
|
+
const host = String(options.host || "").trim();
|
|
2336
|
+
return {
|
|
2337
|
+
host: host || undefined,
|
|
2338
|
+
port: parsePositivePort(options.port),
|
|
2339
|
+
target_url_includes: targetUrlIncludes || undefined,
|
|
2340
|
+
allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
|
|
2341
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true
|
|
2342
|
+
};
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
async function listRecommendJobsCli(options = {}) {
|
|
2346
|
+
printJson(await listRecommendJobsTool({
|
|
2347
|
+
workspaceRoot: getWorkspaceRoot(options),
|
|
2348
|
+
args: buildRecommendJobListCliInput(options)
|
|
2349
|
+
}));
|
|
1481
2350
|
}
|
|
1482
2351
|
|
|
1483
2352
|
function buildBossChatCliInput(options = {}) {
|
|
@@ -1487,6 +2356,8 @@ function buildBossChatCliInput(options = {}) {
|
|
|
1487
2356
|
?? options.greetingText
|
|
1488
2357
|
?? options.greeting;
|
|
1489
2358
|
const greetingText = typeof greetingTextRaw === "string" ? greetingTextRaw.trim() : undefined;
|
|
2359
|
+
const targetUrlIncludes = String(options["target-url-includes"] || options.target_url_includes || "").trim();
|
|
2360
|
+
const host = String(options.host || "").trim();
|
|
1490
2361
|
return {
|
|
1491
2362
|
profile: typeof options.profile === "string" ? options.profile.trim() : undefined,
|
|
1492
2363
|
job: typeof options.job === "string" ? options.job.trim() : undefined,
|
|
@@ -1494,7 +2365,14 @@ function buildBossChatCliInput(options = {}) {
|
|
|
1494
2365
|
criteria: typeof options.criteria === "string" ? options.criteria.trim() : undefined,
|
|
1495
2366
|
greeting_text: greetingText || undefined,
|
|
1496
2367
|
target_count: parseBossChatTargetCountOption(options.targetCount || options["target-count"] || options.target_count),
|
|
2368
|
+
host: host || undefined,
|
|
1497
2369
|
port: parsePositivePort(options.port),
|
|
2370
|
+
target_url_includes: targetUrlIncludes || undefined,
|
|
2371
|
+
allow_navigate: !(options["no-navigate"] === true || options.noNavigate === true || options.allow_navigate === false),
|
|
2372
|
+
slow_live: options["slow-live"] === true || options.slowLive === true || options.slow_live === true,
|
|
2373
|
+
detail_limit: parseNonNegativeInteger(options["detail-limit"] ?? options.detail_limit),
|
|
2374
|
+
delay_ms: parseNonNegativeInteger(options["delay-ms"] ?? options.delay_ms),
|
|
2375
|
+
max_candidates: parseNonNegativeInteger(options["max-candidates"] ?? options.max_candidates),
|
|
1498
2376
|
dry_run: options["dry-run"] === true || options.dryRun === true,
|
|
1499
2377
|
no_state: options["no-state"] === true || options.noState === true,
|
|
1500
2378
|
safe_pacing: parseBooleanOption(options["safe-pacing"] ?? options.safe_pacing),
|
|
@@ -1509,67 +2387,69 @@ function getBossChatCliRunTarget(options = {}) {
|
|
|
1509
2387
|
};
|
|
1510
2388
|
}
|
|
1511
2389
|
|
|
2390
|
+
function buildUnsupportedBossChatCliStartResponse(subcommand) {
|
|
2391
|
+
return {
|
|
2392
|
+
status: "FAILED",
|
|
2393
|
+
error: {
|
|
2394
|
+
code: bossChatCliUnsupportedStartCode,
|
|
2395
|
+
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.`,
|
|
2396
|
+
retryable: false
|
|
2397
|
+
},
|
|
2398
|
+
cdp_only: true,
|
|
2399
|
+
runtime_evaluate_used: false,
|
|
2400
|
+
method_summary: {},
|
|
2401
|
+
method_log: []
|
|
2402
|
+
};
|
|
2403
|
+
}
|
|
2404
|
+
|
|
1512
2405
|
async function runBossChatCliCommand(subcommand, options = {}) {
|
|
1513
2406
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
2407
|
+
const input = buildBossChatCliInput(options);
|
|
1514
2408
|
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({
|
|
2409
|
+
printJson(await bossChatHealthCheckTool({
|
|
1523
2410
|
workspaceRoot,
|
|
1524
|
-
|
|
2411
|
+
args: input
|
|
1525
2412
|
}));
|
|
1526
2413
|
return;
|
|
1527
2414
|
}
|
|
1528
2415
|
|
|
1529
|
-
if (subcommand === "run") {
|
|
1530
|
-
printJson(await
|
|
2416
|
+
if (subcommand === "prepare-run") {
|
|
2417
|
+
printJson(await prepareBossChatRunTool({
|
|
1531
2418
|
workspaceRoot,
|
|
1532
|
-
|
|
2419
|
+
args: input
|
|
1533
2420
|
}));
|
|
1534
2421
|
return;
|
|
1535
2422
|
}
|
|
1536
2423
|
|
|
1537
|
-
if (subcommand === "start-run") {
|
|
1538
|
-
printJson(
|
|
1539
|
-
workspaceRoot,
|
|
1540
|
-
input: buildBossChatCliInput(options)
|
|
1541
|
-
}));
|
|
2424
|
+
if (subcommand === "run" || subcommand === "start-run") {
|
|
2425
|
+
printJson(buildUnsupportedBossChatCliStartResponse(subcommand));
|
|
1542
2426
|
return;
|
|
1543
2427
|
}
|
|
1544
2428
|
|
|
1545
2429
|
if (subcommand === "get-run") {
|
|
1546
|
-
printJson(
|
|
1547
|
-
|
|
1548
|
-
input: getBossChatCliRunTarget(options)
|
|
2430
|
+
printJson(getBossChatRunTool({
|
|
2431
|
+
args: getBossChatCliRunTarget(options)
|
|
1549
2432
|
}));
|
|
1550
2433
|
return;
|
|
1551
2434
|
}
|
|
1552
2435
|
|
|
1553
2436
|
if (subcommand === "pause-run") {
|
|
1554
|
-
printJson(
|
|
1555
|
-
|
|
1556
|
-
input: getBossChatCliRunTarget(options)
|
|
2437
|
+
printJson(pauseBossChatRunTool({
|
|
2438
|
+
args: getBossChatCliRunTarget(options)
|
|
1557
2439
|
}));
|
|
1558
2440
|
return;
|
|
1559
2441
|
}
|
|
1560
2442
|
|
|
1561
2443
|
if (subcommand === "resume-run") {
|
|
1562
|
-
printJson(
|
|
1563
|
-
|
|
1564
|
-
input: getBossChatCliRunTarget(options)
|
|
2444
|
+
printJson(resumeBossChatRunTool({
|
|
2445
|
+
args: getBossChatCliRunTarget(options)
|
|
1565
2446
|
}));
|
|
1566
2447
|
return;
|
|
1567
2448
|
}
|
|
1568
2449
|
|
|
1569
2450
|
if (subcommand === "cancel-run") {
|
|
1570
|
-
printJson(
|
|
1571
|
-
|
|
1572
|
-
input: getBossChatCliRunTarget(options)
|
|
2451
|
+
printJson(cancelBossChatRunTool({
|
|
2452
|
+
args: getBossChatCliRunTarget(options)
|
|
1573
2453
|
}));
|
|
1574
2454
|
return;
|
|
1575
2455
|
}
|
|
@@ -1601,6 +2481,23 @@ export async function runCli(argv = process.argv) {
|
|
|
1601
2481
|
process.exitCode = 1;
|
|
1602
2482
|
}
|
|
1603
2483
|
break;
|
|
2484
|
+
case "list-jobs":
|
|
2485
|
+
case "jobs":
|
|
2486
|
+
case "recommend-jobs":
|
|
2487
|
+
try {
|
|
2488
|
+
await listRecommendJobsCli(options);
|
|
2489
|
+
} catch (error) {
|
|
2490
|
+
printJson({
|
|
2491
|
+
status: "FAILED",
|
|
2492
|
+
error: {
|
|
2493
|
+
code: "RECOMMEND_JOB_LIST_CLI_FAILED",
|
|
2494
|
+
message: error.message || "Failed to list recommend jobs",
|
|
2495
|
+
retryable: true
|
|
2496
|
+
}
|
|
2497
|
+
});
|
|
2498
|
+
process.exitCode = 1;
|
|
2499
|
+
}
|
|
2500
|
+
break;
|
|
1604
2501
|
case "chat":
|
|
1605
2502
|
try {
|
|
1606
2503
|
const chatSubcommand = String(argv[3] || "").trim().toLowerCase();
|
|
@@ -1620,7 +2517,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1620
2517
|
break;
|
|
1621
2518
|
case "install":
|
|
1622
2519
|
try {
|
|
1623
|
-
installAll(options);
|
|
2520
|
+
await installAll(options);
|
|
1624
2521
|
} catch (error) {
|
|
1625
2522
|
console.error(error.message || "Install failed.");
|
|
1626
2523
|
process.exitCode = 1;
|
|
@@ -1632,8 +2529,8 @@ export async function runCli(argv = process.argv) {
|
|
|
1632
2529
|
}
|
|
1633
2530
|
break;
|
|
1634
2531
|
case "init-config": {
|
|
1635
|
-
const runtimeDirsResult = ensureRuntimeDirectories(options);
|
|
1636
|
-
const result = ensureUserConfig(options);
|
|
2532
|
+
const runtimeDirsResult = await ensureRuntimeDirectories(options);
|
|
2533
|
+
const result = await ensureUserConfig(options);
|
|
1637
2534
|
console.log(
|
|
1638
2535
|
`Runtime directories prepared: created=${runtimeDirsResult.created.length}, existing=${runtimeDirsResult.existed.length}, failed=${runtimeDirsResult.failed.length}`
|
|
1639
2536
|
);
|
|
@@ -1657,7 +2554,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1657
2554
|
}
|
|
1658
2555
|
case "set-port": {
|
|
1659
2556
|
try {
|
|
1660
|
-
const result = setDebugPort(options);
|
|
2557
|
+
const result = await setDebugPort(options);
|
|
1661
2558
|
console.log(`Preferred debug port saved: ${result.port}`);
|
|
1662
2559
|
console.log(`Updated config: ${result.configPath}`);
|
|
1663
2560
|
console.log("Port priority for runtime commands: --port > BOSS_RECOMMEND_CHROME_PORT > screening-config.json.debugPort > 9222");
|
|
@@ -1668,8 +2565,8 @@ export async function runCli(argv = process.argv) {
|
|
|
1668
2565
|
break;
|
|
1669
2566
|
}
|
|
1670
2567
|
case "set-config": {
|
|
1671
|
-
|
|
1672
|
-
|
|
2568
|
+
try {
|
|
2569
|
+
const result = await setScreeningConfig(options);
|
|
1673
2570
|
console.log(`screening-config.json updated: ${result.path}`);
|
|
1674
2571
|
} catch (error) {
|
|
1675
2572
|
console.error(error.message || "Failed to write screening-config.json.");
|
|
@@ -1682,7 +2579,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1682
2579
|
if (!sub || sub.startsWith("--") || sub === "set") {
|
|
1683
2580
|
const configOptions = sub === "set" ? parseOptions(argv.slice(4)) : options;
|
|
1684
2581
|
try {
|
|
1685
|
-
const result = setScreeningConfig(configOptions);
|
|
2582
|
+
const result = await setScreeningConfig(configOptions);
|
|
1686
2583
|
console.log(`screening-config.json updated: ${result.path}`);
|
|
1687
2584
|
} catch (error) {
|
|
1688
2585
|
console.error(error.message || "Failed to write screening-config.json.");
|
|
@@ -1712,7 +2609,7 @@ export async function runCli(argv = process.argv) {
|
|
|
1712
2609
|
await launchChrome(options);
|
|
1713
2610
|
break;
|
|
1714
2611
|
case "where":
|
|
1715
|
-
printPaths();
|
|
2612
|
+
await printPaths();
|
|
1716
2613
|
break;
|
|
1717
2614
|
case "help":
|
|
1718
2615
|
case "--help":
|
|
@@ -1727,18 +2624,20 @@ export async function runCli(argv = process.argv) {
|
|
|
1727
2624
|
}
|
|
1728
2625
|
|
|
1729
2626
|
export const __testables = {
|
|
2627
|
+
buildRecommendJobListCliInput,
|
|
1730
2628
|
buildBossChatCliInput,
|
|
1731
2629
|
buildDefaultMcpArgs,
|
|
1732
2630
|
buildMcpLaunchConfig,
|
|
2631
|
+
buildUnsupportedRecommendCliRunResponse,
|
|
1733
2632
|
collectRuntimeDirectories,
|
|
1734
|
-
ensureBossChatRuntimeReady,
|
|
2633
|
+
ensureBossChatRuntimeReady: ensureBossChatRuntimeReadyLocal,
|
|
1735
2634
|
ensureRuntimeDirectories,
|
|
1736
2635
|
getBossChatCliRunTarget,
|
|
1737
2636
|
getDefaultMcpPackageSpecifier,
|
|
1738
2637
|
getRunFollowUp,
|
|
1739
2638
|
installSkill,
|
|
1740
2639
|
isInstalledPackageRoot,
|
|
1741
|
-
resolveBossChatRuntimeLayout,
|
|
2640
|
+
resolveBossChatRuntimeLayout: resolveCdpBossChatRuntimeLayout,
|
|
1742
2641
|
runBossChatCliCommand,
|
|
1743
2642
|
runPipelineOnce
|
|
1744
2643
|
};
|