@reconcrap/boss-recommend-mcp 2.1.21 → 2.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -2
- package/bin/boss-recommend-mcp.js +4 -4
- package/config/screening-config.example.json +33 -33
- package/package.json +2 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +42 -42
- package/skills/boss-chat/SKILL.md +106 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +219 -214
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +127 -127
- package/src/chat-runtime-config.js +775 -775
- package/src/cli.js +573 -573
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +2416 -2385
- package/src/core/capture/index.js +1201 -1201
- package/src/core/cv-acquisition/index.js +238 -238
- package/src/core/cv-capture-target/index.js +299 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +1315 -1306
- package/src/core/reporting/legacy-csv.js +334 -332
- package/src/core/run/index.js +44 -27
- package/src/core/run/timing.js +33 -33
- package/src/core/screening/index.js +2135 -2135
- package/src/core/self-heal/index.js +973 -973
- package/src/core/self-heal/viewport.js +564 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +113 -113
- package/src/domains/chat/index.js +7 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +122 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +571 -571
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +472 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +333 -333
- package/src/domains/recommend/constants.js +228 -159
- package/src/domains/recommend/detail.js +655 -646
- package/src/domains/recommend/filters.js +748 -377
- package/src/domains/recommend/index.js +4 -3
- package/src/domains/recommend/jobs.js +542 -542
- package/src/domains/recommend/location.js +736 -0
- package/src/domains/recommend/refresh.js +575 -351
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +1616 -878
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1620 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +121 -4
- package/src/parser.js +376 -8
- package/src/recommend-mcp.js +1174 -914
- package/src/recommend-scheduler.js +535 -469
- package/src/recruit-mcp.js +2121 -2121
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
4
4
|
import {
|
|
5
5
|
addTiming,
|
|
@@ -8,13 +8,13 @@ import {
|
|
|
8
8
|
} from "../../core/run/timing.js";
|
|
9
9
|
import { captureScrolledNodeScreenshots } from "../../core/capture/index.js";
|
|
10
10
|
import { waitForCvCaptureTarget } from "../../core/cv-capture-target/index.js";
|
|
11
|
-
import {
|
|
12
|
-
configureHumanInteraction,
|
|
13
|
-
createHumanRestController,
|
|
14
|
-
humanDelay,
|
|
15
|
-
normalizeHumanBehaviorOptions,
|
|
16
|
-
sleep
|
|
17
|
-
} from "../../core/browser/index.js";
|
|
11
|
+
import {
|
|
12
|
+
configureHumanInteraction,
|
|
13
|
+
createHumanRestController,
|
|
14
|
+
humanDelay,
|
|
15
|
+
normalizeHumanBehaviorOptions,
|
|
16
|
+
sleep
|
|
17
|
+
} from "../../core/browser/index.js";
|
|
18
18
|
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
19
19
|
import {
|
|
20
20
|
compactCvAcquisitionState,
|
|
@@ -38,41 +38,43 @@ import {
|
|
|
38
38
|
resolveInfiniteListFallbackPoint
|
|
39
39
|
} from "../../core/infinite-list/index.js";
|
|
40
40
|
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
41
|
-
import {
|
|
42
|
-
callScreeningLlm,
|
|
43
|
-
compactScreeningLlmResult,
|
|
44
|
-
createFatalLlmRunError,
|
|
45
|
-
createFailedLlmScreeningResult,
|
|
46
|
-
isFatalLlmProviderError,
|
|
47
|
-
llmResultToScreening,
|
|
48
|
-
screenCandidate
|
|
49
|
-
} from "../../core/screening/index.js";
|
|
50
|
-
import {
|
|
51
|
-
closeRecommendBlockingPanels,
|
|
52
|
-
closeRecommendAvatarPreview,
|
|
53
|
-
closeRecommendDetail,
|
|
54
|
-
createRecommendDetailNetworkRecorder,
|
|
55
|
-
extractRecommendDetailCandidate,
|
|
56
|
-
isRecommendDetailOpenMissError,
|
|
57
|
-
isStaleRecommendNodeError,
|
|
58
|
-
openRecommendCardDetailWithFreshRetry,
|
|
59
|
-
waitForRecommendDetailNetworkEvents
|
|
60
|
-
} from "./detail.js";
|
|
61
41
|
import {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
42
|
+
callScreeningLlm,
|
|
43
|
+
compactScreeningLlmResult,
|
|
44
|
+
createFatalLlmRunError,
|
|
45
|
+
createFailedLlmScreeningResult,
|
|
46
|
+
isFatalLlmProviderError,
|
|
47
|
+
llmResultToScreening,
|
|
48
|
+
screenCandidate
|
|
49
|
+
} from "../../core/screening/index.js";
|
|
66
50
|
import {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
closeRecommendBlockingPanels,
|
|
52
|
+
closeRecommendAvatarPreview,
|
|
53
|
+
closeRecommendDetail,
|
|
54
|
+
createRecommendDetailNetworkRecorder,
|
|
55
|
+
extractRecommendDetailCandidate,
|
|
56
|
+
isRecommendDetailOpenMissError,
|
|
57
|
+
isStaleRecommendNodeError,
|
|
58
|
+
openRecommendCardDetailWithFreshRetry,
|
|
59
|
+
waitForRecommendDetailNetworkEvents
|
|
60
|
+
} from "./detail.js";
|
|
61
|
+
import {
|
|
62
|
+
readRecommendCardCandidate,
|
|
63
|
+
waitForRecommendCardNodeIds
|
|
64
|
+
} from "./cards.js";
|
|
65
|
+
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
66
|
+
import { ensureRecommendCurrentCityOnly } from "./location.js";
|
|
71
67
|
import {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
applyRecommendFilterEnvelopeStages,
|
|
69
|
+
buildRecommendFilterSelectionOptions,
|
|
70
|
+
refreshRecommendListAtEnd
|
|
71
|
+
} from "./refresh.js";
|
|
72
|
+
import { selectRecommendJob } from "./jobs.js";
|
|
73
|
+
import {
|
|
74
|
+
normalizeRecommendPageScope,
|
|
75
|
+
selectRecommendPageScope
|
|
76
|
+
} from "./scopes.js";
|
|
77
|
+
import { inspectRecentColleagueContact } from "./colleague-contact.js";
|
|
76
78
|
import {
|
|
77
79
|
RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
78
80
|
RECOMMEND_CARD_SELECTOR,
|
|
@@ -86,9 +88,304 @@ import {
|
|
|
86
88
|
resolveRecommendPostAction,
|
|
87
89
|
waitForRecommendDetailActionControls
|
|
88
90
|
} from "./actions.js";
|
|
89
|
-
import { getRecommendRoots } from "./roots.js";
|
|
90
|
-
|
|
91
|
-
|
|
91
|
+
import { getRecommendRoots } from "./roots.js";
|
|
92
|
+
|
|
93
|
+
const RECOMMEND_DEBUG_BOUNDARY_MODES = Object.freeze({
|
|
94
|
+
list_end: "debug_force_list_end_after_processed",
|
|
95
|
+
context_recovery: "debug_force_context_recovery_after_processed",
|
|
96
|
+
cdp_reconnect: "debug_force_cdp_reconnect_after_processed"
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
function hasOwn(source, key) {
|
|
100
|
+
return Boolean(source && Object.prototype.hasOwnProperty.call(source, key));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function readDebugBoundaryValue(source, snakeKey, camelKey) {
|
|
104
|
+
if (hasOwn(source, snakeKey)) return source[snakeKey];
|
|
105
|
+
if (hasOwn(source, camelKey)) return source[camelKey];
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function normalizeDebugBoundaryThreshold(raw, field) {
|
|
110
|
+
if (raw === undefined || raw === null || raw === "") return null;
|
|
111
|
+
const parsed = Number(raw);
|
|
112
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
113
|
+
const error = new Error(`${field} must be a positive integer`);
|
|
114
|
+
error.code = "INVALID_RECOMMEND_DEBUG_BOUNDARY";
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
return parsed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function normalizeRecommendDebugBoundaryOptions(source = {}) {
|
|
121
|
+
const debugTestMode = source.debugTestMode === true || source.debug_test_mode === true;
|
|
122
|
+
const thresholds = {
|
|
123
|
+
list_end: normalizeDebugBoundaryThreshold(
|
|
124
|
+
readDebugBoundaryValue(
|
|
125
|
+
source,
|
|
126
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.list_end,
|
|
127
|
+
"debugForceListEndAfterProcessed"
|
|
128
|
+
),
|
|
129
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.list_end
|
|
130
|
+
),
|
|
131
|
+
context_recovery: normalizeDebugBoundaryThreshold(
|
|
132
|
+
readDebugBoundaryValue(
|
|
133
|
+
source,
|
|
134
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.context_recovery,
|
|
135
|
+
"debugForceContextRecoveryAfterProcessed"
|
|
136
|
+
),
|
|
137
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.context_recovery
|
|
138
|
+
),
|
|
139
|
+
cdp_reconnect: normalizeDebugBoundaryThreshold(
|
|
140
|
+
readDebugBoundaryValue(
|
|
141
|
+
source,
|
|
142
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.cdp_reconnect,
|
|
143
|
+
"debugForceCdpReconnectAfterProcessed"
|
|
144
|
+
),
|
|
145
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.cdp_reconnect
|
|
146
|
+
)
|
|
147
|
+
};
|
|
148
|
+
const configured = Object.entries(thresholds).filter(([, threshold]) => threshold !== null);
|
|
149
|
+
if (configured.length > 1) {
|
|
150
|
+
const error = new Error(
|
|
151
|
+
`${configured.map(([mode]) => RECOMMEND_DEBUG_BOUNDARY_MODES[mode]).join(", ")} are mutually exclusive`
|
|
152
|
+
);
|
|
153
|
+
error.code = "RECOMMEND_DEBUG_BOUNDARIES_MUTUALLY_EXCLUSIVE";
|
|
154
|
+
throw error;
|
|
155
|
+
}
|
|
156
|
+
if (configured.length && !debugTestMode) {
|
|
157
|
+
const error = new Error(
|
|
158
|
+
`${RECOMMEND_DEBUG_BOUNDARY_MODES[configured[0][0]]} requires debug_test_mode=true`
|
|
159
|
+
);
|
|
160
|
+
error.code = "DEBUG_TEST_MODE_REQUIRED";
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
const [configuredEntry = null] = configured;
|
|
164
|
+
return {
|
|
165
|
+
debugTestMode,
|
|
166
|
+
mode: configuredEntry?.[0] || null,
|
|
167
|
+
field: configuredEntry ? RECOMMEND_DEBUG_BOUNDARY_MODES[configuredEntry[0]] : null,
|
|
168
|
+
threshold: configuredEntry?.[1] ?? null,
|
|
169
|
+
debugForceListEndAfterProcessed: thresholds.list_end,
|
|
170
|
+
debugForceContextRecoveryAfterProcessed: thresholds.context_recovery,
|
|
171
|
+
debugForceCdpReconnectAfterProcessed: thresholds.cdp_reconnect
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function createRecommendDebugBoundaryController(source = {}) {
|
|
176
|
+
const config = normalizeRecommendDebugBoundaryOptions(source);
|
|
177
|
+
let triggered = false;
|
|
178
|
+
let triggerCount = 0;
|
|
179
|
+
return {
|
|
180
|
+
config,
|
|
181
|
+
take(processedCount) {
|
|
182
|
+
const processed = Math.max(0, Number(processedCount) || 0);
|
|
183
|
+
if (triggered || !config.mode || processed < config.threshold) return null;
|
|
184
|
+
triggered = true;
|
|
185
|
+
triggerCount += 1;
|
|
186
|
+
return {
|
|
187
|
+
mode: config.mode,
|
|
188
|
+
field: config.field,
|
|
189
|
+
threshold: config.threshold,
|
|
190
|
+
processed,
|
|
191
|
+
trigger_count: triggerCount
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
getState() {
|
|
195
|
+
return {
|
|
196
|
+
...config,
|
|
197
|
+
triggered,
|
|
198
|
+
trigger_count: triggerCount
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function compactListReadStaleDiagnostic(error, {
|
|
205
|
+
attempt = 0,
|
|
206
|
+
exhausted = false
|
|
207
|
+
} = {}) {
|
|
208
|
+
return {
|
|
209
|
+
code: error?.code || "RECOMMEND_LIST_READ_STALE_NODE",
|
|
210
|
+
message: String(error?.message || error || "Stale recommend list node").slice(0, 500),
|
|
211
|
+
phase: "recommend:list-read",
|
|
212
|
+
cdp_method: error?.cdp_method || null,
|
|
213
|
+
cdp_at: error?.cdp_at ? String(error.cdp_at).slice(0, 100) : null,
|
|
214
|
+
cdp_node_id: Number.isInteger(error?.cdp_node_id) ? error.cdp_node_id : null,
|
|
215
|
+
cdp_backend_node_id: Number.isInteger(error?.cdp_backend_node_id)
|
|
216
|
+
? error.cdp_backend_node_id
|
|
217
|
+
: null,
|
|
218
|
+
cdp_param_keys: Array.isArray(error?.cdp_param_keys)
|
|
219
|
+
? error.cdp_param_keys
|
|
220
|
+
.map((key) => String(key || "").trim())
|
|
221
|
+
.filter((key) => /^[A-Za-z][A-Za-z0-9_]*$/.test(key))
|
|
222
|
+
.slice(0, 20)
|
|
223
|
+
: [],
|
|
224
|
+
attempt,
|
|
225
|
+
exhausted: Boolean(exhausted),
|
|
226
|
+
at: new Date().toISOString()
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function annotateListReadStaleFailure(error, diagnostics, {
|
|
231
|
+
exhausted = false,
|
|
232
|
+
recoveryFailed = false
|
|
233
|
+
} = {}) {
|
|
234
|
+
if (!error || typeof error !== "object") return error;
|
|
235
|
+
error.phase = error.phase || "recommend:list-read";
|
|
236
|
+
error.list_read_stale_recovery_attempts = diagnostics;
|
|
237
|
+
if (exhausted) error.list_read_stale_recovery_exhausted = true;
|
|
238
|
+
if (recoveryFailed) error.list_read_stale_recovery_failed = true;
|
|
239
|
+
return error;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export async function acquireRecommendListReadWithStaleRecovery({
|
|
243
|
+
acquire,
|
|
244
|
+
recover,
|
|
245
|
+
maxRetries = 2,
|
|
246
|
+
onStale = null,
|
|
247
|
+
onRecoveryApplied = null,
|
|
248
|
+
onRecovered = null,
|
|
249
|
+
onExhausted = null
|
|
250
|
+
} = {}) {
|
|
251
|
+
if (typeof acquire !== "function") {
|
|
252
|
+
throw new Error("acquireRecommendListReadWithStaleRecovery requires acquire");
|
|
253
|
+
}
|
|
254
|
+
if (typeof recover !== "function") {
|
|
255
|
+
throw new Error("acquireRecommendListReadWithStaleRecovery requires recover");
|
|
256
|
+
}
|
|
257
|
+
const retryLimit = Math.max(0, Number.isInteger(maxRetries) ? maxRetries : 2);
|
|
258
|
+
const diagnostics = [];
|
|
259
|
+
let acquireAttempt = 0;
|
|
260
|
+
let pendingRecoveryDiagnostic = null;
|
|
261
|
+
while (true) {
|
|
262
|
+
acquireAttempt += 1;
|
|
263
|
+
try {
|
|
264
|
+
const result = await acquire({
|
|
265
|
+
acquireAttempt,
|
|
266
|
+
recoveryCount: diagnostics.filter((item) => item.recovered === true).length
|
|
267
|
+
});
|
|
268
|
+
if (pendingRecoveryDiagnostic) {
|
|
269
|
+
pendingRecoveryDiagnostic.recovered = true;
|
|
270
|
+
pendingRecoveryDiagnostic.recovered_at = new Date().toISOString();
|
|
271
|
+
if (typeof onRecovered === "function") {
|
|
272
|
+
await onRecovered({
|
|
273
|
+
diagnostic: pendingRecoveryDiagnostic,
|
|
274
|
+
diagnostics: diagnostics.slice()
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
pendingRecoveryDiagnostic = null;
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
result,
|
|
281
|
+
acquire_attempts: acquireAttempt,
|
|
282
|
+
stale_diagnostics: diagnostics
|
|
283
|
+
};
|
|
284
|
+
} catch (error) {
|
|
285
|
+
if (!isStaleRecommendNodeError(error)) throw error;
|
|
286
|
+
pendingRecoveryDiagnostic = null;
|
|
287
|
+
const staleAttempt = diagnostics.length + 1;
|
|
288
|
+
const exhausted = staleAttempt > retryLimit;
|
|
289
|
+
const diagnostic = compactListReadStaleDiagnostic(error, {
|
|
290
|
+
attempt: staleAttempt,
|
|
291
|
+
exhausted
|
|
292
|
+
});
|
|
293
|
+
diagnostics.push(diagnostic);
|
|
294
|
+
if (exhausted) {
|
|
295
|
+
if (typeof onExhausted === "function") {
|
|
296
|
+
await onExhausted({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
297
|
+
}
|
|
298
|
+
throw annotateListReadStaleFailure(error, diagnostics, { exhausted: true });
|
|
299
|
+
}
|
|
300
|
+
if (typeof onStale === "function") {
|
|
301
|
+
await onStale({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
302
|
+
}
|
|
303
|
+
let recoveryResult = null;
|
|
304
|
+
try {
|
|
305
|
+
recoveryResult = await recover({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
306
|
+
} catch (recoveryError) {
|
|
307
|
+
if (recoveryError && typeof recoveryError === "object" && !recoveryError.cause) {
|
|
308
|
+
recoveryError.cause = error;
|
|
309
|
+
}
|
|
310
|
+
throw annotateListReadStaleFailure(recoveryError, diagnostics, {
|
|
311
|
+
recoveryFailed: true
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
diagnostic.recovery_applied = true;
|
|
315
|
+
diagnostic.recovery_mode = recoveryResult?.recovery_mode || "unknown";
|
|
316
|
+
diagnostic.recovery_applied_at = new Date().toISOString();
|
|
317
|
+
pendingRecoveryDiagnostic = diagnostic;
|
|
318
|
+
if (typeof onRecoveryApplied === "function") {
|
|
319
|
+
await onRecoveryApplied({
|
|
320
|
+
error,
|
|
321
|
+
diagnostic,
|
|
322
|
+
recoveryResult,
|
|
323
|
+
diagnostics: diagnostics.slice()
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export async function recoverRecommendListReadStaleContext({
|
|
331
|
+
staleAttempt = 1,
|
|
332
|
+
listState,
|
|
333
|
+
rootReacquire,
|
|
334
|
+
contextReapply
|
|
335
|
+
} = {}) {
|
|
336
|
+
if (typeof rootReacquire !== "function") {
|
|
337
|
+
throw new Error("recoverRecommendListReadStaleContext requires rootReacquire");
|
|
338
|
+
}
|
|
339
|
+
if (typeof contextReapply !== "function") {
|
|
340
|
+
throw new Error("recoverRecommendListReadStaleContext requires contextReapply");
|
|
341
|
+
}
|
|
342
|
+
const processedKeys = new Set(listState?.processed_keys || []);
|
|
343
|
+
try {
|
|
344
|
+
if (Number(staleAttempt) <= 1) {
|
|
345
|
+
try {
|
|
346
|
+
const rootResult = await rootReacquire();
|
|
347
|
+
if (Number(rootResult?.card_count) <= 0) {
|
|
348
|
+
const noCardsError = new Error("Recommend list root reacquire returned no candidate cards");
|
|
349
|
+
noCardsError.code = "RECOMMEND_LIST_ROOT_REACQUIRE_NO_CARDS";
|
|
350
|
+
throw noCardsError;
|
|
351
|
+
}
|
|
352
|
+
resetInfiniteListForRefreshRound(listState, {
|
|
353
|
+
reason: "list_read_stale_node_root_reacquire",
|
|
354
|
+
round: 1,
|
|
355
|
+
method: "root_reacquire",
|
|
356
|
+
metadata: {
|
|
357
|
+
card_count: Number(rootResult?.card_count) || 0,
|
|
358
|
+
processed_count: processedKeys.size
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
recovery_mode: "root_reacquire",
|
|
363
|
+
root_reacquire: rootResult || null
|
|
364
|
+
};
|
|
365
|
+
} catch (rootReacquireError) {
|
|
366
|
+
const contextResult = await contextReapply({ rootReacquireError });
|
|
367
|
+
return {
|
|
368
|
+
recovery_mode: "context_reapply",
|
|
369
|
+
escalated_from: "root_reacquire",
|
|
370
|
+
root_reacquire_error: compactListReadStaleDiagnostic(rootReacquireError, {
|
|
371
|
+
attempt: 1
|
|
372
|
+
}),
|
|
373
|
+
context_reapply: contextResult || null
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const contextResult = await contextReapply({ rootReacquireError: null });
|
|
378
|
+
return {
|
|
379
|
+
recovery_mode: "context_reapply",
|
|
380
|
+
escalated_from: "repeated_stale",
|
|
381
|
+
context_reapply: contextResult || null
|
|
382
|
+
};
|
|
383
|
+
} finally {
|
|
384
|
+
for (const key of processedKeys) listState?.processed_keys?.add(key);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function normalizeLabels(labels = []) {
|
|
92
389
|
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
93
390
|
}
|
|
94
391
|
|
|
@@ -104,26 +401,46 @@ function isRefreshableListStall(reason = "") {
|
|
|
104
401
|
function normalizeFilter(filter = {}) {
|
|
105
402
|
const filterGroups = Array.isArray(filter.filterGroups)
|
|
106
403
|
? filter.filterGroups
|
|
107
|
-
: Array.isArray(filter.groups)
|
|
108
|
-
? filter.groups
|
|
109
|
-
: [];
|
|
110
|
-
return {
|
|
111
|
-
enabled: filter.enabled !== false,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
404
|
+
: Array.isArray(filter.groups)
|
|
405
|
+
? filter.groups
|
|
406
|
+
: [];
|
|
407
|
+
return {
|
|
408
|
+
enabled: filter.enabled !== false,
|
|
409
|
+
currentCityOnly: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
410
|
+
group: String(filter.group || ""),
|
|
411
|
+
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
412
|
+
selectAllLabels: Boolean(filter.selectAllLabels),
|
|
413
|
+
allowUnlimited: filter.allowUnlimited === true,
|
|
414
|
+
verifySticky: filter.verifySticky === true,
|
|
415
|
+
filterGroups: filterGroups.map((group) => ({
|
|
416
|
+
group: String(group?.group || ""),
|
|
417
|
+
labels: normalizeLabels(group?.labels || group?.filterLabels || []),
|
|
418
|
+
selectAllLabels: group?.selectAllLabels !== false,
|
|
419
|
+
allowUnlimited: group?.allowUnlimited === true,
|
|
420
|
+
verifySticky: group?.verifySticky === true
|
|
421
|
+
})).filter((group) => group.group || group.labels.length)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
export function compactFilterResult(filterResult) {
|
|
426
|
+
if (!filterResult) return null;
|
|
427
|
+
return {
|
|
428
|
+
opened_panel: Boolean(filterResult.opened_panel),
|
|
429
|
+
requested_groups: (filterResult.requested_groups || []).map((group) => ({
|
|
430
|
+
group: group.group,
|
|
431
|
+
labels: group.labels || [],
|
|
432
|
+
select_all_labels: group.select_all_labels !== false,
|
|
433
|
+
allow_unlimited: Boolean(group.allow_unlimited),
|
|
434
|
+
verify_sticky: Boolean(group.verify_sticky)
|
|
435
|
+
})),
|
|
436
|
+
effective_groups: (filterResult.sticky_verification?.groups || []).map((group) => ({
|
|
437
|
+
group: group.group,
|
|
438
|
+
requested_labels: group.requested_labels || [],
|
|
439
|
+
active_labels: group.active_labels || [],
|
|
440
|
+
verified: Boolean(group.verified),
|
|
441
|
+
unavailable: Boolean(group.unavailable),
|
|
442
|
+
reason: group.reason || null
|
|
443
|
+
})),
|
|
127
444
|
selected_option: filterResult.selected_option
|
|
128
445
|
? {
|
|
129
446
|
group: filterResult.selected_option.group,
|
|
@@ -133,49 +450,98 @@ function compactFilterResult(filterResult) {
|
|
|
133
450
|
}
|
|
134
451
|
: null,
|
|
135
452
|
selected_options: (filterResult.selected_options || []).map((option) => ({
|
|
136
|
-
group: option.group,
|
|
137
|
-
label: option.label,
|
|
138
|
-
was_active: Boolean(option.was_active),
|
|
139
|
-
clicked: option.clicked !== false
|
|
140
|
-
})),
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
453
|
+
group: option.group,
|
|
454
|
+
label: option.label,
|
|
455
|
+
was_active: Boolean(option.was_active),
|
|
456
|
+
clicked: option.clicked !== false
|
|
457
|
+
})),
|
|
458
|
+
unavailable: Boolean(filterResult.unavailable),
|
|
459
|
+
unavailable_groups: filterResult.unavailable_groups || [],
|
|
460
|
+
confirmed: Boolean(filterResult.confirmed),
|
|
461
|
+
sticky_verification: filterResult.sticky_verification || null,
|
|
462
|
+
attempts: {
|
|
463
|
+
initial_close: filterResult.initial_close_attempts || [],
|
|
464
|
+
open: (filterResult.open_attempts || []).map((attempt) => ({
|
|
465
|
+
selector: attempt.selector || null,
|
|
466
|
+
node_id: attempt.node_id || null,
|
|
467
|
+
click_target: attempt.click_target || null
|
|
468
|
+
})),
|
|
469
|
+
confirmation: (filterResult.confirm_attempts || []).map((attempt) => ({
|
|
470
|
+
node_id: attempt.node_id || null,
|
|
471
|
+
label: attempt.label || null,
|
|
472
|
+
clicked: Boolean(attempt.clicked),
|
|
473
|
+
errors: (attempt.errors || []).map((error) => ({
|
|
474
|
+
node_id: error.node_id || null,
|
|
475
|
+
message: error.message || String(error)
|
|
476
|
+
}))
|
|
477
|
+
}))
|
|
478
|
+
},
|
|
479
|
+
before_counts: filterResult.before_counts,
|
|
480
|
+
after_confirm_counts: filterResult.after_confirm_counts
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function compactCurrentCityOnlyResult(result) {
|
|
485
|
+
if (!result) return null;
|
|
486
|
+
return {
|
|
487
|
+
requested: Boolean(result.requested),
|
|
488
|
+
effective: typeof result.effective === "boolean" ? result.effective : null,
|
|
489
|
+
available: result.available !== false,
|
|
490
|
+
unavailable: Boolean(result.unavailable),
|
|
491
|
+
reason: result.reason || null,
|
|
492
|
+
clicked: Boolean(result.clicked),
|
|
493
|
+
current_city_label: result.current_city_label || null,
|
|
494
|
+
before: result.before || null,
|
|
495
|
+
after_toggle: result.after_toggle || null,
|
|
496
|
+
confirmation: result.confirmation || null,
|
|
497
|
+
sticky_verification: result.sticky_verification
|
|
163
498
|
? {
|
|
164
|
-
verified: Boolean(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
ok: Boolean(jobSelection.sticky_verification.menu_close.ok),
|
|
172
|
-
closed: Boolean(jobSelection.sticky_verification.menu_close.closed),
|
|
173
|
-
reason: jobSelection.sticky_verification.menu_close.reason || ""
|
|
174
|
-
}
|
|
175
|
-
: null
|
|
499
|
+
verified: Boolean(result.sticky_verification.verified),
|
|
500
|
+
expected: Boolean(result.sticky_verification.expected),
|
|
501
|
+
actual: typeof result.sticky_verification.actual === "boolean"
|
|
502
|
+
? result.sticky_verification.actual
|
|
503
|
+
: null,
|
|
504
|
+
state_source: result.sticky_verification.state_source || null,
|
|
505
|
+
close_confirmation: result.sticky_verification.close_confirmation || null
|
|
176
506
|
}
|
|
177
507
|
: null,
|
|
178
|
-
|
|
508
|
+
attempts: Array.isArray(result.attempts) ? result.attempts : [],
|
|
509
|
+
evidence: result.evidence || null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function compactJobSelection(jobSelection) {
|
|
514
|
+
if (!jobSelection) return null;
|
|
515
|
+
return {
|
|
516
|
+
requested: jobSelection.requested || "",
|
|
517
|
+
selected: Boolean(jobSelection.selected),
|
|
518
|
+
already_current: Boolean(jobSelection.already_current),
|
|
519
|
+
reason: jobSelection.reason || null,
|
|
520
|
+
selected_option: jobSelection.selected_option || null,
|
|
521
|
+
menu_close: jobSelection.menu_close
|
|
522
|
+
? {
|
|
523
|
+
ok: Boolean(jobSelection.menu_close.ok),
|
|
524
|
+
closed: Boolean(jobSelection.menu_close.closed),
|
|
525
|
+
reason: jobSelection.menu_close.reason || ""
|
|
526
|
+
}
|
|
527
|
+
: null,
|
|
528
|
+
sticky_verification: jobSelection.sticky_verification
|
|
529
|
+
? {
|
|
530
|
+
verified: Boolean(jobSelection.sticky_verification.verified),
|
|
531
|
+
current_label: jobSelection.sticky_verification.current_label_without_salary
|
|
532
|
+
|| jobSelection.sticky_verification.current_label
|
|
533
|
+
|| "",
|
|
534
|
+
visible_option_count: jobSelection.sticky_verification.visible_option_count || 0,
|
|
535
|
+
menu_close: jobSelection.sticky_verification.menu_close
|
|
536
|
+
? {
|
|
537
|
+
ok: Boolean(jobSelection.sticky_verification.menu_close.ok),
|
|
538
|
+
closed: Boolean(jobSelection.sticky_verification.menu_close.closed),
|
|
539
|
+
reason: jobSelection.sticky_verification.menu_close.reason || ""
|
|
540
|
+
}
|
|
541
|
+
: null
|
|
542
|
+
}
|
|
543
|
+
: null,
|
|
544
|
+
options: (jobSelection.options || []).map((option) => ({
|
|
179
545
|
label: option.label,
|
|
180
546
|
label_without_salary: option.label_without_salary,
|
|
181
547
|
current: Boolean(option.current),
|
|
@@ -225,20 +591,20 @@ function compactCandidate(candidate) {
|
|
|
225
591
|
};
|
|
226
592
|
}
|
|
227
593
|
|
|
228
|
-
function compactDetail(detailResult) {
|
|
229
|
-
if (!detailResult) return null;
|
|
230
|
-
return {
|
|
231
|
-
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
232
|
-
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
594
|
+
function compactDetail(detailResult) {
|
|
595
|
+
if (!detailResult) return null;
|
|
596
|
+
return {
|
|
597
|
+
popup_text_length: detailResult.detail?.popup_text?.length || 0,
|
|
598
|
+
resume_text_length: detailResult.detail?.resume_text?.length || 0,
|
|
233
599
|
network_body_count: detailResult.network_bodies?.filter((item) => item.body).length || 0,
|
|
234
600
|
parsed_network_profile_count: detailResult.parsed_network_profiles?.filter((item) => item.ok).length || 0,
|
|
235
|
-
cv_acquisition: detailResult.cv_acquisition || null,
|
|
236
|
-
colleague_contact: detailResult.colleague_contact || null,
|
|
237
|
-
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
238
|
-
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
239
|
-
close_result: detailResult.close_result
|
|
240
|
-
};
|
|
241
|
-
}
|
|
601
|
+
cv_acquisition: detailResult.cv_acquisition || null,
|
|
602
|
+
colleague_contact: detailResult.colleague_contact || null,
|
|
603
|
+
image_evidence: summarizeImageEvidence(detailResult.image_evidence),
|
|
604
|
+
llm_screening: compactScreeningLlmResult(detailResult.llm_result),
|
|
605
|
+
close_result: detailResult.close_result
|
|
606
|
+
};
|
|
607
|
+
}
|
|
242
608
|
|
|
243
609
|
function normalizeScreeningMode(value) {
|
|
244
610
|
const normalized = String(value || "llm").trim().toLowerCase();
|
|
@@ -287,22 +653,22 @@ async function runRecommendPostAction({
|
|
|
287
653
|
reason: ""
|
|
288
654
|
};
|
|
289
655
|
|
|
290
|
-
if (!screening?.passed) {
|
|
291
|
-
result.reason = "screening_not_passed";
|
|
292
|
-
return result;
|
|
293
|
-
}
|
|
294
|
-
if (plan.effective === "none") {
|
|
295
|
-
result.reason = plan.reason === "greet_limit_reached" ? "greet_limit_reached" : "post_action_none";
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const summary = actionDiscovery?.summary || {};
|
|
300
|
-
const control = summary.greet;
|
|
301
|
-
if (!control?.found) {
|
|
302
|
-
result.reason = `${plan.effective}_control_not_found`;
|
|
303
|
-
return result;
|
|
304
|
-
}
|
|
305
|
-
result.control = control;
|
|
656
|
+
if (!screening?.passed) {
|
|
657
|
+
result.reason = "screening_not_passed";
|
|
658
|
+
return result;
|
|
659
|
+
}
|
|
660
|
+
if (plan.effective === "none") {
|
|
661
|
+
result.reason = plan.reason === "greet_limit_reached" ? "greet_limit_reached" : "post_action_none";
|
|
662
|
+
return result;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const summary = actionDiscovery?.summary || {};
|
|
666
|
+
const control = summary.greet;
|
|
667
|
+
if (!control?.found) {
|
|
668
|
+
result.reason = `${plan.effective}_control_not_found`;
|
|
669
|
+
return result;
|
|
670
|
+
}
|
|
671
|
+
result.control = control;
|
|
306
672
|
|
|
307
673
|
if (plan.effective === "greet" && control.continue_chat) {
|
|
308
674
|
result.reason = "already_connected_continue_chat";
|
|
@@ -319,10 +685,10 @@ async function runRecommendPostAction({
|
|
|
319
685
|
result.reason = "greet_control_not_available";
|
|
320
686
|
return result;
|
|
321
687
|
}
|
|
322
|
-
if (control.disabled) {
|
|
323
|
-
result.reason = `${plan.effective}_control_disabled`;
|
|
324
|
-
return result;
|
|
325
|
-
}
|
|
688
|
+
if (control.disabled) {
|
|
689
|
+
result.reason = `${plan.effective}_control_disabled`;
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
326
692
|
if (!executePostAction) {
|
|
327
693
|
result.reason = "dry_run_post_action";
|
|
328
694
|
result.would_click = true;
|
|
@@ -357,18 +723,18 @@ async function runRecommendPostAction({
|
|
|
357
723
|
timeoutMs: 2500,
|
|
358
724
|
intervalMs: 300,
|
|
359
725
|
requireAny: false
|
|
360
|
-
});
|
|
361
|
-
const afterSummary = afterDiscovery?.summary || {};
|
|
362
|
-
const afterControl = afterSummary.greet;
|
|
363
|
-
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
364
|
-
result.control_after = afterControl || null;
|
|
365
|
-
if (plan.effective === "greet") {
|
|
366
|
-
result.verified_after_click = Boolean(
|
|
367
|
-
afterControl?.continue_chat
|
|
368
|
-
|| String(afterControl?.label || "").includes("继续沟通")
|
|
369
|
-
);
|
|
370
|
-
}
|
|
371
|
-
} catch (error) {
|
|
726
|
+
});
|
|
727
|
+
const afterSummary = afterDiscovery?.summary || {};
|
|
728
|
+
const afterControl = afterSummary.greet;
|
|
729
|
+
result.action_discovery_after = compactActionDiscovery(afterDiscovery);
|
|
730
|
+
result.control_after = afterControl || null;
|
|
731
|
+
if (plan.effective === "greet") {
|
|
732
|
+
result.verified_after_click = Boolean(
|
|
733
|
+
afterControl?.continue_chat
|
|
734
|
+
|| String(afterControl?.label || "").includes("继续沟通")
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
} catch (error) {
|
|
372
738
|
result.verify_error = {
|
|
373
739
|
message: error?.message || String(error)
|
|
374
740
|
};
|
|
@@ -380,178 +746,244 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
380
746
|
if (!refreshAttempt) return null;
|
|
381
747
|
return {
|
|
382
748
|
ok: Boolean(refreshAttempt.ok),
|
|
383
|
-
method: refreshAttempt.method || "",
|
|
384
|
-
reason: refreshAttempt.reason || null,
|
|
385
|
-
error: refreshAttempt.error || null,
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
749
|
+
method: refreshAttempt.method || "",
|
|
750
|
+
reason: refreshAttempt.reason || null,
|
|
751
|
+
error: refreshAttempt.error || null,
|
|
752
|
+
error_diagnostic: refreshAttempt.error_diagnostic || null,
|
|
753
|
+
forced_recent_not_view: Boolean(refreshAttempt.forced_recent_not_view),
|
|
754
|
+
target_url: refreshAttempt.target_url || null,
|
|
755
|
+
card_count: refreshAttempt.card_count || 0,
|
|
756
|
+
elapsed_ms: refreshAttempt.elapsed_ms || 0,
|
|
757
|
+
recovery_settle: refreshAttempt.recovery_settle
|
|
758
|
+
? {
|
|
759
|
+
ok: Boolean(refreshAttempt.recovery_settle.ok),
|
|
760
|
+
status: refreshAttempt.recovery_settle.status || "",
|
|
761
|
+
reason: refreshAttempt.recovery_settle.reason || "",
|
|
762
|
+
elapsed_ms: refreshAttempt.recovery_settle.elapsed_ms || 0
|
|
763
|
+
}
|
|
764
|
+
: null,
|
|
765
|
+
attempts: (refreshAttempt.attempts || []).map((attempt) => ({
|
|
399
766
|
ok: Boolean(attempt.ok),
|
|
400
|
-
method: attempt.method || "",
|
|
401
|
-
reason: attempt.reason || null,
|
|
402
|
-
error: attempt.error || null,
|
|
403
|
-
|
|
767
|
+
method: attempt.method || "",
|
|
768
|
+
reason: attempt.reason || null,
|
|
769
|
+
error: attempt.error || null,
|
|
770
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
771
|
+
label: attempt.label || null,
|
|
404
772
|
before_card_count: attempt.before_card_count || 0,
|
|
405
773
|
after_card_count: attempt.after_card_count || 0,
|
|
406
774
|
card_count: attempt.card_count || 0,
|
|
407
|
-
elapsed_ms: attempt.elapsed_ms || 0
|
|
775
|
+
elapsed_ms: attempt.elapsed_ms || 0,
|
|
776
|
+
current_city_only: compactCurrentCityOnlyResult(attempt.current_city_only),
|
|
777
|
+
current_city_only_attempts: (attempt.current_city_only_attempts || []).map((cityAttempt) => ({
|
|
778
|
+
ok: Boolean(cityAttempt.ok),
|
|
779
|
+
method: cityAttempt.method || "current_city_only_reapply",
|
|
780
|
+
reason: cityAttempt.reason || null,
|
|
781
|
+
error: cityAttempt.error || null,
|
|
782
|
+
error_diagnostic: cityAttempt.error_diagnostic || null,
|
|
783
|
+
attempt: cityAttempt.attempt || 0,
|
|
784
|
+
result: compactCurrentCityOnlyResult(cityAttempt.result)
|
|
785
|
+
})),
|
|
786
|
+
filter: compactFilterResult(attempt.filter)
|
|
408
787
|
})),
|
|
409
|
-
|
|
788
|
+
current_city_only_attempts: (refreshAttempt.current_city_only_attempts || []).map((attempt) => ({
|
|
410
789
|
ok: Boolean(attempt.ok),
|
|
411
|
-
method: attempt.method || "
|
|
790
|
+
method: attempt.method || "current_city_only_reapply",
|
|
412
791
|
reason: attempt.reason || null,
|
|
413
792
|
error: attempt.error || null,
|
|
414
|
-
|
|
793
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
794
|
+
attempt: attempt.attempt || 0,
|
|
795
|
+
result: compactCurrentCityOnlyResult(attempt.result)
|
|
415
796
|
})),
|
|
416
|
-
|
|
797
|
+
filter_reapply_attempts: (refreshAttempt.filter_reapply_attempts || []).map((attempt) => ({
|
|
417
798
|
ok: Boolean(attempt.ok),
|
|
799
|
+
method: attempt.method || "filter_reapply",
|
|
800
|
+
reason: attempt.reason || null,
|
|
801
|
+
error: attempt.error || null,
|
|
802
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
803
|
+
attempt: attempt.attempt || 0
|
|
804
|
+
})),
|
|
805
|
+
job_selection_attempts: (refreshAttempt.job_selection_attempts || []).map((attempt) => ({
|
|
806
|
+
ok: Boolean(attempt.ok),
|
|
418
807
|
method: attempt.method || "job_select",
|
|
419
808
|
reason: attempt.reason || null,
|
|
420
809
|
error: attempt.error || null,
|
|
421
|
-
|
|
422
|
-
|
|
810
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
811
|
+
attempt: attempt.attempt || 0,
|
|
812
|
+
iframe_document_node_id: attempt.iframe_document_node_id || 0,
|
|
423
813
|
selected: Boolean(attempt.selected),
|
|
424
814
|
selection_reason: attempt.selection_reason || null
|
|
425
815
|
})),
|
|
426
816
|
job_selection: compactJobSelection(refreshAttempt.job_selection),
|
|
427
817
|
page_scope: compactPageScopeSelection(refreshAttempt.page_scope),
|
|
818
|
+
current_city_only: compactCurrentCityOnlyResult(refreshAttempt.current_city_only),
|
|
428
819
|
filter: compactFilterResult(refreshAttempt.filter)
|
|
429
820
|
};
|
|
430
821
|
}
|
|
431
|
-
|
|
822
|
+
|
|
432
823
|
export function countRecommendResultStatuses(results = [], {
|
|
433
|
-
greetCount = 0
|
|
434
|
-
} = {}) {
|
|
435
|
-
return {
|
|
436
|
-
processed: results.length,
|
|
437
|
-
screened: results.length,
|
|
438
|
-
detail_opened: results.filter((item) => item.detail).length,
|
|
439
|
-
passed: results.filter((item) => item.screening?.passed).length,
|
|
440
|
-
skipped: results.filter((item) => item.screening?.passed === false).length,
|
|
441
|
-
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
442
|
-
greet_count: greetCount,
|
|
443
|
-
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
444
|
-
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
824
|
+
greetCount = 0
|
|
825
|
+
} = {}) {
|
|
826
|
+
return {
|
|
827
|
+
processed: results.length,
|
|
828
|
+
screened: results.length,
|
|
829
|
+
detail_opened: results.filter((item) => item.detail).length,
|
|
830
|
+
passed: results.filter((item) => item.screening?.passed).length,
|
|
831
|
+
skipped: results.filter((item) => item.screening?.passed === false).length,
|
|
832
|
+
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
833
|
+
greet_count: greetCount,
|
|
834
|
+
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
835
|
+
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
445
836
|
detail_open_failed: results.filter((item) => (
|
|
446
837
|
item.error?.code === "DETAIL_STALE_NODE"
|
|
447
838
|
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
448
|
-
)).length,
|
|
449
|
-
transient_recovered: results.filter((item) => (
|
|
450
|
-
item.error?.code === "DETAIL_STALE_NODE"
|
|
451
|
-
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
452
|
-
|| item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
|
|
453
|
-
|| item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
|
|
454
|
-
|| item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
|
|
455
|
-
)).length,
|
|
456
|
-
colleague_contact_checked: results.filter((item) => item.detail?.colleague_contact?.checked).length,
|
|
457
|
-
recent_colleague_contact_skipped: results.filter((item) => (
|
|
458
|
-
item.screening?.status === "skip"
|
|
459
|
-
&& item.screening?.reasons?.includes("skipped_recent_colleague_contact")
|
|
460
|
-
)).length,
|
|
461
|
-
colleague_contact_panel_missing: results.filter((item) => (
|
|
462
|
-
item.detail?.colleague_contact?.reason === "panel_missing"
|
|
463
|
-
)).length
|
|
464
|
-
};
|
|
465
|
-
}
|
|
839
|
+
)).length,
|
|
840
|
+
transient_recovered: results.filter((item) => (
|
|
841
|
+
item.error?.code === "DETAIL_STALE_NODE"
|
|
842
|
+
|| item.error?.code === "DETAIL_OPEN_FAILED"
|
|
843
|
+
|| item.error?.code === "IMAGE_CAPTURE_STALE_NODE"
|
|
844
|
+
|| item.error?.code === "IMAGE_CAPTURE_TIMEOUT"
|
|
845
|
+
|| item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT"
|
|
846
|
+
)).length,
|
|
847
|
+
colleague_contact_checked: results.filter((item) => item.detail?.colleague_contact?.checked).length,
|
|
848
|
+
recent_colleague_contact_skipped: results.filter((item) => (
|
|
849
|
+
item.screening?.status === "skip"
|
|
850
|
+
&& item.screening?.reasons?.includes("skipped_recent_colleague_contact")
|
|
851
|
+
)).length,
|
|
852
|
+
colleague_contact_panel_missing: results.filter((item) => (
|
|
853
|
+
item.detail?.colleague_contact?.reason === "panel_missing"
|
|
854
|
+
)).length
|
|
855
|
+
};
|
|
856
|
+
}
|
|
466
857
|
|
|
467
|
-
function countPassedResults(results = []) {
|
|
468
|
-
return countRecommendResultStatuses(results).passed;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function compactCloseResult(closeResult) {
|
|
472
|
-
if (!closeResult) return null;
|
|
473
|
-
const result = {
|
|
474
|
-
closed: Boolean(closeResult.closed),
|
|
475
|
-
reason: closeResult.reason || null,
|
|
476
|
-
probe: closeResult.probe || null,
|
|
477
|
-
attempts: closeResult.attempts || [],
|
|
478
|
-
verification: closeResult.verification || null
|
|
479
|
-
};
|
|
480
|
-
if (closeResult.already_closed !== undefined) {
|
|
481
|
-
result.already_closed = Boolean(closeResult.already_closed);
|
|
482
|
-
}
|
|
483
|
-
return result;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
487
|
-
if (!error) return null;
|
|
488
|
-
const result = {
|
|
489
|
-
code: error.code || fallbackCode,
|
|
490
|
-
message: error.message || String(error)
|
|
491
|
-
};
|
|
492
|
-
if (error.close_result) {
|
|
493
|
-
result.close_result = compactCloseResult(error.close_result);
|
|
494
|
-
}
|
|
495
|
-
if (error.phase) {
|
|
496
|
-
result.phase = error.phase;
|
|
497
|
-
}
|
|
858
|
+
function countPassedResults(results = []) {
|
|
859
|
+
return countRecommendResultStatuses(results).passed;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function compactCloseResult(closeResult) {
|
|
863
|
+
if (!closeResult) return null;
|
|
864
|
+
const result = {
|
|
865
|
+
closed: Boolean(closeResult.closed),
|
|
866
|
+
reason: closeResult.reason || null,
|
|
867
|
+
probe: closeResult.probe || null,
|
|
868
|
+
attempts: closeResult.attempts || [],
|
|
869
|
+
verification: closeResult.verification || null
|
|
870
|
+
};
|
|
871
|
+
if (closeResult.already_closed !== undefined) {
|
|
872
|
+
result.already_closed = Boolean(closeResult.already_closed);
|
|
873
|
+
}
|
|
874
|
+
return result;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
878
|
+
if (!error) return null;
|
|
879
|
+
const result = {
|
|
880
|
+
code: error.code || fallbackCode,
|
|
881
|
+
message: error.message || String(error)
|
|
882
|
+
};
|
|
883
|
+
if (error.close_result) {
|
|
884
|
+
result.close_result = compactCloseResult(error.close_result);
|
|
885
|
+
}
|
|
886
|
+
if (error.phase) {
|
|
887
|
+
result.phase = error.phase;
|
|
888
|
+
}
|
|
498
889
|
if (error.refresh_attempt) {
|
|
499
890
|
result.refresh_attempt = error.refresh_attempt;
|
|
500
891
|
}
|
|
501
|
-
if (error.
|
|
502
|
-
result.
|
|
503
|
-
}
|
|
504
|
-
if (error.target_count != null) {
|
|
505
|
-
result.target_count = error.target_count;
|
|
506
|
-
}
|
|
507
|
-
if (error.passed_count != null) {
|
|
508
|
-
result.passed_count = error.passed_count;
|
|
509
|
-
}
|
|
510
|
-
if (Array.isArray(error.recommend_detail_open_attempts)) {
|
|
511
|
-
result.recommend_detail_open_attempts = error.recommend_detail_open_attempts;
|
|
512
|
-
}
|
|
513
|
-
if (Array.isArray(error.click_attempts)) {
|
|
514
|
-
result.click_attempts = error.click_attempts;
|
|
892
|
+
if (error.error_diagnostic) {
|
|
893
|
+
result.error_diagnostic = error.error_diagnostic;
|
|
515
894
|
}
|
|
516
|
-
if (error.
|
|
517
|
-
result.
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
}
|
|
895
|
+
if (error.list_end_reason) {
|
|
896
|
+
result.list_end_reason = error.list_end_reason;
|
|
897
|
+
}
|
|
898
|
+
if (error.target_count != null) {
|
|
899
|
+
result.target_count = error.target_count;
|
|
900
|
+
}
|
|
901
|
+
if (error.passed_count != null) {
|
|
902
|
+
result.passed_count = error.passed_count;
|
|
903
|
+
}
|
|
904
|
+
if (Array.isArray(error.recommend_detail_open_attempts)) {
|
|
905
|
+
result.recommend_detail_open_attempts = error.recommend_detail_open_attempts;
|
|
906
|
+
}
|
|
907
|
+
if (Array.isArray(error.click_attempts)) {
|
|
908
|
+
result.click_attempts = error.click_attempts;
|
|
909
|
+
}
|
|
910
|
+
if (error.avatar_preview) {
|
|
911
|
+
result.avatar_preview = {
|
|
912
|
+
open: Boolean(error.avatar_preview.open),
|
|
913
|
+
selector: error.avatar_preview.preview?.selector || null,
|
|
914
|
+
rect: error.avatar_preview.preview?.rect || null
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
return result;
|
|
918
|
+
}
|
|
525
919
|
|
|
526
|
-
function createRecommendCloseFailureError(closeResult) {
|
|
527
|
-
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
528
|
-
error.code = "DETAIL_CLOSE_FAILED";
|
|
529
|
-
error.close_result = closeResult || null;
|
|
530
|
-
return error;
|
|
920
|
+
function createRecommendCloseFailureError(closeResult) {
|
|
921
|
+
const error = new Error(closeResult?.reason || "Recommend detail did not close before recovery");
|
|
922
|
+
error.code = "DETAIL_CLOSE_FAILED";
|
|
923
|
+
error.close_result = closeResult || null;
|
|
924
|
+
return error;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function createRecommendBlockingPanelCloseFailureError(closeResult, phase = "") {
|
|
928
|
+
const error = new Error(closeResult?.reason || "Boss account-rights panel did not close before recovery");
|
|
929
|
+
error.code = "ACCOUNT_RIGHTS_PANEL_CLOSE_FAILED";
|
|
930
|
+
error.close_result = closeResult || null;
|
|
931
|
+
error.phase = phase || null;
|
|
932
|
+
return error;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
function findRecommendRefreshErrorDiagnostic(refreshAttempt) {
|
|
936
|
+
if (!refreshAttempt || typeof refreshAttempt !== "object") return null;
|
|
937
|
+
if (refreshAttempt.error_diagnostic) return refreshAttempt.error_diagnostic;
|
|
938
|
+
const candidates = [
|
|
939
|
+
...(refreshAttempt.attempts || []),
|
|
940
|
+
...(refreshAttempt.current_city_only_attempts || []),
|
|
941
|
+
...(refreshAttempt.filter_reapply_attempts || []),
|
|
942
|
+
...(refreshAttempt.job_selection_attempts || [])
|
|
943
|
+
].reverse();
|
|
944
|
+
for (const attempt of candidates) {
|
|
945
|
+
if (attempt?.error_diagnostic) return attempt.error_diagnostic;
|
|
946
|
+
const nestedCityAttempts = (attempt?.current_city_only_attempts || []).slice().reverse();
|
|
947
|
+
for (const cityAttempt of nestedCityAttempts) {
|
|
948
|
+
if (cityAttempt?.error_diagnostic) return cityAttempt.error_diagnostic;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return null;
|
|
531
952
|
}
|
|
532
953
|
|
|
533
|
-
function
|
|
534
|
-
const
|
|
535
|
-
error
|
|
536
|
-
error.
|
|
537
|
-
|
|
954
|
+
function attachRecommendRefreshErrorDiagnostic(error, refreshAttempt) {
|
|
955
|
+
const diagnostic = findRecommendRefreshErrorDiagnostic(refreshAttempt);
|
|
956
|
+
if (!error || !diagnostic) return error;
|
|
957
|
+
error.error_diagnostic = diagnostic;
|
|
958
|
+
for (const key of [
|
|
959
|
+
"cdp_method",
|
|
960
|
+
"cdp_at",
|
|
961
|
+
"cdp_node_id",
|
|
962
|
+
"cdp_backend_node_id",
|
|
963
|
+
"cdp_search_id",
|
|
964
|
+
"cdp_param_keys"
|
|
965
|
+
]) {
|
|
966
|
+
if (diagnostic[key] !== undefined && error[key] === undefined) {
|
|
967
|
+
error[key] = diagnostic[key];
|
|
968
|
+
}
|
|
969
|
+
}
|
|
538
970
|
return error;
|
|
539
971
|
}
|
|
540
972
|
|
|
541
|
-
function createRecommendRefreshFailureError(refreshAttempt, {
|
|
542
|
-
listEndReason = "",
|
|
543
|
-
targetCount = 0,
|
|
544
|
-
passedCount = 0
|
|
545
|
-
} = {}) {
|
|
546
|
-
const reason = refreshAttempt?.reason || "refresh_failed";
|
|
547
|
-
const detail = refreshAttempt?.error ? `: ${refreshAttempt.error}` : "";
|
|
548
|
-
const error = new Error(`Recommend refresh failed before target was reached (${reason}${detail})`);
|
|
549
|
-
error.code = "RECOMMEND_END_REFRESH_FAILED";
|
|
550
|
-
error.refresh_attempt = refreshAttempt || null;
|
|
551
|
-
error.list_end_reason = listEndReason || null;
|
|
973
|
+
export function createRecommendRefreshFailureError(refreshAttempt, {
|
|
974
|
+
listEndReason = "",
|
|
975
|
+
targetCount = 0,
|
|
976
|
+
passedCount = 0
|
|
977
|
+
} = {}) {
|
|
978
|
+
const reason = refreshAttempt?.reason || "refresh_failed";
|
|
979
|
+
const detail = refreshAttempt?.error ? `: ${refreshAttempt.error}` : "";
|
|
980
|
+
const error = new Error(`Recommend refresh failed before target was reached (${reason}${detail})`);
|
|
981
|
+
error.code = "RECOMMEND_END_REFRESH_FAILED";
|
|
982
|
+
error.refresh_attempt = refreshAttempt || null;
|
|
983
|
+
error.list_end_reason = listEndReason || null;
|
|
552
984
|
error.target_count = targetCount;
|
|
553
985
|
error.passed_count = passedCount;
|
|
554
|
-
return error;
|
|
986
|
+
return attachRecommendRefreshErrorDiagnostic(error, refreshAttempt);
|
|
555
987
|
}
|
|
556
988
|
|
|
557
989
|
export function isRecoverableImageCaptureError(error) {
|
|
@@ -615,41 +1047,41 @@ function createImageCaptureFailureScreening(candidate, error) {
|
|
|
615
1047
|
};
|
|
616
1048
|
}
|
|
617
1049
|
|
|
618
|
-
export function isRecoverableRecommendDetailError(error) {
|
|
619
|
-
return isStaleRecommendNodeError(error) || isRecommendDetailOpenMissError(error);
|
|
620
|
-
}
|
|
1050
|
+
export function isRecoverableRecommendDetailError(error) {
|
|
1051
|
+
return isStaleRecommendNodeError(error) || isRecommendDetailOpenMissError(error);
|
|
1052
|
+
}
|
|
621
1053
|
|
|
622
1054
|
function compactRecoverableDetailError(error) {
|
|
623
1055
|
return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
|
|
624
1056
|
}
|
|
625
1057
|
|
|
626
|
-
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
627
|
-
return {
|
|
628
|
-
status: "fail",
|
|
629
|
-
passed: false,
|
|
630
|
-
score: 0,
|
|
631
|
-
reasons: isStaleRecommendNodeError(error)
|
|
632
|
-
? ["detail_open_failed", "stale_node"]
|
|
633
|
-
: isRecommendDetailOpenMissError(error)
|
|
634
|
-
? ["detail_open_failed", "detail_open_miss"]
|
|
635
|
-
: ["detail_open_failed"],
|
|
1058
|
+
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
1059
|
+
return {
|
|
1060
|
+
status: "fail",
|
|
1061
|
+
passed: false,
|
|
1062
|
+
score: 0,
|
|
1063
|
+
reasons: isStaleRecommendNodeError(error)
|
|
1064
|
+
? ["detail_open_failed", "stale_node"]
|
|
1065
|
+
: isRecommendDetailOpenMissError(error)
|
|
1066
|
+
? ["detail_open_failed", "detail_open_miss"]
|
|
1067
|
+
: ["detail_open_failed"],
|
|
636
1068
|
error: compactRecoverableDetailError(error),
|
|
637
|
-
candidate
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
function createRecentColleagueContactSkipScreening(candidate, colleagueContact) {
|
|
642
|
-
const matched = colleagueContact?.matched_row || null;
|
|
643
|
-
return {
|
|
644
|
-
status: "skip",
|
|
645
|
-
passed: false,
|
|
646
|
-
score: 0,
|
|
647
|
-
reasons: ["skipped_recent_colleague_contact"],
|
|
648
|
-
reason: matched?.text || "Candidate has recent colleague contact history",
|
|
649
|
-
matched_colleague_contact: matched,
|
|
650
|
-
candidate
|
|
651
|
-
};
|
|
652
|
-
}
|
|
1069
|
+
candidate
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
function createRecentColleagueContactSkipScreening(candidate, colleagueContact) {
|
|
1074
|
+
const matched = colleagueContact?.matched_row || null;
|
|
1075
|
+
return {
|
|
1076
|
+
status: "skip",
|
|
1077
|
+
passed: false,
|
|
1078
|
+
score: 0,
|
|
1079
|
+
reasons: ["skipped_recent_colleague_contact"],
|
|
1080
|
+
reason: matched?.text || "Candidate has recent colleague contact history",
|
|
1081
|
+
matched_colleague_contact: matched,
|
|
1082
|
+
candidate
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
653
1085
|
|
|
654
1086
|
export async function runRecommendWorkflow({
|
|
655
1087
|
client,
|
|
@@ -681,47 +1113,57 @@ export async function runRecommendWorkflow({
|
|
|
681
1113
|
executePostAction = true,
|
|
682
1114
|
actionTimeoutMs = 8000,
|
|
683
1115
|
actionIntervalMs = 500,
|
|
684
|
-
actionAfterClickDelayMs = 900,
|
|
685
|
-
screeningMode = "llm",
|
|
686
|
-
llmConfig = null,
|
|
687
|
-
llmTimeoutMs = 120000,
|
|
688
|
-
llmImageLimit = 8,
|
|
689
|
-
llmImageDetail = "high",
|
|
690
|
-
imageOutputDir = "",
|
|
1116
|
+
actionAfterClickDelayMs = 900,
|
|
1117
|
+
screeningMode = "llm",
|
|
1118
|
+
llmConfig = null,
|
|
1119
|
+
llmTimeoutMs = 120000,
|
|
1120
|
+
llmImageLimit = 8,
|
|
1121
|
+
llmImageDetail = "high",
|
|
1122
|
+
imageOutputDir = "",
|
|
691
1123
|
humanRestEnabled = false,
|
|
692
1124
|
humanBehavior = null,
|
|
693
1125
|
skipRecentColleagueContacted = true,
|
|
694
|
-
colleagueContactWindowDays = 14
|
|
1126
|
+
colleagueContactWindowDays = 14,
|
|
1127
|
+
debugTestMode = false,
|
|
1128
|
+
debugForceListEndAfterProcessed = null,
|
|
1129
|
+
debugForceContextRecoveryAfterProcessed = null,
|
|
1130
|
+
debugForceCdpReconnectAfterProcessed = null
|
|
695
1131
|
} = {}, runControl) {
|
|
696
1132
|
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
697
|
-
const
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
enabled: effectiveHumanBehavior.enabled,
|
|
703
|
-
clickMovementEnabled: effectiveHumanBehavior.clickMovement,
|
|
704
|
-
textEntryEnabled: effectiveHumanBehavior.textEntry,
|
|
705
|
-
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
706
|
-
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
1133
|
+
const debugBoundary = createRecommendDebugBoundaryController({
|
|
1134
|
+
debugTestMode,
|
|
1135
|
+
debugForceListEndAfterProcessed,
|
|
1136
|
+
debugForceContextRecoveryAfterProcessed,
|
|
1137
|
+
debugForceCdpReconnectAfterProcessed
|
|
707
1138
|
});
|
|
708
|
-
const
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
1139
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1140
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1141
|
+
});
|
|
1142
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1143
|
+
configureHumanInteraction(client, {
|
|
1144
|
+
enabled: effectiveHumanBehavior.enabled,
|
|
1145
|
+
clickMovementEnabled: effectiveHumanBehavior.clickMovement,
|
|
1146
|
+
textEntryEnabled: effectiveHumanBehavior.textEntry,
|
|
1147
|
+
safeClickPointEnabled: effectiveHumanBehavior.clickMovement,
|
|
1148
|
+
actionCooldownEnabled: effectiveHumanBehavior.actionCooldown
|
|
1149
|
+
});
|
|
1150
|
+
const humanRestController = createHumanRestController({
|
|
1151
|
+
enabled: effectiveHumanRestEnabled,
|
|
1152
|
+
shortRestEnabled: effectiveHumanBehavior.shortRest,
|
|
1153
|
+
batchRestEnabled: effectiveHumanBehavior.batchRest,
|
|
1154
|
+
restLevel: effectiveHumanBehavior.restLevel
|
|
1155
|
+
});
|
|
1156
|
+
const normalizedFilter = normalizeFilter(filter);
|
|
715
1157
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
716
1158
|
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
717
1159
|
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
718
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
719
|
-
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
720
|
-
const postActionEnabled = normalizedPostAction !== "none";
|
|
721
|
-
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
722
|
-
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
723
|
-
const colleagueContactReferenceDate = new Date();
|
|
724
|
-
const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
|
|
1160
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1161
|
+
const useLlmScreening = normalizedScreeningMode !== "deterministic";
|
|
1162
|
+
const postActionEnabled = normalizedPostAction !== "none";
|
|
1163
|
+
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
1164
|
+
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
1165
|
+
const colleagueContactReferenceDate = new Date();
|
|
1166
|
+
const targetPassCount = Math.max(1, Number(maxCandidates) || 1);
|
|
725
1167
|
const detailCountLimit = detailLimit == null ? Number.POSITIVE_INFINITY : Math.max(0, Number(detailLimit) || 0);
|
|
726
1168
|
const effectiveDetailLimit = postActionEnabled ? Number.POSITIVE_INFINITY : detailCountLimit;
|
|
727
1169
|
const networkRecorder = effectiveDetailLimit > 0
|
|
@@ -747,150 +1189,172 @@ export async function runRecommendWorkflow({
|
|
|
747
1189
|
const results = [];
|
|
748
1190
|
const refreshAttempts = [];
|
|
749
1191
|
let refreshRounds = 0;
|
|
750
|
-
let contextRecoveryAttempts = 0;
|
|
751
|
-
let greetCount = 0;
|
|
1192
|
+
let contextRecoveryAttempts = 0;
|
|
1193
|
+
let greetCount = 0;
|
|
752
1194
|
const candidateRecoveryCounts = new Map();
|
|
753
1195
|
let jobSelection = null;
|
|
754
1196
|
let pageScopeSelection = null;
|
|
1197
|
+
let currentCityOnlyResult = null;
|
|
755
1198
|
let filterResult = null;
|
|
756
1199
|
let rootState = null;
|
|
757
1200
|
let cardNodeIds = [];
|
|
758
1201
|
let listEndReason = "";
|
|
759
1202
|
let lastHumanEvent = null;
|
|
760
|
-
|
|
1203
|
+
let debugForceReconnectPending = null;
|
|
1204
|
+
let listReadStaleRecoveryAttempts = 0;
|
|
1205
|
+
let listReadStaleRecoveryApplied = 0;
|
|
1206
|
+
let listReadStaleRecoveries = 0;
|
|
1207
|
+
let lastListReadStaleDiagnostic = null;
|
|
1208
|
+
let lastListReadRecoveryMode = null;
|
|
1209
|
+
const listReadStaleDiagnostics = [];
|
|
1210
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
761
1211
|
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
762
1212
|
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
763
1213
|
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
764
1214
|
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
765
1215
|
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
766
1216
|
validateViewportPoint: true
|
|
767
|
-
}));
|
|
768
|
-
|
|
769
|
-
function recordHumanEvent(event = null) {
|
|
770
|
-
if (!event) return lastHumanEvent;
|
|
771
|
-
lastHumanEvent = {
|
|
772
|
-
at: new Date().toISOString(),
|
|
773
|
-
...event
|
|
774
|
-
};
|
|
775
|
-
return lastHumanEvent;
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
async function maybeHumanActionCooldown(phase, timings = {}) {
|
|
779
|
-
if (!effectiveHumanBehavior.actionCooldown) return null;
|
|
780
|
-
const pauseMs = humanDelay(280, 90, {
|
|
781
|
-
minMs: 80,
|
|
782
|
-
maxMs: 720
|
|
783
|
-
});
|
|
784
|
-
if (pauseMs > 0) {
|
|
785
|
-
await runControl.sleep(pauseMs);
|
|
786
|
-
addTiming(timings, `human_${phase}_pause_ms`, pauseMs);
|
|
787
|
-
}
|
|
788
|
-
return recordHumanEvent({
|
|
789
|
-
kind: "action_cooldown",
|
|
790
|
-
phase,
|
|
791
|
-
pause_ms: pauseMs
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
|
|
1217
|
+
}));
|
|
1218
|
+
|
|
1219
|
+
function recordHumanEvent(event = null) {
|
|
1220
|
+
if (!event) return lastHumanEvent;
|
|
1221
|
+
lastHumanEvent = {
|
|
1222
|
+
at: new Date().toISOString(),
|
|
1223
|
+
...event
|
|
1224
|
+
};
|
|
1225
|
+
return lastHumanEvent;
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
async function maybeHumanActionCooldown(phase, timings = {}) {
|
|
1229
|
+
if (!effectiveHumanBehavior.actionCooldown) return null;
|
|
1230
|
+
const pauseMs = humanDelay(280, 90, {
|
|
1231
|
+
minMs: 80,
|
|
1232
|
+
maxMs: 720
|
|
1233
|
+
});
|
|
1234
|
+
if (pauseMs > 0) {
|
|
1235
|
+
await runControl.sleep(pauseMs);
|
|
1236
|
+
addTiming(timings, `human_${phase}_pause_ms`, pauseMs);
|
|
1237
|
+
}
|
|
1238
|
+
return recordHumanEvent({
|
|
1239
|
+
kind: "action_cooldown",
|
|
1240
|
+
phase,
|
|
1241
|
+
pause_ms: pauseMs
|
|
1242
|
+
});
|
|
1243
|
+
}
|
|
1244
|
+
|
|
795
1245
|
function updateRecommendProgress(extra = {}) {
|
|
796
1246
|
const counts = countRecommendResultStatuses(results, { greetCount });
|
|
797
|
-
const listSnapshot = compactInfiniteListState(listState);
|
|
798
|
-
const humanRestState = humanRestController.getState();
|
|
799
|
-
runControl.updateProgress({
|
|
800
|
-
card_count: cardNodeIds.length,
|
|
1247
|
+
const listSnapshot = compactInfiniteListState(listState);
|
|
1248
|
+
const humanRestState = humanRestController.getState();
|
|
1249
|
+
runControl.updateProgress({
|
|
1250
|
+
card_count: cardNodeIds.length,
|
|
801
1251
|
target_count: targetPassCount,
|
|
802
1252
|
target_count_semantics: "passed_candidates",
|
|
803
|
-
...counts,
|
|
1253
|
+
...counts,
|
|
1254
|
+
transient_recovered: counts.transient_recovered + listReadStaleRecoveries,
|
|
804
1255
|
screening_mode: normalizedScreeningMode,
|
|
805
1256
|
unique_seen: listSnapshot.seen_count,
|
|
806
1257
|
scroll_count: listSnapshot.scroll_count,
|
|
807
1258
|
refresh_rounds: refreshRounds,
|
|
808
1259
|
refresh_attempts: refreshAttempts.length,
|
|
809
1260
|
context_recoveries: contextRecoveryAttempts,
|
|
810
|
-
list_end_reason: listEndReason || null,
|
|
811
|
-
viewport_checks: viewportGuard.getStats().checks,
|
|
812
|
-
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
813
|
-
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1261
|
+
list_end_reason: listEndReason || null,
|
|
1262
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
1263
|
+
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
1264
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
814
1265
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
815
1266
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
816
1267
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
817
1268
|
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
818
1269
|
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
1270
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
1271
|
+
current_city_only_effective: currentCityOnlyResult?.effective ?? null,
|
|
1272
|
+
current_city_only_unavailable: Boolean(currentCityOnlyResult?.unavailable),
|
|
819
1273
|
human_rest_count: humanRestState.rest_count,
|
|
820
1274
|
human_rest_ms: humanRestState.total_rest_ms,
|
|
821
1275
|
last_human_event: lastHumanEvent,
|
|
1276
|
+
debug_boundary_mode: debugBoundary.config.mode,
|
|
1277
|
+
debug_boundary_threshold: debugBoundary.config.threshold,
|
|
1278
|
+
debug_boundary_triggered: debugBoundary.getState().triggered,
|
|
1279
|
+
debug_boundary_trigger_count: debugBoundary.getState().trigger_count,
|
|
1280
|
+
list_read_stale_recovery_attempts: listReadStaleRecoveryAttempts,
|
|
1281
|
+
list_read_stale_recovery_applied: listReadStaleRecoveryApplied,
|
|
1282
|
+
list_read_stale_recoveries: listReadStaleRecoveries,
|
|
1283
|
+
last_list_read_recovery_mode: lastListReadRecoveryMode,
|
|
1284
|
+
last_list_read_stale_diagnostic: lastListReadStaleDiagnostic,
|
|
822
1285
|
...extra
|
|
823
1286
|
});
|
|
824
1287
|
}
|
|
825
1288
|
|
|
826
|
-
function checkpointInProgressCandidate({
|
|
827
|
-
index = results.length,
|
|
828
|
-
candidateKey = "",
|
|
1289
|
+
function checkpointInProgressCandidate({
|
|
1290
|
+
index = results.length,
|
|
1291
|
+
candidateKey = "",
|
|
829
1292
|
cardNodeId = null,
|
|
830
1293
|
detailStep = "",
|
|
831
1294
|
error = null
|
|
832
1295
|
} = {}) {
|
|
833
1296
|
runControl.checkpoint({
|
|
834
1297
|
in_progress_candidate: {
|
|
835
|
-
index,
|
|
836
|
-
key: candidateKey,
|
|
837
|
-
card_node_id: cardNodeId,
|
|
838
|
-
detail_step: detailStep || null,
|
|
839
|
-
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
840
|
-
error: compactError(error, "RECOMMEND_IN_PROGRESS_ERROR")
|
|
841
|
-
},
|
|
842
|
-
candidate_list: compactInfiniteListState(listState)
|
|
843
|
-
});
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
async function closeRecommendBlockingPanelsForRun(phase = "cleanup") {
|
|
847
|
-
const result = await closeRecommendBlockingPanels(client, {
|
|
848
|
-
attemptsLimit: 2,
|
|
849
|
-
rootState
|
|
850
|
-
});
|
|
851
|
-
if (!result?.closed) {
|
|
852
|
-
throw createRecommendBlockingPanelCloseFailureError(result, phase);
|
|
853
|
-
}
|
|
854
|
-
return result;
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
|
|
1298
|
+
index,
|
|
1299
|
+
key: candidateKey,
|
|
1300
|
+
card_node_id: cardNodeId,
|
|
1301
|
+
detail_step: detailStep || null,
|
|
1302
|
+
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
1303
|
+
error: compactError(error, "RECOMMEND_IN_PROGRESS_ERROR")
|
|
1304
|
+
},
|
|
1305
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1306
|
+
});
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
async function closeRecommendBlockingPanelsForRun(phase = "cleanup") {
|
|
1310
|
+
const result = await closeRecommendBlockingPanels(client, {
|
|
1311
|
+
attemptsLimit: 2,
|
|
1312
|
+
rootState
|
|
1313
|
+
});
|
|
1314
|
+
if (!result?.closed) {
|
|
1315
|
+
throw createRecommendBlockingPanelCloseFailureError(result, phase);
|
|
1316
|
+
}
|
|
1317
|
+
return result;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
|
|
858
1321
|
forceRecentNotView = true
|
|
859
1322
|
} = {}) {
|
|
860
|
-
await runControl.waitIfPaused();
|
|
861
|
-
runControl.throwIfCanceled();
|
|
862
|
-
const started = Date.now();
|
|
863
|
-
runControl.setPhase("recommend:recover-context");
|
|
864
|
-
contextRecoveryAttempts += 1;
|
|
1323
|
+
await runControl.waitIfPaused();
|
|
1324
|
+
runControl.throwIfCanceled();
|
|
1325
|
+
const started = Date.now();
|
|
1326
|
+
runControl.setPhase("recommend:recover-context");
|
|
1327
|
+
contextRecoveryAttempts += 1;
|
|
1328
|
+
const effectiveForceRecentNotView = normalizedFilter.enabled && forceRecentNotView;
|
|
865
1329
|
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
866
1330
|
rootState,
|
|
867
1331
|
jobLabel,
|
|
868
|
-
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
869
|
-
fallbackPageScope: normalizedFallbackPageScope,
|
|
870
|
-
filter: normalizedFilter,
|
|
871
|
-
preferEndRefreshButton: false,
|
|
872
|
-
forceNavigate: true,
|
|
873
|
-
targetUrl: targetUrl || RECOMMEND_TARGET_URL,
|
|
874
|
-
forceRecentNotView,
|
|
875
|
-
cardTimeoutMs,
|
|
1332
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1333
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
1334
|
+
filter: normalizedFilter,
|
|
1335
|
+
preferEndRefreshButton: false,
|
|
1336
|
+
forceNavigate: true,
|
|
1337
|
+
targetUrl: targetUrl || RECOMMEND_TARGET_URL,
|
|
1338
|
+
forceRecentNotView: effectiveForceRecentNotView,
|
|
1339
|
+
cardTimeoutMs,
|
|
876
1340
|
buttonSettleMs: refreshButtonSettleMs,
|
|
877
1341
|
reloadSettleMs: refreshReloadSettleMs
|
|
878
1342
|
});
|
|
879
1343
|
let blockingPanelClose = null;
|
|
880
|
-
if (refreshResult.ok) {
|
|
881
|
-
blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
882
|
-
attemptsLimit: 2,
|
|
883
|
-
rootState: refreshResult.root_state || rootState
|
|
884
|
-
});
|
|
885
|
-
}
|
|
886
|
-
const compactRefresh = {
|
|
887
|
-
...compactRefreshAttempt(refreshResult),
|
|
888
|
-
context_recovery: true,
|
|
889
|
-
recovery_reason: reason,
|
|
890
|
-
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
891
|
-
account_rights_panel_close: compactCloseResult(blockingPanelClose),
|
|
892
|
-
elapsed_ms: Date.now() - started
|
|
893
|
-
};
|
|
1344
|
+
if (refreshResult.ok) {
|
|
1345
|
+
blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1346
|
+
attemptsLimit: 2,
|
|
1347
|
+
rootState: refreshResult.root_state || rootState
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
const compactRefresh = {
|
|
1351
|
+
...compactRefreshAttempt(refreshResult),
|
|
1352
|
+
context_recovery: true,
|
|
1353
|
+
recovery_reason: reason,
|
|
1354
|
+
trigger_error: compactError(error, "RECOMMEND_CONTEXT_RECOVERY_TRIGGER"),
|
|
1355
|
+
account_rights_panel_close: compactCloseResult(blockingPanelClose),
|
|
1356
|
+
elapsed_ms: Date.now() - started
|
|
1357
|
+
};
|
|
894
1358
|
refreshAttempts.push(compactRefresh);
|
|
895
1359
|
runControl.checkpoint({
|
|
896
1360
|
context_recovery: {
|
|
@@ -900,56 +1364,68 @@ export async function runRecommendWorkflow({
|
|
|
900
1364
|
refresh: compactRefresh,
|
|
901
1365
|
counters: countRecommendResultStatuses(results, { greetCount })
|
|
902
1366
|
},
|
|
903
|
-
candidate_list: compactInfiniteListState(listState)
|
|
904
|
-
});
|
|
905
|
-
if (!refreshResult.ok) {
|
|
906
|
-
updateRecommendProgress({
|
|
907
|
-
refresh_method: refreshResult.method || null,
|
|
908
|
-
refresh_forced_recent_not_view:
|
|
909
|
-
recovery_reason: reason
|
|
1367
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1368
|
+
});
|
|
1369
|
+
if (!refreshResult.ok) {
|
|
1370
|
+
updateRecommendProgress({
|
|
1371
|
+
refresh_method: refreshResult.method || null,
|
|
1372
|
+
refresh_forced_recent_not_view: effectiveForceRecentNotView,
|
|
1373
|
+
recovery_reason: reason
|
|
910
1374
|
});
|
|
911
|
-
|
|
1375
|
+
const recoveryError = new Error(
|
|
1376
|
+
`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`
|
|
1377
|
+
);
|
|
1378
|
+
recoveryError.code = "RECOMMEND_CONTEXT_RECOVERY_FAILED";
|
|
1379
|
+
recoveryError.refresh_attempt = compactRefresh;
|
|
1380
|
+
recoveryError.recovery_reason = reason;
|
|
1381
|
+
throw attachRecommendRefreshErrorDiagnostic(recoveryError, compactRefresh);
|
|
1382
|
+
}
|
|
1383
|
+
if (refreshResult.current_city_only) {
|
|
1384
|
+
currentCityOnlyResult = refreshResult.current_city_only;
|
|
1385
|
+
}
|
|
1386
|
+
if (refreshResult.filter) {
|
|
1387
|
+
filterResult = refreshResult.filter;
|
|
912
1388
|
}
|
|
913
1389
|
if (!blockingPanelClose?.closed) {
|
|
914
1390
|
const panelError = createRecommendBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
|
|
915
1391
|
panelError.refresh_attempt = compactRefresh;
|
|
916
1392
|
throw panelError;
|
|
917
1393
|
}
|
|
918
|
-
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1394
|
+
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
919
1395
|
rootState = await ensureRecommendViewport(rootState, "recover_after");
|
|
920
1396
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
921
1397
|
timeoutMs: cardTimeoutMs,
|
|
922
1398
|
intervalMs: 300
|
|
923
1399
|
});
|
|
924
1400
|
resetInfiniteListForRefreshRound(listState, {
|
|
925
|
-
reason: `context_recovery:${reason}`,
|
|
926
|
-
round: contextRecoveryAttempts,
|
|
927
|
-
method: refreshResult.method,
|
|
928
|
-
metadata: {
|
|
929
|
-
card_count: cardNodeIds.length,
|
|
930
|
-
forced_recent_not_view:
|
|
931
|
-
counters: countRecommendResultStatuses(results, { greetCount })
|
|
932
|
-
}
|
|
933
|
-
});
|
|
934
|
-
listEndReason = "";
|
|
935
|
-
updateRecommendProgress({
|
|
936
|
-
card_count: cardNodeIds.length,
|
|
937
|
-
refresh_method: refreshResult.method || null,
|
|
938
|
-
refresh_forced_recent_not_view:
|
|
939
|
-
recovery_reason: reason
|
|
940
|
-
});
|
|
941
|
-
return refreshResult;
|
|
1401
|
+
reason: `context_recovery:${reason}`,
|
|
1402
|
+
round: contextRecoveryAttempts,
|
|
1403
|
+
method: refreshResult.method,
|
|
1404
|
+
metadata: {
|
|
1405
|
+
card_count: cardNodeIds.length,
|
|
1406
|
+
forced_recent_not_view: effectiveForceRecentNotView,
|
|
1407
|
+
counters: countRecommendResultStatuses(results, { greetCount })
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
listEndReason = "";
|
|
1411
|
+
updateRecommendProgress({
|
|
1412
|
+
card_count: cardNodeIds.length,
|
|
1413
|
+
refresh_method: refreshResult.method || null,
|
|
1414
|
+
refresh_forced_recent_not_view: effectiveForceRecentNotView,
|
|
1415
|
+
recovery_reason: reason
|
|
1416
|
+
});
|
|
1417
|
+
return refreshResult;
|
|
942
1418
|
}
|
|
943
1419
|
|
|
944
|
-
runControl.setPhase("recommend:cleanup");
|
|
945
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
946
|
-
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
|
|
947
|
-
await closeRecommendBlockingPanelsForRun("cleanup");
|
|
948
|
-
|
|
949
|
-
await runControl.waitIfPaused();
|
|
950
|
-
runControl.throwIfCanceled();
|
|
951
|
-
runControl.setPhase("recommend:roots");
|
|
952
|
-
rootState = await getRecommendRoots(client);
|
|
1420
|
+
runControl.setPhase("recommend:cleanup");
|
|
1421
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
1422
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
|
|
1423
|
+
await closeRecommendBlockingPanelsForRun("cleanup");
|
|
1424
|
+
|
|
1425
|
+
await runControl.waitIfPaused();
|
|
1426
|
+
runControl.throwIfCanceled();
|
|
1427
|
+
runControl.setPhase("recommend:roots");
|
|
1428
|
+
rootState = await getRecommendRoots(client);
|
|
953
1429
|
rootState = await ensureRecommendViewport(rootState, "roots");
|
|
954
1430
|
runControl.checkpoint({
|
|
955
1431
|
iframe_selector: rootState.iframe.selector,
|
|
@@ -987,34 +1463,55 @@ export async function runRecommendWorkflow({
|
|
|
987
1463
|
throw new Error(`Recommend page scope was not selected: ${pageScopeSelection.reason || pageScopeSelection.effective_scope || requestedPageScope}`);
|
|
988
1464
|
}
|
|
989
1465
|
rootState = await getRecommendRoots(client);
|
|
990
|
-
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
991
|
-
runControl.checkpoint({
|
|
992
|
-
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
993
|
-
});
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1466
|
+
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
1467
|
+
runControl.checkpoint({
|
|
1468
|
+
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
const initialFilterStages = await applyRecommendFilterEnvelopeStages(normalizedFilter, {
|
|
1472
|
+
applyCurrentCityOnly: async () => {
|
|
1473
|
+
await runControl.waitIfPaused();
|
|
1474
|
+
runControl.throwIfCanceled();
|
|
1475
|
+
runControl.setPhase("recommend:current-city-only");
|
|
1476
|
+
const result = await ensureRecommendCurrentCityOnly(
|
|
1477
|
+
client,
|
|
1478
|
+
rootState.iframe.documentNodeId,
|
|
1479
|
+
{ enabled: normalizedFilter.currentCityOnly }
|
|
1480
|
+
);
|
|
1481
|
+
rootState = await getRecommendRoots(client);
|
|
1482
|
+
rootState = await ensureRecommendViewport(rootState, "current_city_only");
|
|
1483
|
+
runControl.checkpoint({
|
|
1484
|
+
current_city_only: compactCurrentCityOnlyResult(result)
|
|
1485
|
+
});
|
|
1486
|
+
return result;
|
|
1487
|
+
},
|
|
1488
|
+
applyFilterPanel: async () => {
|
|
1489
|
+
await runControl.waitIfPaused();
|
|
1490
|
+
runControl.throwIfCanceled();
|
|
1491
|
+
runControl.setPhase("recommend:filter");
|
|
1492
|
+
const result = await selectAndConfirmFirstSafeFilter(
|
|
1493
|
+
client,
|
|
1494
|
+
rootState.iframe.documentNodeId,
|
|
1495
|
+
buildRecommendFilterSelectionOptions(normalizedFilter)
|
|
1496
|
+
);
|
|
1497
|
+
if (!result.confirmed) {
|
|
1498
|
+
throw new Error("Recommend run filter selection was not confirmed");
|
|
1499
|
+
}
|
|
1500
|
+
rootState = await getRecommendRoots(client);
|
|
1501
|
+
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
1502
|
+
runControl.checkpoint({
|
|
1503
|
+
filter: compactFilterResult(result)
|
|
1504
|
+
});
|
|
1505
|
+
return result;
|
|
1506
|
+
}
|
|
1507
|
+
});
|
|
1508
|
+
currentCityOnlyResult = initialFilterStages.current_city_only;
|
|
1509
|
+
filterResult = initialFilterStages.filter;
|
|
1510
|
+
|
|
1511
|
+
await runControl.waitIfPaused();
|
|
1512
|
+
runControl.throwIfCanceled();
|
|
1513
|
+
runControl.setPhase("recommend:cards");
|
|
1514
|
+
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
1018
1515
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1019
1516
|
timeoutMs: cardTimeoutMs,
|
|
1020
1517
|
intervalMs: 300
|
|
@@ -1027,50 +1524,247 @@ export async function runRecommendWorkflow({
|
|
|
1027
1524
|
list_end_reason: null
|
|
1028
1525
|
});
|
|
1029
1526
|
|
|
1030
|
-
while (countPassedResults(results) < targetPassCount) {
|
|
1031
|
-
const candidateStarted = Date.now();
|
|
1032
|
-
const timings = {};
|
|
1033
|
-
await runControl.waitIfPaused();
|
|
1034
|
-
runControl.throwIfCanceled();
|
|
1035
|
-
runControl.setPhase("recommend:
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1527
|
+
while (countPassedResults(results) < targetPassCount) {
|
|
1528
|
+
const candidateStarted = Date.now();
|
|
1529
|
+
const timings = {};
|
|
1530
|
+
await runControl.waitIfPaused();
|
|
1531
|
+
runControl.throwIfCanceled();
|
|
1532
|
+
runControl.setPhase("recommend:list-read");
|
|
1533
|
+
const debugBoundaryAction = debugBoundary.take(results.length);
|
|
1534
|
+
let debugForcedListEnd = false;
|
|
1535
|
+
if (debugBoundaryAction) {
|
|
1536
|
+
const debugProgress = {
|
|
1537
|
+
debug_boundary_mode: debugBoundaryAction.mode,
|
|
1538
|
+
debug_boundary_threshold: debugBoundaryAction.threshold,
|
|
1539
|
+
debug_boundary_triggered: true,
|
|
1540
|
+
debug_boundary_trigger_count: debugBoundaryAction.trigger_count,
|
|
1541
|
+
debug_boundary_processed: debugBoundaryAction.processed
|
|
1542
|
+
};
|
|
1543
|
+
updateRecommendProgress(debugProgress);
|
|
1544
|
+
runControl.checkpoint({
|
|
1545
|
+
debug_boundary: {
|
|
1546
|
+
...debugProgress,
|
|
1547
|
+
field: debugBoundaryAction.field,
|
|
1548
|
+
triggered_at: new Date().toISOString()
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
if (debugBoundaryAction.mode === "context_recovery") {
|
|
1552
|
+
runControl.setPhase("recommend:debug-force-context-recovery");
|
|
1553
|
+
await recoverAndReapplyRecommendContext("debug_force_context_recovery_after_processed", null, {
|
|
1554
|
+
forceRecentNotView: true
|
|
1555
|
+
});
|
|
1556
|
+
updateRecommendProgress({
|
|
1557
|
+
debug_force_context_recovery_count: debugBoundaryAction.trigger_count
|
|
1558
|
+
});
|
|
1559
|
+
runControl.setPhase("recommend:list-read");
|
|
1560
|
+
} else if (debugBoundaryAction.mode === "cdp_reconnect") {
|
|
1561
|
+
debugForceReconnectPending = debugBoundaryAction;
|
|
1562
|
+
updateRecommendProgress({
|
|
1563
|
+
debug_force_cdp_reconnect_pending: true
|
|
1564
|
+
});
|
|
1565
|
+
} else if (debugBoundaryAction.mode === "list_end") {
|
|
1566
|
+
debugForcedListEnd = true;
|
|
1567
|
+
updateRecommendProgress({
|
|
1568
|
+
debug_force_list_end_count: debugBoundaryAction.trigger_count
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
const listReadAcquisition = await acquireRecommendListReadWithStaleRecovery({
|
|
1573
|
+
maxRetries: 2,
|
|
1574
|
+
acquire: async () => {
|
|
1575
|
+
await runControl.waitIfPaused();
|
|
1576
|
+
runControl.throwIfCanceled();
|
|
1577
|
+
runControl.setPhase("recommend:list-read");
|
|
1578
|
+
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
1579
|
+
if (debugForcedListEnd) {
|
|
1580
|
+
return {
|
|
1581
|
+
ok: false,
|
|
1582
|
+
end_reached: true,
|
|
1583
|
+
reason: "debug_forced_list_end"
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1586
|
+
return measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
1587
|
+
client,
|
|
1588
|
+
state: listState,
|
|
1589
|
+
maxScrolls: listMaxScrolls,
|
|
1590
|
+
stableSignatureLimit: listStableSignatureLimit,
|
|
1591
|
+
wheelDeltaY: listWheelDeltaY,
|
|
1592
|
+
settleMs: listSettleMs,
|
|
1593
|
+
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1594
|
+
fallbackPoint: listFallbackResolver,
|
|
1595
|
+
findNodeIds: async () => {
|
|
1596
|
+
let currentRootState = await getRecommendRoots(client);
|
|
1597
|
+
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
1598
|
+
rootState = currentRootState;
|
|
1599
|
+
if (debugForceReconnectPending) {
|
|
1600
|
+
runControl.setPhase("recommend:debug-force-cdp-reconnect");
|
|
1601
|
+
const rawClient = client.__rawClient;
|
|
1602
|
+
if (!rawClient || typeof rawClient.close !== "function") {
|
|
1603
|
+
const error = new Error("Guarded CDP client does not expose a closable __rawClient");
|
|
1604
|
+
error.code = "RECOMMEND_DEBUG_RAW_CDP_UNAVAILABLE";
|
|
1605
|
+
throw error;
|
|
1606
|
+
}
|
|
1607
|
+
const forcedReconnectAction = debugForceReconnectPending;
|
|
1608
|
+
debugForceReconnectPending = null;
|
|
1609
|
+
await rawClient.close();
|
|
1610
|
+
runControl.checkpoint({
|
|
1611
|
+
debug_cdp_reconnect: {
|
|
1612
|
+
raw_connection_closed: true,
|
|
1613
|
+
processed: forcedReconnectAction.processed,
|
|
1614
|
+
threshold: forcedReconnectAction.threshold,
|
|
1615
|
+
closed_at: new Date().toISOString()
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
updateRecommendProgress({
|
|
1619
|
+
debug_force_cdp_reconnect_pending: false,
|
|
1620
|
+
debug_force_cdp_reconnect_count: forcedReconnectAction.trigger_count
|
|
1621
|
+
});
|
|
1622
|
+
runControl.setPhase("recommend:list-read");
|
|
1623
|
+
}
|
|
1624
|
+
const currentCardNodeIds = await waitForRecommendCardNodeIds(
|
|
1625
|
+
client,
|
|
1626
|
+
currentRootState.iframe.documentNodeId,
|
|
1627
|
+
{
|
|
1628
|
+
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
1629
|
+
intervalMs: 300
|
|
1630
|
+
}
|
|
1631
|
+
);
|
|
1632
|
+
cardNodeIds = currentCardNodeIds;
|
|
1633
|
+
return currentCardNodeIds;
|
|
1634
|
+
},
|
|
1635
|
+
readCandidate: async (nodeId, { visibleIndex }) => readRecommendCardCandidate(client, nodeId, {
|
|
1636
|
+
targetUrl,
|
|
1637
|
+
source: "recommend-run-card",
|
|
1638
|
+
metadata: {
|
|
1639
|
+
run_candidate_index: results.length,
|
|
1640
|
+
visible_index: visibleIndex
|
|
1641
|
+
}
|
|
1642
|
+
}),
|
|
1643
|
+
shouldRethrowReadError: isStaleRecommendNodeError,
|
|
1644
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => detectInfiniteListBottomMarker(client, {
|
|
1645
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
1646
|
+
markerSelectors: RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
1647
|
+
refreshSelectors: [RECOMMEND_END_REFRESH_SELECTOR],
|
|
1648
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
1649
|
+
maxTextScanNodes: 500
|
|
1650
|
+
})
|
|
1651
|
+
}));
|
|
1652
|
+
},
|
|
1653
|
+
onStale: async ({ diagnostic }) => {
|
|
1654
|
+
listReadStaleRecoveryAttempts += 1;
|
|
1655
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
1656
|
+
listReadStaleDiagnostics.push(diagnostic);
|
|
1657
|
+
runControl.checkpoint({
|
|
1658
|
+
list_read_stale_recovery: {
|
|
1659
|
+
diagnostic,
|
|
1660
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
1661
|
+
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
1662
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1663
|
+
}
|
|
1664
|
+
});
|
|
1665
|
+
updateRecommendProgress();
|
|
1666
|
+
},
|
|
1667
|
+
recover: async ({ error, diagnostic }) => {
|
|
1668
|
+
try {
|
|
1669
|
+
return await recoverRecommendListReadStaleContext({
|
|
1670
|
+
staleAttempt: diagnostic.attempt,
|
|
1671
|
+
listState,
|
|
1672
|
+
rootReacquire: async () => {
|
|
1673
|
+
await runControl.waitIfPaused();
|
|
1674
|
+
runControl.throwIfCanceled();
|
|
1675
|
+
runControl.setPhase("recommend:list-read-root-reacquire");
|
|
1676
|
+
let freshRootState = await getRecommendRoots(client);
|
|
1677
|
+
freshRootState = await ensureRecommendViewport(
|
|
1678
|
+
freshRootState,
|
|
1679
|
+
"list_read_stale_root_reacquire"
|
|
1680
|
+
);
|
|
1681
|
+
const freshCardNodeIds = await waitForRecommendCardNodeIds(
|
|
1682
|
+
client,
|
|
1683
|
+
freshRootState.iframe.documentNodeId,
|
|
1684
|
+
{
|
|
1685
|
+
timeoutMs: Math.min(cardTimeoutMs, 10000),
|
|
1686
|
+
intervalMs: 300
|
|
1687
|
+
}
|
|
1688
|
+
);
|
|
1689
|
+
rootState = freshRootState;
|
|
1690
|
+
cardNodeIds = freshCardNodeIds;
|
|
1691
|
+
return {
|
|
1692
|
+
card_count: freshCardNodeIds.length,
|
|
1693
|
+
iframe_document_node_id: freshRootState.iframe.documentNodeId
|
|
1694
|
+
};
|
|
1695
|
+
},
|
|
1696
|
+
contextReapply: async ({ rootReacquireError }) => {
|
|
1697
|
+
await runControl.waitIfPaused();
|
|
1698
|
+
runControl.throwIfCanceled();
|
|
1699
|
+
const recovery = await recoverAndReapplyRecommendContext(
|
|
1700
|
+
"list_read_stale_node",
|
|
1701
|
+
rootReacquireError || error,
|
|
1702
|
+
{ forceRecentNotView: true }
|
|
1703
|
+
);
|
|
1704
|
+
return {
|
|
1705
|
+
ok: Boolean(recovery?.ok),
|
|
1706
|
+
method: recovery?.method || null,
|
|
1707
|
+
card_count: recovery?.card_count || 0
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
});
|
|
1711
|
+
} catch (recoveryError) {
|
|
1712
|
+
updateRecommendProgress({
|
|
1713
|
+
list_read_stale_recovery_failed: true
|
|
1714
|
+
});
|
|
1715
|
+
runControl.checkpoint({
|
|
1716
|
+
list_read_stale_recovery_failed: {
|
|
1717
|
+
trigger: lastListReadStaleDiagnostic,
|
|
1718
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
1719
|
+
recovery_error: compactError(recoveryError, "RECOMMEND_LIST_READ_RECOVERY_FAILED"),
|
|
1720
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1721
|
+
}
|
|
1722
|
+
});
|
|
1723
|
+
throw recoveryError;
|
|
1724
|
+
}
|
|
1725
|
+
},
|
|
1726
|
+
onRecoveryApplied: async ({ diagnostic }) => {
|
|
1727
|
+
listReadStaleRecoveryApplied += 1;
|
|
1728
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
1729
|
+
lastListReadRecoveryMode = diagnostic.recovery_mode || null;
|
|
1730
|
+
updateRecommendProgress();
|
|
1731
|
+
runControl.checkpoint({
|
|
1732
|
+
list_read_stale_recovery_applied: {
|
|
1733
|
+
diagnostic,
|
|
1734
|
+
recovery_applied_count: listReadStaleRecoveryApplied,
|
|
1735
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1736
|
+
}
|
|
1737
|
+
});
|
|
1738
|
+
},
|
|
1739
|
+
onRecovered: async ({ diagnostic }) => {
|
|
1740
|
+
listReadStaleRecoveries += 1;
|
|
1741
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
1742
|
+
updateRecommendProgress();
|
|
1743
|
+
runControl.checkpoint({
|
|
1744
|
+
list_read_stale_recovered: {
|
|
1745
|
+
diagnostic,
|
|
1746
|
+
recovery_count: listReadStaleRecoveries,
|
|
1747
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1748
|
+
}
|
|
1749
|
+
});
|
|
1750
|
+
},
|
|
1751
|
+
onExhausted: async ({ diagnostic }) => {
|
|
1752
|
+
listReadStaleRecoveryAttempts += 1;
|
|
1753
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
1754
|
+
listReadStaleDiagnostics.push(diagnostic);
|
|
1755
|
+
updateRecommendProgress({
|
|
1756
|
+
list_read_stale_recovery_exhausted: true
|
|
1757
|
+
});
|
|
1758
|
+
runControl.checkpoint({
|
|
1759
|
+
list_read_stale_recovery_exhausted: {
|
|
1760
|
+
diagnostic,
|
|
1761
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
1762
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1763
|
+
}
|
|
1764
|
+
});
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
const nextCandidateResult = listReadAcquisition.result;
|
|
1074
1768
|
if (!nextCandidateResult.ok) {
|
|
1075
1769
|
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
1076
1770
|
if (
|
|
@@ -1083,99 +1777,107 @@ export async function runRecommendWorkflow({
|
|
|
1083
1777
|
runControl.throwIfCanceled();
|
|
1084
1778
|
runControl.setPhase("recommend:refresh");
|
|
1085
1779
|
refreshRounds += 1;
|
|
1086
|
-
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1087
|
-
rootState,
|
|
1088
|
-
jobLabel,
|
|
1089
|
-
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1090
|
-
fallbackPageScope: normalizedFallbackPageScope,
|
|
1091
|
-
filter: normalizedFilter,
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1780
|
+
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1781
|
+
rootState,
|
|
1782
|
+
jobLabel,
|
|
1783
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1784
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
1785
|
+
filter: normalizedFilter,
|
|
1786
|
+
preferEndRefreshButton: debugForcedListEnd ? false : undefined,
|
|
1787
|
+
forceRecentNotView: normalizedFilter.enabled,
|
|
1788
|
+
cardTimeoutMs,
|
|
1789
|
+
buttonSettleMs: refreshButtonSettleMs,
|
|
1790
|
+
reloadSettleMs: refreshReloadSettleMs
|
|
1791
|
+
});
|
|
1792
|
+
const compactRefresh = compactRefreshAttempt(refreshResult);
|
|
1098
1793
|
refreshAttempts.push(compactRefresh);
|
|
1099
1794
|
runControl.checkpoint({
|
|
1100
1795
|
refresh_round: refreshRounds,
|
|
1101
|
-
refresh: compactRefresh
|
|
1102
|
-
});
|
|
1103
|
-
updateRecommendProgress({
|
|
1104
|
-
card_count: refreshResult.card_count || cardNodeIds.length,
|
|
1105
|
-
refresh_method: refreshResult.method || null,
|
|
1106
|
-
refresh_forced_recent_not_view:
|
|
1107
|
-
list_end_reason: listEndReason
|
|
1108
|
-
});
|
|
1796
|
+
refresh: compactRefresh
|
|
1797
|
+
});
|
|
1798
|
+
updateRecommendProgress({
|
|
1799
|
+
card_count: refreshResult.card_count || cardNodeIds.length,
|
|
1800
|
+
refresh_method: refreshResult.method || null,
|
|
1801
|
+
refresh_forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view),
|
|
1802
|
+
list_end_reason: listEndReason
|
|
1803
|
+
});
|
|
1109
1804
|
if (refreshResult.ok) {
|
|
1805
|
+
if (refreshResult.current_city_only) {
|
|
1806
|
+
currentCityOnlyResult = refreshResult.current_city_only;
|
|
1807
|
+
}
|
|
1808
|
+
if (refreshResult.filter) {
|
|
1809
|
+
filterResult = refreshResult.filter;
|
|
1810
|
+
}
|
|
1110
1811
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1111
1812
|
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
1112
|
-
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1113
|
-
timeoutMs: cardTimeoutMs,
|
|
1114
|
-
intervalMs: 300
|
|
1813
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1814
|
+
timeoutMs: cardTimeoutMs,
|
|
1815
|
+
intervalMs: 300
|
|
1115
1816
|
});
|
|
1116
1817
|
resetInfiniteListForRefreshRound(listState, {
|
|
1117
|
-
reason: listEndReason,
|
|
1118
|
-
round: refreshRounds,
|
|
1119
|
-
method: refreshResult.method,
|
|
1120
|
-
metadata: {
|
|
1121
|
-
card_count: cardNodeIds.length,
|
|
1122
|
-
forced_recent_not_view:
|
|
1123
|
-
}
|
|
1124
|
-
});
|
|
1818
|
+
reason: listEndReason,
|
|
1819
|
+
round: refreshRounds,
|
|
1820
|
+
method: refreshResult.method,
|
|
1821
|
+
metadata: {
|
|
1822
|
+
card_count: cardNodeIds.length,
|
|
1823
|
+
forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view)
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1125
1826
|
listEndReason = "";
|
|
1126
1827
|
continue;
|
|
1127
1828
|
}
|
|
1128
|
-
throw createRecommendRefreshFailureError(compactRefresh, {
|
|
1129
|
-
listEndReason,
|
|
1130
|
-
targetCount: targetPassCount,
|
|
1131
|
-
passedCount: countPassedResults(results)
|
|
1132
|
-
});
|
|
1133
|
-
}
|
|
1134
|
-
break;
|
|
1135
|
-
}
|
|
1829
|
+
throw createRecommendRefreshFailureError(compactRefresh, {
|
|
1830
|
+
listEndReason,
|
|
1831
|
+
targetCount: targetPassCount,
|
|
1832
|
+
passedCount: countPassedResults(results)
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
break;
|
|
1836
|
+
}
|
|
1136
1837
|
|
|
1137
|
-
|
|
1838
|
+
runControl.setPhase("recommend:candidate");
|
|
1839
|
+
const index = results.length;
|
|
1138
1840
|
let cardNodeId = nextCandidateResult.item.node_id;
|
|
1139
1841
|
const candidateKey = nextCandidateResult.item.key;
|
|
1140
1842
|
let cardCandidate = nextCandidateResult.item.candidate;
|
|
1141
1843
|
|
|
1142
1844
|
let screeningCandidate = cardCandidate;
|
|
1143
|
-
let detailResult = null;
|
|
1144
|
-
let recoverableDetailError = null;
|
|
1145
|
-
let colleagueContact = null;
|
|
1146
|
-
let skipRecentColleagueContact = false;
|
|
1147
|
-
let detailStep = "not_started";
|
|
1845
|
+
let detailResult = null;
|
|
1846
|
+
let recoverableDetailError = null;
|
|
1847
|
+
let colleagueContact = null;
|
|
1848
|
+
let skipRecentColleagueContact = false;
|
|
1849
|
+
let detailStep = "not_started";
|
|
1148
1850
|
if (index < effectiveDetailLimit) {
|
|
1149
1851
|
try {
|
|
1150
1852
|
await runControl.waitIfPaused();
|
|
1151
1853
|
runControl.throwIfCanceled();
|
|
1152
|
-
runControl.setPhase("recommend:detail");
|
|
1153
|
-
detailStep = "ensure_viewport";
|
|
1154
|
-
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
1155
|
-
const blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1156
|
-
attemptsLimit: 2,
|
|
1157
|
-
rootState
|
|
1158
|
-
});
|
|
1159
|
-
if (!blockingPanelClose?.closed) {
|
|
1160
|
-
const panelError = createRecommendBlockingPanelCloseFailureError(
|
|
1161
|
-
blockingPanelClose,
|
|
1162
|
-
"before_detail_open"
|
|
1163
|
-
);
|
|
1164
|
-
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1165
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error: panelError });
|
|
1166
|
-
await recoverAndReapplyRecommendContext("account_rights_panel_before_detail", panelError, {
|
|
1167
|
-
forceRecentNotView: true
|
|
1168
|
-
});
|
|
1169
|
-
continue;
|
|
1170
|
-
}
|
|
1171
|
-
if (blockingPanelClose.already_closed === false) {
|
|
1172
|
-
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1173
|
-
}
|
|
1174
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1175
|
-
detailStep = "open_detail";
|
|
1176
|
-
networkRecorder.clear();
|
|
1177
|
-
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1178
|
-
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
1854
|
+
runControl.setPhase("recommend:detail");
|
|
1855
|
+
detailStep = "ensure_viewport";
|
|
1856
|
+
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
1857
|
+
const blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1858
|
+
attemptsLimit: 2,
|
|
1859
|
+
rootState
|
|
1860
|
+
});
|
|
1861
|
+
if (!blockingPanelClose?.closed) {
|
|
1862
|
+
const panelError = createRecommendBlockingPanelCloseFailureError(
|
|
1863
|
+
blockingPanelClose,
|
|
1864
|
+
"before_detail_open"
|
|
1865
|
+
);
|
|
1866
|
+
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1867
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error: panelError });
|
|
1868
|
+
await recoverAndReapplyRecommendContext("account_rights_panel_before_detail", panelError, {
|
|
1869
|
+
forceRecentNotView: true
|
|
1870
|
+
});
|
|
1871
|
+
continue;
|
|
1872
|
+
}
|
|
1873
|
+
if (blockingPanelClose.already_closed === false) {
|
|
1874
|
+
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1875
|
+
}
|
|
1876
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1877
|
+
detailStep = "open_detail";
|
|
1878
|
+
networkRecorder.clear();
|
|
1879
|
+
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1880
|
+
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
1179
1881
|
cardNodeId,
|
|
1180
1882
|
candidateKey,
|
|
1181
1883
|
cardCandidate,
|
|
@@ -1186,52 +1888,52 @@ export async function runRecommendWorkflow({
|
|
|
1186
1888
|
});
|
|
1187
1889
|
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
1188
1890
|
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
1189
|
-
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1190
|
-
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1191
|
-
screeningCandidate = cardCandidate;
|
|
1192
|
-
if (shouldSkipRecentColleagueContacted) {
|
|
1193
|
-
detailStep = "check_colleague_contact";
|
|
1194
|
-
try {
|
|
1195
|
-
colleagueContact = await measureTiming(timings, "colleague_contact_check_ms", () => inspectRecentColleagueContact(
|
|
1196
|
-
client,
|
|
1197
|
-
openedDetail.detail_state,
|
|
1198
|
-
{
|
|
1199
|
-
referenceDate: colleagueContactReferenceDate,
|
|
1200
|
-
windowDays: normalizedColleagueContactWindowDays
|
|
1201
|
-
}
|
|
1202
|
-
));
|
|
1203
|
-
if (colleagueContact?.recent) {
|
|
1204
|
-
skipRecentColleagueContact = true;
|
|
1205
|
-
detailResult = {
|
|
1206
|
-
candidate: screeningCandidate,
|
|
1207
|
-
detail: {
|
|
1208
|
-
popup_text: "",
|
|
1209
|
-
resume_text: ""
|
|
1210
|
-
},
|
|
1211
|
-
colleague_contact: colleagueContact,
|
|
1212
|
-
cv_acquisition: {
|
|
1213
|
-
source: "skipped_recent_colleague_contact",
|
|
1214
|
-
skipped: true,
|
|
1215
|
-
reason: "skipped_recent_colleague_contact"
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1219
|
-
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1220
|
-
}
|
|
1221
|
-
} catch (error) {
|
|
1222
|
-
colleagueContact = {
|
|
1223
|
-
checked: false,
|
|
1224
|
-
recent: false,
|
|
1225
|
-
reason: "inspection_failed",
|
|
1226
|
-
error: error?.message || String(error),
|
|
1227
|
-
window_days: normalizedColleagueContactWindowDays
|
|
1228
|
-
};
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
if (!skipRecentColleagueContact) {
|
|
1232
|
-
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1233
|
-
detailStep = "wait_network";
|
|
1234
|
-
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1891
|
+
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1892
|
+
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1893
|
+
screeningCandidate = cardCandidate;
|
|
1894
|
+
if (shouldSkipRecentColleagueContacted) {
|
|
1895
|
+
detailStep = "check_colleague_contact";
|
|
1896
|
+
try {
|
|
1897
|
+
colleagueContact = await measureTiming(timings, "colleague_contact_check_ms", () => inspectRecentColleagueContact(
|
|
1898
|
+
client,
|
|
1899
|
+
openedDetail.detail_state,
|
|
1900
|
+
{
|
|
1901
|
+
referenceDate: colleagueContactReferenceDate,
|
|
1902
|
+
windowDays: normalizedColleagueContactWindowDays
|
|
1903
|
+
}
|
|
1904
|
+
));
|
|
1905
|
+
if (colleagueContact?.recent) {
|
|
1906
|
+
skipRecentColleagueContact = true;
|
|
1907
|
+
detailResult = {
|
|
1908
|
+
candidate: screeningCandidate,
|
|
1909
|
+
detail: {
|
|
1910
|
+
popup_text: "",
|
|
1911
|
+
resume_text: ""
|
|
1912
|
+
},
|
|
1913
|
+
colleague_contact: colleagueContact,
|
|
1914
|
+
cv_acquisition: {
|
|
1915
|
+
source: "skipped_recent_colleague_contact",
|
|
1916
|
+
skipped: true,
|
|
1917
|
+
reason: "skipped_recent_colleague_contact"
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1921
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1922
|
+
}
|
|
1923
|
+
} catch (error) {
|
|
1924
|
+
colleagueContact = {
|
|
1925
|
+
checked: false,
|
|
1926
|
+
recent: false,
|
|
1927
|
+
reason: "inspection_failed",
|
|
1928
|
+
error: error?.message || String(error),
|
|
1929
|
+
window_days: normalizedColleagueContactWindowDays
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
if (!skipRecentColleagueContact) {
|
|
1934
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1935
|
+
detailStep = "wait_network";
|
|
1936
|
+
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1235
1937
|
waitForRecommendDetailNetworkEvents,
|
|
1236
1938
|
networkRecorder,
|
|
1237
1939
|
{
|
|
@@ -1255,10 +1957,10 @@ export async function runRecommendWorkflow({
|
|
|
1255
1957
|
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
1256
1958
|
networkParseIntervalMs: 250
|
|
1257
1959
|
});
|
|
1258
|
-
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1259
|
-
if (colleagueContact) detailResult.colleague_contact = colleagueContact;
|
|
1260
|
-
|
|
1261
|
-
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1960
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
1961
|
+
if (colleagueContact) detailResult.colleague_contact = colleagueContact;
|
|
1962
|
+
|
|
1963
|
+
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
1262
1964
|
let source = "network";
|
|
1263
1965
|
let imageEvidence = null;
|
|
1264
1966
|
let captureTarget = null;
|
|
@@ -1295,12 +1997,12 @@ export async function runRecommendWorkflow({
|
|
|
1295
1997
|
resizeMaxWidth: 1100,
|
|
1296
1998
|
captureViewport: false,
|
|
1297
1999
|
padding: 0,
|
|
1298
|
-
maxScreenshots: maxImagePages,
|
|
1299
|
-
wheelDeltaY: imageWheelDeltaY,
|
|
1300
|
-
settleMs: 350,
|
|
1301
|
-
scrollMethod: "dom-anchor-fallback-input",
|
|
1302
|
-
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1303
|
-
stepTimeoutMs: 45000,
|
|
2000
|
+
maxScreenshots: maxImagePages,
|
|
2001
|
+
wheelDeltaY: imageWheelDeltaY,
|
|
2002
|
+
settleMs: 350,
|
|
2003
|
+
scrollMethod: "dom-anchor-fallback-input",
|
|
2004
|
+
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
2005
|
+
stepTimeoutMs: 45000,
|
|
1304
2006
|
totalTimeoutMs: 90000,
|
|
1305
2007
|
duplicateStopCount: 1,
|
|
1306
2008
|
skipDuplicateScreenshots: true,
|
|
@@ -1324,14 +2026,14 @@ export async function runRecommendWorkflow({
|
|
|
1324
2026
|
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1325
2027
|
if (recoveryCount < 1) {
|
|
1326
2028
|
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1327
|
-
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1328
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1329
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1330
|
-
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1331
|
-
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1332
|
-
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1333
|
-
forceRecentNotView: true
|
|
1334
|
-
});
|
|
2029
|
+
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
2030
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
2031
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2032
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2033
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
2034
|
+
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
2035
|
+
forceRecentNotView: true
|
|
2036
|
+
});
|
|
1335
2037
|
continue;
|
|
1336
2038
|
}
|
|
1337
2039
|
imageEvidence = createRecoverableImageCaptureEvidence(error, {
|
|
@@ -1370,73 +2072,73 @@ export async function runRecommendWorkflow({
|
|
|
1370
2072
|
image_evidence: summarizeImageEvidence(imageEvidence),
|
|
1371
2073
|
capture_target: captureTarget || null,
|
|
1372
2074
|
capture_target_wait: captureTargetWait
|
|
1373
|
-
};
|
|
1374
|
-
screeningCandidate = detailResult.candidate;
|
|
1375
|
-
}
|
|
1376
|
-
} catch (error) {
|
|
2075
|
+
};
|
|
2076
|
+
screeningCandidate = detailResult.candidate;
|
|
2077
|
+
}
|
|
2078
|
+
} catch (error) {
|
|
1377
2079
|
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
1378
2080
|
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
1379
2081
|
if (recoveryCount < 1) {
|
|
1380
|
-
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
1381
|
-
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1382
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1383
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1384
|
-
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1385
|
-
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1386
|
-
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1387
|
-
forceRecentNotView: true
|
|
1388
|
-
});
|
|
2082
|
+
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
2083
|
+
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
2084
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
2085
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2086
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2087
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
2088
|
+
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
2089
|
+
forceRecentNotView: true
|
|
2090
|
+
});
|
|
1389
2091
|
continue;
|
|
1390
2092
|
}
|
|
1391
|
-
recoverableDetailError = error;
|
|
1392
|
-
detailResult = null;
|
|
1393
|
-
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1394
|
-
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1395
|
-
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1396
|
-
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1397
|
-
}
|
|
2093
|
+
recoverableDetailError = error;
|
|
2094
|
+
detailResult = null;
|
|
2095
|
+
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
2096
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2097
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2098
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
2099
|
+
}
|
|
1398
2100
|
}
|
|
1399
2101
|
|
|
1400
2102
|
await runControl.waitIfPaused();
|
|
1401
2103
|
runControl.throwIfCanceled();
|
|
1402
2104
|
runControl.setPhase("recommend:screening");
|
|
1403
|
-
let llmResult = null;
|
|
1404
|
-
if (useLlmScreening) {
|
|
1405
|
-
if (skipRecentColleagueContact || recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
1406
|
-
llmResult = null;
|
|
1407
|
-
} else if (!llmConfig) {
|
|
2105
|
+
let llmResult = null;
|
|
2106
|
+
if (useLlmScreening) {
|
|
2107
|
+
if (skipRecentColleagueContact || recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
2108
|
+
llmResult = null;
|
|
2109
|
+
} else if (!llmConfig) {
|
|
1408
2110
|
llmResult = createMissingLlmConfigResult();
|
|
1409
2111
|
} else {
|
|
1410
2112
|
try {
|
|
1411
2113
|
const llmTimingKey = detailResult?.image_evidence?.file_paths?.length
|
|
1412
2114
|
? "vision_model_ms"
|
|
1413
2115
|
: "text_model_ms";
|
|
1414
|
-
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
1415
|
-
candidate: screeningCandidate,
|
|
1416
|
-
criteria,
|
|
1417
|
-
config: llmConfig,
|
|
1418
|
-
timeoutMs: llmTimeoutMs,
|
|
2116
|
+
llmResult = await measureTiming(timings, llmTimingKey, () => callScreeningLlm({
|
|
2117
|
+
candidate: screeningCandidate,
|
|
2118
|
+
criteria,
|
|
2119
|
+
config: llmConfig,
|
|
2120
|
+
timeoutMs: llmTimeoutMs,
|
|
1419
2121
|
imageEvidence: detailResult?.image_evidence || null,
|
|
1420
2122
|
maxImages: llmImageLimit,
|
|
1421
2123
|
imageDetail: llmImageDetail
|
|
1422
2124
|
}));
|
|
1423
|
-
} catch (error) {
|
|
1424
|
-
if (isFatalLlmProviderError(error)) {
|
|
1425
|
-
throw createFatalLlmRunError(error, {
|
|
1426
|
-
domain: "recommend",
|
|
1427
|
-
candidate: screeningCandidate
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
llmResult = createFailedLlmScreeningResult(error);
|
|
1431
|
-
}
|
|
1432
|
-
}
|
|
1433
|
-
if (detailResult) detailResult.llm_result = llmResult;
|
|
1434
|
-
}
|
|
1435
|
-
const screening = skipRecentColleagueContact
|
|
1436
|
-
? createRecentColleagueContactSkipScreening(screeningCandidate, colleagueContact)
|
|
1437
|
-
: recoverableDetailError
|
|
1438
|
-
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
1439
|
-
: detailResult?.image_evidence?.ok === false
|
|
2125
|
+
} catch (error) {
|
|
2126
|
+
if (isFatalLlmProviderError(error)) {
|
|
2127
|
+
throw createFatalLlmRunError(error, {
|
|
2128
|
+
domain: "recommend",
|
|
2129
|
+
candidate: screeningCandidate
|
|
2130
|
+
});
|
|
2131
|
+
}
|
|
2132
|
+
llmResult = createFailedLlmScreeningResult(error);
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
if (detailResult) detailResult.llm_result = llmResult;
|
|
2136
|
+
}
|
|
2137
|
+
const screening = skipRecentColleagueContact
|
|
2138
|
+
? createRecentColleagueContactSkipScreening(screeningCandidate, colleagueContact)
|
|
2139
|
+
: recoverableDetailError
|
|
2140
|
+
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
2141
|
+
: detailResult?.image_evidence?.ok === false
|
|
1440
2142
|
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
1441
2143
|
code: detailResult.image_evidence.error_code,
|
|
1442
2144
|
message: detailResult.image_evidence.error
|
|
@@ -1444,17 +2146,17 @@ export async function runRecommendWorkflow({
|
|
|
1444
2146
|
: useLlmScreening
|
|
1445
2147
|
? llmResultToScreening(llmResult, screeningCandidate)
|
|
1446
2148
|
: screenCandidate(screeningCandidate, { criteria });
|
|
1447
|
-
let actionDiscovery = null;
|
|
1448
|
-
let postActionResult = null;
|
|
1449
|
-
let closeFailureError = null;
|
|
1450
|
-
let closeRecoveryFailure = null;
|
|
1451
|
-
if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
|
|
1452
|
-
const postActionStarted = Date.now();
|
|
1453
|
-
await runControl.waitIfPaused();
|
|
1454
|
-
runControl.throwIfCanceled();
|
|
1455
|
-
runControl.setPhase("recommend:post-action");
|
|
1456
|
-
await maybeHumanActionCooldown("before_post_action", timings);
|
|
1457
|
-
actionDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
2149
|
+
let actionDiscovery = null;
|
|
2150
|
+
let postActionResult = null;
|
|
2151
|
+
let closeFailureError = null;
|
|
2152
|
+
let closeRecoveryFailure = null;
|
|
2153
|
+
if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
|
|
2154
|
+
const postActionStarted = Date.now();
|
|
2155
|
+
await runControl.waitIfPaused();
|
|
2156
|
+
runControl.throwIfCanceled();
|
|
2157
|
+
runControl.setPhase("recommend:post-action");
|
|
2158
|
+
await maybeHumanActionCooldown("before_post_action", timings);
|
|
2159
|
+
actionDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
1458
2160
|
timeoutMs: actionTimeoutMs,
|
|
1459
2161
|
intervalMs: actionIntervalMs,
|
|
1460
2162
|
requireAny: true
|
|
@@ -1475,32 +2177,33 @@ export async function runRecommendWorkflow({
|
|
|
1475
2177
|
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
1476
2178
|
}
|
|
1477
2179
|
if (detailResult && closeDetail && !detailResult.close_result?.closed) {
|
|
2180
|
+
runControl.setPhase("recommend:close-detail");
|
|
1478
2181
|
detailResult.close_result = await measureTiming(timings, "close_detail_ms", () => closeRecommendDetail(client));
|
|
1479
|
-
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
1480
|
-
if (!detailResult.close_result?.closed) {
|
|
1481
|
-
closeFailureError = createRecommendCloseFailureError(detailResult.close_result);
|
|
1482
|
-
try {
|
|
1483
|
-
const recovery = await recoverAndReapplyRecommendContext("detail_close_failed", closeFailureError, {
|
|
1484
|
-
forceRecentNotView: true
|
|
1485
|
-
});
|
|
1486
|
-
detailResult.cv_acquisition = {
|
|
1487
|
-
...(detailResult.cv_acquisition || {}),
|
|
1488
|
-
close_recovery: {
|
|
1489
|
-
ok: Boolean(recovery.ok),
|
|
1490
|
-
method: recovery.method || "",
|
|
1491
|
-
forced_recent_not_view: Boolean(recovery.forced_recent_not_view),
|
|
1492
|
-
card_count: recovery.card_count || 0
|
|
1493
|
-
}
|
|
1494
|
-
};
|
|
1495
|
-
} catch (error) {
|
|
1496
|
-
closeRecoveryFailure = error;
|
|
1497
|
-
detailResult.cv_acquisition = {
|
|
2182
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
2183
|
+
if (!detailResult.close_result?.closed) {
|
|
2184
|
+
closeFailureError = createRecommendCloseFailureError(detailResult.close_result);
|
|
2185
|
+
try {
|
|
2186
|
+
const recovery = await recoverAndReapplyRecommendContext("detail_close_failed", closeFailureError, {
|
|
2187
|
+
forceRecentNotView: true
|
|
2188
|
+
});
|
|
2189
|
+
detailResult.cv_acquisition = {
|
|
2190
|
+
...(detailResult.cv_acquisition || {}),
|
|
2191
|
+
close_recovery: {
|
|
2192
|
+
ok: Boolean(recovery.ok),
|
|
2193
|
+
method: recovery.method || "",
|
|
2194
|
+
forced_recent_not_view: Boolean(recovery.forced_recent_not_view),
|
|
2195
|
+
card_count: recovery.card_count || 0
|
|
2196
|
+
}
|
|
2197
|
+
};
|
|
2198
|
+
} catch (error) {
|
|
2199
|
+
closeRecoveryFailure = error;
|
|
2200
|
+
detailResult.cv_acquisition = {
|
|
1498
2201
|
...(detailResult.cv_acquisition || {}),
|
|
1499
2202
|
close_recovery: {
|
|
1500
2203
|
ok: false,
|
|
1501
2204
|
reason: "context_recovery_failed",
|
|
1502
2205
|
error: error?.message || String(error),
|
|
1503
|
-
forced_recent_not_view:
|
|
2206
|
+
forced_recent_not_view: Boolean(normalizedFilter.enabled)
|
|
1504
2207
|
}
|
|
1505
2208
|
};
|
|
1506
2209
|
}
|
|
@@ -1516,14 +2219,14 @@ export async function runRecommendWorkflow({
|
|
|
1516
2219
|
llm_screening: detailResult ? null : compactScreeningLlmResult(llmResult),
|
|
1517
2220
|
screening: compactScreening(screening),
|
|
1518
2221
|
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
1519
|
-
post_action: postActionResult,
|
|
1520
|
-
error: recoverableDetailError
|
|
1521
|
-
? compactRecoverableDetailError(recoverableDetailError)
|
|
1522
|
-
: closeRecoveryFailure
|
|
1523
|
-
? compactError(closeFailureError, "DETAIL_CLOSE_FAILED")
|
|
1524
|
-
: detailResult?.image_evidence?.ok === false
|
|
1525
|
-
? compactError({
|
|
1526
|
-
code: detailResult.image_evidence.error_code,
|
|
2222
|
+
post_action: postActionResult,
|
|
2223
|
+
error: recoverableDetailError
|
|
2224
|
+
? compactRecoverableDetailError(recoverableDetailError)
|
|
2225
|
+
: closeRecoveryFailure
|
|
2226
|
+
? compactError(closeFailureError, "DETAIL_CLOSE_FAILED")
|
|
2227
|
+
: detailResult?.image_evidence?.ok === false
|
|
2228
|
+
? compactError({
|
|
2229
|
+
code: detailResult.image_evidence.error_code,
|
|
1527
2230
|
message: detailResult.image_evidence.error
|
|
1528
2231
|
}, "IMAGE_CAPTURE_FAILED")
|
|
1529
2232
|
: null,
|
|
@@ -1558,70 +2261,80 @@ export async function runRecommendWorkflow({
|
|
|
1558
2261
|
error: compactResult.error,
|
|
1559
2262
|
post_action: postActionResult
|
|
1560
2263
|
}
|
|
1561
|
-
});
|
|
1562
|
-
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
1563
|
-
|
|
1564
|
-
if (closeRecoveryFailure) {
|
|
1565
|
-
throw closeRecoveryFailure;
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
if (postActionResult?.stop_run) {
|
|
1569
|
-
listEndReason = postActionResult.reason || "post_action_stop";
|
|
1570
|
-
break;
|
|
1571
|
-
}
|
|
1572
|
-
|
|
1573
|
-
if (effectiveHumanRestEnabled) {
|
|
1574
|
-
const restStarted = Date.now();
|
|
1575
|
-
const restResult = await humanRestController.takeBreakIfNeeded({
|
|
1576
|
-
sleepFn: (ms) => runControl.sleep(ms)
|
|
1577
|
-
});
|
|
1578
|
-
const restElapsed = Date.now() - restStarted;
|
|
1579
|
-
if (restResult.rested) {
|
|
1580
|
-
recordHumanEvent({
|
|
1581
|
-
kind: "rest",
|
|
1582
|
-
pause_ms: restResult.pause_ms || restElapsed,
|
|
1583
|
-
events: restResult.events || []
|
|
1584
|
-
});
|
|
1585
|
-
compactResult.human_rest = restResult;
|
|
1586
|
-
addTiming(compactResult.timings, "human_rest_ms", restElapsed);
|
|
1587
|
-
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1588
|
-
updateRecommendProgress({
|
|
1589
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1590
|
-
human_rest_last: restResult
|
|
1591
|
-
});
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (delayMs > 0) {
|
|
1596
|
-
const sleepStarted = Date.now();
|
|
1597
|
-
await runControl.sleep(delayMs);
|
|
1598
|
-
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
1599
|
-
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
2264
|
+
});
|
|
2265
|
+
addTiming(compactResult.timings, "checkpoint_save_ms", Date.now() - checkpointStarted);
|
|
2266
|
+
|
|
2267
|
+
if (closeRecoveryFailure) {
|
|
2268
|
+
throw closeRecoveryFailure;
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
if (postActionResult?.stop_run) {
|
|
2272
|
+
listEndReason = postActionResult.reason || "post_action_stop";
|
|
2273
|
+
break;
|
|
2274
|
+
}
|
|
2275
|
+
|
|
2276
|
+
if (effectiveHumanRestEnabled) {
|
|
2277
|
+
const restStarted = Date.now();
|
|
2278
|
+
const restResult = await humanRestController.takeBreakIfNeeded({
|
|
2279
|
+
sleepFn: (ms) => runControl.sleep(ms)
|
|
2280
|
+
});
|
|
2281
|
+
const restElapsed = Date.now() - restStarted;
|
|
2282
|
+
if (restResult.rested) {
|
|
2283
|
+
recordHumanEvent({
|
|
2284
|
+
kind: "rest",
|
|
2285
|
+
pause_ms: restResult.pause_ms || restElapsed,
|
|
2286
|
+
events: restResult.events || []
|
|
2287
|
+
});
|
|
2288
|
+
compactResult.human_rest = restResult;
|
|
2289
|
+
addTiming(compactResult.timings, "human_rest_ms", restElapsed);
|
|
2290
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
2291
|
+
updateRecommendProgress({
|
|
2292
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
2293
|
+
human_rest_last: restResult
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
if (delayMs > 0) {
|
|
2299
|
+
const sleepStarted = Date.now();
|
|
2300
|
+
await runControl.sleep(delayMs);
|
|
2301
|
+
addTiming(compactResult.timings, "sleep_ms", Date.now() - sleepStarted);
|
|
2302
|
+
compactResult.timings.total_ms = Date.now() - candidateStarted;
|
|
1600
2303
|
}
|
|
1601
2304
|
}
|
|
1602
2305
|
|
|
1603
2306
|
runControl.setPhase("recommend:done");
|
|
1604
|
-
return {
|
|
1605
|
-
domain: "recommend",
|
|
1606
|
-
target_url: targetUrl,
|
|
1607
|
-
job_selection: compactJobSelection(jobSelection),
|
|
1608
|
-
page_scope: compactPageScopeSelection(pageScopeSelection),
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
2307
|
+
return {
|
|
2308
|
+
domain: "recommend",
|
|
2309
|
+
target_url: targetUrl,
|
|
2310
|
+
job_selection: compactJobSelection(jobSelection),
|
|
2311
|
+
page_scope: compactPageScopeSelection(pageScopeSelection),
|
|
2312
|
+
current_city_only: compactCurrentCityOnlyResult(currentCityOnlyResult),
|
|
2313
|
+
filter: compactFilterResult(filterResult),
|
|
2314
|
+
card_count: cardNodeIds.length,
|
|
2315
|
+
candidate_list: compactInfiniteListState(listState),
|
|
1612
2316
|
viewport_health: {
|
|
1613
2317
|
stats: viewportGuard.getStats(),
|
|
1614
|
-
events: viewportGuard.getEvents()
|
|
1615
|
-
},
|
|
1616
|
-
human_behavior: effectiveHumanBehavior,
|
|
1617
|
-
human_rest: humanRestController.getState(),
|
|
1618
|
-
last_human_event: lastHumanEvent,
|
|
1619
|
-
list_end_reason: listEndReason || null,
|
|
2318
|
+
events: viewportGuard.getEvents()
|
|
2319
|
+
},
|
|
2320
|
+
human_behavior: effectiveHumanBehavior,
|
|
2321
|
+
human_rest: humanRestController.getState(),
|
|
2322
|
+
last_human_event: lastHumanEvent,
|
|
2323
|
+
list_end_reason: listEndReason || null,
|
|
1620
2324
|
refresh_rounds: refreshRounds,
|
|
1621
|
-
refresh_attempts: refreshAttempts,
|
|
1622
|
-
context_recoveries: contextRecoveryAttempts,
|
|
1623
|
-
|
|
1624
|
-
|
|
2325
|
+
refresh_attempts: refreshAttempts,
|
|
2326
|
+
context_recoveries: contextRecoveryAttempts,
|
|
2327
|
+
debug_boundary: debugBoundary.getState(),
|
|
2328
|
+
list_read_stale_recovery_attempts: listReadStaleRecoveryAttempts,
|
|
2329
|
+
list_read_stale_recovery_applied: listReadStaleRecoveryApplied,
|
|
2330
|
+
list_read_stale_recoveries: listReadStaleRecoveries,
|
|
2331
|
+
last_list_read_recovery_mode: lastListReadRecoveryMode,
|
|
2332
|
+
last_list_read_stale_diagnostic: lastListReadStaleDiagnostic,
|
|
2333
|
+
list_read_stale_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
2334
|
+
...countRecommendResultStatuses(results, { greetCount }),
|
|
2335
|
+
transient_recovered: countRecommendResultStatuses(results, { greetCount }).transient_recovered
|
|
2336
|
+
+ listReadStaleRecoveries,
|
|
2337
|
+
results
|
|
1625
2338
|
};
|
|
1626
2339
|
}
|
|
1627
2340
|
|
|
@@ -1667,48 +2380,59 @@ export function createRecommendRunService({
|
|
|
1667
2380
|
actionIntervalMs = 500,
|
|
1668
2381
|
actionAfterClickDelayMs = 900,
|
|
1669
2382
|
screeningMode = "llm",
|
|
1670
|
-
llmConfig = null,
|
|
1671
|
-
llmTimeoutMs = 120000,
|
|
1672
|
-
llmImageLimit = 8,
|
|
1673
|
-
llmImageDetail = "high",
|
|
1674
|
-
imageOutputDir = "",
|
|
1675
|
-
humanRestEnabled = false,
|
|
2383
|
+
llmConfig = null,
|
|
2384
|
+
llmTimeoutMs = 120000,
|
|
2385
|
+
llmImageLimit = 8,
|
|
2386
|
+
llmImageDetail = "high",
|
|
2387
|
+
imageOutputDir = "",
|
|
2388
|
+
humanRestEnabled = false,
|
|
1676
2389
|
humanBehavior = null,
|
|
1677
2390
|
skipRecentColleagueContacted = true,
|
|
1678
2391
|
colleagueContactWindowDays = 14,
|
|
2392
|
+
debugTestMode = undefined,
|
|
2393
|
+
debugForceListEndAfterProcessed = undefined,
|
|
2394
|
+
debugForceContextRecoveryAfterProcessed = undefined,
|
|
2395
|
+
debugForceCdpReconnectAfterProcessed = undefined,
|
|
1679
2396
|
name = "recommend-domain-run"
|
|
1680
2397
|
} = {}) {
|
|
1681
|
-
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
1682
|
-
const normalizedFilter = normalizeFilter(filter);
|
|
1683
|
-
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
1684
|
-
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
1685
|
-
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
1686
|
-
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
1687
|
-
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
1688
|
-
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
1689
|
-
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1690
|
-
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1691
|
-
});
|
|
2398
|
+
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
2399
|
+
const normalizedFilter = normalizeFilter(filter);
|
|
2400
|
+
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
2401
|
+
const requestedPageScope = normalizeRecommendPageScope(pageScope) || "recommend";
|
|
2402
|
+
const normalizedFallbackPageScope = normalizeRecommendPageScope(fallbackPageScope) || "recommend";
|
|
2403
|
+
const normalizedScreeningMode = normalizeScreeningMode(screeningMode);
|
|
2404
|
+
const shouldSkipRecentColleagueContacted = skipRecentColleagueContacted !== false;
|
|
2405
|
+
const normalizedColleagueContactWindowDays = Math.max(1, Number(colleagueContactWindowDays) || 14);
|
|
2406
|
+
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
2407
|
+
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
2408
|
+
});
|
|
1692
2409
|
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1693
2410
|
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
1694
|
-
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
1695
|
-
|
|
2411
|
+
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
2412
|
+
const debugBoundaryOptions = normalizeRecommendDebugBoundaryOptions({
|
|
2413
|
+
debugTestMode: debugTestMode === true,
|
|
2414
|
+
debugForceListEndAfterProcessed: debugForceListEndAfterProcessed ?? null,
|
|
2415
|
+
debugForceContextRecoveryAfterProcessed: debugForceContextRecoveryAfterProcessed ?? null,
|
|
2416
|
+
debugForceCdpReconnectAfterProcessed: debugForceCdpReconnectAfterProcessed ?? null
|
|
2417
|
+
});
|
|
2418
|
+
return manager.startRun({
|
|
1696
2419
|
runId,
|
|
1697
2420
|
name,
|
|
1698
2421
|
pid,
|
|
1699
2422
|
context: {
|
|
1700
2423
|
domain: "recommend",
|
|
1701
2424
|
target_url: targetUrl,
|
|
1702
|
-
criteria_present: Boolean(criteria),
|
|
1703
|
-
job_label: jobLabel || "",
|
|
1704
|
-
requested_page_scope: requestedPageScope,
|
|
1705
|
-
fallback_page_scope: normalizedFallbackPageScope,
|
|
1706
|
-
filter: normalizedFilter,
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
2425
|
+
criteria_present: Boolean(criteria),
|
|
2426
|
+
job_label: jobLabel || "",
|
|
2427
|
+
requested_page_scope: requestedPageScope,
|
|
2428
|
+
fallback_page_scope: normalizedFallbackPageScope,
|
|
2429
|
+
filter: normalizedFilter,
|
|
2430
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
2431
|
+
max_candidates: maxCandidates,
|
|
2432
|
+
max_candidates_semantics: "passed_candidates",
|
|
2433
|
+
detail_limit: normalizedDetailLimit,
|
|
2434
|
+
close_detail: closeDetail,
|
|
2435
|
+
cv_acquisition_mode: cvAcquisitionMode,
|
|
1712
2436
|
max_image_pages: maxImagePages,
|
|
1713
2437
|
image_wheel_delta_y: imageWheelDeltaY,
|
|
1714
2438
|
list_max_scrolls: listMaxScrolls,
|
|
@@ -1726,45 +2450,55 @@ export function createRecommendRunService({
|
|
|
1726
2450
|
action_timeout_ms: actionTimeoutMs,
|
|
1727
2451
|
screening_mode: normalizedScreeningMode,
|
|
1728
2452
|
llm_configured: Boolean(llmConfig),
|
|
1729
|
-
llm_timeout_ms: llmTimeoutMs,
|
|
1730
|
-
llm_image_limit: llmImageLimit,
|
|
1731
|
-
llm_image_detail: llmImageDetail,
|
|
1732
|
-
image_output_dir: imageOutputDir || "",
|
|
1733
|
-
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
1734
|
-
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
1735
|
-
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1736
|
-
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
2453
|
+
llm_timeout_ms: llmTimeoutMs,
|
|
2454
|
+
llm_image_limit: llmImageLimit,
|
|
2455
|
+
llm_image_detail: llmImageDetail,
|
|
2456
|
+
image_output_dir: imageOutputDir || "",
|
|
2457
|
+
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
2458
|
+
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
2459
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
2460
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1737
2461
|
human_behavior: effectiveHumanBehavior,
|
|
1738
2462
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1739
|
-
human_rest_enabled: effectiveHumanRestEnabled
|
|
1740
|
-
|
|
1741
|
-
|
|
2463
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
2464
|
+
debug_test_mode: debugBoundaryOptions.debugTestMode,
|
|
2465
|
+
debug_boundary_mode: debugBoundaryOptions.mode,
|
|
2466
|
+
debug_boundary_threshold: debugBoundaryOptions.threshold
|
|
2467
|
+
},
|
|
2468
|
+
progress: {
|
|
1742
2469
|
card_count: 0,
|
|
1743
2470
|
target_count: candidateLimit,
|
|
1744
2471
|
target_count_semantics: "passed_candidates",
|
|
1745
2472
|
processed: 0,
|
|
1746
2473
|
screened: 0,
|
|
1747
2474
|
detail_opened: 0,
|
|
1748
|
-
llm_screened: 0,
|
|
1749
|
-
passed: 0,
|
|
1750
|
-
skipped: 0,
|
|
1751
|
-
greet_count: 0,
|
|
2475
|
+
llm_screened: 0,
|
|
2476
|
+
passed: 0,
|
|
2477
|
+
skipped: 0,
|
|
2478
|
+
greet_count: 0,
|
|
1752
2479
|
post_action_clicked: 0,
|
|
1753
|
-
image_capture_failed: 0,
|
|
1754
|
-
detail_open_failed: 0,
|
|
2480
|
+
image_capture_failed: 0,
|
|
2481
|
+
detail_open_failed: 0,
|
|
1755
2482
|
transient_recovered: 0,
|
|
1756
2483
|
colleague_contact_checked: 0,
|
|
1757
2484
|
recent_colleague_contact_skipped: 0,
|
|
1758
2485
|
colleague_contact_panel_missing: 0,
|
|
1759
2486
|
context_recoveries: 0,
|
|
2487
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
2488
|
+
current_city_only_effective: null,
|
|
2489
|
+
current_city_only_unavailable: false,
|
|
1760
2490
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1761
2491
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1762
2492
|
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1763
2493
|
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1764
2494
|
human_rest_count: 0,
|
|
1765
2495
|
human_rest_ms: 0,
|
|
1766
|
-
last_human_event: null
|
|
1767
|
-
|
|
2496
|
+
last_human_event: null,
|
|
2497
|
+
debug_boundary_mode: debugBoundaryOptions.mode,
|
|
2498
|
+
debug_boundary_threshold: debugBoundaryOptions.threshold,
|
|
2499
|
+
debug_boundary_triggered: false,
|
|
2500
|
+
debug_boundary_trigger_count: 0
|
|
2501
|
+
},
|
|
1768
2502
|
checkpoint: {},
|
|
1769
2503
|
task: (runControl) => workflow({
|
|
1770
2504
|
client,
|
|
@@ -1799,17 +2533,21 @@ export function createRecommendRunService({
|
|
|
1799
2533
|
actionAfterClickDelayMs,
|
|
1800
2534
|
screeningMode: normalizedScreeningMode,
|
|
1801
2535
|
llmConfig,
|
|
1802
|
-
llmTimeoutMs,
|
|
1803
|
-
llmImageLimit,
|
|
1804
|
-
llmImageDetail,
|
|
1805
|
-
imageOutputDir,
|
|
1806
|
-
humanRestEnabled: effectiveHumanRestEnabled,
|
|
2536
|
+
llmTimeoutMs,
|
|
2537
|
+
llmImageLimit,
|
|
2538
|
+
llmImageDetail,
|
|
2539
|
+
imageOutputDir,
|
|
2540
|
+
humanRestEnabled: effectiveHumanRestEnabled,
|
|
1807
2541
|
humanBehavior: effectiveHumanBehavior,
|
|
1808
2542
|
skipRecentColleagueContacted: shouldSkipRecentColleagueContacted,
|
|
1809
|
-
colleagueContactWindowDays: normalizedColleagueContactWindowDays
|
|
2543
|
+
colleagueContactWindowDays: normalizedColleagueContactWindowDays,
|
|
2544
|
+
debugTestMode: debugBoundaryOptions.debugTestMode,
|
|
2545
|
+
debugForceListEndAfterProcessed: debugBoundaryOptions.debugForceListEndAfterProcessed,
|
|
2546
|
+
debugForceContextRecoveryAfterProcessed: debugBoundaryOptions.debugForceContextRecoveryAfterProcessed,
|
|
2547
|
+
debugForceCdpReconnectAfterProcessed: debugBoundaryOptions.debugForceCdpReconnectAfterProcessed
|
|
1810
2548
|
}, runControl)
|
|
1811
|
-
});
|
|
1812
|
-
}
|
|
2549
|
+
});
|
|
2550
|
+
}
|
|
1813
2551
|
|
|
1814
2552
|
return {
|
|
1815
2553
|
startRecommendRun,
|