@reconcrap/boss-recommend-mcp 1.0.1 → 1.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -76,9 +76,15 @@ function normalizeDegree(value) {
76
76
  return DEGREE_OPTIONS.includes(normalized) ? normalized : null;
77
77
  }
78
78
 
79
- function sortDegreeSelection(values) {
80
- return Array.from(new Set(values.filter(Boolean))).sort((left, right) => DEGREE_ORDER.indexOf(left) - DEGREE_ORDER.indexOf(right));
81
- }
79
+ function sortDegreeSelection(values) {
80
+ return Array.from(new Set(values.filter(Boolean))).sort((left, right) => DEGREE_ORDER.indexOf(left) - DEGREE_ORDER.indexOf(right));
81
+ }
82
+
83
+ function selectionEquals(left, right) {
84
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
85
+ if (left.length !== right.length) return false;
86
+ return left.every((value, index) => value === right[index]);
87
+ }
82
88
 
83
89
  function expandDegreeAtOrAbove(value) {
84
90
  const normalized = normalizeDegree(value);
@@ -1115,35 +1121,53 @@ class RecommendSearchCli {
1115
1121
  })()`);
1116
1122
  }
1117
1123
 
1118
- async selectSchoolFilter(labels) {
1119
- const ensure = await this.ensureGroupReady("school");
1120
- if (!ensure?.ok) {
1121
- throw new Error(ensure?.error || "GROUP_NOT_FOUND");
1122
- }
1123
-
1124
- const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
1125
- if (targetLabels.includes("不限")) {
1126
- await this.selectOption("school", "不限");
1127
- return;
1128
- }
1129
-
1130
- const currentState = await this.getSchoolFilterState();
1131
- if (!currentState?.ok) {
1132
- throw new Error(currentState?.error || "SCHOOL_FILTER_STATE_FAILED");
1133
- }
1134
- const current = sortSchoolSelection(currentState.activeLabels || []);
1135
- const desired = sortSchoolSelection(targetLabels);
1136
- const same =
1137
- !currentState.defaultActive
1138
- && current.length === desired.length
1139
- && current.every((value, index) => value === desired[index]);
1140
- if (same) return;
1141
-
1142
- await this.selectOption("school", "不限");
1143
- for (const label of desired) {
1144
- await this.selectOption("school", label);
1145
- }
1146
- }
1124
+ async selectSchoolFilter(labels) {
1125
+ const ensure = await this.ensureGroupReady("school");
1126
+ if (!ensure?.ok) {
1127
+ throw new Error(ensure?.error || "GROUP_NOT_FOUND");
1128
+ }
1129
+
1130
+ const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
1131
+ const desired = sortSchoolSelection(targetLabels);
1132
+ const expectDefaultOnly = desired.includes("不限");
1133
+ let lastState = null;
1134
+
1135
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1136
+ const state = await this.getSchoolFilterState();
1137
+ if (!state?.ok) {
1138
+ throw new Error(state?.error || "SCHOOL_FILTER_STATE_FAILED");
1139
+ }
1140
+ lastState = state;
1141
+ const current = sortSchoolSelection(state.activeLabels || []);
1142
+ const matched = expectDefaultOnly
1143
+ ? Boolean(state.defaultActive)
1144
+ : (!state.defaultActive && selectionEquals(current, desired));
1145
+ if (matched) {
1146
+ return;
1147
+ }
1148
+
1149
+ if (expectDefaultOnly) {
1150
+ await this.selectOption("school", "不限");
1151
+ await sleep(humanDelay(180, 50));
1152
+ continue;
1153
+ }
1154
+
1155
+ if (state.defaultActive) {
1156
+ const clearDefault = await this.clickOptionBySelector("school", "不限");
1157
+ if (!clearDefault?.ok) {
1158
+ throw new Error(clearDefault?.error || "SCHOOL_DEFAULT_CLEAR_FAILED");
1159
+ }
1160
+ await sleep(humanDelay(180, 50));
1161
+ }
1162
+ for (const label of desired) {
1163
+ await this.selectOption("school", label);
1164
+ await sleep(humanDelay(120, 40));
1165
+ }
1166
+ await sleep(humanDelay(180, 50));
1167
+ }
1168
+
1169
+ throw new Error(`SCHOOL_FILTER_VERIFY_FAILED:${JSON.stringify(lastState || {})}`);
1170
+ }
1147
1171
 
1148
1172
  async getDegreeFilterState() {
1149
1173
  return this.evaluate(`(() => {
@@ -1183,35 +1207,53 @@ class RecommendSearchCli {
1183
1207
  })()`);
1184
1208
  }
1185
1209
 
1186
- async selectDegreeFilter(labels) {
1187
- const ensure = await this.ensureGroupReady("degree");
1188
- if (!ensure?.ok) {
1189
- throw new Error(ensure?.error || "GROUP_NOT_FOUND");
1190
- }
1191
-
1192
- const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
1193
- if (targetLabels.includes("不限")) {
1194
- await this.selectOption("degree", "不限");
1195
- return;
1196
- }
1197
-
1198
- const currentState = await this.getDegreeFilterState();
1199
- if (!currentState?.ok) {
1200
- throw new Error(currentState?.error || "DEGREE_FILTER_STATE_FAILED");
1201
- }
1202
- const current = sortDegreeSelection(currentState.activeLabels || []);
1203
- const desired = sortDegreeSelection(targetLabels);
1204
- const same =
1205
- !currentState.defaultActive
1206
- && current.length === desired.length
1207
- && current.every((value, index) => value === desired[index]);
1208
- if (same) return;
1209
-
1210
- await this.selectOption("degree", "不限");
1211
- for (const label of desired) {
1212
- await this.selectOption("degree", label);
1213
- }
1214
- }
1210
+ async selectDegreeFilter(labels) {
1211
+ const ensure = await this.ensureGroupReady("degree");
1212
+ if (!ensure?.ok) {
1213
+ throw new Error(ensure?.error || "GROUP_NOT_FOUND");
1214
+ }
1215
+
1216
+ const targetLabels = Array.isArray(labels) && labels.length > 0 ? labels : ["不限"];
1217
+ const desired = sortDegreeSelection(targetLabels);
1218
+ const expectDefaultOnly = desired.includes("不限");
1219
+ let lastState = null;
1220
+
1221
+ for (let attempt = 0; attempt < 3; attempt += 1) {
1222
+ const state = await this.getDegreeFilterState();
1223
+ if (!state?.ok) {
1224
+ throw new Error(state?.error || "DEGREE_FILTER_STATE_FAILED");
1225
+ }
1226
+ lastState = state;
1227
+ const current = sortDegreeSelection(state.activeLabels || []);
1228
+ const matched = expectDefaultOnly
1229
+ ? Boolean(state.defaultActive)
1230
+ : (!state.defaultActive && selectionEquals(current, desired));
1231
+ if (matched) {
1232
+ return;
1233
+ }
1234
+
1235
+ if (expectDefaultOnly) {
1236
+ await this.selectOption("degree", "不限");
1237
+ await sleep(humanDelay(180, 50));
1238
+ continue;
1239
+ }
1240
+
1241
+ if (state.defaultActive) {
1242
+ const clearDefault = await this.clickOptionBySelector("degree", "不限");
1243
+ if (!clearDefault?.ok) {
1244
+ throw new Error(clearDefault?.error || "DEGREE_DEFAULT_CLEAR_FAILED");
1245
+ }
1246
+ await sleep(humanDelay(180, 50));
1247
+ }
1248
+ for (const label of desired) {
1249
+ await this.selectOption("degree", label);
1250
+ await sleep(humanDelay(120, 40));
1251
+ }
1252
+ await sleep(humanDelay(180, 50));
1253
+ }
1254
+
1255
+ throw new Error(`DEGREE_FILTER_VERIFY_FAILED:${JSON.stringify(lastState || {})}`);
1256
+ }
1215
1257
 
1216
1258
  async countCandidates() {
1217
1259
  return this.evaluate(`(() => {
@@ -1306,20 +1348,26 @@ class RecommendSearchCli {
1306
1348
  await this.openFilterPanel();
1307
1349
  await this.selectSchoolFilter(this.args.schoolTag);
1308
1350
  await this.selectOption("gender", this.args.gender);
1309
- await this.selectOption("recentNotView", this.args.recentNotView);
1310
- await this.selectDegreeFilter(this.args.degree);
1311
- await this.closeFilterPanel();
1312
- const candidateInfo = await this.waitForCandidateCountStable();
1313
-
1314
- console.log(JSON.stringify({
1315
- status: "COMPLETED",
1316
- result: {
1351
+ await this.selectOption("recentNotView", this.args.recentNotView);
1352
+ await this.selectDegreeFilter(this.args.degree);
1353
+ const verifiedSchoolState = await this.getSchoolFilterState();
1354
+ const verifiedDegreeState = await this.getDegreeFilterState();
1355
+ await this.closeFilterPanel();
1356
+ const candidateInfo = await this.waitForCandidateCountStable();
1357
+
1358
+ console.log(JSON.stringify({
1359
+ status: "COMPLETED",
1360
+ result: {
1317
1361
  applied_filters: {
1318
1362
  school_tag: this.args.schoolTag,
1319
1363
  degree: this.args.degree,
1320
1364
  gender: this.args.gender,
1321
1365
  recent_not_view: this.args.recentNotView
1322
1366
  },
1367
+ verified_filters: {
1368
+ school: verifiedSchoolState,
1369
+ degree: verifiedDegreeState
1370
+ },
1323
1371
  selected_job: selectedJob,
1324
1372
  candidate_count: candidateInfo?.candidateCount ?? null,
1325
1373
  page_state: {