@reconcrap/boss-recommend-mcp 2.0.52 → 2.0.54
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 +245 -221
- package/src/domains/chat/detail.js +1853 -1668
- package/src/domains/chat/run-service.js +2039 -1979
- package/src/domains/recommend/detail.js +544 -544
- package/src/domains/recommend/run-service.js +1235 -1235
- package/src/index.js +16 -5
- 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,37 @@ 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";
|
|
48
|
-
import {
|
|
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
|
+
import {
|
|
49
49
|
closeRecommendDetail,
|
|
50
50
|
createRecommendDetailNetworkRecorder,
|
|
51
51
|
extractRecommendDetailCandidate,
|
|
@@ -54,96 +54,96 @@ import {
|
|
|
54
54
|
openRecommendCardDetailWithFreshRetry,
|
|
55
55
|
waitForRecommendDetailNetworkEvents
|
|
56
56
|
} 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),
|
|
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),
|
|
147
147
|
already_current: Boolean(jobSelection.already_current),
|
|
148
148
|
reason: jobSelection.reason || null,
|
|
149
149
|
selected_option: jobSelection.selected_option || null,
|
|
@@ -171,222 +171,222 @@ function compactJobSelection(jobSelection) {
|
|
|
171
171
|
}
|
|
172
172
|
: null,
|
|
173
173
|
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,
|
|
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,
|
|
390
390
|
forced_recent_not_view: Boolean(refreshAttempt.forced_recent_not_view),
|
|
391
391
|
target_url: refreshAttempt.target_url || null,
|
|
392
392
|
card_count: refreshAttempt.card_count || 0,
|
|
@@ -400,11 +400,11 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
400
400
|
}
|
|
401
401
|
: null,
|
|
402
402
|
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,
|
|
403
|
+
ok: Boolean(attempt.ok),
|
|
404
|
+
method: attempt.method || "",
|
|
405
|
+
reason: attempt.reason || null,
|
|
406
|
+
error: attempt.error || null,
|
|
407
|
+
label: attempt.label || null,
|
|
408
408
|
before_card_count: attempt.before_card_count || 0,
|
|
409
409
|
after_card_count: attempt.after_card_count || 0,
|
|
410
410
|
card_count: attempt.card_count || 0,
|
|
@@ -432,22 +432,22 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
432
432
|
filter: compactFilterResult(refreshAttempt.filter)
|
|
433
433
|
};
|
|
434
434
|
}
|
|
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"
|
|
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"
|
|
451
451
|
)).length,
|
|
452
452
|
transient_recovered: results.filter((item) => (
|
|
453
453
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
@@ -455,10 +455,10 @@ export function countRecommendResultStatuses(results = [], {
|
|
|
455
455
|
|| item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
|
|
456
456
|
|| item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
|
|
457
457
|
|| item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
|
|
458
|
-
)).length
|
|
459
|
-
};
|
|
460
|
-
}
|
|
461
|
-
|
|
458
|
+
)).length
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
462
|
function countPassedResults(results = []) {
|
|
463
463
|
return countRecommendResultStatuses(results).passed;
|
|
464
464
|
}
|
|
@@ -499,7 +499,7 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
499
499
|
}
|
|
500
500
|
return result;
|
|
501
501
|
}
|
|
502
|
-
|
|
502
|
+
|
|
503
503
|
function createRecommendCloseFailureError(closeResult) {
|
|
504
504
|
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
505
505
|
error.code = "DETAIL_CLOSE_FAILED";
|
|
@@ -522,124 +522,124 @@ function createRecommendRefreshFailureError(refreshAttempt, {
|
|
|
522
522
|
error.passed_count = passedCount;
|
|
523
523
|
return error;
|
|
524
524
|
}
|
|
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
|
-
|
|
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
|
+
|
|
587
587
|
export function isRecoverableRecommendDetailError(error) {
|
|
588
588
|
return isStaleRecommendNodeError(error) || isRecommendDetailOpenMissError(error);
|
|
589
589
|
}
|
|
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,
|
|
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,
|
|
599
599
|
score: 0,
|
|
600
600
|
reasons: isStaleRecommendNodeError(error)
|
|
601
601
|
? ["detail_open_failed", "stale_node"]
|
|
602
602
|
: isRecommendDetailOpenMissError(error)
|
|
603
603
|
? ["detail_open_failed", "detail_open_miss"]
|
|
604
604
|
: ["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,
|
|
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,
|
|
643
643
|
llmTimeoutMs = 120000,
|
|
644
644
|
llmImageLimit = 8,
|
|
645
645
|
llmImageDetail = "high",
|
|
@@ -665,54 +665,54 @@ export async function runRecommendWorkflow({
|
|
|
665
665
|
batchRestEnabled: effectiveHumanBehavior.batchRest
|
|
666
666
|
});
|
|
667
667
|
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;
|
|
702
|
-
const candidateRecoveryCounts = new Map();
|
|
703
|
-
let jobSelection = null;
|
|
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;
|
|
702
|
+
const candidateRecoveryCounts = new Map();
|
|
703
|
+
let jobSelection = null;
|
|
704
704
|
let pageScopeSelection = null;
|
|
705
705
|
let filterResult = null;
|
|
706
706
|
let cardNodeIds = [];
|
|
707
707
|
let listEndReason = "";
|
|
708
708
|
let lastHumanEvent = null;
|
|
709
709
|
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
|
|
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
|
|
716
716
|
}));
|
|
717
717
|
|
|
718
718
|
function recordHumanEvent(event = null) {
|
|
@@ -749,13 +749,13 @@ export async function runRecommendWorkflow({
|
|
|
749
749
|
card_count: cardNodeIds.length,
|
|
750
750
|
target_count: targetPassCount,
|
|
751
751
|
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,
|
|
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,
|
|
759
759
|
list_end_reason: listEndReason || null,
|
|
760
760
|
viewport_checks: viewportGuard.getStats().checks,
|
|
761
761
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
@@ -768,280 +768,280 @@ export async function runRecommendWorkflow({
|
|
|
768
768
|
...extra
|
|
769
769
|
});
|
|
770
770
|
}
|
|
771
|
-
|
|
772
|
-
function checkpointInProgressCandidate({
|
|
773
|
-
index = results.length,
|
|
774
|
-
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
|
-
},
|
|
788
|
-
candidate_list: compactInfiniteListState(listState)
|
|
789
|
-
});
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
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;
|
|
800
|
-
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
801
|
-
rootState,
|
|
802
|
-
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,
|
|
811
|
-
buttonSettleMs: refreshButtonSettleMs,
|
|
812
|
-
reloadSettleMs: refreshReloadSettleMs
|
|
813
|
-
});
|
|
814
|
-
const compactRefresh = {
|
|
815
|
-
...compactRefreshAttempt(refreshResult),
|
|
816
|
-
context_recovery: true,
|
|
817
|
-
recovery_reason: reason,
|
|
818
|
-
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
819
|
-
elapsed_ms: Date.now() - started
|
|
820
|
-
};
|
|
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
|
|
837
|
-
});
|
|
838
|
-
throw new Error(`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`);
|
|
839
|
-
}
|
|
840
|
-
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
|
-
|
|
866
|
-
runControl.setPhase("recommend:cleanup");
|
|
867
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
868
|
-
|
|
869
|
-
await runControl.waitIfPaused();
|
|
870
|
-
runControl.throwIfCanceled();
|
|
871
|
-
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
|
-
rootState = await getRecommendRoots(client);
|
|
910
|
-
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
911
|
-
runControl.checkpoint({
|
|
912
|
-
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
913
|
-
});
|
|
914
|
-
|
|
915
|
-
if (normalizedFilter.enabled) {
|
|
916
|
-
await runControl.waitIfPaused();
|
|
917
|
-
runControl.throwIfCanceled();
|
|
918
|
-
runControl.setPhase("recommend:filter");
|
|
919
|
-
filterResult = await selectAndConfirmFirstSafeFilter(
|
|
920
|
-
client,
|
|
921
|
-
rootState.iframe.documentNodeId,
|
|
922
|
-
buildRecommendFilterSelectionOptions(normalizedFilter)
|
|
923
|
-
);
|
|
924
|
-
if (!filterResult.confirmed) {
|
|
925
|
-
throw new Error("Recommend run filter selection was not confirmed");
|
|
926
|
-
}
|
|
927
|
-
rootState = await getRecommendRoots(client);
|
|
928
|
-
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
929
|
-
runControl.checkpoint({
|
|
930
|
-
filter: compactFilterResult(filterResult)
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
await runControl.waitIfPaused();
|
|
935
|
-
runControl.throwIfCanceled();
|
|
936
|
-
runControl.setPhase("recommend:cards");
|
|
937
|
-
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
938
|
-
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
939
|
-
timeoutMs: cardTimeoutMs,
|
|
940
|
-
intervalMs: 300
|
|
941
|
-
});
|
|
942
|
-
if (!cardNodeIds.length) {
|
|
943
|
-
throw new Error("No recommend candidate cards found for run service");
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
updateRecommendProgress({
|
|
947
|
-
list_end_reason: null
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
while (countPassedResults(results) < targetPassCount) {
|
|
951
|
-
const candidateStarted = Date.now();
|
|
952
|
-
const timings = {};
|
|
953
|
-
await runControl.waitIfPaused();
|
|
954
|
-
runControl.throwIfCanceled();
|
|
955
|
-
runControl.setPhase("recommend:candidate");
|
|
956
|
-
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
957
|
-
|
|
958
|
-
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
959
|
-
client,
|
|
960
|
-
state: listState,
|
|
961
|
-
maxScrolls: listMaxScrolls,
|
|
771
|
+
|
|
772
|
+
function checkpointInProgressCandidate({
|
|
773
|
+
index = results.length,
|
|
774
|
+
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
|
+
},
|
|
788
|
+
candidate_list: compactInfiniteListState(listState)
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
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;
|
|
800
|
+
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
801
|
+
rootState,
|
|
802
|
+
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,
|
|
811
|
+
buttonSettleMs: refreshButtonSettleMs,
|
|
812
|
+
reloadSettleMs: refreshReloadSettleMs
|
|
813
|
+
});
|
|
814
|
+
const compactRefresh = {
|
|
815
|
+
...compactRefreshAttempt(refreshResult),
|
|
816
|
+
context_recovery: true,
|
|
817
|
+
recovery_reason: reason,
|
|
818
|
+
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
819
|
+
elapsed_ms: Date.now() - started
|
|
820
|
+
};
|
|
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
|
|
837
|
+
});
|
|
838
|
+
throw new Error(`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`);
|
|
839
|
+
}
|
|
840
|
+
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
|
+
|
|
866
|
+
runControl.setPhase("recommend:cleanup");
|
|
867
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
868
|
+
|
|
869
|
+
await runControl.waitIfPaused();
|
|
870
|
+
runControl.throwIfCanceled();
|
|
871
|
+
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
|
+
rootState = await getRecommendRoots(client);
|
|
910
|
+
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
911
|
+
runControl.checkpoint({
|
|
912
|
+
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
if (normalizedFilter.enabled) {
|
|
916
|
+
await runControl.waitIfPaused();
|
|
917
|
+
runControl.throwIfCanceled();
|
|
918
|
+
runControl.setPhase("recommend:filter");
|
|
919
|
+
filterResult = await selectAndConfirmFirstSafeFilter(
|
|
920
|
+
client,
|
|
921
|
+
rootState.iframe.documentNodeId,
|
|
922
|
+
buildRecommendFilterSelectionOptions(normalizedFilter)
|
|
923
|
+
);
|
|
924
|
+
if (!filterResult.confirmed) {
|
|
925
|
+
throw new Error("Recommend run filter selection was not confirmed");
|
|
926
|
+
}
|
|
927
|
+
rootState = await getRecommendRoots(client);
|
|
928
|
+
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
929
|
+
runControl.checkpoint({
|
|
930
|
+
filter: compactFilterResult(filterResult)
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
await runControl.waitIfPaused();
|
|
935
|
+
runControl.throwIfCanceled();
|
|
936
|
+
runControl.setPhase("recommend:cards");
|
|
937
|
+
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
938
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
939
|
+
timeoutMs: cardTimeoutMs,
|
|
940
|
+
intervalMs: 300
|
|
941
|
+
});
|
|
942
|
+
if (!cardNodeIds.length) {
|
|
943
|
+
throw new Error("No recommend candidate cards found for run service");
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
updateRecommendProgress({
|
|
947
|
+
list_end_reason: null
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
while (countPassedResults(results) < targetPassCount) {
|
|
951
|
+
const candidateStarted = Date.now();
|
|
952
|
+
const timings = {};
|
|
953
|
+
await runControl.waitIfPaused();
|
|
954
|
+
runControl.throwIfCanceled();
|
|
955
|
+
runControl.setPhase("recommend:candidate");
|
|
956
|
+
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
957
|
+
|
|
958
|
+
const nextCandidateResult = await measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
959
|
+
client,
|
|
960
|
+
state: listState,
|
|
961
|
+
maxScrolls: listMaxScrolls,
|
|
962
962
|
stableSignatureLimit: listStableSignatureLimit,
|
|
963
963
|
wheelDeltaY: listWheelDeltaY,
|
|
964
964
|
settleMs: listSettleMs,
|
|
965
965
|
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
966
966
|
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
|
-
});
|
|
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
|
+
});
|
|
1029
1029
|
if (refreshResult.ok) {
|
|
1030
1030
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1031
1031
|
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
|
-
});
|
|
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
|
+
});
|
|
1045
1045
|
listEndReason = "";
|
|
1046
1046
|
continue;
|
|
1047
1047
|
}
|
|
@@ -1053,240 +1053,240 @@ export async function runRecommendWorkflow({
|
|
|
1053
1053
|
}
|
|
1054
1054
|
break;
|
|
1055
1055
|
}
|
|
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();
|
|
1070
|
-
runControl.setPhase("recommend:detail");
|
|
1071
|
-
detailStep = "ensure_viewport";
|
|
1072
|
-
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
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();
|
|
1070
|
+
runControl.setPhase("recommend:detail");
|
|
1071
|
+
detailStep = "ensure_viewport";
|
|
1072
|
+
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
1073
1073
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1074
1074
|
detailStep = "open_detail";
|
|
1075
1075
|
networkRecorder.clear();
|
|
1076
1076
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1077
1077
|
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,
|
|
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,
|
|
1156
1156
|
maxScreenshots: maxImagePages,
|
|
1157
1157
|
wheelDeltaY: imageWheelDeltaY,
|
|
1158
1158
|
settleMs: 350,
|
|
1159
1159
|
scrollMethod: "dom-anchor-fallback-input",
|
|
1160
1160
|
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1161
1161
|
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);
|
|
1185
|
-
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1186
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1187
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1188
|
-
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1189
|
-
forceRecentNotView: true
|
|
1190
|
-
});
|
|
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) {
|
|
1235
|
-
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1236
|
-
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1237
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1238
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1239
|
-
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1240
|
-
forceRecentNotView: true
|
|
1241
|
-
});
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1244
|
-
recoverableDetailError = error;
|
|
1245
|
-
detailResult = null;
|
|
1246
|
-
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1247
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1248
|
-
}
|
|
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 });
|
|
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);
|
|
1185
|
+
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1186
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1187
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1188
|
+
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1189
|
+
forceRecentNotView: true
|
|
1190
|
+
});
|
|
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) {
|
|
1235
|
+
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1236
|
+
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1237
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1238
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1239
|
+
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1240
|
+
forceRecentNotView: true
|
|
1241
|
+
});
|
|
1242
|
+
continue;
|
|
1243
|
+
}
|
|
1244
|
+
recoverableDetailError = error;
|
|
1245
|
+
detailResult = null;
|
|
1246
|
+
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1247
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1248
|
+
}
|
|
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 });
|
|
1290
1290
|
let actionDiscovery = null;
|
|
1291
1291
|
let postActionResult = null;
|
|
1292
1292
|
let closeFailureError = null;
|
|
@@ -1298,24 +1298,24 @@ export async function runRecommendWorkflow({
|
|
|
1298
1298
|
runControl.setPhase("recommend:post-action");
|
|
1299
1299
|
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1300
1300
|
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);
|
|
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);
|
|
1319
1319
|
}
|
|
1320
1320
|
if (detailResult && closeDetail) {
|
|
1321
1321
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
@@ -1349,16 +1349,16 @@ export async function runRecommendWorkflow({
|
|
|
1349
1349
|
}
|
|
1350
1350
|
}
|
|
1351
1351
|
}
|
|
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),
|
|
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),
|
|
1362
1362
|
post_action: postActionResult,
|
|
1363
1363
|
error: recoverableDetailError
|
|
1364
1364
|
? compactRecoverableDetailError(recoverableDetailError)
|
|
@@ -1367,40 +1367,40 @@ export async function runRecommendWorkflow({
|
|
|
1367
1367
|
: detailResult?.image_evidence?.ok === false
|
|
1368
1368
|
? compactError({
|
|
1369
1369
|
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
|
-
}
|
|
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
|
+
}
|
|
1404
1404
|
});
|
|
1405
1405
|
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1406
1406
|
|
|
@@ -1439,18 +1439,18 @@ export async function runRecommendWorkflow({
|
|
|
1439
1439
|
await runControl.sleep(delayMs);
|
|
1440
1440
|
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1441
1441
|
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),
|
|
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),
|
|
1454
1454
|
viewport_health: {
|
|
1455
1455
|
stats: viewportGuard.getStats(),
|
|
1456
1456
|
events: viewportGuard.getEvents()
|
|
@@ -1459,56 +1459,56 @@ export async function runRecommendWorkflow({
|
|
|
1459
1459
|
human_rest: humanRestController.getState(),
|
|
1460
1460
|
last_human_event: lastHumanEvent,
|
|
1461
1461
|
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",
|
|
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",
|
|
1512
1512
|
llmConfig = null,
|
|
1513
1513
|
llmTimeoutMs = 120000,
|
|
1514
1514
|
llmImageLimit = 8,
|
|
@@ -1529,41 +1529,41 @@ export function createRecommendRunService({
|
|
|
1529
1529
|
});
|
|
1530
1530
|
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1531
1531
|
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),
|
|
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),
|
|
1567
1567
|
llm_timeout_ms: llmTimeoutMs,
|
|
1568
1568
|
llm_image_limit: llmImageLimit,
|
|
1569
1569
|
llm_image_detail: llmImageDetail,
|
|
@@ -1574,16 +1574,16 @@ export function createRecommendRunService({
|
|
|
1574
1574
|
human_rest_enabled: effectiveHumanRestEnabled
|
|
1575
1575
|
},
|
|
1576
1576
|
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,
|
|
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,
|
|
1587
1587
|
image_capture_failed: 0,
|
|
1588
1588
|
detail_open_failed: 0,
|
|
1589
1589
|
transient_recovered: 0,
|
|
@@ -1595,40 +1595,40 @@ export function createRecommendRunService({
|
|
|
1595
1595
|
human_rest_ms: 0,
|
|
1596
1596
|
last_human_event: null
|
|
1597
1597
|
},
|
|
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,
|
|
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,
|
|
1632
1632
|
llmTimeoutMs,
|
|
1633
1633
|
llmImageLimit,
|
|
1634
1634
|
llmImageDetail,
|
|
@@ -1638,15 +1638,15 @@ export function createRecommendRunService({
|
|
|
1638
1638
|
}, runControl)
|
|
1639
1639
|
});
|
|
1640
1640
|
}
|
|
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
|
-
}
|
|
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
|
+
}
|