@reconcrap/boss-recommend-mcp 2.0.43 → 2.0.44
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 +7 -1
- package/config/screening-config.example.json +10 -0
- package/package.json +1 -2
- package/src/chat-mcp.js +4 -0
- package/src/chat-runtime-config.js +85 -1
- package/src/cli.js +13 -1
- package/src/core/browser/index.js +651 -4
- package/src/core/capture/index.js +118 -14
- package/src/core/infinite-list/index.js +122 -8
- package/src/domains/chat/run-service.js +121 -5
- package/src/domains/recommend/run-service.js +200 -95
- package/src/domains/recruit/run-service.js +108 -4
- package/src/index.js +58 -0
- package/src/recommend-mcp.js +22 -18
- package/src/recruit-mcp.js +44 -0
- package/scripts/live-recommend-recovery-smoke.js +0 -305
package/src/index.js
CHANGED
|
@@ -265,6 +265,27 @@ function createTargetCountInputSchema(description) {
|
|
|
265
265
|
};
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
+
function createHumanBehaviorInputSchema(description = "可选,启用可靠性实验用的人类节奏配置;默认关闭") {
|
|
269
|
+
return {
|
|
270
|
+
type: "object",
|
|
271
|
+
properties: {
|
|
272
|
+
enabled: { type: "boolean" },
|
|
273
|
+
profile: {
|
|
274
|
+
type: "string",
|
|
275
|
+
enum: ["baseline", "paced", "paced_with_rests"]
|
|
276
|
+
},
|
|
277
|
+
clickMovement: { type: "boolean" },
|
|
278
|
+
textEntry: { type: "boolean" },
|
|
279
|
+
listScrollJitter: { type: "boolean" },
|
|
280
|
+
shortRest: { type: "boolean" },
|
|
281
|
+
batchRest: { type: "boolean" },
|
|
282
|
+
actionCooldown: { type: "boolean" }
|
|
283
|
+
},
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
description
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
268
289
|
function getRecommendedPollAfterSec(args = {}) {
|
|
269
290
|
return hasFollowUpChatRequest(args)
|
|
270
291
|
? getLongRunPollAfterSec()
|
|
@@ -609,6 +630,13 @@ function createRunInputSchema() {
|
|
|
609
630
|
target_count: createTargetCountInputSchema("boss-chat follow-up 本次处理人数上限;支持正整数、all 或 -1(扫到底)"),
|
|
610
631
|
dry_run: { type: "boolean" },
|
|
611
632
|
no_state: { type: "boolean" },
|
|
633
|
+
human_behavior: createHumanBehaviorInputSchema("可选,follow-up chat 节奏配置;默认 baseline/off"),
|
|
634
|
+
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
|
|
635
|
+
human_behavior_enabled: { type: "boolean" },
|
|
636
|
+
human_behavior_profile: {
|
|
637
|
+
type: "string",
|
|
638
|
+
enum: ["baseline", "paced", "paced_with_rests"]
|
|
639
|
+
},
|
|
612
640
|
safe_pacing: { type: "boolean" },
|
|
613
641
|
batch_rest_enabled: { type: "boolean" }
|
|
614
642
|
},
|
|
@@ -638,6 +666,25 @@ function createRunInputSchema() {
|
|
|
638
666
|
type: "boolean",
|
|
639
667
|
description: "可选,VPN/慢页面 live 测试模式,放宽等待时间"
|
|
640
668
|
},
|
|
669
|
+
human_behavior: createHumanBehaviorInputSchema("可选,recommend 可靠性实验用节奏配置;默认 baseline/off"),
|
|
670
|
+
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
|
|
671
|
+
human_behavior_enabled: {
|
|
672
|
+
type: "boolean",
|
|
673
|
+
description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
|
|
674
|
+
},
|
|
675
|
+
human_behavior_profile: {
|
|
676
|
+
type: "string",
|
|
677
|
+
enum: ["baseline", "paced", "paced_with_rests"],
|
|
678
|
+
description: "可选实验 profile:baseline/paced/paced_with_rests"
|
|
679
|
+
},
|
|
680
|
+
safe_pacing: {
|
|
681
|
+
type: "boolean",
|
|
682
|
+
description: "兼容字段;true 启用 paced,false 关闭"
|
|
683
|
+
},
|
|
684
|
+
batch_rest_enabled: {
|
|
685
|
+
type: "boolean",
|
|
686
|
+
description: "兼容字段;true 启用 paced_with_rests 的候选人短休/批次休息"
|
|
687
|
+
},
|
|
641
688
|
max_candidates: createTargetCountInputSchema("本次最多处理候选人数;默认使用确认后的 target_count,未设置时为 5"),
|
|
642
689
|
detail_limit: {
|
|
643
690
|
type: "integer",
|
|
@@ -866,6 +913,17 @@ function createBossChatStartInputSchema({ requireFullInput = false } = {}) {
|
|
|
866
913
|
},
|
|
867
914
|
dry_run: { type: "boolean" },
|
|
868
915
|
no_state: { type: "boolean" },
|
|
916
|
+
human_behavior: createHumanBehaviorInputSchema("可选,chat 可靠性实验用节奏配置;默认 baseline/off"),
|
|
917
|
+
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
|
|
918
|
+
human_behavior_enabled: {
|
|
919
|
+
type: "boolean",
|
|
920
|
+
description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
|
|
921
|
+
},
|
|
922
|
+
human_behavior_profile: {
|
|
923
|
+
type: "string",
|
|
924
|
+
enum: ["baseline", "paced", "paced_with_rests"],
|
|
925
|
+
description: "可选实验 profile:baseline/paced/paced_with_rests"
|
|
926
|
+
},
|
|
869
927
|
safe_pacing: { type: "boolean" },
|
|
870
928
|
batch_rest_enabled: { type: "boolean" }
|
|
871
929
|
},
|
package/src/recommend-mcp.js
CHANGED
|
@@ -44,10 +44,11 @@ import {
|
|
|
44
44
|
parseRecommendInstruction
|
|
45
45
|
} from "./parser.js";
|
|
46
46
|
import { getRunsDir } from "./run-state.js";
|
|
47
|
-
import {
|
|
48
|
-
resolveBossConfiguredOutputDir,
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
import {
|
|
48
|
+
resolveBossConfiguredOutputDir,
|
|
49
|
+
resolveHumanBehaviorForRun,
|
|
50
|
+
resolveBossScreeningConfig
|
|
51
|
+
} from "./chat-runtime-config.js";
|
|
51
52
|
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
52
53
|
|
|
53
54
|
const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
|
|
@@ -1216,12 +1217,13 @@ function normalizeRecommendStartInput(args = {}, parsed, configResolution = null
|
|
|
1216
1217
|
};
|
|
1217
1218
|
}
|
|
1218
1219
|
|
|
1219
|
-
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
1220
|
-
const slowLive = args.slow_live === true;
|
|
1221
|
-
const executePostAction = args.dry_run_post_action === true
|
|
1222
|
-
? false
|
|
1223
|
-
: args.execute_post_action !== false;
|
|
1224
|
-
|
|
1220
|
+
function getRunOptions(args, parsed, normalized, session, configResolution = null) {
|
|
1221
|
+
const slowLive = args.slow_live === true;
|
|
1222
|
+
const executePostAction = args.dry_run_post_action === true
|
|
1223
|
+
? false
|
|
1224
|
+
: args.execute_post_action !== false;
|
|
1225
|
+
const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
|
|
1226
|
+
return {
|
|
1225
1227
|
client: session.client,
|
|
1226
1228
|
targetUrl: RECOMMEND_TARGET_URL,
|
|
1227
1229
|
criteria: normalized.criteria,
|
|
@@ -1264,14 +1266,16 @@ function getRunOptions(args, parsed, normalized, session, configResolution = nul
|
|
|
1264
1266
|
args.llm_image_limit,
|
|
1265
1267
|
parsePositiveInteger(configResolution?.config?.llmImageLimit || configResolution?.config?.imageLimit, 8)
|
|
1266
1268
|
),
|
|
1267
|
-
llmImageDetail: normalizeText(
|
|
1268
|
-
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1269
|
-
) || "low",
|
|
1270
|
-
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1269
|
+
llmImageDetail: normalizeText(
|
|
1270
|
+
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
1271
|
+
) || "low",
|
|
1272
|
+
imageOutputDir: resolveBossConfiguredOutputDir("", getRunsDir()),
|
|
1273
|
+
humanRestEnabled: humanBehavior.restEnabled,
|
|
1274
|
+
humanBehavior,
|
|
1275
|
+
name: "mcp-recommend-pipeline-run",
|
|
1276
|
+
parsed
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1275
1279
|
|
|
1276
1280
|
function prepareRecommendPipelineStart(args = {}, { workspaceRoot = "" } = {}) {
|
|
1277
1281
|
const parsed = parseRecommendPipelineRequest(args);
|
package/src/recruit-mcp.js
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
} from "./domains/recruit/index.js";
|
|
35
35
|
import {
|
|
36
36
|
resolveBossConfiguredOutputDir,
|
|
37
|
+
resolveHumanBehaviorForRun,
|
|
37
38
|
resolveBossScreeningConfig
|
|
38
39
|
} from "./chat-runtime-config.js";
|
|
39
40
|
import { DEFAULT_MAX_IMAGE_PAGES } from "./core/cv-acquisition/index.js";
|
|
@@ -364,6 +365,27 @@ function createTargetCountSchema(description) {
|
|
|
364
365
|
};
|
|
365
366
|
}
|
|
366
367
|
|
|
368
|
+
function createHumanBehaviorInputSchema(description = "可选,search/recruit 可靠性实验用节奏配置;默认关闭") {
|
|
369
|
+
return {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: {
|
|
372
|
+
enabled: { type: "boolean" },
|
|
373
|
+
profile: {
|
|
374
|
+
type: "string",
|
|
375
|
+
enum: ["baseline", "paced", "paced_with_rests"]
|
|
376
|
+
},
|
|
377
|
+
clickMovement: { type: "boolean" },
|
|
378
|
+
textEntry: { type: "boolean" },
|
|
379
|
+
listScrollJitter: { type: "boolean" },
|
|
380
|
+
shortRest: { type: "boolean" },
|
|
381
|
+
batchRest: { type: "boolean" },
|
|
382
|
+
actionCooldown: { type: "boolean" }
|
|
383
|
+
},
|
|
384
|
+
additionalProperties: false,
|
|
385
|
+
description
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
367
389
|
export function createRecruitPipelineInputSchema() {
|
|
368
390
|
return {
|
|
369
391
|
type: "object",
|
|
@@ -431,6 +453,25 @@ export function createRecruitPipelineInputSchema() {
|
|
|
431
453
|
type: "boolean",
|
|
432
454
|
description: "VPN/慢页面模式:放宽 live DOM 等待时间"
|
|
433
455
|
},
|
|
456
|
+
human_behavior: createHumanBehaviorInputSchema("可选,search/recruit 可靠性实验用节奏配置;默认 baseline/off"),
|
|
457
|
+
humanBehavior: createHumanBehaviorInputSchema("兼容字段;优先使用 human_behavior"),
|
|
458
|
+
human_behavior_enabled: {
|
|
459
|
+
type: "boolean",
|
|
460
|
+
description: "兼容字段;true 等同启用 paced 默认配置,false 等同 baseline"
|
|
461
|
+
},
|
|
462
|
+
human_behavior_profile: {
|
|
463
|
+
type: "string",
|
|
464
|
+
enum: ["baseline", "paced", "paced_with_rests"],
|
|
465
|
+
description: "可选实验 profile:baseline/paced/paced_with_rests"
|
|
466
|
+
},
|
|
467
|
+
safe_pacing: {
|
|
468
|
+
type: "boolean",
|
|
469
|
+
description: "兼容字段;true 启用 paced,false 关闭"
|
|
470
|
+
},
|
|
471
|
+
batch_rest_enabled: {
|
|
472
|
+
type: "boolean",
|
|
473
|
+
description: "兼容字段;true 启用 paced_with_rests 的候选人短休/批次休息"
|
|
474
|
+
},
|
|
434
475
|
max_candidates: createTargetCountSchema("本次最多处理候选人数;默认使用解析出的 target_count"),
|
|
435
476
|
detail_limit: {
|
|
436
477
|
type: "integer",
|
|
@@ -843,6 +884,7 @@ function getRunOptions(args, parsed, session, configResolution = null) {
|
|
|
843
884
|
const slowLive = args.slow_live === true;
|
|
844
885
|
const targetCount = parsePositiveInteger(args.max_candidates, parsed.screenParams.target_count || 10);
|
|
845
886
|
const screeningMode = normalizeScreeningModeArg(args);
|
|
887
|
+
const humanBehavior = resolveHumanBehaviorForRun(args, configResolution?.config || {});
|
|
846
888
|
return {
|
|
847
889
|
client: session.client,
|
|
848
890
|
targetUrl: RECRUIT_TARGET_URL,
|
|
@@ -873,6 +915,8 @@ function getRunOptions(args, parsed, session, configResolution = null) {
|
|
|
873
915
|
args.llm_image_detail || configResolution?.config?.llmImageDetail || configResolution?.config?.imageDetail
|
|
874
916
|
) || "low",
|
|
875
917
|
imageOutputDir: resolveBossConfiguredOutputDir("", getRecruitRunsDir()),
|
|
918
|
+
humanRestEnabled: humanBehavior.restEnabled,
|
|
919
|
+
humanBehavior,
|
|
876
920
|
name: "mcp-recruit-pipeline-run"
|
|
877
921
|
};
|
|
878
922
|
}
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import process from "node:process";
|
|
5
|
-
import {
|
|
6
|
-
assertNoForbiddenCdpCalls,
|
|
7
|
-
assertRuntimeEvaluateBlocked,
|
|
8
|
-
bringPageToFront,
|
|
9
|
-
connectToChromeTarget,
|
|
10
|
-
enableDomains,
|
|
11
|
-
getAttributesMap,
|
|
12
|
-
getOuterHTML,
|
|
13
|
-
querySelector,
|
|
14
|
-
sleep
|
|
15
|
-
} from "../src/core/browser/index.js";
|
|
16
|
-
import {
|
|
17
|
-
htmlToText,
|
|
18
|
-
normalizeText
|
|
19
|
-
} from "../src/core/screening/index.js";
|
|
20
|
-
import {
|
|
21
|
-
findRecommendJobTrigger,
|
|
22
|
-
getRecommendRoots,
|
|
23
|
-
refreshRecommendListAtEnd,
|
|
24
|
-
RECOMMEND_TARGET_URL,
|
|
25
|
-
waitForRecommendCardNodeIds
|
|
26
|
-
} from "../src/domains/recommend/index.js";
|
|
27
|
-
|
|
28
|
-
function parseArgs(argv) {
|
|
29
|
-
const options = {
|
|
30
|
-
host: "127.0.0.1",
|
|
31
|
-
port: 9222,
|
|
32
|
-
targetUrl: RECOMMEND_TARGET_URL,
|
|
33
|
-
targetUrlIncludes: RECOMMEND_TARGET_URL,
|
|
34
|
-
jobLabel: "",
|
|
35
|
-
pageScope: "recommend",
|
|
36
|
-
fallbackPageScope: "recommend",
|
|
37
|
-
forceNavigate: true,
|
|
38
|
-
forceRecentNotView: true,
|
|
39
|
-
reloadSettleMs: 12000,
|
|
40
|
-
cardTimeoutMs: 60000,
|
|
41
|
-
saveReport: ".live-artifacts/recommend-recovery-smoke.json",
|
|
42
|
-
filterGroups: [
|
|
43
|
-
{
|
|
44
|
-
group: "degree",
|
|
45
|
-
labels: ["本科", "硕士", "博士"],
|
|
46
|
-
selectAllLabels: true
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
group: "school",
|
|
50
|
-
labels: ["985", "211", "双一流院校", "国内外名校"],
|
|
51
|
-
selectAllLabels: true
|
|
52
|
-
}
|
|
53
|
-
]
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
57
|
-
const arg = argv[index];
|
|
58
|
-
if (arg === "--host") options.host = argv[++index];
|
|
59
|
-
if (arg === "--port") options.port = Number(argv[++index]);
|
|
60
|
-
if (arg === "--target-url") options.targetUrl = argv[++index];
|
|
61
|
-
if (arg === "--target-url-includes") options.targetUrlIncludes = argv[++index];
|
|
62
|
-
if (arg === "--job") options.jobLabel = argv[++index];
|
|
63
|
-
if (arg === "--page-scope") options.pageScope = argv[++index];
|
|
64
|
-
if (arg === "--fallback-page-scope") options.fallbackPageScope = argv[++index];
|
|
65
|
-
if (arg === "--no-force-navigate") options.forceNavigate = false;
|
|
66
|
-
if (arg === "--no-recent-not-view") options.forceRecentNotView = false;
|
|
67
|
-
if (arg === "--reload-settle-ms") options.reloadSettleMs = Number(argv[++index]);
|
|
68
|
-
if (arg === "--card-timeout-ms") options.cardTimeoutMs = Number(argv[++index]);
|
|
69
|
-
if (arg === "--save-report") options.saveReport = argv[++index];
|
|
70
|
-
if (arg === "--no-save-report") options.saveReport = "";
|
|
71
|
-
if (arg === "--no-default-filter") options.filterGroups = [];
|
|
72
|
-
if (arg === "--filter") {
|
|
73
|
-
const raw = String(argv[++index] || "");
|
|
74
|
-
const [group, labelsRaw = ""] = raw.split(/[:=]/);
|
|
75
|
-
options.filterGroups.push({
|
|
76
|
-
group: group.trim(),
|
|
77
|
-
labels: labelsRaw.split(/[,,、|/]/).map((item) => item.trim()).filter(Boolean),
|
|
78
|
-
selectAllLabels: true
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return options;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function methodSummary(methodLog) {
|
|
87
|
-
const summary = {};
|
|
88
|
-
for (const entry of methodLog) {
|
|
89
|
-
summary[entry.method] = (summary[entry.method] || 0) + 1;
|
|
90
|
-
}
|
|
91
|
-
return summary;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function writeJsonFile(filePath, payload) {
|
|
95
|
-
const resolved = path.resolve(filePath);
|
|
96
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
97
|
-
fs.writeFileSync(resolved, `${JSON.stringify(payload, null, 2)}\n`, "utf8");
|
|
98
|
-
return resolved;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async function connectToRecommendSession(options) {
|
|
102
|
-
try {
|
|
103
|
-
return await connectToChromeTarget({
|
|
104
|
-
host: options.host,
|
|
105
|
-
port: options.port,
|
|
106
|
-
targetUrlIncludes: options.targetUrlIncludes
|
|
107
|
-
});
|
|
108
|
-
} catch (error) {
|
|
109
|
-
return connectToChromeTarget({
|
|
110
|
-
host: options.host,
|
|
111
|
-
port: options.port,
|
|
112
|
-
targetPredicate: (target) => (
|
|
113
|
-
target?.type === "page"
|
|
114
|
-
&& String(target?.url || "").includes("zhipin.com/web/chat")
|
|
115
|
-
)
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async function readCurrentJobLabel(client, frameDocumentNodeId) {
|
|
121
|
-
const labelNodeId = await querySelector(
|
|
122
|
-
client,
|
|
123
|
-
frameDocumentNodeId,
|
|
124
|
-
".job-selecter-wrap .ui-dropmenu-label, .ui-dropmenu-label"
|
|
125
|
-
);
|
|
126
|
-
if (!labelNodeId) return "";
|
|
127
|
-
const html = await getOuterHTML(client, labelNodeId);
|
|
128
|
-
return normalizeText(htmlToText(html));
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
async function summarizeRecommendState(client) {
|
|
132
|
-
const roots = await getRecommendRoots(client);
|
|
133
|
-
const iframeAttributes = await getAttributesMap(client, roots.iframe.nodeId).catch(() => ({}));
|
|
134
|
-
const cardNodeIds = await waitForRecommendCardNodeIds(client, roots.iframe.documentNodeId, {
|
|
135
|
-
timeoutMs: 1200,
|
|
136
|
-
intervalMs: 200
|
|
137
|
-
}).catch(() => []);
|
|
138
|
-
const trigger = await findRecommendJobTrigger(client, roots.iframe.documentNodeId).catch(() => null);
|
|
139
|
-
const currentJobLabel = trigger
|
|
140
|
-
? await readCurrentJobLabel(client, roots.iframe.documentNodeId).catch(() => "")
|
|
141
|
-
: "";
|
|
142
|
-
|
|
143
|
-
return {
|
|
144
|
-
roots,
|
|
145
|
-
summary: {
|
|
146
|
-
iframe_selector: roots.iframe.selector || "",
|
|
147
|
-
iframe_node_id: roots.iframe.nodeId,
|
|
148
|
-
iframe_document_node_id: roots.iframe.documentNodeId,
|
|
149
|
-
iframe_src: iframeAttributes.src || "",
|
|
150
|
-
current_job_label: currentJobLabel,
|
|
151
|
-
job_trigger_found: Boolean(trigger),
|
|
152
|
-
job_trigger_rect: trigger?.rect || null,
|
|
153
|
-
card_count: cardNodeIds.length
|
|
154
|
-
}
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function compactRefreshResult(refreshResult = {}) {
|
|
159
|
-
return {
|
|
160
|
-
ok: Boolean(refreshResult.ok),
|
|
161
|
-
method: refreshResult.method || "",
|
|
162
|
-
reason: refreshResult.reason || null,
|
|
163
|
-
error: refreshResult.error || null,
|
|
164
|
-
forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view),
|
|
165
|
-
target_url: refreshResult.target_url || null,
|
|
166
|
-
card_count: refreshResult.card_count || 0,
|
|
167
|
-
elapsed_ms: refreshResult.elapsed_ms || 0,
|
|
168
|
-
attempts: (refreshResult.attempts || []).map((attempt) => ({
|
|
169
|
-
ok: Boolean(attempt.ok),
|
|
170
|
-
method: attempt.method || "",
|
|
171
|
-
reason: attempt.reason || null,
|
|
172
|
-
error: attempt.error || null,
|
|
173
|
-
card_count: attempt.card_count || 0,
|
|
174
|
-
elapsed_ms: attempt.elapsed_ms || 0,
|
|
175
|
-
job_selection_attempts: attempt.job_selection_attempts || []
|
|
176
|
-
})),
|
|
177
|
-
job_selection: refreshResult.job_selection
|
|
178
|
-
? {
|
|
179
|
-
requested: refreshResult.job_selection.requested,
|
|
180
|
-
selected: Boolean(refreshResult.job_selection.selected),
|
|
181
|
-
already_current: Boolean(refreshResult.job_selection.already_current),
|
|
182
|
-
reason: refreshResult.job_selection.reason || null,
|
|
183
|
-
selected_option: refreshResult.job_selection.selected_option || null,
|
|
184
|
-
refresh_attempts: refreshResult.job_selection.refresh_attempts || []
|
|
185
|
-
}
|
|
186
|
-
: null,
|
|
187
|
-
job_selection_attempts: refreshResult.job_selection_attempts || [],
|
|
188
|
-
page_scope: refreshResult.page_scope
|
|
189
|
-
? {
|
|
190
|
-
requested_scope: refreshResult.page_scope.requested_scope,
|
|
191
|
-
effective_scope: refreshResult.page_scope.effective_scope,
|
|
192
|
-
selected: Boolean(refreshResult.page_scope.selected),
|
|
193
|
-
fallback_applied: Boolean(refreshResult.page_scope.fallback_applied),
|
|
194
|
-
reason: refreshResult.page_scope.reason || null,
|
|
195
|
-
card_count: refreshResult.page_scope.card_count || refreshResult.page_scope.after?.card_count || 0
|
|
196
|
-
}
|
|
197
|
-
: null,
|
|
198
|
-
filter: refreshResult.filter
|
|
199
|
-
? {
|
|
200
|
-
confirmed: Boolean(refreshResult.filter.confirmed),
|
|
201
|
-
selected_option: refreshResult.filter.selected_option || null,
|
|
202
|
-
selected_options: refreshResult.filter.selected_options || []
|
|
203
|
-
}
|
|
204
|
-
: null,
|
|
205
|
-
filter_reapply_attempts: refreshResult.filter_reapply_attempts || []
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async function run() {
|
|
210
|
-
const options = parseArgs(process.argv.slice(2));
|
|
211
|
-
let session;
|
|
212
|
-
const result = {
|
|
213
|
-
status: "UNKNOWN",
|
|
214
|
-
generated_at: new Date().toISOString(),
|
|
215
|
-
chrome: {
|
|
216
|
-
host: options.host,
|
|
217
|
-
port: options.port,
|
|
218
|
-
target_url_includes: options.targetUrlIncludes
|
|
219
|
-
},
|
|
220
|
-
input: {
|
|
221
|
-
target_url: options.targetUrl,
|
|
222
|
-
job_label: options.jobLabel,
|
|
223
|
-
page_scope: options.pageScope,
|
|
224
|
-
fallback_page_scope: options.fallbackPageScope,
|
|
225
|
-
force_navigate: options.forceNavigate,
|
|
226
|
-
force_recent_not_view: options.forceRecentNotView,
|
|
227
|
-
reload_settle_ms: options.reloadSettleMs,
|
|
228
|
-
card_timeout_ms: options.cardTimeoutMs,
|
|
229
|
-
filter_groups: options.filterGroups
|
|
230
|
-
},
|
|
231
|
-
before: null,
|
|
232
|
-
refresh: null,
|
|
233
|
-
after: null
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
session = await connectToRecommendSession(options);
|
|
238
|
-
const { client, methodLog, target } = session;
|
|
239
|
-
result.chrome.target = {
|
|
240
|
-
id: target.id,
|
|
241
|
-
type: target.type,
|
|
242
|
-
url: target.url,
|
|
243
|
-
title: target.title
|
|
244
|
-
};
|
|
245
|
-
result.runtime_guard_probe = await assertRuntimeEvaluateBlocked(client);
|
|
246
|
-
await enableDomains(client, ["Page", "DOM", "Input", "Accessibility"]);
|
|
247
|
-
await bringPageToFront(client);
|
|
248
|
-
|
|
249
|
-
const beforeState = await summarizeRecommendState(client);
|
|
250
|
-
result.before = beforeState.summary;
|
|
251
|
-
const jobLabel = options.jobLabel || beforeState.summary.current_job_label;
|
|
252
|
-
if (!jobLabel) {
|
|
253
|
-
throw new Error("No recommend job label was provided or detectable; pass --job");
|
|
254
|
-
}
|
|
255
|
-
result.input.job_label = jobLabel;
|
|
256
|
-
|
|
257
|
-
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
258
|
-
rootState: beforeState.roots,
|
|
259
|
-
jobLabel,
|
|
260
|
-
pageScope: options.pageScope,
|
|
261
|
-
fallbackPageScope: options.fallbackPageScope,
|
|
262
|
-
filter: { filterGroups: options.filterGroups },
|
|
263
|
-
preferEndRefreshButton: false,
|
|
264
|
-
forceNavigate: options.forceNavigate,
|
|
265
|
-
targetUrl: options.targetUrl,
|
|
266
|
-
forceRecentNotView: options.forceRecentNotView,
|
|
267
|
-
cardTimeoutMs: options.cardTimeoutMs,
|
|
268
|
-
reloadSettleMs: options.reloadSettleMs
|
|
269
|
-
});
|
|
270
|
-
result.refresh = compactRefreshResult(refreshResult);
|
|
271
|
-
|
|
272
|
-
await sleep(1000);
|
|
273
|
-
const afterState = await summarizeRecommendState(client);
|
|
274
|
-
result.after = afterState.summary;
|
|
275
|
-
result.method_summary = methodSummary(methodLog);
|
|
276
|
-
assertNoForbiddenCdpCalls(methodLog);
|
|
277
|
-
|
|
278
|
-
if (!refreshResult.ok) {
|
|
279
|
-
throw new Error(`Recommend recovery refresh failed: ${refreshResult.reason || refreshResult.error || "unknown"}`);
|
|
280
|
-
}
|
|
281
|
-
if (!refreshResult.job_selection?.selected) {
|
|
282
|
-
throw new Error("Recommend recovery smoke did not select the requested job");
|
|
283
|
-
}
|
|
284
|
-
if (!refreshResult.card_count) {
|
|
285
|
-
throw new Error("Recommend recovery smoke found no cards after refresh");
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
result.status = "PASS";
|
|
289
|
-
} catch (error) {
|
|
290
|
-
result.status = "FAIL";
|
|
291
|
-
result.error = {
|
|
292
|
-
message: error?.message || String(error),
|
|
293
|
-
stack: error?.stack || ""
|
|
294
|
-
};
|
|
295
|
-
process.exitCode = 1;
|
|
296
|
-
} finally {
|
|
297
|
-
if (session) await session.close().catch(() => null);
|
|
298
|
-
if (options.saveReport) {
|
|
299
|
-
result.report_path = writeJsonFile(options.saveReport, result);
|
|
300
|
-
}
|
|
301
|
-
console.log(JSON.stringify(result, null, 2));
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
await run();
|