From 70cc267fc4b44978a469fa86bcbfa118043cefaa Mon Sep 17 00:00:00 2001 From: "@Ginkgo0110" Date: Thu, 2 Apr 2026 11:48:06 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix(boss):=20=E4=BF=AE=E5=A4=8D=E6=B2=BB?= =?UTF-8?q?=E7=90=86=E5=85=AC=E5=91=8A=E5=BC=B9=E7=AA=97=E5=85=B3=E9=97=AD?= =?UTF-8?q?=20timing=20=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用 waitForSelector 替换 page.$(),等待弹窗异步渲染完成后再点击; 超时由 60s 缩短为 10s,避免弹窗未出现时长时间阻塞。 Co-Authored-By: Claude Sonnet 4.6 --- packages/boss-auto-browse-and-chat/index.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/boss-auto-browse-and-chat/index.mjs b/packages/boss-auto-browse-and-chat/index.mjs index f53f4de..48fa5d2 100644 --- a/packages/boss-auto-browse-and-chat/index.mjs +++ b/packages/boss-auto-browse-and-chat/index.mjs @@ -69,7 +69,9 @@ export async function initPuppeteer () { */ async function dismissGovernanceNoticeDialog (page) { try { - const confirmBtn = await page.$(GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR) + const confirmBtn = await page + .waitForSelector(GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR, { timeout: 10000 }) + .catch(() => null) if (!confirmBtn) return logInfo('[boss-auto-browse] 检测到「治理公告」弹窗,点击「我已知晓」关闭...') try { From 9123a4d41660aa8c92742411ba6b017ab4608097 Mon Sep 17 00:00:00 2001 From: "@Ginkgo0110" Date: Thu, 2 Apr 2026 11:48:53 +0800 Subject: [PATCH 2/4] =?UTF-8?q?fix(boss):=20=E4=BF=AE=E6=AD=A3=E6=B2=9F?= =?UTF-8?q?=E9=80=9A=E9=A1=B5=E8=81=8C=E4=BD=8D=E4=B8=8B=E6=8B=89=E9=80=89?= =?UTF-8?q?=E6=8B=A9=E5=99=A8=E5=B9=B6=E9=87=8D=E6=9E=84=E6=9C=AA=E8=AF=BB?= =?UTF-8?q?=E4=BC=9A=E8=AF=9D=E6=89=B9=E6=AC=A1=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit constant.mjs:将沟通页职位下拉选择器更新为 .dropmenu-label.chat-select-job; chat-page-processor.mjs:未读会话改为每批 10 条循环处理,每批结束后 重新点击「未读」tab 刷新列表,并用 seenIds 防止重复处理。 Co-Authored-By: Claude Sonnet 4.6 --- .../chat-page-processor.mjs | 73 ++++++++++++------- .../boss-auto-browse-and-chat/constant.mjs | 2 +- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/packages/boss-auto-browse-and-chat/chat-page-processor.mjs b/packages/boss-auto-browse-and-chat/chat-page-processor.mjs index c9ba551..3cc24e3 100644 --- a/packages/boss-auto-browse-and-chat/chat-page-processor.mjs +++ b/packages/boss-auto-browse-and-chat/chat-page-processor.mjs @@ -31,7 +31,9 @@ import { CHAT_PAGE_NAME_SELECTOR, CHAT_PAGE_JOB_SELECTOR, CHAT_PAGE_PREVIEW_RESUME_BTN_SELECTOR, - CHAT_PAGE_ONLINE_RESUME_CLOSE_SELECTOR + CHAT_PAGE_ONLINE_RESUME_CLOSE_SELECTOR, + CHAT_PAGE_JOB_DROPDOWN_SELECTOR, + CHAT_PAGE_JOB_ITEM_SELECTOR } from './constant.mjs' const LOG = '[chat-page-processor]' @@ -45,7 +47,7 @@ async function switchChatPageJobId (page, jobId) { try { const cursor = await createHumanCursor(page) // 用拟人轨迹点击下拉触发按钮 - const dropdownBtn = await page.$('.ui-dropmenu.chat-top-job .ui-dropmenu-label') + const dropdownBtn = await page.$(CHAT_PAGE_JOB_DROPDOWN_SELECTOR) if (dropdownBtn) { const box = await dropdownBtn.boundingBox().catch(() => null) if (box) { @@ -54,12 +56,12 @@ async function switchChatPageJobId (page, jobId) { await dropdownBtn.click() } } else { - await page.click('.ui-dropmenu.chat-top-job .ui-dropmenu-label') + await page.click(CHAT_PAGE_JOB_DROPDOWN_SELECTOR) } - await page.waitForSelector('.ui-dropmenu.chat-top-job .ui-dropmenu-list', { timeout: 5000 }) + await page.waitForSelector(CHAT_PAGE_JOB_ITEM_SELECTOR, { timeout: 5000 }) await sleepWithRandomDelay(150, 300) // 用拟人轨迹点击目标职位项 - const items = await page.$$('.ui-dropmenu.chat-top-job .ui-dropmenu-list li') + const items = await page.$$(CHAT_PAGE_JOB_ITEM_SELECTOR) let found = false for (const item of items) { const val = await item.evaluate(el => el.getAttribute('value')).catch(() => null) @@ -798,31 +800,52 @@ export default async function startBossChatPageProcess (hooksFromCaller, options // 「新招呼」分类与「未读」tab 已在上方初始化阶段完成切换(force: true),此处直接解析列表。 // 若经过 retryCandidate 流程,retry 结束时已切回「未读」tab,状态同样正确。 - const conversations = await parseConversationList(page) - logDebug(`${LOG} DOM 解析到 ${conversations.length} 条会话`) + // ── 批次循环:每处理 BATCH_REFRESH_SIZE 条后重新点击「未读」刷新列表(步骤4-5)──── + const BATCH_REFRESH_SIZE = 10 + let totalAttempted = 0 + let totalProcessed = 0 + const seenIds = new Set() - const unreadItems = conversations.filter((c) => c.encryptGeekId) - const toProcess = unreadItems.slice(0, maxProcessPerRun) - logInfo(`${LOG} 未读会话 ${unreadItems.length} 条,本次最多处理 ${toProcess.length} 条`) - if (toProcess.length > 0) { - logDebug(`${LOG} 候选人列表:${toProcess.map((c, i) => `[${i}] ${c.geekName}(${c.encryptGeekId})`).join(', ')}`) - } + while (totalAttempted < maxProcessPerRun) { + const conversations = await parseConversationList(page) + logDebug(`${LOG} DOM 解析到 ${conversations.length} 条会话`) - await hooks.onProgress?.promise?.({ phase: 'chatPage', current: 0, max: toProcess.length }).catch(() => {}) - - let processed = 0 - - for (let i = 0; i < toProcess.length; i++) { - const item = toProcess[i] - logInfo(`${LOG} ── [${i + 1}/${toProcess.length}] 开始处理 ${item.geekName}(${item.encryptGeekId})──`) - const result = await processOneCandidateConversation(item) - if (result.processed) { - processed++ - await hooks.onProgress?.promise?.({ phase: 'chatPage', current: processed, max: toProcess.length }).catch(() => {}) + const unreadItems = conversations.filter((c) => c.encryptGeekId && !seenIds.has(c.encryptGeekId)) + if (unreadItems.length === 0) { + logInfo(`${LOG} 「未读」列表为空,全部处理完毕`) + break } + + const batchSize = Math.min(BATCH_REFRESH_SIZE, maxProcessPerRun - totalAttempted) + const batch = unreadItems.slice(0, batchSize) + logInfo(`${LOG} 当前未读 ${unreadItems.length} 条,本批次处理 ${batch.length} 条(已尝试 ${totalAttempted}/${maxProcessPerRun})`) + + await hooks.onProgress?.promise?.({ phase: 'chatPage', current: totalProcessed, max: maxProcessPerRun }).catch(() => {}) + + for (const item of batch) { + seenIds.add(item.encryptGeekId) + logInfo(`${LOG} ── [${totalAttempted + 1}/${maxProcessPerRun}] 开始处理 ${item.geekName}(${item.encryptGeekId})──`) + const result = await processOneCandidateConversation(item) + totalAttempted++ + if (result.processed) { + totalProcessed++ + await hooks.onProgress?.promise?.({ phase: 'chatPage', current: totalProcessed, max: maxProcessPerRun }).catch(() => {}) + } + if (totalAttempted >= maxProcessPerRun) break + } + + if (totalAttempted >= maxProcessPerRun) { + logInfo(`${LOG} 已达本次最大处理数 ${maxProcessPerRun},停止`) + break + } + + // 步骤4:每批次结束后重新点击「未读」标签,刷新列表 + logInfo(`${LOG} 本批次结束,重新点击「未读」标签刷新列表...`) + await switchToTab(CHAT_PAGE_UNREAD_FILTER_SELECTOR, '未读', { force: true }) + await sleepWithRandomDelay(400, 600) } - logInfo(`${LOG} 本次共处理 ${processed} 条未读会话`) + logInfo(`${LOG} 本次共处理 ${totalProcessed} 条未读会话(尝试 ${totalAttempted} 条)`) } catch (err) { await hooks.onError?.promise?.(err) throw err diff --git a/packages/boss-auto-browse-and-chat/constant.mjs b/packages/boss-auto-browse-and-chat/constant.mjs index c04c3c2..ddbd89d 100644 --- a/packages/boss-auto-browse-and-chat/constant.mjs +++ b/packages/boss-auto-browse-and-chat/constant.mjs @@ -98,7 +98,7 @@ export const RECOMMEND_JOB_LIST_SELECTOR = '#headerWrap ul.job-list' export const RECOMMEND_JOB_ITEM_SELECTOR = '#headerWrap ul.job-list li.job-item' /** 沟通页:顶部职位筛选下拉触发按钮(点击展开职位列表) */ -export const CHAT_PAGE_JOB_DROPDOWN_SELECTOR = '.chat-top-job .ui-dropmenu-label' +export const CHAT_PAGE_JOB_DROPDOWN_SELECTOR = '.dropmenu-label.chat-select-job' /** 沟通页:职位下拉展开后的列表项(过滤 value="-1" 的"全部职位") */ export const CHAT_PAGE_JOB_ITEM_SELECTOR = '.chat-top-job .ui-dropmenu-list li' From 8bfcb6d0447a1287feaf7611e748b215a1395038 Mon Sep 17 00:00:00 2001 From: "@Ginkgo0110" Date: Thu, 2 Apr 2026 11:50:33 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix(ui):=20=E7=94=A8=20path.resolve=20?= =?UTF-8?q?=E8=A7=84=E8=8C=83=E5=8C=96=E5=BC=80=E5=8F=91=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E7=9A=84=E8=BF=9B=E7=A8=8B=E5=90=AF=E5=8A=A8=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit run-common.ts 和 launch-daemon.ts 中对 process.argv[1] 应用 resolve(), 避免开发模式下相对路径导致子进程启动失败。 Co-Authored-By: Claude Sonnet 4.6 --- packages/ui/src/main/features/run-common.ts | 3 ++- packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ui/src/main/features/run-common.ts b/packages/ui/src/main/features/run-common.ts index 5b0e19c..51b0450 100644 --- a/packages/ui/src/main/features/run-common.ts +++ b/packages/ui/src/main/features/run-common.ts @@ -1,3 +1,4 @@ +import { resolve } from 'path' import { AUTO_CHAT_ERROR_EXIT_CODE } from '../../common/enums/auto-start-chat' import { daemonEE, sendToDaemon } from '../flow/OPEN_SETTING_WINDOW/connect-to-daemon' import { saveAndGetCurrentRunRecord } from '../flow/OPEN_SETTING_WINDOW/utils/db' @@ -43,7 +44,7 @@ export async function runCommon({ mode }) { } const args = process.env.NODE_ENV === 'development' - ? [process.argv[1], `--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`] + ? [resolve(process.argv[1]), `--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`] : [`--mode=${mode}`, `--run-record-id=${currentRunRecord?.id || 0}`] await sendToDaemon( { diff --git a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts index ffd3714..8831dfb 100644 --- a/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts +++ b/packages/ui/src/main/flow/OPEN_SETTING_WINDOW/launch-daemon.ts @@ -1,4 +1,5 @@ import { spawn } from 'child_process' +import { resolve } from 'path' import { ensureStorageFileExist, writeStorageFile, @@ -29,7 +30,7 @@ export async function launchDaemon() { daemonProcess = spawn( process.argv[0], process.env.NODE_ENV === 'development' - ? [process.argv[1], `--mode=launchDaemon`] + ? [resolve(process.argv[1]), `--mode=launchDaemon`] : [`--mode=launchDaemon`], { stdio: ['ignore', 'pipe', 'pipe', 'pipe'], From 23c820d4af90760aabb22ea99a8d54bfe1087dd4 Mon Sep 17 00:00:00 2001 From: "@Ginkgo0110" Date: Thu, 2 Apr 2026 12:06:26 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix(boss):=20=E4=BF=AE=E5=A4=8D=E6=B2=9F?= =?UTF-8?q?=E9=80=9A=E9=A1=B5=E6=B2=BB=E7=90=86=E5=85=AC=E5=91=8A=E5=BC=B9?= =?UTF-8?q?=E7=AA=97=E6=9C=AA=E5=85=B3=E9=97=AD=E5=8F=8A=E6=96=B0=E6=8B=9B?= =?UTF-8?q?=E5=91=BC=20tab=20=E9=80=89=E6=8B=A9=E5=99=A8=E5=A4=B1=E6=95=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BOSS_CHAT_PAGE_MAIN 有独立的浏览器启动逻辑,未调用 dismissGovernanceNoticeDialog, 导致弹窗一直阻挡后续所有操作;现将其导出并在 page.goto 完成后显式调用。 新招呼 tab 的 title 含动态未读数(如"新招呼(1312)"),精确匹配 [title="新招呼"] 永远失败,改为前缀匹配 [title^="新招呼"]。 Co-Authored-By: Claude Sonnet 4.6 --- packages/boss-auto-browse-and-chat/constant.mjs | 2 +- packages/boss-auto-browse-and-chat/index.mjs | 2 +- packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/boss-auto-browse-and-chat/constant.mjs b/packages/boss-auto-browse-and-chat/constant.mjs index ddbd89d..cc395b1 100644 --- a/packages/boss-auto-browse-and-chat/constant.mjs +++ b/packages/boss-auto-browse-and-chat/constant.mjs @@ -239,4 +239,4 @@ export const GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR = '.dialog-uninstall- * 每次开始处理前须先点击此 tab,确保只扫描新招呼消息,避免遍历其他类型会话。 * HTML: div.chat-label-item[title="新招呼"],选中态有 class selected。 */ -export const CHAT_PAGE_TAB_NEW_GREET_SELECTOR = '.chat-label-item[title="新招呼"]' +export const CHAT_PAGE_TAB_NEW_GREET_SELECTOR = '.chat-label-item[title^="新招呼"]' diff --git a/packages/boss-auto-browse-and-chat/index.mjs b/packages/boss-auto-browse-and-chat/index.mjs index 48fa5d2..53ea74f 100644 --- a/packages/boss-auto-browse-and-chat/index.mjs +++ b/packages/boss-auto-browse-and-chat/index.mjs @@ -67,7 +67,7 @@ export async function initPuppeteer () { * 该弹窗在每次登录后必现,不处理会导致后续自动化操作卡死超时。 * @param {import('puppeteer').Page} page */ -async function dismissGovernanceNoticeDialog (page) { +export async function dismissGovernanceNoticeDialog (page) { try { const confirmBtn = await page .waitForSelector(GOVERNANCE_NOTICE_DIALOG_CONFIRM_BTN_SELECTOR, { timeout: 10000 }) diff --git a/packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts b/packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts index aec5875..047c03b 100644 --- a/packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts +++ b/packages/ui/src/main/flow/BOSS_CHAT_PAGE_MAIN/index.ts @@ -146,10 +146,12 @@ const runChatPage = async () => { processContext?: { currentCandidate: any } | null; }) => Promise initPuppeteer: () => Promise<{ puppeteer: any }> + dismissGovernanceNoticeDialog: (page: any) => Promise } const { startBossChatPageProcess, - initPuppeteer + initPuppeteer, + dismissGovernanceNoticeDialog } = (await import('@geekgeekrun/boss-auto-browse-and-chat/index.mjs')) as unknown as BossAutoBrowseModule const { setupCanvasTextHook } = (await import('@geekgeekrun/boss-auto-browse-and-chat/resume-extractor.mjs')) as any log('boss package import 完成,初始化 puppeteer...') @@ -252,6 +254,8 @@ const runChatPage = async () => { await setDomainLocalStorage(browser, localStoragePageUrl, bossLocalStorage || {}) await page.goto(BOSS_CHAT_PAGE_URL, { timeout: 60 * 1000 }) await page.waitForFunction(() => document.readyState === 'complete', { timeout: 120 * 1000 }) + await new Promise(r => setTimeout(r, 1500)) + await dismissGovernanceNoticeDialog(page) sendToDaemon({ type: 'worker-to-gui-message',