@reconcrap/boss-recommend-mcp 1.3.24 → 1.3.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.24",
3
+ "version": "1.3.25",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
@@ -16,6 +16,7 @@ import {
16
16
  import { __testables as cliTestables } from "./cli.js";
17
17
  import { __testables as indexTestables } from "./index.js";
18
18
  import { BossChatApp } from "../vendor/boss-chat-cli/src/app.js";
19
+ import { BossChatPage } from "../vendor/boss-chat-cli/src/browser/chat-page.js";
19
20
  import { LlmClient, parseLlmJson } from "../vendor/boss-chat-cli/src/services/llm.js";
20
21
 
21
22
  const { handleRequest } = indexTestables;
@@ -398,6 +399,90 @@ async function testBossChatPrepareShouldRetryWhenChatPageIsNotReady() {
398
399
  });
399
400
  }
400
401
 
402
+ async function testBossChatPageShouldTreatBlankChatShellAsOnChatPage() {
403
+ const fakeChromeClient = {
404
+ async callFunction() {
405
+ return {
406
+ href: "https://www.zhipin.com/web/chat/index",
407
+ readyState: "complete",
408
+ hasListContainer: false,
409
+ listItemCount: 0
410
+ };
411
+ }
412
+ };
413
+
414
+ const page = new BossChatPage(fakeChromeClient);
415
+ const pageState = await page.ensureOnChatPage();
416
+ assert.equal(pageState.href, "https://www.zhipin.com/web/chat/index");
417
+
418
+ await assert.rejects(
419
+ () => page.ensureReady(),
420
+ /CHAT_LIST_CONTAINER_NOT_FOUND/
421
+ );
422
+ }
423
+
424
+ async function testBossChatRecoverToChatIndexShouldForceNavigateAndWaitForCompleteLoad() {
425
+ const calls = [];
426
+ let stateIndex = 0;
427
+ const states = [
428
+ {
429
+ href: "https://www.zhipin.com/web/chat/index",
430
+ readyState: "loading",
431
+ hasListContainer: false,
432
+ listItemCount: 0
433
+ },
434
+ {
435
+ href: "https://www.zhipin.com/web/chat/index",
436
+ readyState: "interactive",
437
+ hasListContainer: false,
438
+ listItemCount: 0
439
+ },
440
+ {
441
+ href: "https://www.zhipin.com/web/chat/index",
442
+ readyState: "complete",
443
+ hasListContainer: false,
444
+ listItemCount: 0
445
+ }
446
+ ];
447
+
448
+ const fakeChromeClient = {
449
+ async callFunction(fn, arg) {
450
+ calls.push({ name: fn.name, arg });
451
+ if (fn.name === "browserGetCurrentHref") {
452
+ return { href: "https://www.zhipin.com/web/chat/index" };
453
+ }
454
+ if (fn.name === "browserNavigateToChatIndex") {
455
+ return { ok: true, changed: true, href: "https://www.zhipin.com/web/chat/index" };
456
+ }
457
+ if (fn.name === "browserGetPageState") {
458
+ const value = states[Math.min(stateIndex, states.length - 1)];
459
+ stateIndex += 1;
460
+ return value;
461
+ }
462
+ throw new Error(`unexpected function: ${fn.name}`);
463
+ }
464
+ };
465
+
466
+ const page = new BossChatPage(fakeChromeClient);
467
+ const result = await page.recoverToChatIndex({
468
+ forceNavigate: true,
469
+ waitForReadyState: "complete",
470
+ maxAttempts: 5,
471
+ delayMs: 0
472
+ });
473
+
474
+ assert.equal(result.changed, true);
475
+ assert.equal(result.href, "https://www.zhipin.com/web/chat/index");
476
+ assert.equal(
477
+ calls.some((entry) => entry.name === "browserNavigateToChatIndex" && entry.arg?.force === true),
478
+ true
479
+ );
480
+ assert.equal(
481
+ calls.filter((entry) => entry.name === "browserGetPageState").length >= 3,
482
+ true
483
+ );
484
+ }
485
+
401
486
  async function testBossChatMcpToolsShouldValidateAndRoute() {
402
487
  await withBossChatWorkspace(async (workspaceRoot) => {
403
488
  const toolsResponse = await handleRequest({
@@ -941,6 +1026,8 @@ async function testBossChatAppShouldPersistEvidenceArtifacts() {
941
1026
  async function main() {
942
1027
  await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
943
1028
  await testBossChatPrepareShouldRetryWhenChatPageIsNotReady();
1029
+ await testBossChatPageShouldTreatBlankChatShellAsOnChatPage();
1030
+ await testBossChatRecoverToChatIndexShouldForceNavigateAndWaitForCompleteLoad();
944
1031
  await testBossChatMcpToolsShouldValidateAndRoute();
945
1032
  await testBossChatCliShouldSupportRunAndFollowUpParsing();
946
1033
  testBossChatLlmEvidenceGateShouldDemoteMissingEvidence();
@@ -24,6 +24,7 @@ function browserGetPageState() {
24
24
 
25
25
  return {
26
26
  href: window.location.href,
27
+ readyState: document.readyState,
27
28
  hasListContainer: Boolean(listContainer),
28
29
  listItemCount: listItems.length,
29
30
  };
@@ -33,9 +34,10 @@ function browserGetCurrentHref() {
33
34
  return { href: window.location.href };
34
35
  }
35
36
 
36
- function browserNavigateToChatIndex() {
37
+ function browserNavigateToChatIndex(options = {}) {
37
38
  const chatUrl = 'https://www.zhipin.com/web/chat/index';
38
- if (!String(window.location.href || '').includes('/web/chat/index')) {
39
+ const force = options?.force === true;
40
+ if (force || !String(window.location.href || '').includes('/web/chat/index')) {
39
41
  window.location.assign(chatUrl);
40
42
  return { ok: true, changed: true, href: chatUrl };
41
43
  }
@@ -2236,11 +2238,20 @@ export class BossChatPage {
2236
2238
  return target?.type === 'page' && String(target.url || '').includes(CHAT_URL_TOKEN);
2237
2239
  }
2238
2240
 
2239
- async ensureReady() {
2240
- const pageState = await this.chromeClient.callFunction(browserGetPageState);
2241
+ async getPageState() {
2242
+ return this.chromeClient.callFunction(browserGetPageState);
2243
+ }
2244
+
2245
+ async ensureOnChatPage() {
2246
+ const pageState = await this.getPageState();
2241
2247
  if (!pageState?.href?.includes(CHAT_URL_TOKEN)) {
2242
2248
  throw new Error('ACTIVE_TAB_IS_NOT_BOSS_CHAT_PAGE');
2243
2249
  }
2250
+ return pageState;
2251
+ }
2252
+
2253
+ async ensureReady() {
2254
+ const pageState = await this.ensureOnChatPage();
2244
2255
  if (!pageState.hasListContainer && Number(pageState.listItemCount || 0) <= 0) {
2245
2256
  throw new Error('CHAT_LIST_CONTAINER_NOT_FOUND');
2246
2257
  }
@@ -2250,16 +2261,20 @@ export class BossChatPage {
2250
2261
  async recoverToChatIndex(options = {}) {
2251
2262
  const maxAttempts = options.maxAttempts || 20;
2252
2263
  const delayMs = options.delayMs || 500;
2264
+ const forceNavigate = options.forceNavigate === true;
2265
+ const waitForReadyState = options.waitForReadyState || 'complete';
2253
2266
  const hrefResult = await this.chromeClient.callFunction(browserGetCurrentHref);
2254
- if (String(hrefResult?.href || '').includes(CHAT_URL_TOKEN)) {
2267
+ if (!forceNavigate && String(hrefResult?.href || '').includes(CHAT_URL_TOKEN)) {
2255
2268
  return { changed: false, href: hrefResult?.href || '' };
2256
2269
  }
2257
2270
 
2258
- await this.chromeClient.callFunction(browserNavigateToChatIndex);
2271
+ await this.chromeClient.callFunction(browserNavigateToChatIndex, { force: forceNavigate });
2259
2272
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
2260
2273
  await new Promise((resolve) => setTimeout(resolve, delayMs));
2261
- const state = await this.chromeClient.callFunction(browserGetPageState);
2262
- if (String(state?.href || '').includes(CHAT_URL_TOKEN)) {
2274
+ const state = await this.getPageState();
2275
+ const onChatPage = String(state?.href || '').includes(CHAT_URL_TOKEN);
2276
+ const ready = !waitForReadyState || String(state?.readyState || '').toLowerCase() === String(waitForReadyState).toLowerCase();
2277
+ if (onChatPage && ready) {
2263
2278
  return { changed: true, href: state.href };
2264
2279
  }
2265
2280
  }
@@ -44,6 +44,7 @@ const CLI_FILE_PATH = fileURLToPath(import.meta.url);
44
44
  const MINIMAL_TERMINAL_PATTERNS = [/^进度: /, /^候选人结果: /];
45
45
  const CHAT_INDEX_URL = 'https://www.zhipin.com/web/chat/index';
46
46
  const CHAT_START_REQUIRED_FIELDS = ['job', 'start_from', 'target_count', 'criteria'];
47
+ const CHAT_PAGE_RENAVIGATE_MAX_ATTEMPTS = 3;
47
48
 
48
49
  function sanitizePathToken(value, fallback = 'run') {
49
50
  const token = String(value || '')
@@ -651,6 +652,8 @@ async function connectBossChatPage(chromeClient) {
651
652
  target?.type === 'page' && /zhipin\.com/i.test(String(target?.url || ''));
652
653
  let target = null;
653
654
  let recoveredToChatIndex = false;
655
+ let blankChatPage = false;
656
+ let renavigateAttempts = 0;
654
657
 
655
658
  try {
656
659
  target = await chromeClient.connect(BossChatPage.targetMatcher);
@@ -659,15 +662,41 @@ async function connectBossChatPage(chromeClient) {
659
662
  }
660
663
 
661
664
  const page = new BossChatPage(chromeClient);
662
- try {
663
- await page.ensureReady();
664
- } catch {
665
- await page.recoverToChatIndex();
666
- recoveredToChatIndex = true;
667
- await page.ensureReady();
665
+ for (let attempt = 1; attempt <= CHAT_PAGE_RENAVIGATE_MAX_ATTEMPTS + 1; attempt += 1) {
666
+ try {
667
+ await page.ensureReady();
668
+ return {
669
+ target,
670
+ page,
671
+ recoveredToChatIndex,
672
+ blankChatPage,
673
+ renavigateAttempts,
674
+ };
675
+ } catch (error) {
676
+ const message = String(error?.message || error || '');
677
+ const canRetry =
678
+ /ACTIVE_TAB_IS_NOT_BOSS_CHAT_PAGE|CHAT_LIST_CONTAINER_NOT_FOUND/.test(message)
679
+ && attempt <= CHAT_PAGE_RENAVIGATE_MAX_ATTEMPTS;
680
+
681
+ if (!canRetry) {
682
+ if (/CHAT_LIST_CONTAINER_NOT_FOUND/.test(message)) {
683
+ blankChatPage = true;
684
+ await page.ensureOnChatPage();
685
+ break;
686
+ }
687
+ throw error;
688
+ }
689
+
690
+ await page.recoverToChatIndex({
691
+ forceNavigate: true,
692
+ waitForReadyState: 'complete',
693
+ });
694
+ recoveredToChatIndex = true;
695
+ renavigateAttempts += 1;
696
+ }
668
697
  }
669
698
 
670
- return { target, page, recoveredToChatIndex };
699
+ return { target, page, recoveredToChatIndex, blankChatPage, renavigateAttempts };
671
700
  }
672
701
 
673
702
  async function handlePrepareRunCommand(args, dataDir) {
@@ -704,7 +733,7 @@ async function handlePrepareRunCommand(args, dataDir) {
704
733
  let chromeClient = null;
705
734
  try {
706
735
  chromeClient = new ChromeClient(mergedProfile.chrome.port);
707
- const { target, page, recoveredToChatIndex } = await connectBossChatPage(chromeClient);
736
+ const { target, page, recoveredToChatIndex, blankChatPage, renavigateAttempts } = await connectBossChatPage(chromeClient);
708
737
  const jobs = await page.listJobs();
709
738
  if (!Array.isArray(jobs) || jobs.length === 0) {
710
739
  return {
@@ -723,6 +752,8 @@ async function handlePrepareRunCommand(args, dataDir) {
723
752
  page_url: CHAT_INDEX_URL,
724
753
  connected_target: target?.url || '',
725
754
  recovered_to_chat_index: recoveredToChatIndex,
755
+ blank_chat_page: blankChatPage,
756
+ renavigate_attempts: renavigateAttempts,
726
757
  required_fields: CHAT_START_REQUIRED_FIELDS.slice(),
727
758
  defaults: {
728
759
  profile: String(args.profile || 'default').trim() || 'default',
@@ -1159,10 +1190,13 @@ async function executeRunCommand(args, dataDir) {
1159
1190
 
1160
1191
  chromeClient = new ChromeClient(persistentProfile.chrome.port);
1161
1192
 
1162
- const { target, page, recoveredToChatIndex } = await connectBossChatPage(chromeClient);
1193
+ const { target, page, recoveredToChatIndex, blankChatPage, renavigateAttempts } = await connectBossChatPage(chromeClient);
1163
1194
  logger.log(`已连接 Chrome tab: ${target.title || target.url}`);
1164
1195
  if (recoveredToChatIndex) {
1165
- logger.log(`检测到当前标签不在聊天页,已自动跳转到 ${CHAT_INDEX_URL}`);
1196
+ logger.log(`检测到页面不符合预期,已重新跳转到 ${CHAT_INDEX_URL} 并等待加载完成。attempts=${renavigateAttempts}`);
1197
+ }
1198
+ if (blankChatPage) {
1199
+ logger.log('检测到聊天页处于空白未初始化状态,将继续通过岗位选择和首位候选人预热来恢复列表。');
1166
1200
  }
1167
1201
 
1168
1202
  const runProfile = await promptRunProfile({