@reconcrap/boss-recommend-mcp 2.1.24 → 2.1.25
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/chat-runtime-config.js +7 -5
- package/src/core/browser/index.js +16 -9
- package/src/core/run/index.js +5 -4
- package/src/core/screening/index.js +291 -26
- package/src/domains/chat/action-journal.js +97 -4
- package/src/domains/recommend/actions.js +740 -36
- package/src/domains/recommend/colleague-contact.js +836 -90
- package/src/domains/recommend/detail.js +4029 -128
- package/src/domains/recommend/filters.js +46 -28
- package/src/domains/recommend/jobs.js +32 -3
- package/src/domains/recommend/location.js +93 -46
- package/src/domains/recommend/refresh.js +354 -69
- package/src/domains/recommend/run-service.js +2499 -484
- package/src/recommend-mcp.js +514 -92
package/package.json
CHANGED
|
@@ -277,11 +277,13 @@ export function resolveHumanBehaviorForRun(args = {}, config = {}) {
|
|
|
277
277
|
override.enabled = rawBehavior;
|
|
278
278
|
} else if (typeof rawBehavior === "string") {
|
|
279
279
|
applyHumanBehaviorProfileDefaults(override, rawBehavior);
|
|
280
|
-
} else if (rawBehavior && typeof rawBehavior === "object" && !Array.isArray(rawBehavior)) {
|
|
281
|
-
const rawProfile = readFirstOwn(rawBehavior, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
282
|
-
if (rawProfile !== undefined) applyHumanBehaviorProfileDefaults(override, rawProfile);
|
|
283
|
-
Object.assign(override, rawBehavior);
|
|
284
|
-
|
|
280
|
+
} else if (rawBehavior && typeof rawBehavior === "object" && !Array.isArray(rawBehavior)) {
|
|
281
|
+
const rawProfile = readFirstOwn(rawBehavior, ["profile", "mode", "behaviorProfile", "behavior_profile"]);
|
|
282
|
+
if (rawProfile !== undefined) applyHumanBehaviorProfileDefaults(override, rawProfile);
|
|
283
|
+
Object.assign(override, rawBehavior);
|
|
284
|
+
const rawRestLevel = readFirstOwn(rawBehavior, ["restLevel", "rest_level"]);
|
|
285
|
+
if (rawRestLevel !== undefined) override.restLevel = rawRestLevel;
|
|
286
|
+
}
|
|
285
287
|
|
|
286
288
|
const profile = readFirstOwn(args, ["human_behavior_profile", "humanBehaviorProfile"]);
|
|
287
289
|
if (profile !== undefined) applyHumanBehaviorProfileDefaults(override, profile);
|
|
@@ -2375,13 +2375,14 @@ export function resolveHumanClickPointForBox(box, {
|
|
|
2375
2375
|
};
|
|
2376
2376
|
}
|
|
2377
2377
|
|
|
2378
|
-
export async function clickPoint(client, x, y, {
|
|
2379
|
-
button = "left",
|
|
2380
|
-
clickCount = 1,
|
|
2381
|
-
delayMs = 80,
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2378
|
+
export async function clickPoint(client, x, y, {
|
|
2379
|
+
button = "left",
|
|
2380
|
+
clickCount = 1,
|
|
2381
|
+
delayMs = 80,
|
|
2382
|
+
moveBeforePress = true,
|
|
2383
|
+
humanRestEnabled = null,
|
|
2384
|
+
humanInteraction = null
|
|
2385
|
+
} = {}) {
|
|
2385
2386
|
const configured = getHumanInteractionConfig(client);
|
|
2386
2387
|
const mergedHumanInteraction = {
|
|
2387
2388
|
...(configured || {}),
|
|
@@ -2390,7 +2391,11 @@ export async function clickPoint(client, x, y, {
|
|
|
2390
2391
|
const humanEnabled = humanRestEnabled === true
|
|
2391
2392
|
|| humanInteraction?.enabled === true
|
|
2392
2393
|
|| (humanRestEnabled !== false && configured?.enabled === true);
|
|
2393
|
-
if (
|
|
2394
|
+
if (
|
|
2395
|
+
moveBeforePress !== false
|
|
2396
|
+
&& humanEnabled
|
|
2397
|
+
&& mergedHumanInteraction.clickMovementEnabled !== false
|
|
2398
|
+
) {
|
|
2394
2399
|
return simulateHumanClick(client, x, y, {
|
|
2395
2400
|
...mergedHumanInteraction,
|
|
2396
2401
|
button,
|
|
@@ -2398,7 +2403,9 @@ export async function clickPoint(client, x, y, {
|
|
|
2398
2403
|
delayMs
|
|
2399
2404
|
});
|
|
2400
2405
|
}
|
|
2401
|
-
|
|
2406
|
+
if (moveBeforePress !== false) {
|
|
2407
|
+
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
|
|
2408
|
+
}
|
|
2402
2409
|
await client.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button, clickCount });
|
|
2403
2410
|
if (delayMs > 0) await sleep(delayMs);
|
|
2404
2411
|
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button, clickCount });
|
package/src/core/run/index.js
CHANGED
|
@@ -276,10 +276,11 @@ export function createRunLifecycleManager({
|
|
|
276
276
|
});
|
|
277
277
|
return;
|
|
278
278
|
}
|
|
279
|
-
setStatus(entry, RUN_STATUS_FAILED, {
|
|
280
|
-
completedAt: now(),
|
|
281
|
-
error: errorDiagnostic(error, entry.run.phase)
|
|
282
|
-
|
|
279
|
+
setStatus(entry, RUN_STATUS_FAILED, {
|
|
280
|
+
completedAt: now(),
|
|
281
|
+
error: errorDiagnostic(error, entry.run.phase),
|
|
282
|
+
summary: error?.run_summary || entry.run.summary
|
|
283
|
+
});
|
|
283
284
|
}
|
|
284
285
|
}
|
|
285
286
|
|
|
@@ -37,6 +37,33 @@ const GENDER_CODE_MAP = {
|
|
|
37
37
|
2: "女"
|
|
38
38
|
};
|
|
39
39
|
|
|
40
|
+
const RECOMMEND_NETWORK_CANDIDATE_ID_KEYS = new Set([
|
|
41
|
+
"candidateid",
|
|
42
|
+
"encryptjid",
|
|
43
|
+
"encryptgeekid",
|
|
44
|
+
"gid",
|
|
45
|
+
"geekid",
|
|
46
|
+
"jid",
|
|
47
|
+
"securityid"
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
const RECOMMEND_PLACEHOLDER_PROFILE_NAMES = new Set([
|
|
51
|
+
"-",
|
|
52
|
+
"--",
|
|
53
|
+
"boss用户",
|
|
54
|
+
"候选人",
|
|
55
|
+
"匿名",
|
|
56
|
+
"匿名候选人",
|
|
57
|
+
"匿名用户",
|
|
58
|
+
"暂无",
|
|
59
|
+
"未知",
|
|
60
|
+
"未填写",
|
|
61
|
+
"用户",
|
|
62
|
+
"求职者",
|
|
63
|
+
"牛人",
|
|
64
|
+
"保密"
|
|
65
|
+
]);
|
|
66
|
+
|
|
40
67
|
const LLM_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "auto", "current"]);
|
|
41
68
|
const LLM_SCREENING_STRATEGIES = new Set(["single_pass", "fast_first_verified"]);
|
|
42
69
|
const FAST_FIRST_DEFAULT_FAST_MAX_TOKENS = 384;
|
|
@@ -756,6 +783,177 @@ function collectObjects(root, {
|
|
|
756
783
|
return objects;
|
|
757
784
|
}
|
|
758
785
|
|
|
786
|
+
function normalizeRecommendBindingCandidateId(value = "") {
|
|
787
|
+
if (!["string", "number"].includes(typeof value)) return "";
|
|
788
|
+
const raw = normalizeText(value);
|
|
789
|
+
if (!raw) return "";
|
|
790
|
+
try {
|
|
791
|
+
return normalizeText(decodeURIComponent(raw));
|
|
792
|
+
} catch {
|
|
793
|
+
return raw;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function normalizeRecommendBindingName(value = "") {
|
|
798
|
+
return normalizeText(value).normalize("NFKC").toLocaleLowerCase("zh-CN");
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function isRecommendPlaceholderProfileName(value = "") {
|
|
802
|
+
const normalized = normalizeRecommendBindingName(value);
|
|
803
|
+
return !normalized
|
|
804
|
+
|| RECOMMEND_PLACEHOLDER_PROFILE_NAMES.has(normalized)
|
|
805
|
+
|| /^[-—–_**]+$/u.test(normalized);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
function canonicalRecommendNetworkCandidateIdKey(value = "") {
|
|
809
|
+
return String(value || "").replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function collectRecommendNetworkCandidateIdEvidence(networkBody = {}, profileRoots = []) {
|
|
813
|
+
const evidence = [];
|
|
814
|
+
const seen = new Set();
|
|
815
|
+
const add = (value, source) => {
|
|
816
|
+
const candidateId = normalizeRecommendBindingCandidateId(value);
|
|
817
|
+
if (!candidateId) return;
|
|
818
|
+
const key = `${candidateId}\u0000${source}`;
|
|
819
|
+
if (seen.has(key)) return;
|
|
820
|
+
seen.add(key);
|
|
821
|
+
evidence.push({ candidate_id: candidateId, source });
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
const rawUrl = normalizeText(networkBody?.url);
|
|
825
|
+
if (rawUrl) {
|
|
826
|
+
try {
|
|
827
|
+
const parsedUrl = new URL(rawUrl, "https://www.zhipin.com");
|
|
828
|
+
if (/\/geek\/info(?:\/|$)/i.test(parsedUrl.pathname)) {
|
|
829
|
+
for (const [key, value] of parsedUrl.searchParams.entries()) {
|
|
830
|
+
const canonicalKey = canonicalRecommendNetworkCandidateIdKey(key);
|
|
831
|
+
if (RECOMMEND_NETWORK_CANDIDATE_ID_KEYS.has(canonicalKey)) {
|
|
832
|
+
add(value, `url_query:${canonicalKey}`);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
} catch {
|
|
837
|
+
// An unparseable response URL cannot prove candidate identity.
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
for (const object of profileRoots.filter(isPlainObject)) {
|
|
842
|
+
for (const [key, value] of Object.entries(object)) {
|
|
843
|
+
const canonicalKey = canonicalRecommendNetworkCandidateIdKey(key);
|
|
844
|
+
if (!RECOMMEND_NETWORK_CANDIDATE_ID_KEYS.has(canonicalKey)) continue;
|
|
845
|
+
add(value, `payload_profile_field:${canonicalKey}`);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
return evidence.slice(0, 20);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
function collectRecommendResponseCandidateIdDiagnostics(parsedObjects = []) {
|
|
853
|
+
const ids = [];
|
|
854
|
+
for (const object of collectObjects(parsedObjects, { maxObjects: 800, maxDepth: 10 })) {
|
|
855
|
+
for (const [key, value] of Object.entries(object)) {
|
|
856
|
+
const canonicalKey = canonicalRecommendNetworkCandidateIdKey(key);
|
|
857
|
+
if (!RECOMMEND_NETWORK_CANDIDATE_ID_KEYS.has(canonicalKey)) continue;
|
|
858
|
+
const candidateId = normalizeRecommendBindingCandidateId(value);
|
|
859
|
+
if (candidateId) ids.push(candidateId);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return [...new Set(ids)].slice(0, 10);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function bindRecommendNetworkProfileToCard(parsedProfile, cardCandidate) {
|
|
866
|
+
if (!parsedProfile?.ok) return parsedProfile;
|
|
867
|
+
const expectedCandidateId = normalizeRecommendBindingCandidateId(cardCandidate?.id);
|
|
868
|
+
const expectedName = normalizeText(cardCandidate?.identity?.name);
|
|
869
|
+
const normalizedExpectedName = normalizeRecommendBindingName(expectedName);
|
|
870
|
+
const profileName = normalizeText(parsedProfile?.profile?.identity?.name);
|
|
871
|
+
const normalizedProfileName = normalizeRecommendBindingName(profileName);
|
|
872
|
+
const candidateIdEvidence = Array.isArray(parsedProfile.candidate_id_evidence)
|
|
873
|
+
? parsedProfile.candidate_id_evidence
|
|
874
|
+
: [];
|
|
875
|
+
const observedCandidateIds = [...new Set(candidateIdEvidence.map((item) => (
|
|
876
|
+
normalizeRecommendBindingCandidateId(item?.candidate_id)
|
|
877
|
+
)).filter(Boolean))].slice(0, 5);
|
|
878
|
+
const responseObservedCandidateIds = Array.isArray(parsedProfile.response_candidate_id_diagnostics)
|
|
879
|
+
? parsedProfile.response_candidate_id_diagnostics.slice(0, 10)
|
|
880
|
+
: [];
|
|
881
|
+
const matchedCandidateIdEvidence = candidateIdEvidence.find((item) => (
|
|
882
|
+
normalizeRecommendBindingCandidateId(item?.candidate_id) === expectedCandidateId
|
|
883
|
+
)) || null;
|
|
884
|
+
const candidateIdConflict = observedCandidateIds.length > 1;
|
|
885
|
+
const candidateIdVerified = Boolean(
|
|
886
|
+
expectedCandidateId
|
|
887
|
+
&& observedCandidateIds.length === 1
|
|
888
|
+
&& observedCandidateIds[0] === expectedCandidateId
|
|
889
|
+
);
|
|
890
|
+
const profileNameUsable = !isRecommendPlaceholderProfileName(profileName);
|
|
891
|
+
const expectedNameUsable = !isRecommendPlaceholderProfileName(expectedName);
|
|
892
|
+
const nameVerified = Boolean(
|
|
893
|
+
expectedNameUsable
|
|
894
|
+
&& profileNameUsable
|
|
895
|
+
&& normalizedProfileName === normalizedExpectedName
|
|
896
|
+
);
|
|
897
|
+
let reason = null;
|
|
898
|
+
if (!expectedCandidateId) reason = "expected_candidate_id_missing";
|
|
899
|
+
else if (!candidateIdEvidence.length) reason = "network_candidate_id_evidence_missing";
|
|
900
|
+
else if (candidateIdConflict) reason = "network_candidate_id_conflict";
|
|
901
|
+
else if (!candidateIdVerified) reason = "network_candidate_id_mismatch";
|
|
902
|
+
else if (!expectedNameUsable) reason = "expected_card_name_placeholder_or_missing";
|
|
903
|
+
else if (!profileNameUsable) reason = "network_profile_name_placeholder_or_missing";
|
|
904
|
+
else if (!nameVerified) reason = "network_profile_name_mismatch";
|
|
905
|
+
|
|
906
|
+
const binding = {
|
|
907
|
+
required: true,
|
|
908
|
+
verified: !reason,
|
|
909
|
+
reason,
|
|
910
|
+
expected_candidate_id: expectedCandidateId || null,
|
|
911
|
+
matched_candidate_id_source: candidateIdVerified
|
|
912
|
+
? matchedCandidateIdEvidence?.source || null
|
|
913
|
+
: null,
|
|
914
|
+
observed_candidate_id_count: candidateIdEvidence.length,
|
|
915
|
+
observed_candidate_ids: observedCandidateIds,
|
|
916
|
+
response_observed_candidate_ids: responseObservedCandidateIds,
|
|
917
|
+
expected_name: expectedName || null,
|
|
918
|
+
profile_name: profileName || null,
|
|
919
|
+
exact_name: nameVerified
|
|
920
|
+
};
|
|
921
|
+
if (!reason) {
|
|
922
|
+
return {
|
|
923
|
+
...parsedProfile,
|
|
924
|
+
candidate_binding: binding
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const { profile: _excludedProfile, ...diagnostic } = parsedProfile;
|
|
929
|
+
return {
|
|
930
|
+
...diagnostic,
|
|
931
|
+
ok: false,
|
|
932
|
+
error: "RECOMMEND_NETWORK_PROFILE_CANDIDATE_BINDING_UNVERIFIED",
|
|
933
|
+
candidate_binding: binding
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
function compactNetworkProfileCandidateBinding(binding = null) {
|
|
938
|
+
if (!binding) return null;
|
|
939
|
+
return {
|
|
940
|
+
verified: binding.verified === true,
|
|
941
|
+
reason: binding.reason || null,
|
|
942
|
+
expected_candidate_id: binding.expected_candidate_id || null,
|
|
943
|
+
matched_candidate_id_source: binding.matched_candidate_id_source || null,
|
|
944
|
+
observed_candidate_id_count: Number(binding.observed_candidate_id_count) || 0,
|
|
945
|
+
observed_candidate_ids: Array.isArray(binding.observed_candidate_ids)
|
|
946
|
+
? binding.observed_candidate_ids.slice(0, 5)
|
|
947
|
+
: [],
|
|
948
|
+
response_observed_candidate_ids: Array.isArray(binding.response_observed_candidate_ids)
|
|
949
|
+
? binding.response_observed_candidate_ids.slice(0, 10)
|
|
950
|
+
: [],
|
|
951
|
+
expected_name: binding.expected_name || null,
|
|
952
|
+
profile_name: binding.profile_name || null,
|
|
953
|
+
exact_name: binding.exact_name === true
|
|
954
|
+
};
|
|
955
|
+
}
|
|
956
|
+
|
|
759
957
|
function joinRange(start, end, fallback = "") {
|
|
760
958
|
const left = parseDateLike(start);
|
|
761
959
|
const right = parseDateLike(end);
|
|
@@ -889,8 +1087,29 @@ function resolveBossGeekDetail(payload = {}) {
|
|
|
889
1087
|
return found || { sourceKey: "", detail: null };
|
|
890
1088
|
}
|
|
891
1089
|
|
|
1090
|
+
function resolveBossChatGeekInfoData(payload = {}) {
|
|
1091
|
+
return payload?.zpData?.data
|
|
1092
|
+
|| payload?.data
|
|
1093
|
+
|| payload?.zpData?.geekInfo
|
|
1094
|
+
|| payload?.geekInfo
|
|
1095
|
+
|| null;
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
function resolveBossChatHistoryResumeObject(payload = {}) {
|
|
1099
|
+
const messages = normalizeList(payload?.zpData?.messages).length
|
|
1100
|
+
? normalizeList(payload?.zpData?.messages)
|
|
1101
|
+
: normalizeList(payload?.messages).length
|
|
1102
|
+
? normalizeList(payload?.messages)
|
|
1103
|
+
: normalizeList(payload?.data?.messages).length
|
|
1104
|
+
? normalizeList(payload?.data?.messages)
|
|
1105
|
+
: normalizeList(payload?.zpData?.data?.messages);
|
|
1106
|
+
return messages
|
|
1107
|
+
.map((message) => message?.body?.resume)
|
|
1108
|
+
.find((resume) => resume && typeof resume === "object") || null;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
892
1111
|
function extractBossChatGeekInfo(payload = {}) {
|
|
893
|
-
const data = payload
|
|
1112
|
+
const data = resolveBossChatGeekInfoData(payload);
|
|
894
1113
|
if (!data || typeof data !== "object") return null;
|
|
895
1114
|
if (!isBossChatProfileShape(data)) return null;
|
|
896
1115
|
const educationList = normalizeList(data.eduExpList);
|
|
@@ -961,17 +1180,7 @@ function extractBossChatGeekInfo(payload = {}) {
|
|
|
961
1180
|
}
|
|
962
1181
|
|
|
963
1182
|
function extractBossChatHistoryResume(payload = {}) {
|
|
964
|
-
const
|
|
965
|
-
? normalizeList(payload?.zpData?.messages)
|
|
966
|
-
: normalizeList(payload?.messages).length
|
|
967
|
-
? normalizeList(payload?.messages)
|
|
968
|
-
: normalizeList(payload?.data?.messages).length
|
|
969
|
-
? normalizeList(payload?.data?.messages)
|
|
970
|
-
: normalizeList(payload?.zpData?.data?.messages);
|
|
971
|
-
const resumes = messages
|
|
972
|
-
.map((message) => message?.body?.resume)
|
|
973
|
-
.filter((resume) => resume && typeof resume === "object");
|
|
974
|
-
const resume = resumes[0];
|
|
1183
|
+
const resume = resolveBossChatHistoryResumeObject(payload);
|
|
975
1184
|
if (!resume) return null;
|
|
976
1185
|
const user = resume.user || {};
|
|
977
1186
|
const educationList = normalizeList(resume.education);
|
|
@@ -1177,6 +1386,10 @@ export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
|
1177
1386
|
tryParseJson(text),
|
|
1178
1387
|
...tryParseEmbeddedJsonObjects(text)
|
|
1179
1388
|
].filter((item) => item && typeof item === "object");
|
|
1389
|
+
const responseCandidateIdDiagnostics = collectRecommendResponseCandidateIdDiagnostics(
|
|
1390
|
+
parsedObjects
|
|
1391
|
+
);
|
|
1392
|
+
let candidateIdEvidence = collectRecommendNetworkCandidateIdEvidence(networkBody, []);
|
|
1180
1393
|
if (!parsedObjects.length) {
|
|
1181
1394
|
const htmlText = /<html|<body|<div|<section|<script/i.test(text) ? htmlToText(text) : "";
|
|
1182
1395
|
if (htmlText && htmlText.length > 80) {
|
|
@@ -1191,6 +1404,8 @@ export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
|
1191
1404
|
status: networkBody.status ?? null,
|
|
1192
1405
|
mimeType: networkBody.mimeType || null,
|
|
1193
1406
|
text_length: text.length,
|
|
1407
|
+
candidate_id_evidence: candidateIdEvidence,
|
|
1408
|
+
response_candidate_id_diagnostics: responseCandidateIdDiagnostics,
|
|
1194
1409
|
profile: {
|
|
1195
1410
|
identity: candidate.identity,
|
|
1196
1411
|
tags: candidate.tags,
|
|
@@ -1206,21 +1421,49 @@ export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
|
1206
1421
|
return {
|
|
1207
1422
|
ok: false,
|
|
1208
1423
|
error: "NETWORK_BODY_NOT_JSON",
|
|
1209
|
-
text_length: text.length
|
|
1424
|
+
text_length: text.length,
|
|
1425
|
+
candidate_id_evidence: candidateIdEvidence,
|
|
1426
|
+
response_candidate_id_diagnostics: responseCandidateIdDiagnostics
|
|
1210
1427
|
};
|
|
1211
1428
|
}
|
|
1212
1429
|
let profile = null;
|
|
1213
1430
|
let parsed = parsedObjects[0];
|
|
1431
|
+
let profileBindingRoots = [];
|
|
1214
1432
|
for (const candidateObject of parsedObjects) {
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1433
|
+
const geekDetail = resolveBossGeekDetail(candidateObject).detail;
|
|
1434
|
+
if (geekDetail) {
|
|
1435
|
+
profile = extractBossGeekDetailInfo(candidateObject);
|
|
1436
|
+
profileBindingRoots = [
|
|
1437
|
+
geekDetail,
|
|
1438
|
+
geekDetail.geekBaseInfo,
|
|
1439
|
+
geekDetail.baseInfo,
|
|
1440
|
+
geekDetail.base
|
|
1441
|
+
].filter(isPlainObject);
|
|
1442
|
+
} else {
|
|
1443
|
+
const chatData = resolveBossChatGeekInfoData(candidateObject);
|
|
1444
|
+
if (isBossChatProfileShape(chatData)) {
|
|
1445
|
+
profile = extractBossChatGeekInfo(candidateObject);
|
|
1446
|
+
profileBindingRoots = [chatData];
|
|
1447
|
+
} else {
|
|
1448
|
+
const historyResume = resolveBossChatHistoryResumeObject(candidateObject);
|
|
1449
|
+
if (historyResume) {
|
|
1450
|
+
profile = extractBossChatHistoryResume(candidateObject);
|
|
1451
|
+
profileBindingRoots = [historyResume, historyResume.user].filter(isPlainObject);
|
|
1452
|
+
} else {
|
|
1453
|
+
profile = extractBossProfileRecursively(candidateObject);
|
|
1454
|
+
profileBindingRoots = [];
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1219
1458
|
if (profile) {
|
|
1220
1459
|
parsed = candidateObject;
|
|
1221
1460
|
break;
|
|
1222
1461
|
}
|
|
1223
1462
|
}
|
|
1463
|
+
candidateIdEvidence = collectRecommendNetworkCandidateIdEvidence(
|
|
1464
|
+
networkBody,
|
|
1465
|
+
profileBindingRoots
|
|
1466
|
+
);
|
|
1224
1467
|
if (!profile) {
|
|
1225
1468
|
const encryptedPayload = parsedObjects.find((item) => (
|
|
1226
1469
|
normalizeText(item?.zpData?.encryptGeekDetailInfo || item?.encryptGeekDetailInfo || "")
|
|
@@ -1234,7 +1477,9 @@ export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
|
1234
1477
|
zpData_keys: Object.keys(parsed?.zpData || {}).slice(0, 50),
|
|
1235
1478
|
data_keys: Object.keys(parsed?.data || parsed?.zpData?.data || {}).slice(0, 50),
|
|
1236
1479
|
encrypted_resume: Boolean(encryptedPayload),
|
|
1237
|
-
encrypted_resume_length: normalizeText(encryptedPayload?.zpData?.encryptGeekDetailInfo || encryptedPayload?.encryptGeekDetailInfo || "").length
|
|
1480
|
+
encrypted_resume_length: normalizeText(encryptedPayload?.zpData?.encryptGeekDetailInfo || encryptedPayload?.encryptGeekDetailInfo || "").length,
|
|
1481
|
+
candidate_id_evidence: candidateIdEvidence,
|
|
1482
|
+
response_candidate_id_diagnostics: responseCandidateIdDiagnostics
|
|
1238
1483
|
};
|
|
1239
1484
|
}
|
|
1240
1485
|
return {
|
|
@@ -1243,6 +1488,8 @@ export function extractBossProfileFromNetworkBody(networkBody = {}) {
|
|
|
1243
1488
|
status: networkBody.status ?? null,
|
|
1244
1489
|
mimeType: networkBody.mimeType || null,
|
|
1245
1490
|
text_length: text.length,
|
|
1491
|
+
candidate_id_evidence: candidateIdEvidence,
|
|
1492
|
+
response_candidate_id_diagnostics: responseCandidateIdDiagnostics,
|
|
1246
1493
|
profile
|
|
1247
1494
|
};
|
|
1248
1495
|
}
|
|
@@ -1269,19 +1516,34 @@ export function buildScreeningCandidateFromDetail({
|
|
|
1269
1516
|
source = "live-cdp-detail",
|
|
1270
1517
|
metadata = {}
|
|
1271
1518
|
} = {}) {
|
|
1272
|
-
const
|
|
1519
|
+
const normalizedDomain = normalizeDomain(domain);
|
|
1520
|
+
const extractedNetworkProfiles = networkBodies.map(extractBossProfileFromNetworkBody);
|
|
1521
|
+
const parsedNetworkProfiles = normalizedDomain === "recommend"
|
|
1522
|
+
? extractedNetworkProfiles.map((item) => bindRecommendNetworkProfileToCard(item, cardCandidate))
|
|
1523
|
+
: extractedNetworkProfiles;
|
|
1273
1524
|
const successfulProfiles = parsedNetworkProfiles.filter((item) => item.ok).map((item) => item.profile);
|
|
1274
1525
|
const networkText = successfulProfiles.map((profile) => profile.text).filter(Boolean).join("\n\n");
|
|
1275
1526
|
const networkIdentity = mergeCandidateProfiles(
|
|
1276
1527
|
...successfulProfiles.map((profile) => profile.identity)
|
|
1277
1528
|
);
|
|
1278
1529
|
const networkTags = unique(successfulProfiles.flatMap((profile) => profile.tags || []));
|
|
1279
|
-
const combinedIdentity =
|
|
1280
|
-
networkIdentity
|
|
1281
|
-
cardCandidate?.identity
|
|
1282
|
-
|
|
1530
|
+
const combinedIdentity = normalizedDomain === "recommend"
|
|
1531
|
+
? mergeCandidateProfiles(cardCandidate?.identity, networkIdentity)
|
|
1532
|
+
: mergeCandidateProfiles(networkIdentity, cardCandidate?.identity);
|
|
1533
|
+
const networkProfileBinding = normalizedDomain === "recommend"
|
|
1534
|
+
? {
|
|
1535
|
+
required: true,
|
|
1536
|
+
accepted_count: successfulProfiles.length,
|
|
1537
|
+
rejected_count: parsedNetworkProfiles.filter((item) => (
|
|
1538
|
+
item?.error === "RECOMMEND_NETWORK_PROFILE_CANDIDATE_BINDING_UNVERIFIED"
|
|
1539
|
+
)).length,
|
|
1540
|
+
profiles: parsedNetworkProfiles.map((item) => compactNetworkProfileCandidateBinding(
|
|
1541
|
+
item?.candidate_binding
|
|
1542
|
+
)).filter(Boolean)
|
|
1543
|
+
}
|
|
1544
|
+
: null;
|
|
1283
1545
|
const candidate = normalizeCandidateProfile({
|
|
1284
|
-
domain,
|
|
1546
|
+
domain: normalizedDomain,
|
|
1285
1547
|
source,
|
|
1286
1548
|
id: cardCandidate?.id,
|
|
1287
1549
|
href: cardCandidate?.links?.href,
|
|
@@ -1300,19 +1562,22 @@ export function buildScreeningCandidateFromDetail({
|
|
|
1300
1562
|
...metadata,
|
|
1301
1563
|
card_candidate_source: cardCandidate?.source || null,
|
|
1302
1564
|
network_profile_count: successfulProfiles.length,
|
|
1565
|
+
network_profile_binding: networkProfileBinding,
|
|
1303
1566
|
network_profiles: parsedNetworkProfiles.map((item) => ({
|
|
1304
1567
|
ok: item.ok,
|
|
1305
1568
|
url: item.url,
|
|
1306
1569
|
status: item.status,
|
|
1307
1570
|
error: item.error,
|
|
1308
1571
|
text_length: item.text_length,
|
|
1309
|
-
source_keys: item.profile?.source_keys || null
|
|
1572
|
+
source_keys: item.profile?.source_keys || null,
|
|
1573
|
+
candidate_binding: compactNetworkProfileCandidateBinding(item.candidate_binding)
|
|
1310
1574
|
}))
|
|
1311
1575
|
}
|
|
1312
1576
|
});
|
|
1313
1577
|
return {
|
|
1314
1578
|
candidate,
|
|
1315
|
-
parsed_network_profiles: parsedNetworkProfiles
|
|
1579
|
+
parsed_network_profiles: parsedNetworkProfiles,
|
|
1580
|
+
network_profile_binding: networkProfileBinding
|
|
1316
1581
|
};
|
|
1317
1582
|
}
|
|
1318
1583
|
|
|
@@ -239,14 +239,31 @@ function appendRunId(runIds, runId) {
|
|
|
239
239
|
|
|
240
240
|
const EVIDENCE_KEYS = new Set([
|
|
241
241
|
"action",
|
|
242
|
+
"action_hit_test_attempt_count",
|
|
243
|
+
"action_hit_test_last_hit_backend_node_id",
|
|
244
|
+
"action_hit_test_reason",
|
|
242
245
|
"active_candidate_id",
|
|
243
246
|
"ask_error",
|
|
244
247
|
"ask_ok",
|
|
245
248
|
"confirm_confirmed",
|
|
246
249
|
"confirm_error",
|
|
250
|
+
"control_backend_node_id",
|
|
251
|
+
"control_center_x",
|
|
252
|
+
"control_center_y",
|
|
253
|
+
"control_label",
|
|
254
|
+
"control_node_id",
|
|
255
|
+
"control_root_backend_node_id",
|
|
256
|
+
"control_root_node_id",
|
|
257
|
+
"control_rect_height",
|
|
258
|
+
"control_rect_width",
|
|
259
|
+
"control_rect_x",
|
|
260
|
+
"control_rect_y",
|
|
261
|
+
"control_root",
|
|
247
262
|
"greeting_baseline_count",
|
|
248
263
|
"greeting_evidence_readable",
|
|
249
264
|
"message_observed",
|
|
265
|
+
"operation_id",
|
|
266
|
+
"pre_input_cdp_method",
|
|
250
267
|
"request_confirmation_source",
|
|
251
268
|
"request_ready_state_observed",
|
|
252
269
|
"reason",
|
|
@@ -271,6 +288,37 @@ function sanitizeEvidence(value = {}) {
|
|
|
271
288
|
return result;
|
|
272
289
|
}
|
|
273
290
|
|
|
291
|
+
function recordRevision(record) {
|
|
292
|
+
if (!record) return 0;
|
|
293
|
+
const historyLength = Array.isArray(record.history) ? record.history.length : 0;
|
|
294
|
+
const storedRevision = record.revision;
|
|
295
|
+
if (storedRevision == null) return historyLength;
|
|
296
|
+
const normalizedRevision = Number(storedRevision);
|
|
297
|
+
if (
|
|
298
|
+
!Number.isSafeInteger(normalizedRevision)
|
|
299
|
+
|| normalizedRevision < 1
|
|
300
|
+
|| normalizedRevision !== historyLength
|
|
301
|
+
) {
|
|
302
|
+
throw journalError(
|
|
303
|
+
"CHAT_ACTION_JOURNAL_CORRUPT",
|
|
304
|
+
"Boss chat action journal revision does not exactly match its append-only history.",
|
|
305
|
+
{
|
|
306
|
+
stored_revision: storedRevision,
|
|
307
|
+
history_length: historyLength
|
|
308
|
+
}
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return normalizedRevision;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function withRecordRevision(record) {
|
|
315
|
+
if (!record) return null;
|
|
316
|
+
return {
|
|
317
|
+
...record,
|
|
318
|
+
revision: recordRevision(record)
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
274
322
|
export function hashChatActionGreeting(greeting) {
|
|
275
323
|
const normalized = normalizeGreeting(greeting);
|
|
276
324
|
if (!normalized) {
|
|
@@ -318,7 +366,7 @@ export function createChatActionJournal({
|
|
|
318
366
|
const filePath = path.join(resolvedBaseDir, `${identity.actionKey}.json`);
|
|
319
367
|
const stored = readJsonRecord(filePath);
|
|
320
368
|
if (!stored) return null;
|
|
321
|
-
return cloneRecord(validateStoredRecord(stored, identity, filePath));
|
|
369
|
+
return cloneRecord(withRecordRevision(validateStoredRecord(stored, identity, filePath)));
|
|
322
370
|
}
|
|
323
371
|
|
|
324
372
|
function transition({
|
|
@@ -327,7 +375,10 @@ export function createChatActionJournal({
|
|
|
327
375
|
state,
|
|
328
376
|
runId = "",
|
|
329
377
|
greeting,
|
|
330
|
-
evidence = {}
|
|
378
|
+
evidence = {},
|
|
379
|
+
recordIdempotent = false,
|
|
380
|
+
expectedUpdatedAt = null,
|
|
381
|
+
expectedRevision = null
|
|
331
382
|
} = {}) {
|
|
332
383
|
const identity = createChatActionIdentity({ scope, candidateId });
|
|
333
384
|
const nextState = requireState(state);
|
|
@@ -337,7 +388,47 @@ export function createChatActionJournal({
|
|
|
337
388
|
const lockHandle = acquireRecordLock(lockPath);
|
|
338
389
|
try {
|
|
339
390
|
const stored = readJsonRecord(filePath);
|
|
340
|
-
const existing = stored
|
|
391
|
+
const existing = stored
|
|
392
|
+
? withRecordRevision(validateStoredRecord(stored, identity, filePath))
|
|
393
|
+
: null;
|
|
394
|
+
const observedRevision = recordRevision(existing);
|
|
395
|
+
if (expectedRevision != null) {
|
|
396
|
+
const normalizedExpectedRevision = Number(expectedRevision);
|
|
397
|
+
if (!Number.isSafeInteger(normalizedExpectedRevision) || normalizedExpectedRevision < 0) {
|
|
398
|
+
throw journalError(
|
|
399
|
+
"CHAT_ACTION_JOURNAL_REVISION_INVALID",
|
|
400
|
+
"Boss action journal expected revision must be a non-negative safe integer.",
|
|
401
|
+
{ expected_revision: expectedRevision }
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
if (observedRevision !== normalizedExpectedRevision) {
|
|
405
|
+
throw journalError(
|
|
406
|
+
"CHAT_ACTION_JOURNAL_CONCURRENT_UPDATE",
|
|
407
|
+
"Boss action journal changed after it was read; this operation does not own the outbound action.",
|
|
408
|
+
{
|
|
409
|
+
action_key: identity.actionKey,
|
|
410
|
+
expected_revision: normalizedExpectedRevision,
|
|
411
|
+
observed_revision: observedRevision,
|
|
412
|
+
expected_updated_at: normalizeText(expectedUpdatedAt) || null,
|
|
413
|
+
observed_updated_at: normalizeText(existing?.updated_at) || null
|
|
414
|
+
}
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
if (
|
|
419
|
+
expectedUpdatedAt != null
|
|
420
|
+
&& normalizeText(existing?.updated_at) !== normalizeText(expectedUpdatedAt)
|
|
421
|
+
) {
|
|
422
|
+
throw journalError(
|
|
423
|
+
"CHAT_ACTION_JOURNAL_CONCURRENT_UPDATE",
|
|
424
|
+
"Boss action journal changed after it was read; this operation does not own the outbound action.",
|
|
425
|
+
{
|
|
426
|
+
action_key: identity.actionKey,
|
|
427
|
+
expected_updated_at: normalizeText(expectedUpdatedAt) || null,
|
|
428
|
+
observed_updated_at: normalizeText(existing?.updated_at) || null
|
|
429
|
+
}
|
|
430
|
+
);
|
|
431
|
+
}
|
|
341
432
|
const suppliedGreeting = greeting == null ? "" : normalizeGreeting(greeting);
|
|
342
433
|
const suppliedGreetingSha256 = suppliedGreeting ? hashChatActionGreeting(suppliedGreeting) : "";
|
|
343
434
|
|
|
@@ -369,7 +460,7 @@ export function createChatActionJournal({
|
|
|
369
460
|
}
|
|
370
461
|
);
|
|
371
462
|
}
|
|
372
|
-
if (existing?.state === nextState) {
|
|
463
|
+
if (existing?.state === nextState && recordIdempotent !== true) {
|
|
373
464
|
return {
|
|
374
465
|
changed: false,
|
|
375
466
|
idempotent: true,
|
|
@@ -384,6 +475,7 @@ export function createChatActionJournal({
|
|
|
384
475
|
const record = existing ? {
|
|
385
476
|
...existing,
|
|
386
477
|
state: nextState,
|
|
478
|
+
revision: observedRevision + 1,
|
|
387
479
|
updated_at: at,
|
|
388
480
|
evidence: {
|
|
389
481
|
...(existing.evidence && typeof existing.evidence === "object" ? existing.evidence : {}),
|
|
@@ -407,6 +499,7 @@ export function createChatActionJournal({
|
|
|
407
499
|
scope_sha256: identity.scopeSha256,
|
|
408
500
|
candidate_id: identity.candidateId,
|
|
409
501
|
state: nextState,
|
|
502
|
+
revision: 1,
|
|
410
503
|
greeting_sha256: suppliedGreetingSha256,
|
|
411
504
|
evidence: safeEvidence,
|
|
412
505
|
created_at: at,
|