@reconcrap/boss-recommend-mcp 2.1.23 → 2.1.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/bin/boss-recommend-mcp.js +4 -4
- package/package.json +6 -1
- package/scripts/install-macos.sh +280 -280
- package/scripts/postinstall.cjs +44 -44
- package/skills/boss-chat/README.md +43 -42
- package/skills/boss-chat/SKILL.md +107 -106
- package/skills/boss-recommend-pipeline/README.md +13 -13
- package/skills/boss-recommend-pipeline/SKILL.md +47 -47
- package/skills/boss-recruit-pipeline/README.md +19 -19
- package/skills/boss-recruit-pipeline/SKILL.md +89 -89
- package/src/chat-mcp.js +301 -127
- package/src/core/boss-cards/index.js +199 -199
- package/src/core/browser/index.js +286 -136
- package/src/core/capture/index.js +2930 -1201
- package/src/core/cv-acquisition/index.js +512 -238
- package/src/core/cv-capture-target/index.js +513 -299
- package/src/core/greet-quota/index.js +71 -71
- package/src/core/infinite-list/index.js +31 -31
- package/src/core/reporting/legacy-csv.js +12 -12
- package/src/core/run/detached-launcher.js +305 -0
- package/src/core/run/index.js +105 -52
- package/src/core/run/timing.js +33 -33
- package/src/core/run/windows-detached-worker.ps1 +106 -0
- package/src/core/screening/index.js +2135 -2135
- package/src/core/self-heal/index.js +989 -973
- package/src/core/self-heal/viewport.js +1505 -564
- package/src/detached-worker.js +99 -99
- package/src/domains/chat/action-journal.js +443 -0
- package/src/domains/chat/cards.js +137 -137
- package/src/domains/chat/constants.js +9 -9
- package/src/domains/chat/detail.js +1684 -401
- package/src/domains/chat/index.js +8 -7
- package/src/domains/chat/jobs.js +620 -620
- package/src/domains/chat/page-guard.js +157 -122
- package/src/domains/chat/roots.js +56 -56
- package/src/domains/chat/run-service.js +1801 -760
- package/src/domains/common/account-rights-panel.js +314 -314
- package/src/domains/common/recovery-settle.js +159 -159
- package/src/domains/recommend/actions.js +515 -472
- package/src/domains/recommend/cards.js +243 -243
- package/src/domains/recommend/colleague-contact.js +333 -333
- package/src/domains/recommend/constants.js +92 -92
- package/src/domains/recommend/detail.js +12 -12
- package/src/domains/recommend/filters.js +611 -611
- package/src/domains/recommend/index.js +9 -9
- package/src/domains/recommend/jobs.js +542 -542
- package/src/domains/recommend/location.js +736 -736
- package/src/domains/recommend/refresh.js +422 -422
- package/src/domains/recommend/roots.js +80 -80
- package/src/domains/recommend/run-service.js +1791 -1205
- package/src/domains/recommend/scopes.js +246 -246
- package/src/domains/recruit/actions.js +277 -277
- package/src/domains/recruit/cards.js +74 -74
- package/src/domains/recruit/constants.js +236 -236
- package/src/domains/recruit/detail.js +588 -588
- package/src/domains/recruit/index.js +9 -9
- package/src/domains/recruit/instruction-parser.js +866 -866
- package/src/domains/recruit/refresh.js +45 -45
- package/src/domains/recruit/roots.js +68 -68
- package/src/domains/recruit/run-service.js +1817 -1620
- package/src/domains/recruit/search.js +3229 -3229
- package/src/index.js +16 -1
- package/src/parser.js +1296 -1296
- package/src/recommend-mcp.js +551 -362
- package/src/recommend-scheduler.js +75 -75
|
@@ -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,
|
|
@@ -16,16 +16,25 @@ import {
|
|
|
16
16
|
sleep
|
|
17
17
|
} from "../../core/browser/index.js";
|
|
18
18
|
import { GREET_CREDITS_EXHAUSTED_CODE } from "../../core/greet-quota/index.js";
|
|
19
|
-
import {
|
|
20
|
-
|
|
19
|
+
import {
|
|
20
|
+
attemptImageCaptureCheckpointResume,
|
|
21
|
+
compactCvAcquisitionState,
|
|
22
|
+
createImageCaptureWorkflowRetryTracker,
|
|
23
|
+
createRequiredImageEvidenceFailure,
|
|
24
|
+
imageCaptureResumeCheckpoint,
|
|
21
25
|
countParsedNetworkProfiles,
|
|
22
26
|
createCvAcquisitionState,
|
|
23
27
|
DEFAULT_MAX_IMAGE_PAGES,
|
|
24
|
-
getCvNetworkWaitPlan,
|
|
25
|
-
|
|
28
|
+
getCvNetworkWaitPlan,
|
|
29
|
+
isFailedClosedImageAcquisition,
|
|
30
|
+
isIncompleteImageEvidence,
|
|
31
|
+
isRecoverableImageCaptureWorkflowError,
|
|
32
|
+
recordCvImageFallback,
|
|
26
33
|
recordCvNetworkHit,
|
|
27
|
-
recordCvNetworkMiss,
|
|
28
|
-
|
|
34
|
+
recordCvNetworkMiss,
|
|
35
|
+
reacquireImageCaptureResumeTarget,
|
|
36
|
+
requireCompleteImageEvidence,
|
|
37
|
+
summarizeImageEvidence,
|
|
29
38
|
waitForCvNetworkEvents
|
|
30
39
|
} from "../../core/cv-acquisition/index.js";
|
|
31
40
|
import {
|
|
@@ -55,22 +64,23 @@ import {
|
|
|
55
64
|
extractRecommendDetailCandidate,
|
|
56
65
|
isRecommendDetailOpenMissError,
|
|
57
66
|
isStaleRecommendNodeError,
|
|
58
|
-
openRecommendCardDetailWithFreshRetry,
|
|
59
|
-
|
|
67
|
+
openRecommendCardDetailWithFreshRetry,
|
|
68
|
+
waitForRecommendDetail,
|
|
69
|
+
waitForRecommendDetailNetworkEvents
|
|
60
70
|
} 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";
|
|
67
|
-
import {
|
|
68
|
-
applyRecommendFilterEnvelopeStages,
|
|
69
|
-
buildRecommendFilterSelectionOptions,
|
|
70
|
-
refreshRecommendListAtEnd
|
|
71
|
-
} from "./refresh.js";
|
|
72
|
-
import { selectRecommendJob } from "./jobs.js";
|
|
73
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
readRecommendCardCandidate,
|
|
73
|
+
waitForRecommendCardNodeIds
|
|
74
|
+
} from "./cards.js";
|
|
75
|
+
import { selectAndConfirmFirstSafeFilter } from "./filters.js";
|
|
76
|
+
import { ensureRecommendCurrentCityOnly } from "./location.js";
|
|
77
|
+
import {
|
|
78
|
+
applyRecommendFilterEnvelopeStages,
|
|
79
|
+
buildRecommendFilterSelectionOptions,
|
|
80
|
+
refreshRecommendListAtEnd
|
|
81
|
+
} from "./refresh.js";
|
|
82
|
+
import { selectRecommendJob } from "./jobs.js";
|
|
83
|
+
import {
|
|
74
84
|
normalizeRecommendPageScope,
|
|
75
85
|
selectRecommendPageScope
|
|
76
86
|
} from "./scopes.js";
|
|
@@ -88,116 +98,249 @@ import {
|
|
|
88
98
|
resolveRecommendPostAction,
|
|
89
99
|
waitForRecommendDetailActionControls
|
|
90
100
|
} from "./actions.js";
|
|
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;
|
|
101
|
+
import { getRecommendRoots } from "./roots.js";
|
|
102
|
+
|
|
103
|
+
const RECOMMEND_DEBUG_BOUNDARY_MODES = Object.freeze({
|
|
104
|
+
list_end: "debug_force_list_end_after_processed",
|
|
105
|
+
context_recovery: "debug_force_context_recovery_after_processed",
|
|
106
|
+
cdp_reconnect: "debug_force_cdp_reconnect_after_processed"
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
function hasOwn(source, key) {
|
|
110
|
+
return Boolean(source && Object.prototype.hasOwnProperty.call(source, key));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function readDebugBoundaryValue(source, snakeKey, camelKey) {
|
|
114
|
+
if (hasOwn(source, snakeKey)) return source[snakeKey];
|
|
115
|
+
if (hasOwn(source, camelKey)) return source[camelKey];
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizeDebugBoundaryThreshold(raw, field) {
|
|
120
|
+
if (raw === undefined || raw === null || raw === "") return null;
|
|
121
|
+
const parsed = Number(raw);
|
|
122
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
123
|
+
const error = new Error(`${field} must be a positive integer`);
|
|
124
|
+
error.code = "INVALID_RECOMMEND_DEBUG_BOUNDARY";
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
return parsed;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function normalizeRecommendDebugBoundaryOptions(source = {}) {
|
|
131
|
+
const debugTestMode = source.debugTestMode === true || source.debug_test_mode === true;
|
|
132
|
+
const thresholds = {
|
|
133
|
+
list_end: normalizeDebugBoundaryThreshold(
|
|
134
|
+
readDebugBoundaryValue(
|
|
135
|
+
source,
|
|
136
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.list_end,
|
|
137
|
+
"debugForceListEndAfterProcessed"
|
|
138
|
+
),
|
|
139
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.list_end
|
|
140
|
+
),
|
|
141
|
+
context_recovery: normalizeDebugBoundaryThreshold(
|
|
142
|
+
readDebugBoundaryValue(
|
|
143
|
+
source,
|
|
144
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.context_recovery,
|
|
145
|
+
"debugForceContextRecoveryAfterProcessed"
|
|
146
|
+
),
|
|
147
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.context_recovery
|
|
148
|
+
),
|
|
149
|
+
cdp_reconnect: normalizeDebugBoundaryThreshold(
|
|
150
|
+
readDebugBoundaryValue(
|
|
151
|
+
source,
|
|
152
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.cdp_reconnect,
|
|
153
|
+
"debugForceCdpReconnectAfterProcessed"
|
|
154
|
+
),
|
|
155
|
+
RECOMMEND_DEBUG_BOUNDARY_MODES.cdp_reconnect
|
|
156
|
+
)
|
|
157
|
+
};
|
|
158
|
+
const configured = Object.entries(thresholds).filter(([, threshold]) => threshold !== null);
|
|
159
|
+
if (configured.length > 1) {
|
|
160
|
+
const error = new Error(
|
|
161
|
+
`${configured.map(([mode]) => RECOMMEND_DEBUG_BOUNDARY_MODES[mode]).join(", ")} are mutually exclusive`
|
|
162
|
+
);
|
|
163
|
+
error.code = "RECOMMEND_DEBUG_BOUNDARIES_MUTUALLY_EXCLUSIVE";
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
if (configured.length && !debugTestMode) {
|
|
167
|
+
const error = new Error(
|
|
168
|
+
`${RECOMMEND_DEBUG_BOUNDARY_MODES[configured[0][0]]} requires debug_test_mode=true`
|
|
169
|
+
);
|
|
170
|
+
error.code = "DEBUG_TEST_MODE_REQUIRED";
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
const [configuredEntry = null] = configured;
|
|
174
|
+
return {
|
|
175
|
+
debugTestMode,
|
|
176
|
+
mode: configuredEntry?.[0] || null,
|
|
177
|
+
field: configuredEntry ? RECOMMEND_DEBUG_BOUNDARY_MODES[configuredEntry[0]] : null,
|
|
178
|
+
threshold: configuredEntry?.[1] ?? null,
|
|
179
|
+
debugForceListEndAfterProcessed: thresholds.list_end,
|
|
180
|
+
debugForceContextRecoveryAfterProcessed: thresholds.context_recovery,
|
|
181
|
+
debugForceCdpReconnectAfterProcessed: thresholds.cdp_reconnect
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function createRecommendDebugBoundaryController(source = {}) {
|
|
186
|
+
const config = normalizeRecommendDebugBoundaryOptions(source);
|
|
187
|
+
let triggered = false;
|
|
188
|
+
let triggerCount = 0;
|
|
189
|
+
return {
|
|
190
|
+
config,
|
|
191
|
+
take(processedCount) {
|
|
192
|
+
const processed = Math.max(0, Number(processedCount) || 0);
|
|
193
|
+
if (triggered || !config.mode || processed < config.threshold) return null;
|
|
194
|
+
triggered = true;
|
|
195
|
+
triggerCount += 1;
|
|
196
|
+
return {
|
|
197
|
+
mode: config.mode,
|
|
198
|
+
field: config.field,
|
|
199
|
+
threshold: config.threshold,
|
|
200
|
+
processed,
|
|
201
|
+
trigger_count: triggerCount
|
|
202
|
+
};
|
|
203
|
+
},
|
|
204
|
+
getState() {
|
|
205
|
+
return {
|
|
206
|
+
...config,
|
|
207
|
+
triggered,
|
|
208
|
+
trigger_count: triggerCount
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
};
|
|
107
212
|
}
|
|
108
213
|
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
214
|
+
function readErrorChainField(error, key) {
|
|
215
|
+
const seen = new Set();
|
|
216
|
+
let current = error;
|
|
217
|
+
for (let depth = 0; current && depth < 6; depth += 1) {
|
|
218
|
+
if ((typeof current === "object" || typeof current === "function") && seen.has(current)) break;
|
|
219
|
+
if (typeof current === "object" || typeof current === "function") seen.add(current);
|
|
220
|
+
if (current?.[key] !== undefined && current?.[key] !== null) return current[key];
|
|
221
|
+
current = current?.cause || null;
|
|
116
222
|
}
|
|
117
|
-
return
|
|
223
|
+
return undefined;
|
|
118
224
|
}
|
|
119
225
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
),
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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
|
-
)
|
|
226
|
+
function compactCdpFailureDiagnostic(error, {
|
|
227
|
+
fallbackCode = "RECOMMEND_DOM_STALE",
|
|
228
|
+
fallbackPhase = ""
|
|
229
|
+
} = {}) {
|
|
230
|
+
const message = String(readErrorChainField(error, "message") || error || "DOM node became stale");
|
|
231
|
+
const diagnostic = {
|
|
232
|
+
name: String(readErrorChainField(error, "name") || "Error").slice(0, 100),
|
|
233
|
+
code: String(readErrorChainField(error, "code") || fallbackCode).slice(0, 160),
|
|
234
|
+
message: message.slice(0, 500),
|
|
235
|
+
phase: String(readErrorChainField(error, "phase") || fallbackPhase || "").slice(0, 200) || null,
|
|
236
|
+
cdp_method: String(readErrorChainField(error, "cdp_method") || "").slice(0, 200) || null,
|
|
237
|
+
cdp_at: String(readErrorChainField(error, "cdp_at") || "").slice(0, 100) || null,
|
|
238
|
+
cdp_search_id: String(readErrorChainField(error, "cdp_search_id") || "").slice(0, 200) || null,
|
|
239
|
+
cdp_replay_policy: String(readErrorChainField(error, "cdp_replay_policy") || "").slice(0, 100) || null,
|
|
240
|
+
cdp_reconnect_error: String(readErrorChainField(error, "cdp_reconnect_error") || "").slice(0, 300) || null
|
|
147
241
|
};
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
242
|
+
for (const key of [
|
|
243
|
+
"cdp_node_id",
|
|
244
|
+
"cdp_backend_node_id",
|
|
245
|
+
"cdp_connection_epoch",
|
|
246
|
+
"cdp_reconnected_epoch"
|
|
247
|
+
]) {
|
|
248
|
+
const value = Number(readErrorChainField(error, key));
|
|
249
|
+
diagnostic[key] = Number.isInteger(value) && value >= 0 ? value : null;
|
|
155
250
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
251
|
+
for (const key of [
|
|
252
|
+
"cdp_reconnected",
|
|
253
|
+
"cdp_replayed_after_reconnect",
|
|
254
|
+
"cdp_replay_suppressed",
|
|
255
|
+
"cdp_outcome_unknown"
|
|
256
|
+
]) {
|
|
257
|
+
const value = readErrorChainField(error, key);
|
|
258
|
+
diagnostic[key] = typeof value === "boolean" ? value : null;
|
|
162
259
|
}
|
|
163
|
-
const
|
|
260
|
+
const paramKeys = readErrorChainField(error, "cdp_param_keys");
|
|
261
|
+
diagnostic.cdp_param_keys = Array.isArray(paramKeys)
|
|
262
|
+
? paramKeys
|
|
263
|
+
.map((key) => String(key || "").trim())
|
|
264
|
+
.filter((key) => /^[A-Za-z][A-Za-z0-9_]*$/.test(key))
|
|
265
|
+
.slice(0, 20)
|
|
266
|
+
: [];
|
|
267
|
+
return diagnostic;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
export function compactRecommendDomRootIdentity(rootState = null, connectionEpoch = null) {
|
|
271
|
+
const epoch = Number(connectionEpoch);
|
|
272
|
+
const topNodeId = Number(rootState?.rootNodes?.top || rootState?.topRoot?.nodeId || 0);
|
|
273
|
+
const frameOwnerNodeId = Number(rootState?.rootNodes?.frameOwner || rootState?.iframe?.nodeId || 0);
|
|
274
|
+
const frameDocumentNodeId = Number(
|
|
275
|
+
rootState?.rootNodes?.frame || rootState?.iframe?.documentNodeId || 0
|
|
276
|
+
);
|
|
164
277
|
return {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
278
|
+
connection_epoch: Number.isInteger(epoch) && epoch > 0 ? epoch : null,
|
|
279
|
+
top_document_node_id: Number.isInteger(topNodeId) && topNodeId > 0 ? topNodeId : null,
|
|
280
|
+
iframe_owner_node_id: Number.isInteger(frameOwnerNodeId) && frameOwnerNodeId > 0
|
|
281
|
+
? frameOwnerNodeId
|
|
282
|
+
: null,
|
|
283
|
+
iframe_document_node_id: Number.isInteger(frameDocumentNodeId) && frameDocumentNodeId > 0
|
|
284
|
+
? frameDocumentNodeId
|
|
285
|
+
: null,
|
|
286
|
+
iframe_selector: String(rootState?.iframe?.selector || "").slice(0, 300) || null
|
|
172
287
|
};
|
|
173
288
|
}
|
|
174
289
|
|
|
175
|
-
|
|
176
|
-
const
|
|
177
|
-
let
|
|
178
|
-
|
|
290
|
+
function latestInfiniteListReadError(listState = null) {
|
|
291
|
+
const ledger = Array.isArray(listState?.ledger) ? listState.ledger : [];
|
|
292
|
+
for (let index = ledger.length - 1; index >= 0; index -= 1) {
|
|
293
|
+
const item = ledger[index];
|
|
294
|
+
if (item?.event !== "candidate_read_error") continue;
|
|
295
|
+
return {
|
|
296
|
+
at: item.at || null,
|
|
297
|
+
node_id: Number.isInteger(item.node_id) ? item.node_id : null,
|
|
298
|
+
visible_index: Number.isInteger(item.visible_index) ? item.visible_index : null,
|
|
299
|
+
error: String(item.error || "").slice(0, 500) || null
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export function createRecommendDomStaleForensicEvent(error, {
|
|
306
|
+
eventId = "",
|
|
307
|
+
phase = "",
|
|
308
|
+
operation = "",
|
|
309
|
+
detailStep = "",
|
|
310
|
+
candidateIndex = null,
|
|
311
|
+
candidateKey = "",
|
|
312
|
+
cardNodeId = null,
|
|
313
|
+
rootState = null,
|
|
314
|
+
connectionEpoch = null,
|
|
315
|
+
listState = null,
|
|
316
|
+
counters = null,
|
|
317
|
+
timeline = []
|
|
318
|
+
} = {}) {
|
|
319
|
+
const at = new Date().toISOString();
|
|
320
|
+
const listReadError = latestInfiniteListReadError(listState);
|
|
179
321
|
return {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
322
|
+
schema_version: 1,
|
|
323
|
+
event_id: String(eventId || `recommend_dom_stale_${Date.now()}`).slice(0, 160),
|
|
324
|
+
event_type: "dom_stale",
|
|
325
|
+
at,
|
|
326
|
+
phase: String(phase || "").slice(0, 200) || null,
|
|
327
|
+
operation: String(operation || "").slice(0, 200) || null,
|
|
328
|
+
detail_step: String(detailStep || "").slice(0, 200) || null,
|
|
329
|
+
candidate: {
|
|
330
|
+
index: Number.isInteger(candidateIndex) && candidateIndex >= 0 ? candidateIndex : null,
|
|
331
|
+
key: String(candidateKey || "").slice(0, 300) || null,
|
|
332
|
+
card_node_id: Number.isInteger(cardNodeId) && cardNodeId > 0 ? cardNodeId : null,
|
|
333
|
+
visible_index: listReadError?.visible_index ?? null,
|
|
334
|
+
failing_list_node_id: listReadError?.node_id ?? null
|
|
193
335
|
},
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
336
|
+
error: compactCdpFailureDiagnostic(error, {
|
|
337
|
+
fallbackCode: "RECOMMEND_DOM_STALE",
|
|
338
|
+
fallbackPhase: phase
|
|
339
|
+
}),
|
|
340
|
+
pre_recovery_roots: compactRecommendDomRootIdentity(rootState, connectionEpoch),
|
|
341
|
+
candidate_list: compactInfiniteListState(listState || {}),
|
|
342
|
+
counters: counters && typeof counters === "object" ? counters : null,
|
|
343
|
+
lifecycle_timeline: Array.isArray(timeline) ? timeline.slice(-20) : []
|
|
201
344
|
};
|
|
202
345
|
}
|
|
203
346
|
|
|
@@ -205,187 +348,180 @@ function compactListReadStaleDiagnostic(error, {
|
|
|
205
348
|
attempt = 0,
|
|
206
349
|
exhausted = false
|
|
207
350
|
} = {}) {
|
|
351
|
+
const cdp = compactCdpFailureDiagnostic(error, {
|
|
352
|
+
fallbackCode: "RECOMMEND_LIST_READ_STALE_NODE",
|
|
353
|
+
fallbackPhase: "recommend:list-read"
|
|
354
|
+
});
|
|
208
355
|
return {
|
|
209
|
-
|
|
210
|
-
|
|
356
|
+
...cdp,
|
|
357
|
+
code: error?.code || cdp.code || "RECOMMEND_LIST_READ_STALE_NODE",
|
|
358
|
+
message: String(error?.message || cdp.message || error || "Stale recommend list node").slice(0, 500),
|
|
211
359
|
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
360
|
attempt,
|
|
225
361
|
exhausted: Boolean(exhausted),
|
|
226
362
|
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 = []) {
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function annotateListReadStaleFailure(error, diagnostics, {
|
|
367
|
+
exhausted = false,
|
|
368
|
+
recoveryFailed = false
|
|
369
|
+
} = {}) {
|
|
370
|
+
if (!error || typeof error !== "object") return error;
|
|
371
|
+
error.phase = error.phase || "recommend:list-read";
|
|
372
|
+
error.list_read_stale_recovery_attempts = diagnostics;
|
|
373
|
+
if (exhausted) error.list_read_stale_recovery_exhausted = true;
|
|
374
|
+
if (recoveryFailed) error.list_read_stale_recovery_failed = true;
|
|
375
|
+
return error;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
export async function acquireRecommendListReadWithStaleRecovery({
|
|
379
|
+
acquire,
|
|
380
|
+
recover,
|
|
381
|
+
maxRetries = 2,
|
|
382
|
+
onStale = null,
|
|
383
|
+
onRecoveryApplied = null,
|
|
384
|
+
onRecovered = null,
|
|
385
|
+
onExhausted = null
|
|
386
|
+
} = {}) {
|
|
387
|
+
if (typeof acquire !== "function") {
|
|
388
|
+
throw new Error("acquireRecommendListReadWithStaleRecovery requires acquire");
|
|
389
|
+
}
|
|
390
|
+
if (typeof recover !== "function") {
|
|
391
|
+
throw new Error("acquireRecommendListReadWithStaleRecovery requires recover");
|
|
392
|
+
}
|
|
393
|
+
const retryLimit = Math.max(0, Number.isInteger(maxRetries) ? maxRetries : 2);
|
|
394
|
+
const diagnostics = [];
|
|
395
|
+
let acquireAttempt = 0;
|
|
396
|
+
let pendingRecoveryDiagnostic = null;
|
|
397
|
+
while (true) {
|
|
398
|
+
acquireAttempt += 1;
|
|
399
|
+
try {
|
|
400
|
+
const result = await acquire({
|
|
401
|
+
acquireAttempt,
|
|
402
|
+
recoveryCount: diagnostics.filter((item) => item.recovered === true).length
|
|
403
|
+
});
|
|
404
|
+
if (pendingRecoveryDiagnostic) {
|
|
405
|
+
pendingRecoveryDiagnostic.recovered = true;
|
|
406
|
+
pendingRecoveryDiagnostic.recovered_at = new Date().toISOString();
|
|
407
|
+
if (typeof onRecovered === "function") {
|
|
408
|
+
await onRecovered({
|
|
409
|
+
diagnostic: pendingRecoveryDiagnostic,
|
|
410
|
+
diagnostics: diagnostics.slice()
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
pendingRecoveryDiagnostic = null;
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
result,
|
|
417
|
+
acquire_attempts: acquireAttempt,
|
|
418
|
+
stale_diagnostics: diagnostics
|
|
419
|
+
};
|
|
420
|
+
} catch (error) {
|
|
421
|
+
if (!isStaleRecommendNodeError(error)) throw error;
|
|
422
|
+
pendingRecoveryDiagnostic = null;
|
|
423
|
+
const staleAttempt = diagnostics.length + 1;
|
|
424
|
+
const exhausted = staleAttempt > retryLimit;
|
|
425
|
+
const diagnostic = compactListReadStaleDiagnostic(error, {
|
|
426
|
+
attempt: staleAttempt,
|
|
427
|
+
exhausted
|
|
428
|
+
});
|
|
429
|
+
diagnostics.push(diagnostic);
|
|
430
|
+
if (exhausted) {
|
|
431
|
+
if (typeof onExhausted === "function") {
|
|
432
|
+
await onExhausted({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
433
|
+
}
|
|
434
|
+
throw annotateListReadStaleFailure(error, diagnostics, { exhausted: true });
|
|
435
|
+
}
|
|
436
|
+
if (typeof onStale === "function") {
|
|
437
|
+
await onStale({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
438
|
+
}
|
|
439
|
+
let recoveryResult = null;
|
|
440
|
+
try {
|
|
441
|
+
recoveryResult = await recover({ error, diagnostic, diagnostics: diagnostics.slice() });
|
|
442
|
+
} catch (recoveryError) {
|
|
443
|
+
if (recoveryError && typeof recoveryError === "object" && !recoveryError.cause) {
|
|
444
|
+
recoveryError.cause = error;
|
|
445
|
+
}
|
|
446
|
+
throw annotateListReadStaleFailure(recoveryError, diagnostics, {
|
|
447
|
+
recoveryFailed: true
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
diagnostic.recovery_applied = true;
|
|
451
|
+
diagnostic.recovery_mode = recoveryResult?.recovery_mode || "unknown";
|
|
452
|
+
diagnostic.recovery_applied_at = new Date().toISOString();
|
|
453
|
+
pendingRecoveryDiagnostic = diagnostic;
|
|
454
|
+
if (typeof onRecoveryApplied === "function") {
|
|
455
|
+
await onRecoveryApplied({
|
|
456
|
+
error,
|
|
457
|
+
diagnostic,
|
|
458
|
+
recoveryResult,
|
|
459
|
+
diagnostics: diagnostics.slice()
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export async function recoverRecommendListReadStaleContext({
|
|
467
|
+
staleAttempt = 1,
|
|
468
|
+
listState,
|
|
469
|
+
rootReacquire,
|
|
470
|
+
contextReapply
|
|
471
|
+
} = {}) {
|
|
472
|
+
if (typeof rootReacquire !== "function") {
|
|
473
|
+
throw new Error("recoverRecommendListReadStaleContext requires rootReacquire");
|
|
474
|
+
}
|
|
475
|
+
if (typeof contextReapply !== "function") {
|
|
476
|
+
throw new Error("recoverRecommendListReadStaleContext requires contextReapply");
|
|
477
|
+
}
|
|
478
|
+
const processedKeys = new Set(listState?.processed_keys || []);
|
|
479
|
+
try {
|
|
480
|
+
if (Number(staleAttempt) <= 1) {
|
|
481
|
+
try {
|
|
482
|
+
const rootResult = await rootReacquire();
|
|
483
|
+
if (Number(rootResult?.card_count) <= 0) {
|
|
484
|
+
const noCardsError = new Error("Recommend list root reacquire returned no candidate cards");
|
|
485
|
+
noCardsError.code = "RECOMMEND_LIST_ROOT_REACQUIRE_NO_CARDS";
|
|
486
|
+
throw noCardsError;
|
|
487
|
+
}
|
|
488
|
+
resetInfiniteListForRefreshRound(listState, {
|
|
489
|
+
reason: "list_read_stale_node_root_reacquire",
|
|
490
|
+
round: 1,
|
|
491
|
+
method: "root_reacquire",
|
|
492
|
+
metadata: {
|
|
493
|
+
card_count: Number(rootResult?.card_count) || 0,
|
|
494
|
+
processed_count: processedKeys.size
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
return {
|
|
498
|
+
recovery_mode: "root_reacquire",
|
|
499
|
+
root_reacquire: rootResult || null
|
|
500
|
+
};
|
|
501
|
+
} catch (rootReacquireError) {
|
|
502
|
+
const contextResult = await contextReapply({ rootReacquireError });
|
|
503
|
+
return {
|
|
504
|
+
recovery_mode: "context_reapply",
|
|
505
|
+
escalated_from: "root_reacquire",
|
|
506
|
+
root_reacquire_error: compactListReadStaleDiagnostic(rootReacquireError, {
|
|
507
|
+
attempt: 1
|
|
508
|
+
}),
|
|
509
|
+
context_reapply: contextResult || null
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const contextResult = await contextReapply({ rootReacquireError: null });
|
|
514
|
+
return {
|
|
515
|
+
recovery_mode: "context_reapply",
|
|
516
|
+
escalated_from: "repeated_stale",
|
|
517
|
+
context_reapply: contextResult || null
|
|
518
|
+
};
|
|
519
|
+
} finally {
|
|
520
|
+
for (const key of processedKeys) listState?.processed_keys?.add(key);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function normalizeLabels(labels = []) {
|
|
389
525
|
return labels.map((label) => String(label || "").trim()).filter(Boolean);
|
|
390
526
|
}
|
|
391
527
|
|
|
@@ -401,46 +537,46 @@ function isRefreshableListStall(reason = "") {
|
|
|
401
537
|
function normalizeFilter(filter = {}) {
|
|
402
538
|
const filterGroups = Array.isArray(filter.filterGroups)
|
|
403
539
|
? filter.filterGroups
|
|
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
|
-
})),
|
|
540
|
+
: Array.isArray(filter.groups)
|
|
541
|
+
? filter.groups
|
|
542
|
+
: [];
|
|
543
|
+
return {
|
|
544
|
+
enabled: filter.enabled !== false,
|
|
545
|
+
currentCityOnly: filter.currentCityOnly === true || filter.current_city_only === true,
|
|
546
|
+
group: String(filter.group || ""),
|
|
547
|
+
labels: normalizeLabels(filter.labels || filter.filterLabels || []),
|
|
548
|
+
selectAllLabels: Boolean(filter.selectAllLabels),
|
|
549
|
+
allowUnlimited: filter.allowUnlimited === true,
|
|
550
|
+
verifySticky: filter.verifySticky === true,
|
|
551
|
+
filterGroups: filterGroups.map((group) => ({
|
|
552
|
+
group: String(group?.group || ""),
|
|
553
|
+
labels: normalizeLabels(group?.labels || group?.filterLabels || []),
|
|
554
|
+
selectAllLabels: group?.selectAllLabels !== false,
|
|
555
|
+
allowUnlimited: group?.allowUnlimited === true,
|
|
556
|
+
verifySticky: group?.verifySticky === true
|
|
557
|
+
})).filter((group) => group.group || group.labels.length)
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
export function compactFilterResult(filterResult) {
|
|
562
|
+
if (!filterResult) return null;
|
|
563
|
+
return {
|
|
564
|
+
opened_panel: Boolean(filterResult.opened_panel),
|
|
565
|
+
requested_groups: (filterResult.requested_groups || []).map((group) => ({
|
|
566
|
+
group: group.group,
|
|
567
|
+
labels: group.labels || [],
|
|
568
|
+
select_all_labels: group.select_all_labels !== false,
|
|
569
|
+
allow_unlimited: Boolean(group.allow_unlimited),
|
|
570
|
+
verify_sticky: Boolean(group.verify_sticky)
|
|
571
|
+
})),
|
|
572
|
+
effective_groups: (filterResult.sticky_verification?.groups || []).map((group) => ({
|
|
573
|
+
group: group.group,
|
|
574
|
+
requested_labels: group.requested_labels || [],
|
|
575
|
+
active_labels: group.active_labels || [],
|
|
576
|
+
verified: Boolean(group.verified),
|
|
577
|
+
unavailable: Boolean(group.unavailable),
|
|
578
|
+
reason: group.reason || null
|
|
579
|
+
})),
|
|
444
580
|
selected_option: filterResult.selected_option
|
|
445
581
|
? {
|
|
446
582
|
group: filterResult.selected_option.group,
|
|
@@ -450,71 +586,71 @@ export function compactFilterResult(filterResult) {
|
|
|
450
586
|
}
|
|
451
587
|
: null,
|
|
452
588
|
selected_options: (filterResult.selected_options || []).map((option) => ({
|
|
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
|
|
498
|
-
? {
|
|
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
|
|
506
|
-
}
|
|
507
|
-
: null,
|
|
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),
|
|
589
|
+
group: option.group,
|
|
590
|
+
label: option.label,
|
|
591
|
+
was_active: Boolean(option.was_active),
|
|
592
|
+
clicked: option.clicked !== false
|
|
593
|
+
})),
|
|
594
|
+
unavailable: Boolean(filterResult.unavailable),
|
|
595
|
+
unavailable_groups: filterResult.unavailable_groups || [],
|
|
596
|
+
confirmed: Boolean(filterResult.confirmed),
|
|
597
|
+
sticky_verification: filterResult.sticky_verification || null,
|
|
598
|
+
attempts: {
|
|
599
|
+
initial_close: filterResult.initial_close_attempts || [],
|
|
600
|
+
open: (filterResult.open_attempts || []).map((attempt) => ({
|
|
601
|
+
selector: attempt.selector || null,
|
|
602
|
+
node_id: attempt.node_id || null,
|
|
603
|
+
click_target: attempt.click_target || null
|
|
604
|
+
})),
|
|
605
|
+
confirmation: (filterResult.confirm_attempts || []).map((attempt) => ({
|
|
606
|
+
node_id: attempt.node_id || null,
|
|
607
|
+
label: attempt.label || null,
|
|
608
|
+
clicked: Boolean(attempt.clicked),
|
|
609
|
+
errors: (attempt.errors || []).map((error) => ({
|
|
610
|
+
node_id: error.node_id || null,
|
|
611
|
+
message: error.message || String(error)
|
|
612
|
+
}))
|
|
613
|
+
}))
|
|
614
|
+
},
|
|
615
|
+
before_counts: filterResult.before_counts,
|
|
616
|
+
after_confirm_counts: filterResult.after_confirm_counts
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
function compactCurrentCityOnlyResult(result) {
|
|
621
|
+
if (!result) return null;
|
|
622
|
+
return {
|
|
623
|
+
requested: Boolean(result.requested),
|
|
624
|
+
effective: typeof result.effective === "boolean" ? result.effective : null,
|
|
625
|
+
available: result.available !== false,
|
|
626
|
+
unavailable: Boolean(result.unavailable),
|
|
627
|
+
reason: result.reason || null,
|
|
628
|
+
clicked: Boolean(result.clicked),
|
|
629
|
+
current_city_label: result.current_city_label || null,
|
|
630
|
+
before: result.before || null,
|
|
631
|
+
after_toggle: result.after_toggle || null,
|
|
632
|
+
confirmation: result.confirmation || null,
|
|
633
|
+
sticky_verification: result.sticky_verification
|
|
634
|
+
? {
|
|
635
|
+
verified: Boolean(result.sticky_verification.verified),
|
|
636
|
+
expected: Boolean(result.sticky_verification.expected),
|
|
637
|
+
actual: typeof result.sticky_verification.actual === "boolean"
|
|
638
|
+
? result.sticky_verification.actual
|
|
639
|
+
: null,
|
|
640
|
+
state_source: result.sticky_verification.state_source || null,
|
|
641
|
+
close_confirmation: result.sticky_verification.close_confirmation || null
|
|
642
|
+
}
|
|
643
|
+
: null,
|
|
644
|
+
attempts: Array.isArray(result.attempts) ? result.attempts : [],
|
|
645
|
+
evidence: result.evidence || null
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function compactJobSelection(jobSelection) {
|
|
650
|
+
if (!jobSelection) return null;
|
|
651
|
+
return {
|
|
652
|
+
requested: jobSelection.requested || "",
|
|
653
|
+
selected: Boolean(jobSelection.selected),
|
|
518
654
|
already_current: Boolean(jobSelection.already_current),
|
|
519
655
|
reason: jobSelection.reason || null,
|
|
520
656
|
selected_option: jobSelection.selected_option || null,
|
|
@@ -746,10 +882,10 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
746
882
|
if (!refreshAttempt) return null;
|
|
747
883
|
return {
|
|
748
884
|
ok: Boolean(refreshAttempt.ok),
|
|
749
|
-
method: refreshAttempt.method || "",
|
|
750
|
-
reason: refreshAttempt.reason || null,
|
|
751
|
-
error: refreshAttempt.error || null,
|
|
752
|
-
error_diagnostic: refreshAttempt.error_diagnostic || null,
|
|
885
|
+
method: refreshAttempt.method || "",
|
|
886
|
+
reason: refreshAttempt.reason || null,
|
|
887
|
+
error: refreshAttempt.error || null,
|
|
888
|
+
error_diagnostic: refreshAttempt.error_diagnostic || null,
|
|
753
889
|
forced_recent_not_view: Boolean(refreshAttempt.forced_recent_not_view),
|
|
754
890
|
target_url: refreshAttempt.target_url || null,
|
|
755
891
|
card_count: refreshAttempt.card_count || 0,
|
|
@@ -764,63 +900,63 @@ function compactRefreshAttempt(refreshAttempt) {
|
|
|
764
900
|
: null,
|
|
765
901
|
attempts: (refreshAttempt.attempts || []).map((attempt) => ({
|
|
766
902
|
ok: Boolean(attempt.ok),
|
|
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,
|
|
772
|
-
before_card_count: attempt.before_card_count || 0,
|
|
773
|
-
after_card_count: attempt.after_card_count || 0,
|
|
774
|
-
card_count: attempt.card_count || 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)
|
|
787
|
-
})),
|
|
788
|
-
current_city_only_attempts: (refreshAttempt.current_city_only_attempts || []).map((attempt) => ({
|
|
789
|
-
ok: Boolean(attempt.ok),
|
|
790
|
-
method: attempt.method || "current_city_only_reapply",
|
|
791
|
-
reason: attempt.reason || null,
|
|
792
|
-
error: attempt.error || null,
|
|
793
|
-
error_diagnostic: attempt.error_diagnostic || null,
|
|
794
|
-
attempt: attempt.attempt || 0,
|
|
795
|
-
result: compactCurrentCityOnlyResult(attempt.result)
|
|
796
|
-
})),
|
|
797
|
-
filter_reapply_attempts: (refreshAttempt.filter_reapply_attempts || []).map((attempt) => ({
|
|
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,
|
|
903
|
+
method: attempt.method || "",
|
|
904
|
+
reason: attempt.reason || null,
|
|
905
|
+
error: attempt.error || null,
|
|
906
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
907
|
+
label: attempt.label || null,
|
|
908
|
+
before_card_count: attempt.before_card_count || 0,
|
|
909
|
+
after_card_count: attempt.after_card_count || 0,
|
|
910
|
+
card_count: attempt.card_count || 0,
|
|
911
|
+
elapsed_ms: attempt.elapsed_ms || 0,
|
|
912
|
+
current_city_only: compactCurrentCityOnlyResult(attempt.current_city_only),
|
|
913
|
+
current_city_only_attempts: (attempt.current_city_only_attempts || []).map((cityAttempt) => ({
|
|
914
|
+
ok: Boolean(cityAttempt.ok),
|
|
915
|
+
method: cityAttempt.method || "current_city_only_reapply",
|
|
916
|
+
reason: cityAttempt.reason || null,
|
|
917
|
+
error: cityAttempt.error || null,
|
|
918
|
+
error_diagnostic: cityAttempt.error_diagnostic || null,
|
|
919
|
+
attempt: cityAttempt.attempt || 0,
|
|
920
|
+
result: compactCurrentCityOnlyResult(cityAttempt.result)
|
|
921
|
+
})),
|
|
922
|
+
filter: compactFilterResult(attempt.filter)
|
|
923
|
+
})),
|
|
924
|
+
current_city_only_attempts: (refreshAttempt.current_city_only_attempts || []).map((attempt) => ({
|
|
925
|
+
ok: Boolean(attempt.ok),
|
|
926
|
+
method: attempt.method || "current_city_only_reapply",
|
|
927
|
+
reason: attempt.reason || null,
|
|
928
|
+
error: attempt.error || null,
|
|
929
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
930
|
+
attempt: attempt.attempt || 0,
|
|
931
|
+
result: compactCurrentCityOnlyResult(attempt.result)
|
|
932
|
+
})),
|
|
933
|
+
filter_reapply_attempts: (refreshAttempt.filter_reapply_attempts || []).map((attempt) => ({
|
|
934
|
+
ok: Boolean(attempt.ok),
|
|
935
|
+
method: attempt.method || "filter_reapply",
|
|
936
|
+
reason: attempt.reason || null,
|
|
937
|
+
error: attempt.error || null,
|
|
938
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
803
939
|
attempt: attempt.attempt || 0
|
|
804
940
|
})),
|
|
805
941
|
job_selection_attempts: (refreshAttempt.job_selection_attempts || []).map((attempt) => ({
|
|
806
942
|
ok: Boolean(attempt.ok),
|
|
807
|
-
method: attempt.method || "job_select",
|
|
808
|
-
reason: attempt.reason || null,
|
|
809
|
-
error: attempt.error || null,
|
|
810
|
-
error_diagnostic: attempt.error_diagnostic || null,
|
|
943
|
+
method: attempt.method || "job_select",
|
|
944
|
+
reason: attempt.reason || null,
|
|
945
|
+
error: attempt.error || null,
|
|
946
|
+
error_diagnostic: attempt.error_diagnostic || null,
|
|
811
947
|
attempt: attempt.attempt || 0,
|
|
812
948
|
iframe_document_node_id: attempt.iframe_document_node_id || 0,
|
|
813
|
-
selected: Boolean(attempt.selected),
|
|
814
|
-
selection_reason: attempt.selection_reason || null
|
|
815
|
-
})),
|
|
816
|
-
job_selection: compactJobSelection(refreshAttempt.job_selection),
|
|
817
|
-
page_scope: compactPageScopeSelection(refreshAttempt.page_scope),
|
|
818
|
-
current_city_only: compactCurrentCityOnlyResult(refreshAttempt.current_city_only),
|
|
819
|
-
filter: compactFilterResult(refreshAttempt.filter)
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
export function countRecommendResultStatuses(results = [], {
|
|
949
|
+
selected: Boolean(attempt.selected),
|
|
950
|
+
selection_reason: attempt.selection_reason || null
|
|
951
|
+
})),
|
|
952
|
+
job_selection: compactJobSelection(refreshAttempt.job_selection),
|
|
953
|
+
page_scope: compactPageScopeSelection(refreshAttempt.page_scope),
|
|
954
|
+
current_city_only: compactCurrentCityOnlyResult(refreshAttempt.current_city_only),
|
|
955
|
+
filter: compactFilterResult(refreshAttempt.filter)
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
export function countRecommendResultStatuses(results = [], {
|
|
824
960
|
greetCount = 0
|
|
825
961
|
} = {}) {
|
|
826
962
|
return {
|
|
@@ -874,23 +1010,33 @@ function compactCloseResult(closeResult) {
|
|
|
874
1010
|
return result;
|
|
875
1011
|
}
|
|
876
1012
|
|
|
877
|
-
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
878
|
-
if (!error) return null;
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
};
|
|
1013
|
+
function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
1014
|
+
if (!error) return null;
|
|
1015
|
+
const cdpDiagnostic = compactCdpFailureDiagnostic(error, {
|
|
1016
|
+
fallbackCode,
|
|
1017
|
+
fallbackPhase: error?.phase || ""
|
|
1018
|
+
});
|
|
1019
|
+
const result = {
|
|
1020
|
+
code: error.code || fallbackCode,
|
|
1021
|
+
message: error.message || String(error)
|
|
1022
|
+
};
|
|
883
1023
|
if (error.close_result) {
|
|
884
1024
|
result.close_result = compactCloseResult(error.close_result);
|
|
885
1025
|
}
|
|
886
1026
|
if (error.phase) {
|
|
887
1027
|
result.phase = error.phase;
|
|
888
1028
|
}
|
|
889
|
-
if (error.refresh_attempt) {
|
|
890
|
-
result.refresh_attempt = error.refresh_attempt;
|
|
891
|
-
}
|
|
1029
|
+
if (error.refresh_attempt) {
|
|
1030
|
+
result.refresh_attempt = error.refresh_attempt;
|
|
1031
|
+
}
|
|
892
1032
|
if (error.error_diagnostic) {
|
|
893
1033
|
result.error_diagnostic = error.error_diagnostic;
|
|
1034
|
+
} else if (
|
|
1035
|
+
cdpDiagnostic.cdp_method
|
|
1036
|
+
|| cdpDiagnostic.cdp_connection_epoch !== null
|
|
1037
|
+
|| cdpDiagnostic.cdp_node_id !== null
|
|
1038
|
+
) {
|
|
1039
|
+
result.error_diagnostic = cdpDiagnostic;
|
|
894
1040
|
}
|
|
895
1041
|
if (error.list_end_reason) {
|
|
896
1042
|
result.list_end_reason = error.list_end_reason;
|
|
@@ -932,45 +1078,45 @@ function createRecommendBlockingPanelCloseFailureError(closeResult, phase = "")
|
|
|
932
1078
|
return error;
|
|
933
1079
|
}
|
|
934
1080
|
|
|
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;
|
|
952
|
-
}
|
|
953
|
-
|
|
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
|
-
}
|
|
970
|
-
return error;
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
export function createRecommendRefreshFailureError(refreshAttempt, {
|
|
1081
|
+
function findRecommendRefreshErrorDiagnostic(refreshAttempt) {
|
|
1082
|
+
if (!refreshAttempt || typeof refreshAttempt !== "object") return null;
|
|
1083
|
+
if (refreshAttempt.error_diagnostic) return refreshAttempt.error_diagnostic;
|
|
1084
|
+
const candidates = [
|
|
1085
|
+
...(refreshAttempt.attempts || []),
|
|
1086
|
+
...(refreshAttempt.current_city_only_attempts || []),
|
|
1087
|
+
...(refreshAttempt.filter_reapply_attempts || []),
|
|
1088
|
+
...(refreshAttempt.job_selection_attempts || [])
|
|
1089
|
+
].reverse();
|
|
1090
|
+
for (const attempt of candidates) {
|
|
1091
|
+
if (attempt?.error_diagnostic) return attempt.error_diagnostic;
|
|
1092
|
+
const nestedCityAttempts = (attempt?.current_city_only_attempts || []).slice().reverse();
|
|
1093
|
+
for (const cityAttempt of nestedCityAttempts) {
|
|
1094
|
+
if (cityAttempt?.error_diagnostic) return cityAttempt.error_diagnostic;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function attachRecommendRefreshErrorDiagnostic(error, refreshAttempt) {
|
|
1101
|
+
const diagnostic = findRecommendRefreshErrorDiagnostic(refreshAttempt);
|
|
1102
|
+
if (!error || !diagnostic) return error;
|
|
1103
|
+
error.error_diagnostic = diagnostic;
|
|
1104
|
+
for (const key of [
|
|
1105
|
+
"cdp_method",
|
|
1106
|
+
"cdp_at",
|
|
1107
|
+
"cdp_node_id",
|
|
1108
|
+
"cdp_backend_node_id",
|
|
1109
|
+
"cdp_search_id",
|
|
1110
|
+
"cdp_param_keys"
|
|
1111
|
+
]) {
|
|
1112
|
+
if (diagnostic[key] !== undefined && error[key] === undefined) {
|
|
1113
|
+
error[key] = diagnostic[key];
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return error;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
export function createRecommendRefreshFailureError(refreshAttempt, {
|
|
974
1120
|
listEndReason = "",
|
|
975
1121
|
targetCount = 0,
|
|
976
1122
|
passedCount = 0
|
|
@@ -981,18 +1127,24 @@ export function createRecommendRefreshFailureError(refreshAttempt, {
|
|
|
981
1127
|
error.code = "RECOMMEND_END_REFRESH_FAILED";
|
|
982
1128
|
error.refresh_attempt = refreshAttempt || null;
|
|
983
1129
|
error.list_end_reason = listEndReason || null;
|
|
984
|
-
error.target_count = targetCount;
|
|
985
|
-
error.passed_count = passedCount;
|
|
986
|
-
return attachRecommendRefreshErrorDiagnostic(error, refreshAttempt);
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
export function isRecoverableImageCaptureError(error) {
|
|
990
|
-
const code = String(error?.code || "");
|
|
991
|
-
if (code === "IMAGE_CAPTURE_TIMEOUT" || code === "IMAGE_CAPTURE_TOTAL_TIMEOUT") return true;
|
|
992
|
-
if (isStaleRecommendNodeError(error)) return true;
|
|
993
|
-
return /Image fallback capture timed out/i.test(String(error?.message || error || ""));
|
|
1130
|
+
error.target_count = targetCount;
|
|
1131
|
+
error.passed_count = passedCount;
|
|
1132
|
+
return attachRecommendRefreshErrorDiagnostic(error, refreshAttempt);
|
|
994
1133
|
}
|
|
995
1134
|
|
|
1135
|
+
export function isRecoverableImageCaptureError(error) {
|
|
1136
|
+
if (isRecoverableImageCaptureWorkflowError(error)) return true;
|
|
1137
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
1138
|
+
return /Image fallback capture timed out/i.test(String(error?.message || error || ""));
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
export function shouldFailClosedRecommendImageAcquisition(detailResult = null) {
|
|
1142
|
+
return isFailedClosedImageAcquisition({
|
|
1143
|
+
source: detailResult?.cv_acquisition?.source,
|
|
1144
|
+
imageEvidence: detailResult?.image_evidence
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
|
|
996
1148
|
function collectPartialImageEvidencePaths(basePath = "", extension = "jpg", maxCount = 12) {
|
|
997
1149
|
const resolved = String(basePath || "").trim();
|
|
998
1150
|
if (!resolved) return [];
|
|
@@ -1120,22 +1272,22 @@ export async function runRecommendWorkflow({
|
|
|
1120
1272
|
llmImageLimit = 8,
|
|
1121
1273
|
llmImageDetail = "high",
|
|
1122
1274
|
imageOutputDir = "",
|
|
1123
|
-
humanRestEnabled = false,
|
|
1124
|
-
humanBehavior = null,
|
|
1125
|
-
skipRecentColleagueContacted = true,
|
|
1126
|
-
colleagueContactWindowDays = 14,
|
|
1127
|
-
debugTestMode = false,
|
|
1128
|
-
debugForceListEndAfterProcessed = null,
|
|
1129
|
-
debugForceContextRecoveryAfterProcessed = null,
|
|
1130
|
-
debugForceCdpReconnectAfterProcessed = null
|
|
1131
|
-
} = {}, runControl) {
|
|
1132
|
-
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
1133
|
-
const debugBoundary = createRecommendDebugBoundaryController({
|
|
1134
|
-
debugTestMode,
|
|
1135
|
-
debugForceListEndAfterProcessed,
|
|
1136
|
-
debugForceContextRecoveryAfterProcessed,
|
|
1137
|
-
debugForceCdpReconnectAfterProcessed
|
|
1138
|
-
});
|
|
1275
|
+
humanRestEnabled = false,
|
|
1276
|
+
humanBehavior = null,
|
|
1277
|
+
skipRecentColleagueContacted = true,
|
|
1278
|
+
colleagueContactWindowDays = 14,
|
|
1279
|
+
debugTestMode = false,
|
|
1280
|
+
debugForceListEndAfterProcessed = null,
|
|
1281
|
+
debugForceContextRecoveryAfterProcessed = null,
|
|
1282
|
+
debugForceCdpReconnectAfterProcessed = null
|
|
1283
|
+
} = {}, runControl) {
|
|
1284
|
+
if (!client) throw new Error("runRecommendWorkflow requires a guarded CDP client");
|
|
1285
|
+
const debugBoundary = createRecommendDebugBoundaryController({
|
|
1286
|
+
debugTestMode,
|
|
1287
|
+
debugForceListEndAfterProcessed,
|
|
1288
|
+
debugForceContextRecoveryAfterProcessed,
|
|
1289
|
+
debugForceCdpReconnectAfterProcessed
|
|
1290
|
+
});
|
|
1139
1291
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
1140
1292
|
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
1141
1293
|
});
|
|
@@ -1189,69 +1341,201 @@ export async function runRecommendWorkflow({
|
|
|
1189
1341
|
const results = [];
|
|
1190
1342
|
const refreshAttempts = [];
|
|
1191
1343
|
let refreshRounds = 0;
|
|
1192
|
-
let contextRecoveryAttempts = 0;
|
|
1344
|
+
let contextRecoveryAttempts = 0;
|
|
1193
1345
|
let greetCount = 0;
|
|
1194
1346
|
const candidateRecoveryCounts = new Map();
|
|
1195
|
-
|
|
1196
|
-
let
|
|
1197
|
-
let
|
|
1198
|
-
let
|
|
1199
|
-
let
|
|
1200
|
-
let
|
|
1201
|
-
let
|
|
1202
|
-
let
|
|
1203
|
-
let
|
|
1204
|
-
let
|
|
1205
|
-
let
|
|
1206
|
-
let
|
|
1347
|
+
const imageCaptureWorkflowRetries = createImageCaptureWorkflowRetryTracker();
|
|
1348
|
+
let jobSelection = null;
|
|
1349
|
+
let pageScopeSelection = null;
|
|
1350
|
+
let currentCityOnlyResult = null;
|
|
1351
|
+
let filterResult = null;
|
|
1352
|
+
let rootState = null;
|
|
1353
|
+
let cardNodeIds = [];
|
|
1354
|
+
let listEndReason = "";
|
|
1355
|
+
let lastHumanEvent = null;
|
|
1356
|
+
let debugForceReconnectPending = null;
|
|
1357
|
+
let listReadStaleRecoveryAttempts = 0;
|
|
1358
|
+
let listReadStaleRecoveryApplied = 0;
|
|
1359
|
+
let listReadStaleRecoveries = 0;
|
|
1207
1360
|
let lastListReadStaleDiagnostic = null;
|
|
1208
1361
|
let lastListReadRecoveryMode = null;
|
|
1209
1362
|
const listReadStaleDiagnostics = [];
|
|
1210
|
-
|
|
1363
|
+
let domStaleEventCount = 0;
|
|
1364
|
+
let currentDomOperation = "recommend:initialize";
|
|
1365
|
+
const domLifecycleTimeline = [];
|
|
1366
|
+
const domStaleForensics = [];
|
|
1367
|
+
const listFallbackResolver = listFallbackPoint || (async ({ items = [] } = {}) => resolveInfiniteListFallbackPoint(client, {
|
|
1211
1368
|
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
1212
1369
|
containerSelectors: RECOMMEND_LIST_CONTAINER_SELECTORS,
|
|
1213
1370
|
itemNodeIds: items.map((item) => item.node_id).filter(Boolean),
|
|
1214
1371
|
itemSelectors: [RECOMMEND_CARD_SELECTOR],
|
|
1215
1372
|
viewportPoint: { xRatio: 0.28, yRatio: 0.5 },
|
|
1216
|
-
validateViewportPoint: true
|
|
1217
|
-
}));
|
|
1218
|
-
|
|
1219
|
-
function
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1373
|
+
validateViewportPoint: true
|
|
1374
|
+
}));
|
|
1375
|
+
|
|
1376
|
+
function currentConnectionEpoch() {
|
|
1377
|
+
const epoch = Number(client?.__connectionEpoch);
|
|
1378
|
+
return Number.isInteger(epoch) && epoch > 0 ? epoch : null;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function compactLifecycleUrl(value = "") {
|
|
1382
|
+
const text = String(value || "").trim();
|
|
1383
|
+
if (!text) return null;
|
|
1384
|
+
try {
|
|
1385
|
+
const parsed = new URL(text);
|
|
1386
|
+
return `${parsed.origin}${parsed.pathname}`.slice(0, 500);
|
|
1387
|
+
} catch {
|
|
1388
|
+
return text.split(/[?#]/, 1)[0].slice(0, 500) || null;
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function recordDomLifecycleEvent(type, payload = {}) {
|
|
1393
|
+
const frame = payload?.frame || payload || {};
|
|
1394
|
+
const event = {
|
|
1395
|
+
at: new Date().toISOString(),
|
|
1396
|
+
type: String(type || "unknown").slice(0, 100),
|
|
1397
|
+
operation: currentDomOperation,
|
|
1398
|
+
connection_epoch: currentConnectionEpoch(),
|
|
1399
|
+
frame_id: String(frame?.id || payload?.frameId || "").slice(0, 200) || null,
|
|
1400
|
+
parent_frame_id: String(frame?.parentId || "").slice(0, 200) || null,
|
|
1401
|
+
loader_id: String(frame?.loaderId || "").slice(0, 200) || null,
|
|
1402
|
+
url: compactLifecycleUrl(frame?.url || "")
|
|
1403
|
+
};
|
|
1404
|
+
domLifecycleTimeline.push(event);
|
|
1405
|
+
if (domLifecycleTimeline.length > 40) domLifecycleTimeline.splice(0, domLifecycleTimeline.length - 40);
|
|
1406
|
+
return event;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function subscribeDomLifecycleEvents() {
|
|
1410
|
+
const subscriptions = [
|
|
1411
|
+
[client?.DOM, "documentUpdated", "DOM.documentUpdated"],
|
|
1412
|
+
[client?.Page, "frameNavigated", "Page.frameNavigated"],
|
|
1413
|
+
[client?.Page, "frameDetached", "Page.frameDetached"],
|
|
1414
|
+
[client?.Page, "frameStartedLoading", "Page.frameStartedLoading"],
|
|
1415
|
+
[client?.Page, "frameStoppedLoading", "Page.frameStoppedLoading"]
|
|
1416
|
+
];
|
|
1417
|
+
for (const [domain, eventName, label] of subscriptions) {
|
|
1418
|
+
if (typeof domain?.[eventName] !== "function") continue;
|
|
1419
|
+
try {
|
|
1420
|
+
domain[eventName]((payload = {}) => recordDomLifecycleEvent(label, payload));
|
|
1421
|
+
} catch {
|
|
1422
|
+
// Lifecycle observation is diagnostic-only and must not alter the run.
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
function checkpointDomStaleForensic(error, {
|
|
1428
|
+
phase = "",
|
|
1429
|
+
operation = currentDomOperation,
|
|
1430
|
+
detailStep = "",
|
|
1431
|
+
candidateIndex = null,
|
|
1432
|
+
candidateKey = "",
|
|
1433
|
+
cardNodeId = null
|
|
1434
|
+
} = {}) {
|
|
1435
|
+
if (!isStaleRecommendNodeError(error)) return null;
|
|
1436
|
+
domStaleEventCount += 1;
|
|
1437
|
+
const event = createRecommendDomStaleForensicEvent(error, {
|
|
1438
|
+
eventId: `recommend_dom_stale_${runControl?.runId || "run"}_${domStaleEventCount}`,
|
|
1439
|
+
phase,
|
|
1440
|
+
operation,
|
|
1441
|
+
detailStep,
|
|
1442
|
+
candidateIndex,
|
|
1443
|
+
candidateKey,
|
|
1444
|
+
cardNodeId,
|
|
1445
|
+
rootState,
|
|
1446
|
+
connectionEpoch: currentConnectionEpoch(),
|
|
1447
|
+
listState,
|
|
1448
|
+
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
1449
|
+
timeline: domLifecycleTimeline
|
|
1450
|
+
});
|
|
1451
|
+
domStaleForensics.push(event);
|
|
1452
|
+
if (domStaleForensics.length > 12) domStaleForensics.splice(0, domStaleForensics.length - 12);
|
|
1453
|
+
runControl.checkpoint({
|
|
1454
|
+
dom_stale_forensic: event,
|
|
1455
|
+
dom_stale_forensics: domStaleForensics.slice(),
|
|
1456
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1457
|
+
});
|
|
1458
|
+
updateRecommendProgress({
|
|
1459
|
+
dom_stale_events: domStaleEventCount,
|
|
1460
|
+
last_dom_stale_phase: phase || null,
|
|
1461
|
+
last_dom_stale_operation: operation || null
|
|
1462
|
+
});
|
|
1463
|
+
return event;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function checkpointDomStaleRecovery(event, {
|
|
1467
|
+
status = "unknown",
|
|
1468
|
+
recoveryResult = null,
|
|
1469
|
+
recoveryError = null
|
|
1470
|
+
} = {}) {
|
|
1471
|
+
if (!event) return null;
|
|
1472
|
+
event.recovery = {
|
|
1473
|
+
status: String(status || "unknown").slice(0, 100),
|
|
1474
|
+
at: new Date().toISOString(),
|
|
1475
|
+
mode: String(recoveryResult?.recovery_mode || recoveryResult?.method || "").slice(0, 100) || null,
|
|
1476
|
+
escalated_from: String(recoveryResult?.escalated_from || "").slice(0, 100) || null,
|
|
1477
|
+
ok: recoveryResult?.ok === undefined ? null : Boolean(recoveryResult.ok),
|
|
1478
|
+
method: String(recoveryResult?.method || recoveryResult?.context_reapply?.method || "").slice(0, 100) || null,
|
|
1479
|
+
card_count: Number.isInteger(recoveryResult?.card_count)
|
|
1480
|
+
? recoveryResult.card_count
|
|
1481
|
+
: Number.isInteger(recoveryResult?.root_reacquire?.card_count)
|
|
1482
|
+
? recoveryResult.root_reacquire.card_count
|
|
1483
|
+
: Number.isInteger(recoveryResult?.context_reapply?.card_count)
|
|
1484
|
+
? recoveryResult.context_reapply.card_count
|
|
1485
|
+
: null,
|
|
1486
|
+
error: recoveryError ? compactCdpFailureDiagnostic(recoveryError, {
|
|
1487
|
+
fallbackCode: "RECOMMEND_DOM_STALE_RECOVERY_FAILED",
|
|
1488
|
+
fallbackPhase: "recommend:recover-context"
|
|
1489
|
+
}) : null
|
|
1490
|
+
};
|
|
1491
|
+
event.post_recovery_roots = compactRecommendDomRootIdentity(rootState, currentConnectionEpoch());
|
|
1492
|
+
event.lifecycle_timeline = domLifecycleTimeline.slice(-20);
|
|
1493
|
+
runControl.checkpoint({
|
|
1494
|
+
dom_stale_forensic: event,
|
|
1495
|
+
dom_stale_forensics: domStaleForensics.slice(),
|
|
1496
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1497
|
+
});
|
|
1498
|
+
return event;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
subscribeDomLifecycleEvents();
|
|
1502
|
+
|
|
1503
|
+
function recordHumanEvent(event = null) {
|
|
1504
|
+
if (!event) return lastHumanEvent;
|
|
1505
|
+
lastHumanEvent = {
|
|
1506
|
+
at: new Date().toISOString(),
|
|
1507
|
+
...event
|
|
1508
|
+
};
|
|
1509
|
+
return lastHumanEvent;
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
async function maybeHumanActionCooldown(phase, timings = {}) {
|
|
1513
|
+
if (!effectiveHumanBehavior.actionCooldown) return null;
|
|
1514
|
+
const pauseMs = humanDelay(280, 90, {
|
|
1515
|
+
minMs: 80,
|
|
1516
|
+
maxMs: 720
|
|
1517
|
+
});
|
|
1518
|
+
if (pauseMs > 0) {
|
|
1519
|
+
await runControl.sleep(pauseMs);
|
|
1520
|
+
addTiming(timings, `human_${phase}_pause_ms`, pauseMs);
|
|
1521
|
+
}
|
|
1522
|
+
return recordHumanEvent({
|
|
1523
|
+
kind: "action_cooldown",
|
|
1524
|
+
phase,
|
|
1525
|
+
pause_ms: pauseMs
|
|
1526
|
+
});
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
function updateRecommendProgress(extra = {}) {
|
|
1530
|
+
const counts = countRecommendResultStatuses(results, { greetCount });
|
|
1247
1531
|
const listSnapshot = compactInfiniteListState(listState);
|
|
1248
1532
|
const humanRestState = humanRestController.getState();
|
|
1249
1533
|
runControl.updateProgress({
|
|
1250
1534
|
card_count: cardNodeIds.length,
|
|
1251
|
-
target_count: targetPassCount,
|
|
1252
|
-
target_count_semantics: "passed_candidates",
|
|
1253
|
-
...counts,
|
|
1254
|
-
transient_recovered: counts.transient_recovered + listReadStaleRecoveries,
|
|
1535
|
+
target_count: targetPassCount,
|
|
1536
|
+
target_count_semantics: "passed_candidates",
|
|
1537
|
+
...counts,
|
|
1538
|
+
transient_recovered: counts.transient_recovered + listReadStaleRecoveries,
|
|
1255
1539
|
screening_mode: normalizedScreeningMode,
|
|
1256
1540
|
unique_seen: listSnapshot.seen_count,
|
|
1257
1541
|
scroll_count: listSnapshot.scroll_count,
|
|
@@ -1262,29 +1546,29 @@ export async function runRecommendWorkflow({
|
|
|
1262
1546
|
viewport_checks: viewportGuard.getStats().checks,
|
|
1263
1547
|
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
1264
1548
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
1265
|
-
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1266
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1267
|
-
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1268
|
-
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
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),
|
|
1273
|
-
human_rest_count: humanRestState.rest_count,
|
|
1274
|
-
human_rest_ms: humanRestState.total_rest_ms,
|
|
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,
|
|
1285
|
-
...extra
|
|
1286
|
-
});
|
|
1287
|
-
}
|
|
1549
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
1550
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
1551
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
1552
|
+
skip_recent_colleague_contacted: shouldSkipRecentColleagueContacted,
|
|
1553
|
+
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
1554
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
1555
|
+
current_city_only_effective: currentCityOnlyResult?.effective ?? null,
|
|
1556
|
+
current_city_only_unavailable: Boolean(currentCityOnlyResult?.unavailable),
|
|
1557
|
+
human_rest_count: humanRestState.rest_count,
|
|
1558
|
+
human_rest_ms: humanRestState.total_rest_ms,
|
|
1559
|
+
last_human_event: lastHumanEvent,
|
|
1560
|
+
debug_boundary_mode: debugBoundary.config.mode,
|
|
1561
|
+
debug_boundary_threshold: debugBoundary.config.threshold,
|
|
1562
|
+
debug_boundary_triggered: debugBoundary.getState().triggered,
|
|
1563
|
+
debug_boundary_trigger_count: debugBoundary.getState().trigger_count,
|
|
1564
|
+
list_read_stale_recovery_attempts: listReadStaleRecoveryAttempts,
|
|
1565
|
+
list_read_stale_recovery_applied: listReadStaleRecoveryApplied,
|
|
1566
|
+
list_read_stale_recoveries: listReadStaleRecoveries,
|
|
1567
|
+
last_list_read_recovery_mode: lastListReadRecoveryMode,
|
|
1568
|
+
last_list_read_stale_diagnostic: lastListReadStaleDiagnostic,
|
|
1569
|
+
...extra
|
|
1570
|
+
});
|
|
1571
|
+
}
|
|
1288
1572
|
|
|
1289
1573
|
function checkpointInProgressCandidate({
|
|
1290
1574
|
index = results.length,
|
|
@@ -1320,27 +1604,27 @@ export async function runRecommendWorkflow({
|
|
|
1320
1604
|
async function recoverAndReapplyRecommendContext(reason = "context_recovery", error = null, {
|
|
1321
1605
|
forceRecentNotView = true
|
|
1322
1606
|
} = {}) {
|
|
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;
|
|
1329
|
-
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1330
|
-
rootState,
|
|
1331
|
-
jobLabel,
|
|
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,
|
|
1340
|
-
buttonSettleMs: refreshButtonSettleMs,
|
|
1341
|
-
reloadSettleMs: refreshReloadSettleMs
|
|
1342
|
-
});
|
|
1343
|
-
let blockingPanelClose = null;
|
|
1607
|
+
await runControl.waitIfPaused();
|
|
1608
|
+
runControl.throwIfCanceled();
|
|
1609
|
+
const started = Date.now();
|
|
1610
|
+
runControl.setPhase("recommend:recover-context");
|
|
1611
|
+
contextRecoveryAttempts += 1;
|
|
1612
|
+
const effectiveForceRecentNotView = normalizedFilter.enabled && forceRecentNotView;
|
|
1613
|
+
const refreshResult = await refreshRecommendListAtEnd(client, {
|
|
1614
|
+
rootState,
|
|
1615
|
+
jobLabel,
|
|
1616
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
1617
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
1618
|
+
filter: normalizedFilter,
|
|
1619
|
+
preferEndRefreshButton: false,
|
|
1620
|
+
forceNavigate: true,
|
|
1621
|
+
targetUrl: targetUrl || RECOMMEND_TARGET_URL,
|
|
1622
|
+
forceRecentNotView: effectiveForceRecentNotView,
|
|
1623
|
+
cardTimeoutMs,
|
|
1624
|
+
buttonSettleMs: refreshButtonSettleMs,
|
|
1625
|
+
reloadSettleMs: refreshReloadSettleMs
|
|
1626
|
+
});
|
|
1627
|
+
let blockingPanelClose = null;
|
|
1344
1628
|
if (refreshResult.ok) {
|
|
1345
1629
|
blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1346
1630
|
attemptsLimit: 2,
|
|
@@ -1364,33 +1648,33 @@ export async function runRecommendWorkflow({
|
|
|
1364
1648
|
refresh: compactRefresh,
|
|
1365
1649
|
counters: countRecommendResultStatuses(results, { greetCount })
|
|
1366
1650
|
},
|
|
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
|
|
1374
|
-
});
|
|
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;
|
|
1388
|
-
}
|
|
1389
|
-
if (!blockingPanelClose?.closed) {
|
|
1390
|
-
const panelError = createRecommendBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
|
|
1391
|
-
panelError.refresh_attempt = compactRefresh;
|
|
1392
|
-
throw panelError;
|
|
1393
|
-
}
|
|
1651
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1652
|
+
});
|
|
1653
|
+
if (!refreshResult.ok) {
|
|
1654
|
+
updateRecommendProgress({
|
|
1655
|
+
refresh_method: refreshResult.method || null,
|
|
1656
|
+
refresh_forced_recent_not_view: effectiveForceRecentNotView,
|
|
1657
|
+
recovery_reason: reason
|
|
1658
|
+
});
|
|
1659
|
+
const recoveryError = new Error(
|
|
1660
|
+
`Recommend context recovery failed after ${reason}: ${refreshResult.reason || refreshResult.error || "refresh returned no cards"}`
|
|
1661
|
+
);
|
|
1662
|
+
recoveryError.code = "RECOMMEND_CONTEXT_RECOVERY_FAILED";
|
|
1663
|
+
recoveryError.refresh_attempt = compactRefresh;
|
|
1664
|
+
recoveryError.recovery_reason = reason;
|
|
1665
|
+
throw attachRecommendRefreshErrorDiagnostic(recoveryError, compactRefresh);
|
|
1666
|
+
}
|
|
1667
|
+
if (refreshResult.current_city_only) {
|
|
1668
|
+
currentCityOnlyResult = refreshResult.current_city_only;
|
|
1669
|
+
}
|
|
1670
|
+
if (refreshResult.filter) {
|
|
1671
|
+
filterResult = refreshResult.filter;
|
|
1672
|
+
}
|
|
1673
|
+
if (!blockingPanelClose?.closed) {
|
|
1674
|
+
const panelError = createRecommendBlockingPanelCloseFailureError(blockingPanelClose, `recover:${reason}`);
|
|
1675
|
+
panelError.refresh_attempt = compactRefresh;
|
|
1676
|
+
throw panelError;
|
|
1677
|
+
}
|
|
1394
1678
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1395
1679
|
rootState = await ensureRecommendViewport(rootState, "recover_after");
|
|
1396
1680
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
@@ -1398,25 +1682,25 @@ export async function runRecommendWorkflow({
|
|
|
1398
1682
|
intervalMs: 300
|
|
1399
1683
|
});
|
|
1400
1684
|
resetInfiniteListForRefreshRound(listState, {
|
|
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;
|
|
1418
|
-
}
|
|
1419
|
-
|
|
1685
|
+
reason: `context_recovery:${reason}`,
|
|
1686
|
+
round: contextRecoveryAttempts,
|
|
1687
|
+
method: refreshResult.method,
|
|
1688
|
+
metadata: {
|
|
1689
|
+
card_count: cardNodeIds.length,
|
|
1690
|
+
forced_recent_not_view: effectiveForceRecentNotView,
|
|
1691
|
+
counters: countRecommendResultStatuses(results, { greetCount })
|
|
1692
|
+
}
|
|
1693
|
+
});
|
|
1694
|
+
listEndReason = "";
|
|
1695
|
+
updateRecommendProgress({
|
|
1696
|
+
card_count: cardNodeIds.length,
|
|
1697
|
+
refresh_method: refreshResult.method || null,
|
|
1698
|
+
refresh_forced_recent_not_view: effectiveForceRecentNotView,
|
|
1699
|
+
recovery_reason: reason
|
|
1700
|
+
});
|
|
1701
|
+
return refreshResult;
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1420
1704
|
runControl.setPhase("recommend:cleanup");
|
|
1421
1705
|
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
1422
1706
|
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
|
|
@@ -1463,55 +1747,55 @@ export async function runRecommendWorkflow({
|
|
|
1463
1747
|
throw new Error(`Recommend page scope was not selected: ${pageScopeSelection.reason || pageScopeSelection.effective_scope || requestedPageScope}`);
|
|
1464
1748
|
}
|
|
1465
1749
|
rootState = await getRecommendRoots(client);
|
|
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");
|
|
1750
|
+
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
1751
|
+
runControl.checkpoint({
|
|
1752
|
+
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
1753
|
+
});
|
|
1754
|
+
|
|
1755
|
+
const initialFilterStages = await applyRecommendFilterEnvelopeStages(normalizedFilter, {
|
|
1756
|
+
applyCurrentCityOnly: async () => {
|
|
1757
|
+
await runControl.waitIfPaused();
|
|
1758
|
+
runControl.throwIfCanceled();
|
|
1759
|
+
runControl.setPhase("recommend:current-city-only");
|
|
1760
|
+
const result = await ensureRecommendCurrentCityOnly(
|
|
1761
|
+
client,
|
|
1762
|
+
rootState.iframe.documentNodeId,
|
|
1763
|
+
{ enabled: normalizedFilter.currentCityOnly }
|
|
1764
|
+
);
|
|
1765
|
+
rootState = await getRecommendRoots(client);
|
|
1766
|
+
rootState = await ensureRecommendViewport(rootState, "current_city_only");
|
|
1767
|
+
runControl.checkpoint({
|
|
1768
|
+
current_city_only: compactCurrentCityOnlyResult(result)
|
|
1769
|
+
});
|
|
1770
|
+
return result;
|
|
1771
|
+
},
|
|
1772
|
+
applyFilterPanel: async () => {
|
|
1773
|
+
await runControl.waitIfPaused();
|
|
1774
|
+
runControl.throwIfCanceled();
|
|
1775
|
+
runControl.setPhase("recommend:filter");
|
|
1776
|
+
const result = await selectAndConfirmFirstSafeFilter(
|
|
1777
|
+
client,
|
|
1778
|
+
rootState.iframe.documentNodeId,
|
|
1779
|
+
buildRecommendFilterSelectionOptions(normalizedFilter)
|
|
1780
|
+
);
|
|
1781
|
+
if (!result.confirmed) {
|
|
1782
|
+
throw new Error("Recommend run filter selection was not confirmed");
|
|
1783
|
+
}
|
|
1784
|
+
rootState = await getRecommendRoots(client);
|
|
1785
|
+
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
1786
|
+
runControl.checkpoint({
|
|
1787
|
+
filter: compactFilterResult(result)
|
|
1788
|
+
});
|
|
1789
|
+
return result;
|
|
1790
|
+
}
|
|
1791
|
+
});
|
|
1792
|
+
currentCityOnlyResult = initialFilterStages.current_city_only;
|
|
1793
|
+
filterResult = initialFilterStages.filter;
|
|
1794
|
+
|
|
1795
|
+
await runControl.waitIfPaused();
|
|
1796
|
+
runControl.throwIfCanceled();
|
|
1797
|
+
runControl.setPhase("recommend:cards");
|
|
1798
|
+
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
1515
1799
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1516
1800
|
timeoutMs: cardTimeoutMs,
|
|
1517
1801
|
intervalMs: 300
|
|
@@ -1527,244 +1811,305 @@ export async function runRecommendWorkflow({
|
|
|
1527
1811
|
while (countPassedResults(results) < targetPassCount) {
|
|
1528
1812
|
const candidateStarted = Date.now();
|
|
1529
1813
|
const timings = {};
|
|
1530
|
-
await runControl.waitIfPaused();
|
|
1814
|
+
await runControl.waitIfPaused();
|
|
1531
1815
|
runControl.throwIfCanceled();
|
|
1532
1816
|
runControl.setPhase("recommend:list-read");
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
runControl.
|
|
1577
|
-
runControl.
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1817
|
+
currentDomOperation = "candidate:list-read";
|
|
1818
|
+
const debugBoundaryAction = debugBoundary.take(results.length);
|
|
1819
|
+
let debugForcedListEnd = false;
|
|
1820
|
+
if (debugBoundaryAction) {
|
|
1821
|
+
const debugProgress = {
|
|
1822
|
+
debug_boundary_mode: debugBoundaryAction.mode,
|
|
1823
|
+
debug_boundary_threshold: debugBoundaryAction.threshold,
|
|
1824
|
+
debug_boundary_triggered: true,
|
|
1825
|
+
debug_boundary_trigger_count: debugBoundaryAction.trigger_count,
|
|
1826
|
+
debug_boundary_processed: debugBoundaryAction.processed
|
|
1827
|
+
};
|
|
1828
|
+
updateRecommendProgress(debugProgress);
|
|
1829
|
+
runControl.checkpoint({
|
|
1830
|
+
debug_boundary: {
|
|
1831
|
+
...debugProgress,
|
|
1832
|
+
field: debugBoundaryAction.field,
|
|
1833
|
+
triggered_at: new Date().toISOString()
|
|
1834
|
+
}
|
|
1835
|
+
});
|
|
1836
|
+
if (debugBoundaryAction.mode === "context_recovery") {
|
|
1837
|
+
runControl.setPhase("recommend:debug-force-context-recovery");
|
|
1838
|
+
await recoverAndReapplyRecommendContext("debug_force_context_recovery_after_processed", null, {
|
|
1839
|
+
forceRecentNotView: true
|
|
1840
|
+
});
|
|
1841
|
+
updateRecommendProgress({
|
|
1842
|
+
debug_force_context_recovery_count: debugBoundaryAction.trigger_count
|
|
1843
|
+
});
|
|
1844
|
+
runControl.setPhase("recommend:list-read");
|
|
1845
|
+
} else if (debugBoundaryAction.mode === "cdp_reconnect") {
|
|
1846
|
+
debugForceReconnectPending = debugBoundaryAction;
|
|
1847
|
+
updateRecommendProgress({
|
|
1848
|
+
debug_force_cdp_reconnect_pending: true
|
|
1849
|
+
});
|
|
1850
|
+
} else if (debugBoundaryAction.mode === "list_end") {
|
|
1851
|
+
debugForcedListEnd = true;
|
|
1852
|
+
updateRecommendProgress({
|
|
1853
|
+
debug_force_list_end_count: debugBoundaryAction.trigger_count
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
const listReadAcquisition = await acquireRecommendListReadWithStaleRecovery({
|
|
1858
|
+
maxRetries: 2,
|
|
1859
|
+
acquire: async () => {
|
|
1860
|
+
await runControl.waitIfPaused();
|
|
1861
|
+
runControl.throwIfCanceled();
|
|
1862
|
+
runControl.setPhase("recommend:list-read");
|
|
1863
|
+
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
1864
|
+
if (debugForcedListEnd) {
|
|
1865
|
+
return {
|
|
1866
|
+
ok: false,
|
|
1867
|
+
end_reached: true,
|
|
1868
|
+
reason: "debug_forced_list_end"
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
return measureTiming(timings, "card_read_ms", () => getNextInfiniteListCandidate({
|
|
1872
|
+
client,
|
|
1873
|
+
state: listState,
|
|
1874
|
+
maxScrolls: listMaxScrolls,
|
|
1875
|
+
stableSignatureLimit: listStableSignatureLimit,
|
|
1876
|
+
wheelDeltaY: listWheelDeltaY,
|
|
1877
|
+
settleMs: listSettleMs,
|
|
1878
|
+
listScrollJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
1879
|
+
fallbackPoint: listFallbackResolver,
|
|
1595
1880
|
findNodeIds: async () => {
|
|
1881
|
+
currentDomOperation = "candidate:list-find-nodes";
|
|
1596
1882
|
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
|
|
1883
|
+
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
1884
|
+
rootState = currentRootState;
|
|
1885
|
+
if (debugForceReconnectPending) {
|
|
1886
|
+
runControl.setPhase("recommend:debug-force-cdp-reconnect");
|
|
1887
|
+
const rawClient = client.__rawClient;
|
|
1888
|
+
if (!rawClient || typeof rawClient.close !== "function") {
|
|
1889
|
+
const error = new Error("Guarded CDP client does not expose a closable __rawClient");
|
|
1890
|
+
error.code = "RECOMMEND_DEBUG_RAW_CDP_UNAVAILABLE";
|
|
1891
|
+
throw error;
|
|
1892
|
+
}
|
|
1893
|
+
const forcedReconnectAction = debugForceReconnectPending;
|
|
1894
|
+
debugForceReconnectPending = null;
|
|
1895
|
+
await rawClient.close();
|
|
1896
|
+
runControl.checkpoint({
|
|
1897
|
+
debug_cdp_reconnect: {
|
|
1898
|
+
raw_connection_closed: true,
|
|
1899
|
+
processed: forcedReconnectAction.processed,
|
|
1900
|
+
threshold: forcedReconnectAction.threshold,
|
|
1901
|
+
closed_at: new Date().toISOString()
|
|
1902
|
+
}
|
|
1903
|
+
});
|
|
1904
|
+
updateRecommendProgress({
|
|
1905
|
+
debug_force_cdp_reconnect_pending: false,
|
|
1906
|
+
debug_force_cdp_reconnect_count: forcedReconnectAction.trigger_count
|
|
1907
|
+
});
|
|
1908
|
+
runControl.setPhase("recommend:list-read");
|
|
1909
|
+
}
|
|
1910
|
+
const currentCardNodeIds = await waitForRecommendCardNodeIds(
|
|
1911
|
+
client,
|
|
1912
|
+
currentRootState.iframe.documentNodeId,
|
|
1913
|
+
{
|
|
1914
|
+
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
1915
|
+
intervalMs: 300
|
|
1916
|
+
}
|
|
1917
|
+
);
|
|
1918
|
+
cardNodeIds = currentCardNodeIds;
|
|
1919
|
+
return currentCardNodeIds;
|
|
1920
|
+
},
|
|
1921
|
+
readCandidate: async (nodeId, { visibleIndex }) => {
|
|
1922
|
+
currentDomOperation = "candidate:list-read-card";
|
|
1923
|
+
return readRecommendCardCandidate(client, nodeId, {
|
|
1924
|
+
targetUrl,
|
|
1925
|
+
source: "recommend-run-card",
|
|
1926
|
+
metadata: {
|
|
1927
|
+
run_candidate_index: results.length,
|
|
1928
|
+
visible_index: visibleIndex
|
|
1630
1929
|
}
|
|
1631
|
-
);
|
|
1632
|
-
cardNodeIds = currentCardNodeIds;
|
|
1633
|
-
return currentCardNodeIds;
|
|
1930
|
+
});
|
|
1634
1931
|
},
|
|
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
1932
|
shouldRethrowReadError: isStaleRecommendNodeError,
|
|
1644
|
-
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) =>
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1933
|
+
detectBottomMarker: async ({ scrollAttempt = 0, signature = {} } = {}) => {
|
|
1934
|
+
currentDomOperation = "candidate:list-bottom-probe";
|
|
1935
|
+
return detectInfiniteListBottomMarker(client, {
|
|
1936
|
+
rootNodeId: rootState?.iframe?.documentNodeId,
|
|
1937
|
+
markerSelectors: RECOMMEND_BOTTOM_MARKER_SELECTORS,
|
|
1938
|
+
refreshSelectors: [RECOMMEND_END_REFRESH_SELECTOR],
|
|
1939
|
+
textScanSelectors: scrollAttempt > 0 || (signature?.stable_signature_count || 0) >= 2 ? undefined : [],
|
|
1940
|
+
maxTextScanNodes: 500
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1651
1943
|
}));
|
|
1652
1944
|
},
|
|
1653
|
-
onStale: async ({ diagnostic }) => {
|
|
1945
|
+
onStale: async ({ error, diagnostic }) => {
|
|
1654
1946
|
listReadStaleRecoveryAttempts += 1;
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
diagnostic,
|
|
1660
|
-
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
1661
|
-
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
1662
|
-
candidate_list: compactInfiniteListState(listState)
|
|
1663
|
-
}
|
|
1947
|
+
const forensic = checkpointDomStaleForensic(error, {
|
|
1948
|
+
phase: "recommend:list-read",
|
|
1949
|
+
operation: currentDomOperation,
|
|
1950
|
+
candidateIndex: results.length
|
|
1664
1951
|
});
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
);
|
|
1689
|
-
|
|
1690
|
-
|
|
1952
|
+
if (forensic) {
|
|
1953
|
+
diagnostic.forensic_event_id = forensic.event_id;
|
|
1954
|
+
diagnostic.pre_recovery_roots = forensic.pre_recovery_roots;
|
|
1955
|
+
diagnostic.failing_candidate = forensic.candidate;
|
|
1956
|
+
}
|
|
1957
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
1958
|
+
listReadStaleDiagnostics.push(diagnostic);
|
|
1959
|
+
runControl.checkpoint({
|
|
1960
|
+
list_read_stale_recovery: {
|
|
1961
|
+
diagnostic,
|
|
1962
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
1963
|
+
counters: countRecommendResultStatuses(results, { greetCount }),
|
|
1964
|
+
candidate_list: compactInfiniteListState(listState)
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
updateRecommendProgress();
|
|
1968
|
+
},
|
|
1969
|
+
recover: async ({ error, diagnostic }) => {
|
|
1970
|
+
try {
|
|
1971
|
+
return await recoverRecommendListReadStaleContext({
|
|
1972
|
+
staleAttempt: diagnostic.attempt,
|
|
1973
|
+
listState,
|
|
1974
|
+
rootReacquire: async () => {
|
|
1975
|
+
await runControl.waitIfPaused();
|
|
1976
|
+
runControl.throwIfCanceled();
|
|
1977
|
+
runControl.setPhase("recommend:list-read-root-reacquire");
|
|
1978
|
+
let freshRootState = await getRecommendRoots(client);
|
|
1979
|
+
freshRootState = await ensureRecommendViewport(
|
|
1980
|
+
freshRootState,
|
|
1981
|
+
"list_read_stale_root_reacquire"
|
|
1982
|
+
);
|
|
1983
|
+
const freshCardNodeIds = await waitForRecommendCardNodeIds(
|
|
1984
|
+
client,
|
|
1985
|
+
freshRootState.iframe.documentNodeId,
|
|
1986
|
+
{
|
|
1987
|
+
timeoutMs: Math.min(cardTimeoutMs, 10000),
|
|
1988
|
+
intervalMs: 300
|
|
1989
|
+
}
|
|
1990
|
+
);
|
|
1991
|
+
rootState = freshRootState;
|
|
1992
|
+
cardNodeIds = freshCardNodeIds;
|
|
1691
1993
|
return {
|
|
1692
1994
|
card_count: freshCardNodeIds.length,
|
|
1693
|
-
iframe_document_node_id: freshRootState.iframe.documentNodeId
|
|
1995
|
+
iframe_document_node_id: freshRootState.iframe.documentNodeId,
|
|
1996
|
+
root_identity: compactRecommendDomRootIdentity(
|
|
1997
|
+
freshRootState,
|
|
1998
|
+
currentConnectionEpoch()
|
|
1999
|
+
)
|
|
1694
2000
|
};
|
|
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
|
-
);
|
|
2001
|
+
},
|
|
2002
|
+
contextReapply: async ({ rootReacquireError }) => {
|
|
2003
|
+
await runControl.waitIfPaused();
|
|
2004
|
+
runControl.throwIfCanceled();
|
|
2005
|
+
const recovery = await recoverAndReapplyRecommendContext(
|
|
2006
|
+
"list_read_stale_node",
|
|
2007
|
+
rootReacquireError || error,
|
|
2008
|
+
{ forceRecentNotView: true }
|
|
2009
|
+
);
|
|
1704
2010
|
return {
|
|
1705
2011
|
ok: Boolean(recovery?.ok),
|
|
1706
2012
|
method: recovery?.method || null,
|
|
1707
|
-
card_count: recovery?.card_count || 0
|
|
2013
|
+
card_count: recovery?.card_count || 0,
|
|
2014
|
+
root_identity: compactRecommendDomRootIdentity(
|
|
2015
|
+
rootState,
|
|
2016
|
+
currentConnectionEpoch()
|
|
2017
|
+
)
|
|
1708
2018
|
};
|
|
1709
|
-
}
|
|
1710
|
-
});
|
|
2019
|
+
}
|
|
2020
|
+
});
|
|
1711
2021
|
} catch (recoveryError) {
|
|
2022
|
+
const forensic = domStaleForensics.find((item) => (
|
|
2023
|
+
item.event_id === diagnostic.forensic_event_id
|
|
2024
|
+
));
|
|
2025
|
+
checkpointDomStaleRecovery(forensic, {
|
|
2026
|
+
status: "recovery_failed",
|
|
2027
|
+
recoveryError
|
|
2028
|
+
});
|
|
1712
2029
|
updateRecommendProgress({
|
|
1713
2030
|
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 }) => {
|
|
2031
|
+
});
|
|
2032
|
+
runControl.checkpoint({
|
|
2033
|
+
list_read_stale_recovery_failed: {
|
|
2034
|
+
trigger: lastListReadStaleDiagnostic,
|
|
2035
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
2036
|
+
recovery_error: compactError(recoveryError, "RECOMMEND_LIST_READ_RECOVERY_FAILED"),
|
|
2037
|
+
candidate_list: compactInfiniteListState(listState)
|
|
2038
|
+
}
|
|
2039
|
+
});
|
|
2040
|
+
throw recoveryError;
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
onRecoveryApplied: async ({ diagnostic, recoveryResult }) => {
|
|
1727
2044
|
listReadStaleRecoveryApplied += 1;
|
|
1728
2045
|
lastListReadStaleDiagnostic = diagnostic;
|
|
1729
2046
|
lastListReadRecoveryMode = diagnostic.recovery_mode || null;
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
}
|
|
2047
|
+
const forensic = domStaleForensics.find((item) => (
|
|
2048
|
+
item.event_id === diagnostic.forensic_event_id
|
|
2049
|
+
));
|
|
2050
|
+
checkpointDomStaleRecovery(forensic, {
|
|
2051
|
+
status: "recovery_applied",
|
|
2052
|
+
recoveryResult
|
|
1737
2053
|
});
|
|
1738
|
-
|
|
2054
|
+
updateRecommendProgress();
|
|
2055
|
+
runControl.checkpoint({
|
|
2056
|
+
list_read_stale_recovery_applied: {
|
|
2057
|
+
diagnostic,
|
|
2058
|
+
recovery_applied_count: listReadStaleRecoveryApplied,
|
|
2059
|
+
candidate_list: compactInfiniteListState(listState)
|
|
2060
|
+
}
|
|
2061
|
+
});
|
|
2062
|
+
},
|
|
1739
2063
|
onRecovered: async ({ diagnostic }) => {
|
|
1740
2064
|
listReadStaleRecoveries += 1;
|
|
1741
2065
|
lastListReadStaleDiagnostic = diagnostic;
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
2066
|
+
const forensic = domStaleForensics.find((item) => (
|
|
2067
|
+
item.event_id === diagnostic.forensic_event_id
|
|
2068
|
+
));
|
|
2069
|
+
checkpointDomStaleRecovery(forensic, {
|
|
2070
|
+
status: "recovered",
|
|
2071
|
+
recoveryResult: {
|
|
2072
|
+
recovery_mode: diagnostic.recovery_mode,
|
|
2073
|
+
ok: true
|
|
1748
2074
|
}
|
|
1749
2075
|
});
|
|
1750
|
-
|
|
1751
|
-
|
|
2076
|
+
updateRecommendProgress();
|
|
2077
|
+
runControl.checkpoint({
|
|
2078
|
+
list_read_stale_recovered: {
|
|
2079
|
+
diagnostic,
|
|
2080
|
+
recovery_count: listReadStaleRecoveries,
|
|
2081
|
+
candidate_list: compactInfiniteListState(listState)
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
},
|
|
2085
|
+
onExhausted: async ({ error, diagnostic }) => {
|
|
1752
2086
|
listReadStaleRecoveryAttempts += 1;
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
2087
|
+
const forensic = checkpointDomStaleForensic(error, {
|
|
2088
|
+
phase: "recommend:list-read",
|
|
2089
|
+
operation: currentDomOperation,
|
|
2090
|
+
candidateIndex: results.length
|
|
1757
2091
|
});
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2092
|
+
if (forensic) {
|
|
2093
|
+
diagnostic.forensic_event_id = forensic.event_id;
|
|
2094
|
+
diagnostic.pre_recovery_roots = forensic.pre_recovery_roots;
|
|
2095
|
+
diagnostic.failing_candidate = forensic.candidate;
|
|
2096
|
+
checkpointDomStaleRecovery(forensic, { status: "exhausted" });
|
|
2097
|
+
}
|
|
2098
|
+
lastListReadStaleDiagnostic = diagnostic;
|
|
2099
|
+
listReadStaleDiagnostics.push(diagnostic);
|
|
2100
|
+
updateRecommendProgress({
|
|
2101
|
+
list_read_stale_recovery_exhausted: true
|
|
2102
|
+
});
|
|
2103
|
+
runControl.checkpoint({
|
|
2104
|
+
list_read_stale_recovery_exhausted: {
|
|
2105
|
+
diagnostic,
|
|
2106
|
+
recent_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
2107
|
+
candidate_list: compactInfiniteListState(listState)
|
|
2108
|
+
}
|
|
2109
|
+
});
|
|
2110
|
+
}
|
|
2111
|
+
});
|
|
2112
|
+
const nextCandidateResult = listReadAcquisition.result;
|
|
1768
2113
|
if (!nextCandidateResult.ok) {
|
|
1769
2114
|
listEndReason = nextCandidateResult.reason || "list_exhausted";
|
|
1770
2115
|
if (
|
|
@@ -1774,58 +2119,79 @@ export async function runRecommendWorkflow({
|
|
|
1774
2119
|
&& refreshRounds < Math.max(0, Number(maxRefreshRounds) || 0)
|
|
1775
2120
|
) {
|
|
1776
2121
|
await runControl.waitIfPaused();
|
|
1777
|
-
runControl.throwIfCanceled();
|
|
1778
|
-
runControl.setPhase("recommend:refresh");
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2122
|
+
runControl.throwIfCanceled();
|
|
2123
|
+
runControl.setPhase("recommend:refresh");
|
|
2124
|
+
currentDomOperation = "candidate:list-end-refresh";
|
|
2125
|
+
refreshRounds += 1;
|
|
2126
|
+
let refreshResult;
|
|
2127
|
+
try {
|
|
2128
|
+
refreshResult = await refreshRecommendListAtEnd(client, {
|
|
2129
|
+
rootState,
|
|
2130
|
+
jobLabel,
|
|
2131
|
+
pageScope: pageScopeSelection?.effective_scope || requestedPageScope,
|
|
2132
|
+
fallbackPageScope: normalizedFallbackPageScope,
|
|
2133
|
+
filter: normalizedFilter,
|
|
2134
|
+
preferEndRefreshButton: debugForcedListEnd ? false : undefined,
|
|
2135
|
+
forceRecentNotView: normalizedFilter.enabled,
|
|
2136
|
+
cardTimeoutMs,
|
|
2137
|
+
buttonSettleMs: refreshButtonSettleMs,
|
|
2138
|
+
reloadSettleMs: refreshReloadSettleMs
|
|
2139
|
+
});
|
|
2140
|
+
} catch (error) {
|
|
2141
|
+
checkpointDomStaleForensic(error, {
|
|
2142
|
+
phase: "recommend:refresh",
|
|
2143
|
+
operation: currentDomOperation,
|
|
2144
|
+
candidateIndex: results.length
|
|
2145
|
+
});
|
|
2146
|
+
throw error;
|
|
2147
|
+
}
|
|
2148
|
+
const compactRefresh = compactRefreshAttempt(refreshResult);
|
|
1793
2149
|
refreshAttempts.push(compactRefresh);
|
|
1794
2150
|
runControl.checkpoint({
|
|
1795
2151
|
refresh_round: refreshRounds,
|
|
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
|
-
});
|
|
2152
|
+
refresh: compactRefresh
|
|
2153
|
+
});
|
|
2154
|
+
updateRecommendProgress({
|
|
2155
|
+
card_count: refreshResult.card_count || cardNodeIds.length,
|
|
2156
|
+
refresh_method: refreshResult.method || null,
|
|
2157
|
+
refresh_forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view),
|
|
2158
|
+
list_end_reason: listEndReason
|
|
2159
|
+
});
|
|
1804
2160
|
if (refreshResult.ok) {
|
|
1805
|
-
if (refreshResult.current_city_only) {
|
|
1806
|
-
currentCityOnlyResult = refreshResult.current_city_only;
|
|
1807
|
-
}
|
|
1808
|
-
if (refreshResult.filter) {
|
|
1809
|
-
filterResult = refreshResult.filter;
|
|
2161
|
+
if (refreshResult.current_city_only) {
|
|
2162
|
+
currentCityOnlyResult = refreshResult.current_city_only;
|
|
2163
|
+
}
|
|
2164
|
+
if (refreshResult.filter) {
|
|
2165
|
+
filterResult = refreshResult.filter;
|
|
2166
|
+
}
|
|
2167
|
+
try {
|
|
2168
|
+
currentDomOperation = "candidate:list-end-refresh-reacquire";
|
|
2169
|
+
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
2170
|
+
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
2171
|
+
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
2172
|
+
timeoutMs: cardTimeoutMs,
|
|
2173
|
+
intervalMs: 300
|
|
2174
|
+
});
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
checkpointDomStaleForensic(error, {
|
|
2177
|
+
phase: "recommend:refresh",
|
|
2178
|
+
operation: currentDomOperation,
|
|
2179
|
+
candidateIndex: results.length
|
|
2180
|
+
});
|
|
2181
|
+
throw error;
|
|
1810
2182
|
}
|
|
1811
|
-
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
1812
|
-
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
1813
|
-
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
1814
|
-
timeoutMs: cardTimeoutMs,
|
|
1815
|
-
intervalMs: 300
|
|
1816
|
-
});
|
|
1817
2183
|
resetInfiniteListForRefreshRound(listState, {
|
|
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
|
-
});
|
|
1826
|
-
listEndReason = "";
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
2184
|
+
reason: listEndReason,
|
|
2185
|
+
round: refreshRounds,
|
|
2186
|
+
method: refreshResult.method,
|
|
2187
|
+
metadata: {
|
|
2188
|
+
card_count: cardNodeIds.length,
|
|
2189
|
+
forced_recent_not_view: Boolean(refreshResult.forced_recent_not_view)
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
listEndReason = "";
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
1829
2195
|
throw createRecommendRefreshFailureError(compactRefresh, {
|
|
1830
2196
|
listEndReason,
|
|
1831
2197
|
targetCount: targetPassCount,
|
|
@@ -1835,8 +2201,8 @@ export async function runRecommendWorkflow({
|
|
|
1835
2201
|
break;
|
|
1836
2202
|
}
|
|
1837
2203
|
|
|
1838
|
-
runControl.setPhase("recommend:candidate");
|
|
1839
|
-
const index = results.length;
|
|
2204
|
+
runControl.setPhase("recommend:candidate");
|
|
2205
|
+
const index = results.length;
|
|
1840
2206
|
let cardNodeId = nextCandidateResult.item.node_id;
|
|
1841
2207
|
const candidateKey = nextCandidateResult.item.key;
|
|
1842
2208
|
let cardCandidate = nextCandidateResult.item.candidate;
|
|
@@ -1850,11 +2216,13 @@ export async function runRecommendWorkflow({
|
|
|
1850
2216
|
if (index < effectiveDetailLimit) {
|
|
1851
2217
|
try {
|
|
1852
2218
|
await runControl.waitIfPaused();
|
|
1853
|
-
runControl.throwIfCanceled();
|
|
1854
|
-
runControl.setPhase("recommend:detail");
|
|
1855
|
-
detailStep = "ensure_viewport";
|
|
1856
|
-
|
|
1857
|
-
|
|
2219
|
+
runControl.throwIfCanceled();
|
|
2220
|
+
runControl.setPhase("recommend:detail");
|
|
2221
|
+
detailStep = "ensure_viewport";
|
|
2222
|
+
currentDomOperation = "candidate:detail-ensure-viewport";
|
|
2223
|
+
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
2224
|
+
currentDomOperation = "candidate:detail-close-blocking-panels";
|
|
2225
|
+
const blockingPanelClose = await closeRecommendBlockingPanels(client, {
|
|
1858
2226
|
attemptsLimit: 2,
|
|
1859
2227
|
rootState
|
|
1860
2228
|
});
|
|
@@ -1872,10 +2240,12 @@ export async function runRecommendWorkflow({
|
|
|
1872
2240
|
}
|
|
1873
2241
|
if (blockingPanelClose.already_closed === false) {
|
|
1874
2242
|
timings.account_rights_panel_close = compactCloseResult(blockingPanelClose);
|
|
1875
|
-
}
|
|
1876
|
-
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1877
|
-
detailStep = "open_detail";
|
|
1878
|
-
|
|
2243
|
+
}
|
|
2244
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2245
|
+
detailStep = "open_detail";
|
|
2246
|
+
currentDomOperation = "candidate:detail-open";
|
|
2247
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2248
|
+
networkRecorder.clear();
|
|
1879
2249
|
await maybeHumanActionCooldown("before_detail_open", timings);
|
|
1880
2250
|
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
1881
2251
|
cardNodeId,
|
|
@@ -1891,8 +2261,10 @@ export async function runRecommendWorkflow({
|
|
|
1891
2261
|
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
1892
2262
|
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
1893
2263
|
screeningCandidate = cardCandidate;
|
|
1894
|
-
if (shouldSkipRecentColleagueContacted) {
|
|
1895
|
-
detailStep = "check_colleague_contact";
|
|
2264
|
+
if (shouldSkipRecentColleagueContacted) {
|
|
2265
|
+
detailStep = "check_colleague_contact";
|
|
2266
|
+
currentDomOperation = "candidate:detail-colleague-contact";
|
|
2267
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1896
2268
|
try {
|
|
1897
2269
|
colleagueContact = await measureTiming(timings, "colleague_contact_check_ms", () => inspectRecentColleagueContact(
|
|
1898
2270
|
client,
|
|
@@ -1930,9 +2302,11 @@ export async function runRecommendWorkflow({
|
|
|
1930
2302
|
};
|
|
1931
2303
|
}
|
|
1932
2304
|
}
|
|
1933
|
-
if (!skipRecentColleagueContact) {
|
|
1934
|
-
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
1935
|
-
detailStep = "wait_network";
|
|
2305
|
+
if (!skipRecentColleagueContact) {
|
|
2306
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
2307
|
+
detailStep = "wait_network";
|
|
2308
|
+
currentDomOperation = "candidate:detail-network-wait";
|
|
2309
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1936
2310
|
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
1937
2311
|
waitForRecommendDetailNetworkEvents,
|
|
1938
2312
|
networkRecorder,
|
|
@@ -1945,8 +2319,10 @@ export async function runRecommendWorkflow({
|
|
|
1945
2319
|
));
|
|
1946
2320
|
if (networkWait?.elapsed_ms != null) {
|
|
1947
2321
|
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
1948
|
-
}
|
|
1949
|
-
detailStep = "extract_detail";
|
|
2322
|
+
}
|
|
2323
|
+
detailStep = "extract_detail";
|
|
2324
|
+
currentDomOperation = "candidate:detail-extract";
|
|
2325
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1950
2326
|
detailResult = await extractRecommendDetailCandidate(client, {
|
|
1951
2327
|
cardCandidate,
|
|
1952
2328
|
cardNodeId,
|
|
@@ -1970,8 +2346,10 @@ export async function runRecommendWorkflow({
|
|
|
1970
2346
|
parsedNetworkProfileCount,
|
|
1971
2347
|
waitResult: networkWait
|
|
1972
2348
|
});
|
|
1973
|
-
} else {
|
|
1974
|
-
detailStep = "wait_capture_target";
|
|
2349
|
+
} else {
|
|
2350
|
+
detailStep = "wait_capture_target";
|
|
2351
|
+
currentDomOperation = "candidate:detail-wait-capture-target";
|
|
2352
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
1975
2353
|
captureTargetWait = await waitForCvCaptureTarget(client, openedDetail.detail_state, {
|
|
1976
2354
|
domain: "recommend",
|
|
1977
2355
|
timeoutMs: 6000,
|
|
@@ -1980,70 +2358,206 @@ export async function runRecommendWorkflow({
|
|
|
1980
2358
|
captureTarget = captureTargetWait.target || null;
|
|
1981
2359
|
const captureNodeId = captureTarget?.node_id || null;
|
|
1982
2360
|
if (captureNodeId) {
|
|
1983
|
-
const imageEvidencePath = imageEvidenceFilePath({
|
|
1984
|
-
imageOutputDir,
|
|
1985
|
-
domain: "recommend",
|
|
1986
|
-
runId: runControl?.runId,
|
|
1987
|
-
index,
|
|
1988
|
-
extension: "jpg"
|
|
1989
|
-
});
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2361
|
+
const imageEvidencePath = imageEvidenceFilePath({
|
|
2362
|
+
imageOutputDir,
|
|
2363
|
+
domain: "recommend",
|
|
2364
|
+
runId: runControl?.runId,
|
|
2365
|
+
index,
|
|
2366
|
+
extension: "jpg"
|
|
2367
|
+
});
|
|
2368
|
+
const captureImageForTarget = (target, targetWait, resumeCheckpoint = null) => captureScrolledNodeScreenshots(
|
|
2369
|
+
client,
|
|
2370
|
+
target.node_id,
|
|
2371
|
+
{
|
|
2372
|
+
filePath: imageEvidencePath,
|
|
2373
|
+
format: "jpeg",
|
|
2374
|
+
quality: 72,
|
|
2375
|
+
optimize: true,
|
|
2376
|
+
resizeMaxWidth: 1100,
|
|
2377
|
+
captureViewport: false,
|
|
2378
|
+
iframeOwnerNodeId: target?.iframe_node_id || null,
|
|
2379
|
+
padding: 0,
|
|
2380
|
+
maxScreenshots: maxImagePages,
|
|
2381
|
+
wheelDeltaY: imageWheelDeltaY,
|
|
2382
|
+
settleMs: 350,
|
|
2383
|
+
scrollMethod: "dom-anchor-fallback-input",
|
|
2384
|
+
scrollDeltaJitterEnabled: effectiveHumanBehavior.listScrollJitter,
|
|
2385
|
+
stepTimeoutMs: 45000,
|
|
2386
|
+
totalTimeoutMs: 90000,
|
|
2387
|
+
duplicateStopCount: 2,
|
|
2388
|
+
skipDuplicateScreenshots: true,
|
|
2389
|
+
requireTerminalProof: true,
|
|
2390
|
+
composeForLlm: true,
|
|
2391
|
+
llmPagesPerImage: 3,
|
|
2392
|
+
llmResizeMaxWidth: 1100,
|
|
2393
|
+
llmQuality: 72,
|
|
2394
|
+
resumeCheckpoint,
|
|
2395
|
+
metadata: {
|
|
2396
|
+
domain: "recommend",
|
|
2397
|
+
capture_mode: "scroll_sequence",
|
|
2398
|
+
acquisition_reason: "network_miss_image_fallback",
|
|
2399
|
+
run_candidate_index: index,
|
|
2400
|
+
candidate_key: candidateKey,
|
|
2401
|
+
capture_target: target,
|
|
2402
|
+
capture_target_wait: targetWait,
|
|
2403
|
+
resumed_from_checkpoint: Boolean(resumeCheckpoint)
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
);
|
|
2407
|
+
try {
|
|
2408
|
+
detailStep = "capture_image";
|
|
2409
|
+
currentDomOperation = "candidate:detail-capture-image";
|
|
2410
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2411
|
+
imageEvidence = await measureTiming(
|
|
2412
|
+
timings,
|
|
2413
|
+
"screenshot_capture_ms",
|
|
2414
|
+
() => captureImageForTarget(captureTarget, captureTargetWait)
|
|
2415
|
+
);
|
|
2416
|
+
imageEvidence = requireCompleteImageEvidence(imageEvidence, {
|
|
2417
|
+
code: "IMAGE_CAPTURE_EVIDENCE_MISSING",
|
|
2418
|
+
message: "Recommend CV capture returned no complete persisted evidence",
|
|
2419
|
+
metadata: { domain: "recommend", candidate_key: candidateKey }
|
|
2420
|
+
});
|
|
2421
|
+
source = isIncompleteImageEvidence(imageEvidence) ? "image_capture_failed" : "image";
|
|
2422
|
+
} catch (error) {
|
|
2423
|
+
if (!isRecoverableImageCaptureError(error)) throw error;
|
|
2424
|
+
const retryReservation = imageCaptureWorkflowRetries.consume(candidateKey);
|
|
2425
|
+
if (retryReservation.allowed) {
|
|
2426
|
+
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
2427
|
+
timings.image_capture_workflow_retry = retryReservation;
|
|
2428
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
2429
|
+
const resumeCheckpoint = imageCaptureResumeCheckpoint(error);
|
|
2430
|
+
if (resumeCheckpoint) {
|
|
2431
|
+
detailStep = "resume_image_capture_reacquire";
|
|
2432
|
+
const resumeAttempt = await attemptImageCaptureCheckpointResume({
|
|
2433
|
+
checkpoint: resumeCheckpoint,
|
|
2434
|
+
reacquire: () => reacquireImageCaptureResumeTarget({
|
|
2435
|
+
domain: "recommend",
|
|
2436
|
+
getRoots: () => getRecommendRoots(client),
|
|
2437
|
+
ensureViewport: (freshRoots) => ensureRecommendViewport(
|
|
2438
|
+
freshRoots,
|
|
2439
|
+
"image_capture_resume_reacquire"
|
|
2440
|
+
),
|
|
2441
|
+
getDetailState: () => waitForRecommendDetail(client, {
|
|
2442
|
+
timeoutMs: 4000,
|
|
2443
|
+
intervalMs: 200
|
|
2444
|
+
}),
|
|
2445
|
+
isDetailAvailable: (state) => Boolean(state?.popup || state?.resumeIframe),
|
|
2446
|
+
waitForTarget: (detailState) => waitForCvCaptureTarget(client, detailState, {
|
|
2447
|
+
domain: "recommend",
|
|
2448
|
+
timeoutMs: 4000,
|
|
2449
|
+
intervalMs: 200
|
|
2450
|
+
})
|
|
2451
|
+
}),
|
|
2452
|
+
capture: (reacquired) => {
|
|
2453
|
+
rootState = reacquired.root_state;
|
|
2454
|
+
captureTarget = reacquired.target;
|
|
2455
|
+
captureTargetWait = reacquired.target_wait;
|
|
2456
|
+
detailStep = "resume_image_capture";
|
|
2457
|
+
return measureTiming(
|
|
2458
|
+
timings,
|
|
2459
|
+
"screenshot_capture_ms",
|
|
2460
|
+
() => captureImageForTarget(captureTarget, captureTargetWait, resumeCheckpoint)
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
});
|
|
2464
|
+
if (resumeAttempt.outcome === "reacquire_failed") {
|
|
2465
|
+
const reacquireError = resumeAttempt.error;
|
|
2466
|
+
imageEvidence = {
|
|
2467
|
+
...createRecoverableImageCaptureEvidence(reacquireError, {
|
|
2468
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
2469
|
+
filePath: imageEvidencePath,
|
|
2470
|
+
extension: "jpg",
|
|
2471
|
+
maxScreenshots: maxImagePages
|
|
2472
|
+
}),
|
|
2473
|
+
coverage_complete: false,
|
|
2474
|
+
resumed_from_checkpoint: true,
|
|
2475
|
+
resume_checkpoint_id: resumeCheckpoint.checkpoint_id || null,
|
|
2476
|
+
coverage_checkpoint: resumeCheckpoint
|
|
2477
|
+
};
|
|
2478
|
+
source = "image_capture_failed";
|
|
2479
|
+
timings.image_capture_resume = {
|
|
2480
|
+
attempted: true,
|
|
2481
|
+
ok: false,
|
|
2482
|
+
phase: "reacquire",
|
|
2483
|
+
checkpoint_id: resumeCheckpoint.checkpoint_id || null,
|
|
2484
|
+
error: compactError(reacquireError, "IMAGE_CAPTURE_RESUME_REACQUIRE_FAILED")
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
if (resumeAttempt.outcome === "completed") {
|
|
2488
|
+
imageEvidence = requireCompleteImageEvidence(resumeAttempt.evidence, {
|
|
2489
|
+
code: "IMAGE_CAPTURE_RESUME_EVIDENCE_MISSING",
|
|
2490
|
+
message: "Recommend resumed CV capture returned no complete persisted evidence",
|
|
2491
|
+
metadata: { domain: "recommend", candidate_key: candidateKey }
|
|
2492
|
+
});
|
|
2493
|
+
source = isIncompleteImageEvidence(imageEvidence) ? "image_capture_failed" : "image";
|
|
2494
|
+
timings.image_capture_resume = {
|
|
2495
|
+
attempted: true,
|
|
2496
|
+
ok: !isIncompleteImageEvidence(imageEvidence),
|
|
2497
|
+
phase: "capture",
|
|
2498
|
+
checkpoint_id: resumeCheckpoint.checkpoint_id || null,
|
|
2499
|
+
confirmed_screenshot_count: resumeCheckpoint.unique_screenshot_count || 0
|
|
2500
|
+
};
|
|
2501
|
+
} else if (resumeAttempt.outcome === "capture_failed") {
|
|
2502
|
+
const resumeError = resumeAttempt.error;
|
|
2503
|
+
imageEvidence = {
|
|
2504
|
+
...createRecoverableImageCaptureEvidence(resumeError, {
|
|
2505
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
2506
|
+
filePath: imageEvidencePath,
|
|
2507
|
+
extension: "jpg",
|
|
2508
|
+
maxScreenshots: maxImagePages
|
|
2509
|
+
}),
|
|
2510
|
+
coverage_complete: false,
|
|
2511
|
+
resumed_from_checkpoint: true,
|
|
2512
|
+
resume_checkpoint_id: resumeCheckpoint.checkpoint_id || null,
|
|
2513
|
+
coverage_checkpoint: resumeError.capture_checkpoint || resumeCheckpoint
|
|
2514
|
+
};
|
|
2515
|
+
source = "image_capture_failed";
|
|
2516
|
+
timings.image_capture_resume = {
|
|
2517
|
+
attempted: true,
|
|
2518
|
+
ok: false,
|
|
2519
|
+
phase: "capture",
|
|
2520
|
+
checkpoint_id: resumeCheckpoint.checkpoint_id || null,
|
|
2521
|
+
error: compactError(resumeError, "IMAGE_CAPTURE_RESUME_FAILED")
|
|
2522
|
+
};
|
|
2523
|
+
}
|
|
2524
|
+
} else {
|
|
2525
|
+
imageEvidence = {
|
|
2526
|
+
...createRecoverableImageCaptureEvidence(error, {
|
|
2527
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
2528
|
+
filePath: imageEvidencePath,
|
|
2529
|
+
extension: "jpg",
|
|
2530
|
+
maxScreenshots: maxImagePages
|
|
2531
|
+
}),
|
|
2532
|
+
coverage_complete: false
|
|
2533
|
+
};
|
|
2534
|
+
source = "image_capture_failed";
|
|
2535
|
+
timings.image_capture_resume = {
|
|
2536
|
+
attempted: false,
|
|
2537
|
+
ok: false,
|
|
2538
|
+
phase: "checkpoint",
|
|
2539
|
+
error: {
|
|
2540
|
+
code: "IMAGE_CAPTURE_CHECKPOINT_UNAVAILABLE",
|
|
2541
|
+
message: "Capture failed without a resumable checkpoint"
|
|
2542
|
+
}
|
|
2543
|
+
};
|
|
2544
|
+
}
|
|
2545
|
+
} else {
|
|
2546
|
+
imageEvidence = {
|
|
2547
|
+
...createRecoverableImageCaptureEvidence(error, {
|
|
2548
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
2549
|
+
filePath: imageEvidencePath,
|
|
2550
|
+
extension: "jpg",
|
|
2551
|
+
maxScreenshots: maxImagePages
|
|
2552
|
+
}),
|
|
2553
|
+
coverage_complete: false,
|
|
2554
|
+
metadata: {
|
|
2555
|
+
image_capture_workflow_retry: retryReservation
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
source = "image_capture_failed";
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2047
2561
|
recordCvImageFallback(cvAcquisitionState, {
|
|
2048
2562
|
reason: source === "image_capture_failed"
|
|
2049
2563
|
? "network_miss_image_capture_failed"
|
|
@@ -2052,9 +2566,18 @@ export async function runRecommendWorkflow({
|
|
|
2052
2566
|
waitResult: networkWait,
|
|
2053
2567
|
imageEvidence
|
|
2054
2568
|
});
|
|
2055
|
-
} else {
|
|
2056
|
-
source = "missing_capture_node";
|
|
2057
|
-
|
|
2569
|
+
} else {
|
|
2570
|
+
source = "missing_capture_node";
|
|
2571
|
+
imageEvidence = createRequiredImageEvidenceFailure({
|
|
2572
|
+
code: "IMAGE_CAPTURE_TARGET_UNAVAILABLE",
|
|
2573
|
+
message: "Recommend CV capture target was unavailable after the network fallback missed",
|
|
2574
|
+
metadata: {
|
|
2575
|
+
domain: "recommend",
|
|
2576
|
+
candidate_key: candidateKey,
|
|
2577
|
+
capture_target_wait: captureTargetWait || null
|
|
2578
|
+
}
|
|
2579
|
+
});
|
|
2580
|
+
recordCvNetworkMiss(cvAcquisitionState, {
|
|
2058
2581
|
reason: "network_miss_no_capture_node",
|
|
2059
2582
|
parsedNetworkProfileCount,
|
|
2060
2583
|
waitResult: networkWait
|
|
@@ -2075,21 +2598,42 @@ export async function runRecommendWorkflow({
|
|
|
2075
2598
|
};
|
|
2076
2599
|
screeningCandidate = detailResult.candidate;
|
|
2077
2600
|
}
|
|
2078
|
-
} catch (error) {
|
|
2079
|
-
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
2080
|
-
const
|
|
2081
|
-
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
2603
|
+
const staleForensic = checkpointDomStaleForensic(error, {
|
|
2604
|
+
phase: "recommend:detail",
|
|
2605
|
+
operation: currentDomOperation,
|
|
2606
|
+
detailStep,
|
|
2607
|
+
candidateIndex: index,
|
|
2608
|
+
candidateKey,
|
|
2609
|
+
cardNodeId
|
|
2610
|
+
});
|
|
2611
|
+
const recoveryCount = candidateRecoveryCounts.get(candidateKey) || 0;
|
|
2612
|
+
if (recoveryCount < 1) {
|
|
2082
2613
|
candidateRecoveryCounts.set(candidateKey, recoveryCount + 1);
|
|
2083
2614
|
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
2084
2615
|
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
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2616
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2617
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
2618
|
+
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
2619
|
+
try {
|
|
2620
|
+
const recovery = await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
2621
|
+
forceRecentNotView: true
|
|
2622
|
+
});
|
|
2623
|
+
checkpointDomStaleRecovery(staleForensic, {
|
|
2624
|
+
status: "recovered",
|
|
2625
|
+
recoveryResult: recovery
|
|
2626
|
+
});
|
|
2627
|
+
} catch (recoveryError) {
|
|
2628
|
+
checkpointDomStaleRecovery(staleForensic, {
|
|
2629
|
+
status: "recovery_failed",
|
|
2630
|
+
recoveryError
|
|
2631
|
+
});
|
|
2632
|
+
throw recoveryError;
|
|
2633
|
+
}
|
|
2634
|
+
continue;
|
|
2635
|
+
}
|
|
2636
|
+
checkpointDomStaleRecovery(staleForensic, { status: "candidate_failed_closed" });
|
|
2093
2637
|
recoverableDetailError = error;
|
|
2094
2638
|
detailResult = null;
|
|
2095
2639
|
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
@@ -2100,11 +2644,12 @@ export async function runRecommendWorkflow({
|
|
|
2100
2644
|
}
|
|
2101
2645
|
|
|
2102
2646
|
await runControl.waitIfPaused();
|
|
2103
|
-
runControl.throwIfCanceled();
|
|
2104
|
-
runControl.setPhase("recommend:screening");
|
|
2105
|
-
let llmResult = null;
|
|
2106
|
-
|
|
2107
|
-
|
|
2647
|
+
runControl.throwIfCanceled();
|
|
2648
|
+
runControl.setPhase("recommend:screening");
|
|
2649
|
+
let llmResult = null;
|
|
2650
|
+
const imageAcquisitionFailed = shouldFailClosedRecommendImageAcquisition(detailResult);
|
|
2651
|
+
if (useLlmScreening) {
|
|
2652
|
+
if (skipRecentColleagueContact || recoverableDetailError || imageAcquisitionFailed) {
|
|
2108
2653
|
llmResult = null;
|
|
2109
2654
|
} else if (!llmConfig) {
|
|
2110
2655
|
llmResult = createMissingLlmConfigResult();
|
|
@@ -2138,48 +2683,85 @@ export async function runRecommendWorkflow({
|
|
|
2138
2683
|
? createRecentColleagueContactSkipScreening(screeningCandidate, colleagueContact)
|
|
2139
2684
|
: recoverableDetailError
|
|
2140
2685
|
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
2141
|
-
:
|
|
2142
|
-
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
2143
|
-
code: detailResult
|
|
2144
|
-
message: detailResult
|
|
2145
|
-
})
|
|
2686
|
+
: imageAcquisitionFailed
|
|
2687
|
+
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
2688
|
+
code: detailResult?.image_evidence?.error_code || "IMAGE_CAPTURE_EVIDENCE_MISSING",
|
|
2689
|
+
message: detailResult?.image_evidence?.error || "Required CV image evidence is unavailable"
|
|
2690
|
+
})
|
|
2146
2691
|
: useLlmScreening
|
|
2147
2692
|
? llmResultToScreening(llmResult, screeningCandidate)
|
|
2148
2693
|
: screenCandidate(screeningCandidate, { criteria });
|
|
2149
2694
|
let actionDiscovery = null;
|
|
2150
2695
|
let postActionResult = null;
|
|
2151
|
-
let closeFailureError = null;
|
|
2152
|
-
let closeRecoveryFailure = null;
|
|
2153
|
-
if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2696
|
+
let closeFailureError = null;
|
|
2697
|
+
let closeRecoveryFailure = null;
|
|
2698
|
+
if (postActionEnabled && detailResult && !skipRecentColleagueContact) {
|
|
2699
|
+
try {
|
|
2700
|
+
const postActionStarted = Date.now();
|
|
2701
|
+
await runControl.waitIfPaused();
|
|
2702
|
+
runControl.throwIfCanceled();
|
|
2703
|
+
runControl.setPhase("recommend:post-action");
|
|
2704
|
+
detailStep = "post_action_discovery";
|
|
2705
|
+
currentDomOperation = "candidate:post-action-discovery";
|
|
2706
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2707
|
+
await maybeHumanActionCooldown("before_post_action", timings);
|
|
2708
|
+
actionDiscovery = await waitForRecommendDetailActionControls(client, {
|
|
2709
|
+
timeoutMs: actionTimeoutMs,
|
|
2710
|
+
intervalMs: actionIntervalMs,
|
|
2711
|
+
requireAny: true
|
|
2712
|
+
});
|
|
2713
|
+
detailStep = "post_action_execute";
|
|
2714
|
+
currentDomOperation = "candidate:post-action-execute";
|
|
2715
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2716
|
+
postActionResult = await runRecommendPostAction({
|
|
2717
|
+
client,
|
|
2718
|
+
screening,
|
|
2719
|
+
actionDiscovery,
|
|
2720
|
+
postAction: normalizedPostAction,
|
|
2721
|
+
greetCount,
|
|
2722
|
+
maxGreetCount: Number.isInteger(maxGreetCount) ? maxGreetCount : null,
|
|
2723
|
+
executePostAction,
|
|
2724
|
+
afterClickDelayMs: actionAfterClickDelayMs
|
|
2725
|
+
});
|
|
2726
|
+
if (postActionResult.counted_as_greet && postActionResult.action_clicked) {
|
|
2727
|
+
greetCount += 1;
|
|
2728
|
+
}
|
|
2729
|
+
addTiming(timings, "post_action_ms", Date.now() - postActionStarted);
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
checkpointDomStaleForensic(error, {
|
|
2732
|
+
phase: "recommend:post-action",
|
|
2733
|
+
operation: currentDomOperation,
|
|
2734
|
+
detailStep,
|
|
2735
|
+
candidateIndex: index,
|
|
2736
|
+
candidateKey,
|
|
2737
|
+
cardNodeId
|
|
2738
|
+
});
|
|
2739
|
+
throw error;
|
|
2740
|
+
}
|
|
2178
2741
|
}
|
|
2179
2742
|
if (detailResult && closeDetail && !detailResult.close_result?.closed) {
|
|
2180
2743
|
runControl.setPhase("recommend:close-detail");
|
|
2181
|
-
|
|
2182
|
-
|
|
2744
|
+
detailStep = "close_detail";
|
|
2745
|
+
currentDomOperation = "candidate:close-detail";
|
|
2746
|
+
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep });
|
|
2747
|
+
try {
|
|
2748
|
+
detailResult.close_result = await measureTiming(
|
|
2749
|
+
timings,
|
|
2750
|
+
"close_detail_ms",
|
|
2751
|
+
() => closeRecommendDetail(client)
|
|
2752
|
+
);
|
|
2753
|
+
} catch (error) {
|
|
2754
|
+
checkpointDomStaleForensic(error, {
|
|
2755
|
+
phase: "recommend:close-detail",
|
|
2756
|
+
operation: currentDomOperation,
|
|
2757
|
+
detailStep,
|
|
2758
|
+
candidateIndex: index,
|
|
2759
|
+
candidateKey,
|
|
2760
|
+
cardNodeId
|
|
2761
|
+
});
|
|
2762
|
+
throw error;
|
|
2763
|
+
}
|
|
2764
|
+
await maybeHumanActionCooldown("after_detail_close", timings);
|
|
2183
2765
|
if (!detailResult.close_result?.closed) {
|
|
2184
2766
|
closeFailureError = createRecommendCloseFailureError(detailResult.close_result);
|
|
2185
2767
|
try {
|
|
@@ -2198,17 +2780,17 @@ export async function runRecommendWorkflow({
|
|
|
2198
2780
|
} catch (error) {
|
|
2199
2781
|
closeRecoveryFailure = error;
|
|
2200
2782
|
detailResult.cv_acquisition = {
|
|
2201
|
-
...(detailResult.cv_acquisition || {}),
|
|
2202
|
-
close_recovery: {
|
|
2203
|
-
ok: false,
|
|
2204
|
-
reason: "context_recovery_failed",
|
|
2205
|
-
error: error?.message || String(error),
|
|
2206
|
-
forced_recent_not_view: Boolean(normalizedFilter.enabled)
|
|
2207
|
-
}
|
|
2208
|
-
};
|
|
2209
|
-
}
|
|
2210
|
-
}
|
|
2211
|
-
}
|
|
2783
|
+
...(detailResult.cv_acquisition || {}),
|
|
2784
|
+
close_recovery: {
|
|
2785
|
+
ok: false,
|
|
2786
|
+
reason: "context_recovery_failed",
|
|
2787
|
+
error: error?.message || String(error),
|
|
2788
|
+
forced_recent_not_view: Boolean(normalizedFilter.enabled)
|
|
2789
|
+
}
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
}
|
|
2793
|
+
}
|
|
2212
2794
|
timings.total_ms = Date.now() - candidateStarted;
|
|
2213
2795
|
const compactResult = {
|
|
2214
2796
|
index,
|
|
@@ -2245,9 +2827,10 @@ export async function runRecommendWorkflow({
|
|
|
2245
2827
|
last_candidate_key: candidateKey,
|
|
2246
2828
|
last_score: screening.score
|
|
2247
2829
|
});
|
|
2248
|
-
const checkpointStarted = Date.now();
|
|
2249
|
-
runControl.checkpoint({
|
|
2250
|
-
|
|
2830
|
+
const checkpointStarted = Date.now();
|
|
2831
|
+
runControl.checkpoint({
|
|
2832
|
+
in_progress_candidate: null,
|
|
2833
|
+
results,
|
|
2251
2834
|
last_candidate: {
|
|
2252
2835
|
id: screeningCandidate.id || null,
|
|
2253
2836
|
key: candidateKey,
|
|
@@ -2304,37 +2887,40 @@ export async function runRecommendWorkflow({
|
|
|
2304
2887
|
}
|
|
2305
2888
|
|
|
2306
2889
|
runControl.setPhase("recommend:done");
|
|
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),
|
|
2316
|
-
viewport_health: {
|
|
2317
|
-
stats: viewportGuard.getStats(),
|
|
2890
|
+
return {
|
|
2891
|
+
domain: "recommend",
|
|
2892
|
+
target_url: targetUrl,
|
|
2893
|
+
job_selection: compactJobSelection(jobSelection),
|
|
2894
|
+
page_scope: compactPageScopeSelection(pageScopeSelection),
|
|
2895
|
+
current_city_only: compactCurrentCityOnlyResult(currentCityOnlyResult),
|
|
2896
|
+
filter: compactFilterResult(filterResult),
|
|
2897
|
+
card_count: cardNodeIds.length,
|
|
2898
|
+
candidate_list: compactInfiniteListState(listState),
|
|
2899
|
+
viewport_health: {
|
|
2900
|
+
stats: viewportGuard.getStats(),
|
|
2318
2901
|
events: viewportGuard.getEvents()
|
|
2319
2902
|
},
|
|
2320
2903
|
human_behavior: effectiveHumanBehavior,
|
|
2321
2904
|
human_rest: humanRestController.getState(),
|
|
2322
2905
|
last_human_event: lastHumanEvent,
|
|
2323
2906
|
list_end_reason: listEndReason || null,
|
|
2324
|
-
refresh_rounds: refreshRounds,
|
|
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,
|
|
2907
|
+
refresh_rounds: refreshRounds,
|
|
2908
|
+
refresh_attempts: refreshAttempts,
|
|
2909
|
+
context_recoveries: contextRecoveryAttempts,
|
|
2910
|
+
debug_boundary: debugBoundary.getState(),
|
|
2911
|
+
list_read_stale_recovery_attempts: listReadStaleRecoveryAttempts,
|
|
2912
|
+
list_read_stale_recovery_applied: listReadStaleRecoveryApplied,
|
|
2913
|
+
list_read_stale_recoveries: listReadStaleRecoveries,
|
|
2331
2914
|
last_list_read_recovery_mode: lastListReadRecoveryMode,
|
|
2332
2915
|
last_list_read_stale_diagnostic: lastListReadStaleDiagnostic,
|
|
2333
2916
|
list_read_stale_diagnostics: listReadStaleDiagnostics.slice(-12),
|
|
2917
|
+
dom_stale_events: domStaleEventCount,
|
|
2918
|
+
dom_stale_forensics: domStaleForensics.slice(-12),
|
|
2919
|
+
dom_lifecycle_timeline: domLifecycleTimeline.slice(-20),
|
|
2334
2920
|
...countRecommendResultStatuses(results, { greetCount }),
|
|
2335
|
-
transient_recovered: countRecommendResultStatuses(results, { greetCount }).transient_recovered
|
|
2336
|
-
+ listReadStaleRecoveries,
|
|
2337
|
-
results
|
|
2921
|
+
transient_recovered: countRecommendResultStatuses(results, { greetCount }).transient_recovered
|
|
2922
|
+
+ listReadStaleRecoveries,
|
|
2923
|
+
results
|
|
2338
2924
|
};
|
|
2339
2925
|
}
|
|
2340
2926
|
|
|
@@ -2386,15 +2972,15 @@ export function createRecommendRunService({
|
|
|
2386
2972
|
llmImageDetail = "high",
|
|
2387
2973
|
imageOutputDir = "",
|
|
2388
2974
|
humanRestEnabled = false,
|
|
2389
|
-
humanBehavior = null,
|
|
2390
|
-
skipRecentColleagueContacted = true,
|
|
2391
|
-
colleagueContactWindowDays = 14,
|
|
2392
|
-
debugTestMode = undefined,
|
|
2393
|
-
debugForceListEndAfterProcessed = undefined,
|
|
2394
|
-
debugForceContextRecoveryAfterProcessed = undefined,
|
|
2395
|
-
debugForceCdpReconnectAfterProcessed = undefined,
|
|
2396
|
-
name = "recommend-domain-run"
|
|
2397
|
-
} = {}) {
|
|
2975
|
+
humanBehavior = null,
|
|
2976
|
+
skipRecentColleagueContacted = true,
|
|
2977
|
+
colleagueContactWindowDays = 14,
|
|
2978
|
+
debugTestMode = undefined,
|
|
2979
|
+
debugForceListEndAfterProcessed = undefined,
|
|
2980
|
+
debugForceContextRecoveryAfterProcessed = undefined,
|
|
2981
|
+
debugForceCdpReconnectAfterProcessed = undefined,
|
|
2982
|
+
name = "recommend-domain-run"
|
|
2983
|
+
} = {}) {
|
|
2398
2984
|
if (!client) throw new Error("startRecommendRun requires a guarded CDP client");
|
|
2399
2985
|
const normalizedFilter = normalizeFilter(filter);
|
|
2400
2986
|
const normalizedPostAction = normalizeRecommendPostAction(postAction) || "none";
|
|
@@ -2406,33 +2992,33 @@ export function createRecommendRunService({
|
|
|
2406
2992
|
const effectiveHumanBehavior = normalizeHumanBehaviorOptions(humanBehavior, {
|
|
2407
2993
|
legacyEnabled: humanRestEnabled === true || llmConfig?.humanRestEnabled === true
|
|
2408
2994
|
});
|
|
2409
|
-
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
2410
|
-
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
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({
|
|
2995
|
+
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
2996
|
+
const candidateLimit = Math.max(1, Number(maxCandidates) || 1);
|
|
2997
|
+
const normalizedDetailLimit = detailLimit == null ? null : Math.max(0, Number(detailLimit) || 0);
|
|
2998
|
+
const debugBoundaryOptions = normalizeRecommendDebugBoundaryOptions({
|
|
2999
|
+
debugTestMode: debugTestMode === true,
|
|
3000
|
+
debugForceListEndAfterProcessed: debugForceListEndAfterProcessed ?? null,
|
|
3001
|
+
debugForceContextRecoveryAfterProcessed: debugForceContextRecoveryAfterProcessed ?? null,
|
|
3002
|
+
debugForceCdpReconnectAfterProcessed: debugForceCdpReconnectAfterProcessed ?? null
|
|
3003
|
+
});
|
|
3004
|
+
return manager.startRun({
|
|
2419
3005
|
runId,
|
|
2420
3006
|
name,
|
|
2421
3007
|
pid,
|
|
2422
3008
|
context: {
|
|
2423
3009
|
domain: "recommend",
|
|
2424
3010
|
target_url: targetUrl,
|
|
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,
|
|
3011
|
+
criteria_present: Boolean(criteria),
|
|
3012
|
+
job_label: jobLabel || "",
|
|
3013
|
+
requested_page_scope: requestedPageScope,
|
|
3014
|
+
fallback_page_scope: normalizedFallbackPageScope,
|
|
3015
|
+
filter: normalizedFilter,
|
|
3016
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
3017
|
+
max_candidates: maxCandidates,
|
|
3018
|
+
max_candidates_semantics: "passed_candidates",
|
|
3019
|
+
detail_limit: normalizedDetailLimit,
|
|
3020
|
+
close_detail: closeDetail,
|
|
3021
|
+
cv_acquisition_mode: cvAcquisitionMode,
|
|
2436
3022
|
max_image_pages: maxImagePages,
|
|
2437
3023
|
image_wheel_delta_y: imageWheelDeltaY,
|
|
2438
3024
|
list_max_scrolls: listMaxScrolls,
|
|
@@ -2458,12 +3044,12 @@ export function createRecommendRunService({
|
|
|
2458
3044
|
colleague_contact_window_days: normalizedColleagueContactWindowDays,
|
|
2459
3045
|
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
2460
3046
|
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
2461
|
-
human_behavior: effectiveHumanBehavior,
|
|
2462
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
2463
|
-
human_rest_enabled: effectiveHumanRestEnabled,
|
|
2464
|
-
debug_test_mode: debugBoundaryOptions.debugTestMode,
|
|
2465
|
-
debug_boundary_mode: debugBoundaryOptions.mode,
|
|
2466
|
-
debug_boundary_threshold: debugBoundaryOptions.threshold
|
|
3047
|
+
human_behavior: effectiveHumanBehavior,
|
|
3048
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
3049
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
3050
|
+
debug_test_mode: debugBoundaryOptions.debugTestMode,
|
|
3051
|
+
debug_boundary_mode: debugBoundaryOptions.mode,
|
|
3052
|
+
debug_boundary_threshold: debugBoundaryOptions.threshold
|
|
2467
3053
|
},
|
|
2468
3054
|
progress: {
|
|
2469
3055
|
card_count: 0,
|
|
@@ -2479,25 +3065,25 @@ export function createRecommendRunService({
|
|
|
2479
3065
|
post_action_clicked: 0,
|
|
2480
3066
|
image_capture_failed: 0,
|
|
2481
3067
|
detail_open_failed: 0,
|
|
2482
|
-
transient_recovered: 0,
|
|
2483
|
-
colleague_contact_checked: 0,
|
|
2484
|
-
recent_colleague_contact_skipped: 0,
|
|
2485
|
-
colleague_contact_panel_missing: 0,
|
|
2486
|
-
context_recoveries: 0,
|
|
2487
|
-
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
2488
|
-
current_city_only_effective: null,
|
|
2489
|
-
current_city_only_unavailable: false,
|
|
2490
|
-
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
2491
|
-
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
2492
|
-
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
2493
|
-
human_rest_enabled: effectiveHumanRestEnabled,
|
|
2494
|
-
human_rest_count: 0,
|
|
2495
|
-
human_rest_ms: 0,
|
|
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
|
|
3068
|
+
transient_recovered: 0,
|
|
3069
|
+
colleague_contact_checked: 0,
|
|
3070
|
+
recent_colleague_contact_skipped: 0,
|
|
3071
|
+
colleague_contact_panel_missing: 0,
|
|
3072
|
+
context_recoveries: 0,
|
|
3073
|
+
current_city_only_requested: normalizedFilter.currentCityOnly,
|
|
3074
|
+
current_city_only_effective: null,
|
|
3075
|
+
current_city_only_unavailable: false,
|
|
3076
|
+
human_behavior_enabled: effectiveHumanBehavior.enabled,
|
|
3077
|
+
human_behavior_profile: effectiveHumanBehavior.profile,
|
|
3078
|
+
human_rest_level: effectiveHumanBehavior.restLevel,
|
|
3079
|
+
human_rest_enabled: effectiveHumanRestEnabled,
|
|
3080
|
+
human_rest_count: 0,
|
|
3081
|
+
human_rest_ms: 0,
|
|
3082
|
+
last_human_event: null,
|
|
3083
|
+
debug_boundary_mode: debugBoundaryOptions.mode,
|
|
3084
|
+
debug_boundary_threshold: debugBoundaryOptions.threshold,
|
|
3085
|
+
debug_boundary_triggered: false,
|
|
3086
|
+
debug_boundary_trigger_count: 0
|
|
2501
3087
|
},
|
|
2502
3088
|
checkpoint: {},
|
|
2503
3089
|
task: (runControl) => workflow({
|
|
@@ -2538,14 +3124,14 @@ export function createRecommendRunService({
|
|
|
2538
3124
|
llmImageDetail,
|
|
2539
3125
|
imageOutputDir,
|
|
2540
3126
|
humanRestEnabled: effectiveHumanRestEnabled,
|
|
2541
|
-
humanBehavior: effectiveHumanBehavior,
|
|
2542
|
-
skipRecentColleagueContacted: shouldSkipRecentColleagueContacted,
|
|
2543
|
-
colleagueContactWindowDays: normalizedColleagueContactWindowDays,
|
|
2544
|
-
debugTestMode: debugBoundaryOptions.debugTestMode,
|
|
2545
|
-
debugForceListEndAfterProcessed: debugBoundaryOptions.debugForceListEndAfterProcessed,
|
|
2546
|
-
debugForceContextRecoveryAfterProcessed: debugBoundaryOptions.debugForceContextRecoveryAfterProcessed,
|
|
2547
|
-
debugForceCdpReconnectAfterProcessed: debugBoundaryOptions.debugForceCdpReconnectAfterProcessed
|
|
2548
|
-
}, runControl)
|
|
3127
|
+
humanBehavior: effectiveHumanBehavior,
|
|
3128
|
+
skipRecentColleagueContacted: shouldSkipRecentColleagueContacted,
|
|
3129
|
+
colleagueContactWindowDays: normalizedColleagueContactWindowDays,
|
|
3130
|
+
debugTestMode: debugBoundaryOptions.debugTestMode,
|
|
3131
|
+
debugForceListEndAfterProcessed: debugBoundaryOptions.debugForceListEndAfterProcessed,
|
|
3132
|
+
debugForceContextRecoveryAfterProcessed: debugBoundaryOptions.debugForceContextRecoveryAfterProcessed,
|
|
3133
|
+
debugForceCdpReconnectAfterProcessed: debugBoundaryOptions.debugForceCdpReconnectAfterProcessed
|
|
3134
|
+
}, runControl)
|
|
2549
3135
|
});
|
|
2550
3136
|
}
|
|
2551
3137
|
|