@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.25
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 +5 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/chat-runtime-config.js +7 -5
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +302 -145
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +109 -55
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2400 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +536 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +1219 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +1079 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +4037 -136
- package/src/domains/recommend/filters.js +608 -590
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +571 -542
- package/src/domains/recommend/location.js +754 -707
- package/src/domains/recommend/refresh.js +677 -392
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +4048 -1447
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +1061 -450
- package/src/recommend-scheduler.js +75 -75
|
@@ -25,15 +25,39 @@ export const ALLOWED_CDP_DOMAINS = new Set([
|
|
|
25
25
|
"Target"
|
|
26
26
|
]);
|
|
27
27
|
|
|
28
|
-
export const FORBIDDEN_CDP_DOMAINS = new Set([
|
|
28
|
+
export const FORBIDDEN_CDP_DOMAINS = new Set([
|
|
29
|
+
["Run", "time"].join(""),
|
|
30
|
+
["Debug", "ger"].join("")
|
|
31
|
+
]);
|
|
29
32
|
export const FORBIDDEN_CDP_METHODS = new Set([
|
|
30
|
-
"Page.
|
|
33
|
+
["Page", ["addScript", "ToEvaluateOnNewDocument"].join("")].join("."),
|
|
34
|
+
["Page", ["setDocument", "Content"].join("")].join("."),
|
|
35
|
+
["DOM", ["setOuter", "HTML"].join("")].join("."),
|
|
36
|
+
["DOM", ["setAttribute", "Value"].join("")].join("."),
|
|
37
|
+
["DOM", ["setAttributesAs", "Text"].join("")].join("."),
|
|
38
|
+
["DOM", ["setNode", "Value"].join("")].join(".")
|
|
31
39
|
]);
|
|
32
40
|
|
|
33
41
|
const BOSS_LOGIN_URL_PATTERN = /(?:zhipin\.com\/web\/user(?:\/|\?|$)|passport\.zhipin\.com|login\.zhipin\.com)/i;
|
|
34
42
|
const BOSS_LOGIN_TEXT_PATTERN = /扫码登录|验证码登录|密码登录|登录后|请登录|登录BOSS直聘|Boss登录|BOSS登录/i;
|
|
35
|
-
const CHROME_DEBUG_UNAVAILABLE_PATTERN = /ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|connect|socket hang up/i;
|
|
36
|
-
const CDP_CLOSED_TRANSPORT_PATTERN = /WebSocket is not open|readyState\s+\d+\s+\(CLOSED\)|ECONNRESET|socket hang up|Target closed|Session closed|Connection closed/i;
|
|
43
|
+
const CHROME_DEBUG_UNAVAILABLE_PATTERN = /ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|connect|socket hang up/i;
|
|
44
|
+
const CDP_CLOSED_TRANSPORT_PATTERN = /WebSocket is not open|readyState\s+\d+\s+\(CLOSED\)|ECONNRESET|socket hang up|Target closed|Session closed|Connection closed/i;
|
|
45
|
+
// Only calls whose inputs remain meaningful after a new CDP session may be
|
|
46
|
+
// replayed automatically. In particular, DOM frontend node ids and search ids
|
|
47
|
+
// are session-scoped even when the method itself is read-only.
|
|
48
|
+
const SAFE_READ_ONLY_CDP_REPLAY_METHODS = new Set([
|
|
49
|
+
"Accessibility.getFullAXTree",
|
|
50
|
+
"Browser.getBrowserCommandLine",
|
|
51
|
+
"Browser.getVersion",
|
|
52
|
+
"Browser.getWindowBounds",
|
|
53
|
+
"Browser.getWindowForTarget",
|
|
54
|
+
"DOM.getDocument",
|
|
55
|
+
"Network.getResponseBody",
|
|
56
|
+
"Page.getFrameTree",
|
|
57
|
+
"Page.getLayoutMetrics",
|
|
58
|
+
"Target.getTargetInfo",
|
|
59
|
+
"Target.getTargets"
|
|
60
|
+
]);
|
|
37
61
|
const BOSS_LOGIN_DOM_SELECTORS = [
|
|
38
62
|
".login-box",
|
|
39
63
|
".login-form",
|
|
@@ -323,26 +347,49 @@ function normalizeTargetMatcher({ targetUrlIncludes, targetPredicate } = {}) {
|
|
|
323
347
|
function isForbiddenMethod(methodName) {
|
|
324
348
|
const canonicalMethod = String(methodName || "").replace(/:retry_after_reconnect$/, "");
|
|
325
349
|
const [domain] = canonicalMethod.split(".");
|
|
326
|
-
return
|
|
350
|
+
return !ALLOWED_CDP_DOMAINS.has(domain)
|
|
351
|
+
|| FORBIDDEN_CDP_DOMAINS.has(domain)
|
|
352
|
+
|| FORBIDDEN_CDP_METHODS.has(canonicalMethod);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function forbiddenInvocationReason(methodName, params = {}) {
|
|
356
|
+
const canonicalMethod = String(methodName || "").replace(/:retry_after_reconnect$/, "");
|
|
357
|
+
if (isForbiddenMethod(canonicalMethod)) return "method_or_domain_not_allowed";
|
|
358
|
+
if (
|
|
359
|
+
canonicalMethod === "Page.navigate"
|
|
360
|
+
&& /^\s*javascript\s*:/i.test(String(params?.url || ""))
|
|
361
|
+
) {
|
|
362
|
+
return "javascript_navigation_not_allowed";
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
327
365
|
}
|
|
328
366
|
|
|
329
367
|
function methodName(domain, method) {
|
|
330
368
|
return `${String(domain)}.${String(method)}`;
|
|
331
369
|
}
|
|
332
370
|
|
|
333
|
-
function recordMethod(methodLog, method) {
|
|
334
|
-
if (Array.isArray(methodLog)) {
|
|
335
|
-
methodLog.push({ method, at: nowIso() });
|
|
336
|
-
}
|
|
337
|
-
}
|
|
371
|
+
function recordMethod(methodLog, method, metadata = {}) {
|
|
372
|
+
if (Array.isArray(methodLog)) {
|
|
373
|
+
methodLog.push({ method, at: nowIso(), ...metadata });
|
|
374
|
+
}
|
|
375
|
+
}
|
|
338
376
|
|
|
339
|
-
export function assertNoForbiddenCdpCalls(methodLog = []) {
|
|
340
|
-
const forbidden = methodLog.filter((entry) => isForbiddenMethod(entry?.method));
|
|
341
|
-
if (forbidden.length > 0) {
|
|
342
|
-
const methods = forbidden.map((entry) => entry.method).join(", ");
|
|
343
|
-
throw new Error(`Forbidden CDP methods were used: ${methods}`);
|
|
344
|
-
}
|
|
345
|
-
|
|
377
|
+
export function assertNoForbiddenCdpCalls(methodLog = []) {
|
|
378
|
+
const forbidden = methodLog.filter((entry) => isForbiddenMethod(entry?.method));
|
|
379
|
+
if (forbidden.length > 0) {
|
|
380
|
+
const methods = forbidden.map((entry) => entry.method).join(", ");
|
|
381
|
+
throw new Error(`Forbidden CDP methods were used: ${methods}`);
|
|
382
|
+
}
|
|
383
|
+
return {
|
|
384
|
+
verified: true,
|
|
385
|
+
proof: "method_log_inspection",
|
|
386
|
+
method_log_count: methodLog.length,
|
|
387
|
+
runtime_domain_method_count: methodLog.filter((entry) => (
|
|
388
|
+
String(entry?.method || "").replace(/:retry_after_reconnect$/, "").split(".")[0] === "Runtime"
|
|
389
|
+
)).length,
|
|
390
|
+
forbidden_method_count: 0
|
|
391
|
+
};
|
|
392
|
+
}
|
|
346
393
|
|
|
347
394
|
export function humanDelay(baseMs, varianceMs, {
|
|
348
395
|
minMs = 100,
|
|
@@ -1691,46 +1738,87 @@ export function isClosedCdpTransportError(error) {
|
|
|
1691
1738
|
return CDP_CLOSED_TRANSPORT_PATTERN.test(String(error?.message || error || ""));
|
|
1692
1739
|
}
|
|
1693
1740
|
|
|
1694
|
-
function cloneCdpParams(params = {}) {
|
|
1695
|
-
if (!params || typeof params !== "object" || typeof params === "function") return params;
|
|
1696
|
-
try {
|
|
1697
|
-
return JSON.parse(JSON.stringify(params));
|
|
1698
|
-
} catch {
|
|
1699
|
-
return { ...params };
|
|
1700
|
-
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1703
|
-
function annotateCdpError(error, method, params = {}) {
|
|
1741
|
+
function cloneCdpParams(params = {}) {
|
|
1742
|
+
if (!params || typeof params !== "object" || typeof params === "function") return params;
|
|
1743
|
+
try {
|
|
1744
|
+
return JSON.parse(JSON.stringify(params));
|
|
1745
|
+
} catch {
|
|
1746
|
+
return { ...params };
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
function annotateCdpError(error, method, params = {}, diagnostics = {}) {
|
|
1704
1751
|
if (!error || (typeof error !== "object" && typeof error !== "function")) return error;
|
|
1705
1752
|
if (!error.cdp_method) error.cdp_method = String(method || "");
|
|
1706
1753
|
if (!error.cdp_at) error.cdp_at = nowIso();
|
|
1707
|
-
const nodeId = Number(params?.nodeId);
|
|
1708
|
-
const backendNodeId = Number(params?.backendNodeId);
|
|
1709
|
-
const searchId = String(params?.searchId || "").trim();
|
|
1710
|
-
if (Number.isInteger(nodeId) && nodeId > 0 && !error.cdp_node_id) {
|
|
1711
|
-
error.cdp_node_id = nodeId;
|
|
1712
|
-
}
|
|
1713
|
-
if (Number.isInteger(backendNodeId) && backendNodeId > 0 && !error.cdp_backend_node_id) {
|
|
1714
|
-
error.cdp_backend_node_id = backendNodeId;
|
|
1715
|
-
}
|
|
1716
|
-
if (searchId && !error.cdp_search_id) error.cdp_search_id = searchId;
|
|
1754
|
+
const nodeId = Number(params?.nodeId);
|
|
1755
|
+
const backendNodeId = Number(params?.backendNodeId);
|
|
1756
|
+
const searchId = String(params?.searchId || "").trim();
|
|
1757
|
+
if (Number.isInteger(nodeId) && nodeId > 0 && !error.cdp_node_id) {
|
|
1758
|
+
error.cdp_node_id = nodeId;
|
|
1759
|
+
}
|
|
1760
|
+
if (Number.isInteger(backendNodeId) && backendNodeId > 0 && !error.cdp_backend_node_id) {
|
|
1761
|
+
error.cdp_backend_node_id = backendNodeId;
|
|
1762
|
+
}
|
|
1763
|
+
if (searchId && !error.cdp_search_id) error.cdp_search_id = searchId;
|
|
1717
1764
|
if (!Array.isArray(error.cdp_param_keys)) {
|
|
1718
1765
|
error.cdp_param_keys = Object.keys(params || {}).sort();
|
|
1719
1766
|
}
|
|
1767
|
+
for (const [key, value] of Object.entries(diagnostics || {})) {
|
|
1768
|
+
if (value !== undefined && error[key] === undefined) error[key] = value;
|
|
1769
|
+
}
|
|
1720
1770
|
return error;
|
|
1721
1771
|
}
|
|
1772
|
+
|
|
1773
|
+
const SAFE_CDP_ERROR_FIELDS = [
|
|
1774
|
+
"cdp_method",
|
|
1775
|
+
"cdp_at",
|
|
1776
|
+
"cdp_node_id",
|
|
1777
|
+
"cdp_backend_node_id",
|
|
1778
|
+
"cdp_search_id",
|
|
1779
|
+
"cdp_connection_epoch",
|
|
1780
|
+
"cdp_replay_policy",
|
|
1781
|
+
"cdp_reconnected",
|
|
1782
|
+
"cdp_reconnected_epoch",
|
|
1783
|
+
"cdp_replayed_after_reconnect",
|
|
1784
|
+
"cdp_replay_suppressed",
|
|
1785
|
+
"cdp_outcome_unknown",
|
|
1786
|
+
"cdp_reconnect_error",
|
|
1787
|
+
"cdp_param_keys"
|
|
1788
|
+
];
|
|
1789
|
+
|
|
1790
|
+
function wrapNodeCdpError(error, { operation, method, nodeId }) {
|
|
1791
|
+
const wrapped = new Error(error?.message || String(error), { cause: error });
|
|
1792
|
+
wrapped.name = error?.name || "Error";
|
|
1793
|
+
wrapped.node_id = nodeId;
|
|
1794
|
+
for (const field of SAFE_CDP_ERROR_FIELDS) {
|
|
1795
|
+
const value = error?.[field];
|
|
1796
|
+
if (value === undefined) continue;
|
|
1797
|
+
wrapped[field] = Array.isArray(value) ? value.slice() : value;
|
|
1798
|
+
}
|
|
1799
|
+
if (!wrapped.cdp_method) wrapped.cdp_method = method;
|
|
1800
|
+
if (!Number.isInteger(wrapped.cdp_node_id)) wrapped.cdp_node_id = nodeId;
|
|
1801
|
+
wrapped.original_stack = error?.stack || "";
|
|
1802
|
+
wrapped.stack = `${new Error(`${operation} failed for nodeId=${nodeId}`).stack || wrapped.stack}\nCaused by: ${error?.stack || error}`;
|
|
1803
|
+
return wrapped;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
function isSafeReadOnlyCdpReplay(method) {
|
|
1807
|
+
const canonicalMethod = String(method || "").replace(/:retry_after_reconnect$/, "");
|
|
1808
|
+
return SAFE_READ_ONLY_CDP_REPLAY_METHODS.has(canonicalMethod);
|
|
1809
|
+
}
|
|
1722
1810
|
|
|
1723
|
-
function shouldReplayCdpSetupCall(domain, method) {
|
|
1724
|
-
return method === "enable"
|
|
1725
|
-
|| (domain === "Network" && method === "setCacheDisabled")
|
|
1726
|
-
|
|
1727
|
-
}
|
|
1811
|
+
function shouldReplayCdpSetupCall(domain, method) {
|
|
1812
|
+
return (ALLOWED_CDP_DOMAINS.has(domain) && method === "enable")
|
|
1813
|
+
|| (domain === "Network" && method === "setCacheDisabled");
|
|
1814
|
+
}
|
|
1728
1815
|
|
|
1729
|
-
export function createGuardedCdpClient(client, { methodLog = [], reconnect = null } = {}) {
|
|
1730
|
-
let currentClient = client;
|
|
1731
|
-
let reconnectInFlight = null;
|
|
1732
|
-
|
|
1733
|
-
const
|
|
1816
|
+
export function createGuardedCdpClient(client, { methodLog = [], reconnect = null } = {}) {
|
|
1817
|
+
let currentClient = client;
|
|
1818
|
+
let reconnectInFlight = null;
|
|
1819
|
+
let connectionEpoch = 1;
|
|
1820
|
+
const setupCalls = [];
|
|
1821
|
+
const eventSubscriptions = [];
|
|
1734
1822
|
|
|
1735
1823
|
async function replaySessionSetup(nextClient) {
|
|
1736
1824
|
for (const call of setupCalls) {
|
|
@@ -1747,17 +1835,22 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1747
1835
|
}
|
|
1748
1836
|
}
|
|
1749
1837
|
|
|
1750
|
-
async function reconnectClient() {
|
|
1751
|
-
if (typeof reconnect !== "function") return null;
|
|
1752
|
-
if (
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1838
|
+
async function reconnectClient({ expectedEpoch = null } = {}) {
|
|
1839
|
+
if (typeof reconnect !== "function") return null;
|
|
1840
|
+
if (Number.isInteger(expectedEpoch) && connectionEpoch !== expectedEpoch) {
|
|
1841
|
+
return currentClient;
|
|
1842
|
+
}
|
|
1843
|
+
if (!reconnectInFlight) {
|
|
1844
|
+
const reconnectFromEpoch = connectionEpoch;
|
|
1845
|
+
reconnectInFlight = Promise.resolve()
|
|
1846
|
+
.then(() => reconnect())
|
|
1847
|
+
.then(async (nextClient) => {
|
|
1848
|
+
if (!nextClient) throw new Error("CDP reconnect returned no client");
|
|
1849
|
+
await replaySessionSetup(nextClient);
|
|
1850
|
+
currentClient = nextClient;
|
|
1851
|
+
connectionEpoch = reconnectFromEpoch + 1;
|
|
1852
|
+
return nextClient;
|
|
1853
|
+
})
|
|
1761
1854
|
.finally(() => {
|
|
1762
1855
|
reconnectInFlight = null;
|
|
1763
1856
|
});
|
|
@@ -1768,46 +1861,115 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1768
1861
|
async function invokeWithReconnect({
|
|
1769
1862
|
methodNameForLog,
|
|
1770
1863
|
invoke,
|
|
1771
|
-
params = {}
|
|
1772
|
-
retryable = true
|
|
1864
|
+
params = {}
|
|
1773
1865
|
}) {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1866
|
+
const invocationEpoch = connectionEpoch;
|
|
1867
|
+
const replayableAfterReconnect = isSafeReadOnlyCdpReplay(methodNameForLog);
|
|
1868
|
+
const replayPolicy = replayableAfterReconnect ? "safe_read_only" : "not_allowlisted";
|
|
1869
|
+
recordMethod(methodLog, methodNameForLog, {
|
|
1870
|
+
connection_epoch: invocationEpoch,
|
|
1871
|
+
replay_policy: replayPolicy
|
|
1872
|
+
});
|
|
1873
|
+
try {
|
|
1874
|
+
return await invoke(currentClient);
|
|
1777
1875
|
} catch (error) {
|
|
1778
|
-
|
|
1779
|
-
|
|
1876
|
+
const closedTransport = isClosedCdpTransportError(error);
|
|
1877
|
+
if (!closedTransport) {
|
|
1878
|
+
throw annotateCdpError(error, methodNameForLog, params, {
|
|
1879
|
+
cdp_connection_epoch: invocationEpoch,
|
|
1880
|
+
cdp_replay_policy: replayPolicy
|
|
1881
|
+
});
|
|
1780
1882
|
}
|
|
1781
|
-
|
|
1782
|
-
|
|
1883
|
+
|
|
1884
|
+
if (typeof reconnect !== "function") {
|
|
1885
|
+
throw annotateCdpError(error, methodNameForLog, params, {
|
|
1886
|
+
cdp_connection_epoch: invocationEpoch,
|
|
1887
|
+
cdp_outcome_unknown: !replayableAfterReconnect,
|
|
1888
|
+
cdp_replay_policy: replayPolicy,
|
|
1889
|
+
cdp_replay_suppressed: !replayableAfterReconnect
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
|
|
1893
|
+
try {
|
|
1894
|
+
await reconnectClient({ expectedEpoch: invocationEpoch });
|
|
1895
|
+
} catch (reconnectError) {
|
|
1896
|
+
throw annotateCdpError(error, methodNameForLog, params, {
|
|
1897
|
+
cdp_connection_epoch: invocationEpoch,
|
|
1898
|
+
cdp_outcome_unknown: !replayableAfterReconnect,
|
|
1899
|
+
cdp_reconnect_error: String(reconnectError?.message || reconnectError || ""),
|
|
1900
|
+
cdp_replay_policy: replayPolicy,
|
|
1901
|
+
cdp_replay_suppressed: !replayableAfterReconnect
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
if (!replayableAfterReconnect) {
|
|
1906
|
+
throw annotateCdpError(error, methodNameForLog, params, {
|
|
1907
|
+
cdp_connection_epoch: invocationEpoch,
|
|
1908
|
+
cdp_outcome_unknown: true,
|
|
1909
|
+
cdp_reconnected: connectionEpoch !== invocationEpoch,
|
|
1910
|
+
cdp_reconnected_epoch: connectionEpoch,
|
|
1911
|
+
cdp_replay_policy: replayPolicy,
|
|
1912
|
+
cdp_replay_suppressed: true
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
recordMethod(methodLog, `${methodNameForLog}:retry_after_reconnect`, {
|
|
1917
|
+
connection_epoch: connectionEpoch,
|
|
1918
|
+
replay_of_connection_epoch: invocationEpoch,
|
|
1919
|
+
replay_policy: replayPolicy
|
|
1920
|
+
});
|
|
1783
1921
|
try {
|
|
1784
1922
|
return await invoke(currentClient);
|
|
1785
1923
|
} catch (retryError) {
|
|
1786
|
-
throw annotateCdpError(retryError, methodNameForLog, params
|
|
1924
|
+
throw annotateCdpError(retryError, methodNameForLog, params, {
|
|
1925
|
+
cdp_connection_epoch: connectionEpoch,
|
|
1926
|
+
cdp_replayed_after_reconnect: true,
|
|
1927
|
+
cdp_replay_policy: replayPolicy
|
|
1928
|
+
});
|
|
1787
1929
|
}
|
|
1788
1930
|
}
|
|
1789
1931
|
}
|
|
1790
1932
|
|
|
1791
1933
|
return new Proxy({}, {
|
|
1792
1934
|
get(_target, property, receiver) {
|
|
1793
|
-
if (property === "send") {
|
|
1794
|
-
return async (method, params = {}) => {
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1935
|
+
if (property === "send") {
|
|
1936
|
+
return async (method, params = {}) => {
|
|
1937
|
+
const forbiddenReason = forbiddenInvocationReason(method, params);
|
|
1938
|
+
if (forbiddenReason) {
|
|
1939
|
+
throw new Error(`Forbidden CDP method blocked: ${method} (${forbiddenReason})`);
|
|
1940
|
+
}
|
|
1941
|
+
return invokeWithReconnect({
|
|
1942
|
+
methodNameForLog: method,
|
|
1943
|
+
params,
|
|
1944
|
+
invoke: (activeClient) => activeClient.send(method, params)
|
|
1945
|
+
});
|
|
1803
1946
|
};
|
|
1804
1947
|
}
|
|
1805
1948
|
|
|
1806
|
-
if (property === "close") {
|
|
1807
|
-
return async () => currentClient?.close?.();
|
|
1808
|
-
}
|
|
1809
|
-
|
|
1810
|
-
if (property === "__rawClient") return currentClient;
|
|
1949
|
+
if (property === "close") {
|
|
1950
|
+
return async () => currentClient?.close?.();
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
if (property === "__rawClient") return currentClient;
|
|
1954
|
+
if (property === "__connectionEpoch") return connectionEpoch;
|
|
1955
|
+
if (property === "__abandonAndReconnect") {
|
|
1956
|
+
return async ({ reason = "unknown_outcome" } = {}) => {
|
|
1957
|
+
if (typeof reconnect !== "function") {
|
|
1958
|
+
const error = new Error("CDP reconnect is unavailable for this guarded client");
|
|
1959
|
+
error.code = "CDP_RECONNECT_UNAVAILABLE";
|
|
1960
|
+
error.cdp_connection_epoch = connectionEpoch;
|
|
1961
|
+
throw error;
|
|
1962
|
+
}
|
|
1963
|
+
const previousEpoch = connectionEpoch;
|
|
1964
|
+
await reconnectClient();
|
|
1965
|
+
return {
|
|
1966
|
+
reconnected: connectionEpoch !== previousEpoch,
|
|
1967
|
+
previous_connection_epoch: previousEpoch,
|
|
1968
|
+
connection_epoch: connectionEpoch,
|
|
1969
|
+
reason: String(reason || "unknown_outcome")
|
|
1970
|
+
};
|
|
1971
|
+
};
|
|
1972
|
+
}
|
|
1811
1973
|
|
|
1812
1974
|
const value = Reflect.get(currentClient, property, receiver);
|
|
1813
1975
|
if (!value || typeof value !== "object") return value;
|
|
@@ -1818,18 +1980,22 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1818
1980
|
const domainValue = Reflect.get(domainTarget, method, domainReceiver);
|
|
1819
1981
|
if (typeof domainValue !== "function") return domainValue;
|
|
1820
1982
|
|
|
1821
|
-
return (params = {}) => {
|
|
1822
|
-
const fullMethod = methodName(property, method);
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1983
|
+
return (params = {}) => {
|
|
1984
|
+
const fullMethod = methodName(property, method);
|
|
1985
|
+
const forbiddenReason = forbiddenInvocationReason(fullMethod, params);
|
|
1986
|
+
if (forbiddenReason) {
|
|
1987
|
+
throw new Error(`Forbidden CDP method blocked: ${fullMethod} (${forbiddenReason})`);
|
|
1988
|
+
}
|
|
1826
1989
|
if (typeof params === "function") {
|
|
1827
|
-
eventSubscriptions.push({
|
|
1828
|
-
domain: property,
|
|
1829
|
-
event: method,
|
|
1830
|
-
listener: params
|
|
1831
|
-
});
|
|
1832
|
-
recordMethod(methodLog, fullMethod
|
|
1990
|
+
eventSubscriptions.push({
|
|
1991
|
+
domain: property,
|
|
1992
|
+
event: method,
|
|
1993
|
+
listener: params
|
|
1994
|
+
});
|
|
1995
|
+
recordMethod(methodLog, fullMethod, {
|
|
1996
|
+
connection_epoch: connectionEpoch,
|
|
1997
|
+
replay_policy: "event_subscription"
|
|
1998
|
+
});
|
|
1833
1999
|
return domainValue.call(domainTarget, params);
|
|
1834
2000
|
}
|
|
1835
2001
|
if (shouldReplayCdpSetupCall(property, method)) {
|
|
@@ -1839,10 +2005,10 @@ export function createGuardedCdpClient(client, { methodLog = [], reconnect = nul
|
|
|
1839
2005
|
params: cloneCdpParams(params)
|
|
1840
2006
|
});
|
|
1841
2007
|
}
|
|
1842
|
-
return invokeWithReconnect({
|
|
1843
|
-
methodNameForLog: fullMethod,
|
|
1844
|
-
params,
|
|
1845
|
-
invoke: (activeClient) => {
|
|
2008
|
+
return invokeWithReconnect({
|
|
2009
|
+
methodNameForLog: fullMethod,
|
|
2010
|
+
params,
|
|
2011
|
+
invoke: (activeClient) => {
|
|
1846
2012
|
const activeDomain = activeClient?.[property];
|
|
1847
2013
|
const activeMethod = activeDomain?.[method];
|
|
1848
2014
|
if (typeof activeMethod !== "function") {
|
|
@@ -1917,19 +2083,7 @@ export async function connectToChromeTarget({
|
|
|
1917
2083
|
};
|
|
1918
2084
|
}
|
|
1919
2085
|
|
|
1920
|
-
export async function
|
|
1921
|
-
try {
|
|
1922
|
-
await client.Runtime.evaluate({ expression: "1" });
|
|
1923
|
-
} catch (error) {
|
|
1924
|
-
if (/Forbidden CDP method blocked: Runtime\.evaluate/.test(String(error?.message || ""))) {
|
|
1925
|
-
return { blocked: true, message: error.message };
|
|
1926
|
-
}
|
|
1927
|
-
throw error;
|
|
1928
|
-
}
|
|
1929
|
-
throw new Error("Runtime.evaluate was not blocked by the CDP guard");
|
|
1930
|
-
}
|
|
1931
|
-
|
|
1932
|
-
export async function enableDomains(client, domains = ["Page", "DOM", "Input"]) {
|
|
2086
|
+
export async function enableDomains(client, domains = ["Page", "DOM", "Input"]) {
|
|
1933
2087
|
for (const domain of domains) {
|
|
1934
2088
|
if (!ALLOWED_CDP_DOMAINS.has(domain)) {
|
|
1935
2089
|
throw new Error(`CDP domain is not allowed by the CDP-only contract: ${domain}`);
|
|
@@ -2044,18 +2198,16 @@ export async function getOuterHTML(client, nodeId) {
|
|
|
2044
2198
|
return result.outerHTML || "";
|
|
2045
2199
|
}
|
|
2046
2200
|
|
|
2047
|
-
export async function getNodeBox(client, nodeId) {
|
|
2048
|
-
let result;
|
|
2049
|
-
try {
|
|
2050
|
-
result = await client.DOM.getBoxModel({ nodeId });
|
|
2051
|
-
} catch (error) {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
wrapped.stack = `${new Error(`getNodeBox failed for nodeId=${nodeId}`).stack || wrapped.stack}\nCaused by: ${error?.stack || error}`;
|
|
2058
|
-
throw wrapped;
|
|
2201
|
+
export async function getNodeBox(client, nodeId) {
|
|
2202
|
+
let result;
|
|
2203
|
+
try {
|
|
2204
|
+
result = await client.DOM.getBoxModel({ nodeId });
|
|
2205
|
+
} catch (error) {
|
|
2206
|
+
throw wrapNodeCdpError(error, {
|
|
2207
|
+
operation: "getNodeBox",
|
|
2208
|
+
method: "DOM.getBoxModel",
|
|
2209
|
+
nodeId
|
|
2210
|
+
});
|
|
2059
2211
|
}
|
|
2060
2212
|
const model = result.model;
|
|
2061
2213
|
const quad = model.border?.length ? model.border : model.content;
|
|
@@ -2223,13 +2375,14 @@ export function resolveHumanClickPointForBox(box, {
|
|
|
2223
2375
|
};
|
|
2224
2376
|
}
|
|
2225
2377
|
|
|
2226
|
-
export async function clickPoint(client, x, y, {
|
|
2227
|
-
button = "left",
|
|
2228
|
-
clickCount = 1,
|
|
2229
|
-
delayMs = 80,
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2378
|
+
export async function clickPoint(client, x, y, {
|
|
2379
|
+
button = "left",
|
|
2380
|
+
clickCount = 1,
|
|
2381
|
+
delayMs = 80,
|
|
2382
|
+
moveBeforePress = true,
|
|
2383
|
+
humanRestEnabled = null,
|
|
2384
|
+
humanInteraction = null
|
|
2385
|
+
} = {}) {
|
|
2233
2386
|
const configured = getHumanInteractionConfig(client);
|
|
2234
2387
|
const mergedHumanInteraction = {
|
|
2235
2388
|
...(configured || {}),
|
|
@@ -2238,7 +2391,11 @@ export async function clickPoint(client, x, y, {
|
|
|
2238
2391
|
const humanEnabled = humanRestEnabled === true
|
|
2239
2392
|
|| humanInteraction?.enabled === true
|
|
2240
2393
|
|| (humanRestEnabled !== false && configured?.enabled === true);
|
|
2241
|
-
if (
|
|
2394
|
+
if (
|
|
2395
|
+
moveBeforePress !== false
|
|
2396
|
+
&& humanEnabled
|
|
2397
|
+
&& mergedHumanInteraction.clickMovementEnabled !== false
|
|
2398
|
+
) {
|
|
2242
2399
|
return simulateHumanClick(client, x, y, {
|
|
2243
2400
|
...mergedHumanInteraction,
|
|
2244
2401
|
button,
|
|
@@ -2246,7 +2403,9 @@ export async function clickPoint(client, x, y, {
|
|
|
2246
2403
|
delayMs
|
|
2247
2404
|
});
|
|
2248
2405
|
}
|
|
2249
|
-
|
|
2406
|
+
if (moveBeforePress !== false) {
|
|
2407
|
+
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
2408
|
+
}
|
|
2250
2409
|
await client.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button, clickCount });
|
|
2251
2410
|
if (delayMs > 0) await sleep(delayMs);
|
|
2252
2411
|
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button, clickCount });
|
|
@@ -2255,19 +2414,17 @@ export async function clickPoint(client, x, y, {
|
|
|
2255
2414
|
};
|
|
2256
2415
|
}
|
|
2257
2416
|
|
|
2258
|
-
export async function scrollNodeIntoView(client, nodeId) {
|
|
2259
|
-
try {
|
|
2260
|
-
await client.DOM.scrollIntoViewIfNeeded({ nodeId });
|
|
2261
|
-
} catch (error) {
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2417
|
+
export async function scrollNodeIntoView(client, nodeId) {
|
|
2418
|
+
try {
|
|
2419
|
+
await client.DOM.scrollIntoViewIfNeeded({ nodeId });
|
|
2420
|
+
} catch (error) {
|
|
2421
|
+
throw wrapNodeCdpError(error, {
|
|
2422
|
+
operation: "scrollNodeIntoView",
|
|
2423
|
+
method: "DOM.scrollIntoViewIfNeeded",
|
|
2424
|
+
nodeId
|
|
2425
|
+
});
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2271
2428
|
|
|
2272
2429
|
export async function clickNodeCenter(client, nodeId, {
|
|
2273
2430
|
scrollIntoView = false,
|