@reconcrap/boss-recommend-mcp 2.0.2 → 2.0.4
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 +3 -3
- package/config/screening-config.example.json +10 -8
- package/package.json +1 -1
- package/skills/boss-chat/SKILL.md +3 -0
- package/skills/boss-recommend-pipeline/SKILL.md +9 -2
- package/skills/boss-recruit-pipeline/SKILL.md +3 -0
- package/src/chat-mcp.js +160 -41
- package/src/chat-runtime-config.js +28 -1
- package/src/core/browser/index.js +392 -0
- package/src/core/screening/index.js +3 -3
- package/src/index.js +4 -2
- package/src/recommend-mcp.js +142 -33
- package/src/recruit-mcp.js +157 -30
package/src/recommend-mcp.js
CHANGED
|
@@ -4,8 +4,13 @@ import process from "node:process";
|
|
|
4
4
|
import {
|
|
5
5
|
assertNoForbiddenCdpCalls,
|
|
6
6
|
bringPageToFront,
|
|
7
|
-
|
|
7
|
+
connectToChromeTargetOrOpen,
|
|
8
|
+
createBossLoginRequiredError,
|
|
9
|
+
detectBossLoginState,
|
|
8
10
|
enableDomains,
|
|
11
|
+
getMainFrameUrl,
|
|
12
|
+
isBossLoginUrl,
|
|
13
|
+
waitForMainFrameUrl,
|
|
9
14
|
sleep
|
|
10
15
|
} from "./core/browser/index.js";
|
|
11
16
|
import {
|
|
@@ -39,6 +44,10 @@ import {
|
|
|
39
44
|
parseRecommendInstruction
|
|
40
45
|
} from "./parser.js";
|
|
41
46
|
import { getRunsDir } from "./run-state.js";
|
|
47
|
+
import {
|
|
48
|
+
resolveBossConfiguredOutputDir,
|
|
49
|
+
resolveBossScreeningConfig
|
|
50
|
+
} from "./chat-runtime-config.js";
|
|
42
51
|
|
|
43
52
|
const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
|
|
44
53
|
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
@@ -110,12 +119,14 @@ function getRecommendRunArtifacts(runId) {
|
|
|
110
119
|
const normalized = normalizeRunId(runId);
|
|
111
120
|
if (!normalized) return null;
|
|
112
121
|
const runsDir = getRunsDir();
|
|
122
|
+
const outputDir = resolveBossConfiguredOutputDir("", runsDir);
|
|
113
123
|
return {
|
|
114
124
|
runs_dir: runsDir,
|
|
125
|
+
output_dir: outputDir,
|
|
115
126
|
run_state_path: path.join(runsDir, `${normalized}.json`),
|
|
116
127
|
checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
|
|
117
|
-
output_csv: path.join(
|
|
118
|
-
report_json: path.join(
|
|
128
|
+
output_csv: path.join(outputDir, `${normalized}.results.csv`),
|
|
129
|
+
report_json: path.join(outputDir, `${normalized}.report.json`)
|
|
119
130
|
};
|
|
120
131
|
}
|
|
121
132
|
|
|
@@ -479,8 +490,12 @@ async function readRecommendJobOptionsFromSession(session) {
|
|
|
479
490
|
}
|
|
480
491
|
|
|
481
492
|
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
493
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
482
494
|
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
483
|
-
const port = parsePositiveInteger(
|
|
495
|
+
const port = parsePositiveInteger(
|
|
496
|
+
args.port,
|
|
497
|
+
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
498
|
+
);
|
|
484
499
|
const targetUrlIncludes = normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL;
|
|
485
500
|
const allowNavigate = args.allow_navigate !== false;
|
|
486
501
|
const slowLive = args.slow_live === true;
|
|
@@ -526,27 +541,36 @@ export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } =
|
|
|
526
541
|
host,
|
|
527
542
|
port,
|
|
528
543
|
target_url: session.navigation?.url || session.target?.url || RECOMMEND_TARGET_URL,
|
|
529
|
-
target_id: session.target?.id || null
|
|
544
|
+
target_id: session.target?.id || null,
|
|
545
|
+
auto_launch: session.chrome || null
|
|
530
546
|
},
|
|
531
547
|
method_summary: methodSummary(session.methodLog || []),
|
|
532
548
|
method_log: session.methodLog || []
|
|
533
549
|
};
|
|
534
550
|
} catch (error) {
|
|
535
551
|
const methodLog = session?.methodLog || [];
|
|
552
|
+
const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
|
|
536
553
|
return {
|
|
537
554
|
status: "FAILED",
|
|
538
555
|
stage: "recommend_job_list",
|
|
539
556
|
cdp_only: true,
|
|
540
557
|
runtime_evaluate_used: methodLog.some((entry) => String(entry?.method || entry).startsWith("Runtime.")),
|
|
541
558
|
error: {
|
|
542
|
-
code: "RECOMMEND_JOB_LIST_FAILED",
|
|
559
|
+
code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "RECOMMEND_JOB_LIST_FAILED",
|
|
543
560
|
message: error?.message || "Failed to read recommend job list",
|
|
561
|
+
requires_login: Boolean(error?.requires_login),
|
|
562
|
+
login_url: error?.login_url || null,
|
|
563
|
+
login_detection: error?.login_detection || null,
|
|
564
|
+
current_url: error?.current_url || null,
|
|
565
|
+
target_url: error?.target_url || RECOMMEND_TARGET_URL,
|
|
566
|
+
chrome: error?.chrome || null,
|
|
544
567
|
retryable: true
|
|
545
568
|
},
|
|
546
569
|
chrome: {
|
|
547
570
|
host,
|
|
548
571
|
port,
|
|
549
|
-
target_url: targetUrlIncludes
|
|
572
|
+
target_url: targetUrlIncludes,
|
|
573
|
+
auto_launch: error?.chrome || session?.chrome || null
|
|
550
574
|
},
|
|
551
575
|
method_summary: methodSummary(methodLog),
|
|
552
576
|
method_log: methodLog
|
|
@@ -585,6 +609,14 @@ async function waitForHealthyRecommend(client, config, {
|
|
|
585
609
|
const started = Date.now();
|
|
586
610
|
let lastCheck = null;
|
|
587
611
|
while (Date.now() - started <= timeoutMs) {
|
|
612
|
+
const loginDetection = await detectBossLoginState(client).catch(() => null);
|
|
613
|
+
if (loginDetection?.requires_login) {
|
|
614
|
+
return {
|
|
615
|
+
status: "login_required",
|
|
616
|
+
summary: "Boss login is required",
|
|
617
|
+
loginDetection
|
|
618
|
+
};
|
|
619
|
+
}
|
|
588
620
|
const roots = await resolveRecommendSelfHealRoots(client, config);
|
|
589
621
|
lastCheck = await runSelfHealCheck({
|
|
590
622
|
client,
|
|
@@ -610,24 +642,18 @@ async function connectRecommendChromeSession({
|
|
|
610
642
|
allowNavigate = true,
|
|
611
643
|
slowLive = false
|
|
612
644
|
} = {}) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
targetPredicate: (target) => (
|
|
626
|
-
target?.type === "page"
|
|
627
|
-
&& String(target?.url || "").includes("zhipin.com/web/chat")
|
|
628
|
-
)
|
|
629
|
-
});
|
|
630
|
-
}
|
|
645
|
+
const session = await connectToChromeTargetOrOpen({
|
|
646
|
+
host,
|
|
647
|
+
port,
|
|
648
|
+
targetUrlIncludes,
|
|
649
|
+
targetUrl: RECOMMEND_TARGET_URL,
|
|
650
|
+
allowNavigate,
|
|
651
|
+
slowLive,
|
|
652
|
+
fallbackTargetPredicate: (target) => (
|
|
653
|
+
target?.type === "page"
|
|
654
|
+
&& String(target?.url || "").includes("zhipin.com")
|
|
655
|
+
)
|
|
656
|
+
});
|
|
631
657
|
|
|
632
658
|
const { client, target } = session;
|
|
633
659
|
await enableDomains(client, ["Page", "DOM", "Input", "Network", "Accessibility"]);
|
|
@@ -644,12 +670,56 @@ async function connectRecommendChromeSession({
|
|
|
644
670
|
if (allowNavigate && shouldNavigateToRecommend(targetUrl)) {
|
|
645
671
|
await client.Page.navigate({ url: RECOMMEND_TARGET_URL });
|
|
646
672
|
const settleMs = slowLive ? 12000 : 5000;
|
|
647
|
-
await
|
|
673
|
+
const waited = await waitForMainFrameUrl(
|
|
674
|
+
client,
|
|
675
|
+
(url) => isBossLoginUrl(url) || !shouldNavigateToRecommend(url),
|
|
676
|
+
{ timeoutMs: settleMs, intervalMs: 500 }
|
|
677
|
+
);
|
|
678
|
+
navigation = {
|
|
679
|
+
navigated: true,
|
|
680
|
+
url: RECOMMEND_TARGET_URL,
|
|
681
|
+
settle_ms: settleMs,
|
|
682
|
+
observed_url: waited.url || null,
|
|
683
|
+
observed_url_ok: waited.ok
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
let currentUrl = await getMainFrameUrl(client).catch(() => navigation.url || targetUrl);
|
|
687
|
+
if (allowNavigate && shouldNavigateToRecommend(currentUrl) && !isBossLoginUrl(currentUrl)) {
|
|
688
|
+
await client.Page.navigate({ url: RECOMMEND_TARGET_URL });
|
|
689
|
+
const settleMs = slowLive ? 12000 : 5000;
|
|
690
|
+
const waited = await waitForMainFrameUrl(
|
|
691
|
+
client,
|
|
692
|
+
(url) => isBossLoginUrl(url) || !shouldNavigateToRecommend(url),
|
|
693
|
+
{ timeoutMs: settleMs, intervalMs: 500 }
|
|
694
|
+
);
|
|
648
695
|
navigation = {
|
|
649
696
|
navigated: true,
|
|
650
697
|
url: RECOMMEND_TARGET_URL,
|
|
651
|
-
settle_ms: settleMs
|
|
698
|
+
settle_ms: settleMs,
|
|
699
|
+
observed_url: waited.url || null,
|
|
700
|
+
observed_url_ok: waited.ok,
|
|
701
|
+
reason: "observed_url_mismatch"
|
|
652
702
|
};
|
|
703
|
+
currentUrl = await getMainFrameUrl(client).catch(() => waited.url || currentUrl);
|
|
704
|
+
}
|
|
705
|
+
const loginDetection = await detectBossLoginState(client, { currentUrl }).catch(() => ({
|
|
706
|
+
requires_login: isBossLoginUrl(currentUrl),
|
|
707
|
+
reason: "login_detection_failed",
|
|
708
|
+
current_url: currentUrl
|
|
709
|
+
}));
|
|
710
|
+
if (loginDetection.requires_login) {
|
|
711
|
+
await session.close?.();
|
|
712
|
+
throw createBossLoginRequiredError({
|
|
713
|
+
domain: "recommend",
|
|
714
|
+
currentUrl: loginDetection.current_url || currentUrl,
|
|
715
|
+
targetUrl: RECOMMEND_TARGET_URL,
|
|
716
|
+
loginDetection,
|
|
717
|
+
chrome: session.chrome || null
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
if (shouldNavigateToRecommend(currentUrl)) {
|
|
721
|
+
await session.close?.();
|
|
722
|
+
throw new Error(`Boss recommend page did not navigate to ${RECOMMEND_TARGET_URL}; current URL: ${currentUrl || "unknown"}`);
|
|
653
723
|
}
|
|
654
724
|
|
|
655
725
|
const selfHealConfig = buildRecommendSelfHealConfig();
|
|
@@ -657,7 +727,33 @@ async function connectRecommendChromeSession({
|
|
|
657
727
|
timeoutMs: slowLive ? 180000 : 90000,
|
|
658
728
|
intervalMs: slowLive ? 1200 : 800
|
|
659
729
|
});
|
|
730
|
+
if (health?.loginDetection?.requires_login) {
|
|
731
|
+
await session.close?.();
|
|
732
|
+
throw createBossLoginRequiredError({
|
|
733
|
+
domain: "recommend",
|
|
734
|
+
currentUrl: health.loginDetection.current_url || currentUrl,
|
|
735
|
+
targetUrl: RECOMMEND_TARGET_URL,
|
|
736
|
+
loginDetection: health.loginDetection,
|
|
737
|
+
chrome: session.chrome || null
|
|
738
|
+
});
|
|
739
|
+
}
|
|
660
740
|
if (!health || health.status !== HEALTH_STATUS.HEALTHY) {
|
|
741
|
+
const latestUrl = await getMainFrameUrl(client).catch(() => currentUrl);
|
|
742
|
+
const latestLoginDetection = await detectBossLoginState(client, { currentUrl: latestUrl }).catch(() => ({
|
|
743
|
+
requires_login: isBossLoginUrl(latestUrl),
|
|
744
|
+
reason: "login_detection_failed",
|
|
745
|
+
current_url: latestUrl
|
|
746
|
+
}));
|
|
747
|
+
if (latestLoginDetection.requires_login) {
|
|
748
|
+
await session.close?.();
|
|
749
|
+
throw createBossLoginRequiredError({
|
|
750
|
+
domain: "recommend",
|
|
751
|
+
currentUrl: latestLoginDetection.current_url || latestUrl,
|
|
752
|
+
targetUrl: RECOMMEND_TARGET_URL,
|
|
753
|
+
loginDetection: latestLoginDetection,
|
|
754
|
+
chrome: session.chrome || null
|
|
755
|
+
});
|
|
756
|
+
}
|
|
661
757
|
throw new Error(`Boss recommend page is not healthy: ${health?.status || "missing"}`);
|
|
662
758
|
}
|
|
663
759
|
|
|
@@ -836,7 +932,7 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
836
932
|
return groups.length ? { filterGroups: groups } : { enabled: false };
|
|
837
933
|
}
|
|
838
934
|
|
|
839
|
-
function normalizeRecommendStartInput(args = {}, parsed) {
|
|
935
|
+
function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
|
|
840
936
|
const confirmation = args.confirmation || {};
|
|
841
937
|
const overrides = args.overrides || {};
|
|
842
938
|
const slowLive = args.slow_live === true;
|
|
@@ -846,7 +942,10 @@ function normalizeRecommendStartInput(args = {}, parsed) {
|
|
|
846
942
|
);
|
|
847
943
|
return {
|
|
848
944
|
host: normalizeText(args.host) || DEFAULT_RECOMMEND_HOST,
|
|
849
|
-
port: parsePositiveInteger(
|
|
945
|
+
port: parsePositiveInteger(
|
|
946
|
+
args.port,
|
|
947
|
+
configResolution?.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
948
|
+
),
|
|
850
949
|
targetUrlIncludes: normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL,
|
|
851
950
|
allowNavigate: args.allow_navigate !== false,
|
|
852
951
|
slowLive,
|
|
@@ -952,7 +1051,8 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
952
1051
|
const parsed = parseRecommendPipelineRequest(args);
|
|
953
1052
|
const gate = evaluateRecommendPipelineGate(parsed, args);
|
|
954
1053
|
if (gate) return gate;
|
|
955
|
-
const
|
|
1054
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1055
|
+
const normalized = normalizeRecommendStartInput(args, parsed, configResolution);
|
|
956
1056
|
|
|
957
1057
|
let session;
|
|
958
1058
|
try {
|
|
@@ -964,13 +1064,21 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
964
1064
|
slowLive: normalized.slowLive
|
|
965
1065
|
});
|
|
966
1066
|
} catch (error) {
|
|
1067
|
+
const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
|
|
967
1068
|
return {
|
|
968
1069
|
status: "FAILED",
|
|
969
1070
|
error: {
|
|
970
|
-
code: "BOSS_RECOMMEND_PAGE_NOT_READY",
|
|
1071
|
+
code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_RECOMMEND_PAGE_NOT_READY",
|
|
971
1072
|
message: error?.message || "Boss recommend page is not ready",
|
|
1073
|
+
requires_login: Boolean(error?.requires_login),
|
|
1074
|
+
login_url: error?.login_url || null,
|
|
1075
|
+
login_detection: error?.login_detection || null,
|
|
1076
|
+
chrome: error?.chrome || null,
|
|
1077
|
+
current_url: error?.current_url || null,
|
|
1078
|
+
target_url: error?.target_url || RECOMMEND_TARGET_URL,
|
|
972
1079
|
retryable: true
|
|
973
|
-
}
|
|
1080
|
+
},
|
|
1081
|
+
chrome: error?.chrome || null
|
|
974
1082
|
};
|
|
975
1083
|
}
|
|
976
1084
|
|
|
@@ -1000,7 +1108,8 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1000
1108
|
host: normalized.host,
|
|
1001
1109
|
port: normalized.port,
|
|
1002
1110
|
target_url: session.navigation?.url || session.target?.url || RECOMMEND_TARGET_URL,
|
|
1003
|
-
target_id: session.target?.id || null
|
|
1111
|
+
target_id: session.target?.id || null,
|
|
1112
|
+
auto_launch: session.chrome || null
|
|
1004
1113
|
},
|
|
1005
1114
|
health: session.health || null
|
|
1006
1115
|
});
|
package/src/recruit-mcp.js
CHANGED
|
@@ -4,8 +4,13 @@ import path from "node:path";
|
|
|
4
4
|
import {
|
|
5
5
|
assertNoForbiddenCdpCalls,
|
|
6
6
|
bringPageToFront,
|
|
7
|
-
|
|
7
|
+
connectToChromeTargetOrOpen,
|
|
8
|
+
createBossLoginRequiredError,
|
|
9
|
+
detectBossLoginState,
|
|
8
10
|
enableDomains,
|
|
11
|
+
getMainFrameUrl,
|
|
12
|
+
isBossLoginUrl,
|
|
13
|
+
waitForMainFrameUrl,
|
|
9
14
|
sleep
|
|
10
15
|
} from "./core/browser/index.js";
|
|
11
16
|
import {
|
|
@@ -27,6 +32,10 @@ import {
|
|
|
27
32
|
runRecruitWorkflow,
|
|
28
33
|
waitForRecruitSearchControls
|
|
29
34
|
} from "./domains/recruit/index.js";
|
|
35
|
+
import {
|
|
36
|
+
resolveBossConfiguredOutputDir,
|
|
37
|
+
resolveBossScreeningConfig
|
|
38
|
+
} from "./chat-runtime-config.js";
|
|
30
39
|
|
|
31
40
|
const RUN_MODE_ASYNC = "async";
|
|
32
41
|
const RUN_MODE_SYNC = "sync";
|
|
@@ -103,12 +112,14 @@ function getRecruitRunArtifacts(runId) {
|
|
|
103
112
|
const normalized = normalizeRunId(runId);
|
|
104
113
|
if (!normalized) return null;
|
|
105
114
|
const runsDir = getRecruitRunsDir();
|
|
115
|
+
const outputDir = resolveBossConfiguredOutputDir("", runsDir);
|
|
106
116
|
return {
|
|
107
117
|
runs_dir: runsDir,
|
|
118
|
+
output_dir: outputDir,
|
|
108
119
|
run_state_path: path.join(runsDir, `${normalized}.json`),
|
|
109
120
|
checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
|
|
110
|
-
output_csv: path.join(
|
|
111
|
-
report_json: path.join(
|
|
121
|
+
output_csv: path.join(outputDir, `${normalized}.results.csv`),
|
|
122
|
+
report_json: path.join(outputDir, `${normalized}.report.json`)
|
|
112
123
|
};
|
|
113
124
|
}
|
|
114
125
|
|
|
@@ -586,6 +597,32 @@ function attachMethodEvidence(payload, runId) {
|
|
|
586
597
|
};
|
|
587
598
|
}
|
|
588
599
|
|
|
600
|
+
async function waitForRecruitSearchControlsOrLogin(client, {
|
|
601
|
+
timeoutMs = 90000,
|
|
602
|
+
intervalMs = 300
|
|
603
|
+
} = {}) {
|
|
604
|
+
const started = Date.now();
|
|
605
|
+
let lastControls = null;
|
|
606
|
+
while (Date.now() - started <= timeoutMs) {
|
|
607
|
+
const loginDetection = await detectBossLoginState(client).catch(() => null);
|
|
608
|
+
if (loginDetection?.requires_login) {
|
|
609
|
+
return {
|
|
610
|
+
ok: false,
|
|
611
|
+
reason: "login_required",
|
|
612
|
+
loginDetection
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
const remainingMs = Math.max(1, timeoutMs - (Date.now() - started));
|
|
616
|
+
lastControls = await waitForRecruitSearchControls(client, {
|
|
617
|
+
timeoutMs: Math.min(remainingMs, 1500),
|
|
618
|
+
intervalMs
|
|
619
|
+
});
|
|
620
|
+
if (lastControls.ok) return lastControls;
|
|
621
|
+
await sleep(intervalMs);
|
|
622
|
+
}
|
|
623
|
+
return lastControls || { ok: false, reason: "timeout" };
|
|
624
|
+
}
|
|
625
|
+
|
|
589
626
|
async function connectRecruitChromeSession({
|
|
590
627
|
host = DEFAULT_RECRUIT_HOST,
|
|
591
628
|
port = DEFAULT_RECRUIT_PORT,
|
|
@@ -593,24 +630,18 @@ async function connectRecruitChromeSession({
|
|
|
593
630
|
allowNavigate = true,
|
|
594
631
|
slowLive = false
|
|
595
632
|
} = {}) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
targetPredicate: (target) => (
|
|
609
|
-
target?.type === "page"
|
|
610
|
-
&& String(target?.url || "").includes("zhipin.com/web/chat")
|
|
611
|
-
)
|
|
612
|
-
});
|
|
613
|
-
}
|
|
633
|
+
const session = await connectToChromeTargetOrOpen({
|
|
634
|
+
host,
|
|
635
|
+
port,
|
|
636
|
+
targetUrlIncludes,
|
|
637
|
+
targetUrl: RECRUIT_TARGET_URL,
|
|
638
|
+
allowNavigate,
|
|
639
|
+
slowLive,
|
|
640
|
+
fallbackTargetPredicate: (target) => (
|
|
641
|
+
target?.type === "page"
|
|
642
|
+
&& String(target?.url || "").includes("zhipin.com")
|
|
643
|
+
)
|
|
644
|
+
});
|
|
614
645
|
|
|
615
646
|
const { client, target } = session;
|
|
616
647
|
await enableDomains(client, ["Page", "DOM", "Input", "Network", "Accessibility"]);
|
|
@@ -620,21 +651,102 @@ async function connectRecruitChromeSession({
|
|
|
620
651
|
await bringPageToFront(client);
|
|
621
652
|
|
|
622
653
|
const targetUrl = String(target?.url || "");
|
|
654
|
+
let navigation = {
|
|
655
|
+
navigated: false,
|
|
656
|
+
url: targetUrl
|
|
657
|
+
};
|
|
623
658
|
if (allowNavigate && !targetUrl.includes(targetUrlIncludes)) {
|
|
624
659
|
await client.Page.navigate({ url: RECRUIT_TARGET_URL });
|
|
625
|
-
|
|
660
|
+
const settleMs = slowLive ? 8000 : 3000;
|
|
661
|
+
const waited = await waitForMainFrameUrl(
|
|
662
|
+
client,
|
|
663
|
+
(url) => isBossLoginUrl(url) || String(url || "").includes(RECRUIT_TARGET_URL),
|
|
664
|
+
{ timeoutMs: settleMs, intervalMs: 500 }
|
|
665
|
+
);
|
|
666
|
+
navigation = {
|
|
667
|
+
navigated: true,
|
|
668
|
+
url: RECRUIT_TARGET_URL,
|
|
669
|
+
settle_ms: settleMs,
|
|
670
|
+
observed_url: waited.url || null,
|
|
671
|
+
observed_url_ok: waited.ok
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
let currentUrl = await getMainFrameUrl(client).catch(() => targetUrl);
|
|
675
|
+
if (allowNavigate && !String(currentUrl || "").includes(RECRUIT_TARGET_URL) && !isBossLoginUrl(currentUrl)) {
|
|
676
|
+
await client.Page.navigate({ url: RECRUIT_TARGET_URL });
|
|
677
|
+
const settleMs = slowLive ? 8000 : 3000;
|
|
678
|
+
const waited = await waitForMainFrameUrl(
|
|
679
|
+
client,
|
|
680
|
+
(url) => isBossLoginUrl(url) || String(url || "").includes(RECRUIT_TARGET_URL),
|
|
681
|
+
{ timeoutMs: settleMs, intervalMs: 500 }
|
|
682
|
+
);
|
|
683
|
+
navigation = {
|
|
684
|
+
navigated: true,
|
|
685
|
+
url: RECRUIT_TARGET_URL,
|
|
686
|
+
settle_ms: settleMs,
|
|
687
|
+
observed_url: waited.url || null,
|
|
688
|
+
observed_url_ok: waited.ok,
|
|
689
|
+
reason: "observed_url_mismatch"
|
|
690
|
+
};
|
|
691
|
+
currentUrl = await getMainFrameUrl(client).catch(() => waited.url || currentUrl);
|
|
692
|
+
}
|
|
693
|
+
const loginDetection = await detectBossLoginState(client, { currentUrl }).catch(() => ({
|
|
694
|
+
requires_login: isBossLoginUrl(currentUrl),
|
|
695
|
+
reason: "login_detection_failed",
|
|
696
|
+
current_url: currentUrl
|
|
697
|
+
}));
|
|
698
|
+
if (loginDetection.requires_login) {
|
|
699
|
+
await session.close?.();
|
|
700
|
+
throw createBossLoginRequiredError({
|
|
701
|
+
domain: "search",
|
|
702
|
+
currentUrl: loginDetection.current_url || currentUrl,
|
|
703
|
+
targetUrl: RECRUIT_TARGET_URL,
|
|
704
|
+
loginDetection,
|
|
705
|
+
chrome: session.chrome || null
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
if (!String(currentUrl || "").includes(RECRUIT_TARGET_URL)) {
|
|
709
|
+
await session.close?.();
|
|
710
|
+
throw new Error(`Boss search page did not navigate to ${RECRUIT_TARGET_URL}; current URL: ${currentUrl || "unknown"}`);
|
|
626
711
|
}
|
|
627
712
|
|
|
628
|
-
const controls = await
|
|
713
|
+
const controls = await waitForRecruitSearchControlsOrLogin(client, {
|
|
629
714
|
timeoutMs: slowLive ? 180000 : 90000,
|
|
630
715
|
intervalMs: 300
|
|
631
716
|
});
|
|
717
|
+
if (controls.loginDetection?.requires_login) {
|
|
718
|
+
await session.close?.();
|
|
719
|
+
throw createBossLoginRequiredError({
|
|
720
|
+
domain: "search",
|
|
721
|
+
currentUrl: controls.loginDetection.current_url || currentUrl,
|
|
722
|
+
targetUrl: RECRUIT_TARGET_URL,
|
|
723
|
+
loginDetection: controls.loginDetection,
|
|
724
|
+
chrome: session.chrome || null
|
|
725
|
+
});
|
|
726
|
+
}
|
|
632
727
|
if (!controls.ok) {
|
|
728
|
+
const latestUrl = await getMainFrameUrl(client).catch(() => currentUrl);
|
|
729
|
+
const latestLoginDetection = await detectBossLoginState(client, { currentUrl: latestUrl }).catch(() => ({
|
|
730
|
+
requires_login: isBossLoginUrl(latestUrl),
|
|
731
|
+
reason: "login_detection_failed",
|
|
732
|
+
current_url: latestUrl
|
|
733
|
+
}));
|
|
734
|
+
if (latestLoginDetection.requires_login) {
|
|
735
|
+
await session.close?.();
|
|
736
|
+
throw createBossLoginRequiredError({
|
|
737
|
+
domain: "search",
|
|
738
|
+
currentUrl: latestLoginDetection.current_url || latestUrl,
|
|
739
|
+
targetUrl: RECRUIT_TARGET_URL,
|
|
740
|
+
loginDetection: latestLoginDetection,
|
|
741
|
+
chrome: session.chrome || null
|
|
742
|
+
});
|
|
743
|
+
}
|
|
633
744
|
throw new Error("Boss recruit search page did not expose ready search controls");
|
|
634
745
|
}
|
|
635
746
|
|
|
636
747
|
return {
|
|
637
748
|
...session,
|
|
749
|
+
navigation,
|
|
638
750
|
controls
|
|
639
751
|
};
|
|
640
752
|
}
|
|
@@ -701,24 +813,38 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
|
|
|
701
813
|
const parsed = parseRecruitPipelineRequest(args);
|
|
702
814
|
const gate = evaluateRecruitPipelineGate(parsed);
|
|
703
815
|
if (gate) return gate;
|
|
816
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
817
|
+
const host = normalizeText(args.host) || DEFAULT_RECRUIT_HOST;
|
|
818
|
+
const port = parsePositiveInteger(
|
|
819
|
+
args.port,
|
|
820
|
+
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECRUIT_PORT
|
|
821
|
+
);
|
|
704
822
|
|
|
705
823
|
let session;
|
|
706
824
|
try {
|
|
707
825
|
session = await recruitConnectorImpl({
|
|
708
|
-
host
|
|
709
|
-
port
|
|
826
|
+
host,
|
|
827
|
+
port,
|
|
710
828
|
targetUrlIncludes: normalizeText(args.target_url_includes) || RECRUIT_TARGET_URL,
|
|
711
829
|
allowNavigate: args.allow_navigate !== false,
|
|
712
830
|
slowLive: args.slow_live === true
|
|
713
831
|
});
|
|
714
832
|
} catch (error) {
|
|
833
|
+
const loginRequired = error?.code === "BOSS_LOGIN_REQUIRED";
|
|
715
834
|
return {
|
|
716
835
|
status: "FAILED",
|
|
717
836
|
error: {
|
|
718
|
-
code: "BOSS_SEARCH_PAGE_NOT_READY",
|
|
837
|
+
code: loginRequired ? "BOSS_LOGIN_REQUIRED" : "BOSS_SEARCH_PAGE_NOT_READY",
|
|
719
838
|
message: error?.message || "Boss recruit search page is not ready",
|
|
839
|
+
requires_login: Boolean(error?.requires_login),
|
|
840
|
+
login_url: error?.login_url || null,
|
|
841
|
+
login_detection: error?.login_detection || null,
|
|
842
|
+
chrome: error?.chrome || null,
|
|
843
|
+
current_url: error?.current_url || null,
|
|
844
|
+
target_url: error?.target_url || RECRUIT_TARGET_URL,
|
|
720
845
|
retryable: true
|
|
721
|
-
}
|
|
846
|
+
},
|
|
847
|
+
chrome: error?.chrome || null
|
|
722
848
|
};
|
|
723
849
|
}
|
|
724
850
|
|
|
@@ -744,10 +870,11 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
|
|
|
744
870
|
workspaceRoot: normalizeText(workspaceRoot) || globalThis.process?.cwd?.() || "",
|
|
745
871
|
args: clonePlain(args, {}),
|
|
746
872
|
chrome: {
|
|
747
|
-
host
|
|
748
|
-
port
|
|
873
|
+
host,
|
|
874
|
+
port,
|
|
749
875
|
target_url: session.target?.url || RECRUIT_TARGET_URL,
|
|
750
|
-
target_id: session.target?.id || null
|
|
876
|
+
target_id: session.target?.id || null,
|
|
877
|
+
auto_launch: session.chrome || null
|
|
751
878
|
},
|
|
752
879
|
parsed
|
|
753
880
|
});
|