@reconcrap/boss-recommend-mcp 1.1.11 → 1.2.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/package.json +1 -1
- package/skills/boss-recommend-pipeline/SKILL.md +4 -3
- package/src/adapters.js +33 -19
- package/src/cli.js +1 -0
- package/src/index.js +2 -2
- package/src/parser.js +9 -4
- package/src/pipeline.js +7 -3
- package/src/test-adapters-runtime.js +107 -0
- package/src/test-parser.js +16 -0
- package/src/test-pipeline.js +78 -1
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +528 -104
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +187 -1
- package/vendor/boss-recommend-search-cli/src/cli.js +14 -6
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +2 -0
|
@@ -15,6 +15,7 @@ const RESUME_CAPTURE_RETRY_DELAY_MS = 1200;
|
|
|
15
15
|
const MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES = 10;
|
|
16
16
|
const PAGE_SCOPE_TAB_STATUS = {
|
|
17
17
|
recommend: "0",
|
|
18
|
+
latest: "1",
|
|
18
19
|
featured: "3"
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -79,6 +80,7 @@ function normalizePageScope(value) {
|
|
|
79
80
|
const normalized = normalizeText(value).toLowerCase();
|
|
80
81
|
if (!normalized) return null;
|
|
81
82
|
if (["recommend", "推荐", "推荐页", "推荐页面"].includes(normalized)) return "recommend";
|
|
83
|
+
if (["latest", "最新", "最新页", "最新页面"].includes(normalized)) return "latest";
|
|
82
84
|
if (["featured", "精选", "精选页", "精选页面", "精选牛人"].includes(normalized)) return "featured";
|
|
83
85
|
return null;
|
|
84
86
|
}
|
|
@@ -91,6 +93,234 @@ function parseBoolean(value) {
|
|
|
91
93
|
return null;
|
|
92
94
|
}
|
|
93
95
|
|
|
96
|
+
function normalizeCliOptionToken(rawToken) {
|
|
97
|
+
const token = String(rawToken || "").trim();
|
|
98
|
+
if (!token) {
|
|
99
|
+
return { token: "", inlineValue: null };
|
|
100
|
+
}
|
|
101
|
+
const normalizedDashes = token.replace(/^[\u2010-\u2015\u2212\uFE58\uFE63\uFF0D]+/, "--");
|
|
102
|
+
const eqIndex = normalizedDashes.indexOf("=");
|
|
103
|
+
if (normalizedDashes.startsWith("--") && eqIndex > 2) {
|
|
104
|
+
return {
|
|
105
|
+
token: normalizedDashes.slice(0, eqIndex),
|
|
106
|
+
inlineValue: normalizedDashes.slice(eqIndex + 1)
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return { token: normalizedDashes, inlineValue: null };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function parseFavoriteActionValue(rawValue) {
|
|
113
|
+
if (rawValue === null || rawValue === undefined) return null;
|
|
114
|
+
if (typeof rawValue === "boolean") return rawValue ? "add" : "del";
|
|
115
|
+
if (typeof rawValue === "number") {
|
|
116
|
+
if (rawValue === 1) return "add";
|
|
117
|
+
if (rawValue === 0) return "del";
|
|
118
|
+
}
|
|
119
|
+
const normalized = normalizeText(rawValue).toLowerCase();
|
|
120
|
+
if (!normalized) return null;
|
|
121
|
+
if (
|
|
122
|
+
["1", "add", "favorite", "collect", "interested", "true", "yes", "on"].includes(normalized)
|
|
123
|
+
|| /(?:^|[_\W])(add|favorite|collect|interest(?:ed)?)(?:$|[_\W])/.test(normalized)
|
|
124
|
+
) {
|
|
125
|
+
return "add";
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
["0", "del", "delete", "remove", "cancel", "unfavorite", "uncollect", "false", "no", "off"].includes(normalized)
|
|
129
|
+
|| /(?:^|[_\W])(del|delete|remove|cancel|unfavorite|uncollect|uninterest)(?:$|[_\W])/.test(normalized)
|
|
130
|
+
) {
|
|
131
|
+
return "del";
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseFavoriteActionFromObject(payload, visited = new Set()) {
|
|
137
|
+
if (!payload || typeof payload !== "object") return null;
|
|
138
|
+
if (visited.has(payload)) return null;
|
|
139
|
+
visited.add(payload);
|
|
140
|
+
|
|
141
|
+
if (Array.isArray(payload)) {
|
|
142
|
+
for (const item of payload) {
|
|
143
|
+
const action = parseFavoriteActionFromObject(item, visited);
|
|
144
|
+
if (action) return action;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const keys = Object.keys(payload);
|
|
150
|
+
const strongSignalKey = (key) => /p3|status|state|favorite|collect|interested|markstatus|isfavorite|iscollect/.test(key);
|
|
151
|
+
const weakSignalKey = (key) => /action|op|operation|type|mode|mark|interest/.test(key);
|
|
152
|
+
for (const key of keys) {
|
|
153
|
+
const value = payload[key];
|
|
154
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
155
|
+
if (strongSignalKey(normalizedKey)) {
|
|
156
|
+
const action = parseFavoriteActionValue(value);
|
|
157
|
+
if (action) return action;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
for (const key of keys) {
|
|
161
|
+
const value = payload[key];
|
|
162
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
163
|
+
if (weakSignalKey(normalizedKey)) {
|
|
164
|
+
const action = parseFavoriteActionValue(value);
|
|
165
|
+
if (action) return action;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for (const key of keys) {
|
|
170
|
+
const value = payload[key];
|
|
171
|
+
if (value && typeof value === "object") {
|
|
172
|
+
const action = parseFavoriteActionFromObject(value, visited);
|
|
173
|
+
if (action) return action;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function parseFavoriteActionFromPostData(rawPostData) {
|
|
180
|
+
const postData = normalizeText(rawPostData);
|
|
181
|
+
if (!postData) return null;
|
|
182
|
+
|
|
183
|
+
try {
|
|
184
|
+
const parsed = JSON.parse(postData);
|
|
185
|
+
const action = parseFavoriteActionFromObject(parsed);
|
|
186
|
+
if (action) return action;
|
|
187
|
+
} catch {}
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
const params = new URLSearchParams(postData);
|
|
191
|
+
const strongEntries = [];
|
|
192
|
+
const weakEntries = [];
|
|
193
|
+
for (const [key, value] of params.entries()) {
|
|
194
|
+
const normalizedKey = normalizeText(key).toLowerCase();
|
|
195
|
+
if (/p3|status|state|favorite|collect|interested|markstatus|isfavorite|iscollect/.test(normalizedKey)) {
|
|
196
|
+
strongEntries.push(value);
|
|
197
|
+
} else if (/action|op|operation|type|mode|mark|interest/.test(normalizedKey)) {
|
|
198
|
+
weakEntries.push(value);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const value of strongEntries) {
|
|
202
|
+
const action = parseFavoriteActionValue(value);
|
|
203
|
+
if (action) return action;
|
|
204
|
+
}
|
|
205
|
+
for (const value of weakEntries) {
|
|
206
|
+
const action = parseFavoriteActionValue(value);
|
|
207
|
+
if (action) return action;
|
|
208
|
+
}
|
|
209
|
+
} catch {}
|
|
210
|
+
|
|
211
|
+
const fallback = parseFavoriteActionValue(postData);
|
|
212
|
+
if (fallback) return fallback;
|
|
213
|
+
|
|
214
|
+
if (/star-interest-click/i.test(postData)) {
|
|
215
|
+
if (/(?:^|[?&"'\s])p3(?:["'\s:=]){1,3}1(?:$|[&"'\s,}])/i.test(postData)) return "add";
|
|
216
|
+
if (/(?:^|[?&"'\s])p3(?:["'\s:=]){1,3}0(?:$|[&"'\s,}])/i.test(postData)) return "del";
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function parseFavoriteActionFromRequest(url, postData = "") {
|
|
222
|
+
const normalizedUrl = normalizeText(url).toLowerCase();
|
|
223
|
+
if (!normalizedUrl) return null;
|
|
224
|
+
|
|
225
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b|[?&](?:status|p3|favorite|collect|interested)=1\b/i.test(normalizedUrl)) {
|
|
226
|
+
return "add";
|
|
227
|
+
}
|
|
228
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b|[?&](?:status|p3|favorite|collect|interested)=0\b/i.test(normalizedUrl)) {
|
|
229
|
+
return "del";
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return parseFavoriteActionFromPostData(postData);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function parseFavoriteActionFromActionLog(postData = "") {
|
|
236
|
+
const raw = normalizeText(postData);
|
|
237
|
+
if (!raw) return null;
|
|
238
|
+
try {
|
|
239
|
+
const payload = JSON.parse(raw);
|
|
240
|
+
if (normalizeText(payload?.action).toLowerCase() !== "star-interest-click") return null;
|
|
241
|
+
return parseFavoriteActionValue(payload?.p3);
|
|
242
|
+
} catch {}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const params = new URLSearchParams(raw);
|
|
246
|
+
const actionName = normalizeText(params.get("action")).toLowerCase();
|
|
247
|
+
if (actionName !== "star-interest-click") return null;
|
|
248
|
+
return parseFavoriteActionValue(params.get("p3"));
|
|
249
|
+
} catch {}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function parseFavoriteActionFromKnownRequest(url, postData = "") {
|
|
254
|
+
const normalizedUrl = normalizeText(url).toLowerCase();
|
|
255
|
+
if (!normalizedUrl) return null;
|
|
256
|
+
|
|
257
|
+
if (normalizedUrl.includes("usermark")) {
|
|
258
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b/i.test(normalizedUrl)) {
|
|
259
|
+
return "add";
|
|
260
|
+
}
|
|
261
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b/i.test(normalizedUrl)) {
|
|
262
|
+
return "del";
|
|
263
|
+
}
|
|
264
|
+
return null;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (normalizedUrl.includes("actionlog/common.json")) {
|
|
268
|
+
return parseFavoriteActionFromActionLog(postData);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function parseFavoriteActionFromWsPayload(payload, depth = 0) {
|
|
275
|
+
if (depth > 3 || payload === null || payload === undefined) return null;
|
|
276
|
+
|
|
277
|
+
if (typeof payload === "object") {
|
|
278
|
+
if (normalizeText(payload?.action).toLowerCase() === "star-interest-click") {
|
|
279
|
+
const strictAction = parseFavoriteActionValue(payload?.p3);
|
|
280
|
+
if (strictAction) return strictAction;
|
|
281
|
+
}
|
|
282
|
+
const nestedCandidates = [
|
|
283
|
+
payload.data,
|
|
284
|
+
payload.payload,
|
|
285
|
+
payload.body,
|
|
286
|
+
payload.message,
|
|
287
|
+
payload.msg
|
|
288
|
+
];
|
|
289
|
+
for (const nested of nestedCandidates) {
|
|
290
|
+
const action = parseFavoriteActionFromWsPayload(nested, depth + 1);
|
|
291
|
+
if (action) return action;
|
|
292
|
+
}
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const text = normalizeText(payload);
|
|
297
|
+
if (!text) return null;
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(text);
|
|
301
|
+
const action = parseFavoriteActionFromWsPayload(parsed, depth + 1);
|
|
302
|
+
if (action) return action;
|
|
303
|
+
} catch {}
|
|
304
|
+
|
|
305
|
+
const actionFromActionLog = parseFavoriteActionFromActionLog(text);
|
|
306
|
+
if (actionFromActionLog) return actionFromActionLog;
|
|
307
|
+
if (/usermark/i.test(text)) {
|
|
308
|
+
if (/\/add(?:\/|$)|[?&](?:action|op|operation|type)=add\b/i.test(text)) return "add";
|
|
309
|
+
if (/\/del(?:\/|$)|[?&](?:action|op|operation|type)=del\b/i.test(text)) return "del";
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function isRecoverablePostActionError(error, action) {
|
|
316
|
+
const normalizedAction = normalizeText(action).toLowerCase();
|
|
317
|
+
const normalizedCode = normalizeText(error?.code).toUpperCase();
|
|
318
|
+
if (!normalizedAction || !normalizedCode) return false;
|
|
319
|
+
if (normalizedAction === "favorite" && normalizedCode === "FAVORITE_BUTTON_FAILED") return true;
|
|
320
|
+
if (normalizedAction === "greet" && normalizedCode === "GREET_BUTTON_FAILED") return true;
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
|
|
94
324
|
function loadCalibrationPosition(filePath) {
|
|
95
325
|
try {
|
|
96
326
|
const resolved = path.resolve(String(filePath || ""));
|
|
@@ -162,69 +392,71 @@ function parseArgs(argv) {
|
|
|
162
392
|
};
|
|
163
393
|
|
|
164
394
|
for (let index = 0; index < argv.length; index += 1) {
|
|
165
|
-
const
|
|
395
|
+
const normalizedToken = normalizeCliOptionToken(argv[index]);
|
|
396
|
+
const token = normalizedToken.token;
|
|
166
397
|
const next = argv[index + 1];
|
|
167
|
-
|
|
168
|
-
|
|
398
|
+
const inlineValue = normalizedToken.inlineValue;
|
|
399
|
+
if ((token === "--baseurl" || token === "--base-url") && (inlineValue || next)) {
|
|
400
|
+
parsed.baseUrl = inlineValue || next;
|
|
169
401
|
parsed.__provided.baseUrl = true;
|
|
170
|
-
index += 1;
|
|
171
|
-
} else if (token === "--apikey" && next) {
|
|
172
|
-
parsed.apiKey = next;
|
|
402
|
+
if (!inlineValue) index += 1;
|
|
403
|
+
} else if ((token === "--apikey" || token === "--api-key") && (inlineValue || next)) {
|
|
404
|
+
parsed.apiKey = inlineValue || next;
|
|
173
405
|
parsed.__provided.apiKey = true;
|
|
174
|
-
index += 1;
|
|
175
|
-
} else if (token === "--model" && next) {
|
|
176
|
-
parsed.model = next;
|
|
406
|
+
if (!inlineValue) index += 1;
|
|
407
|
+
} else if (token === "--model" && (inlineValue || next)) {
|
|
408
|
+
parsed.model = inlineValue || next;
|
|
177
409
|
parsed.__provided.model = true;
|
|
178
|
-
index += 1;
|
|
179
|
-
} else if (token === "--openai-organization" && next) {
|
|
180
|
-
parsed.openaiOrganization = next;
|
|
181
|
-
index += 1;
|
|
182
|
-
} else if (token === "--openai-project" && next) {
|
|
183
|
-
parsed.openaiProject = next;
|
|
184
|
-
index += 1;
|
|
185
|
-
} else if (token === "--criteria" && next) {
|
|
186
|
-
parsed.criteria = next;
|
|
410
|
+
if (!inlineValue) index += 1;
|
|
411
|
+
} else if (token === "--openai-organization" && (inlineValue || next)) {
|
|
412
|
+
parsed.openaiOrganization = inlineValue || next;
|
|
413
|
+
if (!inlineValue) index += 1;
|
|
414
|
+
} else if (token === "--openai-project" && (inlineValue || next)) {
|
|
415
|
+
parsed.openaiProject = inlineValue || next;
|
|
416
|
+
if (!inlineValue) index += 1;
|
|
417
|
+
} else if (token === "--criteria" && (inlineValue || next)) {
|
|
418
|
+
parsed.criteria = inlineValue || next;
|
|
187
419
|
parsed.__provided.criteria = true;
|
|
188
|
-
index += 1;
|
|
189
|
-
} else if (token === "--targetCount" && next) {
|
|
190
|
-
parsed.targetCount = parsePositiveInteger(next);
|
|
420
|
+
if (!inlineValue) index += 1;
|
|
421
|
+
} else if ((token === "--targetCount" || token === "--target-count") && (inlineValue || next)) {
|
|
422
|
+
parsed.targetCount = parsePositiveInteger(inlineValue || next);
|
|
191
423
|
parsed.__provided.targetCount = true;
|
|
192
|
-
index += 1;
|
|
193
|
-
} else if (token === "--max-greet-count" && next) {
|
|
194
|
-
parsed.maxGreetCount = parsePositiveInteger(next);
|
|
424
|
+
if (!inlineValue) index += 1;
|
|
425
|
+
} else if ((token === "--max-greet-count" || token === "--maxGreetCount") && (inlineValue || next)) {
|
|
426
|
+
parsed.maxGreetCount = parsePositiveInteger(inlineValue || next);
|
|
195
427
|
parsed.__provided.maxGreetCount = true;
|
|
196
|
-
index += 1;
|
|
197
|
-
} else if (token === "--page-scope" && next) {
|
|
198
|
-
parsed.pageScope = normalizePageScope(next) || "recommend";
|
|
428
|
+
if (!inlineValue) index += 1;
|
|
429
|
+
} else if ((token === "--page-scope" || token === "--pageScope" || token === "--page_scope") && (inlineValue || next)) {
|
|
430
|
+
parsed.pageScope = normalizePageScope(inlineValue || next) || "recommend";
|
|
199
431
|
parsed.__provided.pageScope = true;
|
|
200
|
-
index += 1;
|
|
201
|
-
} else if (token === "--calibration" && next) {
|
|
202
|
-
parsed.calibrationPath = path.resolve(next);
|
|
432
|
+
if (!inlineValue) index += 1;
|
|
433
|
+
} else if ((token === "--calibration" || token === "--calibration-path") && (inlineValue || next)) {
|
|
434
|
+
parsed.calibrationPath = path.resolve(inlineValue || next);
|
|
203
435
|
parsed.__provided.calibrationPath = true;
|
|
204
|
-
index += 1;
|
|
205
|
-
} else if (token === "--port" && next) {
|
|
206
|
-
parsed.port = parsePositiveInteger(next) || DEFAULT_PORT;
|
|
436
|
+
if (!inlineValue) index += 1;
|
|
437
|
+
} else if (token === "--port" && (inlineValue || next)) {
|
|
438
|
+
parsed.port = parsePositiveInteger(inlineValue || next) || DEFAULT_PORT;
|
|
207
439
|
parsed.__provided.port = true;
|
|
208
|
-
index += 1;
|
|
209
|
-
} else if (token === "--output" && next) {
|
|
210
|
-
parsed.output = path.resolve(next);
|
|
211
|
-
index += 1;
|
|
212
|
-
} else if (token === "--checkpoint-path" && next) {
|
|
213
|
-
parsed.checkpointPath = path.resolve(next);
|
|
214
|
-
index += 1;
|
|
215
|
-
} else if (token === "--pause-control-path" && next) {
|
|
216
|
-
parsed.pauseControlPath = path.resolve(next);
|
|
217
|
-
index += 1;
|
|
440
|
+
if (!inlineValue) index += 1;
|
|
441
|
+
} else if (token === "--output" && (inlineValue || next)) {
|
|
442
|
+
parsed.output = path.resolve(inlineValue || next);
|
|
443
|
+
if (!inlineValue) index += 1;
|
|
444
|
+
} else if (token === "--checkpoint-path" && (inlineValue || next)) {
|
|
445
|
+
parsed.checkpointPath = path.resolve(inlineValue || next);
|
|
446
|
+
if (!inlineValue) index += 1;
|
|
447
|
+
} else if (token === "--pause-control-path" && (inlineValue || next)) {
|
|
448
|
+
parsed.pauseControlPath = path.resolve(inlineValue || next);
|
|
449
|
+
if (!inlineValue) index += 1;
|
|
218
450
|
} else if (token === "--resume") {
|
|
219
451
|
parsed.resume = true;
|
|
220
|
-
} else if (token === "--post-action" && next) {
|
|
221
|
-
parsed.postAction = normalizePostAction(next);
|
|
452
|
+
} else if ((token === "--post-action" || token === "--postAction") && (inlineValue || next)) {
|
|
453
|
+
parsed.postAction = normalizePostAction(inlineValue || next);
|
|
222
454
|
parsed.__provided.postAction = true;
|
|
223
|
-
index += 1;
|
|
224
|
-
} else if (token === "--post-action-confirmed" && next) {
|
|
225
|
-
parsed.postActionConfirmed = parseBoolean(next);
|
|
455
|
+
if (!inlineValue) index += 1;
|
|
456
|
+
} else if ((token === "--post-action-confirmed" || token === "--postActionConfirmed") && (inlineValue || next)) {
|
|
457
|
+
parsed.postActionConfirmed = parseBoolean(inlineValue || next);
|
|
226
458
|
parsed.__provided.postActionConfirmed = true;
|
|
227
|
-
index += 1;
|
|
459
|
+
if (!inlineValue) index += 1;
|
|
228
460
|
} else if (token === "--help" || token === "-h") {
|
|
229
461
|
parsed.help = true;
|
|
230
462
|
}
|
|
@@ -544,6 +776,7 @@ function buildListCandidatesExpr(processedKeys) {
|
|
|
544
776
|
const processed = new Set(processedKeys || []);
|
|
545
777
|
const cards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item'));
|
|
546
778
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
|
|
779
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
|
|
547
780
|
const textOf = (el) => String(el ? el.textContent : '').replace(/\s+/g, ' ').trim();
|
|
548
781
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
549
782
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
@@ -608,19 +841,71 @@ function buildListCandidatesExpr(processedKeys) {
|
|
|
608
841
|
layout: 'featured'
|
|
609
842
|
};
|
|
610
843
|
}).filter(Boolean);
|
|
844
|
+
const latestCandidates = latestCards.map((card, index) => {
|
|
845
|
+
const inner = card.querySelector('.card-inner[data-geek]') || card.querySelector('[data-geek]');
|
|
846
|
+
if (!inner) return null;
|
|
847
|
+
const geekId = inner.getAttribute('data-geek');
|
|
848
|
+
if (!geekId) return null;
|
|
849
|
+
const rect = card.getBoundingClientRect();
|
|
850
|
+
const nameEl = card.querySelector('.name, .name-wrap .name, .name-wrap');
|
|
851
|
+
const tags = Array.from(card.querySelectorAll('.base-info span, .edu-wrap span, .desc span, .tag-wrap span, .tag-item'))
|
|
852
|
+
.map((item) => textOf(item))
|
|
853
|
+
.filter(Boolean);
|
|
854
|
+
const latestWork = card.querySelector('.timeline-wrap.work-exps .timeline-item');
|
|
855
|
+
const workSpans = latestWork
|
|
856
|
+
? Array.from(latestWork.querySelectorAll('.join-text-wrap.content span')).map((item) => textOf(item)).filter(Boolean)
|
|
857
|
+
: [];
|
|
858
|
+
return {
|
|
859
|
+
found: true,
|
|
860
|
+
index,
|
|
861
|
+
key: geekId,
|
|
862
|
+
geek_id: geekId,
|
|
863
|
+
name: textOf(nameEl),
|
|
864
|
+
school: tags[0] || '',
|
|
865
|
+
major: tags[1] || '',
|
|
866
|
+
degree: tags[2] || '',
|
|
867
|
+
last_company: workSpans[0] || '',
|
|
868
|
+
last_position: workSpans[1] || '',
|
|
869
|
+
x: frameRect.left + rect.left + Math.min(Math.max(rect.width / 2, 80), Math.max(rect.width - 40, 80)),
|
|
870
|
+
y: frameRect.top + rect.top + Math.min(Math.max(rect.height / 2, 24), Math.max(rect.height - 12, 24)),
|
|
871
|
+
width: rect.width,
|
|
872
|
+
height: rect.height,
|
|
873
|
+
layout: 'latest'
|
|
874
|
+
};
|
|
875
|
+
}).filter(Boolean);
|
|
611
876
|
const inferredStatus = activeStatus
|
|
612
|
-
|| (
|
|
877
|
+
|| (
|
|
878
|
+
featuredCandidates.length > 0 && recommendCandidates.length === 0 && latestCandidates.length === 0
|
|
879
|
+
? '3'
|
|
880
|
+
: latestCandidates.length > 0 && recommendCandidates.length === 0 && featuredCandidates.length === 0
|
|
881
|
+
? '1'
|
|
882
|
+
: recommendCandidates.length > 0 && featuredCandidates.length === 0 && latestCandidates.length === 0
|
|
883
|
+
? '0'
|
|
884
|
+
: ''
|
|
885
|
+
);
|
|
613
886
|
const activeLayout = inferredStatus === '3'
|
|
614
887
|
? 'featured'
|
|
615
|
-
: inferredStatus === '
|
|
616
|
-
? '
|
|
617
|
-
:
|
|
618
|
-
|
|
888
|
+
: inferredStatus === '1'
|
|
889
|
+
? 'latest'
|
|
890
|
+
: inferredStatus === '0'
|
|
891
|
+
? 'recommend'
|
|
892
|
+
: (
|
|
893
|
+
featuredCandidates.length > 0 && recommendCandidates.length === 0 && latestCandidates.length === 0
|
|
894
|
+
? 'featured'
|
|
895
|
+
: latestCandidates.length > 0 && featuredCandidates.length === 0 && recommendCandidates.length === 0
|
|
896
|
+
? 'latest'
|
|
897
|
+
: 'recommend'
|
|
898
|
+
);
|
|
899
|
+
const candidates = activeLayout === 'featured'
|
|
900
|
+
? featuredCandidates
|
|
901
|
+
: activeLayout === 'latest'
|
|
902
|
+
? latestCandidates
|
|
903
|
+
: recommendCandidates;
|
|
619
904
|
return {
|
|
620
905
|
ok: true,
|
|
621
906
|
candidates: candidates.filter((candidate) => !processed.has(candidate.key)),
|
|
622
907
|
candidate_count: candidates.length,
|
|
623
|
-
total_cards: activeLayout === 'featured' ? featuredCards.length : cards.length,
|
|
908
|
+
total_cards: activeLayout === 'featured' ? featuredCards.length : activeLayout === 'latest' ? latestCards.length : cards.length,
|
|
624
909
|
active_tab_status: inferredStatus || null,
|
|
625
910
|
layout: activeLayout
|
|
626
911
|
};
|
|
@@ -641,16 +926,28 @@ const jsGetListState = `(() => {
|
|
|
641
926
|
const candidateCards = cards.filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
642
927
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card'));
|
|
643
928
|
const featuredCandidates = featuredCards.filter((card) => card.querySelector('a[data-geekid]'));
|
|
929
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap'));
|
|
930
|
+
const latestCandidates = latestCards.filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
|
|
644
931
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
645
932
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
646
933
|
const activeTabStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
|
|
647
934
|
const inferredStatus = activeTabStatus
|
|
648
|
-
|| (
|
|
935
|
+
|| (
|
|
936
|
+
featuredCandidates.length > 0 && candidateCards.length === 0 && latestCandidates.length === 0
|
|
937
|
+
? '3'
|
|
938
|
+
: latestCandidates.length > 0 && candidateCards.length === 0 && featuredCandidates.length === 0
|
|
939
|
+
? '1'
|
|
940
|
+
: candidateCards.length > 0 && featuredCandidates.length === 0 && latestCandidates.length === 0
|
|
941
|
+
? '0'
|
|
942
|
+
: ''
|
|
943
|
+
);
|
|
649
944
|
const effectiveCount = inferredStatus === '3'
|
|
650
945
|
? featuredCandidates.length
|
|
651
|
-
: inferredStatus === '
|
|
652
|
-
?
|
|
653
|
-
:
|
|
946
|
+
: inferredStatus === '1'
|
|
947
|
+
? latestCandidates.length
|
|
948
|
+
: inferredStatus === '0'
|
|
949
|
+
? candidateCards.length
|
|
950
|
+
: Math.max(candidateCards.length, featuredCandidates.length, latestCandidates.length);
|
|
654
951
|
return {
|
|
655
952
|
ok: true,
|
|
656
953
|
scrollTop: body ? body.scrollTop : 0,
|
|
@@ -668,7 +965,8 @@ const jsGetListState = `(() => {
|
|
|
668
965
|
candidateCount: effectiveCount,
|
|
669
966
|
recommendCandidateCount: candidateCards.length,
|
|
670
967
|
featuredCandidateCount: featuredCandidates.length,
|
|
671
|
-
|
|
968
|
+
latestCandidateCount: latestCandidates.length,
|
|
969
|
+
totalCards: Math.max(cards.length, featuredCards.length, latestCards.length),
|
|
672
970
|
activeTabStatus: inferredStatus || null
|
|
673
971
|
};
|
|
674
972
|
})()`;
|
|
@@ -684,12 +982,21 @@ const jsScrollList = `(() => {
|
|
|
684
982
|
const body = doc.body;
|
|
685
983
|
const recommendCards = Array.from(doc.querySelectorAll('ul.card-list > li.card-item')).filter((card) => card.querySelector('.card-inner[data-geekid]'));
|
|
686
984
|
const featuredCards = Array.from(doc.querySelectorAll('li.geek-info-card')).filter((card) => card.querySelector('a[data-geekid]'));
|
|
985
|
+
const latestCards = Array.from(doc.querySelectorAll('.candidate-card-wrap')).filter((card) => card.querySelector('.card-inner[data-geek], [data-geek]'));
|
|
687
986
|
const tabs = Array.from(doc.querySelectorAll('li.tab-item[data-status], li[data-status][class*="tab"]'));
|
|
688
987
|
const activeTab = tabs.find((node) => /(?:^|\\s)(?:curr|current|active|selected)(?:\\s|$)/i.test(String(node.className || ''))) || null;
|
|
689
988
|
const activeStatus = activeTab ? String(activeTab.getAttribute('data-status') || '') : '';
|
|
690
989
|
const inferredStatus = activeStatus
|
|
691
|
-
|| (
|
|
692
|
-
|
|
990
|
+
|| (
|
|
991
|
+
featuredCards.length > 0 && recommendCards.length === 0 && latestCards.length === 0
|
|
992
|
+
? '3'
|
|
993
|
+
: latestCards.length > 0 && recommendCards.length === 0 && featuredCards.length === 0
|
|
994
|
+
? '1'
|
|
995
|
+
: recommendCards.length > 0 && featuredCards.length === 0 && latestCards.length === 0
|
|
996
|
+
? '0'
|
|
997
|
+
: ''
|
|
998
|
+
);
|
|
999
|
+
const activeCards = inferredStatus === '3' ? featuredCards : inferredStatus === '1' ? latestCards : recommendCards;
|
|
693
1000
|
const lastCard = activeCards[activeCards.length - 1];
|
|
694
1001
|
const before = {
|
|
695
1002
|
scrollTop: body ? body.scrollTop : 0,
|
|
@@ -1546,6 +1853,8 @@ class RecommendScreenCli {
|
|
|
1546
1853
|
this.latestResumeNetworkPayload = null;
|
|
1547
1854
|
this.favoriteActionEvents = [];
|
|
1548
1855
|
this.favoriteClickPendingSince = 0;
|
|
1856
|
+
this.favoriteNetworkTraces = [];
|
|
1857
|
+
this.webSocketByRequestId = new Map();
|
|
1549
1858
|
this.resumeSourceStats = {
|
|
1550
1859
|
network: 0,
|
|
1551
1860
|
image_fallback: 0
|
|
@@ -1640,6 +1949,7 @@ class RecommendScreenCli {
|
|
|
1640
1949
|
|
|
1641
1950
|
markFavoriteClickPending() {
|
|
1642
1951
|
this.favoriteClickPendingSince = Date.now();
|
|
1952
|
+
this.favoriteNetworkTraces = [];
|
|
1643
1953
|
}
|
|
1644
1954
|
|
|
1645
1955
|
consumeFavoriteActionResult(since = 0) {
|
|
@@ -1656,6 +1966,30 @@ class RecommendScreenCli {
|
|
|
1656
1966
|
return matched.action || null;
|
|
1657
1967
|
}
|
|
1658
1968
|
|
|
1969
|
+
recordFavoriteNetworkTrace(entry) {
|
|
1970
|
+
const trace = {
|
|
1971
|
+
ts: Date.now(),
|
|
1972
|
+
...entry
|
|
1973
|
+
};
|
|
1974
|
+
this.favoriteNetworkTraces.push(trace);
|
|
1975
|
+
if (this.favoriteNetworkTraces.length > 60) {
|
|
1976
|
+
this.favoriteNetworkTraces = this.favoriteNetworkTraces.slice(-60);
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
summarizeFavoriteNetworkTrace(since = 0) {
|
|
1981
|
+
const timestamp = Number.isFinite(since) ? since : 0;
|
|
1982
|
+
return this.favoriteNetworkTraces
|
|
1983
|
+
.filter((item) => Number(item?.ts || 0) >= timestamp)
|
|
1984
|
+
.slice(-12)
|
|
1985
|
+
.map((item) => {
|
|
1986
|
+
if (item.kind === "ws") {
|
|
1987
|
+
return `[ws:${item.direction}] ${item.url || "unknown"} payload=${item.payload || ""}`;
|
|
1988
|
+
}
|
|
1989
|
+
return `[http] ${item.method || "GET"} ${item.url || ""} body=${item.postData || ""}`;
|
|
1990
|
+
});
|
|
1991
|
+
}
|
|
1992
|
+
|
|
1659
1993
|
cacheResumeNetworkPayload(payload, fallbackGeekId = null) {
|
|
1660
1994
|
if (!payload || typeof payload !== "object") return;
|
|
1661
1995
|
const geekDetail = payload.geekDetail || payload;
|
|
@@ -1734,31 +2068,55 @@ class RecommendScreenCli {
|
|
|
1734
2068
|
|
|
1735
2069
|
if (this.favoriteClickPendingSince <= 0) return;
|
|
1736
2070
|
const requestTs = Date.now();
|
|
1737
|
-
if (requestTs < this.favoriteClickPendingSince
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2071
|
+
if (requestTs < this.favoriteClickPendingSince) return;
|
|
2072
|
+
const method = normalizeText(params?.request?.method || "").toUpperCase() || "GET";
|
|
2073
|
+
const postData = params?.request?.postData || "";
|
|
2074
|
+
this.recordFavoriteNetworkTrace({
|
|
2075
|
+
ts: requestTs,
|
|
2076
|
+
kind: "http",
|
|
2077
|
+
method,
|
|
2078
|
+
url: url.slice(0, 240),
|
|
2079
|
+
postData: normalizeText(postData).slice(0, 200)
|
|
2080
|
+
});
|
|
2081
|
+
const action = parseFavoriteActionFromKnownRequest(url, postData);
|
|
2082
|
+
if (!action) return;
|
|
2083
|
+
const source = url.includes("userMark")
|
|
2084
|
+
? "userMark"
|
|
2085
|
+
: url.includes("actionLog/common.json")
|
|
2086
|
+
? "actionLog"
|
|
2087
|
+
: "favorite";
|
|
2088
|
+
this.favoriteActionEvents.push({ action, ts: requestTs, source, url });
|
|
2089
|
+
}
|
|
1750
2090
|
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
2091
|
+
handleNetworkWebSocketCreated(params) {
|
|
2092
|
+
const requestId = normalizeText(params?.requestId || "");
|
|
2093
|
+
if (!requestId) return;
|
|
2094
|
+
const url = normalizeText(params?.url || "");
|
|
2095
|
+
this.webSocketByRequestId.set(requestId, url || "");
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
handleNetworkWebSocketFrame(params, direction = "sent") {
|
|
2099
|
+
if (this.favoriteClickPendingSince <= 0) return;
|
|
2100
|
+
const ts = Date.now();
|
|
2101
|
+
if (ts < this.favoriteClickPendingSince) return;
|
|
2102
|
+
const requestId = normalizeText(params?.requestId || "");
|
|
2103
|
+
const payloadData = normalizeText(params?.response?.payloadData || "");
|
|
2104
|
+
const wsUrl = this.webSocketByRequestId.get(requestId) || "";
|
|
2105
|
+
this.recordFavoriteNetworkTrace({
|
|
2106
|
+
ts,
|
|
2107
|
+
kind: "ws",
|
|
2108
|
+
direction,
|
|
2109
|
+
url: wsUrl ? wsUrl.slice(0, 240) : requestId ? `ws:${requestId}` : "ws",
|
|
2110
|
+
payload: payloadData.slice(0, 200)
|
|
2111
|
+
});
|
|
2112
|
+
const action = parseFavoriteActionFromWsPayload(payloadData);
|
|
2113
|
+
if (!action) return;
|
|
2114
|
+
this.favoriteActionEvents.push({
|
|
2115
|
+
action,
|
|
2116
|
+
ts,
|
|
2117
|
+
source: `websocket_${direction}`,
|
|
2118
|
+
url: wsUrl || (requestId ? `ws:${requestId}` : "websocket")
|
|
2119
|
+
});
|
|
1762
2120
|
}
|
|
1763
2121
|
|
|
1764
2122
|
async handleNetworkLoadingFinished(params) {
|
|
@@ -1921,6 +2279,27 @@ class RecommendScreenCli {
|
|
|
1921
2279
|
} catch {}
|
|
1922
2280
|
});
|
|
1923
2281
|
}
|
|
2282
|
+
if (typeof this.Network.webSocketCreated === "function") {
|
|
2283
|
+
this.Network.webSocketCreated((params) => {
|
|
2284
|
+
try {
|
|
2285
|
+
this.handleNetworkWebSocketCreated(params);
|
|
2286
|
+
} catch {}
|
|
2287
|
+
});
|
|
2288
|
+
}
|
|
2289
|
+
if (typeof this.Network.webSocketFrameSent === "function") {
|
|
2290
|
+
this.Network.webSocketFrameSent((params) => {
|
|
2291
|
+
try {
|
|
2292
|
+
this.handleNetworkWebSocketFrame(params, "sent");
|
|
2293
|
+
} catch {}
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
if (typeof this.Network.webSocketFrameReceived === "function") {
|
|
2297
|
+
this.Network.webSocketFrameReceived((params) => {
|
|
2298
|
+
try {
|
|
2299
|
+
this.handleNetworkWebSocketFrame(params, "received");
|
|
2300
|
+
} catch {}
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
1924
2303
|
if (typeof this.Network.loadingFinished === "function") {
|
|
1925
2304
|
this.Network.loadingFinished((params) => {
|
|
1926
2305
|
this.handleNetworkLoadingFinished(params).catch(() => {});
|
|
@@ -2444,7 +2823,8 @@ class RecommendScreenCli {
|
|
|
2444
2823
|
async favoriteCandidate(options = {}) {
|
|
2445
2824
|
if (this.args.pageScope === "featured") {
|
|
2446
2825
|
if (options.alreadyInterested === true) {
|
|
2447
|
-
|
|
2826
|
+
log("[FAVORITE] network profile indicates alreadyInterested=true,跳过点击以避免误取消收藏。");
|
|
2827
|
+
return { actionTaken: "already_favorited", source: "network_profile" };
|
|
2448
2828
|
}
|
|
2449
2829
|
if (!this.featuredCalibration?.position) {
|
|
2450
2830
|
throw this.buildError(
|
|
@@ -2459,6 +2839,7 @@ class RecommendScreenCli {
|
|
|
2459
2839
|
|
|
2460
2840
|
const base = this.featuredCalibration.position;
|
|
2461
2841
|
const maxClicks = 5;
|
|
2842
|
+
let detectedAlreadyFavoritedByNetwork = false;
|
|
2462
2843
|
for (let clickIndex = 0; clickIndex < maxClicks; clickIndex += 1) {
|
|
2463
2844
|
const clickStartedAt = Date.now();
|
|
2464
2845
|
this.markFavoriteClickPending();
|
|
@@ -2474,22 +2855,38 @@ class RecommendScreenCli {
|
|
|
2474
2855
|
}
|
|
2475
2856
|
|
|
2476
2857
|
let sawDel = false;
|
|
2477
|
-
for (let index = 0; index <
|
|
2858
|
+
for (let index = 0; index < 14; index += 1) {
|
|
2478
2859
|
await sleep(humanDelay(260, 80));
|
|
2479
2860
|
const networkAction = this.consumeFavoriteActionResult(clickStartedAt);
|
|
2480
2861
|
if (networkAction === "add") {
|
|
2481
|
-
return
|
|
2862
|
+
return detectedAlreadyFavoritedByNetwork
|
|
2863
|
+
? { actionTaken: "already_favorited", re_favorited: true }
|
|
2864
|
+
: { actionTaken: "favorite" };
|
|
2482
2865
|
}
|
|
2483
2866
|
if (networkAction === "del") {
|
|
2867
|
+
detectedAlreadyFavoritedByNetwork = true;
|
|
2868
|
+
log("[FAVORITE] 检测到 network=del,推断该人选原本已收藏,继续点击恢复为收藏状态。");
|
|
2484
2869
|
sawDel = true;
|
|
2485
2870
|
break;
|
|
2486
2871
|
}
|
|
2487
2872
|
}
|
|
2488
2873
|
if (!sawDel && clickIndex === maxClicks - 1) {
|
|
2874
|
+
const traceSummary = this.summarizeFavoriteNetworkTrace(clickStartedAt);
|
|
2875
|
+
if (traceSummary.length > 0) {
|
|
2876
|
+
log(`[FAVORITE_NETWORK_TRACE] ${traceSummary.join(" | ")}`);
|
|
2877
|
+
} else {
|
|
2878
|
+
log("[FAVORITE_NETWORK_TRACE] 点击后未捕获到可识别的 HTTP/WS 网络信号。");
|
|
2879
|
+
}
|
|
2489
2880
|
break;
|
|
2490
2881
|
}
|
|
2491
2882
|
}
|
|
2492
2883
|
|
|
2884
|
+
if (detectedAlreadyFavoritedByNetwork) {
|
|
2885
|
+
throw this.buildError(
|
|
2886
|
+
"FAVORITE_BUTTON_FAILED",
|
|
2887
|
+
"精选页检测到 network del(原本已收藏),但后续未检测到 network add(恢复收藏)成功信号。"
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2493
2890
|
throw this.buildError("FAVORITE_BUTTON_FAILED", "精选页收藏未检测到 network add 成功信号。");
|
|
2494
2891
|
}
|
|
2495
2892
|
|
|
@@ -2700,6 +3097,10 @@ class RecommendScreenCli {
|
|
|
2700
3097
|
if (!this.args.baseUrl || !this.args.apiKey || !this.args.model) {
|
|
2701
3098
|
throw this.buildError("SCREEN_CONFIG_ERROR", "Missing baseUrl/apiKey/model", false);
|
|
2702
3099
|
}
|
|
3100
|
+
log(
|
|
3101
|
+
`[ARGS] page_scope=${this.args.pageScope} target_count=${this.args.targetCount ?? "none"} ` +
|
|
3102
|
+
`post_action=${this.args.postAction || "unset"} port=${this.args.port}`
|
|
3103
|
+
);
|
|
2703
3104
|
|
|
2704
3105
|
if (!(this.args.postActionConfirmed === true && this.args.postAction)) {
|
|
2705
3106
|
this.args.postAction = await promptPostAction();
|
|
@@ -2888,23 +3289,41 @@ class RecommendScreenCli {
|
|
|
2888
3289
|
effectiveAction = "favorite";
|
|
2889
3290
|
this.greetLimitFallbackCount += 1;
|
|
2890
3291
|
}
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
:
|
|
3292
|
+
let actionResult = { actionTaken: "none" };
|
|
3293
|
+
try {
|
|
3294
|
+
actionResult = effectiveAction === "favorite"
|
|
3295
|
+
? await this.favoriteCandidate({
|
|
3296
|
+
alreadyInterested: networkCandidateInfo?.alreadyInterested === true
|
|
3297
|
+
})
|
|
3298
|
+
: effectiveAction === "greet"
|
|
3299
|
+
? await this.greetCandidate()
|
|
3300
|
+
: { actionTaken: "none" };
|
|
3301
|
+
} catch (postActionError) {
|
|
3302
|
+
if (!isRecoverablePostActionError(postActionError, effectiveAction)) {
|
|
3303
|
+
throw postActionError;
|
|
3304
|
+
}
|
|
3305
|
+
log(`[POST_ACTION_WARN] ${effectiveAction} 失败,继续写入通过候选人: ${postActionError.message || postActionError}`);
|
|
3306
|
+
actionResult = {
|
|
3307
|
+
actionTaken: `${effectiveAction}_failed`,
|
|
3308
|
+
errorCode: postActionError.code || "POST_ACTION_FAILED",
|
|
3309
|
+
errorMessage: normalizeText(postActionError.message || "post action failed")
|
|
3310
|
+
};
|
|
3311
|
+
}
|
|
2898
3312
|
if (actionResult.actionTaken === "greet") {
|
|
2899
3313
|
this.greetCount += 1;
|
|
2900
3314
|
}
|
|
3315
|
+
const screeningReason = normalizeText(screening.reason || screening.summary || "");
|
|
3316
|
+
const actionErrorMessage = normalizeText(actionResult.errorMessage || "");
|
|
3317
|
+
const mergedReason = actionErrorMessage
|
|
3318
|
+
? `${screeningReason}${screeningReason ? " | " : ""}[${effectiveAction}失败] ${actionErrorMessage}`
|
|
3319
|
+
: screeningReason;
|
|
2901
3320
|
this.passedCandidates.push({
|
|
2902
3321
|
name: candidateProfile.name,
|
|
2903
3322
|
school: candidateProfile.school,
|
|
2904
3323
|
major: candidateProfile.major,
|
|
2905
3324
|
company: candidateProfile.company,
|
|
2906
3325
|
position: candidateProfile.position,
|
|
2907
|
-
reason:
|
|
3326
|
+
reason: mergedReason,
|
|
2908
3327
|
action: actionResult.actionTaken,
|
|
2909
3328
|
geekId: nextCandidate.geek_id,
|
|
2910
3329
|
summary: screening.summary,
|
|
@@ -3019,11 +3438,11 @@ async function main() {
|
|
|
3019
3438
|
const args = parseArgs(process.argv.slice(2));
|
|
3020
3439
|
if (args.help) {
|
|
3021
3440
|
console.log(JSON.stringify({
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3441
|
+
status: "COMPLETED",
|
|
3442
|
+
result: {
|
|
3443
|
+
usage: "node boss-recommend-screen-cli.cjs --criteria \"有 MCP 开发经验\" --post-action <favorite|greet|none> --max-greet-count 10 --post-action-confirmed true --baseurl <url> --apikey <key> --model <model> --page-scope recommend|latest|featured --calibration <favorite-calibration.json> --port 9222 --output <csv-path> --checkpoint-path <checkpoint.json> --pause-control-path <pause-control.json> [--resume]"
|
|
3444
|
+
}
|
|
3445
|
+
}));
|
|
3027
3446
|
return;
|
|
3028
3447
|
}
|
|
3029
3448
|
|
|
@@ -3060,8 +3479,13 @@ if (require.main === module) {
|
|
|
3060
3479
|
__testables: {
|
|
3061
3480
|
MAX_CONSECUTIVE_RESUME_CAPTURE_FAILURES,
|
|
3062
3481
|
RESUME_CAPTURE_MAX_ATTEMPTS,
|
|
3063
|
-
RESUME_CAPTURE_WAIT_MS
|
|
3482
|
+
RESUME_CAPTURE_WAIT_MS,
|
|
3483
|
+
parseFavoriteActionFromPostData,
|
|
3484
|
+
parseFavoriteActionFromRequest,
|
|
3485
|
+
parseFavoriteActionFromKnownRequest,
|
|
3486
|
+
parseFavoriteActionFromActionLog,
|
|
3487
|
+
parseFavoriteActionFromWsPayload,
|
|
3488
|
+
isRecoverablePostActionError
|
|
3064
3489
|
}
|
|
3065
3490
|
};
|
|
3066
3491
|
}
|
|
3067
|
-
|