@reconcrap/boss-recommend-mcp 2.0.3 → 2.0.5
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 +3 -3
- package/config/screening-config.example.json +10 -8
- package/package.json +1 -1
- package/src/chat-mcp.js +18 -10
- package/src/chat-runtime-config.js +28 -1
- package/src/core/browser/index.js +1 -0
- package/src/core/screening/index.js +3 -3
- package/src/core/self-heal/index.js +128 -3
- package/src/core/self-heal/viewport.js +564 -0
- package/src/domains/chat/run-service.js +52 -8
- package/src/domains/recommend/roots.js +4 -2
- package/src/domains/recommend/run-service.js +37 -3
- package/src/domains/recruit/roots.js +2 -1
- package/src/domains/recruit/run-service.js +34 -3
- package/src/index.js +6 -3
- package/src/recommend-mcp.js +22 -7
- package/src/recruit-mcp.js +18 -6
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
getNextInfiniteListCandidate,
|
|
23
23
|
markInfiniteListCandidateProcessed
|
|
24
24
|
} from "../../core/infinite-list/index.js";
|
|
25
|
+
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
25
26
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
26
27
|
import {
|
|
27
28
|
callScreeningLlm,
|
|
@@ -250,9 +251,13 @@ async function setupChatRunContext(client, {
|
|
|
250
251
|
normalizedStartFrom,
|
|
251
252
|
readyTimeoutMs,
|
|
252
253
|
listSettleMs,
|
|
253
|
-
runControl
|
|
254
|
+
runControl,
|
|
255
|
+
ensureViewport = null
|
|
254
256
|
} = {}) {
|
|
255
|
-
|
|
257
|
+
let rootState = await getChatRoots(client);
|
|
258
|
+
if (ensureViewport) {
|
|
259
|
+
rootState = await ensureViewport(rootState, "context_roots");
|
|
260
|
+
}
|
|
256
261
|
runControl.checkpoint({
|
|
257
262
|
top_document_node_id: rootState.rootNodes.top
|
|
258
263
|
});
|
|
@@ -280,6 +285,10 @@ async function setupChatRunContext(client, {
|
|
|
280
285
|
if (normalizeText(job) && !jobSelection.selected) {
|
|
281
286
|
throw new Error(`Chat job selection failed: ${jobSelection.reason || "unknown"}`);
|
|
282
287
|
}
|
|
288
|
+
rootState = await getChatRoots(client);
|
|
289
|
+
if (ensureViewport) {
|
|
290
|
+
rootState = await ensureViewport(rootState, "context_job");
|
|
291
|
+
}
|
|
283
292
|
runControl.checkpoint({
|
|
284
293
|
chat_context_step: "job_selection",
|
|
285
294
|
primary_label: primaryLabel,
|
|
@@ -294,6 +303,10 @@ async function setupChatRunContext(client, {
|
|
|
294
303
|
if (!startFilter.ok) {
|
|
295
304
|
throw new Error(`Chat start filter selection failed: ${startFilter.error || "unknown"}`);
|
|
296
305
|
}
|
|
306
|
+
rootState = await getChatRoots(client);
|
|
307
|
+
if (ensureViewport) {
|
|
308
|
+
rootState = await ensureViewport(rootState, "context_start_filter");
|
|
309
|
+
}
|
|
297
310
|
runControl.checkpoint({
|
|
298
311
|
chat_context_step: "start_filter",
|
|
299
312
|
primary_label: primaryLabel,
|
|
@@ -362,6 +375,18 @@ export async function runChatWorkflow({
|
|
|
362
375
|
domain: "chat",
|
|
363
376
|
listName: "chat-candidates"
|
|
364
377
|
});
|
|
378
|
+
const viewportGuard = createViewportRunGuard({
|
|
379
|
+
client,
|
|
380
|
+
domain: "chat",
|
|
381
|
+
root: "top",
|
|
382
|
+
frameOwnerRoot: "top",
|
|
383
|
+
runControl,
|
|
384
|
+
getRoots: getChatRoots
|
|
385
|
+
});
|
|
386
|
+
async function ensureChatViewport(rootState, phase) {
|
|
387
|
+
const result = await viewportGuard.ensure(rootState, { phase });
|
|
388
|
+
return result.rootState || rootState;
|
|
389
|
+
}
|
|
365
390
|
const results = [];
|
|
366
391
|
let cardNodeIds = [];
|
|
367
392
|
let listEndReason = "";
|
|
@@ -398,7 +423,8 @@ export async function runChatWorkflow({
|
|
|
398
423
|
normalizedStartFrom,
|
|
399
424
|
readyTimeoutMs,
|
|
400
425
|
listSettleMs,
|
|
401
|
-
runControl
|
|
426
|
+
runControl,
|
|
427
|
+
ensureViewport: ensureChatViewport
|
|
402
428
|
});
|
|
403
429
|
let rootState = setup.rootState;
|
|
404
430
|
contextSetup = {
|
|
@@ -431,7 +457,8 @@ export async function runChatWorkflow({
|
|
|
431
457
|
normalizedStartFrom,
|
|
432
458
|
readyTimeoutMs,
|
|
433
459
|
listSettleMs,
|
|
434
|
-
runControl
|
|
460
|
+
runControl,
|
|
461
|
+
ensureViewport: ensureChatViewport
|
|
435
462
|
});
|
|
436
463
|
rootState = recoveredSetup.rootState;
|
|
437
464
|
contextSetup = {
|
|
@@ -449,7 +476,7 @@ export async function runChatWorkflow({
|
|
|
449
476
|
await runControl.waitIfPaused();
|
|
450
477
|
runControl.throwIfCanceled();
|
|
451
478
|
runControl.setPhase("chat:cards");
|
|
452
|
-
const cardRootState = await getChatRoots(client);
|
|
479
|
+
const cardRootState = await ensureChatViewport(await getChatRoots(client), "cards");
|
|
453
480
|
const initialCards = await waitForChatCandidateNodeIds(client, cardRootState.rootNodes.top, {
|
|
454
481
|
timeoutMs: cardTimeoutMs,
|
|
455
482
|
intervalMs: 500
|
|
@@ -479,7 +506,9 @@ export async function runChatWorkflow({
|
|
|
479
506
|
request_skipped: 0,
|
|
480
507
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
481
508
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
482
|
-
list_end_reason: listEndReason
|
|
509
|
+
list_end_reason: listEndReason,
|
|
510
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
511
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
483
512
|
});
|
|
484
513
|
runControl.setPhase("chat:done");
|
|
485
514
|
return {
|
|
@@ -493,6 +522,10 @@ export async function runChatWorkflow({
|
|
|
493
522
|
requested_start_from: normalizedStartFrom
|
|
494
523
|
},
|
|
495
524
|
candidate_list: compactInfiniteListState(listState),
|
|
525
|
+
viewport_health: {
|
|
526
|
+
stats: viewportGuard.getStats(),
|
|
527
|
+
events: viewportGuard.getEvents()
|
|
528
|
+
},
|
|
496
529
|
list_end_reason: listEndReason,
|
|
497
530
|
target_pass_count: passTarget,
|
|
498
531
|
process_until_list_end: Boolean(processUntilListEnd),
|
|
@@ -524,7 +557,9 @@ export async function runChatWorkflow({
|
|
|
524
557
|
request_satisfied: 0,
|
|
525
558
|
request_skipped: 0,
|
|
526
559
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
527
|
-
scroll_count: 0
|
|
560
|
+
scroll_count: 0,
|
|
561
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
562
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
528
563
|
});
|
|
529
564
|
|
|
530
565
|
while (
|
|
@@ -537,6 +572,7 @@ export async function runChatWorkflow({
|
|
|
537
572
|
await runControl.waitIfPaused();
|
|
538
573
|
runControl.throwIfCanceled();
|
|
539
574
|
runControl.setPhase("chat:candidate");
|
|
575
|
+
rootState = await ensureChatViewport(rootState, "candidate_loop");
|
|
540
576
|
const loopTopLevelState = await getChatTopLevelState(client);
|
|
541
577
|
if (!loopTopLevelState.is_chat_shell) {
|
|
542
578
|
await recoverAndReapplyChatContext("candidate_loop_non_chat_shell", {
|
|
@@ -554,7 +590,8 @@ export async function runChatWorkflow({
|
|
|
554
590
|
settleMs: listSettleMs,
|
|
555
591
|
fallbackPoint: listFallbackPoint,
|
|
556
592
|
findNodeIds: async () => {
|
|
557
|
-
const currentRootState = await getChatRoots(client);
|
|
593
|
+
const currentRootState = await ensureChatViewport(await getChatRoots(client), "candidate_find_nodes");
|
|
594
|
+
rootState = currentRootState;
|
|
558
595
|
const currentCards = await waitForChatCandidateNodeIds(client, currentRootState.rootNodes.top, {
|
|
559
596
|
timeoutMs: Math.min(cardTimeoutMs, 8000),
|
|
560
597
|
intervalMs: 500
|
|
@@ -609,6 +646,7 @@ export async function runChatWorkflow({
|
|
|
609
646
|
await runControl.waitIfPaused();
|
|
610
647
|
runControl.throwIfCanceled();
|
|
611
648
|
runControl.setPhase("chat:detail");
|
|
649
|
+
rootState = await ensureChatViewport(rootState, "detail");
|
|
612
650
|
|
|
613
651
|
detailStep = "select_candidate";
|
|
614
652
|
networkRecorder.clear();
|
|
@@ -921,6 +959,8 @@ export async function runChatWorkflow({
|
|
|
921
959
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
922
960
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
923
961
|
list_end_reason: listEndReason || null,
|
|
962
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
963
|
+
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
924
964
|
last_candidate_id: screeningCandidate.id || null,
|
|
925
965
|
last_candidate_key: candidateKey,
|
|
926
966
|
last_score: screening.score
|
|
@@ -950,6 +990,10 @@ export async function runChatWorkflow({
|
|
|
950
990
|
card_count: cardNodeIds.length,
|
|
951
991
|
context_setup: contextSetup,
|
|
952
992
|
candidate_list: compactInfiniteListState(listState),
|
|
993
|
+
viewport_health: {
|
|
994
|
+
stats: viewportGuard.getStats(),
|
|
995
|
+
events: viewportGuard.getEvents()
|
|
996
|
+
},
|
|
953
997
|
list_end_reason: listEndReason || null,
|
|
954
998
|
target_pass_count: passTarget,
|
|
955
999
|
process_until_list_end: Boolean(processUntilListEnd),
|
|
@@ -25,7 +25,8 @@ export async function getRecommendRoots(client, {
|
|
|
25
25
|
].filter(Boolean),
|
|
26
26
|
rootNodes: {
|
|
27
27
|
top: topRoot.nodeId,
|
|
28
|
-
frame: iframe?.documentNodeId || 0
|
|
28
|
+
frame: iframe?.documentNodeId || 0,
|
|
29
|
+
frameOwner: iframe?.nodeId || 0
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
}
|
|
@@ -49,7 +50,8 @@ export async function waitForRecommendRoots(client, {
|
|
|
49
50
|
roots: [],
|
|
50
51
|
rootNodes: {
|
|
51
52
|
top: 0,
|
|
52
|
-
frame: 0
|
|
53
|
+
frame: 0,
|
|
54
|
+
frameOwner: 0
|
|
53
55
|
}
|
|
54
56
|
};
|
|
55
57
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
markInfiniteListCandidateProcessed,
|
|
21
21
|
resetInfiniteListForRefreshRound
|
|
22
22
|
} from "../../core/infinite-list/index.js";
|
|
23
|
+
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
23
24
|
import { screenCandidate } from "../../core/screening/index.js";
|
|
24
25
|
import {
|
|
25
26
|
closeRecommendDetail,
|
|
@@ -372,6 +373,18 @@ export async function runRecommendWorkflow({
|
|
|
372
373
|
domain: "recommend",
|
|
373
374
|
listName: "recommend-candidates"
|
|
374
375
|
});
|
|
376
|
+
const viewportGuard = createViewportRunGuard({
|
|
377
|
+
client,
|
|
378
|
+
domain: "recommend",
|
|
379
|
+
root: "frame",
|
|
380
|
+
frameOwnerRoot: "frameOwner",
|
|
381
|
+
runControl,
|
|
382
|
+
getRoots: getRecommendRoots
|
|
383
|
+
});
|
|
384
|
+
async function ensureRecommendViewport(rootState, phase) {
|
|
385
|
+
const result = await viewportGuard.ensure(rootState, { phase });
|
|
386
|
+
return result.rootState || rootState;
|
|
387
|
+
}
|
|
375
388
|
const results = [];
|
|
376
389
|
const refreshAttempts = [];
|
|
377
390
|
let refreshRounds = 0;
|
|
@@ -389,6 +402,7 @@ export async function runRecommendWorkflow({
|
|
|
389
402
|
runControl.throwIfCanceled();
|
|
390
403
|
runControl.setPhase("recommend:roots");
|
|
391
404
|
let rootState = await getRecommendRoots(client);
|
|
405
|
+
rootState = await ensureRecommendViewport(rootState, "roots");
|
|
392
406
|
runControl.checkpoint({
|
|
393
407
|
iframe_selector: rootState.iframe.selector,
|
|
394
408
|
iframe_document_node_id: rootState.iframe.documentNodeId
|
|
@@ -406,6 +420,7 @@ export async function runRecommendWorkflow({
|
|
|
406
420
|
throw new Error(`Requested recommend job was not selected: ${jobSelection.reason}`);
|
|
407
421
|
}
|
|
408
422
|
rootState = await getRecommendRoots(client);
|
|
423
|
+
rootState = await ensureRecommendViewport(rootState, "job");
|
|
409
424
|
runControl.checkpoint({
|
|
410
425
|
job_selection: compactJobSelection(jobSelection)
|
|
411
426
|
});
|
|
@@ -424,6 +439,7 @@ export async function runRecommendWorkflow({
|
|
|
424
439
|
throw new Error(`Recommend page scope was not selected: ${pageScopeSelection.reason || pageScopeSelection.effective_scope || requestedPageScope}`);
|
|
425
440
|
}
|
|
426
441
|
rootState = await getRecommendRoots(client);
|
|
442
|
+
rootState = await ensureRecommendViewport(rootState, "page_scope");
|
|
427
443
|
runControl.checkpoint({
|
|
428
444
|
page_scope: compactPageScopeSelection(pageScopeSelection)
|
|
429
445
|
});
|
|
@@ -440,6 +456,8 @@ export async function runRecommendWorkflow({
|
|
|
440
456
|
if (!filterResult.confirmed) {
|
|
441
457
|
throw new Error("Recommend run filter selection was not confirmed");
|
|
442
458
|
}
|
|
459
|
+
rootState = await getRecommendRoots(client);
|
|
460
|
+
rootState = await ensureRecommendViewport(rootState, "filter");
|
|
443
461
|
runControl.checkpoint({
|
|
444
462
|
filter: compactFilterResult(filterResult)
|
|
445
463
|
});
|
|
@@ -448,6 +466,7 @@ export async function runRecommendWorkflow({
|
|
|
448
466
|
await runControl.waitIfPaused();
|
|
449
467
|
runControl.throwIfCanceled();
|
|
450
468
|
runControl.setPhase("recommend:cards");
|
|
469
|
+
rootState = await ensureRecommendViewport(rootState, "cards");
|
|
451
470
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
452
471
|
timeoutMs: cardTimeoutMs,
|
|
453
472
|
intervalMs: 300
|
|
@@ -468,13 +487,16 @@ export async function runRecommendWorkflow({
|
|
|
468
487
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
469
488
|
scroll_count: 0,
|
|
470
489
|
refresh_rounds: 0,
|
|
471
|
-
refresh_attempts: 0
|
|
490
|
+
refresh_attempts: 0,
|
|
491
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
492
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
472
493
|
});
|
|
473
494
|
|
|
474
495
|
while (results.length < limit) {
|
|
475
496
|
await runControl.waitIfPaused();
|
|
476
497
|
runControl.throwIfCanceled();
|
|
477
498
|
runControl.setPhase("recommend:candidate");
|
|
499
|
+
rootState = await ensureRecommendViewport(rootState, "candidate_loop");
|
|
478
500
|
|
|
479
501
|
const nextCandidateResult = await getNextInfiniteListCandidate({
|
|
480
502
|
client,
|
|
@@ -485,7 +507,9 @@ export async function runRecommendWorkflow({
|
|
|
485
507
|
settleMs: listSettleMs,
|
|
486
508
|
fallbackPoint: listFallbackPoint,
|
|
487
509
|
findNodeIds: async () => {
|
|
488
|
-
|
|
510
|
+
let currentRootState = await getRecommendRoots(client);
|
|
511
|
+
currentRootState = await ensureRecommendViewport(currentRootState, "candidate_find_nodes");
|
|
512
|
+
rootState = currentRootState;
|
|
489
513
|
const currentCardNodeIds = await waitForRecommendCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
490
514
|
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
491
515
|
intervalMs: 300
|
|
@@ -544,10 +568,13 @@ export async function runRecommendWorkflow({
|
|
|
544
568
|
refresh_attempts: refreshAttempts.length,
|
|
545
569
|
refresh_method: refreshResult.method || null,
|
|
546
570
|
refresh_forced_recent_not_view: true,
|
|
547
|
-
list_end_reason: listEndReason
|
|
571
|
+
list_end_reason: listEndReason,
|
|
572
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
573
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
548
574
|
});
|
|
549
575
|
if (refreshResult.ok) {
|
|
550
576
|
rootState = refreshResult.root_state || await getRecommendRoots(client);
|
|
577
|
+
rootState = await ensureRecommendViewport(rootState, "refresh_after");
|
|
551
578
|
cardNodeIds = await waitForRecommendCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
552
579
|
timeoutMs: cardTimeoutMs,
|
|
553
580
|
intervalMs: 300
|
|
@@ -579,6 +606,7 @@ export async function runRecommendWorkflow({
|
|
|
579
606
|
await runControl.waitIfPaused();
|
|
580
607
|
runControl.throwIfCanceled();
|
|
581
608
|
runControl.setPhase("recommend:detail");
|
|
609
|
+
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
582
610
|
networkRecorder.clear();
|
|
583
611
|
const openedDetail = await openRecommendCardDetail(client, cardNodeId);
|
|
584
612
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
@@ -719,6 +747,8 @@ export async function runRecommendWorkflow({
|
|
|
719
747
|
refresh_rounds: refreshRounds,
|
|
720
748
|
refresh_attempts: refreshAttempts.length,
|
|
721
749
|
list_end_reason: listEndReason || null,
|
|
750
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
751
|
+
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
722
752
|
last_candidate_id: screeningCandidate.id || null,
|
|
723
753
|
last_candidate_key: candidateKey,
|
|
724
754
|
last_score: screening.score
|
|
@@ -756,6 +786,10 @@ export async function runRecommendWorkflow({
|
|
|
756
786
|
filter: compactFilterResult(filterResult),
|
|
757
787
|
card_count: cardNodeIds.length,
|
|
758
788
|
candidate_list: compactInfiniteListState(listState),
|
|
789
|
+
viewport_health: {
|
|
790
|
+
stats: viewportGuard.getStats(),
|
|
791
|
+
events: viewportGuard.getEvents()
|
|
792
|
+
},
|
|
759
793
|
list_end_reason: listEndReason || null,
|
|
760
794
|
refresh_rounds: refreshRounds,
|
|
761
795
|
refresh_attempts: refreshAttempts,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
markInfiniteListCandidateProcessed,
|
|
19
19
|
resetInfiniteListForRefreshRound
|
|
20
20
|
} from "../../core/infinite-list/index.js";
|
|
21
|
+
import { createViewportRunGuard } from "../../core/self-heal/index.js";
|
|
21
22
|
import { screenCandidate } from "../../core/screening/index.js";
|
|
22
23
|
import {
|
|
23
24
|
closeRecruitDetail,
|
|
@@ -140,6 +141,18 @@ export async function runRecruitWorkflow({
|
|
|
140
141
|
domain: "recruit",
|
|
141
142
|
listName: "search-results"
|
|
142
143
|
});
|
|
144
|
+
const viewportGuard = createViewportRunGuard({
|
|
145
|
+
client,
|
|
146
|
+
domain: "recruit",
|
|
147
|
+
root: "frame",
|
|
148
|
+
frameOwnerRoot: "frameOwner",
|
|
149
|
+
runControl,
|
|
150
|
+
getRoots: getRecruitRoots
|
|
151
|
+
});
|
|
152
|
+
async function ensureRecruitViewport(rootState, phase) {
|
|
153
|
+
const result = await viewportGuard.ensure(rootState, { phase });
|
|
154
|
+
return result.rootState || rootState;
|
|
155
|
+
}
|
|
143
156
|
const results = [];
|
|
144
157
|
const refreshAttempts = [];
|
|
145
158
|
let refreshRounds = 0;
|
|
@@ -153,6 +166,7 @@ export async function runRecruitWorkflow({
|
|
|
153
166
|
runControl.throwIfCanceled();
|
|
154
167
|
runControl.setPhase("recruit:roots");
|
|
155
168
|
let rootState = await getRecruitRoots(client);
|
|
169
|
+
rootState = await ensureRecruitViewport(rootState, "roots");
|
|
156
170
|
runControl.checkpoint({
|
|
157
171
|
iframe_selector: rootState.iframe.selector,
|
|
158
172
|
iframe_document_node_id: rootState.iframe.documentNodeId,
|
|
@@ -186,11 +200,13 @@ export async function runRecruitWorkflow({
|
|
|
186
200
|
}
|
|
187
201
|
});
|
|
188
202
|
rootState = await getRecruitRoots(client);
|
|
203
|
+
rootState = await ensureRecruitViewport(rootState, "search");
|
|
189
204
|
}
|
|
190
205
|
|
|
191
206
|
await runControl.waitIfPaused();
|
|
192
207
|
runControl.throwIfCanceled();
|
|
193
208
|
runControl.setPhase("recruit:cards");
|
|
209
|
+
rootState = await ensureRecruitViewport(rootState, "cards");
|
|
194
210
|
cardNodeIds = await waitForRecruitCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
195
211
|
timeoutMs: cardTimeoutMs,
|
|
196
212
|
intervalMs: 300
|
|
@@ -209,13 +225,16 @@ export async function runRecruitWorkflow({
|
|
|
209
225
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
210
226
|
scroll_count: 0,
|
|
211
227
|
refresh_rounds: 0,
|
|
212
|
-
refresh_attempts: 0
|
|
228
|
+
refresh_attempts: 0,
|
|
229
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
230
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
213
231
|
});
|
|
214
232
|
|
|
215
233
|
while (results.length < limit) {
|
|
216
234
|
await runControl.waitIfPaused();
|
|
217
235
|
runControl.throwIfCanceled();
|
|
218
236
|
runControl.setPhase("recruit:candidate");
|
|
237
|
+
rootState = await ensureRecruitViewport(rootState, "candidate_loop");
|
|
219
238
|
|
|
220
239
|
const nextCandidateResult = await getNextInfiniteListCandidate({
|
|
221
240
|
client,
|
|
@@ -226,7 +245,9 @@ export async function runRecruitWorkflow({
|
|
|
226
245
|
settleMs: listSettleMs,
|
|
227
246
|
fallbackPoint: listFallbackPoint,
|
|
228
247
|
findNodeIds: async () => {
|
|
229
|
-
|
|
248
|
+
let currentRootState = await getRecruitRoots(client);
|
|
249
|
+
currentRootState = await ensureRecruitViewport(currentRootState, "candidate_find_nodes");
|
|
250
|
+
rootState = currentRootState;
|
|
230
251
|
const currentCardNodeIds = await waitForRecruitCardNodeIds(client, currentRootState.iframe.documentNodeId, {
|
|
231
252
|
timeoutMs: Math.min(cardTimeoutMs, 5000),
|
|
232
253
|
intervalMs: 300
|
|
@@ -283,10 +304,13 @@ export async function runRecruitWorkflow({
|
|
|
283
304
|
refresh_attempts: refreshAttempts.length,
|
|
284
305
|
refresh_method: refreshResult.method || null,
|
|
285
306
|
refresh_forced_recent_viewed: true,
|
|
286
|
-
list_end_reason: listEndReason
|
|
307
|
+
list_end_reason: listEndReason,
|
|
308
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
309
|
+
viewport_recoveries: viewportGuard.getStats().recoveries
|
|
287
310
|
});
|
|
288
311
|
if (refreshResult.ok) {
|
|
289
312
|
rootState = await getRecruitRoots(client);
|
|
313
|
+
rootState = await ensureRecruitViewport(rootState, "refresh_after");
|
|
290
314
|
cardNodeIds = await waitForRecruitCardNodeIds(client, rootState.iframe.documentNodeId, {
|
|
291
315
|
timeoutMs: cardTimeoutMs,
|
|
292
316
|
intervalMs: 300
|
|
@@ -318,6 +342,7 @@ export async function runRecruitWorkflow({
|
|
|
318
342
|
await runControl.waitIfPaused();
|
|
319
343
|
runControl.throwIfCanceled();
|
|
320
344
|
runControl.setPhase("recruit:detail");
|
|
345
|
+
rootState = await ensureRecruitViewport(rootState, "detail");
|
|
321
346
|
networkRecorder.clear();
|
|
322
347
|
const openedDetail = await openRecruitCardDetail(client, cardNodeId);
|
|
323
348
|
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
@@ -430,6 +455,8 @@ export async function runRecruitWorkflow({
|
|
|
430
455
|
refresh_rounds: refreshRounds,
|
|
431
456
|
refresh_attempts: refreshAttempts.length,
|
|
432
457
|
list_end_reason: listEndReason || null,
|
|
458
|
+
viewport_checks: viewportGuard.getStats().checks,
|
|
459
|
+
viewport_recoveries: viewportGuard.getStats().recoveries,
|
|
433
460
|
last_candidate_id: screeningCandidate.id || null,
|
|
434
461
|
last_candidate_key: candidateKey,
|
|
435
462
|
last_score: screening.score
|
|
@@ -459,6 +486,10 @@ export async function runRecruitWorkflow({
|
|
|
459
486
|
search_params: normalizedSearchParams,
|
|
460
487
|
card_count: cardNodeIds.length,
|
|
461
488
|
candidate_list: compactInfiniteListState(listState),
|
|
489
|
+
viewport_health: {
|
|
490
|
+
stats: viewportGuard.getStats(),
|
|
491
|
+
events: viewportGuard.getEvents()
|
|
492
|
+
},
|
|
462
493
|
list_end_reason: listEndReason || null,
|
|
463
494
|
refresh_rounds: refreshRounds,
|
|
464
495
|
refresh_attempts: refreshAttempts,
|
package/src/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import {
|
|
8
8
|
getFeaturedCalibrationResolution,
|
|
9
9
|
getBossChatTargetCountValue,
|
|
10
|
-
normalizeTargetCountInput
|
|
10
|
+
normalizeTargetCountInput,
|
|
11
|
+
resolveBossScreeningConfig
|
|
11
12
|
} from "./chat-runtime-config.js";
|
|
12
13
|
import {
|
|
13
14
|
__resetChatMcpStateForTests,
|
|
@@ -1890,7 +1891,8 @@ async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
|
|
|
1890
1891
|
}
|
|
1891
1892
|
|
|
1892
1893
|
const host = "127.0.0.1";
|
|
1893
|
-
const
|
|
1894
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1895
|
+
const port = parsePositiveInteger(args.port, configResolution.ok ? configResolution.config.debugPort : 9222);
|
|
1894
1896
|
let session = null;
|
|
1895
1897
|
try {
|
|
1896
1898
|
session = await connectToChromeTarget({
|
|
@@ -1909,7 +1911,8 @@ async function handleRunRecommendSelfHealTool({ workspaceRoot, args }) {
|
|
|
1909
1911
|
domain: "recommend",
|
|
1910
1912
|
roots: rootState?.roots || {},
|
|
1911
1913
|
selectorProbes: config.selectorProbes,
|
|
1912
|
-
accessibilityProbes: config.accessibilityProbes
|
|
1914
|
+
accessibilityProbes: config.accessibilityProbes,
|
|
1915
|
+
viewportProbes: config.viewportProbes
|
|
1913
1916
|
});
|
|
1914
1917
|
assertNoForbiddenCdpCalls(methodLog);
|
|
1915
1918
|
|
package/src/recommend-mcp.js
CHANGED
|
@@ -44,6 +44,10 @@ import {
|
|
|
44
44
|
parseRecommendInstruction
|
|
45
45
|
} from "./parser.js";
|
|
46
46
|
import { getRunsDir } from "./run-state.js";
|
|
47
|
+
import {
|
|
48
|
+
resolveBossConfiguredOutputDir,
|
|
49
|
+
resolveBossScreeningConfig
|
|
50
|
+
} from "./chat-runtime-config.js";
|
|
47
51
|
|
|
48
52
|
const DEFAULT_RECOMMEND_HOST = "127.0.0.1";
|
|
49
53
|
const DEFAULT_RECOMMEND_PORT = 9222;
|
|
@@ -115,12 +119,14 @@ function getRecommendRunArtifacts(runId) {
|
|
|
115
119
|
const normalized = normalizeRunId(runId);
|
|
116
120
|
if (!normalized) return null;
|
|
117
121
|
const runsDir = getRunsDir();
|
|
122
|
+
const outputDir = resolveBossConfiguredOutputDir("", runsDir);
|
|
118
123
|
return {
|
|
119
124
|
runs_dir: runsDir,
|
|
125
|
+
output_dir: outputDir,
|
|
120
126
|
run_state_path: path.join(runsDir, `${normalized}.json`),
|
|
121
127
|
checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
|
|
122
|
-
output_csv: path.join(
|
|
123
|
-
report_json: path.join(
|
|
128
|
+
output_csv: path.join(outputDir, `${normalized}.results.csv`),
|
|
129
|
+
report_json: path.join(outputDir, `${normalized}.report.json`)
|
|
124
130
|
};
|
|
125
131
|
}
|
|
126
132
|
|
|
@@ -484,8 +490,12 @@ async function readRecommendJobOptionsFromSession(session) {
|
|
|
484
490
|
}
|
|
485
491
|
|
|
486
492
|
export async function listRecommendJobsTool({ workspaceRoot = "", args = {} } = {}) {
|
|
493
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
487
494
|
const host = normalizeText(args.host) || DEFAULT_RECOMMEND_HOST;
|
|
488
|
-
const port = parsePositiveInteger(
|
|
495
|
+
const port = parsePositiveInteger(
|
|
496
|
+
args.port,
|
|
497
|
+
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
498
|
+
);
|
|
489
499
|
const targetUrlIncludes = normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL;
|
|
490
500
|
const allowNavigate = args.allow_navigate !== false;
|
|
491
501
|
const slowLive = args.slow_live === true;
|
|
@@ -613,7 +623,8 @@ async function waitForHealthyRecommend(client, config, {
|
|
|
613
623
|
domain: "recommend",
|
|
614
624
|
roots: roots.roots,
|
|
615
625
|
selectorProbes: config.selectorProbes,
|
|
616
|
-
accessibilityProbes: config.accessibilityProbes
|
|
626
|
+
accessibilityProbes: config.accessibilityProbes,
|
|
627
|
+
viewportProbes: config.viewportProbes
|
|
617
628
|
});
|
|
618
629
|
if (lastCheck.status === HEALTH_STATUS.HEALTHY) return lastCheck;
|
|
619
630
|
await sleep(intervalMs);
|
|
@@ -922,7 +933,7 @@ function buildRecommendFilter(parsed, args = {}) {
|
|
|
922
933
|
return groups.length ? { filterGroups: groups } : { enabled: false };
|
|
923
934
|
}
|
|
924
935
|
|
|
925
|
-
function normalizeRecommendStartInput(args = {}, parsed) {
|
|
936
|
+
function normalizeRecommendStartInput(args = {}, parsed, configResolution = null) {
|
|
926
937
|
const confirmation = args.confirmation || {};
|
|
927
938
|
const overrides = args.overrides || {};
|
|
928
939
|
const slowLive = args.slow_live === true;
|
|
@@ -932,7 +943,10 @@ function normalizeRecommendStartInput(args = {}, parsed) {
|
|
|
932
943
|
);
|
|
933
944
|
return {
|
|
934
945
|
host: normalizeText(args.host) || DEFAULT_RECOMMEND_HOST,
|
|
935
|
-
port: parsePositiveInteger(
|
|
946
|
+
port: parsePositiveInteger(
|
|
947
|
+
args.port,
|
|
948
|
+
configResolution?.ok ? configResolution.config.debugPort : DEFAULT_RECOMMEND_PORT
|
|
949
|
+
),
|
|
936
950
|
targetUrlIncludes: normalizeText(args.target_url_includes) || RECOMMEND_TARGET_URL,
|
|
937
951
|
allowNavigate: args.allow_navigate !== false,
|
|
938
952
|
slowLive,
|
|
@@ -1038,7 +1052,8 @@ async function startRecommendPipelineRunInternal(args = {}, { workspaceRoot = ""
|
|
|
1038
1052
|
const parsed = parseRecommendPipelineRequest(args);
|
|
1039
1053
|
const gate = evaluateRecommendPipelineGate(parsed, args);
|
|
1040
1054
|
if (gate) return gate;
|
|
1041
|
-
const
|
|
1055
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
1056
|
+
const normalized = normalizeRecommendStartInput(args, parsed, configResolution);
|
|
1042
1057
|
|
|
1043
1058
|
let session;
|
|
1044
1059
|
try {
|
package/src/recruit-mcp.js
CHANGED
|
@@ -32,6 +32,10 @@ import {
|
|
|
32
32
|
runRecruitWorkflow,
|
|
33
33
|
waitForRecruitSearchControls
|
|
34
34
|
} from "./domains/recruit/index.js";
|
|
35
|
+
import {
|
|
36
|
+
resolveBossConfiguredOutputDir,
|
|
37
|
+
resolveBossScreeningConfig
|
|
38
|
+
} from "./chat-runtime-config.js";
|
|
35
39
|
|
|
36
40
|
const RUN_MODE_ASYNC = "async";
|
|
37
41
|
const RUN_MODE_SYNC = "sync";
|
|
@@ -108,12 +112,14 @@ function getRecruitRunArtifacts(runId) {
|
|
|
108
112
|
const normalized = normalizeRunId(runId);
|
|
109
113
|
if (!normalized) return null;
|
|
110
114
|
const runsDir = getRecruitRunsDir();
|
|
115
|
+
const outputDir = resolveBossConfiguredOutputDir("", runsDir);
|
|
111
116
|
return {
|
|
112
117
|
runs_dir: runsDir,
|
|
118
|
+
output_dir: outputDir,
|
|
113
119
|
run_state_path: path.join(runsDir, `${normalized}.json`),
|
|
114
120
|
checkpoint_path: path.join(runsDir, `${normalized}.checkpoint.json`),
|
|
115
|
-
output_csv: path.join(
|
|
116
|
-
report_json: path.join(
|
|
121
|
+
output_csv: path.join(outputDir, `${normalized}.results.csv`),
|
|
122
|
+
report_json: path.join(outputDir, `${normalized}.report.json`)
|
|
117
123
|
};
|
|
118
124
|
}
|
|
119
125
|
|
|
@@ -807,12 +813,18 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
|
|
|
807
813
|
const parsed = parseRecruitPipelineRequest(args);
|
|
808
814
|
const gate = evaluateRecruitPipelineGate(parsed);
|
|
809
815
|
if (gate) return gate;
|
|
816
|
+
const configResolution = resolveBossScreeningConfig(workspaceRoot);
|
|
817
|
+
const host = normalizeText(args.host) || DEFAULT_RECRUIT_HOST;
|
|
818
|
+
const port = parsePositiveInteger(
|
|
819
|
+
args.port,
|
|
820
|
+
configResolution.ok ? configResolution.config.debugPort : DEFAULT_RECRUIT_PORT
|
|
821
|
+
);
|
|
810
822
|
|
|
811
823
|
let session;
|
|
812
824
|
try {
|
|
813
825
|
session = await recruitConnectorImpl({
|
|
814
|
-
host
|
|
815
|
-
port
|
|
826
|
+
host,
|
|
827
|
+
port,
|
|
816
828
|
targetUrlIncludes: normalizeText(args.target_url_includes) || RECRUIT_TARGET_URL,
|
|
817
829
|
allowNavigate: args.allow_navigate !== false,
|
|
818
830
|
slowLive: args.slow_live === true
|
|
@@ -858,8 +870,8 @@ async function startRecruitPipelineRunInternal(args = {}, { workspaceRoot = "" }
|
|
|
858
870
|
workspaceRoot: normalizeText(workspaceRoot) || globalThis.process?.cwd?.() || "",
|
|
859
871
|
args: clonePlain(args, {}),
|
|
860
872
|
chrome: {
|
|
861
|
-
host
|
|
862
|
-
port
|
|
873
|
+
host,
|
|
874
|
+
port,
|
|
863
875
|
target_url: session.target?.url || RECRUIT_TARGET_URL,
|
|
864
876
|
target_id: session.target?.id || null,
|
|
865
877
|
auto_launch: session.chrome || null
|