@reconcrap/boss-recommend-mcp 1.3.38 → 2.0.0
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 +53 -33
- package/package.json +61 -9
- package/skills/boss-recommend-pipeline/SKILL.md +4 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1095 -196
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +67 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +68 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -6927
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2294
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
getNodeBox,
|
|
4
|
+
scrollNodeIntoView,
|
|
5
|
+
sleep
|
|
6
|
+
} from "../browser/index.js";
|
|
7
|
+
|
|
8
|
+
function nowIso() {
|
|
9
|
+
return new Date().toISOString();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function normalizeText(value) {
|
|
13
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function shortHash(value) {
|
|
17
|
+
return crypto.createHash("sha256").update(String(value || "")).digest("hex").slice(0, 16);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function pickAttribute(attributes = {}, names = []) {
|
|
21
|
+
for (const name of names) {
|
|
22
|
+
const value = normalizeText(attributes[name]);
|
|
23
|
+
if (value) return value;
|
|
24
|
+
}
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function candidateKeyFromProfile(candidate = {}, {
|
|
29
|
+
nodeId = null,
|
|
30
|
+
attributes = candidate.attributes || candidate.metadata?.attributes || {}
|
|
31
|
+
} = {}) {
|
|
32
|
+
const text = normalizeText(candidate.text?.raw || candidate.text || "");
|
|
33
|
+
const textSuffix = text ? `:text:${shortHash(text.slice(0, 1000))}` : "";
|
|
34
|
+
const id = normalizeText(candidate.id);
|
|
35
|
+
const stableAttrKey = pickAttribute(attributes, [
|
|
36
|
+
"data-geek",
|
|
37
|
+
"data-geekid",
|
|
38
|
+
"data-expect",
|
|
39
|
+
"data-uid",
|
|
40
|
+
"data-securityid",
|
|
41
|
+
"encryptgeekid",
|
|
42
|
+
"geekid",
|
|
43
|
+
"expect",
|
|
44
|
+
"uid",
|
|
45
|
+
"securityid"
|
|
46
|
+
]);
|
|
47
|
+
if (id && stableAttrKey && id === stableAttrKey) return `${candidate.domain || "candidate"}:id:${id}`;
|
|
48
|
+
if (id && !stableAttrKey) return `${candidate.domain || "candidate"}:id:${id}${textSuffix}`;
|
|
49
|
+
if (id) return `${candidate.domain || "candidate"}:id:${id}${textSuffix}`;
|
|
50
|
+
|
|
51
|
+
const attrKey = pickAttribute(attributes, [
|
|
52
|
+
"data-geek",
|
|
53
|
+
"data-geekid",
|
|
54
|
+
"data-expect",
|
|
55
|
+
"data-jid",
|
|
56
|
+
"data-id",
|
|
57
|
+
"data-uid",
|
|
58
|
+
"data-securityid",
|
|
59
|
+
"encryptgeekid",
|
|
60
|
+
"href",
|
|
61
|
+
"key",
|
|
62
|
+
"id"
|
|
63
|
+
]);
|
|
64
|
+
if (attrKey) return `${candidate.domain || "candidate"}:attr:${attrKey}${textSuffix}`;
|
|
65
|
+
|
|
66
|
+
const identity = candidate.identity || {};
|
|
67
|
+
const identityKey = [
|
|
68
|
+
identity.name,
|
|
69
|
+
identity.current_company,
|
|
70
|
+
identity.current_position,
|
|
71
|
+
identity.school,
|
|
72
|
+
identity.major,
|
|
73
|
+
identity.degree,
|
|
74
|
+
identity.age,
|
|
75
|
+
identity.gender
|
|
76
|
+
].map(normalizeText).filter(Boolean).join("|");
|
|
77
|
+
if (identityKey) return `${candidate.domain || "candidate"}:identity:${shortHash(identityKey)}`;
|
|
78
|
+
|
|
79
|
+
if (text) return `${candidate.domain || "candidate"}:text:${shortHash(text.slice(0, 1000))}`;
|
|
80
|
+
|
|
81
|
+
return `${candidate.domain || "candidate"}:node:${nodeId || "unknown"}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function createInfiniteListState({
|
|
85
|
+
domain = "unknown",
|
|
86
|
+
listName = "candidate-list"
|
|
87
|
+
} = {}) {
|
|
88
|
+
return {
|
|
89
|
+
schema_version: 1,
|
|
90
|
+
domain,
|
|
91
|
+
list_name: listName,
|
|
92
|
+
created_at: nowIso(),
|
|
93
|
+
seen_keys: new Set(),
|
|
94
|
+
queued_keys: new Set(),
|
|
95
|
+
processed_keys: new Set(),
|
|
96
|
+
skipped_duplicate_count: 0,
|
|
97
|
+
read_error_count: 0,
|
|
98
|
+
scroll_count: 0,
|
|
99
|
+
stable_signature_count: 0,
|
|
100
|
+
last_visible_signature: "",
|
|
101
|
+
last_result: null,
|
|
102
|
+
ledger: []
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function compactInfiniteListState(state = {}) {
|
|
107
|
+
return {
|
|
108
|
+
domain: state.domain || "unknown",
|
|
109
|
+
list_name: state.list_name || "candidate-list",
|
|
110
|
+
seen_count: state.seen_keys?.size || 0,
|
|
111
|
+
queued_count: state.queued_keys?.size || 0,
|
|
112
|
+
processed_count: state.processed_keys?.size || 0,
|
|
113
|
+
skipped_duplicate_count: state.skipped_duplicate_count || 0,
|
|
114
|
+
read_error_count: state.read_error_count || 0,
|
|
115
|
+
scroll_count: state.scroll_count || 0,
|
|
116
|
+
stable_signature_count: state.stable_signature_count || 0,
|
|
117
|
+
last_visible_signature: state.last_visible_signature || "",
|
|
118
|
+
last_result: state.last_result || null
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function markInfiniteListCandidateProcessed(state, key, {
|
|
123
|
+
status = "processed",
|
|
124
|
+
metadata = {}
|
|
125
|
+
} = {}) {
|
|
126
|
+
if (!state || !key) return compactInfiniteListState(state);
|
|
127
|
+
state.queued_keys?.delete(key);
|
|
128
|
+
state.processed_keys?.add(key);
|
|
129
|
+
state.ledger?.push({
|
|
130
|
+
at: nowIso(),
|
|
131
|
+
event: "candidate_processed",
|
|
132
|
+
key,
|
|
133
|
+
status,
|
|
134
|
+
metadata
|
|
135
|
+
});
|
|
136
|
+
return compactInfiniteListState(state);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function markInfiniteListCandidateSkipped(state, key, {
|
|
140
|
+
reason = "skipped",
|
|
141
|
+
metadata = {}
|
|
142
|
+
} = {}) {
|
|
143
|
+
if (!state || !key) return compactInfiniteListState(state);
|
|
144
|
+
state.queued_keys?.delete(key);
|
|
145
|
+
state.ledger?.push({
|
|
146
|
+
at: nowIso(),
|
|
147
|
+
event: "candidate_skipped",
|
|
148
|
+
key,
|
|
149
|
+
reason,
|
|
150
|
+
metadata
|
|
151
|
+
});
|
|
152
|
+
return compactInfiniteListState(state);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function resetInfiniteListForRefreshRound(state, {
|
|
156
|
+
reason = "refresh_round",
|
|
157
|
+
round = 0,
|
|
158
|
+
method = "",
|
|
159
|
+
metadata = {}
|
|
160
|
+
} = {}) {
|
|
161
|
+
if (!state) return compactInfiniteListState(state);
|
|
162
|
+
state.queued_keys?.clear();
|
|
163
|
+
state.stable_signature_count = 0;
|
|
164
|
+
state.last_visible_signature = "";
|
|
165
|
+
state.last_result = null;
|
|
166
|
+
state.ledger?.push({
|
|
167
|
+
at: nowIso(),
|
|
168
|
+
event: "refresh_round_started",
|
|
169
|
+
reason,
|
|
170
|
+
round,
|
|
171
|
+
method,
|
|
172
|
+
metadata
|
|
173
|
+
});
|
|
174
|
+
return compactInfiniteListState(state);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function readVisibleInfiniteListItems({
|
|
178
|
+
nodeIds = [],
|
|
179
|
+
readCandidate,
|
|
180
|
+
keyForCandidate = candidateKeyFromProfile,
|
|
181
|
+
state = null
|
|
182
|
+
} = {}) {
|
|
183
|
+
if (typeof readCandidate !== "function") {
|
|
184
|
+
throw new Error("readVisibleInfiniteListItems requires readCandidate");
|
|
185
|
+
}
|
|
186
|
+
const items = [];
|
|
187
|
+
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
188
|
+
const nodeId = nodeIds[visibleIndex];
|
|
189
|
+
let candidate;
|
|
190
|
+
try {
|
|
191
|
+
candidate = await readCandidate(nodeId, { visibleIndex });
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (state) {
|
|
194
|
+
state.read_error_count = (state.read_error_count || 0) + 1;
|
|
195
|
+
state.ledger?.push({
|
|
196
|
+
at: nowIso(),
|
|
197
|
+
event: "candidate_read_error",
|
|
198
|
+
node_id: nodeId,
|
|
199
|
+
visible_index: visibleIndex,
|
|
200
|
+
error: error?.message || String(error)
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
const key = keyForCandidate(candidate, {
|
|
206
|
+
nodeId,
|
|
207
|
+
visibleIndex,
|
|
208
|
+
attributes: candidate?.attributes || candidate?.metadata?.attributes || {}
|
|
209
|
+
});
|
|
210
|
+
items.push({
|
|
211
|
+
key,
|
|
212
|
+
node_id: nodeId,
|
|
213
|
+
visible_index: visibleIndex,
|
|
214
|
+
candidate
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
return items;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function updateInfiniteListVisibleSignature(state, items = []) {
|
|
221
|
+
const signature = items.map((item) => item.key).filter(Boolean).join("|");
|
|
222
|
+
const unchanged = Boolean(signature) && signature === state.last_visible_signature;
|
|
223
|
+
state.stable_signature_count = unchanged ? (state.stable_signature_count || 0) + 1 : 0;
|
|
224
|
+
state.last_visible_signature = signature;
|
|
225
|
+
return {
|
|
226
|
+
signature,
|
|
227
|
+
unchanged,
|
|
228
|
+
stable_signature_count: state.stable_signature_count
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function firstUnseenInfiniteListItem(state, items = []) {
|
|
233
|
+
for (const item of items) {
|
|
234
|
+
if (!item.key) continue;
|
|
235
|
+
if (state.processed_keys.has(item.key) || state.queued_keys.has(item.key)) {
|
|
236
|
+
state.skipped_duplicate_count += 1;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
state.seen_keys.add(item.key);
|
|
240
|
+
state.queued_keys.add(item.key);
|
|
241
|
+
state.ledger.push({
|
|
242
|
+
at: nowIso(),
|
|
243
|
+
event: "candidate_queued",
|
|
244
|
+
key: item.key,
|
|
245
|
+
node_id: item.node_id,
|
|
246
|
+
visible_index: item.visible_index
|
|
247
|
+
});
|
|
248
|
+
return item;
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export async function scrollInfiniteListByVisibleItems(client, items = [], {
|
|
254
|
+
wheelDeltaY = 850,
|
|
255
|
+
settleMs = 1200,
|
|
256
|
+
fallbackPoint = null
|
|
257
|
+
} = {}) {
|
|
258
|
+
const candidates = items.filter((item) => item?.node_id);
|
|
259
|
+
if (!candidates.length) {
|
|
260
|
+
return {
|
|
261
|
+
ok: false,
|
|
262
|
+
reason: "no_visible_items"
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const errors = [];
|
|
267
|
+
for (const anchor of candidates.slice().reverse()) {
|
|
268
|
+
try {
|
|
269
|
+
await scrollNodeIntoView(client, anchor.node_id);
|
|
270
|
+
await sleep(150);
|
|
271
|
+
const box = await getNodeBox(client, anchor.node_id);
|
|
272
|
+
const x = box.center.x;
|
|
273
|
+
const y = box.center.y;
|
|
274
|
+
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
275
|
+
await client.Input.dispatchMouseEvent({
|
|
276
|
+
type: "mouseWheel",
|
|
277
|
+
x,
|
|
278
|
+
y,
|
|
279
|
+
deltaX: 0,
|
|
280
|
+
deltaY: Math.max(1, Number(wheelDeltaY) || 850)
|
|
281
|
+
});
|
|
282
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
283
|
+
return {
|
|
284
|
+
ok: true,
|
|
285
|
+
anchor_key: anchor.key,
|
|
286
|
+
anchor_node_id: anchor.node_id,
|
|
287
|
+
point: { x, y },
|
|
288
|
+
wheel_delta_y: Math.max(1, Number(wheelDeltaY) || 850),
|
|
289
|
+
settle_ms: settleMs,
|
|
290
|
+
skipped_stale_anchor_count: errors.length
|
|
291
|
+
};
|
|
292
|
+
} catch (error) {
|
|
293
|
+
errors.push({
|
|
294
|
+
anchor_key: anchor.key,
|
|
295
|
+
anchor_node_id: anchor.node_id,
|
|
296
|
+
error: error?.message || String(error)
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (fallbackPoint && Number.isFinite(Number(fallbackPoint.x)) && Number.isFinite(Number(fallbackPoint.y))) {
|
|
302
|
+
const x = Number(fallbackPoint.x);
|
|
303
|
+
const y = Number(fallbackPoint.y);
|
|
304
|
+
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
305
|
+
await client.Input.dispatchMouseEvent({
|
|
306
|
+
type: "mouseWheel",
|
|
307
|
+
x,
|
|
308
|
+
y,
|
|
309
|
+
deltaX: 0,
|
|
310
|
+
deltaY: Math.max(1, Number(wheelDeltaY) || 850)
|
|
311
|
+
});
|
|
312
|
+
if (settleMs > 0) await sleep(settleMs);
|
|
313
|
+
return {
|
|
314
|
+
ok: true,
|
|
315
|
+
mode: "fallback_point",
|
|
316
|
+
point: { x, y },
|
|
317
|
+
wheel_delta_y: Math.max(1, Number(wheelDeltaY) || 850),
|
|
318
|
+
settle_ms: settleMs,
|
|
319
|
+
skipped_stale_anchor_count: errors.length,
|
|
320
|
+
stale_anchor_errors: errors
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
ok: false,
|
|
326
|
+
reason: "scroll_anchor_unavailable",
|
|
327
|
+
errors
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function getNextInfiniteListCandidate({
|
|
332
|
+
client,
|
|
333
|
+
state,
|
|
334
|
+
findNodeIds,
|
|
335
|
+
readCandidate,
|
|
336
|
+
keyForCandidate = candidateKeyFromProfile,
|
|
337
|
+
maxScrolls = 20,
|
|
338
|
+
stableSignatureLimit = 2,
|
|
339
|
+
wheelDeltaY = 850,
|
|
340
|
+
settleMs = 1200,
|
|
341
|
+
fallbackPoint = null
|
|
342
|
+
} = {}) {
|
|
343
|
+
if (!client) throw new Error("getNextInfiniteListCandidate requires client");
|
|
344
|
+
if (!state) throw new Error("getNextInfiniteListCandidate requires state");
|
|
345
|
+
if (typeof findNodeIds !== "function") throw new Error("getNextInfiniteListCandidate requires findNodeIds");
|
|
346
|
+
if (typeof readCandidate !== "function") throw new Error("getNextInfiniteListCandidate requires readCandidate");
|
|
347
|
+
|
|
348
|
+
const attempts = [];
|
|
349
|
+
const maxAttempts = Math.max(0, Number(maxScrolls) || 0);
|
|
350
|
+
for (let scrollAttempt = 0; scrollAttempt <= maxAttempts; scrollAttempt += 1) {
|
|
351
|
+
const nodeIds = await findNodeIds();
|
|
352
|
+
const items = await readVisibleInfiniteListItems({
|
|
353
|
+
nodeIds,
|
|
354
|
+
readCandidate,
|
|
355
|
+
keyForCandidate,
|
|
356
|
+
state
|
|
357
|
+
});
|
|
358
|
+
const signature = updateInfiniteListVisibleSignature(state, items);
|
|
359
|
+
const next = firstUnseenInfiniteListItem(state, items);
|
|
360
|
+
attempts.push({
|
|
361
|
+
scroll_attempt: scrollAttempt,
|
|
362
|
+
visible_count: items.length,
|
|
363
|
+
signature: signature.signature,
|
|
364
|
+
stable_signature_count: signature.stable_signature_count,
|
|
365
|
+
found_next: Boolean(next)
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (next) {
|
|
369
|
+
state.stable_signature_count = 0;
|
|
370
|
+
const result = {
|
|
371
|
+
ok: true,
|
|
372
|
+
end_reached: false,
|
|
373
|
+
item: next,
|
|
374
|
+
attempts,
|
|
375
|
+
state: compactInfiniteListState(state)
|
|
376
|
+
};
|
|
377
|
+
state.last_result = {
|
|
378
|
+
at: nowIso(),
|
|
379
|
+
ok: true,
|
|
380
|
+
key: next.key,
|
|
381
|
+
visible_index: next.visible_index
|
|
382
|
+
};
|
|
383
|
+
return result;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!items.length) {
|
|
387
|
+
const result = {
|
|
388
|
+
ok: false,
|
|
389
|
+
end_reached: true,
|
|
390
|
+
reason: "empty_visible_list",
|
|
391
|
+
attempts,
|
|
392
|
+
state: compactInfiniteListState(state)
|
|
393
|
+
};
|
|
394
|
+
state.last_result = {
|
|
395
|
+
at: nowIso(),
|
|
396
|
+
ok: false,
|
|
397
|
+
end_reached: true,
|
|
398
|
+
reason: result.reason
|
|
399
|
+
};
|
|
400
|
+
return result;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (signature.stable_signature_count >= Math.max(1, Number(stableSignatureLimit) || 1)) {
|
|
404
|
+
const result = {
|
|
405
|
+
ok: false,
|
|
406
|
+
end_reached: true,
|
|
407
|
+
reason: "stable_visible_signature",
|
|
408
|
+
attempts,
|
|
409
|
+
state: compactInfiniteListState(state)
|
|
410
|
+
};
|
|
411
|
+
state.last_result = {
|
|
412
|
+
at: nowIso(),
|
|
413
|
+
ok: false,
|
|
414
|
+
end_reached: true,
|
|
415
|
+
reason: result.reason
|
|
416
|
+
};
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const scrollResult = await scrollInfiniteListByVisibleItems(client, items, {
|
|
421
|
+
wheelDeltaY,
|
|
422
|
+
settleMs,
|
|
423
|
+
fallbackPoint
|
|
424
|
+
});
|
|
425
|
+
state.scroll_count += scrollResult.ok ? 1 : 0;
|
|
426
|
+
attempts[attempts.length - 1].scroll_result = scrollResult;
|
|
427
|
+
if (!scrollResult.ok) {
|
|
428
|
+
const result = {
|
|
429
|
+
ok: false,
|
|
430
|
+
end_reached: true,
|
|
431
|
+
reason: scrollResult.reason || "scroll_failed",
|
|
432
|
+
attempts,
|
|
433
|
+
state: compactInfiniteListState(state)
|
|
434
|
+
};
|
|
435
|
+
state.last_result = {
|
|
436
|
+
at: nowIso(),
|
|
437
|
+
ok: false,
|
|
438
|
+
end_reached: true,
|
|
439
|
+
reason: result.reason
|
|
440
|
+
};
|
|
441
|
+
return result;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const result = {
|
|
446
|
+
ok: false,
|
|
447
|
+
end_reached: false,
|
|
448
|
+
reason: "max_scrolls_exhausted",
|
|
449
|
+
attempts,
|
|
450
|
+
state: compactInfiniteListState(state)
|
|
451
|
+
};
|
|
452
|
+
state.last_result = {
|
|
453
|
+
at: nowIso(),
|
|
454
|
+
ok: false,
|
|
455
|
+
end_reached: false,
|
|
456
|
+
reason: result.reason
|
|
457
|
+
};
|
|
458
|
+
return result;
|
|
459
|
+
}
|