@reconcrap/boss-recommend-mcp 2.0.13 → 2.0.14

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": "2.0.13",
3
+ "version": "2.0.14",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -223,6 +223,15 @@ function matchJobOption(option, jobLabel = "") {
223
223
  ));
224
224
  }
225
225
 
226
+ function activeMatchingJobOption(options = [], jobLabel = "") {
227
+ return (options || []).find((option) => option.active && matchJobOption(option, jobLabel)) || null;
228
+ }
229
+
230
+ function selectedLabelMatches(label = "", jobLabel = "") {
231
+ const normalized = normalizeJobText(label);
232
+ return Boolean(normalized && matchJobOption({ label: normalized, value: normalized, title: normalized }, jobLabel));
233
+ }
234
+
226
235
  async function clickFirstVisible(client, rootNodeId, selectors = []) {
227
236
  for (const selector of selectors) {
228
237
  const nodeIds = await safeQuerySelectorAll(client, rootNodeId, selector);
@@ -247,6 +256,62 @@ async function clickFirstVisible(client, rootNodeId, selectors = []) {
247
256
  };
248
257
  }
249
258
 
259
+ async function waitForChatJobOptions(client, rootNodeId, {
260
+ timeoutMs = 12000,
261
+ intervalMs = 300,
262
+ requireVisible = false
263
+ } = {}) {
264
+ const started = Date.now();
265
+ let latest = null;
266
+ while (Date.now() - started <= timeoutMs) {
267
+ const currentRootNodeId = await freshTopRootNodeId(client, rootNodeId);
268
+ latest = await readChatJobOptions(client, currentRootNodeId, {
269
+ timeoutMs: Math.min(intervalMs, 300),
270
+ intervalMs
271
+ });
272
+ const options = latest.job_options || [];
273
+ if (options.length && (!requireVisible || options.some((option) => option.visible))) {
274
+ return latest;
275
+ }
276
+ await sleep(intervalMs);
277
+ }
278
+ return latest || {
279
+ selector: "",
280
+ source: "chat-job-list",
281
+ selected_label: "",
282
+ job_options: []
283
+ };
284
+ }
285
+
286
+ async function waitForSelectedChatJob(client, rootNodeId, jobLabel = "", {
287
+ timeoutMs = 5000,
288
+ intervalMs = 300
289
+ } = {}) {
290
+ const started = Date.now();
291
+ let latest = null;
292
+ while (Date.now() - started <= timeoutMs) {
293
+ const currentRootNodeId = await freshTopRootNodeId(client, rootNodeId);
294
+ latest = await readChatJobOptions(client, currentRootNodeId, {
295
+ timeoutMs: Math.min(intervalMs, 300),
296
+ intervalMs
297
+ });
298
+ if (
299
+ selectedLabelMatches(latest.selected_label, jobLabel)
300
+ || activeMatchingJobOption(latest.job_options || [], jobLabel)
301
+ ) {
302
+ return {
303
+ verified: true,
304
+ result: latest
305
+ };
306
+ }
307
+ await sleep(intervalMs);
308
+ }
309
+ return {
310
+ verified: false,
311
+ result: latest
312
+ };
313
+ }
314
+
250
315
  export async function selectChatJob(client, rootNodeId, {
251
316
  jobLabel = "",
252
317
  timeoutMs = 12000,
@@ -267,14 +332,34 @@ export async function selectChatJob(client, rootNodeId, {
267
332
  intervalMs
268
333
  });
269
334
  let matched = (optionsResult.job_options || []).find((option) => matchJobOption(option, requested)) || null;
335
+ if (
336
+ matched
337
+ && (
338
+ matched.active
339
+ || selectedLabelMatches(optionsResult.selected_label, matched.label)
340
+ || selectedLabelMatches(optionsResult.selected_label, requested)
341
+ )
342
+ ) {
343
+ return {
344
+ selected: true,
345
+ verified: true,
346
+ already_current: true,
347
+ requested,
348
+ selected_option: matched,
349
+ options: optionsResult.job_options || [],
350
+ selected_label: optionsResult.selected_label || matched.label
351
+ };
352
+ }
353
+
270
354
  if (!matched || !matched.visible) {
271
355
  const triggerRootNodeId = await freshTopRootNodeId(client, currentRootNodeId);
272
356
  const trigger = await clickFirstVisible(client, triggerRootNodeId, CHAT_JOB_TRIGGER_SELECTORS);
273
357
  if (settleMs > 0) await sleep(settleMs);
274
358
  currentRootNodeId = await freshTopRootNodeId(client, triggerRootNodeId);
275
- optionsResult = await readChatJobOptions(client, currentRootNodeId, {
359
+ optionsResult = await waitForChatJobOptions(client, currentRootNodeId, {
276
360
  timeoutMs,
277
- intervalMs
361
+ intervalMs,
362
+ requireVisible: true
278
363
  });
279
364
  matched = (optionsResult.job_options || []).find((option) => matchJobOption(option, requested)) || null;
280
365
  if (!matched || !matched.visible) {
@@ -292,6 +377,7 @@ export async function selectChatJob(client, rootNodeId, {
292
377
  if (matched.active || normalizeJobText(optionsResult.selected_label).toLowerCase() === normalizeJobText(matched.label).toLowerCase()) {
293
378
  return {
294
379
  selected: true,
380
+ verified: true,
295
381
  already_current: true,
296
382
  requested,
297
383
  selected_option: matched,
@@ -310,22 +396,27 @@ export async function selectChatJob(client, rootNodeId, {
310
396
  if (settleMs > 0) await sleep(settleMs);
311
397
 
312
398
  const afterRootNodeId = await freshTopRootNodeId(client, currentRootNodeId);
313
- const after = await readChatJobOptions(client, afterRootNodeId, {
314
- timeoutMs: Math.min(timeoutMs, 3000),
399
+ const verification = await waitForSelectedChatJob(client, afterRootNodeId, matched.label, {
400
+ timeoutMs: Math.min(timeoutMs, 5000),
315
401
  intervalMs
316
402
  });
403
+ const after = verification.result || {
404
+ selected_label: "",
405
+ job_options: []
406
+ };
317
407
  const afterMatch = (after.job_options || []).find((option) => matchJobOption(option, matched.label)) || matched;
318
- const selectedLabel = normalizeJobText(after.selected_label || afterMatch.label || "");
319
- const verified = selectedLabel
320
- ? matchJobOption({ label: selectedLabel, value: selectedLabel, title: selectedLabel }, matched.label)
321
- : true;
408
+ const selectedLabel = normalizeJobText(after.selected_label || "");
409
+ const activeMatch = activeMatchingJobOption(after.job_options || [], matched.label);
410
+ const verified = Boolean(verification.verified || selectedLabelMatches(selectedLabel, matched.label) || activeMatch);
322
411
 
323
412
  return {
324
- selected: true,
413
+ selected: verified,
325
414
  verified,
326
415
  already_current: false,
416
+ reason: verified ? "verified" : "job_selection_not_verified",
327
417
  requested,
328
418
  selected_option: afterMatch,
419
+ active_option: activeMatch,
329
420
  options: after.job_options || optionsResult.job_options || [],
330
421
  selected_label: selectedLabel,
331
422
  before: optionsResult,
@@ -306,6 +306,9 @@ async function setupChatRunContext(client, {
306
306
  if (normalizeText(job) && !jobSelection.selected) {
307
307
  throw new Error(`Chat job selection failed: ${jobSelection.reason || "unknown"}`);
308
308
  }
309
+ if (normalizeText(job) && jobSelection.verified !== true) {
310
+ throw new Error(`Chat job selection was not verified: requested=${jobSelection.requested || job}; selected=${jobSelection.selected_label || "unknown"}`);
311
+ }
309
312
  rootState = await getChatRoots(client);
310
313
  if (ensureViewport) {
311
314
  rootState = await ensureViewport(rootState, "context_job");