@reconcrap/boss-recommend-mcp 1.1.4 → 1.1.6
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/package.json +13 -2
- package/skills/boss-recommend-pipeline/SKILL.md +1 -4
- package/src/adapters.js +307 -203
- package/src/pipeline.js +450 -375
- package/src/test-adapters-runtime.js +10 -1
- package/src/test-pipeline.js +506 -4
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +113 -34
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +287 -89
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +413 -245
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reconcrap/boss-recommend-mcp",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"boss",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
],
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"chrome-remote-interface": "^0.33.3",
|
|
42
|
+
"sharp": "^0.34.4",
|
|
42
43
|
"ws": "^8.19.0"
|
|
43
44
|
},
|
|
44
45
|
"engines": {
|
|
@@ -47,5 +48,15 @@
|
|
|
47
48
|
"publishConfig": {
|
|
48
49
|
"access": "public"
|
|
49
50
|
},
|
|
50
|
-
"license": "MIT"
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"devDependencies": {},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "git+https://github.com/reconcrap-cpu/boss-recommend-mcp.git"
|
|
56
|
+
},
|
|
57
|
+
"author": "",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/reconcrap-cpu/boss-recommend-mcp/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/reconcrap-cpu/boss-recommend-mcp#readme"
|
|
51
62
|
}
|
|
@@ -213,16 +213,13 @@ CLI fallback 的状态机与 MCP 保持一致:
|
|
|
213
213
|
```text
|
|
214
214
|
你是环境修复 agent。请根据 diagnostics.checks 修复依赖,必须串行执行:
|
|
215
215
|
1) 若 node_cli 失败:先安装 Node.js >=18,未成功前禁止执行 npm install。
|
|
216
|
-
2) 若任意 npm_dep_* 失败:再安装 npm 依赖(chrome-remote-interface / ws)。
|
|
217
|
-
3) 若 python_cli 失败:安装 Python,并确保 python 命令可用。
|
|
218
|
-
4) 若 python_pillow 失败:最后执行 python -m pip install pillow。
|
|
216
|
+
2) 若任意 npm_dep_* 失败:再安装 npm 依赖(chrome-remote-interface / ws / sharp)。
|
|
219
217
|
每一步完成后重新运行 doctor,全部通过后再重试 start_recommend_pipeline_run。
|
|
220
218
|
```
|
|
221
219
|
|
|
222
220
|
安装顺序约束(必须遵守):
|
|
223
221
|
|
|
224
222
|
- 没有 Node.js 时,不能先装 npm 包
|
|
225
|
-
- 没有 Python 时,不能先装 Pillow
|
|
226
223
|
|
|
227
224
|
## Response Style
|
|
228
225
|
|
package/src/adapters.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import CDP from "chrome-remote-interface";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import CDP from "chrome-remote-interface";
|
|
7
7
|
|
|
8
8
|
const currentFilePath = fileURLToPath(import.meta.url);
|
|
9
9
|
const packagedMcpDir = path.resolve(path.dirname(currentFilePath), "..");
|
|
@@ -679,88 +679,6 @@ function buildNodeCommandCheck() {
|
|
|
679
679
|
};
|
|
680
680
|
}
|
|
681
681
|
|
|
682
|
-
function detectPythonCommand() {
|
|
683
|
-
const python = runProcessSync({
|
|
684
|
-
command: "python",
|
|
685
|
-
args: ["--version"]
|
|
686
|
-
});
|
|
687
|
-
if (python.ok) {
|
|
688
|
-
return {
|
|
689
|
-
ok: true,
|
|
690
|
-
command: "python",
|
|
691
|
-
probe: python
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
const python3 = runProcessSync({
|
|
695
|
-
command: "python3",
|
|
696
|
-
args: ["--version"]
|
|
697
|
-
});
|
|
698
|
-
if (python3.ok) {
|
|
699
|
-
return {
|
|
700
|
-
ok: false,
|
|
701
|
-
command: null,
|
|
702
|
-
probe: python,
|
|
703
|
-
fallback: python3
|
|
704
|
-
};
|
|
705
|
-
}
|
|
706
|
-
return {
|
|
707
|
-
ok: false,
|
|
708
|
-
command: null,
|
|
709
|
-
probe: python,
|
|
710
|
-
fallback: null
|
|
711
|
-
};
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
function buildPythonCommandCheck() {
|
|
715
|
-
const detected = detectPythonCommand();
|
|
716
|
-
if (detected.ok) {
|
|
717
|
-
return {
|
|
718
|
-
key: "python_cli",
|
|
719
|
-
ok: true,
|
|
720
|
-
path: "python --version",
|
|
721
|
-
message: `Python 命令可用 (${detected.probe.output || "unknown version"})`
|
|
722
|
-
};
|
|
723
|
-
}
|
|
724
|
-
if (detected.fallback) {
|
|
725
|
-
return {
|
|
726
|
-
key: "python_cli",
|
|
727
|
-
ok: false,
|
|
728
|
-
path: "python --version",
|
|
729
|
-
message: `检测到 ${detected.fallback.output || "python3"},但当前流程依赖 python 命令;请创建 python 别名后重试。`
|
|
730
|
-
};
|
|
731
|
-
}
|
|
732
|
-
return {
|
|
733
|
-
key: "python_cli",
|
|
734
|
-
ok: false,
|
|
735
|
-
path: "python --version",
|
|
736
|
-
message: "未找到 python 命令,请安装 Python 并确保 python 在 PATH 中。"
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function buildPillowCheck() {
|
|
741
|
-
const detected = detectPythonCommand();
|
|
742
|
-
if (!detected.ok || !detected.command) {
|
|
743
|
-
return {
|
|
744
|
-
key: "python_pillow",
|
|
745
|
-
ok: false,
|
|
746
|
-
path: "python -c \"import PIL\"",
|
|
747
|
-
message: "无法校验 Pillow:python 命令不可用。"
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
const probe = runProcessSync({
|
|
751
|
-
command: detected.command,
|
|
752
|
-
args: ["-c", "import PIL, PIL.Image; print(PIL.__version__)"]
|
|
753
|
-
});
|
|
754
|
-
return {
|
|
755
|
-
key: "python_pillow",
|
|
756
|
-
ok: probe.ok,
|
|
757
|
-
path: `${detected.command} -c "import PIL"`,
|
|
758
|
-
message: probe.ok
|
|
759
|
-
? `Pillow 可用 (${probe.output || "version unknown"})`
|
|
760
|
-
: "Pillow 未安装。请执行 `python -m pip install pillow`。"
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
|
|
764
682
|
function buildNodePackageCheck({ key, moduleName, cwd, missingMessage }) {
|
|
765
683
|
if (!cwd || !pathExists(cwd)) {
|
|
766
684
|
return {
|
|
@@ -792,8 +710,6 @@ function buildNodePackageCheck({ key, moduleName, cwd, missingMessage }) {
|
|
|
792
710
|
function buildRuntimeDependencyChecks({ searchDir, screenDir }) {
|
|
793
711
|
return [
|
|
794
712
|
buildNodeCommandCheck(),
|
|
795
|
-
buildPythonCommandCheck(),
|
|
796
|
-
buildPillowCheck(),
|
|
797
713
|
buildNodePackageCheck({
|
|
798
714
|
key: "npm_dep_chrome_remote_interface_search",
|
|
799
715
|
moduleName: "chrome-remote-interface",
|
|
@@ -811,6 +727,12 @@ function buildRuntimeDependencyChecks({ searchDir, screenDir }) {
|
|
|
811
727
|
moduleName: "ws",
|
|
812
728
|
cwd: screenDir,
|
|
813
729
|
missingMessage: "无法校验 ws:boss-recommend-screen-cli 目录不存在。"
|
|
730
|
+
}),
|
|
731
|
+
buildNodePackageCheck({
|
|
732
|
+
key: "npm_dep_sharp",
|
|
733
|
+
moduleName: "sharp",
|
|
734
|
+
cwd: screenDir,
|
|
735
|
+
missingMessage: "无法校验 sharp:boss-recommend-screen-cli 目录不存在。"
|
|
814
736
|
})
|
|
815
737
|
];
|
|
816
738
|
}
|
|
@@ -1065,7 +987,8 @@ function collectNpmInstallDirsFromChecks(checks = [], workspaceRoot) {
|
|
|
1065
987
|
const npmKeys = new Set([
|
|
1066
988
|
"npm_dep_chrome_remote_interface_search",
|
|
1067
989
|
"npm_dep_chrome_remote_interface_screen",
|
|
1068
|
-
"npm_dep_ws"
|
|
990
|
+
"npm_dep_ws",
|
|
991
|
+
"npm_dep_sharp"
|
|
1069
992
|
]);
|
|
1070
993
|
const dirs = checks
|
|
1071
994
|
.filter((item) => item && item.ok === false && npmKeys.has(item.key))
|
|
@@ -1104,28 +1027,6 @@ function installNpmDependencies(checks, workspaceRoot) {
|
|
|
1104
1027
|
};
|
|
1105
1028
|
}
|
|
1106
1029
|
|
|
1107
|
-
function installPillowIfPossible() {
|
|
1108
|
-
const detected = detectPythonCommand();
|
|
1109
|
-
if (!detected.ok || !detected.command) {
|
|
1110
|
-
return {
|
|
1111
|
-
ok: false,
|
|
1112
|
-
action: "install_pillow",
|
|
1113
|
-
changed: false,
|
|
1114
|
-
message: "未检测到可用 python 命令,无法自动安装 Pillow。"
|
|
1115
|
-
};
|
|
1116
|
-
}
|
|
1117
|
-
const install = runProcessSync({
|
|
1118
|
-
command: detected.command,
|
|
1119
|
-
args: ["-m", "pip", "install", "pillow"]
|
|
1120
|
-
});
|
|
1121
|
-
return {
|
|
1122
|
-
ok: install.ok,
|
|
1123
|
-
action: "install_pillow",
|
|
1124
|
-
changed: install.ok,
|
|
1125
|
-
message: install.ok ? "Pillow 自动安装完成。" : `Pillow 自动安装失败:${install.output || install.error_message || "unknown"}`
|
|
1126
|
-
};
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
1030
|
export function attemptPipelineAutoRepair(workspaceRoot, preflight = {}) {
|
|
1130
1031
|
const checks = Array.isArray(preflight.checks) ? preflight.checks : [];
|
|
1131
1032
|
const failed = collectFailedCheckKeys(checks);
|
|
@@ -1135,6 +1036,7 @@ export function attemptPipelineAutoRepair(workspaceRoot, preflight = {}) {
|
|
|
1135
1036
|
failed.has("npm_dep_chrome_remote_interface_search")
|
|
1136
1037
|
|| failed.has("npm_dep_chrome_remote_interface_screen")
|
|
1137
1038
|
|| failed.has("npm_dep_ws")
|
|
1039
|
+
|| failed.has("npm_dep_sharp")
|
|
1138
1040
|
) {
|
|
1139
1041
|
if (!failed.has("node_cli")) {
|
|
1140
1042
|
actions.push(installNpmDependencies(checks, workspaceRoot));
|
|
@@ -1148,19 +1050,6 @@ export function attemptPipelineAutoRepair(workspaceRoot, preflight = {}) {
|
|
|
1148
1050
|
}
|
|
1149
1051
|
}
|
|
1150
1052
|
|
|
1151
|
-
if (failed.has("python_pillow")) {
|
|
1152
|
-
if (!failed.has("python_cli")) {
|
|
1153
|
-
actions.push(installPillowIfPossible());
|
|
1154
|
-
} else {
|
|
1155
|
-
actions.push({
|
|
1156
|
-
ok: false,
|
|
1157
|
-
action: "install_pillow",
|
|
1158
|
-
changed: false,
|
|
1159
|
-
message: "python 命令不可用,跳过 Pillow 自动安装。"
|
|
1160
|
-
});
|
|
1161
|
-
}
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
1053
|
const attempted = actions.length > 0;
|
|
1165
1054
|
const nextPreflight = runPipelinePreflight(workspaceRoot);
|
|
1166
1055
|
return {
|
|
@@ -1361,10 +1250,10 @@ async function openBossRecommendTab(port) {
|
|
|
1361
1250
|
};
|
|
1362
1251
|
}
|
|
1363
1252
|
|
|
1364
|
-
async function verifyRecommendPageStable(port, options = {}) {
|
|
1365
|
-
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1500;
|
|
1366
|
-
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 2500;
|
|
1367
|
-
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1253
|
+
async function verifyRecommendPageStable(port, options = {}) {
|
|
1254
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1500;
|
|
1255
|
+
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 2500;
|
|
1256
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1368
1257
|
|
|
1369
1258
|
await sleep(settleMs);
|
|
1370
1259
|
const recheck = await inspectBossRecommendPageState(port, {
|
|
@@ -1381,79 +1270,294 @@ async function verifyRecommendPageStable(port, options = {}) {
|
|
|
1381
1270
|
message: "Boss 页面曾进入 recommend 但随后跳转到其他页面,通常表示登录态失效。"
|
|
1382
1271
|
});
|
|
1383
1272
|
}
|
|
1384
|
-
return recheck;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
function pickBossRecommendReloadTarget(tabs = []) {
|
|
1388
|
-
return tabs.find(
|
|
1389
|
-
(tab) => typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend")
|
|
1390
|
-
) || tabs.find(
|
|
1391
|
-
(tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")
|
|
1392
|
-
) || null;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
const
|
|
1397
|
-
|
|
1398
|
-
:
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
const
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
};
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1273
|
+
return recheck;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
function pickBossRecommendReloadTarget(tabs = []) {
|
|
1277
|
+
return tabs.find(
|
|
1278
|
+
(tab) => typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend")
|
|
1279
|
+
) || tabs.find(
|
|
1280
|
+
(tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")
|
|
1281
|
+
) || null;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
async function evaluateCdpExpression(client, expression) {
|
|
1285
|
+
const result = await client.Runtime.evaluate({
|
|
1286
|
+
expression,
|
|
1287
|
+
returnByValue: true,
|
|
1288
|
+
awaitPromise: true
|
|
1289
|
+
});
|
|
1290
|
+
if (result.exceptionDetails) {
|
|
1291
|
+
throw new Error(result.exceptionDetails.exception?.description || "Runtime.evaluate failed");
|
|
1292
|
+
}
|
|
1293
|
+
return result.result?.value;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function buildRecommendRefreshStateExpression() {
|
|
1297
|
+
return `(() => {
|
|
1298
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
1299
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
1300
|
+
|| document.querySelector('iframe');
|
|
1301
|
+
if (!frame || !frame.contentDocument) {
|
|
1302
|
+
return { ok: false, error: 'NO_RECOMMEND_IFRAME' };
|
|
1303
|
+
}
|
|
1304
|
+
const doc = frame.contentDocument;
|
|
1305
|
+
const isVisible = (el) => {
|
|
1306
|
+
if (!el) return false;
|
|
1307
|
+
const win = doc.defaultView;
|
|
1308
|
+
if (!win) return el.offsetParent !== null;
|
|
1309
|
+
const style = win.getComputedStyle(el);
|
|
1310
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.02) {
|
|
1311
|
+
return false;
|
|
1312
|
+
}
|
|
1313
|
+
const rect = el.getBoundingClientRect();
|
|
1314
|
+
return rect.width > 2 && rect.height > 2 && el.offsetParent !== null;
|
|
1315
|
+
};
|
|
1316
|
+
const finishedWrap = Array.from(doc.querySelectorAll('.finished-wrap')).find((el) => isVisible(el)) || null;
|
|
1317
|
+
const refreshButton = Array.from(doc.querySelectorAll('.finished-wrap .btn.btn-refresh, .finished-wrap .btn-refresh, .no-data-refresh .btn-refresh'))
|
|
1318
|
+
.find((el) => isVisible(el)) || null;
|
|
1319
|
+
const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
|
|
1320
|
+
const candidateCards = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
1321
|
+
const finishedText = finishedWrap ? String(finishedWrap.textContent || '').replace(/\\s+/g, ' ').trim() : '';
|
|
1322
|
+
const buttonText = refreshButton ? String(refreshButton.textContent || '').replace(/\\s+/g, ' ').trim() : '';
|
|
1323
|
+
return {
|
|
1324
|
+
ok: true,
|
|
1325
|
+
frame_url: (() => {
|
|
1326
|
+
try { return String(frame.contentWindow.location.href || ''); } catch { return ''; }
|
|
1327
|
+
})(),
|
|
1328
|
+
finished_wrap_visible: Boolean(finishedWrap),
|
|
1329
|
+
finished_wrap_text: finishedText || null,
|
|
1330
|
+
refresh_button_visible: Boolean(refreshButton),
|
|
1331
|
+
refresh_button_text: buttonText || null,
|
|
1332
|
+
candidate_count: candidateCards.length,
|
|
1333
|
+
total_card_count: cards.length,
|
|
1334
|
+
list_ready: candidateCards.length > 0
|
|
1335
|
+
};
|
|
1336
|
+
})()`;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
function buildRecommendRefreshClickExpression() {
|
|
1340
|
+
return `(() => {
|
|
1341
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
1342
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
1343
|
+
|| document.querySelector('iframe');
|
|
1344
|
+
if (!frame || !frame.contentDocument) {
|
|
1345
|
+
return { ok: false, state: 'NO_RECOMMEND_IFRAME' };
|
|
1346
|
+
}
|
|
1347
|
+
const doc = frame.contentDocument;
|
|
1348
|
+
const isVisible = (el) => {
|
|
1349
|
+
if (!el) return false;
|
|
1350
|
+
const win = doc.defaultView;
|
|
1351
|
+
if (!win) return el.offsetParent !== null;
|
|
1352
|
+
const style = win.getComputedStyle(el);
|
|
1353
|
+
if (style.display === 'none' || style.visibility === 'hidden' || Number(style.opacity || '1') < 0.02) {
|
|
1354
|
+
return false;
|
|
1355
|
+
}
|
|
1356
|
+
const rect = el.getBoundingClientRect();
|
|
1357
|
+
return rect.width > 2 && rect.height > 2 && el.offsetParent !== null;
|
|
1358
|
+
};
|
|
1359
|
+
const refreshButton = Array.from(doc.querySelectorAll('.finished-wrap .btn.btn-refresh, .finished-wrap .btn-refresh, .no-data-refresh .btn-refresh'))
|
|
1360
|
+
.find((el) => isVisible(el)) || null;
|
|
1361
|
+
if (!refreshButton) {
|
|
1362
|
+
return { ok: false, state: 'REFRESH_BUTTON_NOT_FOUND' };
|
|
1363
|
+
}
|
|
1364
|
+
try {
|
|
1365
|
+
refreshButton.click();
|
|
1366
|
+
return {
|
|
1367
|
+
ok: true,
|
|
1368
|
+
state: 'REFRESH_BUTTON_CLICKED',
|
|
1369
|
+
refresh_button_text: String(refreshButton.textContent || '').replace(/\\s+/g, ' ').trim() || null
|
|
1370
|
+
};
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
return {
|
|
1373
|
+
ok: false,
|
|
1374
|
+
state: 'REFRESH_BUTTON_CLICK_FAILED',
|
|
1375
|
+
message: error?.message || String(error)
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1378
|
+
})()`;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
export async function refreshBossRecommendList(workspaceRoot, options = {}) {
|
|
1382
|
+
const debugPort = Number.isFinite(options.port)
|
|
1383
|
+
? options.port
|
|
1384
|
+
: resolveWorkspaceDebugPort(workspaceRoot);
|
|
1385
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1386
|
+
const reloadTimeoutMs = Number.isFinite(options.reloadTimeoutMs) ? options.reloadTimeoutMs : 10000;
|
|
1387
|
+
|
|
1388
|
+
let client = null;
|
|
1389
|
+
try {
|
|
1390
|
+
const tabs = await listChromeTabs(debugPort);
|
|
1391
|
+
const target = pickBossRecommendReloadTarget(tabs);
|
|
1392
|
+
if (!target) {
|
|
1393
|
+
return {
|
|
1394
|
+
ok: false,
|
|
1395
|
+
action: "in_page_refresh",
|
|
1396
|
+
debug_port: debugPort,
|
|
1397
|
+
state: "BOSS_TAB_NOT_FOUND",
|
|
1398
|
+
message: "未找到可操作的 Boss recommend 标签页。",
|
|
1399
|
+
before_state: null,
|
|
1400
|
+
after_state: null
|
|
1401
|
+
};
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
client = await CDP({ port: debugPort, target });
|
|
1405
|
+
const { Page, Runtime } = client;
|
|
1406
|
+
if (Runtime && typeof Runtime.enable === "function") {
|
|
1407
|
+
await Runtime.enable();
|
|
1408
|
+
}
|
|
1409
|
+
if (Page && typeof Page.enable === "function") {
|
|
1410
|
+
await Page.enable();
|
|
1411
|
+
}
|
|
1412
|
+
if (Page && typeof Page.bringToFront === "function") {
|
|
1413
|
+
await Page.bringToFront();
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
const beforeState = await evaluateCdpExpression(client, buildRecommendRefreshStateExpression());
|
|
1417
|
+
if (!beforeState?.ok) {
|
|
1418
|
+
return {
|
|
1419
|
+
ok: false,
|
|
1420
|
+
action: "in_page_refresh",
|
|
1421
|
+
debug_port: debugPort,
|
|
1422
|
+
state: beforeState?.error || "NO_RECOMMEND_IFRAME",
|
|
1423
|
+
message: "未能读取 recommend iframe,无法执行页内刷新。",
|
|
1424
|
+
before_state: beforeState || null,
|
|
1425
|
+
after_state: null
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
if (!beforeState.refresh_button_visible) {
|
|
1429
|
+
return {
|
|
1430
|
+
ok: false,
|
|
1431
|
+
action: "in_page_refresh",
|
|
1432
|
+
debug_port: debugPort,
|
|
1433
|
+
state: "REFRESH_BUTTON_NOT_FOUND",
|
|
1434
|
+
message: "推荐列表到底后未发现可点击的刷新按钮。",
|
|
1435
|
+
before_state: beforeState,
|
|
1436
|
+
after_state: beforeState
|
|
1437
|
+
};
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
const clickResult = await evaluateCdpExpression(client, buildRecommendRefreshClickExpression());
|
|
1441
|
+
if (!clickResult?.ok) {
|
|
1442
|
+
return {
|
|
1443
|
+
ok: false,
|
|
1444
|
+
action: "in_page_refresh",
|
|
1445
|
+
debug_port: debugPort,
|
|
1446
|
+
state: clickResult?.state || "REFRESH_BUTTON_CLICK_FAILED",
|
|
1447
|
+
message: clickResult?.message || "页内刷新按钮点击失败。",
|
|
1448
|
+
before_state: beforeState,
|
|
1449
|
+
after_state: null
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
const deadline = Date.now() + reloadTimeoutMs;
|
|
1454
|
+
let lastState = beforeState;
|
|
1455
|
+
while (Date.now() < deadline) {
|
|
1456
|
+
await sleep(pollMs);
|
|
1457
|
+
lastState = await evaluateCdpExpression(client, buildRecommendRefreshStateExpression());
|
|
1458
|
+
if (lastState?.ok && lastState.finished_wrap_visible === false && lastState.list_ready === true) {
|
|
1459
|
+
return {
|
|
1460
|
+
ok: true,
|
|
1461
|
+
action: "in_page_refresh",
|
|
1462
|
+
debug_port: debugPort,
|
|
1463
|
+
state: "RECOMMEND_READY",
|
|
1464
|
+
message: "已点击页内刷新按钮并重新拿到候选人列表。",
|
|
1465
|
+
before_state: beforeState,
|
|
1466
|
+
after_state: lastState
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
return {
|
|
1472
|
+
ok: false,
|
|
1473
|
+
action: "in_page_refresh",
|
|
1474
|
+
debug_port: debugPort,
|
|
1475
|
+
state: "LIST_NOT_RELOADED",
|
|
1476
|
+
message: "已点击页内刷新按钮,但候选人列表未在超时内重新就绪。",
|
|
1477
|
+
before_state: beforeState,
|
|
1478
|
+
after_state: lastState
|
|
1479
|
+
};
|
|
1480
|
+
} catch (error) {
|
|
1481
|
+
return {
|
|
1482
|
+
ok: false,
|
|
1483
|
+
action: "in_page_refresh",
|
|
1484
|
+
debug_port: debugPort,
|
|
1485
|
+
state: "REFRESH_BUTTON_CLICK_FAILED",
|
|
1486
|
+
message: error?.message || "页内刷新失败。",
|
|
1487
|
+
before_state: null,
|
|
1488
|
+
after_state: null
|
|
1489
|
+
};
|
|
1490
|
+
} finally {
|
|
1491
|
+
if (client) {
|
|
1492
|
+
try {
|
|
1493
|
+
await client.close();
|
|
1494
|
+
} catch {}
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
export async function reloadBossRecommendPage(workspaceRoot, options = {}) {
|
|
1500
|
+
const debugPort = Number.isFinite(options.port)
|
|
1501
|
+
? options.port
|
|
1502
|
+
: resolveWorkspaceDebugPort(workspaceRoot);
|
|
1503
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1200;
|
|
1504
|
+
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 4000;
|
|
1505
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1506
|
+
|
|
1507
|
+
let client = null;
|
|
1508
|
+
try {
|
|
1509
|
+
const tabs = await listChromeTabs(debugPort);
|
|
1510
|
+
const target = pickBossRecommendReloadTarget(tabs);
|
|
1511
|
+
if (!target) {
|
|
1512
|
+
return {
|
|
1513
|
+
ok: false,
|
|
1514
|
+
debug_port: debugPort,
|
|
1515
|
+
state: "BOSS_TAB_NOT_FOUND",
|
|
1516
|
+
page_state: null,
|
|
1517
|
+
message: "未找到可刷新的 Boss 标签页。"
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
client = await CDP({ port: debugPort, target });
|
|
1522
|
+
const { Page } = client;
|
|
1523
|
+
if (Page && typeof Page.enable === "function") {
|
|
1524
|
+
await Page.enable();
|
|
1525
|
+
}
|
|
1526
|
+
if (Page && typeof Page.bringToFront === "function") {
|
|
1527
|
+
await Page.bringToFront();
|
|
1528
|
+
}
|
|
1529
|
+
await Page.reload({ ignoreCache: true });
|
|
1530
|
+
|
|
1531
|
+
const stableState = await verifyRecommendPageStable(debugPort, {
|
|
1532
|
+
settleMs,
|
|
1533
|
+
recheckTimeoutMs,
|
|
1534
|
+
pollMs
|
|
1535
|
+
});
|
|
1536
|
+
return {
|
|
1537
|
+
ok: stableState.state === "RECOMMEND_READY",
|
|
1538
|
+
debug_port: debugPort,
|
|
1539
|
+
state: stableState.state,
|
|
1540
|
+
page_state: stableState,
|
|
1541
|
+
reloaded_url: target.url || null
|
|
1542
|
+
};
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
return {
|
|
1545
|
+
ok: false,
|
|
1546
|
+
debug_port: debugPort,
|
|
1547
|
+
state: "RELOAD_FAILED",
|
|
1548
|
+
page_state: null,
|
|
1549
|
+
message: error?.message || "刷新 Boss recommend 页面失败。"
|
|
1550
|
+
};
|
|
1551
|
+
} finally {
|
|
1552
|
+
if (client) {
|
|
1553
|
+
try {
|
|
1554
|
+
await client.close();
|
|
1555
|
+
} catch {}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
export async function ensureBossRecommendPageReady(workspaceRoot, options = {}) {
|
|
1457
1561
|
const debugPort = Number.isFinite(options.port)
|
|
1458
1562
|
? options.port
|
|
1459
1563
|
: resolveWorkspaceDebugPort(workspaceRoot);
|