@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
|
@@ -764,7 +764,7 @@ export async function confirmFilterPanel(client, frameNodeId, {
|
|
|
764
764
|
};
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
-
function normalizeStickyVerificationSpecs(options = {}) {
|
|
767
|
+
function normalizeStickyVerificationSpecs(options = {}) {
|
|
768
768
|
const source = Array.isArray(options.filterGroups) && options.filterGroups.length
|
|
769
769
|
? options.filterGroups
|
|
770
770
|
: [options];
|
|
@@ -774,7 +774,18 @@ function normalizeStickyVerificationSpecs(options = {}) {
|
|
|
774
774
|
selectAllLabels: spec.selectAllLabels !== false,
|
|
775
775
|
allowUnlimited: spec.allowUnlimited === true
|
|
776
776
|
})).filter((spec) => spec.group && spec.labels.length);
|
|
777
|
-
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
function normalizeUniqueFilterLabels(labels = []) {
|
|
780
|
+
return [...new Set(labels.map(normalizeFilterOptionLabel).filter(Boolean))];
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function activeFilterLabelsMatchRequestedExactly(requestedLabels = [], activeLabels = []) {
|
|
784
|
+
const requested = normalizeUniqueFilterLabels(requestedLabels);
|
|
785
|
+
const active = normalizeUniqueFilterLabels(activeLabels);
|
|
786
|
+
return requested.length === active.length
|
|
787
|
+
&& requested.every((label) => active.includes(label));
|
|
788
|
+
}
|
|
778
789
|
|
|
779
790
|
function findUnavailableGroup(unavailableGroups = [], group = "") {
|
|
780
791
|
return unavailableGroups.find((item) => item?.group === group) || null;
|
|
@@ -802,20 +813,27 @@ export async function verifyFilterGroupsSticky(client, frameNodeId, {
|
|
|
802
813
|
} = {}) {
|
|
803
814
|
const normalizedSpecs = specs.filter((spec) => spec?.group && Array.isArray(spec.labels) && spec.labels.length);
|
|
804
815
|
const groups = [];
|
|
805
|
-
const availableSpecs = [];
|
|
806
|
-
for (const spec of normalizedSpecs) {
|
|
807
|
-
const unavailable = findUnavailableGroup(unavailableGroups, spec.group);
|
|
808
|
-
if (unavailable) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
816
|
+
const availableSpecs = [];
|
|
817
|
+
for (const spec of normalizedSpecs) {
|
|
818
|
+
const unavailable = findUnavailableGroup(unavailableGroups, spec.group);
|
|
819
|
+
if (unavailable) {
|
|
820
|
+
const requestedLabels = normalizeUniqueFilterLabels(spec.labels);
|
|
821
|
+
const reason = unavailable.reason || "control_unavailable_default";
|
|
822
|
+
const unavailableDefaultVerified = spec.group === RECOMMEND_ACTIVITY_GROUP
|
|
823
|
+
&& spec.allowUnlimited === true
|
|
824
|
+
&& requestedLabels.length === 1
|
|
825
|
+
&& requestedLabels[0] === normalizeFilterOptionLabel("不限")
|
|
826
|
+
&& /control_unavailable_default$/.test(reason);
|
|
827
|
+
groups.push({
|
|
828
|
+
group: spec.group,
|
|
829
|
+
requested_labels: requestedLabels,
|
|
830
|
+
active_labels: [],
|
|
831
|
+
verified: unavailableDefaultVerified,
|
|
832
|
+
unavailable: true,
|
|
833
|
+
reason
|
|
834
|
+
});
|
|
835
|
+
} else {
|
|
836
|
+
availableSpecs.push(spec);
|
|
819
837
|
}
|
|
820
838
|
}
|
|
821
839
|
|
|
@@ -824,18 +842,18 @@ export async function verifyFilterGroupsSticky(client, frameNodeId, {
|
|
|
824
842
|
if (availableSpecs.length) {
|
|
825
843
|
reopen = await openFilterPanel(client, frameNodeId);
|
|
826
844
|
for (const spec of availableSpecs) {
|
|
827
|
-
const options = await listFilterOptions(client, frameNodeId, {
|
|
828
|
-
groupOrder: [spec.group]
|
|
829
|
-
});
|
|
830
|
-
const requestedLabels = spec.labels
|
|
831
|
-
const activeLabels = options
|
|
832
|
-
.filter((option) => option.group === spec.group && option.active)
|
|
833
|
-
.map((option) =>
|
|
834
|
-
const verified =
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
845
|
+
const options = await listFilterOptions(client, frameNodeId, {
|
|
846
|
+
groupOrder: [spec.group]
|
|
847
|
+
});
|
|
848
|
+
const requestedLabels = normalizeUniqueFilterLabels(spec.labels);
|
|
849
|
+
const activeLabels = normalizeUniqueFilterLabels(options
|
|
850
|
+
.filter((option) => option.group === spec.group && option.active)
|
|
851
|
+
.map((option) => option.label));
|
|
852
|
+
const verified = requestedLabels.length > 0 && (
|
|
853
|
+
spec.selectAllLabels === false
|
|
854
|
+
? activeLabels.length === 1 && requestedLabels.includes(activeLabels[0])
|
|
855
|
+
: activeFilterLabelsMatchRequestedExactly(requestedLabels, activeLabels)
|
|
856
|
+
);
|
|
839
857
|
groups.push({
|
|
840
858
|
group: spec.group,
|
|
841
859
|
requested_labels: requestedLabels,
|
|
@@ -36,17 +36,46 @@ function trimSalarySuffix(label) {
|
|
|
36
36
|
return stripSalaryText(label);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
function splitStructuredJobLabel(label) {
|
|
40
|
+
const withoutSalary = normalizeText(stripSalaryText(label));
|
|
41
|
+
const separatorIndex = withoutSalary.lastIndexOf(" _ ");
|
|
42
|
+
if (separatorIndex < 0) {
|
|
43
|
+
return {
|
|
44
|
+
full: normalizeJobText(withoutSalary),
|
|
45
|
+
role: normalizeJobText(withoutSalary),
|
|
46
|
+
location: ""
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
full: normalizeJobText(withoutSalary),
|
|
51
|
+
role: normalizeJobText(withoutSalary.slice(0, separatorIndex)),
|
|
52
|
+
location: normalizeJobText(withoutSalary.slice(separatorIndex + 3))
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
export function jobLabelMatches(optionLabel, targetLabel) {
|
|
40
57
|
const option = normalizeJobText(optionLabel);
|
|
41
58
|
const target = normalizeJobText(targetLabel);
|
|
42
59
|
const optionWithoutSalary = normalizeJobText(stripSalaryText(optionLabel));
|
|
43
60
|
const targetWithoutSalary = normalizeJobText(stripSalaryText(targetLabel));
|
|
44
61
|
if (!option || !target) return false;
|
|
45
|
-
|
|
46
|
-
|| option.startsWith(target)
|
|
62
|
+
if (option === target
|
|
47
63
|
|| optionWithoutSalary === target
|
|
48
64
|
|| option === targetWithoutSalary
|
|
49
|
-
|| optionWithoutSalary === targetWithoutSalary
|
|
65
|
+
|| optionWithoutSalary === targetWithoutSalary) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const optionParts = splitStructuredJobLabel(optionLabel);
|
|
70
|
+
const targetParts = splitStructuredJobLabel(targetLabel);
|
|
71
|
+
// A caller may provide only the role name while Boss renders the option as
|
|
72
|
+
// "role _ city [salary]". Accept only that structured metadata suffix;
|
|
73
|
+
// never restore arbitrary startsWith matching between two role names.
|
|
74
|
+
return Boolean(
|
|
75
|
+
!targetParts.location
|
|
76
|
+
&& optionParts.location
|
|
77
|
+
&& optionParts.role === targetParts.role
|
|
78
|
+
);
|
|
50
79
|
}
|
|
51
80
|
|
|
52
81
|
function isVisibleBox(box) {
|
|
@@ -478,40 +478,84 @@ async function openRecommendLocationPopover(client, frameNodeId, {
|
|
|
478
478
|
};
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
-
async function confirmRecommendLocationPopover(client, frameNodeId, {
|
|
482
|
-
timeoutMs = 1800,
|
|
483
|
-
intervalMs = 150,
|
|
484
|
-
controlNodeId
|
|
485
|
-
|
|
481
|
+
async function confirmRecommendLocationPopover(client, frameNodeId, {
|
|
482
|
+
timeoutMs = 1800,
|
|
483
|
+
intervalMs = 150,
|
|
484
|
+
controlNodeId,
|
|
485
|
+
stableCloseMs = 300
|
|
486
|
+
} = {}) {
|
|
486
487
|
const candidates = await findExactLocationConfirmCandidates(client, frameNodeId, { controlNodeId });
|
|
487
488
|
if (!candidates.length) {
|
|
488
489
|
throw new Error("Recommend location exact 确认 button was not found");
|
|
489
490
|
}
|
|
490
|
-
const clickErrors = [];
|
|
491
|
-
for (const candidate of candidates) {
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
491
|
+
const clickErrors = [];
|
|
492
|
+
for (const candidate of candidates) {
|
|
493
|
+
let box;
|
|
494
|
+
try {
|
|
495
|
+
box = await clickNodeCenter(client, candidate.node_id, DETERMINISTIC_CLICK_OPTIONS);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
clickErrors.push({
|
|
498
|
+
node_id: candidate.node_id,
|
|
499
|
+
message: error?.message || String(error)
|
|
500
|
+
});
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const started = Date.now();
|
|
505
|
+
const requiredStableCloseMs = Math.max(0, Number(stableCloseMs) || 0);
|
|
506
|
+
let absentSince = null;
|
|
507
|
+
let absentObservations = 0;
|
|
508
|
+
let lastObservation = null;
|
|
509
|
+
while (Date.now() - started <= timeoutMs) {
|
|
510
|
+
try {
|
|
511
|
+
const [control, popover] = await Promise.all([
|
|
512
|
+
findRecommendCurrentCityControl(client, frameNodeId),
|
|
513
|
+
findVisibleRecommendLocationPopover(client, frameNodeId)
|
|
514
|
+
]);
|
|
515
|
+
const observedAt = Date.now();
|
|
516
|
+
lastObservation = {
|
|
517
|
+
control_visible: Boolean(control),
|
|
518
|
+
popover_visible: Boolean(popover),
|
|
519
|
+
observed_after_ms: observedAt - started
|
|
520
|
+
};
|
|
521
|
+
if (!control && !popover) {
|
|
522
|
+
absentSince ??= observedAt;
|
|
523
|
+
absentObservations += 1;
|
|
524
|
+
if (observedAt - absentSince >= requiredStableCloseMs) {
|
|
525
|
+
return {
|
|
526
|
+
confirmed: true,
|
|
527
|
+
label: "确认",
|
|
528
|
+
node_id: candidate.node_id,
|
|
529
|
+
box,
|
|
530
|
+
stable_close_ms: observedAt - absentSince,
|
|
531
|
+
stable_close_observations: absentObservations,
|
|
532
|
+
control_absent: true,
|
|
533
|
+
popover_invisible: true
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
absentSince = null;
|
|
538
|
+
absentObservations = 0;
|
|
539
|
+
}
|
|
540
|
+
} catch (error) {
|
|
541
|
+
const uncertain = new Error("Recommend location popover close state was uncertain after exact 确认 click");
|
|
542
|
+
uncertain.cause = error;
|
|
543
|
+
uncertain.confirm_node_id = candidate.node_id;
|
|
544
|
+
uncertain.last_observation = lastObservation;
|
|
545
|
+
throw uncertain;
|
|
546
|
+
}
|
|
547
|
+
if (intervalMs > 0) await sleep(intervalMs);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const error = new Error("Recommend location popover did not close after exact 确认 click");
|
|
551
|
+
error.confirm_node_id = candidate.node_id;
|
|
552
|
+
error.last_observation = lastObservation;
|
|
553
|
+
error.stable_close_ms = absentSince === null ? 0 : Date.now() - absentSince;
|
|
554
|
+
error.stable_close_observations = absentObservations;
|
|
555
|
+
error.click_errors = clickErrors;
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
const error = new Error("Recommend location popover did not close after exact 确认 click");
|
|
515
559
|
error.click_errors = clickErrors;
|
|
516
560
|
throw error;
|
|
517
561
|
}
|
|
@@ -575,14 +619,15 @@ function unavailableResult({ requested, currentCityLabel, reason, attempts }) {
|
|
|
575
619
|
};
|
|
576
620
|
}
|
|
577
621
|
|
|
578
|
-
export async function ensureRecommendCurrentCityOnly(client, frameNodeId, {
|
|
579
|
-
enabled = false,
|
|
580
|
-
timeoutMs = 1800,
|
|
581
|
-
intervalMs = 150,
|
|
582
|
-
attemptsLimit = 2,
|
|
583
|
-
openAttemptsLimit = 3,
|
|
584
|
-
settleMs = 250
|
|
585
|
-
|
|
622
|
+
export async function ensureRecommendCurrentCityOnly(client, frameNodeId, {
|
|
623
|
+
enabled = false,
|
|
624
|
+
timeoutMs = 1800,
|
|
625
|
+
intervalMs = 150,
|
|
626
|
+
attemptsLimit = 2,
|
|
627
|
+
openAttemptsLimit = 3,
|
|
628
|
+
settleMs = 250,
|
|
629
|
+
closeStableMs = 300
|
|
630
|
+
} = {}) {
|
|
586
631
|
const requested = enabled === true;
|
|
587
632
|
const attempts = [];
|
|
588
633
|
for (let attempt = 1; attempt <= attemptsLimit; attempt += 1) {
|
|
@@ -646,10 +691,11 @@ export async function ensureRecommendCurrentCityOnly(client, frameNodeId, {
|
|
|
646
691
|
}
|
|
647
692
|
}
|
|
648
693
|
const afterToggle = compactControlState(afterToggleControl);
|
|
649
|
-
const confirmation = await confirmRecommendLocationPopover(client, frameNodeId, {
|
|
650
|
-
timeoutMs,
|
|
651
|
-
intervalMs,
|
|
652
|
-
controlNodeId: afterToggleControl.node_id
|
|
694
|
+
const confirmation = await confirmRecommendLocationPopover(client, frameNodeId, {
|
|
695
|
+
timeoutMs,
|
|
696
|
+
intervalMs,
|
|
697
|
+
controlNodeId: afterToggleControl.node_id,
|
|
698
|
+
stableCloseMs: closeStableMs
|
|
653
699
|
});
|
|
654
700
|
if (!confirmation.confirmed) {
|
|
655
701
|
throw new Error("Recommend location state was not confirmed");
|
|
@@ -668,10 +714,11 @@ export async function ensureRecommendCurrentCityOnly(client, frameNodeId, {
|
|
|
668
714
|
throw new Error("Recommend current-city checkbox was visible but unreadable during sticky verification");
|
|
669
715
|
}
|
|
670
716
|
const actual = reopened.control.state.checked;
|
|
671
|
-
const stickyClose = await confirmRecommendLocationPopover(client, frameNodeId, {
|
|
672
|
-
timeoutMs,
|
|
673
|
-
intervalMs,
|
|
674
|
-
controlNodeId: reopened.control.node_id
|
|
717
|
+
const stickyClose = await confirmRecommendLocationPopover(client, frameNodeId, {
|
|
718
|
+
timeoutMs,
|
|
719
|
+
intervalMs,
|
|
720
|
+
controlNodeId: reopened.control.node_id,
|
|
721
|
+
stableCloseMs: closeStableMs
|
|
675
722
|
});
|
|
676
723
|
if (!stickyClose.confirmed) {
|
|
677
724
|
throw new Error("Recommend location sticky verification was not confirmed");
|