@reconcrap/boss-recommend-mcp 2.0.55 → 2.0.57
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 +2 -1
- package/src/chat-mcp.js +397 -3
- package/src/core/browser/index.js +1 -0
- package/src/core/self-heal/viewport.js +1 -1
- package/src/detached-worker.js +99 -0
- package/src/domains/chat/run-service.js +8 -6
- package/src/domains/recommend/constants.js +17 -0
- package/src/domains/recommend/detail.js +355 -63
- package/src/domains/recommend/run-service.js +15 -0
- package/src/domains/recruit/run-service.js +2 -0
- package/src/index.js +35 -2
- package/src/recruit-mcp.js +534 -10
|
@@ -3,12 +3,13 @@ import {
|
|
|
3
3
|
clickPoint,
|
|
4
4
|
DETERMINISTIC_CLICK_OPTIONS,
|
|
5
5
|
getFrameDocumentNodeId,
|
|
6
|
-
getNodeBox,
|
|
7
|
-
getOuterHTML,
|
|
8
|
-
pressKey,
|
|
9
|
-
querySelectorAll,
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
getNodeBox,
|
|
7
|
+
getOuterHTML,
|
|
8
|
+
pressKey,
|
|
9
|
+
querySelectorAll,
|
|
10
|
+
scrollNodeIntoView,
|
|
11
|
+
sleep
|
|
12
|
+
} from "../../core/browser/index.js";
|
|
12
13
|
import { candidateKeyFromProfile } from "../../core/infinite-list/index.js";
|
|
13
14
|
import {
|
|
14
15
|
buildScreeningCandidateFromDetail,
|
|
@@ -18,12 +19,14 @@ import {
|
|
|
18
19
|
closeBossAccountRightsBlockingPanel,
|
|
19
20
|
findBossAccountRightsBlockingPanel
|
|
20
21
|
} from "../common/account-rights-panel.js";
|
|
21
|
-
import {
|
|
22
|
-
DETAIL_CLOSE_SELECTORS,
|
|
23
|
-
DETAIL_NETWORK_PATTERNS,
|
|
24
|
-
DETAIL_POPUP_SELECTORS,
|
|
25
|
-
DETAIL_RESUME_IFRAME_SELECTORS
|
|
26
|
-
|
|
22
|
+
import {
|
|
23
|
+
DETAIL_CLOSE_SELECTORS,
|
|
24
|
+
DETAIL_NETWORK_PATTERNS,
|
|
25
|
+
DETAIL_POPUP_SELECTORS,
|
|
26
|
+
DETAIL_RESUME_IFRAME_SELECTORS,
|
|
27
|
+
RECOMMEND_AVATAR_PREVIEW_CLOSE_SELECTORS,
|
|
28
|
+
RECOMMEND_AVATAR_PREVIEW_SELECTORS
|
|
29
|
+
} from "./constants.js";
|
|
27
30
|
import {
|
|
28
31
|
getRecommendRoots
|
|
29
32
|
} from "./roots.js";
|
|
@@ -93,6 +96,15 @@ export async function closeRecommendBlockingPanels(client, options = {}) {
|
|
|
93
96
|
});
|
|
94
97
|
}
|
|
95
98
|
|
|
99
|
+
function looksLikeRecommendAvatarPreviewHtml(html = "") {
|
|
100
|
+
return /\bavatar-preview\b|\bfigure-preview\b/i.test(String(html || ""));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function isRecommendAvatarPreviewOpenError(error) {
|
|
104
|
+
return error?.code === "RECOMMEND_AVATAR_PREVIEW_OPENED"
|
|
105
|
+
|| /RECOMMEND_AVATAR_PREVIEW_OPENED/i.test(String(error?.message || error || ""));
|
|
106
|
+
}
|
|
107
|
+
|
|
96
108
|
export async function waitForRecommendDetailNetworkEvents(recorder, {
|
|
97
109
|
minCount = 1,
|
|
98
110
|
requireLoaded = true,
|
|
@@ -163,10 +175,10 @@ export async function waitForRecommendDetail(client, {
|
|
|
163
175
|
return lastState;
|
|
164
176
|
}
|
|
165
177
|
|
|
166
|
-
async function readRecommendDetailState(client) {
|
|
167
|
-
const rootState = await getRecommendRoots(client);
|
|
168
|
-
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
169
|
-
const resumeIframe = await findVisibleDetailTarget(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
178
|
+
async function readRecommendDetailState(client) {
|
|
179
|
+
const rootState = await getRecommendRoots(client);
|
|
180
|
+
const popup = await findVisibleDetailTarget(client, rootState.roots, DETAIL_POPUP_SELECTORS);
|
|
181
|
+
const resumeIframe = await findVisibleDetailTarget(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
|
|
170
182
|
return {
|
|
171
183
|
iframe: rootState.iframe,
|
|
172
184
|
roots: rootState.roots,
|
|
@@ -199,6 +211,46 @@ export async function waitForRecommendDetailClosed(client, {
|
|
|
199
211
|
};
|
|
200
212
|
}
|
|
201
213
|
|
|
214
|
+
export async function readRecommendAvatarPreviewState(client) {
|
|
215
|
+
const rootState = await getRecommendRoots(client);
|
|
216
|
+
const topRoot = rootState.rootNodes?.top
|
|
217
|
+
? [{ name: "top", nodeId: rootState.rootNodes.top }]
|
|
218
|
+
: rootState.roots.filter((root) => root?.name === "top");
|
|
219
|
+
const preview = await findVisibleDetailTarget(client, topRoot, RECOMMEND_AVATAR_PREVIEW_SELECTORS, {
|
|
220
|
+
includeAvatarPreview: true
|
|
221
|
+
});
|
|
222
|
+
return {
|
|
223
|
+
open: Boolean(preview),
|
|
224
|
+
root: rootState.topRoot,
|
|
225
|
+
roots: topRoot,
|
|
226
|
+
preview
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export async function waitForRecommendAvatarPreviewClosed(client, {
|
|
231
|
+
timeoutMs = 1200,
|
|
232
|
+
intervalMs = 120
|
|
233
|
+
} = {}) {
|
|
234
|
+
const started = Date.now();
|
|
235
|
+
let state = null;
|
|
236
|
+
while (Date.now() - started <= timeoutMs) {
|
|
237
|
+
state = await readRecommendAvatarPreviewState(client);
|
|
238
|
+
if (!state.open) {
|
|
239
|
+
return {
|
|
240
|
+
closed: true,
|
|
241
|
+
elapsed_ms: Date.now() - started,
|
|
242
|
+
state
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
await sleep(intervalMs);
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
closed: false,
|
|
249
|
+
elapsed_ms: Date.now() - started,
|
|
250
|
+
state
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
202
254
|
function compactRect(rect) {
|
|
203
255
|
if (!rect) return null;
|
|
204
256
|
return {
|
|
@@ -254,18 +306,28 @@ async function verifyRecommendDetailStillOpen(client, {
|
|
|
254
306
|
};
|
|
255
307
|
}
|
|
256
308
|
|
|
257
|
-
async function findVisibleDetailTarget(client, roots, selectors
|
|
309
|
+
async function findVisibleDetailTarget(client, roots, selectors, {
|
|
310
|
+
includeAvatarPreview = false
|
|
311
|
+
} = {}) {
|
|
258
312
|
for (const root of roots) {
|
|
259
313
|
if (!root?.nodeId) continue;
|
|
260
|
-
for (const selector of selectors) {
|
|
261
|
-
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
262
|
-
for (const nodeId of nodeIds) {
|
|
263
|
-
try {
|
|
264
|
-
const box = await getNodeBox(client, nodeId);
|
|
265
|
-
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
314
|
+
for (const selector of selectors) {
|
|
315
|
+
const nodeIds = await querySelectorAll(client, root.nodeId, selector);
|
|
316
|
+
for (const nodeId of nodeIds) {
|
|
317
|
+
try {
|
|
318
|
+
const box = await getNodeBox(client, nodeId);
|
|
319
|
+
if (box.rect.width > 2 && box.rect.height > 2) {
|
|
320
|
+
if (!includeAvatarPreview) {
|
|
321
|
+
try {
|
|
322
|
+
const html = await getOuterHTML(client, nodeId);
|
|
323
|
+
if (looksLikeRecommendAvatarPreviewHtml(html)) continue;
|
|
324
|
+
} catch {
|
|
325
|
+
// If the node went stale, let the next candidate decide.
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
root: root.name,
|
|
330
|
+
root_node_id: root.nodeId,
|
|
269
331
|
selector,
|
|
270
332
|
node_id: nodeId,
|
|
271
333
|
center: box.center,
|
|
@@ -332,7 +394,104 @@ export function isStaleRecommendNodeError(error) {
|
|
|
332
394
|
|
|
333
395
|
export function isRecommendDetailOpenMissError(error) {
|
|
334
396
|
const message = String(error?.message || error || "");
|
|
335
|
-
return
|
|
397
|
+
return isRecommendAvatarPreviewOpenError(error)
|
|
398
|
+
|| /Candidate detail did not open|no known detail selectors mounted/i.test(message);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export function resolveRecommendCardDetailClickPoint(cardBox, {
|
|
402
|
+
attemptIndex = 0
|
|
403
|
+
} = {}) {
|
|
404
|
+
const rect = cardBox?.rect || {};
|
|
405
|
+
const width = Number(rect.width) || 0;
|
|
406
|
+
const height = Number(rect.height) || 0;
|
|
407
|
+
if (width <= 2 || height <= 2) {
|
|
408
|
+
return {
|
|
409
|
+
...(cardBox?.center || { x: 0, y: 0 }),
|
|
410
|
+
mode: "card-center-fallback",
|
|
411
|
+
reason: "invalid_card_rect"
|
|
412
|
+
};
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const xFractions = [0.22, 0.50, 0.72];
|
|
416
|
+
const xFraction = xFractions[Math.min(Math.max(0, attemptIndex), xFractions.length - 1)];
|
|
417
|
+
const minOffsetX = Math.min(width - 12, Math.max(110, Math.min(180, width * 0.18)));
|
|
418
|
+
const maxOffsetX = Math.max(minOffsetX, width - Math.min(220, Math.max(90, width * 0.22)));
|
|
419
|
+
const rawOffsetX = width * xFraction;
|
|
420
|
+
const offsetX = clampPointCoordinate(rawOffsetX, minOffsetX, maxOffsetX);
|
|
421
|
+
const offsetY = clampPointCoordinate(height * 0.28, Math.min(34, height / 2), Math.max(36, height - 28));
|
|
422
|
+
return {
|
|
423
|
+
x: rect.x + offsetX,
|
|
424
|
+
y: rect.y + offsetY,
|
|
425
|
+
mode: "card-body-safe-point",
|
|
426
|
+
attempt_index: attemptIndex,
|
|
427
|
+
offset_x: Math.round(offsetX),
|
|
428
|
+
offset_y: Math.round(offsetY)
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
async function clickRecommendCardDetailPoint(client, nodeId, {
|
|
433
|
+
scrollIntoView = true,
|
|
434
|
+
attemptIndex = 0
|
|
435
|
+
} = {}) {
|
|
436
|
+
if (scrollIntoView) {
|
|
437
|
+
try {
|
|
438
|
+
await scrollNodeIntoView(client, nodeId);
|
|
439
|
+
await sleep(150);
|
|
440
|
+
} catch {
|
|
441
|
+
// Recommend list cards are selected from visible nodes; if this CDP
|
|
442
|
+
// helper races the virtual list, let the box lookup/retry decide.
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const box = await getNodeBox(client, nodeId);
|
|
446
|
+
const clickTarget = resolveRecommendCardDetailClickPoint(box, { attemptIndex });
|
|
447
|
+
const clickResult = await clickPoint(client, clickTarget.x, clickTarget.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
448
|
+
return {
|
|
449
|
+
...box,
|
|
450
|
+
click_target: clickTarget,
|
|
451
|
+
click_result: clickResult
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function waitForRecommendDetailOpenOutcome(client, {
|
|
456
|
+
timeoutMs = 10000,
|
|
457
|
+
intervalMs = 250
|
|
458
|
+
} = {}) {
|
|
459
|
+
const started = Date.now();
|
|
460
|
+
let detailState = null;
|
|
461
|
+
let avatarPreview = null;
|
|
462
|
+
while (Date.now() - started <= timeoutMs) {
|
|
463
|
+
detailState = await readRecommendDetailState(client);
|
|
464
|
+
if (detailState?.popup || detailState?.resumeIframe) {
|
|
465
|
+
return {
|
|
466
|
+
kind: "detail",
|
|
467
|
+
elapsed_ms: Date.now() - started,
|
|
468
|
+
detail_state: detailState
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
avatarPreview = await readRecommendAvatarPreviewState(client);
|
|
472
|
+
if (avatarPreview.open) {
|
|
473
|
+
return {
|
|
474
|
+
kind: "avatar_preview",
|
|
475
|
+
elapsed_ms: Date.now() - started,
|
|
476
|
+
avatar_preview: avatarPreview
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
await sleep(intervalMs);
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
kind: "none",
|
|
483
|
+
elapsed_ms: Date.now() - started,
|
|
484
|
+
detail_state: detailState,
|
|
485
|
+
avatar_preview: avatarPreview
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function makeRecommendAvatarPreviewOpenedError(outcome, clickAttempts = []) {
|
|
490
|
+
const error = new Error("RECOMMEND_AVATAR_PREVIEW_OPENED: candidate avatar preview opened instead of resume detail");
|
|
491
|
+
error.code = "RECOMMEND_AVATAR_PREVIEW_OPENED";
|
|
492
|
+
error.avatar_preview = outcome?.avatar_preview || null;
|
|
493
|
+
error.click_attempts = clickAttempts;
|
|
494
|
+
return error;
|
|
336
495
|
}
|
|
337
496
|
|
|
338
497
|
export async function findRecommendCardNodeForCandidateKey(client, {
|
|
@@ -365,8 +524,17 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
365
524
|
};
|
|
366
525
|
}
|
|
367
526
|
|
|
368
|
-
|
|
369
|
-
|
|
527
|
+
let nodeIds = [];
|
|
528
|
+
try {
|
|
529
|
+
nodeIds = await findRecommendCardNodeIds(client, frameNodeId);
|
|
530
|
+
} catch (error) {
|
|
531
|
+
lastError = error;
|
|
532
|
+
if (!isStaleRecommendNodeError(error)) throw error;
|
|
533
|
+
rootState = null;
|
|
534
|
+
if (intervalMs > 0) await sleep(intervalMs);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
lastCardCount = nodeIds.length;
|
|
370
538
|
for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
|
|
371
539
|
const nodeId = nodeIds[visibleIndex];
|
|
372
540
|
try {
|
|
@@ -412,35 +580,71 @@ export async function findRecommendCardNodeForCandidateKey(client, {
|
|
|
412
580
|
};
|
|
413
581
|
}
|
|
414
582
|
|
|
415
|
-
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
416
|
-
timeoutMs = 12000,
|
|
417
|
-
scrollIntoView = true
|
|
418
|
-
} = {}) {
|
|
419
|
-
const started = Date.now();
|
|
420
|
-
const
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
583
|
+
export async function openRecommendCardDetail(client, cardNodeId, {
|
|
584
|
+
timeoutMs = 12000,
|
|
585
|
+
scrollIntoView = true
|
|
586
|
+
} = {}) {
|
|
587
|
+
const started = Date.now();
|
|
588
|
+
const clickAttempts = [];
|
|
589
|
+
const maxClickAttempts = 3;
|
|
590
|
+
let lastOutcome = null;
|
|
591
|
+
let lastCardBox = null;
|
|
592
|
+
let candidateClickMs = 0;
|
|
593
|
+
let detailOpenMs = 0;
|
|
594
|
+
|
|
595
|
+
for (let attemptIndex = 0; attemptIndex < maxClickAttempts; attemptIndex += 1) {
|
|
596
|
+
const clickStarted = Date.now();
|
|
597
|
+
lastCardBox = await clickRecommendCardDetailPoint(client, cardNodeId, {
|
|
598
|
+
scrollIntoView: attemptIndex === 0 ? scrollIntoView : false,
|
|
599
|
+
attemptIndex
|
|
600
|
+
});
|
|
601
|
+
candidateClickMs += Date.now() - clickStarted;
|
|
602
|
+
const detailStarted = Date.now();
|
|
603
|
+
lastOutcome = await waitForRecommendDetailOpenOutcome(client, {
|
|
604
|
+
timeoutMs: attemptIndex === 0 ? timeoutMs : Math.max(2500, Math.floor(timeoutMs / 3)),
|
|
605
|
+
intervalMs: 250
|
|
606
|
+
});
|
|
607
|
+
detailOpenMs += Date.now() - detailStarted;
|
|
608
|
+
clickAttempts.push({
|
|
609
|
+
attempt: attemptIndex + 1,
|
|
610
|
+
click_target: lastCardBox.click_target,
|
|
611
|
+
click_result: lastCardBox.click_result,
|
|
612
|
+
outcome: lastOutcome.kind,
|
|
613
|
+
elapsed_ms: lastOutcome.elapsed_ms
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
if (lastOutcome.kind === "detail") {
|
|
617
|
+
return {
|
|
618
|
+
card_box: lastCardBox,
|
|
619
|
+
click_attempts: clickAttempts,
|
|
620
|
+
detail_state: lastOutcome.detail_state,
|
|
621
|
+
timings: {
|
|
622
|
+
candidate_click_ms: candidateClickMs,
|
|
623
|
+
detail_open_ms: detailOpenMs,
|
|
624
|
+
open_total_ms: Date.now() - started
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if (lastOutcome.kind === "avatar_preview") {
|
|
630
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2, waitMs: 350 });
|
|
631
|
+
throw makeRecommendAvatarPreviewOpenedError(lastOutcome, clickAttempts);
|
|
632
|
+
}
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (lastOutcome?.kind === "avatar_preview") {
|
|
637
|
+
throw makeRecommendAvatarPreviewOpenedError(lastOutcome, clickAttempts);
|
|
638
|
+
}
|
|
639
|
+
const error = new Error("Candidate detail did not open or no known detail selectors mounted");
|
|
640
|
+
error.click_attempts = clickAttempts;
|
|
641
|
+
error.last_open_outcome = lastOutcome;
|
|
642
|
+
throw error;
|
|
643
|
+
}
|
|
440
644
|
|
|
441
|
-
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
442
|
-
cardNodeId,
|
|
443
|
-
candidateKey = "",
|
|
645
|
+
export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
646
|
+
cardNodeId,
|
|
647
|
+
candidateKey = "",
|
|
444
648
|
cardCandidate = null,
|
|
445
649
|
rootState = null,
|
|
446
650
|
targetUrl = "",
|
|
@@ -507,12 +711,100 @@ export async function openRecommendCardDetailWithFreshRetry(client, {
|
|
|
507
711
|
currentRootState = resolved.root_state || null;
|
|
508
712
|
}
|
|
509
713
|
}
|
|
510
|
-
|
|
511
|
-
throw new Error("Recommend detail retry exhausted");
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
export async function
|
|
515
|
-
attemptsLimit =
|
|
714
|
+
|
|
715
|
+
throw new Error("Recommend detail retry exhausted");
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export async function closeRecommendAvatarPreview(client, {
|
|
719
|
+
attemptsLimit = 2,
|
|
720
|
+
waitMs = 500
|
|
721
|
+
} = {}) {
|
|
722
|
+
const attempts = [];
|
|
723
|
+
for (let index = 0; index < attemptsLimit; index += 1) {
|
|
724
|
+
const state = await readRecommendAvatarPreviewState(client);
|
|
725
|
+
if (!state.open) {
|
|
726
|
+
return {
|
|
727
|
+
closed: true,
|
|
728
|
+
already_closed: true,
|
|
729
|
+
attempts
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
const closeTarget = await findVisibleCloseTarget(client, state.roots, RECOMMEND_AVATAR_PREVIEW_CLOSE_SELECTORS);
|
|
734
|
+
if (closeTarget) {
|
|
735
|
+
try {
|
|
736
|
+
if (closeTarget.center) {
|
|
737
|
+
await clickPoint(client, closeTarget.center.x, closeTarget.center.y, DETERMINISTIC_CLICK_OPTIONS);
|
|
738
|
+
} else {
|
|
739
|
+
await clickNodeCenter(client, closeTarget.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
740
|
+
}
|
|
741
|
+
attempts.push({
|
|
742
|
+
mode: "avatar-preview-close-selector",
|
|
743
|
+
selector: closeTarget.selector,
|
|
744
|
+
root: closeTarget.root
|
|
745
|
+
});
|
|
746
|
+
} catch (error) {
|
|
747
|
+
attempts.push({
|
|
748
|
+
mode: "avatar-preview-close-selector-error",
|
|
749
|
+
selector: closeTarget.selector,
|
|
750
|
+
root: closeTarget.root,
|
|
751
|
+
error: error?.message || String(error)
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
} else {
|
|
755
|
+
await pressEscape(client);
|
|
756
|
+
attempts.push({ mode: "avatar-preview-Escape" });
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const closed = await waitForRecommendAvatarPreviewClosed(client, {
|
|
760
|
+
timeoutMs: waitMs,
|
|
761
|
+
intervalMs: 100
|
|
762
|
+
});
|
|
763
|
+
attempts.push({
|
|
764
|
+
mode: "wait-avatar-preview-closed",
|
|
765
|
+
closed: closed.closed,
|
|
766
|
+
elapsed_ms: closed.elapsed_ms
|
|
767
|
+
});
|
|
768
|
+
if (closed.closed) {
|
|
769
|
+
return {
|
|
770
|
+
closed: true,
|
|
771
|
+
already_closed: false,
|
|
772
|
+
attempts
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
await pressEscape(client);
|
|
777
|
+
attempts.push({ mode: "avatar-preview-Escape-fallback" });
|
|
778
|
+
const closedAfterEscape = await waitForRecommendAvatarPreviewClosed(client, {
|
|
779
|
+
timeoutMs: waitMs,
|
|
780
|
+
intervalMs: 100
|
|
781
|
+
});
|
|
782
|
+
attempts.push({
|
|
783
|
+
mode: "wait-avatar-preview-closed-after-escape",
|
|
784
|
+
closed: closedAfterEscape.closed,
|
|
785
|
+
elapsed_ms: closedAfterEscape.elapsed_ms
|
|
786
|
+
});
|
|
787
|
+
if (closedAfterEscape.closed) {
|
|
788
|
+
return {
|
|
789
|
+
closed: true,
|
|
790
|
+
already_closed: false,
|
|
791
|
+
attempts
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
const state = await readRecommendAvatarPreviewState(client);
|
|
797
|
+
return {
|
|
798
|
+
closed: !state.open,
|
|
799
|
+
already_closed: false,
|
|
800
|
+
reason: state.open ? "avatar_preview_still_visible_after_close_attempts" : null,
|
|
801
|
+
attempts,
|
|
802
|
+
state
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export async function closeRecommendDetail(client, {
|
|
807
|
+
attemptsLimit = 4,
|
|
516
808
|
closeWaitMs = 5000,
|
|
517
809
|
escapeWaitMs = 3500
|
|
518
810
|
} = {}) {
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
} from "../../core/screening/index.js";
|
|
48
48
|
import {
|
|
49
49
|
closeRecommendBlockingPanels,
|
|
50
|
+
closeRecommendAvatarPreview,
|
|
50
51
|
closeRecommendDetail,
|
|
51
52
|
createRecommendDetailNetworkRecorder,
|
|
52
53
|
extractRecommendDetailCandidate,
|
|
@@ -506,6 +507,16 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
506
507
|
if (Array.isArray(error.recommend_detail_open_attempts)) {
|
|
507
508
|
result.recommend_detail_open_attempts = error.recommend_detail_open_attempts;
|
|
508
509
|
}
|
|
510
|
+
if (Array.isArray(error.click_attempts)) {
|
|
511
|
+
result.click_attempts = error.click_attempts;
|
|
512
|
+
}
|
|
513
|
+
if (error.avatar_preview) {
|
|
514
|
+
result.avatar_preview = {
|
|
515
|
+
open: Boolean(error.avatar_preview.open),
|
|
516
|
+
selector: error.avatar_preview.preview?.selector || null,
|
|
517
|
+
rect: error.avatar_preview.preview?.rect || null
|
|
518
|
+
};
|
|
519
|
+
}
|
|
509
520
|
return result;
|
|
510
521
|
}
|
|
511
522
|
|
|
@@ -907,6 +918,7 @@ export async function runRecommendWorkflow({
|
|
|
907
918
|
|
|
908
919
|
runControl.setPhase("recommend:cleanup");
|
|
909
920
|
await closeRecommendDetail(client, { attemptsLimit: 2 });
|
|
921
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 });
|
|
910
922
|
await closeRecommendBlockingPanelsForRun("cleanup");
|
|
911
923
|
|
|
912
924
|
await runControl.waitIfPaused();
|
|
@@ -1247,6 +1259,7 @@ export async function runRecommendWorkflow({
|
|
|
1247
1259
|
timings.image_capture_recovery_trigger = compactError(error, "IMAGE_CAPTURE_FAILED");
|
|
1248
1260
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1249
1261
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1262
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1250
1263
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1251
1264
|
await recoverAndReapplyRecommendContext(`image_capture:${detailStep}`, error, {
|
|
1252
1265
|
forceRecentNotView: true
|
|
@@ -1299,6 +1312,7 @@ export async function runRecommendWorkflow({
|
|
|
1299
1312
|
timings.detail_recovery_trigger = compactRecoverableDetailError(error);
|
|
1300
1313
|
checkpointInProgressCandidate({ index, candidateKey, cardNodeId, detailStep, error });
|
|
1301
1314
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1315
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1302
1316
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1303
1317
|
await recoverAndReapplyRecommendContext(`detail:${detailStep}`, error, {
|
|
1304
1318
|
forceRecentNotView: true
|
|
@@ -1309,6 +1323,7 @@ export async function runRecommendWorkflow({
|
|
|
1309
1323
|
detailResult = null;
|
|
1310
1324
|
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
1311
1325
|
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1326
|
+
await closeRecommendAvatarPreview(client, { attemptsLimit: 2 }).catch(() => null);
|
|
1312
1327
|
await closeRecommendBlockingPanels(client, { attemptsLimit: 2, rootState }).catch(() => null);
|
|
1313
1328
|
}
|
|
1314
1329
|
}
|
|
@@ -1146,6 +1146,7 @@ export function createRecruitRunService({
|
|
|
1146
1146
|
const manager = lifecycle || createRunLifecycleManager({ idPrefix, onSnapshot });
|
|
1147
1147
|
|
|
1148
1148
|
function startRecruitRun({
|
|
1149
|
+
runId = "",
|
|
1149
1150
|
client,
|
|
1150
1151
|
targetUrl = "",
|
|
1151
1152
|
criteria = "",
|
|
@@ -1189,6 +1190,7 @@ export function createRecruitRunService({
|
|
|
1189
1190
|
});
|
|
1190
1191
|
const effectiveHumanRestEnabled = effectiveHumanBehavior.restEnabled;
|
|
1191
1192
|
return manager.startRun({
|
|
1193
|
+
runId,
|
|
1192
1194
|
name,
|
|
1193
1195
|
context: {
|
|
1194
1196
|
domain: "recruit",
|
package/src/index.js
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
pauseBossChatRunTool,
|
|
22
22
|
prepareBossChatRunTool,
|
|
23
23
|
resumeBossChatRunTool,
|
|
24
|
+
startBossChatDetachedRunTool,
|
|
24
25
|
startBossChatRunTool
|
|
25
26
|
} from "./chat-mcp.js";
|
|
26
27
|
import {
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
pauseRecruitPipelineRunTool,
|
|
35
36
|
resumeRecruitPipelineRunTool,
|
|
36
37
|
runRecruitPipelineTool,
|
|
38
|
+
startRecruitPipelineDetachedRunTool,
|
|
37
39
|
startRecruitPipelineRunTool,
|
|
38
40
|
validateRecruitPipelineArgs
|
|
39
41
|
} from "./recruit-mcp.js";
|
|
@@ -135,6 +137,8 @@ const recommendTargetUrl = "https://www.zhipin.com/web/chat/recommend";
|
|
|
135
137
|
let runPipelineImpl = null;
|
|
136
138
|
let runSelfHealImpl = null;
|
|
137
139
|
let spawnProcessImpl = spawn;
|
|
140
|
+
let forceChatInProcForTests = false;
|
|
141
|
+
let forceRecruitInProcForTests = false;
|
|
138
142
|
const TERMINAL_RUN_STATES = new Set([RUN_STATE_COMPLETED, RUN_STATE_FAILED, RUN_STATE_CANCELED]);
|
|
139
143
|
|
|
140
144
|
async function getRunPipelineImpl() {
|
|
@@ -185,6 +189,20 @@ function shouldStartRecommendDetached({ workspaceRoot = "" } = {}) {
|
|
|
185
189
|
return isLikelyAgentRuntime({ workspaceRoot });
|
|
186
190
|
}
|
|
187
191
|
|
|
192
|
+
function shouldStartChatDetached({ workspaceRoot = "" } = {}) {
|
|
193
|
+
if (forceChatInProcForTests) return false;
|
|
194
|
+
if (normalizeText(process.env.BOSS_CHAT_CDP_INPROC || "") === "1") return false;
|
|
195
|
+
if (normalizeText(process.env.BOSS_CHAT_CDP_DETACHED || "") === "1") return true;
|
|
196
|
+
return isLikelyAgentRuntime({ workspaceRoot });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function shouldStartRecruitDetached({ workspaceRoot = "" } = {}) {
|
|
200
|
+
if (forceRecruitInProcForTests) return false;
|
|
201
|
+
if (normalizeText(process.env.BOSS_RECRUIT_CDP_INPROC || "") === "1") return false;
|
|
202
|
+
if (normalizeText(process.env.BOSS_RECRUIT_CDP_DETACHED || "") === "1") return true;
|
|
203
|
+
return isLikelyAgentRuntime({ workspaceRoot });
|
|
204
|
+
}
|
|
205
|
+
|
|
188
206
|
function isUnlimitedTargetCountToken(value) {
|
|
189
207
|
const token = normalizeText(value).toLowerCase();
|
|
190
208
|
if (!token) return false;
|
|
@@ -2468,6 +2486,9 @@ async function handleBossChatPrepareRunTool({ workspaceRoot, args }) {
|
|
|
2468
2486
|
}
|
|
2469
2487
|
|
|
2470
2488
|
async function handleBossChatStartRunTool({ workspaceRoot, args }) {
|
|
2489
|
+
if (shouldStartChatDetached({ workspaceRoot })) {
|
|
2490
|
+
return startBossChatDetachedRunTool({ workspaceRoot, args });
|
|
2491
|
+
}
|
|
2471
2492
|
return startBossChatRunTool({ workspaceRoot, args });
|
|
2472
2493
|
}
|
|
2473
2494
|
|
|
@@ -2618,9 +2639,15 @@ async function handleRequest(message, workspaceRoot) {
|
|
|
2618
2639
|
} else if (toolName === TOOL_BOSS_CHAT_CANCEL_RUN) {
|
|
2619
2640
|
payload = await handleBossChatCancelRunTool({ workspaceRoot, args });
|
|
2620
2641
|
} else if (toolName === TOOL_RUN_RECRUIT_PIPELINE) {
|
|
2621
|
-
payload =
|
|
2642
|
+
payload = normalizeText(args.execution_mode || "").toLowerCase() === "sync"
|
|
2643
|
+
? await runRecruitPipelineTool({ workspaceRoot, args })
|
|
2644
|
+
: shouldStartRecruitDetached({ workspaceRoot })
|
|
2645
|
+
? await startRecruitPipelineDetachedRunTool({ workspaceRoot, args })
|
|
2646
|
+
: await runRecruitPipelineTool({ workspaceRoot, args });
|
|
2622
2647
|
} else if (toolName === TOOL_START_RECRUIT_PIPELINE_RUN) {
|
|
2623
|
-
payload =
|
|
2648
|
+
payload = shouldStartRecruitDetached({ workspaceRoot })
|
|
2649
|
+
? await startRecruitPipelineDetachedRunTool({ workspaceRoot, args })
|
|
2650
|
+
: await startRecruitPipelineRunTool({ workspaceRoot, args });
|
|
2624
2651
|
} else if (toolName === TOOL_GET_RECRUIT_PIPELINE_RUN) {
|
|
2625
2652
|
payload = getRecruitPipelineRunTool({ workspaceRoot, args });
|
|
2626
2653
|
} else if (toolName === TOOL_CANCEL_RECRUIT_PIPELINE_RUN) {
|
|
@@ -2781,24 +2808,30 @@ export const __testables = {
|
|
|
2781
2808
|
__resetRecommendMcpStateForTests();
|
|
2782
2809
|
},
|
|
2783
2810
|
setChatMcpConnectorForTests(nextImpl) {
|
|
2811
|
+
forceChatInProcForTests = typeof nextImpl === "function";
|
|
2784
2812
|
__setChatMcpConnectorForTests(nextImpl);
|
|
2785
2813
|
},
|
|
2786
2814
|
setChatMcpJobReaderForTests(nextImpl) {
|
|
2787
2815
|
__setChatMcpJobReaderForTests(nextImpl);
|
|
2788
2816
|
},
|
|
2789
2817
|
setChatMcpWorkflowForTests(nextImpl) {
|
|
2818
|
+
forceChatInProcForTests = typeof nextImpl === "function";
|
|
2790
2819
|
__setChatMcpWorkflowForTests(nextImpl);
|
|
2791
2820
|
},
|
|
2792
2821
|
resetChatMcpStateForTests() {
|
|
2822
|
+
forceChatInProcForTests = false;
|
|
2793
2823
|
__resetChatMcpStateForTests();
|
|
2794
2824
|
},
|
|
2795
2825
|
setRecruitMcpConnectorForTests(nextImpl) {
|
|
2826
|
+
forceRecruitInProcForTests = typeof nextImpl === "function";
|
|
2796
2827
|
__setRecruitMcpConnectorForTests(nextImpl);
|
|
2797
2828
|
},
|
|
2798
2829
|
setRecruitMcpWorkflowForTests(nextImpl) {
|
|
2830
|
+
forceRecruitInProcForTests = typeof nextImpl === "function";
|
|
2799
2831
|
__setRecruitMcpWorkflowForTests(nextImpl);
|
|
2800
2832
|
},
|
|
2801
2833
|
resetRecruitMcpStateForTests() {
|
|
2834
|
+
forceRecruitInProcForTests = false;
|
|
2802
2835
|
__resetRecruitMcpStateForTests();
|
|
2803
2836
|
}
|
|
2804
2837
|
};
|