@reconcrap/boss-recommend-mcp 2.0.53 → 2.0.55
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/bin/boss-recommend-mcp.js +0 -0
- package/config/screening-config.example.json +1 -1
- package/package.json +120 -120
- package/src/cli.js +3121 -3121
- package/src/core/run/index.js +310 -310
- package/src/domains/chat/constants.js +229 -220
- package/src/domains/chat/detail.js +1686 -1653
- package/src/domains/chat/run-service.js +2039 -1979
- package/src/domains/common/account-rights-panel.js +314 -0
- package/src/domains/recommend/detail.js +546 -531
- package/src/domains/recommend/run-service.js +1245 -1180
- package/src/domains/recruit/detail.js +15 -0
- package/src/domains/recruit/run-service.js +78 -1
- package/src/recommend-mcp.js +1701 -1701
- package/src/run-state.js +358 -358
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
4
|
-
import {
|
|
5
|
-
addTiming,
|
|
6
|
-
imageEvidenceFilePath,
|
|
7
|
-
measureTiming
|
|
8
|
-
} from "../../core/run/timing.js";
|
|
9
|
-
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
10
|
-
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
4
|
+
import {
|
|
5
|
+
addTiming,
|
|
6
|
+
imageEvidenceFilePath,
|
|
7
|
+
measureTiming
|
|
8
|
+
} from "../../core/run/timing.js";
|
|
9
|
+
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
10
|
+
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
11
11
|
import {
|
|
12
12
|
configureHumanInteraction,
|
|
13
13
|
createHumanRestController,
|
|
@@ -15,37 +15,38 @@ import {
|
|
|
15
15
|
normalizeHumanBehaviorOptions,
|
|
16
16
|
sleep
|
|
17
17
|
} from "../../core/browser/index.js";
|
|
18
|
-
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
19
|
-
import {
|
|
20
|
-
compactCvAcquisitionState,
|
|
21
|
-
countParsedNetworkProfiles,
|
|
22
|
-
createCvAcquisitionState,
|
|
23
|
-
DEFAULT_MAX_IMAGE_PAGES,
|
|
24
|
-
getCvNetworkWaitPlan,
|
|
25
|
-
recordCvImageFallback,
|
|
26
|
-
recordCvNetworkHit,
|
|
27
|
-
recordCvNetworkMiss,
|
|
28
|
-
summarizeImageEvidence,
|
|
29
|
-
waitForCvNetworkEvents
|
|
30
|
-
} from "../../core/cv-acquisition/index.js";
|
|
31
|
-
import {
|
|
32
|
-
compactInfiniteListState,
|
|
33
|
-
createInfiniteListState,
|
|
34
|
-
detectInfiniteListBottomMarker,
|
|
35
|
-
getNextInfiniteListCandidate,
|
|
36
|
-
markInfiniteListCandidateProcessed,
|
|
37
|
-
resetInfiniteListForRefreshRound,
|
|
38
|
-
resolveInfiniteListFallbackPoint
|
|
39
|
-
} from "../../core/infinite-list/index.js";
|
|
40
|
-
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
41
|
-
import {
|
|
42
|
-
callScreeningLlm,
|
|
43
|
-
compactScreeningLlmResult,
|
|
44
|
-
createFailedLlmScreeningResult,
|
|
45
|
-
llmResultToScreening,
|
|
46
|
-
screenCandidate
|
|
47
|
-
} from "../../core/screening/index.js";
|
|
18
|
+
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
19
|
+
import {
|
|
20
|
+
compactCvAcquisitionState,
|
|
21
|
+
countParsedNetworkProfiles,
|
|
22
|
+
createCvAcquisitionState,
|
|
23
|
+
DEFAULT_MAX_IMAGE_PAGES,
|
|
24
|
+
getCvNetworkWaitPlan,
|
|
25
|
+
recordCvImageFallback,
|
|
26
|
+
recordCvNetworkHit,
|
|
27
|
+
recordCvNetworkMiss,
|
|
28
|
+
summarizeImageEvidence,
|
|
29
|
+
waitForCvNetworkEvents
|
|
30
|
+
} from "../../core/cv-acquisition/index.js";
|
|
31
|
+
import {
|
|
32
|
+
compactInfiniteListState,
|
|
33
|
+
createInfiniteListState,
|
|
34
|
+
detectInfiniteListBottomMarker,
|
|
35
|
+
getNextInfiniteListCandidate,
|
|
36
|
+
markInfiniteListCandidateProcessed,
|
|
37
|
+
resetInfiniteListForRefreshRound,
|
|
38
|
+
resolveInfiniteListFallbackPoint
|
|
39
|
+
} from "../../core/infinite-list/index.js";
|
|
40
|
+
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
41
|
+
import {
|
|
42
|
+
callScreeningLlm,
|
|
43
|
+
compactScreeningLlmResult,
|
|
44
|
+
createFailedLlmScreeningResult,
|
|
45
|
+
llmResultToScreening,
|
|
46
|
+
screenCandidate
|
|
47
|
+
} from "../../core/screening/index.js";
|
|
48
48
|
import {
|
|
49
|
+
closeRecommendBlockingPanels,
|
|
49
50
|
closeRecommendDetail,
|
|
50
51
|
createRecommendDetailNetworkRecorder,
|
|
51
52
|
extractRecommendDetailCandidate,
|
|
@@ -54,96 +55,96 @@ import {
|
|
|
54
55
|
openRecommendCardDetailWithFreshRetry,
|
|
55
56
|
waitForRecommendDetailNetworkEvents
|
|
56
57
|
} from "./detail.js";
|
|
57
|
-
import {
|
|
58
|
-
readRecommendCardCandidate,
|
|
59
|
-
waitForRecommendCardNodeIds
|
|
60
|
-
} from "./cards.js";
|
|
61
|
-
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
62
|
-
import {
|
|
63
|
-
buildRecommendFilterSelectionOptions,
|
|
64
|
-
refreshRecommendListAtEnd
|
|
65
|
-
} from "./refresh.js";
|
|
66
|
-
import { selectRecommendJob } from "./jobs.js";
|
|
67
|
-
import {
|
|
68
|
-
normalizeRecommendPageScope,
|
|
69
|
-
selectRecommendPageScope
|
|
70
|
-
} from "./scopes.js";
|
|
71
|
-
import {
|
|
72
|
-
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
73
|
-
RECOMMEND_CARD_SELECTOR,
|
|
74
|
-
RECOMMEND_END_REFRESH_SELECTOR,
|
|
75
|
-
RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
76
|
-
RECOMMEND_TARGET_URL
|
|
77
|
-
} from "./constants.js";
|
|
78
|
-
import {
|
|
79
|
-
clickRecommendActionControl,
|
|
80
|
-
normalizeRecommendPostAction,
|
|
81
|
-
resolveRecommendPostAction,
|
|
82
|
-
waitForRecommendDetailActionControls
|
|
83
|
-
} from "./actions.js";
|
|
84
|
-
import { getRecommendRoots } from "./roots.js";
|
|
85
|
-
|
|
86
|
-
function normalizeLabels(labels = []) {
|
|
87
|
-
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function isRefreshableListStall(reason = "") {
|
|
91
|
-
return new Set([
|
|
92
|
-
"stable_visible_signature",
|
|
93
|
-
"max_scrolls_exhausted",
|
|
94
|
-
"scroll_failed",
|
|
95
|
-
"scroll_anchor_unavailable"
|
|
96
|
-
]).has(String(reason || ""));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function normalizeFilter(filter = {}) {
|
|
100
|
-
const filterGroups = Array.isArray(filter.filterGroups)
|
|
101
|
-
? filter.filterGroups
|
|
102
|
-
: Array.isArray(filter.groups)
|
|
103
|
-
? filter.groups
|
|
104
|
-
: [];
|
|
105
|
-
return {
|
|
106
|
-
enabled: filter.enabled !== false,
|
|
107
|
-
group: String(filter.group || ""),
|
|
108
|
-
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
109
|
-
selectAllLabels: Boolean(filter.selectAllLabels),
|
|
110
|
-
filterGroups: filterGroups.map((group) => ({
|
|
111
|
-
group: String(group?.group || ""),
|
|
112
|
-
labels: normalizeLabels(group?.labels || group?.filterLabels || []),
|
|
113
|
-
selectAllLabels: group?.selectAllLabels !== false
|
|
114
|
-
})).filter((group) => group.group || group.labels.length)
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function compactFilterResult(filterResult) {
|
|
119
|
-
if (!filterResult) return null;
|
|
120
|
-
return {
|
|
121
|
-
opened_panel: Boolean(filterResult.opened_panel),
|
|
122
|
-
selected_option: filterResult.selected_option
|
|
123
|
-
? {
|
|
124
|
-
group: filterResult.selected_option.group,
|
|
125
|
-
label: filterResult.selected_option.label,
|
|
126
|
-
was_active: Boolean(filterResult.selected_option.was_active),
|
|
127
|
-
clicked: filterResult.selected_option.clicked !== false
|
|
128
|
-
}
|
|
129
|
-
: null,
|
|
130
|
-
selected_options: (filterResult.selected_options || []).map((option) => ({
|
|
131
|
-
group: option.group,
|
|
132
|
-
label: option.label,
|
|
133
|
-
was_active: Boolean(option.was_active),
|
|
134
|
-
clicked: option.clicked !== false
|
|
135
|
-
})),
|
|
136
|
-
confirmed: Boolean(filterResult.confirmed),
|
|
137
|
-
before_counts: filterResult.before_counts,
|
|
138
|
-
after_confirm_counts: filterResult.after_confirm_counts
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function compactJobSelection(jobSelection) {
|
|
143
|
-
if (!jobSelection) return null;
|
|
144
|
-
return {
|
|
145
|
-
requested: jobSelection.requested || "",
|
|
146
|
-
selected: Boolean(jobSelection.selected),
|
|
58
|
+
import {
|
|
59
|
+
readRecommendCardCandidate,
|
|
60
|
+
waitForRecommendCardNodeIds
|
|
61
|
+
} from "./cards.js";
|
|
62
|
+
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
63
|
+
import {
|
|
64
|
+
buildRecommendFilterSelectionOptions,
|
|
65
|
+
refreshRecommendListAtEnd
|
|
66
|
+
} from "./refresh.js";
|
|
67
|
+
import { selectRecommendJob } from "./jobs.js";
|
|
68
|
+
import {
|
|
69
|
+
normalizeRecommendPageScope,
|
|
70
|
+
selectRecommendPageScope
|
|
71
|
+
} from "./scopes.js";
|
|
72
|
+
import {
|
|
73
|
+
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
74
|
+
RECOMMEND_CARD_SELECTOR,
|
|
75
|
+
RECOMMEND_END_REFRESH_SELECTOR,
|
|
76
|
+
RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
77
|
+
RECOMMEND_TARGET_URL
|
|
78
|
+
} from "./constants.js";
|
|
79
|
+
import {
|
|
80
|
+
clickRecommendActionControl,
|
|
81
|
+
normalizeRecommendPostAction,
|
|
82
|
+
resolveRecommendPostAction,
|
|
83
|
+
waitForRecommendDetailActionControls
|
|
84
|
+
} from "./actions.js";
|
|
85
|
+
import { getRecommendRoots } from "./roots.js";
|
|
86
|
+
|
|
87
|
+
function normalizeLabels(labels = []) {
|
|
88
|
+
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isRefreshableListStall(reason = "") {
|
|
92
|
+
return new Set([
|
|
93
|
+
"stable_visible_signature",
|
|
94
|
+
"max_scrolls_exhausted",
|
|
95
|
+
"scroll_failed",
|
|
96
|
+
"scroll_anchor_unavailable"
|
|
97
|
+
]).has(String(reason || ""));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeFilter(filter = {}) {
|
|
101
|
+
const filterGroups = Array.isArray(filter.filterGroups)
|
|
102
|
+
? filter.filterGroups
|
|
103
|
+
: Array.isArray(filter.groups)
|
|
104
|
+
? filter.groups
|
|
105
|
+
: [];
|
|
106
|
+
return {
|
|
107
|
+
enabled: filter.enabled !== false,
|
|
108
|
+
group: String(filter.group || ""),
|
|
109
|
+
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
110
|
+
selectAllLabels: Boolean(filter.selectAllLabels),
|
|
111
|
+
filterGroups: filterGroups.map((group) => ({
|
|
112
|
+
group: String(group?.group || ""),
|
|
113
|
+
labels: normalizeLabels(group?.labels || group?.filterLabels || []),
|
|
114
|
+
selectAllLabels: group?.selectAllLabels !== false
|
|
115
|
+
})).filter((group) => group.group || group.labels.length)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function compactFilterResult(filterResult) {
|
|
120
|
+
if (!filterResult) return null;
|
|
121
|
+
return {
|
|
122
|
+
opened_panel: Boolean(filterResult.opened_panel),
|
|
123
|
+
selected_option: filterResult.selected_option
|
|
124
|
+
? {
|
|
125
|
+
group: filterResult.selected_option.group,
|
|
126
|
+
label: filterResult.selected_option.label,
|
|
127
|
+
was_active: Boolean(filterResult.selected_option.was_active),
|
|
128
|
+
clicked: filterResult.selected_option.clicked !== false
|
|
129
|
+
}
|
|
130
|
+
: null,
|
|
131
|
+
selected_options: (filterResult.selected_options || []).map((option) => ({
|
|
132
|
+
group: option.group,
|
|
133
|
+
label: option.label,
|
|
134
|
+
was_active: Boolean(option.was_active),
|
|
135
|
+
clicked: option.clicked !== false
|
|
136
|
+
})),
|
|
137
|
+
confirmed: Boolean(filterResult.confirmed),
|
|
138
|
+
before_counts: filterResult.before_counts,
|
|
139
|
+
after_confirm_counts: filterResult.after_confirm_counts
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function compactJobSelection(jobSelection) {
|
|
144
|
+
if (!jobSelection) return null;
|
|
145
|
+
return {
|
|
146
|
+
requested: jobSelection.requested || "",
|
|
147
|
+
selected: Boolean(jobSelection.selected),
|
|
147
148
|
already_current: Boolean(jobSelection.already_current),
|
|
148
149
|
reason: jobSelection.reason || null,
|
|
149
150
|
selected_option: jobSelection.selected_option || null,
|
|
@@ -171,222 +172,222 @@ function compactJobSelection(jobSelection) {
|
|
|
171
172
|
}
|
|
172
173
|
: null,
|
|
173
174
|
options: (jobSelection.options || []).map((option) => ({
|
|
174
|
-
label: option.label,
|
|
175
|
-
label_without_salary: option.label_without_salary,
|
|
176
|
-
current: Boolean(option.current),
|
|
177
|
-
visible: Boolean(option.visible),
|
|
178
|
-
class_name: option.class_name
|
|
179
|
-
}))
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function compactPageScopeSelection(pageScopeSelection) {
|
|
184
|
-
if (!pageScopeSelection) return null;
|
|
185
|
-
return {
|
|
186
|
-
requested_scope: pageScopeSelection.requested_scope || null,
|
|
187
|
-
effective_scope: pageScopeSelection.effective_scope || null,
|
|
188
|
-
fallback_scope: pageScopeSelection.fallback_scope || null,
|
|
189
|
-
fallback_applied: Boolean(pageScopeSelection.fallback_applied),
|
|
190
|
-
selected: Boolean(pageScopeSelection.selected),
|
|
191
|
-
already_current: Boolean(pageScopeSelection.already_current),
|
|
192
|
-
reason: pageScopeSelection.reason || null,
|
|
193
|
-
selected_tab: pageScopeSelection.selected_tab || null,
|
|
194
|
-
available_scopes: pageScopeSelection.available_scopes || [],
|
|
195
|
-
card_count: pageScopeSelection.after?.card_count || null
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function compactScreening(screening) {
|
|
200
|
-
return {
|
|
201
|
-
status: screening.status,
|
|
202
|
-
passed: screening.passed,
|
|
203
|
-
score: screening.score,
|
|
204
|
-
reasons: screening.reasons,
|
|
205
|
-
candidate: {
|
|
206
|
-
domain: screening.candidate?.domain || "recommend",
|
|
207
|
-
source: screening.candidate?.source || "",
|
|
208
|
-
id: screening.candidate?.id || null,
|
|
209
|
-
identity: screening.candidate?.identity || {}
|
|
210
|
-
}
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function compactCandidate(candidate) {
|
|
215
|
-
return {
|
|
216
|
-
id: candidate?.id || null,
|
|
217
|
-
identity: candidate?.identity || {},
|
|
218
|
-
text_length: candidate?.text?.raw?.length || 0,
|
|
219
|
-
tag_count: candidate?.tags?.length || 0
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function compactDetail(detailResult) {
|
|
224
|
-
if (!detailResult) return null;
|
|
225
|
-
return {
|
|
226
|
-
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
227
|
-
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
228
|
-
network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
|
|
229
|
-
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
230
|
-
cv_acquisition: detailResult.cv_acquisition || null,
|
|
231
|
-
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
232
|
-
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
233
|
-
close_result: detailResult.close_result
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function normalizeScreeningMode(value) {
|
|
238
|
-
const normalized = String(value || "llm").trim().toLowerCase();
|
|
239
|
-
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
240
|
-
? "deterministic"
|
|
241
|
-
: "llm";
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function createMissingLlmConfigResult() {
|
|
245
|
-
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production recommend runs"));
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function compactActionDiscovery(discovery) {
|
|
249
|
-
if (!discovery) return null;
|
|
250
|
-
return {
|
|
251
|
-
elapsed_ms: discovery.elapsed_ms,
|
|
252
|
-
timed_out: Boolean(discovery.timed_out),
|
|
253
|
-
detail_root_count: discovery.detail_root_count || 0,
|
|
254
|
-
summary: discovery.summary || null
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async function runRecommendPostAction({
|
|
259
|
-
client,
|
|
260
|
-
screening,
|
|
261
|
-
actionDiscovery,
|
|
262
|
-
postAction = "none",
|
|
263
|
-
greetCount = 0,
|
|
264
|
-
maxGreetCount = null,
|
|
265
|
-
executePostAction = true,
|
|
266
|
-
afterClickDelayMs = 900
|
|
267
|
-
} = {}) {
|
|
268
|
-
const plan = resolveRecommendPostAction({
|
|
269
|
-
postAction,
|
|
270
|
-
greetCount,
|
|
271
|
-
maxGreetCount
|
|
272
|
-
});
|
|
273
|
-
const result = {
|
|
274
|
-
requested: postAction,
|
|
275
|
-
execute_post_action: Boolean(executePostAction),
|
|
276
|
-
plan,
|
|
277
|
-
eligible: Boolean(screening?.passed),
|
|
278
|
-
action_attempted: false,
|
|
279
|
-
action_clicked: false,
|
|
280
|
-
counted_as_greet: false,
|
|
281
|
-
reason: ""
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
if (!screening?.passed) {
|
|
285
|
-
result.reason = "screening_not_passed";
|
|
286
|
-
return result;
|
|
287
|
-
}
|
|
288
|
-
if (plan.effective === "none") {
|
|
289
|
-
result.reason = "post_action_none";
|
|
290
|
-
return result;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const summary = actionDiscovery?.summary || {};
|
|
294
|
-
const control = plan.effective === "favorite" ? summary.favorite : summary.greet;
|
|
295
|
-
if (!control?.found) {
|
|
296
|
-
result.reason = `${plan.effective}_control_not_found`;
|
|
297
|
-
return result;
|
|
298
|
-
}
|
|
299
|
-
result.control = control;
|
|
300
|
-
|
|
301
|
-
if (plan.effective === "greet" && control.continue_chat) {
|
|
302
|
-
result.reason = "already_connected_continue_chat";
|
|
303
|
-
result.already_connected = true;
|
|
304
|
-
return result;
|
|
305
|
-
}
|
|
306
|
-
if (plan.effective === "greet" && control.greet_quota?.exhausted) {
|
|
307
|
-
result.reason = "greet_credits_exhausted";
|
|
308
|
-
result.out_of_greet_credits = true;
|
|
309
|
-
result.stop_run = true;
|
|
310
|
-
return result;
|
|
311
|
-
}
|
|
312
|
-
if (plan.effective === "greet" && control.available === false) {
|
|
313
|
-
result.reason = "greet_control_not_available";
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
if (plan.effective === "favorite" && control.active) {
|
|
317
|
-
result.reason = "already_favorited";
|
|
318
|
-
result.already_favorited = true;
|
|
319
|
-
return result;
|
|
320
|
-
}
|
|
321
|
-
if (control.disabled) {
|
|
322
|
-
result.reason = `${plan.effective}_control_disabled`;
|
|
323
|
-
return result;
|
|
324
|
-
}
|
|
325
|
-
if (!executePostAction) {
|
|
326
|
-
result.reason = "dry_run_post_action";
|
|
327
|
-
result.would_click = true;
|
|
328
|
-
return result;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
result.action_attempted = true;
|
|
332
|
-
result.control_before = control;
|
|
333
|
-
let clickResult;
|
|
334
|
-
try {
|
|
335
|
-
clickResult = await clickRecommendActionControl(client, {
|
|
336
|
-
...control,
|
|
337
|
-
kind: plan.effective
|
|
338
|
-
});
|
|
339
|
-
} catch (error) {
|
|
340
|
-
if (error?.code === GREET_CREDITS_EXHAUSTED_CODE) {
|
|
341
|
-
result.reason = "greet_credits_exhausted";
|
|
342
|
-
result.out_of_greet_credits = true;
|
|
343
|
-
result.stop_run = true;
|
|
344
|
-
result.greet_quota = error.greet_quota || control.greet_quota || null;
|
|
345
|
-
return result;
|
|
346
|
-
}
|
|
347
|
-
throw error;
|
|
348
|
-
}
|
|
349
|
-
result.click_result = clickResult;
|
|
350
|
-
result.action_clicked = true;
|
|
351
|
-
result.counted_as_greet = plan.effective === "greet";
|
|
352
|
-
result.reason = "clicked";
|
|
353
|
-
if (afterClickDelayMs > 0) await sleep(afterClickDelayMs);
|
|
354
|
-
try {
|
|
355
|
-
const afterDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
356
|
-
timeoutMs: 2500,
|
|
357
|
-
intervalMs: 300,
|
|
358
|
-
requireAny: false
|
|
359
|
-
});
|
|
360
|
-
const afterSummary = afterDiscovery?.summary || {};
|
|
361
|
-
const afterControl = plan.effective === "favorite" ? afterSummary.favorite : afterSummary.greet;
|
|
362
|
-
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
363
|
-
result.control_after = afterControl || null;
|
|
364
|
-
if (plan.effective === "greet") {
|
|
365
|
-
result.verified_after_click = Boolean(
|
|
366
|
-
afterControl?.continue_chat
|
|
367
|
-
|| String(afterControl?.label || "").includes("继续沟通")
|
|
368
|
-
);
|
|
369
|
-
} else if (plan.effective === "favorite") {
|
|
370
|
-
result.verified_after_click = Boolean(
|
|
371
|
-
afterControl?.active
|
|
372
|
-
|| String(afterControl?.label || "").includes("已")
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
} catch (error) {
|
|
376
|
-
result.verify_error = {
|
|
377
|
-
message: error?.message || String(error)
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
return result;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
function compactRefreshAttempt(refreshAttempt) {
|
|
384
|
-
if (!refreshAttempt) return null;
|
|
385
|
-
return {
|
|
386
|
-
ok: Boolean(refreshAttempt.ok),
|
|
387
|
-
method: refreshAttempt.method || "",
|
|
388
|
-
reason: refreshAttempt.reason || null,
|
|
389
|
-
error: refreshAttempt.error || null,
|
|
175
|
+
label: option.label,
|
|
176
|
+
label_without_salary: option.label_without_salary,
|
|
177
|
+
current: Boolean(option.current),
|
|
178
|
+
visible: Boolean(option.visible),
|
|
179
|
+
class_name: option.class_name
|
|
180
|
+
}))
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function compactPageScopeSelection(pageScopeSelection) {
|
|
185
|
+
if (!pageScopeSelection) return null;
|
|
186
|
+
return {
|
|
187
|
+
requested_scope: pageScopeSelection.requested_scope || null,
|
|
188
|
+
effective_scope: pageScopeSelection.effective_scope || null,
|
|
189
|
+
fallback_scope: pageScopeSelection.fallback_scope || null,
|
|
190
|
+
fallback_applied: Boolean(pageScopeSelection.fallback_applied),
|
|
191
|
+
selected: Boolean(pageScopeSelection.selected),
|
|
192
|
+
already_current: Boolean(pageScopeSelection.already_current),
|
|
193
|
+
reason: pageScopeSelection.reason || null,
|
|
194
|
+
selected_tab: pageScopeSelection.selected_tab || null,
|
|
195
|
+
available_scopes: pageScopeSelection.available_scopes || [],
|
|
196
|
+
card_count: pageScopeSelection.after?.card_count || null
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function compactScreening(screening) {
|
|
201
|
+
return {
|
|
202
|
+
status: screening.status,
|
|
203
|
+
passed: screening.passed,
|
|
204
|
+
score: screening.score,
|
|
205
|
+
reasons: screening.reasons,
|
|
206
|
+
candidate: {
|
|
207
|
+
domain: screening.candidate?.domain || "recommend",
|
|
208
|
+
source: screening.candidate?.source || "",
|
|
209
|
+
id: screening.candidate?.id || null,
|
|
210
|
+
identity: screening.candidate?.identity || {}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function compactCandidate(candidate) {
|
|
216
|
+
return {
|
|
217
|
+
id: candidate?.id || null,
|
|
218
|
+
identity: candidate?.identity || {},
|
|
219
|
+
text_length: candidate?.text?.raw?.length || 0,
|
|
220
|
+
tag_count: candidate?.tags?.length || 0
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function compactDetail(detailResult) {
|
|
225
|
+
if (!detailResult) return null;
|
|
226
|
+
return {
|
|
227
|
+
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
228
|
+
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
229
|
+
network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
|
|
230
|
+
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
231
|
+
cv_acquisition: detailResult.cv_acquisition || null,
|
|
232
|
+
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
233
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
234
|
+
close_result: detailResult.close_result
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function normalizeScreeningMode(value) {
|
|
239
|
+
const normalized = String(value || "llm").trim().toLowerCase();
|
|
240
|
+
return ["deterministic", "local", "local_scorer"].includes(normalized)
|
|
241
|
+
? "deterministic"
|
|
242
|
+
: "llm";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function createMissingLlmConfigResult() {
|
|
246
|
+
return createFailedLlmScreeningResult(new Error("LLM screening config is required for production recommend runs"));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function compactActionDiscovery(discovery) {
|
|
250
|
+
if (!discovery) return null;
|
|
251
|
+
return {
|
|
252
|
+
elapsed_ms: discovery.elapsed_ms,
|
|
253
|
+
timed_out: Boolean(discovery.timed_out),
|
|
254
|
+
detail_root_count: discovery.detail_root_count || 0,
|
|
255
|
+
summary: discovery.summary || null
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
async function runRecommendPostAction({
|
|
260
|
+
client,
|
|
261
|
+
screening,
|
|
262
|
+
actionDiscovery,
|
|
263
|
+
postAction = "none",
|
|
264
|
+
greetCount = 0,
|
|
265
|
+
maxGreetCount = null,
|
|
266
|
+
executePostAction = true,
|
|
267
|
+
afterClickDelayMs = 900
|
|
268
|
+
} = {}) {
|
|
269
|
+
const plan = resolveRecommendPostAction({
|
|
270
|
+
postAction,
|
|
271
|
+
greetCount,
|
|
272
|
+
maxGreetCount
|
|
273
|
+
});
|
|
274
|
+
const result = {
|
|
275
|
+
requested: postAction,
|
|
276
|
+
execute_post_action: Boolean(executePostAction),
|
|
277
|
+
plan,
|
|
278
|
+
eligible: Boolean(screening?.passed),
|
|
279
|
+
action_attempted: false,
|
|
280
|
+
action_clicked: false,
|
|
281
|
+
counted_as_greet: false,
|
|
282
|
+
reason: ""
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
if (!screening?.passed) {
|
|
286
|
+
result.reason = "screening_not_passed";
|
|
287
|
+
return result;
|
|
288
|
+
}
|
|
289
|
+
if (plan.effective === "none") {
|
|
290
|
+
result.reason = "post_action_none";
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const summary = actionDiscovery?.summary || {};
|
|
295
|
+
const control = plan.effective === "favorite" ? summary.favorite : summary.greet;
|
|
296
|
+
if (!control?.found) {
|
|
297
|
+
result.reason = `${plan.effective}_control_not_found`;
|
|
298
|
+
return result;
|
|
299
|
+
}
|
|
300
|
+
result.control = control;
|
|
301
|
+
|
|
302
|
+
if (plan.effective === "greet" && control.continue_chat) {
|
|
303
|
+
result.reason = "already_connected_continue_chat";
|
|
304
|
+
result.already_connected = true;
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
if (plan.effective === "greet" && control.greet_quota?.exhausted) {
|
|
308
|
+
result.reason = "greet_credits_exhausted";
|
|
309
|
+
result.out_of_greet_credits = true;
|
|
310
|
+
result.stop_run = true;
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
if (plan.effective === "greet" && control.available === false) {
|
|
314
|
+
result.reason = "greet_control_not_available";
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
if (plan.effective === "favorite" && control.active) {
|
|
318
|
+
result.reason = "already_favorited";
|
|
319
|
+
result.already_favorited = true;
|
|
320
|
+
return result;
|
|
321
|
+
}
|
|
322
|
+
if (control.disabled) {
|
|
323
|
+
result.reason = `${plan.effective}_control_disabled`;
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
if (!executePostAction) {
|
|
327
|
+
result.reason = "dry_run_post_action";
|
|
328
|
+
result.would_click = true;
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
result.action_attempted = true;
|
|
333
|
+
result.control_before = control;
|
|
334
|
+
let clickResult;
|
|
335
|
+
try {
|
|
336
|
+
clickResult = await clickRecommendActionControl(client, {
|
|
337
|
+
...control,
|
|
338
|
+
kind: plan.effective
|
|
339
|
+
});
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (error?.code === GREET_CREDITS_EXHAUSTED_CODE) {
|
|
342
|
+
result.reason = "greet_credits_exhausted";
|
|
343
|
+
result.out_of_greet_credits = true;
|
|
344
|
+
result.stop_run = true;
|
|
345
|
+
result.greet_quota = error.greet_quota || control.greet_quota || null;
|
|
346
|
+
return result;
|
|
347
|
+
}
|
|
348
|
+
throw error;
|
|
349
|
+
}
|
|
350
|
+
result.click_result = clickResult;
|
|
351
|
+
result.action_clicked = true;
|
|
352
|
+
result.counted_as_greet = plan.effective === "greet";
|
|
353
|
+
result.reason = "clicked";
|
|
354
|
+
if (afterClickDelayMs > 0) await sleep(afterClickDelayMs);
|
|
355
|
+
try {
|
|
356
|
+
const afterDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
357
|
+
timeoutMs: 2500,
|
|
358
|
+
intervalMs: 300,
|
|
359
|
+
requireAny: false
|
|
360
|
+
});
|
|
361
|
+
const afterSummary = afterDiscovery?.summary || {};
|
|
362
|
+
const afterControl = plan.effective === "favorite" ? afterSummary.favorite : afterSummary.greet;
|
|
363
|
+
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
364
|
+
result.control_after = afterControl || null;
|
|
365
|
+
if (plan.effective === "greet") {
|
|
366
|
+
result.verified_after_click = Boolean(
|
|
367
|
+
afterControl?.continue_chat
|
|
368
|
+
|| String(afterControl?.label || "").includes("继续沟通")
|
|
369
|
+
);
|
|
370
|
+
} else if (plan.effective === "favorite") {
|
|
371
|
+
result.verified_after_click = Boolean(
|
|
372
|
+
afterControl?.active
|
|
373
|
+
|| String(afterControl?.label || "").includes("已")
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
} catch (error) {
|
|
377
|
+
result.verify_error = {
|
|
378
|
+
message: error?.message || String(error)
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
return result;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function compactRefreshAttempt(refreshAttempt) {
|
|
385
|
+
if (!refreshAttempt) return null;
|
|
386
|
+
return {
|
|
387
|
+
ok: Boolean(refreshAttempt.ok),
|
|
388
|
+
method: refreshAttempt.method || "",
|
|
389
|
+
reason: refreshAttempt.reason || null,
|
|
390
|
+
error: refreshAttempt.error || null,
|
|
390
391
|
forced_recent_not_view: Boolean(refreshAttempt.forced_recent_not_view),
|
|
391
392
|
target_url: refreshAttempt.target_url || null,
|
|
392
393
|
card_count: refreshAttempt.card_count || 0,
|
|
@@ -400,11 +401,11 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
400
401
|
}
|
|
401
402
|
: null,
|
|
402
403
|
attempts: (refreshAttempt.attempts || []).map((attempt) => ({
|
|
403
|
-
ok: Boolean(attempt.ok),
|
|
404
|
-
method: attempt.method || "",
|
|
405
|
-
reason: attempt.reason || null,
|
|
406
|
-
error: attempt.error || null,
|
|
407
|
-
label: attempt.label || null,
|
|
404
|
+
ok: Boolean(attempt.ok),
|
|
405
|
+
method: attempt.method || "",
|
|
406
|
+
reason: attempt.reason || null,
|
|
407
|
+
error: attempt.error || null,
|
|
408
|
+
label: attempt.label || null,
|
|
408
409
|
before_card_count: attempt.before_card_count || 0,
|
|
409
410
|
after_card_count: attempt.after_card_count || 0,
|
|
410
411
|
card_count: attempt.card_count || 0,
|
|
@@ -432,22 +433,22 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
432
433
|
filter: compactFilterResult(refreshAttempt.filter)
|
|
433
434
|
};
|
|
434
435
|
}
|
|
435
|
-
|
|
436
|
-
export function countRecommendResultStatuses(results = [], {
|
|
437
|
-
greetCount = 0
|
|
438
|
-
} = {}) {
|
|
439
|
-
return {
|
|
440
|
-
processed: results.length,
|
|
441
|
-
screened: results.length,
|
|
442
|
-
detail_opened: results.filter((item) => item.detail).length,
|
|
443
|
-
passed: results.filter((item) => item.screening?.passed).length,
|
|
444
|
-
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
445
|
-
greet_count: greetCount,
|
|
446
|
-
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
447
|
-
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
448
|
-
detail_open_failed: results.filter((item) => (
|
|
449
|
-
item.error?.code === "DETAIL_STALE_NODE"
|
|
450
|
-
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
436
|
+
|
|
437
|
+
export function countRecommendResultStatuses(results = [], {
|
|
438
|
+
greetCount = 0
|
|
439
|
+
} = {}) {
|
|
440
|
+
return {
|
|
441
|
+
processed: results.length,
|
|
442
|
+
screened: results.length,
|
|
443
|
+
detail_opened: results.filter((item) => item.detail).length,
|
|
444
|
+
passed: results.filter((item) => item.screening?.passed).length,
|
|
445
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
446
|
+
greet_count: greetCount,
|
|
447
|
+
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
448
|
+
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
449
|
+
detail_open_failed: results.filter((item) => (
|
|
450
|
+
item.error?.code === "DETAIL_STALE_NODE"
|
|
451
|
+
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
451
452
|
)).length,
|
|
452
453
|
transient_recovered: results.filter((item) => (
|
|
453
454
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
@@ -455,22 +456,27 @@ export function countRecommendResultStatuses(results = [], {
|
|
|
455
456
|
|| item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
|
|
456
457
|
|| item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
|
|
457
458
|
|| item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
|
|
458
|
-
)).length
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
|
|
459
|
+
)).length
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
|
|
462
463
|
function countPassedResults(results = []) {
|
|
463
464
|
return countRecommendResultStatuses(results).passed;
|
|
464
465
|
}
|
|
465
466
|
|
|
466
467
|
function compactCloseResult(closeResult) {
|
|
467
468
|
if (!closeResult) return null;
|
|
468
|
-
|
|
469
|
+
const result = {
|
|
469
470
|
closed: Boolean(closeResult.closed),
|
|
470
471
|
reason: closeResult.reason || null,
|
|
472
|
+
probe: closeResult.probe || null,
|
|
471
473
|
attempts: closeResult.attempts || [],
|
|
472
474
|
verification: closeResult.verification || null
|
|
473
475
|
};
|
|
476
|
+
if (closeResult.already_closed !== undefined) {
|
|
477
|
+
result.already_closed = Boolean(closeResult.already_closed);
|
|
478
|
+
}
|
|
479
|
+
return result;
|
|
474
480
|
}
|
|
475
481
|
|
|
476
482
|
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
@@ -482,6 +488,9 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
482
488
|
if (error.close_result) {
|
|
483
489
|
result.close_result = compactCloseResult(error.close_result);
|
|
484
490
|
}
|
|
491
|
+
if (error.phase) {
|
|
492
|
+
result.phase = error.phase;
|
|
493
|
+
}
|
|
485
494
|
if (error.refresh_attempt) {
|
|
486
495
|
result.refresh_attempt = error.refresh_attempt;
|
|
487
496
|
}
|
|
@@ -499,7 +508,7 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
499
508
|
}
|
|
500
509
|
return result;
|
|
501
510
|
}
|
|
502
|
-
|
|
511
|
+
|
|
503
512
|
function createRecommendCloseFailureError(closeResult) {
|
|
504
513
|
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
505
514
|
error.code = "DETAIL_CLOSE_FAILED";
|
|
@@ -507,6 +516,14 @@ function createRecommendCloseFailureError(closeResult) {
|
|
|
507
516
|
return error;
|
|
508
517
|
}
|
|
509
518
|
|
|
519
|
+
function createRecommendBlockingPanelCloseFailureError(closeResult, phase = "") {
|
|
520
|
+
const error = new Error(closeResult?.reason || "Boss account-rights panel did not close before recovery");
|
|
521
|
+
error.code = "ACCOUNT_RIGHTS_PANEL_CLOSE_FAILED";
|
|
522
|
+
error.close_result = closeResult || null;
|
|
523
|
+
error.phase = phase || null;
|
|
524
|
+
return error;
|
|
525
|
+
}
|
|
526
|
+
|
|
510
527
|
function createRecommendRefreshFailureError(refreshAttempt, {
|
|
511
528
|
listEndReason = "",
|
|
512
529
|
targetCount = 0,
|
|
@@ -522,124 +539,124 @@ function createRecommendRefreshFailureError(refreshAttempt, {
|
|
|
522
539
|
error.passed_count = passedCount;
|
|
523
540
|
return error;
|
|
524
541
|
}
|
|
525
|
-
|
|
526
|
-
export function isRecoverableImageCaptureError(error) {
|
|
527
|
-
const code = String(error?.code || "");
|
|
528
|
-
if (code === "IMAGE_CAPTURE_TIMEOUT" || code === "IMAGE_CAPTURE_TOTAL_TIMEOUT") return true;
|
|
529
|
-
if (isStaleRecommendNodeError(error)) return true;
|
|
530
|
-
return /Image fallback capture timed out/i.test(String(error?.message || error || ""));
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function collectPartialImageEvidencePaths(basePath = "", extension = "jpg", maxCount = 12) {
|
|
534
|
-
const resolved = String(basePath || "").trim();
|
|
535
|
-
if (!resolved) return [];
|
|
536
|
-
const parsed = path.parse(resolved);
|
|
537
|
-
const ext = parsed.ext || `.${String(extension || "jpg").replace(/^\./, "") || "jpg"}`;
|
|
538
|
-
const files = [];
|
|
539
|
-
for (let index = 0; index < Math.max(1, Number(maxCount) || 1); index += 1) {
|
|
540
|
-
const page = String(index + 1).padStart(2, "0");
|
|
541
|
-
const candidatePath = path.join(parsed.dir, `${parsed.name}-page-${page}${ext}`);
|
|
542
|
-
if (fs.existsSync(candidatePath)) files.push(candidatePath);
|
|
543
|
-
}
|
|
544
|
-
return files;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
export function createRecoverableImageCaptureEvidence(error, {
|
|
548
|
-
elapsedMs = 0,
|
|
549
|
-
filePath = "",
|
|
550
|
-
extension = "jpg",
|
|
551
|
-
maxScreenshots = DEFAULT_MAX_IMAGE_PAGES
|
|
552
|
-
} = {}) {
|
|
553
|
-
const filePaths = collectPartialImageEvidencePaths(filePath, extension, maxScreenshots);
|
|
554
|
-
return {
|
|
555
|
-
schema_version: 1,
|
|
556
|
-
ok: false,
|
|
557
|
-
source: "image-scroll-sequence",
|
|
558
|
-
elapsed_ms: Math.max(0, Math.round(Number(error?.elapsed_ms ?? elapsedMs) || 0)),
|
|
559
|
-
capture_count: filePaths.length,
|
|
560
|
-
screenshot_count: filePaths.length,
|
|
561
|
-
unique_screenshot_count: filePaths.length,
|
|
562
|
-
dropped_duplicate_count: 0,
|
|
563
|
-
total_byte_length: 0,
|
|
564
|
-
original_total_byte_length: 0,
|
|
565
|
-
llm_screenshot_count: 0,
|
|
566
|
-
llm_total_byte_length: 0,
|
|
567
|
-
llm_original_total_byte_length: 0,
|
|
568
|
-
llm_composition_error: null,
|
|
569
|
-
error_code: error?.code || (isStaleRecommendNodeError(error) ? "IMAGE_CAPTURE_STALE_NODE" : "IMAGE_CAPTURE_FAILED"),
|
|
570
|
-
error: error?.message || String(error || "Image capture failed"),
|
|
571
|
-
file_paths: filePaths,
|
|
572
|
-
llm_file_paths: []
|
|
573
|
-
};
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
function createImageCaptureFailureScreening(candidate, error) {
|
|
577
|
-
return {
|
|
578
|
-
status: "fail",
|
|
579
|
-
passed: false,
|
|
580
|
-
score: 0,
|
|
581
|
-
reasons: ["image_capture_failed"],
|
|
582
|
-
error: compactError(error, "IMAGE_CAPTURE_FAILED"),
|
|
583
|
-
candidate
|
|
584
|
-
};
|
|
585
|
-
}
|
|
586
|
-
|
|
542
|
+
|
|
543
|
+
export function isRecoverableImageCaptureError(error) {
|
|
544
|
+
const code = String(error?.code || "");
|
|
545
|
+
if (code === "IMAGE_CAPTURE_TIMEOUT" || code === "IMAGE_CAPTURE_TOTAL_TIMEOUT") return true;
|
|
546
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
547
|
+
return /Image fallback capture timed out/i.test(String(error?.message || error || ""));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function collectPartialImageEvidencePaths(basePath = "", extension = "jpg", maxCount = 12) {
|
|
551
|
+
const resolved = String(basePath || "").trim();
|
|
552
|
+
if (!resolved) return [];
|
|
553
|
+
const parsed = path.parse(resolved);
|
|
554
|
+
const ext = parsed.ext || `.${String(extension || "jpg").replace(/^\./, "") || "jpg"}`;
|
|
555
|
+
const files = [];
|
|
556
|
+
for (let index = 0; index < Math.max(1, Number(maxCount) || 1); index += 1) {
|
|
557
|
+
const page = String(index + 1).padStart(2, "0");
|
|
558
|
+
const candidatePath = path.join(parsed.dir, `${parsed.name}-page-${page}${ext}`);
|
|
559
|
+
if (fs.existsSync(candidatePath)) files.push(candidatePath);
|
|
560
|
+
}
|
|
561
|
+
return files;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export function createRecoverableImageCaptureEvidence(error, {
|
|
565
|
+
elapsedMs = 0,
|
|
566
|
+
filePath = "",
|
|
567
|
+
extension = "jpg",
|
|
568
|
+
maxScreenshots = DEFAULT_MAX_IMAGE_PAGES
|
|
569
|
+
} = {}) {
|
|
570
|
+
const filePaths = collectPartialImageEvidencePaths(filePath, extension, maxScreenshots);
|
|
571
|
+
return {
|
|
572
|
+
schema_version: 1,
|
|
573
|
+
ok: false,
|
|
574
|
+
source: "image-scroll-sequence",
|
|
575
|
+
elapsed_ms: Math.max(0, Math.round(Number(error?.elapsed_ms ?? elapsedMs) || 0)),
|
|
576
|
+
capture_count: filePaths.length,
|
|
577
|
+
screenshot_count: filePaths.length,
|
|
578
|
+
unique_screenshot_count: filePaths.length,
|
|
579
|
+
dropped_duplicate_count: 0,
|
|
580
|
+
total_byte_length: 0,
|
|
581
|
+
original_total_byte_length: 0,
|
|
582
|
+
llm_screenshot_count: 0,
|
|
583
|
+
llm_total_byte_length: 0,
|
|
584
|
+
llm_original_total_byte_length: 0,
|
|
585
|
+
llm_composition_error: null,
|
|
586
|
+
error_code: error?.code || (isStaleRecommendNodeError(error) ? "IMAGE_CAPTURE_STALE_NODE" : "IMAGE_CAPTURE_FAILED"),
|
|
587
|
+
error: error?.message || String(error || "Image capture failed"),
|
|
588
|
+
file_paths: filePaths,
|
|
589
|
+
llm_file_paths: []
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function createImageCaptureFailureScreening(candidate, error) {
|
|
594
|
+
return {
|
|
595
|
+
status: "fail",
|
|
596
|
+
passed: false,
|
|
597
|
+
score: 0,
|
|
598
|
+
reasons: ["image_capture_failed"],
|
|
599
|
+
error: compactError(error, "IMAGE_CAPTURE_FAILED"),
|
|
600
|
+
candidate
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
587
604
|
export function isRecoverableRecommendDetailError(error) {
|
|
588
605
|
return isStaleRecommendNodeError(error) || isRecommendDetailOpenMissError(error);
|
|
589
606
|
}
|
|
590
|
-
|
|
591
|
-
function compactRecoverableDetailError(error) {
|
|
592
|
-
return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
596
|
-
return {
|
|
597
|
-
status: "fail",
|
|
598
|
-
passed: false,
|
|
607
|
+
|
|
608
|
+
function compactRecoverableDetailError(error) {
|
|
609
|
+
return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
613
|
+
return {
|
|
614
|
+
status: "fail",
|
|
615
|
+
passed: false,
|
|
599
616
|
score: 0,
|
|
600
617
|
reasons: isStaleRecommendNodeError(error)
|
|
601
618
|
? ["detail_open_failed", "stale_node"]
|
|
602
619
|
: isRecommendDetailOpenMissError(error)
|
|
603
620
|
? ["detail_open_failed", "detail_open_miss"]
|
|
604
621
|
: ["detail_open_failed"],
|
|
605
|
-
error: compactRecoverableDetailError(error),
|
|
606
|
-
candidate
|
|
607
|
-
};
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
export async function runRecommendWorkflow({
|
|
611
|
-
client,
|
|
612
|
-
targetUrl = "",
|
|
613
|
-
criteria = "",
|
|
614
|
-
jobLabel = "",
|
|
615
|
-
pageScope = "recommend",
|
|
616
|
-
fallbackPageScope = "recommend",
|
|
617
|
-
filter = {},
|
|
618
|
-
maxCandidates = 5,
|
|
619
|
-
detailLimit,
|
|
620
|
-
closeDetail = true,
|
|
621
|
-
delayMs = 0,
|
|
622
|
-
cardTimeoutMs = 10000,
|
|
623
|
-
maxImagePages = DEFAULT_MAX_IMAGE_PAGES,
|
|
624
|
-
imageWheelDeltaY = 650,
|
|
625
|
-
cvAcquisitionMode = "unknown",
|
|
626
|
-
listMaxScrolls = 20,
|
|
627
|
-
listStableSignatureLimit = 5,
|
|
628
|
-
listWheelDeltaY = 850,
|
|
629
|
-
listSettleMs = 2200,
|
|
630
|
-
listFallbackPoint = null,
|
|
631
|
-
refreshOnEnd = true,
|
|
632
|
-
maxRefreshRounds = 2,
|
|
633
|
-
refreshButtonSettleMs = 8000,
|
|
634
|
-
refreshReloadSettleMs = 8000,
|
|
635
|
-
postAction = "none",
|
|
636
|
-
maxGreetCount = null,
|
|
637
|
-
executePostAction = true,
|
|
638
|
-
actionTimeoutMs = 8000,
|
|
639
|
-
actionIntervalMs = 500,
|
|
640
|
-
actionAfterClickDelayMs = 900,
|
|
641
|
-
screeningMode = "llm",
|
|
642
|
-
llmConfig = null,
|
|
622
|
+
error: compactRecoverableDetailError(error),
|
|
623
|
+
candidate
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
export async function runRecommendWorkflow({
|
|
628
|
+
client,
|
|
629
|
+
targetUrl = "",
|
|
630
|
+
criteria = "",
|
|
631
|
+
jobLabel = "",
|
|
632
|
+
pageScope = "recommend",
|
|
633
|
+
fallbackPageScope = "recommend",
|
|
634
|
+
filter = {},
|
|
635
|
+
maxCandidates = 5,
|
|
636
|
+
detailLimit,
|
|
637
|
+
closeDetail = true,
|
|
638
|
+
delayMs = 0,
|
|
639
|
+
cardTimeoutMs = 10000,
|
|
640
|
+
maxImagePages = DEFAULT_MAX_IMAGE_PAGES,
|
|
641
|
+
imageWheelDeltaY = 650,
|
|
642
|
+
cvAcquisitionMode = "unknown",
|
|
643
|
+
listMaxScrolls = 20,
|
|
644
|
+
listStableSignatureLimit = 5,
|
|
645
|
+
listWheelDeltaY = 850,
|
|
646
|
+
listSettleMs = 2200,
|
|
647
|
+
listFallbackPoint = null,
|
|
648
|
+
refreshOnEnd = true,
|
|
649
|
+
maxRefreshRounds = 2,
|
|
650
|
+
refreshButtonSettleMs = 8000,
|
|
651
|
+
refreshReloadSettleMs = 8000,
|
|
652
|
+
postAction = "none",
|
|
653
|
+
maxGreetCount = null,
|
|
654
|
+
executePostAction = true,
|
|
655
|
+
actionTimeoutMs = 8000,
|
|
656
|
+
actionIntervalMs = 500,
|
|
657
|
+
actionAfterClickDelayMs = 900,
|
|
658
|
+
screeningMode = "llm",
|
|
659
|
+
llmConfig = null,
|
|
643
660
|
llmTimeoutMs = 120000,
|
|
644
661
|
llmImageLimit = 8,
|
|
645
662
|
llmImageDetail = "high",
|
|
@@ -665,54 +682,55 @@ export async function runRecommendWorkflow({
|
|
|
665
682
|
batchRestEnabled: effectiveHumanBehavior.batchRest
|
|
666
683
|
});
|
|
667
684
|
const normalizedFilter = normalizeFilter(filter);
|
|
668
|
-
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
669
|
-
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
670
|
-
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
671
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
672
|
-
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
673
|
-
const postActionEnabled = normalizedPostAction !== "none";
|
|
674
|
-
const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
|
|
675
|
-
const detailCountLimit = detailLimit == null ? Number.POSITIVE_INFINITY : Math.max(0, Number(detailLimit) || 0);
|
|
676
|
-
const effectiveDetailLimit = postActionEnabled ? Number.POSITIVE_INFINITY : detailCountLimit;
|
|
677
|
-
const networkRecorder = effectiveDetailLimit > 0
|
|
678
|
-
? createRecommendDetailNetworkRecorder(client)
|
|
679
|
-
: null;
|
|
680
|
-
const cvAcquisitionState = createCvAcquisitionState({ mode: cvAcquisitionMode });
|
|
681
|
-
const listState = createInfiniteListState({
|
|
682
|
-
domain: "recommend",
|
|
683
|
-
listName: "recommend-candidates"
|
|
684
|
-
});
|
|
685
|
-
const viewportGuard = createViewportRunGuard({
|
|
686
|
-
client,
|
|
687
|
-
domain: "recommend",
|
|
688
|
-
root: "frame",
|
|
689
|
-
frameOwnerRoot: "frameOwner",
|
|
690
|
-
runControl,
|
|
691
|
-
getRoots: getRecommendRoots
|
|
692
|
-
});
|
|
693
|
-
async function ensureRecommendViewport(rootState, phase) {
|
|
694
|
-
const result = await viewportGuard.ensure(rootState, { phase });
|
|
695
|
-
return result.rootState || rootState;
|
|
696
|
-
}
|
|
697
|
-
const results = [];
|
|
698
|
-
const refreshAttempts = [];
|
|
699
|
-
let refreshRounds = 0;
|
|
700
|
-
let contextRecoveryAttempts = 0;
|
|
701
|
-
let greetCount = 0;
|
|
685
|
+
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
686
|
+
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
687
|
+
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
688
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
689
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
690
|
+
const postActionEnabled = normalizedPostAction !== "none";
|
|
691
|
+
const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
|
|
692
|
+
const detailCountLimit = detailLimit == null ? Number.POSITIVE_INFINITY : Math.max(0, Number(detailLimit) || 0);
|
|
693
|
+
const effectiveDetailLimit = postActionEnabled ? Number.POSITIVE_INFINITY : detailCountLimit;
|
|
694
|
+
const networkRecorder = effectiveDetailLimit > 0
|
|
695
|
+
? createRecommendDetailNetworkRecorder(client)
|
|
696
|
+
: null;
|
|
697
|
+
const cvAcquisitionState = createCvAcquisitionState({ mode: cvAcquisitionMode });
|
|
698
|
+
const listState = createInfiniteListState({
|
|
699
|
+
domain: "recommend",
|
|
700
|
+
listName: "recommend-candidates"
|
|
701
|
+
});
|
|
702
|
+
const viewportGuard = createViewportRunGuard({
|
|
703
|
+
client,
|
|
704
|
+
domain: "recommend",
|
|
705
|
+
root: "frame",
|
|
706
|
+
frameOwnerRoot: "frameOwner",
|
|
707
|
+
runControl,
|
|
708
|
+
getRoots: getRecommendRoots
|
|
709
|
+
});
|
|
710
|
+
async function ensureRecommendViewport(rootState, phase) {
|
|
711
|
+
const result = await viewportGuard.ensure(rootState, { phase });
|
|
712
|
+
return result.rootState || rootState;
|
|
713
|
+
}
|
|
714
|
+
const results = [];
|
|
715
|
+
const refreshAttempts = [];
|
|
716
|
+
let refreshRounds = 0;
|
|
717
|
+
let contextRecoveryAttempts = 0;
|
|
718
|
+
let greetCount = 0;
|
|
702
719
|
const candidateRecoveryCounts = new Map();
|
|
703
720
|
let jobSelection = null;
|
|
704
721
|
let pageScopeSelection = null;
|
|
705
722
|
let filterResult = null;
|
|
723
|
+
let rootState = null;
|
|
706
724
|
let cardNodeIds = [];
|
|
707
725
|
let listEndReason = "";
|
|
708
726
|
let lastHumanEvent = null;
|
|
709
727
|
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
710
|
-
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
711
|
-
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
712
|
-
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
713
|
-
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
714
|
-
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
715
|
-
validateViewportPoint: true
|
|
728
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
729
|
+
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
730
|
+
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
731
|
+
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
732
|
+
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
733
|
+
validateViewportPoint: true
|
|
716
734
|
}));
|
|
717
735
|
|
|
718
736
|
function recordHumanEvent(event = null) {
|
|
@@ -749,13 +767,13 @@ export async function runRecommendWorkflow({
|
|
|
749
767
|
card_count: cardNodeIds.length,
|
|
750
768
|
target_count: targetPassCount,
|
|
751
769
|
target_count_semantics: "passed_candidates",
|
|
752
|
-
...counts,
|
|
753
|
-
screening_mode: normalizedScreeningMode,
|
|
754
|
-
unique_seen: listSnapshot.seen_count,
|
|
755
|
-
scroll_count: listSnapshot.scroll_count,
|
|
756
|
-
refresh_rounds: refreshRounds,
|
|
757
|
-
refresh_attempts: refreshAttempts.length,
|
|
758
|
-
context_recoveries: contextRecoveryAttempts,
|
|
770
|
+
...counts,
|
|
771
|
+
screening_mode: normalizedScreeningMode,
|
|
772
|
+
unique_seen: listSnapshot.seen_count,
|
|
773
|
+
scroll_count: listSnapshot.scroll_count,
|
|
774
|
+
refresh_rounds: refreshRounds,
|
|
775
|
+
refresh_attempts: refreshAttempts.length,
|
|
776
|
+
context_recoveries: contextRecoveryAttempts,
|
|
759
777
|
list_end_reason: listEndReason || null,
|
|
760
778
|
viewport_checks: viewportGuard.getStats().checks,
|
|
761
779
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
@@ -768,280 +786,305 @@ export async function runRecommendWorkflow({
|
|
|
768
786
|
...extra
|
|
769
787
|
});
|
|
770
788
|
}
|
|
771
|
-
|
|
789
|
+
|
|
772
790
|
function checkpointInProgressCandidate({
|
|
773
791
|
index = results.length,
|
|
774
792
|
candidateKey = "",
|
|
775
|
-
cardNodeId = null,
|
|
776
|
-
detailStep = "",
|
|
777
|
-
error = null
|
|
778
|
-
} = {}) {
|
|
779
|
-
runControl.checkpoint({
|
|
780
|
-
in_progress_candidate: {
|
|
781
|
-
index,
|
|
782
|
-
key: candidateKey,
|
|
783
|
-
card_node_id: cardNodeId,
|
|
784
|
-
detail_step: detailStep || null,
|
|
785
|
-
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
786
|
-
error: compactError(error, "RECOMMEND_IN_PROGRESS_ERROR")
|
|
787
|
-
},
|
|
793
|
+
cardNodeId = null,
|
|
794
|
+
detailStep = "",
|
|
795
|
+
error = null
|
|
796
|
+
} = {}) {
|
|
797
|
+
runControl.checkpoint({
|
|
798
|
+
in_progress_candidate: {
|
|
799
|
+
index,
|
|
800
|
+
key: candidateKey,
|
|
801
|
+
card_node_id: cardNodeId,
|
|
802
|
+
detail_step: detailStep || null,
|
|
803
|
+
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
804
|
+
error: compactError(error, "RECOMMEND_IN_PROGRESS_ERROR")
|
|
805
|
+
},
|
|
788
806
|
candidate_list: compactInfiniteListState(listState)
|
|
789
807
|
});
|
|
790
808
|
}
|
|
791
809
|
|
|
810
|
+
async function closeRecommendBlockingPanelsForRun(phase = "cleanup") {
|
|
811
|
+
const result = await closeRecommendBlockingPanels(client, {
|
|
812
|
+
attemptsLimit: 2,
|
|
813
|
+
rootState
|
|
814
|
+
});
|
|
815
|
+
if (!result?.closed) {
|
|
816
|
+
throw createRecommendBlockingPanelCloseFailureError(result, phase);
|
|
817
|
+
}
|
|
818
|
+
return result;
|
|
819
|
+
}
|
|
820
|
+
|
|
792
821
|
async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
|
|
793
|
-
forceRecentNotView = true
|
|
794
|
-
} = {}) {
|
|
795
|
-
await runControl.waitIfPaused();
|
|
796
|
-
runControl.throwIfCanceled();
|
|
797
|
-
const started = Date.now();
|
|
798
|
-
runControl.setPhase("recommend:recover-context");
|
|
799
|
-
contextRecoveryAttempts += 1;
|
|
822
|
+
forceRecentNotView = true
|
|
823
|
+
} = {}) {
|
|
824
|
+
await runControl.waitIfPaused();
|
|
825
|
+
runControl.throwIfCanceled();
|
|
826
|
+
const started = Date.now();
|
|
827
|
+
runControl.setPhase("recommend:recover-context");
|
|
828
|
+
contextRecoveryAttempts += 1;
|
|
800
829
|
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
801
830
|
rootState,
|
|
802
831
|
jobLabel,
|
|
803
|
-
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
804
|
-
fallbackPageScope: normalizedFallbackPageScope,
|
|
805
|
-
filter: normalizedFilter,
|
|
806
|
-
preferEndRefreshButton: false,
|
|
807
|
-
forceNavigate: true,
|
|
808
|
-
targetUrl: targetUrl || RECOMMEND_TARGET_URL,
|
|
809
|
-
forceRecentNotView,
|
|
810
|
-
cardTimeoutMs,
|
|
832
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
833
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
834
|
+
filter: normalizedFilter,
|
|
835
|
+
preferEndRefreshButton: false,
|
|
836
|
+
forceNavigate: true,
|
|
837
|
+
targetUrl: targetUrl || RECOMMEND_TARGET_URL,
|
|
838
|
+
forceRecentNotView,
|
|
839
|
+
cardTimeoutMs,
|
|
811
840
|
buttonSettleMs: refreshButtonSettleMs,
|
|
812
841
|
reloadSettleMs: refreshReloadSettleMs
|
|
813
842
|
});
|
|
843
|
+
let blockingPanelClose = null;
|
|
844
|
+
if (refreshResult.ok) {
|
|
845
|
+
blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
846
|
+
attemptsLimit: 2,
|
|
847
|
+
rootState: refreshResult.root_state || rootState
|
|
848
|
+
});
|
|
849
|
+
}
|
|
814
850
|
const compactRefresh = {
|
|
815
851
|
...compactRefreshAttempt(refreshResult),
|
|
816
852
|
context_recovery: true,
|
|
817
853
|
recovery_reason: reason,
|
|
818
854
|
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
855
|
+
account_rights_panel_close: compactCloseResult(blockingPanelClose),
|
|
819
856
|
elapsed_ms: Date.now() - started
|
|
820
857
|
};
|
|
821
|
-
refreshAttempts.push(compactRefresh);
|
|
822
|
-
runControl.checkpoint({
|
|
823
|
-
context_recovery: {
|
|
824
|
-
attempt: contextRecoveryAttempts,
|
|
825
|
-
reason,
|
|
826
|
-
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
827
|
-
refresh: compactRefresh,
|
|
828
|
-
counters: countRecommendResultStatuses(results, { greetCount })
|
|
829
|
-
},
|
|
830
|
-
candidate_list: compactInfiniteListState(listState)
|
|
831
|
-
});
|
|
832
|
-
if (!refreshResult.ok) {
|
|
833
|
-
updateRecommendProgress({
|
|
834
|
-
refresh_method: refreshResult.method || null,
|
|
835
|
-
refresh_forced_recent_not_view: forceRecentNotView,
|
|
836
|
-
recovery_reason: reason
|
|
858
|
+
refreshAttempts.push(compactRefresh);
|
|
859
|
+
runControl.checkpoint({
|
|
860
|
+
context_recovery: {
|
|
861
|
+
attempt: contextRecoveryAttempts,
|
|
862
|
+
reason,
|
|
863
|
+
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
864
|
+
refresh: compactRefresh,
|
|
865
|
+
counters: countRecommendResultStatuses(results, { greetCount })
|
|
866
|
+
},
|
|
867
|
+
candidate_list: compactInfiniteListState(listState)
|
|
868
|
+
});
|
|
869
|
+
if (!refreshResult.ok) {
|
|
870
|
+
updateRecommendProgress({
|
|
871
|
+
refresh_method: refreshResult.method || null,
|
|
872
|
+
refresh_forced_recent_not_view: forceRecentNotView,
|
|
873
|
+
recovery_reason: reason
|
|
837
874
|
});
|
|
838
875
|
throw new Error(`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`);
|
|
839
876
|
}
|
|
877
|
+
if (!blockingPanelClose?.closed) {
|
|
878
|
+
const panelError = createRecommendBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
|
|
879
|
+
panelError.refresh_attempt = compactRefresh;
|
|
880
|
+
throw panelError;
|
|
881
|
+
}
|
|
840
882
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
841
|
-
rootState = await ensureRecommendViewport(rootState, "recover_after");
|
|
842
|
-
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
843
|
-
timeoutMs: cardTimeoutMs,
|
|
844
|
-
intervalMs: 300
|
|
845
|
-
});
|
|
846
|
-
resetInfiniteListForRefreshRound(listState, {
|
|
847
|
-
reason: `context_recovery:${reason}`,
|
|
848
|
-
round: contextRecoveryAttempts,
|
|
849
|
-
method: refreshResult.method,
|
|
850
|
-
metadata: {
|
|
851
|
-
card_count: cardNodeIds.length,
|
|
852
|
-
forced_recent_not_view: forceRecentNotView,
|
|
853
|
-
counters: countRecommendResultStatuses(results, { greetCount })
|
|
854
|
-
}
|
|
855
|
-
});
|
|
856
|
-
listEndReason = "";
|
|
857
|
-
updateRecommendProgress({
|
|
858
|
-
card_count: cardNodeIds.length,
|
|
859
|
-
refresh_method: refreshResult.method || null,
|
|
860
|
-
refresh_forced_recent_not_view: forceRecentNotView,
|
|
861
|
-
recovery_reason: reason
|
|
862
|
-
});
|
|
863
|
-
return refreshResult;
|
|
864
|
-
}
|
|
865
|
-
|
|
883
|
+
rootState = await ensureRecommendViewport(rootState, "recover_after");
|
|
884
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
885
|
+
timeoutMs: cardTimeoutMs,
|
|
886
|
+
intervalMs: 300
|
|
887
|
+
});
|
|
888
|
+
resetInfiniteListForRefreshRound(listState, {
|
|
889
|
+
reason: `context_recovery:${reason}`,
|
|
890
|
+
round: contextRecoveryAttempts,
|
|
891
|
+
method: refreshResult.method,
|
|
892
|
+
metadata: {
|
|
893
|
+
card_count: cardNodeIds.length,
|
|
894
|
+
forced_recent_not_view: forceRecentNotView,
|
|
895
|
+
counters: countRecommendResultStatuses(results, { greetCount })
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
listEndReason = "";
|
|
899
|
+
updateRecommendProgress({
|
|
900
|
+
card_count: cardNodeIds.length,
|
|
901
|
+
refresh_method: refreshResult.method || null,
|
|
902
|
+
refresh_forced_recent_not_view: forceRecentNotView,
|
|
903
|
+
recovery_reason: reason
|
|
904
|
+
});
|
|
905
|
+
return refreshResult;
|
|
906
|
+
}
|
|
907
|
+
|
|
866
908
|
runControl.setPhase("recommend:cleanup");
|
|
867
909
|
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
910
|
+
await closeRecommendBlockingPanelsForRun("cleanup");
|
|
868
911
|
|
|
869
912
|
await runControl.waitIfPaused();
|
|
870
913
|
runControl.throwIfCanceled();
|
|
871
914
|
runControl.setPhase("recommend:roots");
|
|
872
|
-
let rootState = await getRecommendRoots(client);
|
|
873
|
-
rootState = await ensureRecommendViewport(rootState, "roots");
|
|
874
|
-
runControl.checkpoint({
|
|
875
|
-
iframe_selector: rootState.iframe.selector,
|
|
876
|
-
iframe_document_node_id: rootState.iframe.documentNodeId
|
|
877
|
-
});
|
|
878
|
-
|
|
879
|
-
if (jobLabel) {
|
|
880
|
-
await runControl.waitIfPaused();
|
|
881
|
-
runControl.throwIfCanceled();
|
|
882
|
-
runControl.setPhase("recommend:job");
|
|
883
|
-
jobSelection = await selectRecommendJob(client, rootState.iframe.documentNodeId, {
|
|
884
|
-
jobLabel,
|
|
885
|
-
settleMs: cardTimeoutMs > 45000 ? 12000 : 6000
|
|
886
|
-
});
|
|
887
|
-
if (!jobSelection.selected) {
|
|
888
|
-
throw new Error(`Requested recommend job was not selected: ${jobSelection.reason}`);
|
|
889
|
-
}
|
|
890
|
-
rootState = await getRecommendRoots(client);
|
|
891
|
-
rootState = await ensureRecommendViewport(rootState, "job");
|
|
892
|
-
runControl.checkpoint({
|
|
893
|
-
job_selection: compactJobSelection(jobSelection)
|
|
894
|
-
});
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
await runControl.waitIfPaused();
|
|
898
|
-
runControl.throwIfCanceled();
|
|
899
|
-
runControl.setPhase("recommend:page-scope");
|
|
900
|
-
pageScopeSelection = await selectRecommendPageScope(client, rootState.iframe.documentNodeId, {
|
|
901
|
-
pageScope: requestedPageScope,
|
|
902
|
-
fallbackScope: normalizedFallbackPageScope,
|
|
903
|
-
settleMs: cardTimeoutMs > 45000 ? 3000 : 1200,
|
|
904
|
-
timeoutMs: Math.min(Math.max(cardTimeoutMs, 10000), 60000)
|
|
905
|
-
});
|
|
906
|
-
if (!pageScopeSelection.selected) {
|
|
907
|
-
throw new Error(`Recommend page scope was not selected: ${pageScopeSelection.reason || pageScopeSelection.effective_scope || requestedPageScope}`);
|
|
908
|
-
}
|
|
909
915
|
rootState = await getRecommendRoots(client);
|
|
910
|
-
rootState = await ensureRecommendViewport(rootState, "
|
|
911
|
-
runControl.checkpoint({
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
runControl.
|
|
918
|
-
runControl.
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
);
|
|
924
|
-
if (!
|
|
925
|
-
throw new Error(
|
|
926
|
-
}
|
|
927
|
-
rootState = await getRecommendRoots(client);
|
|
928
|
-
rootState = await ensureRecommendViewport(rootState, "
|
|
929
|
-
runControl.checkpoint({
|
|
930
|
-
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
await runControl.waitIfPaused();
|
|
935
|
-
runControl.throwIfCanceled();
|
|
936
|
-
runControl.setPhase("recommend:
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
await runControl.waitIfPaused();
|
|
954
|
-
runControl.throwIfCanceled();
|
|
955
|
-
runControl.setPhase("recommend:
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
916
|
+
rootState = await ensureRecommendViewport(rootState, "roots");
|
|
917
|
+
runControl.checkpoint({
|
|
918
|
+
iframe_selector: rootState.iframe.selector,
|
|
919
|
+
iframe_document_node_id: rootState.iframe.documentNodeId
|
|
920
|
+
});
|
|
921
|
+
|
|
922
|
+
if (jobLabel) {
|
|
923
|
+
await runControl.waitIfPaused();
|
|
924
|
+
runControl.throwIfCanceled();
|
|
925
|
+
runControl.setPhase("recommend:job");
|
|
926
|
+
jobSelection = await selectRecommendJob(client, rootState.iframe.documentNodeId, {
|
|
927
|
+
jobLabel,
|
|
928
|
+
settleMs: cardTimeoutMs > 45000 ? 12000 : 6000
|
|
929
|
+
});
|
|
930
|
+
if (!jobSelection.selected) {
|
|
931
|
+
throw new Error(`Requested recommend job was not selected: ${jobSelection.reason}`);
|
|
932
|
+
}
|
|
933
|
+
rootState = await getRecommendRoots(client);
|
|
934
|
+
rootState = await ensureRecommendViewport(rootState, "job");
|
|
935
|
+
runControl.checkpoint({
|
|
936
|
+
job_selection: compactJobSelection(jobSelection)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
await runControl.waitIfPaused();
|
|
941
|
+
runControl.throwIfCanceled();
|
|
942
|
+
runControl.setPhase("recommend:page-scope");
|
|
943
|
+
pageScopeSelection = await selectRecommendPageScope(client, rootState.iframe.documentNodeId, {
|
|
944
|
+
pageScope: requestedPageScope,
|
|
945
|
+
fallbackScope: normalizedFallbackPageScope,
|
|
946
|
+
settleMs: cardTimeoutMs > 45000 ? 3000 : 1200,
|
|
947
|
+
timeoutMs: Math.min(Math.max(cardTimeoutMs, 10000), 60000)
|
|
948
|
+
});
|
|
949
|
+
if (!pageScopeSelection.selected) {
|
|
950
|
+
throw new Error(`Recommend page scope was not selected: ${pageScopeSelection.reason || pageScopeSelection.effective_scope || requestedPageScope}`);
|
|
951
|
+
}
|
|
952
|
+
rootState = await getRecommendRoots(client);
|
|
953
|
+
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
954
|
+
runControl.checkpoint({
|
|
955
|
+
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
if (normalizedFilter.enabled) {
|
|
959
|
+
await runControl.waitIfPaused();
|
|
960
|
+
runControl.throwIfCanceled();
|
|
961
|
+
runControl.setPhase("recommend:filter");
|
|
962
|
+
filterResult = await selectAndConfirmFirstSafeFilter(
|
|
963
|
+
client,
|
|
964
|
+
rootState.iframe.documentNodeId,
|
|
965
|
+
buildRecommendFilterSelectionOptions(normalizedFilter)
|
|
966
|
+
);
|
|
967
|
+
if (!filterResult.confirmed) {
|
|
968
|
+
throw new Error("Recommend run filter selection was not confirmed");
|
|
969
|
+
}
|
|
970
|
+
rootState = await getRecommendRoots(client);
|
|
971
|
+
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
972
|
+
runControl.checkpoint({
|
|
973
|
+
filter: compactFilterResult(filterResult)
|
|
974
|
+
});
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
await runControl.waitIfPaused();
|
|
978
|
+
runControl.throwIfCanceled();
|
|
979
|
+
runControl.setPhase("recommend:cards");
|
|
980
|
+
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
981
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
982
|
+
timeoutMs: cardTimeoutMs,
|
|
983
|
+
intervalMs: 300
|
|
984
|
+
});
|
|
985
|
+
if (!cardNodeIds.length) {
|
|
986
|
+
throw new Error("No recommend candidate cards found for run service");
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
updateRecommendProgress({
|
|
990
|
+
list_end_reason: null
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
while (countPassedResults(results) < targetPassCount) {
|
|
994
|
+
const candidateStarted = Date.now();
|
|
995
|
+
const timings = {};
|
|
996
|
+
await runControl.waitIfPaused();
|
|
997
|
+
runControl.throwIfCanceled();
|
|
998
|
+
runControl.setPhase("recommend:candidate");
|
|
999
|
+
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
1000
|
+
|
|
1001
|
+
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
1002
|
+
client,
|
|
1003
|
+
state: listState,
|
|
1004
|
+
maxScrolls: listMaxScrolls,
|
|
962
1005
|
stableSignatureLimit: listStableSignatureLimit,
|
|
963
1006
|
wheelDeltaY: listWheelDeltaY,
|
|
964
1007
|
settleMs: listSettleMs,
|
|
965
1008
|
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
966
1009
|
fallbackPoint: listFallbackResolver,
|
|
967
|
-
findNodeIds: async () => {
|
|
968
|
-
let currentRootState = await getRecommendRoots(client);
|
|
969
|
-
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
970
|
-
rootState = currentRootState;
|
|
971
|
-
const currentCardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
972
|
-
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
973
|
-
intervalMs: 300
|
|
974
|
-
});
|
|
975
|
-
cardNodeIds = currentCardNodeIds;
|
|
976
|
-
return currentCardNodeIds;
|
|
977
|
-
},
|
|
978
|
-
readCandidate: async (nodeId, { visibleIndex }) => readRecommendCardCandidate(client, nodeId, {
|
|
979
|
-
targetUrl,
|
|
980
|
-
source: "recommend-run-card",
|
|
981
|
-
metadata: {
|
|
982
|
-
run_candidate_index: results.length,
|
|
983
|
-
visible_index: visibleIndex
|
|
984
|
-
}
|
|
985
|
-
}),
|
|
986
|
-
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
987
|
-
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
988
|
-
markerSelectors: RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
989
|
-
refreshSelectors: [RECOMMEND_END_REFRESH_SELECTOR],
|
|
990
|
-
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
991
|
-
maxTextScanNodes: 500
|
|
992
|
-
})
|
|
993
|
-
}));
|
|
994
|
-
if (!nextCandidateResult.ok) {
|
|
995
|
-
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
996
|
-
if (
|
|
997
|
-
(nextCandidateResult.end_reached || isRefreshableListStall(nextCandidateResult.reason))
|
|
998
|
-
&& refreshOnEnd
|
|
999
|
-
&& countPassedResults(results) < targetPassCount
|
|
1000
|
-
&& refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
|
|
1001
|
-
) {
|
|
1002
|
-
await runControl.waitIfPaused();
|
|
1003
|
-
runControl.throwIfCanceled();
|
|
1004
|
-
runControl.setPhase("recommend:refresh");
|
|
1005
|
-
refreshRounds += 1;
|
|
1006
|
-
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1007
|
-
rootState,
|
|
1008
|
-
jobLabel,
|
|
1009
|
-
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1010
|
-
fallbackPageScope: normalizedFallbackPageScope,
|
|
1011
|
-
filter: normalizedFilter,
|
|
1012
|
-
forceRecentNotView: true,
|
|
1013
|
-
cardTimeoutMs,
|
|
1014
|
-
buttonSettleMs: refreshButtonSettleMs,
|
|
1015
|
-
reloadSettleMs: refreshReloadSettleMs
|
|
1016
|
-
});
|
|
1017
|
-
const compactRefresh = compactRefreshAttempt(refreshResult);
|
|
1018
|
-
refreshAttempts.push(compactRefresh);
|
|
1019
|
-
runControl.checkpoint({
|
|
1020
|
-
refresh_round: refreshRounds,
|
|
1021
|
-
refresh: compactRefresh
|
|
1022
|
-
});
|
|
1023
|
-
updateRecommendProgress({
|
|
1024
|
-
card_count: refreshResult.card_count || cardNodeIds.length,
|
|
1025
|
-
refresh_method: refreshResult.method || null,
|
|
1026
|
-
refresh_forced_recent_not_view: true,
|
|
1027
|
-
list_end_reason: listEndReason
|
|
1028
|
-
});
|
|
1010
|
+
findNodeIds: async () => {
|
|
1011
|
+
let currentRootState = await getRecommendRoots(client);
|
|
1012
|
+
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
1013
|
+
rootState = currentRootState;
|
|
1014
|
+
const currentCardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
1015
|
+
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
1016
|
+
intervalMs: 300
|
|
1017
|
+
});
|
|
1018
|
+
cardNodeIds = currentCardNodeIds;
|
|
1019
|
+
return currentCardNodeIds;
|
|
1020
|
+
},
|
|
1021
|
+
readCandidate: async (nodeId, { visibleIndex }) => readRecommendCardCandidate(client, nodeId, {
|
|
1022
|
+
targetUrl,
|
|
1023
|
+
source: "recommend-run-card",
|
|
1024
|
+
metadata: {
|
|
1025
|
+
run_candidate_index: results.length,
|
|
1026
|
+
visible_index: visibleIndex
|
|
1027
|
+
}
|
|
1028
|
+
}),
|
|
1029
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
1030
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
1031
|
+
markerSelectors: RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
1032
|
+
refreshSelectors: [RECOMMEND_END_REFRESH_SELECTOR],
|
|
1033
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
1034
|
+
maxTextScanNodes: 500
|
|
1035
|
+
})
|
|
1036
|
+
}));
|
|
1037
|
+
if (!nextCandidateResult.ok) {
|
|
1038
|
+
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
1039
|
+
if (
|
|
1040
|
+
(nextCandidateResult.end_reached || isRefreshableListStall(nextCandidateResult.reason))
|
|
1041
|
+
&& refreshOnEnd
|
|
1042
|
+
&& countPassedResults(results) < targetPassCount
|
|
1043
|
+
&& refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
|
|
1044
|
+
) {
|
|
1045
|
+
await runControl.waitIfPaused();
|
|
1046
|
+
runControl.throwIfCanceled();
|
|
1047
|
+
runControl.setPhase("recommend:refresh");
|
|
1048
|
+
refreshRounds += 1;
|
|
1049
|
+
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1050
|
+
rootState,
|
|
1051
|
+
jobLabel,
|
|
1052
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1053
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
1054
|
+
filter: normalizedFilter,
|
|
1055
|
+
forceRecentNotView: true,
|
|
1056
|
+
cardTimeoutMs,
|
|
1057
|
+
buttonSettleMs: refreshButtonSettleMs,
|
|
1058
|
+
reloadSettleMs: refreshReloadSettleMs
|
|
1059
|
+
});
|
|
1060
|
+
const compactRefresh = compactRefreshAttempt(refreshResult);
|
|
1061
|
+
refreshAttempts.push(compactRefresh);
|
|
1062
|
+
runControl.checkpoint({
|
|
1063
|
+
refresh_round: refreshRounds,
|
|
1064
|
+
refresh: compactRefresh
|
|
1065
|
+
});
|
|
1066
|
+
updateRecommendProgress({
|
|
1067
|
+
card_count: refreshResult.card_count || cardNodeIds.length,
|
|
1068
|
+
refresh_method: refreshResult.method || null,
|
|
1069
|
+
refresh_forced_recent_not_view: true,
|
|
1070
|
+
list_end_reason: listEndReason
|
|
1071
|
+
});
|
|
1029
1072
|
if (refreshResult.ok) {
|
|
1030
1073
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1031
1074
|
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
1032
|
-
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1033
|
-
timeoutMs: cardTimeoutMs,
|
|
1034
|
-
intervalMs: 300
|
|
1035
|
-
});
|
|
1036
|
-
resetInfiniteListForRefreshRound(listState, {
|
|
1037
|
-
reason: listEndReason,
|
|
1038
|
-
round: refreshRounds,
|
|
1039
|
-
method: refreshResult.method,
|
|
1040
|
-
metadata: {
|
|
1041
|
-
card_count: cardNodeIds.length,
|
|
1042
|
-
forced_recent_not_view: true
|
|
1043
|
-
}
|
|
1044
|
-
});
|
|
1075
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1076
|
+
timeoutMs: cardTimeoutMs,
|
|
1077
|
+
intervalMs: 300
|
|
1078
|
+
});
|
|
1079
|
+
resetInfiniteListForRefreshRound(listState, {
|
|
1080
|
+
reason: listEndReason,
|
|
1081
|
+
round: refreshRounds,
|
|
1082
|
+
method: refreshResult.method,
|
|
1083
|
+
metadata: {
|
|
1084
|
+
card_count: cardNodeIds.length,
|
|
1085
|
+
forced_recent_not_view: true
|
|
1086
|
+
}
|
|
1087
|
+
});
|
|
1045
1088
|
listEndReason = "";
|
|
1046
1089
|
continue;
|
|
1047
1090
|
}
|
|
@@ -1053,240 +1096,262 @@ export async function runRecommendWorkflow({
|
|
|
1053
1096
|
}
|
|
1054
1097
|
break;
|
|
1055
1098
|
}
|
|
1056
|
-
|
|
1057
|
-
const index = results.length;
|
|
1058
|
-
let cardNodeId = nextCandidateResult.item.node_id;
|
|
1059
|
-
const candidateKey = nextCandidateResult.item.key;
|
|
1060
|
-
let cardCandidate = nextCandidateResult.item.candidate;
|
|
1061
|
-
|
|
1062
|
-
let screeningCandidate = cardCandidate;
|
|
1063
|
-
let detailResult = null;
|
|
1064
|
-
let recoverableDetailError = null;
|
|
1065
|
-
let detailStep = "not_started";
|
|
1066
|
-
if (index < effectiveDetailLimit) {
|
|
1067
|
-
try {
|
|
1068
|
-
await runControl.waitIfPaused();
|
|
1069
|
-
runControl.throwIfCanceled();
|
|
1099
|
+
|
|
1100
|
+
const index = results.length;
|
|
1101
|
+
let cardNodeId = nextCandidateResult.item.node_id;
|
|
1102
|
+
const candidateKey = nextCandidateResult.item.key;
|
|
1103
|
+
let cardCandidate = nextCandidateResult.item.candidate;
|
|
1104
|
+
|
|
1105
|
+
let screeningCandidate = cardCandidate;
|
|
1106
|
+
let detailResult = null;
|
|
1107
|
+
let recoverableDetailError = null;
|
|
1108
|
+
let detailStep = "not_started";
|
|
1109
|
+
if (index < effectiveDetailLimit) {
|
|
1110
|
+
try {
|
|
1111
|
+
await runControl.waitIfPaused();
|
|
1112
|
+
runControl.throwIfCanceled();
|
|
1070
1113
|
runControl.setPhase("recommend:detail");
|
|
1071
1114
|
detailStep = "ensure_viewport";
|
|
1072
1115
|
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
1116
|
+
const blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1117
|
+
attemptsLimit: 2,
|
|
1118
|
+
rootState
|
|
1119
|
+
});
|
|
1120
|
+
if (!blockingPanelClose?.closed) {
|
|
1121
|
+
const panelError = createRecommendBlockingPanelCloseFailureError(
|
|
1122
|
+
blockingPanelClose,
|
|
1123
|
+
"before_detail_open"
|
|
1124
|
+
);
|
|
1125
|
+
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1126
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error: panelError });
|
|
1127
|
+
await recoverAndReapplyRecommendContext("account_rights_panel_before_detail", panelError, {
|
|
1128
|
+
forceRecentNotView: true
|
|
1129
|
+
});
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (blockingPanelClose.already_closed === false) {
|
|
1133
|
+
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1134
|
+
}
|
|
1073
1135
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1074
1136
|
detailStep = "open_detail";
|
|
1075
1137
|
networkRecorder.clear();
|
|
1076
1138
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1077
1139
|
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
1078
|
-
cardNodeId,
|
|
1079
|
-
candidateKey,
|
|
1080
|
-
cardCandidate,
|
|
1081
|
-
rootState,
|
|
1082
|
-
targetUrl,
|
|
1083
|
-
retryTimeoutMs: 8000,
|
|
1084
|
-
maxAttempts: 3
|
|
1085
|
-
});
|
|
1086
|
-
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
1087
|
-
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
1088
|
-
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1089
|
-
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1090
|
-
screeningCandidate = cardCandidate;
|
|
1091
|
-
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1092
|
-
detailStep = "wait_network";
|
|
1093
|
-
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1094
|
-
waitForRecommendDetailNetworkEvents,
|
|
1095
|
-
networkRecorder,
|
|
1096
|
-
{
|
|
1097
|
-
waitPlan,
|
|
1098
|
-
minCount: 1,
|
|
1099
|
-
requireLoaded: true,
|
|
1100
|
-
intervalMs: 120
|
|
1101
|
-
}
|
|
1102
|
-
));
|
|
1103
|
-
if (networkWait?.elapsed_ms != null) {
|
|
1104
|
-
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
1105
|
-
}
|
|
1106
|
-
detailStep = "extract_detail";
|
|
1107
|
-
detailResult = await extractRecommendDetailCandidate(client, {
|
|
1108
|
-
cardCandidate,
|
|
1109
|
-
cardNodeId,
|
|
1110
|
-
detailState: openedDetail.detail_state,
|
|
1111
|
-
networkEvents: networkRecorder.events,
|
|
1112
|
-
targetUrl,
|
|
1113
|
-
closeDetail: false,
|
|
1114
|
-
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
1115
|
-
networkParseIntervalMs: 250
|
|
1116
|
-
});
|
|
1117
|
-
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1118
|
-
|
|
1119
|
-
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1120
|
-
let source = "network";
|
|
1121
|
-
let imageEvidence = null;
|
|
1122
|
-
let captureTarget = null;
|
|
1123
|
-
let captureTargetWait = null;
|
|
1124
|
-
if (parsedNetworkProfileCount > 0) {
|
|
1125
|
-
recordCvNetworkHit(cvAcquisitionState, {
|
|
1126
|
-
parsedNetworkProfileCount,
|
|
1127
|
-
waitResult: networkWait
|
|
1128
|
-
});
|
|
1129
|
-
} else {
|
|
1130
|
-
detailStep = "wait_capture_target";
|
|
1131
|
-
captureTargetWait = await waitForCvCaptureTarget(client, openedDetail.detail_state, {
|
|
1132
|
-
domain: "recommend",
|
|
1133
|
-
timeoutMs: 6000,
|
|
1134
|
-
intervalMs: 250
|
|
1135
|
-
});
|
|
1136
|
-
captureTarget = captureTargetWait.target || null;
|
|
1137
|
-
const captureNodeId = captureTarget?.node_id || null;
|
|
1138
|
-
if (captureNodeId) {
|
|
1139
|
-
const imageEvidencePath = imageEvidenceFilePath({
|
|
1140
|
-
imageOutputDir,
|
|
1141
|
-
domain: "recommend",
|
|
1142
|
-
runId: runControl?.runId,
|
|
1143
|
-
index,
|
|
1144
|
-
extension: "jpg"
|
|
1145
|
-
});
|
|
1146
|
-
try {
|
|
1147
|
-
detailStep = "capture_image";
|
|
1148
|
-
imageEvidence = await measureTiming(timings, "screenshot_capture_ms", () => captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
1149
|
-
filePath: imageEvidencePath,
|
|
1150
|
-
format: "jpeg",
|
|
1151
|
-
quality: 72,
|
|
1152
|
-
optimize: true,
|
|
1153
|
-
resizeMaxWidth: 1100,
|
|
1154
|
-
captureViewport: false,
|
|
1155
|
-
padding: 0,
|
|
1140
|
+
cardNodeId,
|
|
1141
|
+
candidateKey,
|
|
1142
|
+
cardCandidate,
|
|
1143
|
+
rootState,
|
|
1144
|
+
targetUrl,
|
|
1145
|
+
retryTimeoutMs: 8000,
|
|
1146
|
+
maxAttempts: 3
|
|
1147
|
+
});
|
|
1148
|
+
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
1149
|
+
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
1150
|
+
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1151
|
+
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1152
|
+
screeningCandidate = cardCandidate;
|
|
1153
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1154
|
+
detailStep = "wait_network";
|
|
1155
|
+
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1156
|
+
waitForRecommendDetailNetworkEvents,
|
|
1157
|
+
networkRecorder,
|
|
1158
|
+
{
|
|
1159
|
+
waitPlan,
|
|
1160
|
+
minCount: 1,
|
|
1161
|
+
requireLoaded: true,
|
|
1162
|
+
intervalMs: 120
|
|
1163
|
+
}
|
|
1164
|
+
));
|
|
1165
|
+
if (networkWait?.elapsed_ms != null) {
|
|
1166
|
+
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
1167
|
+
}
|
|
1168
|
+
detailStep = "extract_detail";
|
|
1169
|
+
detailResult = await extractRecommendDetailCandidate(client, {
|
|
1170
|
+
cardCandidate,
|
|
1171
|
+
cardNodeId,
|
|
1172
|
+
detailState: openedDetail.detail_state,
|
|
1173
|
+
networkEvents: networkRecorder.events,
|
|
1174
|
+
targetUrl,
|
|
1175
|
+
closeDetail: false,
|
|
1176
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
1177
|
+
networkParseIntervalMs: 250
|
|
1178
|
+
});
|
|
1179
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1180
|
+
|
|
1181
|
+
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1182
|
+
let source = "network";
|
|
1183
|
+
let imageEvidence = null;
|
|
1184
|
+
let captureTarget = null;
|
|
1185
|
+
let captureTargetWait = null;
|
|
1186
|
+
if (parsedNetworkProfileCount > 0) {
|
|
1187
|
+
recordCvNetworkHit(cvAcquisitionState, {
|
|
1188
|
+
parsedNetworkProfileCount,
|
|
1189
|
+
waitResult: networkWait
|
|
1190
|
+
});
|
|
1191
|
+
} else {
|
|
1192
|
+
detailStep = "wait_capture_target";
|
|
1193
|
+
captureTargetWait = await waitForCvCaptureTarget(client, openedDetail.detail_state, {
|
|
1194
|
+
domain: "recommend",
|
|
1195
|
+
timeoutMs: 6000,
|
|
1196
|
+
intervalMs: 250
|
|
1197
|
+
});
|
|
1198
|
+
captureTarget = captureTargetWait.target || null;
|
|
1199
|
+
const captureNodeId = captureTarget?.node_id || null;
|
|
1200
|
+
if (captureNodeId) {
|
|
1201
|
+
const imageEvidencePath = imageEvidenceFilePath({
|
|
1202
|
+
imageOutputDir,
|
|
1203
|
+
domain: "recommend",
|
|
1204
|
+
runId: runControl?.runId,
|
|
1205
|
+
index,
|
|
1206
|
+
extension: "jpg"
|
|
1207
|
+
});
|
|
1208
|
+
try {
|
|
1209
|
+
detailStep = "capture_image";
|
|
1210
|
+
imageEvidence = await measureTiming(timings, "screenshot_capture_ms", () => captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
1211
|
+
filePath: imageEvidencePath,
|
|
1212
|
+
format: "jpeg",
|
|
1213
|
+
quality: 72,
|
|
1214
|
+
optimize: true,
|
|
1215
|
+
resizeMaxWidth: 1100,
|
|
1216
|
+
captureViewport: false,
|
|
1217
|
+
padding: 0,
|
|
1156
1218
|
maxScreenshots: maxImagePages,
|
|
1157
1219
|
wheelDeltaY: imageWheelDeltaY,
|
|
1158
1220
|
settleMs: 350,
|
|
1159
1221
|
scrollMethod: "dom-anchor-fallback-input",
|
|
1160
1222
|
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1161
1223
|
stepTimeoutMs: 45000,
|
|
1162
|
-
totalTimeoutMs: 90000,
|
|
1163
|
-
duplicateStopCount: 1,
|
|
1164
|
-
skipDuplicateScreenshots: true,
|
|
1165
|
-
composeForLlm: true,
|
|
1166
|
-
llmPagesPerImage: 3,
|
|
1167
|
-
llmResizeMaxWidth: 1100,
|
|
1168
|
-
llmQuality: 72,
|
|
1169
|
-
metadata: {
|
|
1170
|
-
domain: "recommend",
|
|
1171
|
-
capture_mode: "scroll_sequence",
|
|
1172
|
-
acquisition_reason: "network_miss_image_fallback",
|
|
1173
|
-
run_candidate_index: index,
|
|
1174
|
-
candidate_key: candidateKey,
|
|
1175
|
-
capture_target: captureTarget,
|
|
1176
|
-
capture_target_wait: captureTargetWait
|
|
1177
|
-
}
|
|
1178
|
-
}));
|
|
1179
|
-
source = "image";
|
|
1180
|
-
} catch (error) {
|
|
1181
|
-
if (!isRecoverableImageCaptureError(error)) throw error;
|
|
1182
|
-
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1183
|
-
if (recoveryCount < 1) {
|
|
1184
|
-
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1224
|
+
totalTimeoutMs: 90000,
|
|
1225
|
+
duplicateStopCount: 1,
|
|
1226
|
+
skipDuplicateScreenshots: true,
|
|
1227
|
+
composeForLlm: true,
|
|
1228
|
+
llmPagesPerImage: 3,
|
|
1229
|
+
llmResizeMaxWidth: 1100,
|
|
1230
|
+
llmQuality: 72,
|
|
1231
|
+
metadata: {
|
|
1232
|
+
domain: "recommend",
|
|
1233
|
+
capture_mode: "scroll_sequence",
|
|
1234
|
+
acquisition_reason: "network_miss_image_fallback",
|
|
1235
|
+
run_candidate_index: index,
|
|
1236
|
+
candidate_key: candidateKey,
|
|
1237
|
+
capture_target: captureTarget,
|
|
1238
|
+
capture_target_wait: captureTargetWait
|
|
1239
|
+
}
|
|
1240
|
+
}));
|
|
1241
|
+
source = "image";
|
|
1242
|
+
} catch (error) {
|
|
1243
|
+
if (!isRecoverableImageCaptureError(error)) throw error;
|
|
1244
|
+
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1245
|
+
if (recoveryCount < 1) {
|
|
1246
|
+
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1185
1247
|
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1186
1248
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1187
1249
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1250
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1188
1251
|
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1189
1252
|
forceRecentNotView: true
|
|
1190
1253
|
});
|
|
1191
|
-
continue;
|
|
1192
|
-
}
|
|
1193
|
-
imageEvidence = createRecoverableImageCaptureEvidence(error, {
|
|
1194
|
-
elapsedMs: timings.screenshot_capture_ms,
|
|
1195
|
-
filePath: imageEvidencePath,
|
|
1196
|
-
extension: "jpg",
|
|
1197
|
-
maxScreenshots: maxImagePages
|
|
1198
|
-
});
|
|
1199
|
-
source = "image_capture_failed";
|
|
1200
|
-
}
|
|
1201
|
-
recordCvImageFallback(cvAcquisitionState, {
|
|
1202
|
-
reason: source === "image_capture_failed"
|
|
1203
|
-
? "network_miss_image_capture_failed"
|
|
1204
|
-
: "network_miss_image_fallback",
|
|
1205
|
-
parsedNetworkProfileCount,
|
|
1206
|
-
waitResult: networkWait,
|
|
1207
|
-
imageEvidence
|
|
1208
|
-
});
|
|
1209
|
-
} else {
|
|
1210
|
-
source = "missing_capture_node";
|
|
1211
|
-
recordCvNetworkMiss(cvAcquisitionState, {
|
|
1212
|
-
reason: "network_miss_no_capture_node",
|
|
1213
|
-
parsedNetworkProfileCount,
|
|
1214
|
-
waitResult: networkWait
|
|
1215
|
-
});
|
|
1216
|
-
}
|
|
1217
|
-
}
|
|
1218
|
-
|
|
1219
|
-
detailResult.image_evidence = imageEvidence;
|
|
1220
|
-
detailResult.cv_acquisition = {
|
|
1221
|
-
source,
|
|
1222
|
-
mode_after: compactCvAcquisitionState(cvAcquisitionState).mode,
|
|
1223
|
-
wait_plan: waitPlan,
|
|
1224
|
-
network_wait: networkWait,
|
|
1225
|
-
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
1226
|
-
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1227
|
-
capture_target: captureTarget || null,
|
|
1228
|
-
capture_target_wait: captureTargetWait
|
|
1229
|
-
};
|
|
1230
|
-
screeningCandidate = detailResult.candidate;
|
|
1231
|
-
} catch (error) {
|
|
1232
|
-
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
1233
|
-
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1234
|
-
if (recoveryCount < 1) {
|
|
1254
|
+
continue;
|
|
1255
|
+
}
|
|
1256
|
+
imageEvidence = createRecoverableImageCaptureEvidence(error, {
|
|
1257
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
1258
|
+
filePath: imageEvidencePath,
|
|
1259
|
+
extension: "jpg",
|
|
1260
|
+
maxScreenshots: maxImagePages
|
|
1261
|
+
});
|
|
1262
|
+
source = "image_capture_failed";
|
|
1263
|
+
}
|
|
1264
|
+
recordCvImageFallback(cvAcquisitionState, {
|
|
1265
|
+
reason: source === "image_capture_failed"
|
|
1266
|
+
? "network_miss_image_capture_failed"
|
|
1267
|
+
: "network_miss_image_fallback",
|
|
1268
|
+
parsedNetworkProfileCount,
|
|
1269
|
+
waitResult: networkWait,
|
|
1270
|
+
imageEvidence
|
|
1271
|
+
});
|
|
1272
|
+
} else {
|
|
1273
|
+
source = "missing_capture_node";
|
|
1274
|
+
recordCvNetworkMiss(cvAcquisitionState, {
|
|
1275
|
+
reason: "network_miss_no_capture_node",
|
|
1276
|
+
parsedNetworkProfileCount,
|
|
1277
|
+
waitResult: networkWait
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
detailResult.image_evidence = imageEvidence;
|
|
1283
|
+
detailResult.cv_acquisition = {
|
|
1284
|
+
source,
|
|
1285
|
+
mode_after: compactCvAcquisitionState(cvAcquisitionState).mode,
|
|
1286
|
+
wait_plan: waitPlan,
|
|
1287
|
+
network_wait: networkWait,
|
|
1288
|
+
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
1289
|
+
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1290
|
+
capture_target: captureTarget || null,
|
|
1291
|
+
capture_target_wait: captureTargetWait
|
|
1292
|
+
};
|
|
1293
|
+
screeningCandidate = detailResult.candidate;
|
|
1294
|
+
} catch (error) {
|
|
1295
|
+
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
1296
|
+
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1297
|
+
if (recoveryCount < 1) {
|
|
1235
1298
|
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1236
1299
|
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1237
1300
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1238
1301
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1302
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1239
1303
|
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1240
1304
|
forceRecentNotView: true
|
|
1241
1305
|
});
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1306
|
+
continue;
|
|
1307
|
+
}
|
|
1244
1308
|
recoverableDetailError = error;
|
|
1245
1309
|
detailResult = null;
|
|
1246
1310
|
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1247
1311
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1312
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1248
1313
|
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
await runControl.waitIfPaused();
|
|
1252
|
-
runControl.throwIfCanceled();
|
|
1253
|
-
runControl.setPhase("recommend:screening");
|
|
1254
|
-
let llmResult = null;
|
|
1255
|
-
if (useLlmScreening) {
|
|
1256
|
-
if (recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
1257
|
-
llmResult = null;
|
|
1258
|
-
} else if (!llmConfig) {
|
|
1259
|
-
llmResult = createMissingLlmConfigResult();
|
|
1260
|
-
} else {
|
|
1261
|
-
try {
|
|
1262
|
-
const llmTimingKey = detailResult?.image_evidence?.file_paths?.length
|
|
1263
|
-
? "vision_model_ms"
|
|
1264
|
-
: "text_model_ms";
|
|
1265
|
-
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
1266
|
-
candidate: screeningCandidate,
|
|
1267
|
-
criteria,
|
|
1268
|
-
config: llmConfig,
|
|
1269
|
-
timeoutMs: llmTimeoutMs,
|
|
1270
|
-
imageEvidence: detailResult?.image_evidence || null,
|
|
1271
|
-
maxImages: llmImageLimit,
|
|
1272
|
-
imageDetail: llmImageDetail
|
|
1273
|
-
}));
|
|
1274
|
-
} catch (error) {
|
|
1275
|
-
llmResult = createFailedLlmScreeningResult(error);
|
|
1276
|
-
}
|
|
1277
|
-
}
|
|
1278
|
-
if (detailResult) detailResult.llm_result = llmResult;
|
|
1279
|
-
}
|
|
1280
|
-
const screening = recoverableDetailError
|
|
1281
|
-
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
1282
|
-
: detailResult?.image_evidence?.ok === false
|
|
1283
|
-
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
1284
|
-
code: detailResult.image_evidence.error_code,
|
|
1285
|
-
message: detailResult.image_evidence.error
|
|
1286
|
-
})
|
|
1287
|
-
: useLlmScreening
|
|
1288
|
-
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1289
|
-
: screenCandidate(screeningCandidate, { criteria });
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
await runControl.waitIfPaused();
|
|
1317
|
+
runControl.throwIfCanceled();
|
|
1318
|
+
runControl.setPhase("recommend:screening");
|
|
1319
|
+
let llmResult = null;
|
|
1320
|
+
if (useLlmScreening) {
|
|
1321
|
+
if (recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
1322
|
+
llmResult = null;
|
|
1323
|
+
} else if (!llmConfig) {
|
|
1324
|
+
llmResult = createMissingLlmConfigResult();
|
|
1325
|
+
} else {
|
|
1326
|
+
try {
|
|
1327
|
+
const llmTimingKey = detailResult?.image_evidence?.file_paths?.length
|
|
1328
|
+
? "vision_model_ms"
|
|
1329
|
+
: "text_model_ms";
|
|
1330
|
+
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
1331
|
+
candidate: screeningCandidate,
|
|
1332
|
+
criteria,
|
|
1333
|
+
config: llmConfig,
|
|
1334
|
+
timeoutMs: llmTimeoutMs,
|
|
1335
|
+
imageEvidence: detailResult?.image_evidence || null,
|
|
1336
|
+
maxImages: llmImageLimit,
|
|
1337
|
+
imageDetail: llmImageDetail
|
|
1338
|
+
}));
|
|
1339
|
+
} catch (error) {
|
|
1340
|
+
llmResult = createFailedLlmScreeningResult(error);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
1344
|
+
}
|
|
1345
|
+
const screening = recoverableDetailError
|
|
1346
|
+
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
1347
|
+
: detailResult?.image_evidence?.ok === false
|
|
1348
|
+
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
1349
|
+
code: detailResult.image_evidence.error_code,
|
|
1350
|
+
message: detailResult.image_evidence.error
|
|
1351
|
+
})
|
|
1352
|
+
: useLlmScreening
|
|
1353
|
+
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1354
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
1290
1355
|
let actionDiscovery = null;
|
|
1291
1356
|
let postActionResult = null;
|
|
1292
1357
|
let closeFailureError = null;
|
|
@@ -1298,24 +1363,24 @@ export async function runRecommendWorkflow({
|
|
|
1298
1363
|
runControl.setPhase("recommend:post-action");
|
|
1299
1364
|
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1300
1365
|
actionDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
1301
|
-
timeoutMs: actionTimeoutMs,
|
|
1302
|
-
intervalMs: actionIntervalMs,
|
|
1303
|
-
requireAny: true
|
|
1304
|
-
});
|
|
1305
|
-
postActionResult = await runRecommendPostAction({
|
|
1306
|
-
client,
|
|
1307
|
-
screening,
|
|
1308
|
-
actionDiscovery,
|
|
1309
|
-
postAction: normalizedPostAction,
|
|
1310
|
-
greetCount,
|
|
1311
|
-
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1312
|
-
executePostAction,
|
|
1313
|
-
afterClickDelayMs: actionAfterClickDelayMs
|
|
1314
|
-
});
|
|
1315
|
-
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
1316
|
-
greetCount += 1;
|
|
1317
|
-
}
|
|
1318
|
-
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1366
|
+
timeoutMs: actionTimeoutMs,
|
|
1367
|
+
intervalMs: actionIntervalMs,
|
|
1368
|
+
requireAny: true
|
|
1369
|
+
});
|
|
1370
|
+
postActionResult = await runRecommendPostAction({
|
|
1371
|
+
client,
|
|
1372
|
+
screening,
|
|
1373
|
+
actionDiscovery,
|
|
1374
|
+
postAction: normalizedPostAction,
|
|
1375
|
+
greetCount,
|
|
1376
|
+
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1377
|
+
executePostAction,
|
|
1378
|
+
afterClickDelayMs: actionAfterClickDelayMs
|
|
1379
|
+
});
|
|
1380
|
+
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
1381
|
+
greetCount += 1;
|
|
1382
|
+
}
|
|
1383
|
+
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1319
1384
|
}
|
|
1320
1385
|
if (detailResult && closeDetail) {
|
|
1321
1386
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
@@ -1349,16 +1414,16 @@ export async function runRecommendWorkflow({
|
|
|
1349
1414
|
}
|
|
1350
1415
|
}
|
|
1351
1416
|
}
|
|
1352
|
-
timings.total_ms = Date.now() - candidateStarted;
|
|
1353
|
-
const compactResult = {
|
|
1354
|
-
index,
|
|
1355
|
-
candidate_key: candidateKey,
|
|
1356
|
-
card_node_id: cardNodeId,
|
|
1357
|
-
candidate: compactCandidate(screeningCandidate),
|
|
1358
|
-
detail: compactDetail(detailResult),
|
|
1359
|
-
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1360
|
-
screening: compactScreening(screening),
|
|
1361
|
-
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1417
|
+
timings.total_ms = Date.now() - candidateStarted;
|
|
1418
|
+
const compactResult = {
|
|
1419
|
+
index,
|
|
1420
|
+
candidate_key: candidateKey,
|
|
1421
|
+
card_node_id: cardNodeId,
|
|
1422
|
+
candidate: compactCandidate(screeningCandidate),
|
|
1423
|
+
detail: compactDetail(detailResult),
|
|
1424
|
+
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1425
|
+
screening: compactScreening(screening),
|
|
1426
|
+
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1362
1427
|
post_action: postActionResult,
|
|
1363
1428
|
error: recoverableDetailError
|
|
1364
1429
|
? compactRecoverableDetailError(recoverableDetailError)
|
|
@@ -1367,40 +1432,40 @@ export async function runRecommendWorkflow({
|
|
|
1367
1432
|
: detailResult?.image_evidence?.ok === false
|
|
1368
1433
|
? compactError({
|
|
1369
1434
|
code: detailResult.image_evidence.error_code,
|
|
1370
|
-
message: detailResult.image_evidence.error
|
|
1371
|
-
}, "IMAGE_CAPTURE_FAILED")
|
|
1372
|
-
: null,
|
|
1373
|
-
timings
|
|
1374
|
-
};
|
|
1375
|
-
results.push(compactResult);
|
|
1376
|
-
markInfiniteListCandidateProcessed(listState, candidateKey, {
|
|
1377
|
-
metadata: {
|
|
1378
|
-
result_index: index,
|
|
1379
|
-
candidate_id: screeningCandidate.id || null
|
|
1380
|
-
}
|
|
1381
|
-
});
|
|
1382
|
-
|
|
1383
|
-
updateRecommendProgress({
|
|
1384
|
-
last_candidate_id: screeningCandidate.id || null,
|
|
1385
|
-
last_candidate_key: candidateKey,
|
|
1386
|
-
last_score: screening.score
|
|
1387
|
-
});
|
|
1388
|
-
const checkpointStarted = Date.now();
|
|
1389
|
-
runControl.checkpoint({
|
|
1390
|
-
results,
|
|
1391
|
-
last_candidate: {
|
|
1392
|
-
id: screeningCandidate.id || null,
|
|
1393
|
-
key: candidateKey,
|
|
1394
|
-
identity: screeningCandidate.identity || {},
|
|
1395
|
-
screening: {
|
|
1396
|
-
status: screening.status,
|
|
1397
|
-
passed: screening.passed,
|
|
1398
|
-
score: screening.score
|
|
1399
|
-
},
|
|
1400
|
-
llm_screening: compactScreeningLlmResult(llmResult),
|
|
1401
|
-
error: compactResult.error,
|
|
1402
|
-
post_action: postActionResult
|
|
1403
|
-
}
|
|
1435
|
+
message: detailResult.image_evidence.error
|
|
1436
|
+
}, "IMAGE_CAPTURE_FAILED")
|
|
1437
|
+
: null,
|
|
1438
|
+
timings
|
|
1439
|
+
};
|
|
1440
|
+
results.push(compactResult);
|
|
1441
|
+
markInfiniteListCandidateProcessed(listState, candidateKey, {
|
|
1442
|
+
metadata: {
|
|
1443
|
+
result_index: index,
|
|
1444
|
+
candidate_id: screeningCandidate.id || null
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
updateRecommendProgress({
|
|
1449
|
+
last_candidate_id: screeningCandidate.id || null,
|
|
1450
|
+
last_candidate_key: candidateKey,
|
|
1451
|
+
last_score: screening.score
|
|
1452
|
+
});
|
|
1453
|
+
const checkpointStarted = Date.now();
|
|
1454
|
+
runControl.checkpoint({
|
|
1455
|
+
results,
|
|
1456
|
+
last_candidate: {
|
|
1457
|
+
id: screeningCandidate.id || null,
|
|
1458
|
+
key: candidateKey,
|
|
1459
|
+
identity: screeningCandidate.identity || {},
|
|
1460
|
+
screening: {
|
|
1461
|
+
status: screening.status,
|
|
1462
|
+
passed: screening.passed,
|
|
1463
|
+
score: screening.score
|
|
1464
|
+
},
|
|
1465
|
+
llm_screening: compactScreeningLlmResult(llmResult),
|
|
1466
|
+
error: compactResult.error,
|
|
1467
|
+
post_action: postActionResult
|
|
1468
|
+
}
|
|
1404
1469
|
});
|
|
1405
1470
|
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1406
1471
|
|
|
@@ -1439,18 +1504,18 @@ export async function runRecommendWorkflow({
|
|
|
1439
1504
|
await runControl.sleep(delayMs);
|
|
1440
1505
|
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1441
1506
|
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1442
|
-
}
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
runControl.setPhase("recommend:done");
|
|
1446
|
-
return {
|
|
1447
|
-
domain: "recommend",
|
|
1448
|
-
target_url: targetUrl,
|
|
1449
|
-
job_selection: compactJobSelection(jobSelection),
|
|
1450
|
-
page_scope: compactPageScopeSelection(pageScopeSelection),
|
|
1451
|
-
filter: compactFilterResult(filterResult),
|
|
1452
|
-
card_count: cardNodeIds.length,
|
|
1453
|
-
candidate_list: compactInfiniteListState(listState),
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
runControl.setPhase("recommend:done");
|
|
1511
|
+
return {
|
|
1512
|
+
domain: "recommend",
|
|
1513
|
+
target_url: targetUrl,
|
|
1514
|
+
job_selection: compactJobSelection(jobSelection),
|
|
1515
|
+
page_scope: compactPageScopeSelection(pageScopeSelection),
|
|
1516
|
+
filter: compactFilterResult(filterResult),
|
|
1517
|
+
card_count: cardNodeIds.length,
|
|
1518
|
+
candidate_list: compactInfiniteListState(listState),
|
|
1454
1519
|
viewport_health: {
|
|
1455
1520
|
stats: viewportGuard.getStats(),
|
|
1456
1521
|
events: viewportGuard.getEvents()
|
|
@@ -1459,56 +1524,56 @@ export async function runRecommendWorkflow({
|
|
|
1459
1524
|
human_rest: humanRestController.getState(),
|
|
1460
1525
|
last_human_event: lastHumanEvent,
|
|
1461
1526
|
list_end_reason: listEndReason || null,
|
|
1462
|
-
refresh_rounds: refreshRounds,
|
|
1463
|
-
refresh_attempts: refreshAttempts,
|
|
1464
|
-
context_recoveries: contextRecoveryAttempts,
|
|
1465
|
-
...countRecommendResultStatuses(results, { greetCount }),
|
|
1466
|
-
results
|
|
1467
|
-
};
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
export function createRecommendRunService({
|
|
1471
|
-
lifecycle,
|
|
1472
|
-
idPrefix = "recommend",
|
|
1473
|
-
workflow = runRecommendWorkflow,
|
|
1474
|
-
onSnapshot = null
|
|
1475
|
-
} = {}) {
|
|
1476
|
-
const manager = lifecycle || createRunLifecycleManager({ idPrefix, onSnapshot });
|
|
1477
|
-
|
|
1478
|
-
function startRecommendRun({
|
|
1479
|
-
runId = "",
|
|
1480
|
-
pid = process.pid,
|
|
1481
|
-
client,
|
|
1482
|
-
targetUrl = "",
|
|
1483
|
-
criteria = "",
|
|
1484
|
-
jobLabel = "",
|
|
1485
|
-
pageScope = "recommend",
|
|
1486
|
-
fallbackPageScope = "recommend",
|
|
1487
|
-
filter = {},
|
|
1488
|
-
maxCandidates = 5,
|
|
1489
|
-
detailLimit,
|
|
1490
|
-
closeDetail = true,
|
|
1491
|
-
delayMs = 0,
|
|
1492
|
-
cardTimeoutMs = 10000,
|
|
1493
|
-
maxImagePages = DEFAULT_MAX_IMAGE_PAGES,
|
|
1494
|
-
imageWheelDeltaY = 650,
|
|
1495
|
-
cvAcquisitionMode = "unknown",
|
|
1496
|
-
listMaxScrolls = 20,
|
|
1497
|
-
listStableSignatureLimit = 5,
|
|
1498
|
-
listWheelDeltaY = 850,
|
|
1499
|
-
listSettleMs = 2200,
|
|
1500
|
-
listFallbackPoint = null,
|
|
1501
|
-
refreshOnEnd = true,
|
|
1502
|
-
maxRefreshRounds = 2,
|
|
1503
|
-
refreshButtonSettleMs = 8000,
|
|
1504
|
-
refreshReloadSettleMs = 8000,
|
|
1505
|
-
postAction = "none",
|
|
1506
|
-
maxGreetCount = null,
|
|
1507
|
-
executePostAction = true,
|
|
1508
|
-
actionTimeoutMs = 8000,
|
|
1509
|
-
actionIntervalMs = 500,
|
|
1510
|
-
actionAfterClickDelayMs = 900,
|
|
1511
|
-
screeningMode = "llm",
|
|
1527
|
+
refresh_rounds: refreshRounds,
|
|
1528
|
+
refresh_attempts: refreshAttempts,
|
|
1529
|
+
context_recoveries: contextRecoveryAttempts,
|
|
1530
|
+
...countRecommendResultStatuses(results, { greetCount }),
|
|
1531
|
+
results
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
|
|
1535
|
+
export function createRecommendRunService({
|
|
1536
|
+
lifecycle,
|
|
1537
|
+
idPrefix = "recommend",
|
|
1538
|
+
workflow = runRecommendWorkflow,
|
|
1539
|
+
onSnapshot = null
|
|
1540
|
+
} = {}) {
|
|
1541
|
+
const manager = lifecycle || createRunLifecycleManager({ idPrefix, onSnapshot });
|
|
1542
|
+
|
|
1543
|
+
function startRecommendRun({
|
|
1544
|
+
runId = "",
|
|
1545
|
+
pid = process.pid,
|
|
1546
|
+
client,
|
|
1547
|
+
targetUrl = "",
|
|
1548
|
+
criteria = "",
|
|
1549
|
+
jobLabel = "",
|
|
1550
|
+
pageScope = "recommend",
|
|
1551
|
+
fallbackPageScope = "recommend",
|
|
1552
|
+
filter = {},
|
|
1553
|
+
maxCandidates = 5,
|
|
1554
|
+
detailLimit,
|
|
1555
|
+
closeDetail = true,
|
|
1556
|
+
delayMs = 0,
|
|
1557
|
+
cardTimeoutMs = 10000,
|
|
1558
|
+
maxImagePages = DEFAULT_MAX_IMAGE_PAGES,
|
|
1559
|
+
imageWheelDeltaY = 650,
|
|
1560
|
+
cvAcquisitionMode = "unknown",
|
|
1561
|
+
listMaxScrolls = 20,
|
|
1562
|
+
listStableSignatureLimit = 5,
|
|
1563
|
+
listWheelDeltaY = 850,
|
|
1564
|
+
listSettleMs = 2200,
|
|
1565
|
+
listFallbackPoint = null,
|
|
1566
|
+
refreshOnEnd = true,
|
|
1567
|
+
maxRefreshRounds = 2,
|
|
1568
|
+
refreshButtonSettleMs = 8000,
|
|
1569
|
+
refreshReloadSettleMs = 8000,
|
|
1570
|
+
postAction = "none",
|
|
1571
|
+
maxGreetCount = null,
|
|
1572
|
+
executePostAction = true,
|
|
1573
|
+
actionTimeoutMs = 8000,
|
|
1574
|
+
actionIntervalMs = 500,
|
|
1575
|
+
actionAfterClickDelayMs = 900,
|
|
1576
|
+
screeningMode = "llm",
|
|
1512
1577
|
llmConfig = null,
|
|
1513
1578
|
llmTimeoutMs = 120000,
|
|
1514
1579
|
llmImageLimit = 8,
|
|
@@ -1529,41 +1594,41 @@ export function createRecommendRunService({
|
|
|
1529
1594
|
});
|
|
1530
1595
|
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1531
1596
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1532
|
-
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
1533
|
-
return manager.startRun({
|
|
1534
|
-
runId,
|
|
1535
|
-
name,
|
|
1536
|
-
pid,
|
|
1537
|
-
context: {
|
|
1538
|
-
domain: "recommend",
|
|
1539
|
-
target_url: targetUrl,
|
|
1540
|
-
criteria_present: Boolean(criteria),
|
|
1541
|
-
job_label: jobLabel || "",
|
|
1542
|
-
requested_page_scope: requestedPageScope,
|
|
1543
|
-
fallback_page_scope: normalizedFallbackPageScope,
|
|
1544
|
-
filter: normalizedFilter,
|
|
1545
|
-
max_candidates: maxCandidates,
|
|
1546
|
-
max_candidates_semantics: "passed_candidates",
|
|
1547
|
-
detail_limit: normalizedDetailLimit,
|
|
1548
|
-
close_detail: closeDetail,
|
|
1549
|
-
cv_acquisition_mode: cvAcquisitionMode,
|
|
1550
|
-
max_image_pages: maxImagePages,
|
|
1551
|
-
image_wheel_delta_y: imageWheelDeltaY,
|
|
1552
|
-
list_max_scrolls: listMaxScrolls,
|
|
1553
|
-
list_stable_signature_limit: listStableSignatureLimit,
|
|
1554
|
-
list_wheel_delta_y: listWheelDeltaY,
|
|
1555
|
-
list_settle_ms: listSettleMs,
|
|
1556
|
-
list_fallback_point: listFallbackPoint,
|
|
1557
|
-
refresh_on_end: refreshOnEnd,
|
|
1558
|
-
max_refresh_rounds: maxRefreshRounds,
|
|
1559
|
-
refresh_button_settle_ms: refreshButtonSettleMs,
|
|
1560
|
-
refresh_reload_settle_ms: refreshReloadSettleMs,
|
|
1561
|
-
post_action: normalizedPostAction,
|
|
1562
|
-
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1563
|
-
execute_post_action: Boolean(executePostAction),
|
|
1564
|
-
action_timeout_ms: actionTimeoutMs,
|
|
1565
|
-
screening_mode: normalizedScreeningMode,
|
|
1566
|
-
llm_configured: Boolean(llmConfig),
|
|
1597
|
+
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
1598
|
+
return manager.startRun({
|
|
1599
|
+
runId,
|
|
1600
|
+
name,
|
|
1601
|
+
pid,
|
|
1602
|
+
context: {
|
|
1603
|
+
domain: "recommend",
|
|
1604
|
+
target_url: targetUrl,
|
|
1605
|
+
criteria_present: Boolean(criteria),
|
|
1606
|
+
job_label: jobLabel || "",
|
|
1607
|
+
requested_page_scope: requestedPageScope,
|
|
1608
|
+
fallback_page_scope: normalizedFallbackPageScope,
|
|
1609
|
+
filter: normalizedFilter,
|
|
1610
|
+
max_candidates: maxCandidates,
|
|
1611
|
+
max_candidates_semantics: "passed_candidates",
|
|
1612
|
+
detail_limit: normalizedDetailLimit,
|
|
1613
|
+
close_detail: closeDetail,
|
|
1614
|
+
cv_acquisition_mode: cvAcquisitionMode,
|
|
1615
|
+
max_image_pages: maxImagePages,
|
|
1616
|
+
image_wheel_delta_y: imageWheelDeltaY,
|
|
1617
|
+
list_max_scrolls: listMaxScrolls,
|
|
1618
|
+
list_stable_signature_limit: listStableSignatureLimit,
|
|
1619
|
+
list_wheel_delta_y: listWheelDeltaY,
|
|
1620
|
+
list_settle_ms: listSettleMs,
|
|
1621
|
+
list_fallback_point: listFallbackPoint,
|
|
1622
|
+
refresh_on_end: refreshOnEnd,
|
|
1623
|
+
max_refresh_rounds: maxRefreshRounds,
|
|
1624
|
+
refresh_button_settle_ms: refreshButtonSettleMs,
|
|
1625
|
+
refresh_reload_settle_ms: refreshReloadSettleMs,
|
|
1626
|
+
post_action: normalizedPostAction,
|
|
1627
|
+
max_greet_count: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
1628
|
+
execute_post_action: Boolean(executePostAction),
|
|
1629
|
+
action_timeout_ms: actionTimeoutMs,
|
|
1630
|
+
screening_mode: normalizedScreeningMode,
|
|
1631
|
+
llm_configured: Boolean(llmConfig),
|
|
1567
1632
|
llm_timeout_ms: llmTimeoutMs,
|
|
1568
1633
|
llm_image_limit: llmImageLimit,
|
|
1569
1634
|
llm_image_detail: llmImageDetail,
|
|
@@ -1574,16 +1639,16 @@ export function createRecommendRunService({
|
|
|
1574
1639
|
human_rest_enabled: effectiveHumanRestEnabled
|
|
1575
1640
|
},
|
|
1576
1641
|
progress: {
|
|
1577
|
-
card_count: 0,
|
|
1578
|
-
target_count: candidateLimit,
|
|
1579
|
-
target_count_semantics: "passed_candidates",
|
|
1580
|
-
processed: 0,
|
|
1581
|
-
screened: 0,
|
|
1582
|
-
detail_opened: 0,
|
|
1583
|
-
llm_screened: 0,
|
|
1584
|
-
passed: 0,
|
|
1585
|
-
greet_count: 0,
|
|
1586
|
-
post_action_clicked: 0,
|
|
1642
|
+
card_count: 0,
|
|
1643
|
+
target_count: candidateLimit,
|
|
1644
|
+
target_count_semantics: "passed_candidates",
|
|
1645
|
+
processed: 0,
|
|
1646
|
+
screened: 0,
|
|
1647
|
+
detail_opened: 0,
|
|
1648
|
+
llm_screened: 0,
|
|
1649
|
+
passed: 0,
|
|
1650
|
+
greet_count: 0,
|
|
1651
|
+
post_action_clicked: 0,
|
|
1587
1652
|
image_capture_failed: 0,
|
|
1588
1653
|
detail_open_failed: 0,
|
|
1589
1654
|
transient_recovered: 0,
|
|
@@ -1595,40 +1660,40 @@ export function createRecommendRunService({
|
|
|
1595
1660
|
human_rest_ms: 0,
|
|
1596
1661
|
last_human_event: null
|
|
1597
1662
|
},
|
|
1598
|
-
checkpoint: {},
|
|
1599
|
-
task: (runControl) => workflow({
|
|
1600
|
-
client,
|
|
1601
|
-
targetUrl,
|
|
1602
|
-
criteria,
|
|
1603
|
-
jobLabel,
|
|
1604
|
-
pageScope: requestedPageScope,
|
|
1605
|
-
fallbackPageScope: normalizedFallbackPageScope,
|
|
1606
|
-
filter: normalizedFilter,
|
|
1607
|
-
maxCandidates,
|
|
1608
|
-
detailLimit: normalizedDetailLimit,
|
|
1609
|
-
closeDetail,
|
|
1610
|
-
delayMs,
|
|
1611
|
-
cardTimeoutMs,
|
|
1612
|
-
maxImagePages,
|
|
1613
|
-
imageWheelDeltaY,
|
|
1614
|
-
cvAcquisitionMode,
|
|
1615
|
-
listMaxScrolls,
|
|
1616
|
-
listStableSignatureLimit,
|
|
1617
|
-
listWheelDeltaY,
|
|
1618
|
-
listSettleMs,
|
|
1619
|
-
listFallbackPoint,
|
|
1620
|
-
refreshOnEnd,
|
|
1621
|
-
maxRefreshRounds,
|
|
1622
|
-
refreshButtonSettleMs,
|
|
1623
|
-
refreshReloadSettleMs,
|
|
1624
|
-
postAction: normalizedPostAction,
|
|
1625
|
-
maxGreetCount,
|
|
1626
|
-
executePostAction,
|
|
1627
|
-
actionTimeoutMs,
|
|
1628
|
-
actionIntervalMs,
|
|
1629
|
-
actionAfterClickDelayMs,
|
|
1630
|
-
screeningMode: normalizedScreeningMode,
|
|
1631
|
-
llmConfig,
|
|
1663
|
+
checkpoint: {},
|
|
1664
|
+
task: (runControl) => workflow({
|
|
1665
|
+
client,
|
|
1666
|
+
targetUrl,
|
|
1667
|
+
criteria,
|
|
1668
|
+
jobLabel,
|
|
1669
|
+
pageScope: requestedPageScope,
|
|
1670
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
1671
|
+
filter: normalizedFilter,
|
|
1672
|
+
maxCandidates,
|
|
1673
|
+
detailLimit: normalizedDetailLimit,
|
|
1674
|
+
closeDetail,
|
|
1675
|
+
delayMs,
|
|
1676
|
+
cardTimeoutMs,
|
|
1677
|
+
maxImagePages,
|
|
1678
|
+
imageWheelDeltaY,
|
|
1679
|
+
cvAcquisitionMode,
|
|
1680
|
+
listMaxScrolls,
|
|
1681
|
+
listStableSignatureLimit,
|
|
1682
|
+
listWheelDeltaY,
|
|
1683
|
+
listSettleMs,
|
|
1684
|
+
listFallbackPoint,
|
|
1685
|
+
refreshOnEnd,
|
|
1686
|
+
maxRefreshRounds,
|
|
1687
|
+
refreshButtonSettleMs,
|
|
1688
|
+
refreshReloadSettleMs,
|
|
1689
|
+
postAction: normalizedPostAction,
|
|
1690
|
+
maxGreetCount,
|
|
1691
|
+
executePostAction,
|
|
1692
|
+
actionTimeoutMs,
|
|
1693
|
+
actionIntervalMs,
|
|
1694
|
+
actionAfterClickDelayMs,
|
|
1695
|
+
screeningMode: normalizedScreeningMode,
|
|
1696
|
+
llmConfig,
|
|
1632
1697
|
llmTimeoutMs,
|
|
1633
1698
|
llmImageLimit,
|
|
1634
1699
|
llmImageDetail,
|
|
@@ -1638,15 +1703,15 @@ export function createRecommendRunService({
|
|
|
1638
1703
|
}, runControl)
|
|
1639
1704
|
});
|
|
1640
1705
|
}
|
|
1641
|
-
|
|
1642
|
-
return {
|
|
1643
|
-
startRecommendRun,
|
|
1644
|
-
getRecommendRun: manager.getRun,
|
|
1645
|
-
pauseRecommendRun: manager.pauseRun,
|
|
1646
|
-
resumeRecommendRun: manager.resumeRun,
|
|
1647
|
-
cancelRecommendRun: manager.cancelRun,
|
|
1648
|
-
waitForRecommendRun: manager.waitForRun,
|
|
1649
|
-
listRecommendRuns: manager.listRuns,
|
|
1650
|
-
manager
|
|
1651
|
-
};
|
|
1652
|
-
}
|
|
1706
|
+
|
|
1707
|
+
return {
|
|
1708
|
+
startRecommendRun,
|
|
1709
|
+
getRecommendRun: manager.getRun,
|
|
1710
|
+
pauseRecommendRun: manager.pauseRun,
|
|
1711
|
+
resumeRecommendRun: manager.resumeRun,
|
|
1712
|
+
cancelRecommendRun: manager.cancelRun,
|
|
1713
|
+
waitForRecommendRun: manager.waitForRun,
|
|
1714
|
+
listRecommendRuns: manager.listRuns,
|
|
1715
|
+
manager
|
|
1716
|
+
};
|
|
1717
|
+
}
|