@reconcrap/boss-recommend-mcp 2.0.28 → 2.0.30
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/src/domains/recommend/run-service.js +245 -111
- package/src/recommend-mcp.js +67 -0
package/package.json
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
1
3
|
import { createRunLifecycleManager } from "../../core/run/index.js";
|
|
2
4
|
import {
|
|
3
5
|
addTiming,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
closeRecommendDetail,
|
|
40
42
|
createRecommendDetailNetworkRecorder,
|
|
41
43
|
extractRecommendDetailCandidate,
|
|
44
|
+
isStaleRecommendNodeError,
|
|
42
45
|
openRecommendCardDetailWithFreshRetry,
|
|
43
46
|
waitForRecommendDetailNetworkEvents
|
|
44
47
|
} from "./detail.js";
|
|
@@ -376,6 +379,88 @@ function compactError(error, fallbackCode = "RECOMMEND_RUN_ERROR") {
|
|
|
376
379
|
};
|
|
377
380
|
}
|
|
378
381
|
|
|
382
|
+
export function isRecoverableImageCaptureError(error) {
|
|
383
|
+
const code = String(error?.code || "");
|
|
384
|
+
if (code === "IMAGE_CAPTURE_TIMEOUT" || code === "IMAGE_CAPTURE_TOTAL_TIMEOUT") return true;
|
|
385
|
+
if (isStaleRecommendNodeError(error)) return true;
|
|
386
|
+
return /Image fallback capture timed out/i.test(String(error?.message || error || ""));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function collectPartialImageEvidencePaths(basePath = "", extension = "jpg", maxCount = 12) {
|
|
390
|
+
const resolved = String(basePath || "").trim();
|
|
391
|
+
if (!resolved) return [];
|
|
392
|
+
const parsed = path.parse(resolved);
|
|
393
|
+
const ext = parsed.ext || `.${String(extension || "jpg").replace(/^\./, "") || "jpg"}`;
|
|
394
|
+
const files = [];
|
|
395
|
+
for (let index = 0; index < Math.max(1, Number(maxCount) || 1); index += 1) {
|
|
396
|
+
const page = String(index + 1).padStart(2, "0");
|
|
397
|
+
const candidatePath = path.join(parsed.dir, `${parsed.name}-page-${page}${ext}`);
|
|
398
|
+
if (fs.existsSync(candidatePath)) files.push(candidatePath);
|
|
399
|
+
}
|
|
400
|
+
return files;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export function createRecoverableImageCaptureEvidence(error, {
|
|
404
|
+
elapsedMs = 0,
|
|
405
|
+
filePath = "",
|
|
406
|
+
extension = "jpg",
|
|
407
|
+
maxScreenshots = 8
|
|
408
|
+
} = {}) {
|
|
409
|
+
const filePaths = collectPartialImageEvidencePaths(filePath, extension, maxScreenshots);
|
|
410
|
+
return {
|
|
411
|
+
schema_version: 1,
|
|
412
|
+
ok: false,
|
|
413
|
+
source: "image-scroll-sequence",
|
|
414
|
+
elapsed_ms: Math.max(0, Math.round(Number(error?.elapsed_ms ?? elapsedMs) || 0)),
|
|
415
|
+
capture_count: filePaths.length,
|
|
416
|
+
screenshot_count: filePaths.length,
|
|
417
|
+
unique_screenshot_count: filePaths.length,
|
|
418
|
+
dropped_duplicate_count: 0,
|
|
419
|
+
total_byte_length: 0,
|
|
420
|
+
original_total_byte_length: 0,
|
|
421
|
+
llm_screenshot_count: 0,
|
|
422
|
+
llm_total_byte_length: 0,
|
|
423
|
+
llm_original_total_byte_length: 0,
|
|
424
|
+
llm_composition_error: null,
|
|
425
|
+
error_code: error?.code || (isStaleRecommendNodeError(error) ? "IMAGE_CAPTURE_STALE_NODE" : "IMAGE_CAPTURE_FAILED"),
|
|
426
|
+
error: error?.message || String(error || "Image capture failed"),
|
|
427
|
+
file_paths: filePaths,
|
|
428
|
+
llm_file_paths: []
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function createImageCaptureFailureScreening(candidate, error) {
|
|
433
|
+
return {
|
|
434
|
+
status: "fail",
|
|
435
|
+
passed: false,
|
|
436
|
+
score: 0,
|
|
437
|
+
reasons: ["image_capture_failed"],
|
|
438
|
+
error: compactError(error, "IMAGE_CAPTURE_FAILED"),
|
|
439
|
+
candidate
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
export function isRecoverableRecommendDetailError(error) {
|
|
444
|
+
return isStaleRecommendNodeError(error);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function compactRecoverableDetailError(error) {
|
|
448
|
+
return compactError(error, isStaleRecommendNodeError(error) ? "DETAIL_STALE_NODE" : "DETAIL_OPEN_FAILED");
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function createRecoverableDetailFailureScreening(candidate, error) {
|
|
452
|
+
return {
|
|
453
|
+
status: "fail",
|
|
454
|
+
passed: false,
|
|
455
|
+
score: 0,
|
|
456
|
+
reasons: isStaleRecommendNodeError(error)
|
|
457
|
+
? ["detail_open_failed", "stale_node"]
|
|
458
|
+
: ["detail_open_failed"],
|
|
459
|
+
error: compactRecoverableDetailError(error),
|
|
460
|
+
candidate
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
379
464
|
export async function runRecommendWorkflow({
|
|
380
465
|
client,
|
|
381
466
|
targetUrl = "",
|
|
@@ -684,124 +769,149 @@ export async function runRecommendWorkflow({
|
|
|
684
769
|
|
|
685
770
|
let screeningCandidate = cardCandidate;
|
|
686
771
|
let detailResult = null;
|
|
772
|
+
let recoverableDetailError = null;
|
|
687
773
|
if (index < effectiveDetailLimit) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
774
|
+
try {
|
|
775
|
+
await runControl.waitIfPaused();
|
|
776
|
+
runControl.throwIfCanceled();
|
|
777
|
+
runControl.setPhase("recommend:detail");
|
|
778
|
+
rootState = await ensureRecommendViewport(rootState, "detail");
|
|
779
|
+
networkRecorder.clear();
|
|
780
|
+
const openedDetail = await openRecommendCardDetailWithFreshRetry(client, {
|
|
781
|
+
cardNodeId,
|
|
782
|
+
candidateKey,
|
|
783
|
+
cardCandidate,
|
|
784
|
+
rootState,
|
|
785
|
+
targetUrl,
|
|
786
|
+
retryTimeoutMs: 8000,
|
|
787
|
+
maxAttempts: 3
|
|
788
|
+
});
|
|
789
|
+
addTiming(timings, "candidate_click_ms", openedDetail.timings?.candidate_click_ms);
|
|
790
|
+
addTiming(timings, "detail_open_ms", openedDetail.timings?.detail_open_ms);
|
|
791
|
+
cardNodeId = openedDetail.card_node_id || cardNodeId;
|
|
792
|
+
cardCandidate = openedDetail.card_candidate || cardCandidate;
|
|
793
|
+
screeningCandidate = cardCandidate;
|
|
794
|
+
const waitPlan = getCvNetworkWaitPlan(cvAcquisitionState);
|
|
795
|
+
const networkWait = await measureTiming(timings, "network_cv_wait_ms", () => waitForCvNetworkEvents(
|
|
796
|
+
waitForRecommendDetailNetworkEvents,
|
|
797
|
+
networkRecorder,
|
|
798
|
+
{
|
|
799
|
+
waitPlan,
|
|
800
|
+
minCount: 1,
|
|
801
|
+
requireLoaded: true,
|
|
802
|
+
intervalMs: 120
|
|
803
|
+
}
|
|
804
|
+
));
|
|
805
|
+
if (networkWait?.elapsed_ms != null) {
|
|
806
|
+
timings.network_cv_wait_ms = Math.round(Number(networkWait.elapsed_ms) || 0);
|
|
715
807
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
targetUrl,
|
|
726
|
-
closeDetail: false,
|
|
727
|
-
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
728
|
-
networkParseIntervalMs: 250
|
|
729
|
-
});
|
|
730
|
-
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
731
|
-
|
|
732
|
-
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
733
|
-
let source = "network";
|
|
734
|
-
let imageEvidence = null;
|
|
735
|
-
if (parsedNetworkProfileCount > 0) {
|
|
736
|
-
recordCvNetworkHit(cvAcquisitionState, {
|
|
737
|
-
parsedNetworkProfileCount,
|
|
738
|
-
waitResult: networkWait
|
|
808
|
+
detailResult = await extractRecommendDetailCandidate(client, {
|
|
809
|
+
cardCandidate,
|
|
810
|
+
cardNodeId,
|
|
811
|
+
detailState: openedDetail.detail_state,
|
|
812
|
+
networkEvents: networkRecorder.events,
|
|
813
|
+
targetUrl,
|
|
814
|
+
closeDetail: false,
|
|
815
|
+
networkParseRetryMs: waitPlan.mode_before === "image" ? 500 : 2200,
|
|
816
|
+
networkParseIntervalMs: 250
|
|
739
817
|
});
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
818
|
+
addTiming(timings, "late_network_retry_ms", detailResult.network_parse_retry_elapsed_ms);
|
|
819
|
+
|
|
820
|
+
const parsedNetworkProfileCount = countParsedNetworkProfiles(detailResult);
|
|
821
|
+
let source = "network";
|
|
822
|
+
let imageEvidence = null;
|
|
823
|
+
if (parsedNetworkProfileCount > 0) {
|
|
824
|
+
recordCvNetworkHit(cvAcquisitionState, {
|
|
825
|
+
parsedNetworkProfileCount,
|
|
826
|
+
waitResult: networkWait
|
|
827
|
+
});
|
|
828
|
+
} else {
|
|
829
|
+
const captureNodeId = openedDetail.detail_state?.popup?.node_id
|
|
830
|
+
|| openedDetail.detail_state?.resumeIframe?.node_id
|
|
831
|
+
|| null;
|
|
832
|
+
if (captureNodeId) {
|
|
833
|
+
const imageEvidencePath = imageEvidenceFilePath({
|
|
747
834
|
imageOutputDir,
|
|
748
835
|
domain: "recommend",
|
|
749
836
|
runId: runControl?.runId,
|
|
750
837
|
index,
|
|
751
838
|
extension: "jpg"
|
|
752
|
-
})
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
839
|
+
});
|
|
840
|
+
try {
|
|
841
|
+
imageEvidence = await measureTiming(timings, "screenshot_capture_ms", () => captureScrolledNodeScreenshots(client, captureNodeId, {
|
|
842
|
+
filePath: imageEvidencePath,
|
|
843
|
+
format: "jpeg",
|
|
844
|
+
quality: 72,
|
|
845
|
+
optimize: true,
|
|
846
|
+
resizeMaxWidth: 1100,
|
|
847
|
+
captureViewport: true,
|
|
848
|
+
padding: 4,
|
|
849
|
+
maxScreenshots: maxImagePages,
|
|
850
|
+
wheelDeltaY: imageWheelDeltaY,
|
|
851
|
+
settleMs: 350,
|
|
852
|
+
scrollMethod: "dom-anchor-fallback-input",
|
|
853
|
+
stepTimeoutMs: 45000,
|
|
854
|
+
totalTimeoutMs: 90000,
|
|
855
|
+
duplicateStopCount: 1,
|
|
856
|
+
skipDuplicateScreenshots: true,
|
|
857
|
+
composeForLlm: true,
|
|
858
|
+
llmPagesPerImage: 3,
|
|
859
|
+
llmResizeMaxWidth: 1100,
|
|
860
|
+
llmQuality: 72,
|
|
861
|
+
metadata: {
|
|
862
|
+
domain: "recommend",
|
|
863
|
+
capture_mode: "scroll_sequence",
|
|
864
|
+
acquisition_reason: "network_miss_image_fallback",
|
|
865
|
+
run_candidate_index: index,
|
|
866
|
+
candidate_key: candidateKey
|
|
867
|
+
}
|
|
868
|
+
}));
|
|
869
|
+
source = "image";
|
|
870
|
+
} catch (error) {
|
|
871
|
+
if (!isRecoverableImageCaptureError(error)) throw error;
|
|
872
|
+
imageEvidence = createRecoverableImageCaptureEvidence(error, {
|
|
873
|
+
elapsedMs: timings.screenshot_capture_ms,
|
|
874
|
+
filePath: imageEvidencePath,
|
|
875
|
+
extension: "jpg",
|
|
876
|
+
maxScreenshots: maxImagePages
|
|
877
|
+
});
|
|
878
|
+
source = "image_capture_failed";
|
|
777
879
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
880
|
+
recordCvImageFallback(cvAcquisitionState, {
|
|
881
|
+
reason: source === "image_capture_failed"
|
|
882
|
+
? "network_miss_image_capture_failed"
|
|
883
|
+
: "network_miss_image_fallback",
|
|
884
|
+
parsedNetworkProfileCount,
|
|
885
|
+
waitResult: networkWait,
|
|
886
|
+
imageEvidence
|
|
887
|
+
});
|
|
888
|
+
} else {
|
|
889
|
+
source = "missing_capture_node";
|
|
890
|
+
recordCvNetworkMiss(cvAcquisitionState, {
|
|
891
|
+
reason: "network_miss_no_capture_node",
|
|
892
|
+
parsedNetworkProfileCount,
|
|
893
|
+
waitResult: networkWait
|
|
894
|
+
});
|
|
895
|
+
}
|
|
792
896
|
}
|
|
793
|
-
}
|
|
794
897
|
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
898
|
+
detailResult.image_evidence = imageEvidence;
|
|
899
|
+
detailResult.cv_acquisition = {
|
|
900
|
+
source,
|
|
901
|
+
mode_after: compactCvAcquisitionState(cvAcquisitionState).mode,
|
|
902
|
+
wait_plan: waitPlan,
|
|
903
|
+
network_wait: networkWait,
|
|
904
|
+
parsed_network_profile_count: parsedNetworkProfileCount,
|
|
905
|
+
image_evidence: summarizeImageEvidence(imageEvidence)
|
|
906
|
+
};
|
|
907
|
+
screeningCandidate = detailResult.candidate;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
if (!isRecoverableRecommendDetailError(error)) throw error;
|
|
910
|
+
recoverableDetailError = error;
|
|
911
|
+
detailResult = null;
|
|
912
|
+
timings.detail_recovered_error = compactRecoverableDetailError(error);
|
|
913
|
+
await closeRecommendDetail(client, { attemptsLimit: 2 }).catch(() => null);
|
|
914
|
+
}
|
|
805
915
|
}
|
|
806
916
|
|
|
807
917
|
await runControl.waitIfPaused();
|
|
@@ -809,7 +919,9 @@ export async function runRecommendWorkflow({
|
|
|
809
919
|
runControl.setPhase("recommend:screening");
|
|
810
920
|
let llmResult = null;
|
|
811
921
|
if (useLlmScreening) {
|
|
812
|
-
if (
|
|
922
|
+
if (recoverableDetailError || detailResult?.image_evidence?.ok === false) {
|
|
923
|
+
llmResult = null;
|
|
924
|
+
} else if (!llmConfig) {
|
|
813
925
|
llmResult = createMissingLlmConfigResult();
|
|
814
926
|
} else {
|
|
815
927
|
try {
|
|
@@ -831,9 +943,16 @@ export async function runRecommendWorkflow({
|
|
|
831
943
|
}
|
|
832
944
|
if (detailResult) detailResult.llm_result = llmResult;
|
|
833
945
|
}
|
|
834
|
-
const screening =
|
|
835
|
-
?
|
|
836
|
-
:
|
|
946
|
+
const screening = recoverableDetailError
|
|
947
|
+
? createRecoverableDetailFailureScreening(screeningCandidate, recoverableDetailError)
|
|
948
|
+
: detailResult?.image_evidence?.ok === false
|
|
949
|
+
? createImageCaptureFailureScreening(screeningCandidate, {
|
|
950
|
+
code: detailResult.image_evidence.error_code,
|
|
951
|
+
message: detailResult.image_evidence.error
|
|
952
|
+
})
|
|
953
|
+
: useLlmScreening
|
|
954
|
+
? llmResultToScreening(llmResult, screeningCandidate)
|
|
955
|
+
: screenCandidate(screeningCandidate, { criteria });
|
|
837
956
|
let actionDiscovery = null;
|
|
838
957
|
let postActionResult = null;
|
|
839
958
|
if (postActionEnabled && detailResult) {
|
|
@@ -875,6 +994,14 @@ export async function runRecommendWorkflow({
|
|
|
875
994
|
screening: compactScreening(screening),
|
|
876
995
|
action_discovery: compactActionDiscovery(actionDiscovery),
|
|
877
996
|
post_action: postActionResult,
|
|
997
|
+
error: recoverableDetailError
|
|
998
|
+
? compactRecoverableDetailError(recoverableDetailError)
|
|
999
|
+
: detailResult?.image_evidence?.ok === false
|
|
1000
|
+
? compactError({
|
|
1001
|
+
code: detailResult.image_evidence.error_code,
|
|
1002
|
+
message: detailResult.image_evidence.error
|
|
1003
|
+
}, "IMAGE_CAPTURE_FAILED")
|
|
1004
|
+
: null,
|
|
878
1005
|
timings
|
|
879
1006
|
};
|
|
880
1007
|
results.push(compactResult);
|
|
@@ -896,6 +1023,9 @@ export async function runRecommendWorkflow({
|
|
|
896
1023
|
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
897
1024
|
greet_count: greetCount,
|
|
898
1025
|
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
|
1026
|
+
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
1027
|
+
detail_open_failed: results.filter((item) => item.error?.code === "DETAIL_STALE_NODE" || item.error?.code === "DETAIL_OPEN_FAILED").length,
|
|
1028
|
+
transient_recovered: results.filter((item) => item.error?.code === "DETAIL_STALE_NODE" || item.error?.code === "IMAGE_CAPTURE_STALE_NODE" || item.error?.code === "IMAGE_CAPTURE_TIMEOUT" || item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT").length,
|
|
899
1029
|
unique_seen: compactInfiniteListState(listState).seen_count,
|
|
900
1030
|
scroll_count: compactInfiniteListState(listState).scroll_count,
|
|
901
1031
|
refresh_rounds: refreshRounds,
|
|
@@ -920,6 +1050,7 @@ export async function runRecommendWorkflow({
|
|
|
920
1050
|
score: screening.score
|
|
921
1051
|
},
|
|
922
1052
|
llm_screening: compactScreeningLlmResult(llmResult),
|
|
1053
|
+
error: compactResult.error,
|
|
923
1054
|
post_action: postActionResult
|
|
924
1055
|
}
|
|
925
1056
|
});
|
|
@@ -958,6 +1089,9 @@ export async function runRecommendWorkflow({
|
|
|
958
1089
|
screened: results.length,
|
|
959
1090
|
detail_opened: results.filter((item) => item.detail).length,
|
|
960
1091
|
llm_screened: results.filter((item) => item.detail?.llm_screening || item.llm_screening).length,
|
|
1092
|
+
detail_open_failed: results.filter((item) => item.error?.code === "DETAIL_STALE_NODE" || item.error?.code === "DETAIL_OPEN_FAILED").length,
|
|
1093
|
+
image_capture_failed: results.filter((item) => item.detail?.image_evidence?.ok === false).length,
|
|
1094
|
+
transient_recovered: results.filter((item) => item.error?.code === "DETAIL_STALE_NODE" || item.error?.code === "IMAGE_CAPTURE_STALE_NODE" || item.error?.code === "IMAGE_CAPTURE_TIMEOUT" || item.error?.code === "IMAGE_CAPTURE_TOTAL_TIMEOUT").length,
|
|
961
1095
|
passed: results.filter((item) => item.screening.passed).length,
|
|
962
1096
|
greet_count: greetCount,
|
|
963
1097
|
post_action_clicked: results.filter((item) => item.post_action?.action_clicked).length,
|
package/src/recommend-mcp.js
CHANGED
|
@@ -288,6 +288,66 @@ function completionReason(status) {
|
|
|
288
288
|
return null;
|
|
289
289
|
}
|
|
290
290
|
|
|
291
|
+
function normalizeErrorText(error = {}) {
|
|
292
|
+
return normalizeText([
|
|
293
|
+
error?.code || "",
|
|
294
|
+
error?.message || error || ""
|
|
295
|
+
].join(" "));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function classifyRecommendRecovery(error = {}) {
|
|
299
|
+
const text = normalizeErrorText(error);
|
|
300
|
+
if (!text) return null;
|
|
301
|
+
if (/BOSS_LOGIN_REQUIRED/i.test(text)) return "login_required";
|
|
302
|
+
if (/Could not find node with given id|No node with given id|Node is detached|Cannot find node|DETAIL_STALE_NODE|IMAGE_CAPTURE_STALE_NODE/i.test(text)) {
|
|
303
|
+
return "transient_stale_dom";
|
|
304
|
+
}
|
|
305
|
+
if (/IMAGE_CAPTURE_TIMEOUT|IMAGE_CAPTURE_TOTAL_TIMEOUT|Image fallback capture timed out/i.test(text)) {
|
|
306
|
+
return "transient_image_capture";
|
|
307
|
+
}
|
|
308
|
+
if (/(?:aborted|abort|timeout|timed out|fetch failed|socket|network|ECONNRESET|ETIMEDOUT|EAI_AGAIN)/i.test(text)) {
|
|
309
|
+
return "transient_network_or_llm";
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function buildConstrainedAgentRecovery(snapshot = {}, meta = {}, artifacts = null) {
|
|
315
|
+
const error = snapshot?.error || snapshot?.result?.error || null;
|
|
316
|
+
const classification = classifyRecommendRecovery(error);
|
|
317
|
+
if (!classification) return null;
|
|
318
|
+
const canRestartSameRequest = classification !== "login_required";
|
|
319
|
+
return {
|
|
320
|
+
policy_version: 1,
|
|
321
|
+
classification,
|
|
322
|
+
safe_for_outer_ai_agent: true,
|
|
323
|
+
recommended_action: canRestartSameRequest
|
|
324
|
+
? "restart_same_recommend_request_only"
|
|
325
|
+
: "ask_user_to_login_then_retry_same_recommend_request",
|
|
326
|
+
package_requirement: "@reconcrap/boss-recommend-mcp@>=2.0.30",
|
|
327
|
+
run_id: snapshot?.runId || snapshot?.run_id || null,
|
|
328
|
+
retryable: true,
|
|
329
|
+
same_request_sources: {
|
|
330
|
+
instruction: "run.context.instruction",
|
|
331
|
+
confirmation: "run.context.confirmation",
|
|
332
|
+
overrides: "run.context.overrides",
|
|
333
|
+
follow_up: "run.context.follow_up"
|
|
334
|
+
},
|
|
335
|
+
constraints: [
|
|
336
|
+
"Do not change instruction, criteria, filters, job, page_scope, target_count, post_action, or max_greet_count.",
|
|
337
|
+
"Do not switch to search/recruit/chat and do not add follow_up.chat.",
|
|
338
|
+
"Do not summarize, translate, or rewrite criteria.",
|
|
339
|
+
"Do not ask the user to reconfirm business choices unless Boss login is required or the stored context is missing.",
|
|
340
|
+
"Use the same Chrome debug port and recommend page route."
|
|
341
|
+
],
|
|
342
|
+
artifacts: artifacts ? {
|
|
343
|
+
run_state_path: artifacts.run_state_path || null,
|
|
344
|
+
checkpoint_path: artifacts.checkpoint_path || null,
|
|
345
|
+
report_json: artifacts.report_json || null,
|
|
346
|
+
output_csv: artifacts.output_csv || null
|
|
347
|
+
} : null
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
291
351
|
function ensureRecommendRunArtifacts(snapshot) {
|
|
292
352
|
const artifacts = getRecommendRunArtifacts(snapshot?.runId || snapshot?.run_id);
|
|
293
353
|
if (!artifacts) return null;
|
|
@@ -317,6 +377,9 @@ function ensureRecommendRunArtifacts(snapshot) {
|
|
|
317
377
|
progress: snapshot.progress || {},
|
|
318
378
|
context: snapshot.context || {},
|
|
319
379
|
checkpoint,
|
|
380
|
+
error: snapshot.error || null,
|
|
381
|
+
last_message: snapshot.error?.message || snapshot.phase || snapshot.stage || null,
|
|
382
|
+
recovery: buildConstrainedAgentRecovery(snapshot, meta, artifacts),
|
|
320
383
|
summary: artifactSummary,
|
|
321
384
|
generated_at: new Date().toISOString()
|
|
322
385
|
});
|
|
@@ -395,6 +458,7 @@ function buildLegacyRecommendResult(snapshot) {
|
|
|
395
458
|
screen_params: clonePlain(meta.parsed?.screenParams || {}, {}),
|
|
396
459
|
target_count_semantics: TARGET_COUNT_SEMANTICS,
|
|
397
460
|
error: snapshot.error || null,
|
|
461
|
+
recovery: buildConstrainedAgentRecovery(snapshot, meta, artifacts),
|
|
398
462
|
results: resultRows
|
|
399
463
|
};
|
|
400
464
|
}
|
|
@@ -409,6 +473,7 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
409
473
|
TERMINAL_STATUSES.has(snapshot.status)
|
|
410
474
|
|| snapshot.status === RUN_STATUS_PAUSED
|
|
411
475
|
) ? buildLegacyRecommendResult({ ...snapshot, progress }) : null;
|
|
476
|
+
const recovery = buildConstrainedAgentRecovery(snapshot, meta, artifacts);
|
|
412
477
|
const oldContext = {
|
|
413
478
|
workspace_root: meta.workspaceRoot || null,
|
|
414
479
|
instruction: meta.args?.instruction || "",
|
|
@@ -449,6 +514,7 @@ function normalizeRunSnapshot(snapshot) {
|
|
|
449
514
|
last_resumed_at: meta.lastResumedAt || null,
|
|
450
515
|
last_paused_at: snapshot.status === RUN_STATUS_PAUSED ? snapshot.updatedAt : null
|
|
451
516
|
},
|
|
517
|
+
recovery,
|
|
452
518
|
result: legacyResult,
|
|
453
519
|
artifacts
|
|
454
520
|
};
|
|
@@ -481,6 +547,7 @@ function persistRecommendRunSnapshot(snapshot, {
|
|
|
481
547
|
control: normalized.control,
|
|
482
548
|
resume: normalized.resume,
|
|
483
549
|
error: normalized.error,
|
|
550
|
+
recovery: normalized.recovery,
|
|
484
551
|
result: normalized.result,
|
|
485
552
|
summary: normalized.summary,
|
|
486
553
|
artifacts: normalized.artifacts
|