@reconcrap/boss-recommend-mcp 1.2.5 → 1.2.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 +1 -1
- package/src/adapters.js +293 -38
- package/src/pipeline.js +34 -2
- package/src/test-pipeline.js +67 -0
package/package.json
CHANGED
package/src/adapters.js
CHANGED
|
@@ -1343,15 +1343,21 @@ function findChromeOnboardingUrl(tabs) {
|
|
|
1343
1343
|
return null;
|
|
1344
1344
|
}
|
|
1345
1345
|
|
|
1346
|
-
function isBossLoginTab(tab) {
|
|
1347
|
-
const url = String(tab?.url || "");
|
|
1348
|
-
const title = String(tab?.title || "");
|
|
1349
|
-
return (
|
|
1350
|
-
url === bossLoginUrl
|
|
1351
|
-
|| bossLoginUrlPattern.test(url)
|
|
1352
|
-
|| bossLoginTitlePattern.test(title)
|
|
1353
|
-
);
|
|
1354
|
-
}
|
|
1346
|
+
function isBossLoginTab(tab) {
|
|
1347
|
+
const url = String(tab?.url || "");
|
|
1348
|
+
const title = String(tab?.title || "");
|
|
1349
|
+
return (
|
|
1350
|
+
url === bossLoginUrl
|
|
1351
|
+
|| bossLoginUrlPattern.test(url)
|
|
1352
|
+
|| bossLoginTitlePattern.test(title)
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
function findRecommendTab(tabs = []) {
|
|
1357
|
+
return tabs.find(
|
|
1358
|
+
(tab) => typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend")
|
|
1359
|
+
) || null;
|
|
1360
|
+
}
|
|
1355
1361
|
|
|
1356
1362
|
export async function inspectBossRecommendPageState(port, options = {}) {
|
|
1357
1363
|
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
|
|
@@ -1365,9 +1371,7 @@ export async function inspectBossRecommendPageState(port, options = {}) {
|
|
|
1365
1371
|
try {
|
|
1366
1372
|
const tabs = await listChromeTabs(port);
|
|
1367
1373
|
lastTabs = tabs;
|
|
1368
|
-
const exactTab = tabs
|
|
1369
|
-
(tab) => typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend")
|
|
1370
|
-
);
|
|
1374
|
+
const exactTab = findRecommendTab(tabs);
|
|
1371
1375
|
if (exactTab) {
|
|
1372
1376
|
if (isBossLoginTab(exactTab)) {
|
|
1373
1377
|
return buildBossPageState({
|
|
@@ -1498,36 +1502,287 @@ async function openBossRecommendTab(port) {
|
|
|
1498
1502
|
};
|
|
1499
1503
|
}
|
|
1500
1504
|
|
|
1501
|
-
async function verifyRecommendPageStable(port, options = {}) {
|
|
1502
|
-
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1500;
|
|
1503
|
-
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 2500;
|
|
1504
|
-
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1505
|
+
async function verifyRecommendPageStable(port, options = {}) {
|
|
1506
|
+
const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1500;
|
|
1507
|
+
const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 2500;
|
|
1508
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1505
1509
|
|
|
1506
1510
|
await sleep(settleMs);
|
|
1507
|
-
const recheck = await inspectBossRecommendPageState(port, {
|
|
1508
|
-
timeoutMs: recheckTimeoutMs,
|
|
1509
|
-
pollMs
|
|
1510
|
-
});
|
|
1511
|
-
if (recheck.state === "RECOMMEND_READY") {
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1511
|
+
const recheck = await inspectBossRecommendPageState(port, {
|
|
1512
|
+
timeoutMs: recheckTimeoutMs,
|
|
1513
|
+
pollMs
|
|
1514
|
+
});
|
|
1515
|
+
if (recheck.state === "RECOMMEND_READY") {
|
|
1516
|
+
const iframeState = await waitForRecommendIframeReady(port, {
|
|
1517
|
+
timeoutMs: recheckTimeoutMs,
|
|
1518
|
+
pollMs
|
|
1519
|
+
});
|
|
1520
|
+
if (iframeState.state === "RECOMMEND_IFRAME_READY") {
|
|
1521
|
+
return buildBossPageState({
|
|
1522
|
+
...recheck,
|
|
1523
|
+
ok: true,
|
|
1524
|
+
state: "RECOMMEND_READY",
|
|
1525
|
+
frame_url: iframeState.frame_url || null,
|
|
1526
|
+
iframe_state: iframeState
|
|
1527
|
+
});
|
|
1528
|
+
}
|
|
1529
|
+
if (iframeState.state === "LOGIN_REQUIRED") {
|
|
1530
|
+
return buildBossPageState({
|
|
1531
|
+
...iframeState,
|
|
1532
|
+
state: "LOGIN_REQUIRED_AFTER_REDIRECT",
|
|
1533
|
+
message: "Boss 页面曾进入 recommend 但 iframe 不可用且出现登录态特征,通常表示登录态失效。"
|
|
1534
|
+
});
|
|
1535
|
+
}
|
|
1536
|
+
if (iframeState.state === "NO_RECOMMEND_IFRAME") {
|
|
1537
|
+
return buildBossPageState({
|
|
1538
|
+
...recheck,
|
|
1539
|
+
ok: false,
|
|
1540
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1541
|
+
current_url: iframeState.current_url || recheck.current_url || null,
|
|
1542
|
+
title: iframeState.title || recheck.title || null,
|
|
1543
|
+
frame_url: iframeState.frame_url || null,
|
|
1544
|
+
iframe_state: iframeState,
|
|
1545
|
+
message: "Boss recommend 页面已打开,但 recommend iframe 尚未就绪。"
|
|
1546
|
+
});
|
|
1547
|
+
}
|
|
1548
|
+
return iframeState;
|
|
1549
|
+
}
|
|
1550
|
+
if (recheck.state === "LOGIN_REQUIRED") {
|
|
1551
|
+
return buildBossPageState({
|
|
1552
|
+
...recheck,
|
|
1553
|
+
state: "LOGIN_REQUIRED_AFTER_REDIRECT",
|
|
1518
1554
|
message: "Boss 页面曾进入 recommend 但随后跳转到其他页面,通常表示登录态失效。"
|
|
1519
1555
|
});
|
|
1520
|
-
}
|
|
1521
|
-
return recheck;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
function
|
|
1525
|
-
return
|
|
1526
|
-
(
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1556
|
+
}
|
|
1557
|
+
return recheck;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function buildRecommendIframeProbeExpression() {
|
|
1561
|
+
return `(() => {
|
|
1562
|
+
const currentUrl = (() => {
|
|
1563
|
+
try { return String(window.location.href || ""); } catch { return ""; }
|
|
1564
|
+
})();
|
|
1565
|
+
const title = (() => {
|
|
1566
|
+
try { return String(document.title || ""); } catch { return ""; }
|
|
1567
|
+
})();
|
|
1568
|
+
const isLogin = ${bossLoginUrlPattern}.test(currentUrl)
|
|
1569
|
+
|| ${bossLoginTitlePattern}.test(title);
|
|
1570
|
+
if (isLogin) {
|
|
1571
|
+
return {
|
|
1572
|
+
ok: false,
|
|
1573
|
+
error: "LOGIN_REQUIRED",
|
|
1574
|
+
current_url: currentUrl || ${JSON.stringify(bossLoginUrl)},
|
|
1575
|
+
title
|
|
1576
|
+
};
|
|
1577
|
+
}
|
|
1578
|
+
const frame = document.querySelector('iframe[name="recommendFrame"]')
|
|
1579
|
+
|| document.querySelector('iframe[src*="/web/frame/recommend/"]')
|
|
1580
|
+
|| document.querySelector('iframe');
|
|
1581
|
+
const frameUrl = (() => {
|
|
1582
|
+
try {
|
|
1583
|
+
return String(frame?.contentWindow?.location?.href || frame?.src || "");
|
|
1584
|
+
} catch {
|
|
1585
|
+
return String(frame?.src || "");
|
|
1586
|
+
}
|
|
1587
|
+
})();
|
|
1588
|
+
const iframeCount = document.querySelectorAll("iframe").length;
|
|
1589
|
+
if (!frame || !frame.contentDocument) {
|
|
1590
|
+
return {
|
|
1591
|
+
ok: false,
|
|
1592
|
+
error: "NO_RECOMMEND_IFRAME",
|
|
1593
|
+
current_url: currentUrl,
|
|
1594
|
+
title,
|
|
1595
|
+
frame_url: frameUrl,
|
|
1596
|
+
frame_present: Boolean(frame),
|
|
1597
|
+
iframe_count: iframeCount
|
|
1598
|
+
};
|
|
1599
|
+
}
|
|
1600
|
+
return {
|
|
1601
|
+
ok: true,
|
|
1602
|
+
current_url: currentUrl,
|
|
1603
|
+
title,
|
|
1604
|
+
frame_url: frameUrl,
|
|
1605
|
+
frame_present: true,
|
|
1606
|
+
iframe_count: iframeCount
|
|
1607
|
+
};
|
|
1608
|
+
})()`;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
async function probeRecommendIframeState(port, options = {}) {
|
|
1612
|
+
const expectedUrl = options.expectedUrl || bossRecommendUrl;
|
|
1613
|
+
let client = null;
|
|
1614
|
+
try {
|
|
1615
|
+
const tabs = await listChromeTabs(port);
|
|
1616
|
+
const exactTab = findRecommendTab(tabs);
|
|
1617
|
+
if (!exactTab) {
|
|
1618
|
+
const loginTab = tabs.find((tab) => isBossLoginTab(tab));
|
|
1619
|
+
if (loginTab) {
|
|
1620
|
+
return buildBossPageState({
|
|
1621
|
+
ok: false,
|
|
1622
|
+
state: "LOGIN_REQUIRED",
|
|
1623
|
+
path: loginTab.url || bossLoginUrl,
|
|
1624
|
+
current_url: loginTab.url || bossLoginUrl,
|
|
1625
|
+
title: loginTab.title || null,
|
|
1626
|
+
requires_login: true,
|
|
1627
|
+
expected_url: expectedUrl,
|
|
1628
|
+
login_url: bossLoginUrl,
|
|
1629
|
+
message: "Boss 页面未登录,需先完成登录后再进入 recommend 页面。"
|
|
1630
|
+
});
|
|
1631
|
+
}
|
|
1632
|
+
const bossTab = tabs.find(
|
|
1633
|
+
(tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")
|
|
1634
|
+
);
|
|
1635
|
+
if (bossTab) {
|
|
1636
|
+
return buildBossPageState({
|
|
1637
|
+
ok: false,
|
|
1638
|
+
state: "BOSS_NOT_ON_RECOMMEND",
|
|
1639
|
+
path: bossTab.url,
|
|
1640
|
+
current_url: bossTab.url,
|
|
1641
|
+
title: bossTab.title || null,
|
|
1642
|
+
requires_login: false,
|
|
1643
|
+
expected_url: expectedUrl,
|
|
1644
|
+
message: "Boss 已登录但当前不在 recommend 页面。"
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
return buildBossPageState({
|
|
1648
|
+
ok: false,
|
|
1649
|
+
state: "BOSS_TAB_NOT_FOUND",
|
|
1650
|
+
path: expectedUrl,
|
|
1651
|
+
current_url: null,
|
|
1652
|
+
title: null,
|
|
1653
|
+
requires_login: false,
|
|
1654
|
+
expected_url: expectedUrl,
|
|
1655
|
+
message: "未检测到 Boss 推荐页标签页。",
|
|
1656
|
+
sample_urls: extractSampleUrls(tabs)
|
|
1657
|
+
});
|
|
1658
|
+
}
|
|
1659
|
+
if (isBossLoginTab(exactTab)) {
|
|
1660
|
+
return buildBossPageState({
|
|
1661
|
+
ok: false,
|
|
1662
|
+
state: "LOGIN_REQUIRED",
|
|
1663
|
+
path: exactTab.url || bossLoginUrl,
|
|
1664
|
+
current_url: exactTab.url || bossLoginUrl,
|
|
1665
|
+
title: exactTab.title || null,
|
|
1666
|
+
requires_login: true,
|
|
1667
|
+
expected_url: expectedUrl,
|
|
1668
|
+
login_url: bossLoginUrl,
|
|
1669
|
+
message: "当前标签页虽在 recommend 路径,但检测到登录态页面特征,请先完成 Boss 登录。"
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
client = await CDP({ port, target: exactTab });
|
|
1674
|
+
const { Runtime, Page } = client;
|
|
1675
|
+
if (Runtime && typeof Runtime.enable === "function") {
|
|
1676
|
+
await Runtime.enable();
|
|
1677
|
+
}
|
|
1678
|
+
if (Page && typeof Page.enable === "function") {
|
|
1679
|
+
await Page.enable();
|
|
1680
|
+
}
|
|
1681
|
+
const frameProbe = await evaluateCdpExpression(client, buildRecommendIframeProbeExpression());
|
|
1682
|
+
if (frameProbe?.ok) {
|
|
1683
|
+
return buildBossPageState({
|
|
1684
|
+
ok: true,
|
|
1685
|
+
state: "RECOMMEND_IFRAME_READY",
|
|
1686
|
+
path: frameProbe.current_url || exactTab.url || expectedUrl,
|
|
1687
|
+
current_url: frameProbe.current_url || exactTab.url || null,
|
|
1688
|
+
title: frameProbe.title || exactTab.title || null,
|
|
1689
|
+
expected_url: expectedUrl,
|
|
1690
|
+
frame_url: frameProbe.frame_url || null,
|
|
1691
|
+
frame_present: frameProbe.frame_present === true,
|
|
1692
|
+
iframe_count: Number.isFinite(Number(frameProbe.iframe_count))
|
|
1693
|
+
? Number(frameProbe.iframe_count)
|
|
1694
|
+
: null,
|
|
1695
|
+
message: "Boss 推荐页 iframe 已就绪。"
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
if (frameProbe?.error === "LOGIN_REQUIRED") {
|
|
1699
|
+
return buildBossPageState({
|
|
1700
|
+
ok: false,
|
|
1701
|
+
state: "LOGIN_REQUIRED",
|
|
1702
|
+
path: frameProbe.current_url || exactTab.url || bossLoginUrl,
|
|
1703
|
+
current_url: frameProbe.current_url || exactTab.url || bossLoginUrl,
|
|
1704
|
+
title: frameProbe.title || exactTab.title || null,
|
|
1705
|
+
requires_login: true,
|
|
1706
|
+
expected_url: expectedUrl,
|
|
1707
|
+
login_url: bossLoginUrl,
|
|
1708
|
+
message: "页面检测到登录态特征,请先完成 Boss 登录。"
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
return buildBossPageState({
|
|
1712
|
+
ok: false,
|
|
1713
|
+
state: frameProbe?.error || "NO_RECOMMEND_IFRAME",
|
|
1714
|
+
path: frameProbe?.current_url || exactTab.url || expectedUrl,
|
|
1715
|
+
current_url: frameProbe?.current_url || exactTab.url || null,
|
|
1716
|
+
title: frameProbe?.title || exactTab.title || null,
|
|
1717
|
+
expected_url: expectedUrl,
|
|
1718
|
+
frame_url: frameProbe?.frame_url || null,
|
|
1719
|
+
frame_present: frameProbe?.frame_present === true,
|
|
1720
|
+
iframe_count: Number.isFinite(Number(frameProbe?.iframe_count))
|
|
1721
|
+
? Number(frameProbe.iframe_count)
|
|
1722
|
+
: null,
|
|
1723
|
+
message: "recommend iframe 暂不可用。"
|
|
1724
|
+
});
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
return buildBossPageState({
|
|
1727
|
+
ok: false,
|
|
1728
|
+
state: "DEBUG_PORT_UNREACHABLE",
|
|
1729
|
+
path: `http://127.0.0.1:${port}`,
|
|
1730
|
+
current_url: null,
|
|
1731
|
+
title: null,
|
|
1732
|
+
requires_login: false,
|
|
1733
|
+
expected_url: expectedUrl,
|
|
1734
|
+
message: `无法连接到 Chrome DevTools 端口 ${port}。请确认 Chrome 已以远程调试模式启动。`,
|
|
1735
|
+
error: error?.message || String(error)
|
|
1736
|
+
});
|
|
1737
|
+
} finally {
|
|
1738
|
+
if (client) {
|
|
1739
|
+
try {
|
|
1740
|
+
await client.close();
|
|
1741
|
+
} catch {}
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
async function waitForRecommendIframeReady(port, options = {}) {
|
|
1747
|
+
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 3000;
|
|
1748
|
+
const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
|
|
1749
|
+
const expectedUrl = options.expectedUrl || bossRecommendUrl;
|
|
1750
|
+
const deadline = Date.now() + timeoutMs;
|
|
1751
|
+
let lastState = null;
|
|
1752
|
+
|
|
1753
|
+
while (Date.now() < deadline) {
|
|
1754
|
+
lastState = await probeRecommendIframeState(port, { expectedUrl });
|
|
1755
|
+
if (lastState?.state === "RECOMMEND_IFRAME_READY") {
|
|
1756
|
+
return lastState;
|
|
1757
|
+
}
|
|
1758
|
+
if (
|
|
1759
|
+
lastState?.state === "LOGIN_REQUIRED"
|
|
1760
|
+
|| lastState?.state === "LOGIN_REQUIRED_AFTER_REDIRECT"
|
|
1761
|
+
|| lastState?.state === "DEBUG_PORT_UNREACHABLE"
|
|
1762
|
+
|| lastState?.state === "BOSS_TAB_NOT_FOUND"
|
|
1763
|
+
|| lastState?.state === "BOSS_NOT_ON_RECOMMEND"
|
|
1764
|
+
) {
|
|
1765
|
+
return lastState;
|
|
1766
|
+
}
|
|
1767
|
+
await sleep(pollMs);
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
return lastState || buildBossPageState({
|
|
1771
|
+
ok: false,
|
|
1772
|
+
state: "NO_RECOMMEND_IFRAME",
|
|
1773
|
+
path: expectedUrl,
|
|
1774
|
+
current_url: null,
|
|
1775
|
+
title: null,
|
|
1776
|
+
expected_url: expectedUrl,
|
|
1777
|
+
message: "recommend iframe 尚未就绪。"
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
function pickBossRecommendReloadTarget(tabs = []) {
|
|
1782
|
+
return findRecommendTab(tabs) || tabs.find(
|
|
1783
|
+
(tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")
|
|
1784
|
+
) || null;
|
|
1785
|
+
}
|
|
1531
1786
|
|
|
1532
1787
|
async function evaluateCdpExpression(client, expression) {
|
|
1533
1788
|
const result = await client.Runtime.evaluate({
|
package/src/pipeline.js
CHANGED
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
|
|
17
17
|
const FORCED_RECENT_NOT_VIEW_ON_SCREEN_RECOVERY = "近14天没有";
|
|
18
18
|
const MAX_SCREEN_AUTO_RECOVERY_ATTEMPTS = 5;
|
|
19
|
+
const MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS = 1;
|
|
20
|
+
const SEARCH_NO_IFRAME_RETRY_DELAY_MS = 1200;
|
|
19
21
|
const PAGE_SCOPE_TO_TAB_STATUS = {
|
|
20
22
|
recommend: "0",
|
|
21
23
|
latest: "1",
|
|
@@ -40,6 +42,10 @@ function normalizeText(value) {
|
|
|
40
42
|
return String(value || "").replace(/\s+/g, " ").trim();
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
function sleep(ms) {
|
|
46
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
47
|
+
}
|
|
48
|
+
|
|
43
49
|
function normalizePageScope(value) {
|
|
44
50
|
const normalized = normalizeText(value).toLowerCase();
|
|
45
51
|
if (!normalized) return null;
|
|
@@ -886,6 +892,7 @@ export async function runRecommendPipeline(
|
|
|
886
892
|
let shouldRunSearch = !skipSearchOnResume;
|
|
887
893
|
let screenAutoRecoveryCount = 0;
|
|
888
894
|
let lastAutoRecovery = null;
|
|
895
|
+
let searchNoIframeRetryCount = 0;
|
|
889
896
|
let activeTabStatus = null;
|
|
890
897
|
let currentResumeConfig = {
|
|
891
898
|
checkpoint_path: resume?.checkpoint_path || null,
|
|
@@ -971,11 +978,14 @@ export async function runRecommendPipeline(
|
|
|
971
978
|
if (!searchResult.ok) {
|
|
972
979
|
const searchErrorCode = String(searchResult.error?.code || "");
|
|
973
980
|
const searchErrorMessage = String(searchResult.error?.message || "");
|
|
981
|
+
const isNoIframeSearchFailure = (
|
|
982
|
+
searchErrorCode === "NO_RECOMMEND_IFRAME"
|
|
983
|
+
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
984
|
+
);
|
|
974
985
|
const loginRelatedSearchFailure = (
|
|
975
986
|
searchErrorCode === "LOGIN_REQUIRED"
|
|
976
|
-
||
|
|
987
|
+
|| isNoIframeSearchFailure
|
|
977
988
|
|| searchErrorMessage.includes("LOGIN_REQUIRED")
|
|
978
|
-
|| searchErrorMessage.includes("NO_RECOMMEND_IFRAME")
|
|
979
989
|
);
|
|
980
990
|
if (loginRelatedSearchFailure) {
|
|
981
991
|
const recheck = await ensureRecommendPageReady(workspaceRoot, {
|
|
@@ -1006,6 +1016,28 @@ export async function runRecommendPipeline(
|
|
|
1006
1016
|
}
|
|
1007
1017
|
);
|
|
1008
1018
|
}
|
|
1019
|
+
if (
|
|
1020
|
+
isNoIframeSearchFailure
|
|
1021
|
+
&& recheck.state === "RECOMMEND_READY"
|
|
1022
|
+
&& searchNoIframeRetryCount < MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS
|
|
1023
|
+
) {
|
|
1024
|
+
searchNoIframeRetryCount += 1;
|
|
1025
|
+
const retryDelayMs = SEARCH_NO_IFRAME_RETRY_DELAY_MS;
|
|
1026
|
+
const retryDiagnostics = {
|
|
1027
|
+
trigger: "NO_RECOMMEND_IFRAME",
|
|
1028
|
+
attempt: searchNoIframeRetryCount,
|
|
1029
|
+
max_attempts: MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS,
|
|
1030
|
+
delay_ms: retryDelayMs,
|
|
1031
|
+
page_state: recheck.page_state || null
|
|
1032
|
+
};
|
|
1033
|
+
runtimeHooks.setStage(
|
|
1034
|
+
"search_recovery",
|
|
1035
|
+
`检测到 recommend iframe 暂未就绪,等待 ${Math.round(retryDelayMs / 1000)} 秒后重试 search(第 ${searchNoIframeRetryCount}/${MAX_SEARCH_NO_IFRAME_RETRY_ATTEMPTS} 次)。`
|
|
1036
|
+
);
|
|
1037
|
+
runtimeHooks.heartbeat("search_recovery", retryDiagnostics);
|
|
1038
|
+
await sleep(retryDelayMs);
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
1009
1041
|
}
|
|
1010
1042
|
return buildFailedResponse(
|
|
1011
1043
|
searchResult.error?.code || "RECOMMEND_SEARCH_FAILED",
|
package/src/test-pipeline.js
CHANGED
|
@@ -1505,6 +1505,72 @@ async function testSearchNoIframeWithLoginShouldReturnLoginRequired() {
|
|
|
1505
1505
|
assert.equal(result.guidance.agent_prompt.includes("https://www.zhipin.com/web/user/?ka=bticket"), true);
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
|
+
async function testSearchNoIframeShouldRetryOnceWhenPageRecheckReady() {
|
|
1509
|
+
let searchCallCount = 0;
|
|
1510
|
+
let recheckCount = 0;
|
|
1511
|
+
const result = await runRecommendPipeline(
|
|
1512
|
+
{
|
|
1513
|
+
workspaceRoot: process.cwd(),
|
|
1514
|
+
instruction: "test",
|
|
1515
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
1516
|
+
overrides: {}
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
parseRecommendInstruction: () => createParsed(),
|
|
1520
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
1521
|
+
ensureBossRecommendPageReady: async () => {
|
|
1522
|
+
recheckCount += 1;
|
|
1523
|
+
return {
|
|
1524
|
+
ok: true,
|
|
1525
|
+
debug_port: 9222,
|
|
1526
|
+
state: "RECOMMEND_READY",
|
|
1527
|
+
page_state: {
|
|
1528
|
+
state: "RECOMMEND_READY",
|
|
1529
|
+
expected_url: "https://www.zhipin.com/web/chat/recommend",
|
|
1530
|
+
current_url: "https://www.zhipin.com/web/chat/recommend"
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
},
|
|
1534
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
1535
|
+
runRecommendSearchCli: async () => {
|
|
1536
|
+
searchCallCount += 1;
|
|
1537
|
+
if (searchCallCount === 1) {
|
|
1538
|
+
return {
|
|
1539
|
+
ok: false,
|
|
1540
|
+
stdout: "",
|
|
1541
|
+
stderr: "NO_RECOMMEND_IFRAME",
|
|
1542
|
+
structured: null,
|
|
1543
|
+
error: {
|
|
1544
|
+
code: "NO_RECOMMEND_IFRAME",
|
|
1545
|
+
message: "NO_RECOMMEND_IFRAME"
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
ok: true,
|
|
1551
|
+
summary: {
|
|
1552
|
+
candidate_count: 5,
|
|
1553
|
+
applied_filters: {}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
},
|
|
1557
|
+
runRecommendScreenCli: async () => ({
|
|
1558
|
+
ok: true,
|
|
1559
|
+
summary: {
|
|
1560
|
+
processed_count: 2,
|
|
1561
|
+
passed_count: 1,
|
|
1562
|
+
skipped_count: 1,
|
|
1563
|
+
output_csv: "C:/temp/retry.csv"
|
|
1564
|
+
}
|
|
1565
|
+
})
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
|
|
1569
|
+
assert.equal(result.status, "COMPLETED");
|
|
1570
|
+
assert.equal(searchCallCount, 2);
|
|
1571
|
+
assert.equal(recheckCount >= 2, true);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1508
1574
|
async function testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin() {
|
|
1509
1575
|
const result = await runRecommendPipeline(
|
|
1510
1576
|
{
|
|
@@ -1940,6 +2006,7 @@ async function main() {
|
|
|
1940
2006
|
await testCompletedPipeline();
|
|
1941
2007
|
await testSearchFailure();
|
|
1942
2008
|
await testSearchNoIframeWithLoginShouldReturnLoginRequired();
|
|
2009
|
+
await testSearchNoIframeShouldRetryOnceWhenPageRecheckReady();
|
|
1943
2010
|
await testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin();
|
|
1944
2011
|
await testLoginRequiredShouldReturnGuidance();
|
|
1945
2012
|
await testDebugPortUnreachableShouldReturnConnectionCode();
|