@roll-agent/browser-use-agent 0.7.5 → 0.7.6
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/SKILL.md +75 -217
- package/dist/index.js +1 -1
- package/dist/native-visual-activity-session.d.ts +24 -0
- package/dist/pages/zhipin/native-page.d.ts +196 -0
- package/dist/pages/zhipin/resume-dom-contract.d.ts +43 -0
- package/dist/pages/zhipin/selectors.d.ts +1 -1
- package/dist/tools/zhipin-diagnose-browser-state.d.ts +35 -10
- package/dist/tools/zhipin-exchange-wechat.d.ts +13 -7
- package/dist/tools/zhipin-filter-recommend-candidates.d.ts +6 -13
- package/dist/tools/zhipin-get-candidate-info.d.ts +20 -10
- package/dist/tools/zhipin-get-candidate-list.d.ts +11 -14
- package/dist/tools/zhipin-get-username.d.ts +7 -9
- package/dist/tools/zhipin-locate-resume-canvas.d.ts +2 -2
- package/dist/tools/zhipin-open-chat-page.d.ts +8 -15
- package/dist/tools/zhipin-open-chat.d.ts +12 -2
- package/dist/tools/zhipin-open-recommend-page.d.ts +8 -17
- package/dist/tools/zhipin-read-messages.d.ts +9 -0
- package/dist/tools/zhipin-say-hello.d.ts +13 -23
- package/dist/tools/zhipin-scroll-view.d.ts +10 -0
- package/dist/tools/zhipin-send-reply.d.ts +14 -6
- package/package.json +2 -2
- package/references/zhipin-diagnostics.md +77 -0
- package/references/zhipin-workflows.md +102 -0
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{BrowserRuntimeConfigSchema as n}from"@roll-agent/browser";import{defineTool as a}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as o}from"@roll-agent/sdk";import{BrowserRuntime as i,BrowserContextManager as s,SessionStore as c}from"@roll-agent/browser";var l,u,d,m=!1,g=o("browser-use-agent");async function f(e){l||(m=!1,d=new c(e.sessionsDir),l=new i(e),await l.start(),u=new s(l,d))}function p(){if(!l)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return l}function h(){if(!u)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return u}function w(){if(!d)throw new Error("SessionStore not initialized. Call initRuntime() first.");return d}function y(e){m=e}function b(){return m}async function v(){const e=u,t=l,n=[];if(u=void 0,l=void 0,d=void 0,m=!1,e){g.info("Closing browser contexts...");try{await e.closeAll()}catch(e){n.push(new Error("Failed to close browser contexts",{cause:e})),g.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(t){g.info("Stopping browser process...");try{await t.stop()}catch(e){n.push(new Error("Failed to stop browser runtime",{cause:e})),g.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(g.info("Browser runtime shutdown complete"),n.length>0)throw new AggregateError(n,"Browser runtime shutdown failed")}var x=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),S=a({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:x,execute:async(e,t)=>{const n=p();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as C}from"@roll-agent/sdk";import{z as k}from"zod";import{BrowserStatusSchema as A}from"@roll-agent/browser";import{createHash as I}from"node:crypto";import{z as R}from"zod";var M=["REPLY_AUTHORITY_KEYS_URL","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],P=/^[0-9a-f]{8}$/,B=R.object({present:R.boolean(),fingerprint:R.string().regex(P).optional()}),E=R.record(B);function _(e,t=process.env){return Object.fromEntries(e.map(e=>{const n=t[e];return"string"==typeof n&&n.length>0?[e,{present:!0,fingerprint:T(n)}]:[e,{present:!1}]}))}function T(e){return I("sha256").update(e).digest("hex").slice(0,8)}var q,$=180,N=60,L=280,z="roll-agent-visual-cursor-root",F="__rollVisualCursorState";function j(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function O(){return void 0!==q?q:j(process.env.BROWSER_VISUAL_CURSOR)??!0}async function D(e){return await e.evaluate(e=>{const t=e.getBoundingClientRect();return t.width<=0||t.height<=0?null:{x:Math.round(t.left+t.width/2),y:Math.round(t.top+t.height/2)}})}async function U(e,t){await e.evaluate(e=>{const t="roll-agent-visual-cursor-root",n="roll-agent-visual-cursor-pointer",a="__rollVisualCursorState",r=(()=>{const e=document.getElementById(t);if(e)return e;const a=document.createElement("div");a.id=t,a.style.position="fixed",a.style.left="0",a.style.top="0",a.style.width="0",a.style.height="0",a.style.pointerEvents="none",a.style.zIndex="2147483647";const r=document.createElement("div");return r.id=n,r.setAttribute("aria-hidden","true"),r.style.position="fixed",r.style.left="0",r.style.top="0",r.style.width="24px",r.style.height="24px",r.style.opacity="0",r.style.transform="translate(-9999px, -9999px)",r.style.willChange="transform, opacity",r.style.filter="drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))",r.innerHTML='<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/></svg>',a.append(r),document.documentElement.append(a),a})(),o=r.querySelector(`#${n}`);if(!o)return;const i=window[a],s=e.point,c=e.durationMs,l=s.x-2,u=s.y-2;if(i?o.style.transition=`transform ${c}ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease`:(o.style.transition="opacity 120ms ease",o.style.transform=`translate(${l}px, ${u}px)`),o.style.opacity="1",o.style.transform=`translate(${l}px, ${u}px)`,window[a]={x:s.x,y:s.y},!e.clickPulse)return;const d=document.createElement("div");d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.left="0",d.style.top="0",d.style.width="18px",d.style.height="18px",d.style.borderRadius="9999px",d.style.border="2px solid rgba(20, 184, 166, 0.95)",d.style.background="rgba(20, 184, 166, 0.18)",d.style.pointerEvents="none",d.style.opacity="0.9",d.style.transform=`translate(${s.x-9}px, ${s.y-9}px) scale(0.55)`,d.style.transition=`transform ${e.pulseDurationMs}ms ease, opacity ${e.pulseDurationMs}ms ease`,r.append(d),requestAnimationFrame(()=>{d.style.opacity="0",d.style.transform=`translate(${s.x-18}px, ${s.y-18}px) scale(2)`}),globalThis.setTimeout(()=>{d.remove()},e.pulseDurationMs+40)},{point:t.point,durationMs:t.durationMs,clickPulse:t.clickPulse,pulseDurationMs:t.pulseDurationMs})}function V(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function H(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:z,stateKey:F})}async function W(e,t={}){if(e.isClosed())return!1;let n=!1;for(const a of V(e))if(a!==t.preserveTarget)try{await H(a),n=!0}catch{}return n}async function K(e,t,n={}){if(!O()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const a=await D(t);if(!a)return!1;const r=n.durationMs??$,o=n.settleMs??N,i=n.target??e;return await W(e,{preserveTarget:i}),await U(n.target??e,{point:a,durationMs:r,clickPulse:!1,pulseDurationMs:L}),await e.waitForTimeout(Math.max(r+o,0)),!0}catch{return!1}}async function Z(e,t,n={}){if(!O()||e.isClosed())return!1;try{const a=await D(t);if(!a)return!1;const r=n.pulseDurationMs??L,o=n.target??e;return await W(e,{preserveTarget:o}),await U(o,{point:a,durationMs:0,clickPulse:!0,pulseDurationMs:r}),await e.waitForTimeout(r),!0}catch{return!1}}async function G(e,t,n){return await K(e,e.locator(t).first(),n)}async function Y(e,t){return await Z(e,e.locator(t).first())}var J,X={info:{accent:"#14b8a6",accentSoft:"rgba(20, 184, 166, 0.42)",accentGlow:"rgba(20, 184, 166, 0.18)",capsuleBg:"rgba(15, 23, 42, 0.82)",capsuleBorder:"rgba(45, 212, 191, 0.38)",text:"#F8FAFC",dot:"#2DD4BF"},success:{accent:"#22c55e",accentSoft:"rgba(34, 197, 94, 0.42)",accentGlow:"rgba(34, 197, 94, 0.18)",capsuleBg:"rgba(10, 24, 16, 0.86)",capsuleBorder:"rgba(74, 222, 128, 0.38)",text:"#F0FDF4",dot:"#4ADE80"},error:{accent:"#f59e0b",accentSoft:"rgba(245, 158, 11, 0.42)",accentGlow:"rgba(245, 158, 11, 0.2)",capsuleBg:"rgba(41, 24, 10, 0.88)",capsuleBorder:"rgba(251, 191, 36, 0.4)",text:"#FFFBEB",dot:"#FBBF24"}},Q=14,ee=720;function te(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function ne(e="info"){return X[e]}function ae(){return void 0!==J?J:te(process.env.BROWSER_VISUAL_ACTIVITY)??!0}async function re(e,t=Q){return await e.evaluate((e,t)=>{const n=e.getBoundingClientRect();if(n.width<=0||n.height<=0)return null;const a=globalThis.innerWidth,r=globalThis.innerHeight,o=Math.max(t,0),i=Math.max(n.left-o,0),s=Math.max(n.top-o,0),c=Math.min(n.right+o,a),l=Math.min(n.bottom+o,r);return{x:Math.round(i),y:Math.round(s),width:Math.max(Math.round(c-i),0),height:Math.max(Math.round(l-s),0)}},t)}async function oe(e,t){await e.evaluate(e=>{const t="roll-agent-visual-activity-style",n="roll-agent-visual-activity-root",a="roll-agent-visual-activity-viewport",r="roll-agent-visual-activity-region",o="roll-agent-visual-activity-region-shine",i="roll-agent-visual-activity-capsule",s="roll-agent-visual-activity-dot",c="roll-agent-visual-activity-label",l=(e,t,n,a=!1)=>{a&&(e.style.transition="none",t.style.transition="none",n.style.transition="none"),e.style.opacity="0",e.style.transform="scale(0.995)",t.style.opacity="0",n.style.opacity="0",n.style.transform="translate(-50%, -8px)",t.style.transform="translate(-9999px, -9999px)",a&&requestAnimationFrame(()=>{e.style.transition="opacity 180ms ease, transform 220ms ease",t.style.transition="transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease",n.style.transition="opacity 180ms ease, transform 220ms ease"})};(()=>{if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n @keyframes roll-visual-activity-breathe {\n 0%, 100% { transform: translate(-50%, 0px) scale(1); }\n 50% { transform: translate(-50%, -1px) scale(1.01); }\n }\n @keyframes roll-visual-activity-scan {\n 0% { transform: translateX(-140%) skewX(-18deg); }\n 100% { transform: translateX(200%) skewX(-18deg); }\n }\n ",document.head.append(e)})(),(()=>{const e=document.getElementById(n);if(e)return e;const t=document.createElement("div");t.id=n,t.style.position="fixed",t.style.inset="0",t.style.pointerEvents="none",t.style.zIndex="2147483646";const l=document.createElement("div");l.id=a,l.setAttribute("aria-hidden","true"),l.style.position="fixed",l.style.inset="10px",l.style.borderRadius="20px",l.style.opacity="0",l.style.transform="scale(0.995)",l.style.transition="opacity 180ms ease, transform 220ms ease";const u=document.createElement("div");u.id=r,u.setAttribute("aria-hidden","true"),u.style.position="fixed",u.style.left="0",u.style.top="0",u.style.width="0",u.style.height="0",u.style.borderRadius="18px",u.style.opacity="0",u.style.overflow="hidden",u.style.transform="translate(-9999px, -9999px)",u.style.transition="transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease";const d=document.createElement("div");d.id=o,d.setAttribute("aria-hidden","true"),d.style.position="absolute",d.style.inset="0",d.style.animation="roll-visual-activity-scan 1.6s linear infinite",d.style.opacity="0.9",u.append(d);const m=document.createElement("div");m.id=i,m.setAttribute("aria-hidden","true"),m.style.position="fixed",m.style.left="50%",m.style.top="20px",m.style.display="inline-flex",m.style.alignItems="center",m.style.gap="10px",m.style.padding="10px 14px",m.style.borderRadius="999px",m.style.opacity="0",m.style.transform="translate(-50%, -8px)",m.style.transition="opacity 180ms ease, transform 220ms ease",m.style.backdropFilter="blur(12px)",m.style.animation="roll-visual-activity-breathe 1.8s ease-in-out infinite",m.style.fontSize="13px",m.style.fontWeight="600",m.style.lineHeight="18px",m.style.letterSpacing="0.01em",m.style.whiteSpace="nowrap";const g=document.createElement("div");g.id=s,g.setAttribute("aria-hidden","true"),g.style.width="8px",g.style.height="8px",g.style.borderRadius="999px",g.style.flex="0 0 auto";const f=document.createElement("div");f.id=c,f.setAttribute("aria-live","polite"),m.append(g,f),t.append(l,u,m),document.documentElement.append(t)})();const u=document.getElementById(a),d=document.getElementById(r),m=document.getElementById(o),g=document.getElementById(i),f=document.getElementById(s),p=document.getElementById(c);if(!(u&&d&&m&&g&&f&&p))return;const h=window.__rollVisualActivityTimers??={};if(void 0!==h.hideTimer&&(globalThis.clearTimeout(h.hideTimer),delete h.hideTimer),"clear"===e.mode)return void l(u,d,g,!0);const w=e.theme;if(void 0!==w&&(u.style.border=`1px solid ${w.accentSoft}`,u.style.boxShadow=`inset 0 0 0 1px ${w.accentSoft}, 0 0 52px ${w.accentGlow}`,g.style.border=`1px solid ${w.capsuleBorder}`,g.style.background=w.capsuleBg,g.style.color=w.text,g.style.boxShadow=`0 18px 46px rgba(15, 23, 42, 0.24), 0 0 0 1px ${w.capsuleBorder}`,f.style.background=w.dot,f.style.boxShadow=`0 0 0 5px ${w.accentGlow}`,d.style.border=`1px solid ${w.accentSoft}`,d.style.background=w.accentGlow,d.style.boxShadow=`0 0 0 1px ${w.accentSoft}, 0 16px 42px ${w.accentGlow}`,m.style.background="linear-gradient(115deg, transparent 0%, rgba(255,255,255,0.08) 24%, rgba(255,255,255,0.42) 50%, transparent 76%)"),void 0!==e.label&&(p.textContent=e.label),g.style.opacity="1",g.style.transform="translate(-50%, 0)",u.style.opacity="complete"===e.mode?"0.9":"0.72",u.style.transform="scale(1)","begin"===e.mode)return d.style.opacity="0",void(d.style.transform="translate(-9999px, -9999px)");if(void 0!==e.rect&&(d.style.width=`${e.rect.width}px`,d.style.height=`${e.rect.height}px`,d.style.transform=`translate(${e.rect.x}px, ${e.rect.y}px)`,d.style.opacity="1"),"complete"!==e.mode)return;const y=Math.max(e.lingerMs??0,0);h.hideTimer=globalThis.setTimeout(()=>{l(u,d,g),delete h.hideTimer},y)},t)}async function ie(e,t){if(!ae()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"begin",label:t.label,theme:ne(t.tone??"info")}),!0}catch{return!1}}async function se(e,t,n={}){if(!ae()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const a=await re(t,n.padding??Q);return!!a&&(await oe(n.target??e,{mode:"highlight",...void 0!==n.label?{label:n.label}:{},theme:ne(n.tone??"info"),rect:a}),!0)}catch{return!1}}async function ce(e,t,n){return await se(e,e.locator(t).first(),n)}async function le(e,t){if(!ae()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"complete",label:t.label,theme:ne("error"===t.status?"error":"success"),lingerMs:t.lingerMs??ee}),!0}catch{return!1}}async function ue(e,t={}){if(!ae()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"clear"}),!0}catch{return!1}}var de=A.extend({replyAuthorityKeysLoaded:k.boolean(),visualCursorEnabled:k.boolean(),visualActivityEnabled:k.boolean(),effectiveEnvSources:E}),me=C({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:k.object({}),output:de,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=p(),a=h(),r=w(),o=n.isRunning(),{headless:i,mode:s}=n.getConfig(),c=a.getActivePlatforms(),l=[];for(const e of c){const t=a.getPageCount(e),o=a.getCurrentUrl(e);let i=null,s="unknown";if(n.shouldRestoreSessionSnapshot()){const[t,n]=await Promise.all([r.loadCookies(e),r.loadLocalStorage(e)]);i=void 0!==t&&t.length>0||void 0!==n&&Object.keys(n).length>0,s=i?"snapshot":"none"}else n.usesPersistentProfile()&&(i=null,s="profile");l.push({platform:e,pagesOpen:t,currentUrl:o,hasLoginState:i,loginStateSource:s})}return{running:o,headless:i,mode:s,activeSessions:l,replyAuthorityKeysLoaded:b(),visualCursorEnabled:O(),visualActivityEnabled:ae(),effectiveEnvSources:_(M)}}});import{defineTool as ge}from"@roll-agent/sdk";import{BrowserPageInfoSchema as fe,PlatformSchema as pe}from"@roll-agent/browser";import{z as he}from"zod";import{PLATFORMS as we}from"@roll-agent/browser";var ye={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function be(e){return new URL(ye[e]).host}function ve(e,t){try{return new URL(e).host.includes(be(t))}catch{return!1}}function xe(e){return we.find(t=>ve(e,t))}function Se(e,t){const n=xe(t.url)??null;return{pageId:t.targetId,url:t.url,title:t.title,boundPlatform:e.getBoundPlatformForNativePage(t.targetId)??null,detectedPlatform:n,isSelectedForPlatform:e.isNativePageSelected(t.targetId)}}async function Ce(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:xe(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var ke=he.object({platform:pe.optional().describe("可选:仅返回指定平台相关的页面")}),Ae=he.object({pages:he.array(fe)}),Ie=ge({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:ke,output:Ae,execute:async(e,t)=>{const n=h();t.logger.info("Listing browser pages");const a=(await n.listNativePages()).map(e=>Se(n,e));return{pages:void 0===e.platform?a:a.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{defineTool as Re}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Me}from"@roll-agent/browser";import{z as Pe}from"zod";async function Be(e,t){return e.useTrackedPage(t,e=>ve(e.url(),t))}async function Ee(e,t){return(await e.listNativePages()).find(e=>ve(e.url,t))}async function _e(e,t){const n=await Ee(e,t);if(n)return await e.activateNativePage(n.targetId),{page:n,reusedExistingPage:!0};return{page:await e.openNativePage(ye[t]),reusedExistingPage:!1}}var Te,qe=Pe.object({url:Pe.string().url().describe("要导航到的目标 URL")}),$e=Pe.object({success:Pe.boolean(),page:Me});function Ne(){return{getContextManager:h,detectPlatformFromUrl:xe,matchesPlatformHost:ve,findTrackedPlatformPage:Be,toAttachedPageInfo:Ce,...Te}}async function Le(e,t,n){const a=(await e.listNativePages()).find(e=>t.matchesPlatformHost(e.url,n));if(a)return await e.selectNativePage(n,a.targetId),await e.getPage(n)}async function ze(e,t,n,a){const r=t.detectPlatformFromUrl(n);if(!r){const t=await e.getActivePage();if(!t)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return{page:t}}const o=await t.findTrackedPlatformPage(e,r);if(o)return a.info(`Reusing tracked ${r} page instead of navigating the current unrelated tab`),{page:o,platform:r};const i=await Le(e,t,r);if(i)return a.info(`Reusing native ${r} page instead of navigating the current unrelated tab`),{page:i,platform:r};const s=await e.getActivePage();if(!s)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return a.warn(`No existing ${r} page found; falling back to navigating the current active tab`),{page:s,platform:r}}async function Fe(e,t,n){return await e.selectAttachedPage(n,e.getPageId(t))}async function je(e,t){e.url()!==t&&await e.goto(t,{waitUntil:"domcontentloaded"})}var Oe=Re({name:"navigate_active_tab",description:"导航到指定 URL;若 URL 属于已知平台(Boss/鱼泡),优先复用已打开的平台页,避免把无关 tab 导航成第二个平台页。",input:qe,output:$e,execute:async(e,t)=>{const n=Ne(),a=n.getContextManager();t.logger.info(`Navigating active tab to ${e.url}`);const{page:r,platform:o}=await ze(a,n,e.url,t.logger);await r.bringToFront().catch(()=>{}),await je(r,e.url);const i=o??n.detectPlatformFromUrl(r.url()),s=i?await Fe(a,r,i):r;return i?t.logger.info(`Bound navigated page to ${i}`):a.clearBindingForPage(r),{success:!0,page:await n.toAttachedPageInfo(a,s)}}});import{defineTool as De}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ue,PlatformSchema as Ve}from"@roll-agent/browser";import{z as He}from"zod";var We=He.object({platform:Ve.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡")}),Ke=He.object({success:He.boolean(),page:Ue,reusedExistingTab:He.boolean()}),Ze=De({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:We,output:Ke,execute:async(e,t)=>{const{platform:n}=e,a=p(),r=h();t.logger.info(`Opening platform page for ${n}`);const{page:o,reusedExistingPage:i}=await _e(a,n);return r.rememberNativePageSelection(n,o),{success:!0,page:Se(r,o),reusedExistingTab:i}}});import{defineTool as Ge}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ye,PlatformSchema as Je}from"@roll-agent/browser";import{z as Xe}from"zod";var Qe=Xe.object({platform:Je.describe("要将该页面绑定为当前活跃页的平台"),pageId:Xe.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),et=Xe.object({success:Xe.boolean(),page:Ye}),tt=Ge({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:Qe,output:et,execute:async(e,t)=>{const n=h();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const a=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:Se(n,a)}}});import{defineTool as nt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as at}from"@roll-agent/browser";import{z as rt}from"zod";var ot=["native","native-watch","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],it=[...ot,"browser-attach-watch"],st=rt.enum(["localStorage","sessionStorage"]),ct=rt.enum(["array","object","string","number","boolean","null"]),lt=rt.enum(["empty","json","string"]),ut=rt.enum(ot),dt=rt.enum(it),mt=rt.enum(["request","response"]),gt=rt.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),ft=rt.object({area:st,key:rt.string(),valueLength:rt.number().int().nonnegative(),valueKind:lt,jsonKind:ct.optional(),jsonTopLevelKeys:rt.array(rt.string()).optional(),jsonArrayLength:rt.number().int().nonnegative().optional(),numericFields:rt.record(rt.number()).optional(),booleanFields:rt.record(rt.boolean()).optional(),arrayLengths:rt.record(rt.number().int().nonnegative()).optional()}),pt=rt.object({name:rt.string(),domain:rt.string(),path:rt.string(),expires:rt.string(),valueLength:rt.number().int().nonnegative(),httpOnly:rt.boolean(),secure:rt.boolean(),sameSite:rt.string().optional()}),ht=rt.object({before:rt.number().optional(),after:rt.number().optional(),delta:rt.number().optional()}),wt=rt.object({before:rt.boolean().optional(),after:rt.boolean().optional()}),yt=rt.object({before:rt.number().int().nonnegative().optional(),after:rt.number().int().nonnegative().optional(),delta:rt.number().int().optional()}),bt=rt.object({area:st,key:rt.string(),beforePresent:rt.boolean(),afterPresent:rt.boolean(),numericDeltas:rt.record(ht).optional(),booleanChanges:rt.record(wt).optional(),arrayLengthDeltas:rt.record(yt).optional()}),vt=rt.object({url:rt.string(),title:rt.string(),visibilityState:rt.string(),hasFocus:rt.boolean()}),xt=rt.object({navigatorWebdriver:rt.boolean().optional(),userAgentContainsHeadless:rt.boolean(),languagesLength:rt.number().int().nonnegative(),pluginsLength:rt.number().int().nonnegative(),hasChromeRuntime:rt.boolean(),permissionQueryIsNative:rt.boolean().optional(),hasPlaywrightBinding:rt.boolean(),hasPwInitScripts:rt.boolean(),cdcKeys:rt.array(rt.string()),webdriverKeys:rt.array(rt.string()),automationLikeWindowKeys:rt.array(rt.string())}),St=rt.object({phase:ut,success:rt.boolean(),durationMs:rt.number().int().nonnegative(),error:rt.string().optional()}),Ct=rt.object({phase:dt,capturedAt:rt.string(),targetFound:rt.boolean(),page:at.optional(),urlChangedFromPrevious:rt.boolean(),titleChangedFromPrevious:rt.boolean(),previousUrl:rt.string().optional(),currentUrl:rt.string().optional(),previousTitle:rt.string().optional(),currentTitle:rt.string().optional()}),kt=rt.object({kind:mt,reason:gt,capturedAt:rt.string(),url:rt.string(),method:rt.string().optional(),resourceType:rt.string().optional(),status:rt.number().int().optional()}),At=rt.object({capturedAt:rt.string(),url:rt.string()}),It=rt.object({phase:ut.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;需要显式传更深阶段才会 attach browser/page、监听网络、evaluate、读取检测指纹或读取 storage。"),targetPageId:rt.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:rt.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:rt.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),Rt=rt.object({success:rt.boolean(),requestedPhase:ut,mode:rt.string(),nativePages:rt.array(at),targetPage:at.optional(),browserAttached:rt.boolean(),pageAttached:rt.boolean(),nativeTimeline:rt.array(Ct),networkEvents:rt.array(kt).optional(),navigationEvents:rt.array(At).optional(),evaluate:vt.optional(),detectorFingerprint:xt.optional(),storage:rt.object({localStorage:rt.array(ft),sessionStorage:rt.array(ft),cookies:rt.array(pt),counterDiffs:rt.array(bt)}).optional(),phases:rt.array(St),warnings:rt.array(rt.string())}),Mt=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Pt=new Set(Mt);function Bt(e){if(null===e)return"null";if(Array.isArray(e))return"array";switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":default:return"string";case"object":return"object"}}function Et(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function _t(e){try{return JSON.parse(e)}catch{return}}function Tt(e){const t=Object.fromEntries(Object.entries(e).filter(e=>"number"==typeof e[1])),n=Object.fromEntries(Object.entries(e).filter(e=>"boolean"==typeof e[1])),a=Object.fromEntries(Object.entries(e).filter(e=>Array.isArray(e[1])).map(([e,t])=>[e,t.length]));return{...Object.keys(t).length>0?{numericFields:t}:{},...Object.keys(n).length>0?{booleanFields:n}:{},...Object.keys(a).length>0?{arrayLengths:a}:{}}}function qt(e){return new Promise(t=>{setTimeout(t,e)})}function $t(e){return Pt.has(e)}function Nt(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=_t(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const a=Bt(n),r=Et(n)&&$t(t.key)?Tt(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:a,...Et(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...r}}function Lt(e){return{name:e.name,domain:e.domain,path:e.path,expires:e.expires>0?new Date(1e3*e.expires).toISOString():"Session",valueLength:e.value.length,httpOnly:e.httpOnly,secure:e.secure,...void 0!==e.sameSite?{sameSite:e.sameSite}:{}}}async function zt(e,t){const n=Date.now();try{return{result:await t(),phaseResult:{phase:e,success:!0,durationMs:Date.now()-n}}}catch(t){return{phaseResult:{phase:e,success:!1,durationMs:Date.now()-n,error:t instanceof Error?t.message:String(t)}}}}function Ft(e,t,n){if(void 0!==t){const a=e.find(e=>e.targetId===t);return a?ve(a.url,"zhipin")?a:void n.push(`targetPageId "${t}" is not a zhipin page.`):void n.push(`targetPageId "${t}" not found in native pages.`)}const a=e.filter(e=>ve(e.url,"zhipin"));if(1===a.length)return a[0];0===a.length?n.push("No zhipin native page found. Open Boss first or pass targetPageId."):n.push("Multiple zhipin native pages found. Pass targetPageId to avoid ambiguity.")}function jt(e,t,n,a){const r=n?.url,o=n?.title,i=a?.currentUrl,s=a?.currentTitle;return{phase:t,capturedAt:(new Date).toISOString(),targetFound:void 0!==n,...void 0!==n?{page:Se(e,n)}:{},urlChangedFromPrevious:void 0!==i&&void 0!==r&&i!==r,titleChangedFromPrevious:void 0!==s&&void 0!==o&&s!==o,...void 0!==i?{previousUrl:i}:{},...void 0!==r?{currentUrl:r}:{},...void 0!==s?{previousTitle:s}:{},...void 0!==o?{currentTitle:o}:{}}}async function Ot(e,t,n,a){const r=await e.listNativePages();return jt(e,t,void 0!==n?r.find(e=>e.targetId===n):r.filter(e=>ve(e.url,"zhipin"))[0],a)}async function Dt(e,t,n,a,r){try{const o=t[t.length-1],i=await Ot(e,n,a,o);t.push(i),i.urlChangedFromPrevious&&r.push(`Target URL changed after ${n}: ${i.previousUrl??"(unknown)"} -> ${i.currentUrl??"(missing)"}.`)}catch(e){r.push(`Failed to capture native snapshot after ${n}: ${e instanceof Error?e.message:String(e)}.`)}}async function Ut(e,t,n,a,r,o){const i=t.length,s=Date.now();for(;;){await Dt(e,t,n,a,o);const i=r-(Date.now()-s);if(i<=0)break;await qt(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Vt(e){const t=e.toLowerCase();return t.includes("device-action-report")?"device-action-report":t.includes("boss_risk_report")?"boss-risk-report":t.includes("apm-fe.zhipin.com")||t.includes("/wapi/zpapm/actionlog/")?"apm-action-log":t.includes("zhipin-security")||t.includes("/security/")?"zhipin-security":void 0}async function Ht(e,t,n){const a=[],r=[],o=e=>{a.length<n&&a.push(e)},i=e=>{const t=e.url(),n=Vt(t);void 0!==n&&o({kind:"request",reason:n,capturedAt:(new Date).toISOString(),url:t,method:e.method(),resourceType:e.resourceType()})},s=e=>{const t=e.url(),n=Vt(t);if(void 0===n)return;const a=e.request();o({kind:"response",reason:n,capturedAt:(new Date).toISOString(),url:t,method:a.method(),resourceType:a.resourceType(),status:e.status()})},c=e=>{r.push({capturedAt:(new Date).toISOString(),url:e.url()})};e.on("request",i),e.on("response",s),e.on("framenavigated",c);try{await qt(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:a,navigationEvents:r}}function Wt(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&ve(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function Kt(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function Zt(e,t){return await e.evaluate(e=>{const t="localStorage"===e?window.localStorage:window.sessionStorage;return Array.from({length:t.length},(e,n)=>{const a=t.key(n)??"";return{key:a,value:a.length>0?t.getItem(a)??"":""}}).filter(e=>e.key.length>0)},t)}async function Gt(e){return(await e.evaluate(e=>{const t=["localStorage","sessionStorage"],n=[];for(const a of t){const t="localStorage"===a?window.localStorage:window.sessionStorage;for(const r of e){const e=t.getItem(r);null!==e&&n.push({area:a,key:r,value:e})}}return n},Mt)).map(e=>Nt(e.area,{key:e.key,value:e.value}))}async function Yt(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function Jt(e){return await e.evaluate(()=>{const e=window,t=Object.keys(e),n=t.filter(e=>e.startsWith("cdc_")||e.includes("_cdc_")).slice(0,20),a=t.filter(e=>e.toLowerCase().includes("webdriver")).slice(0,20),r=t.filter(e=>{const t=e.toLowerCase();return t.includes("playwright")||t.includes("puppeteer")||t.includes("selenium")||t.includes("chromedriver")}).slice(0,20),o=navigator.permissions?.query,i="function"==typeof o?o.toString().includes("[native code]"):void 0;return{..."boolean"==typeof navigator.webdriver?{navigatorWebdriver:navigator.webdriver}:{},userAgentContainsHeadless:navigator.userAgent.toLowerCase().includes("headless"),languagesLength:navigator.languages?.length??0,pluginsLength:navigator.plugins?.length??0,hasChromeRuntime:"object"==typeof e.chrome&&null!==e.chrome&&"runtime"in e.chrome,...void 0!==i?{permissionQueryIsNative:i}:{},hasPlaywrightBinding:"__playwright__binding__"in e,hasPwInitScripts:"__pwInitScripts"in e,cdcKeys:n,webdriverKeys:a,automationLikeWindowKeys:r}})}function Xt(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function Qt(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function en(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function tn(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function nn(e,t){const n=Xt(e),a=Xt(t),r=[...new Set([...n.keys(),...a.keys()])],o=[];for(const e of r){const t=n.get(e),r=a.get(e),i=r??t;if(!i)continue;const s={};for(const e of Qt(t?.numericFields,r?.numericFields)){const n=t?.numericFields?.[e],a=r?.numericFields?.[e];n!==a&&(s[e]=en(n,a))}const c={};for(const e of Qt(t?.booleanFields,r?.booleanFields)){const n=t?.booleanFields?.[e],a=r?.booleanFields?.[e];n!==a&&(c[e]={...void 0!==n?{before:n}:{},...void 0!==a?{after:a}:{}})}const l={};for(const e of Qt(t?.arrayLengths,r?.arrayLengths)){const n=t?.arrayLengths?.[e],a=r?.arrayLengths?.[e];n!==a&&(l[e]=tn(n,a))}void 0!==t&&void 0!==r&&0===Object.keys(s).length&&0===Object.keys(c).length&&0===Object.keys(l).length||o.push({area:i.area,key:i.key,beforePresent:void 0!==t,afterPresent:void 0!==r,...Object.keys(s).length>0?{numericDeltas:s}:{},...Object.keys(c).length>0?{booleanChanges:c}:{},...Object.keys(l).length>0?{arrayLengthDeltas:l}:{}})}return o}async function an(e,t){const[n,a,r]=await Promise.all([Zt(e,"localStorage"),Zt(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>$t(e.key)).map(e=>Nt("localStorage",e)),...a.filter(e=>$t(e.key)).map(e=>Nt("sessionStorage",e))];return{localStorage:n.map(e=>Nt("localStorage",e)),sessionStorage:a.map(e=>Nt("sessionStorage",e)),cookies:r.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>Lt(e)),counterDiffs:nn(t,o)}}var rn=nt({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在 CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;browser-attach 会追加 native URL 观察窗口;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:It,output:Rt,execute:async(e,t)=>{const n=e.phase??"native",a=e.watchMs??3e3,r=e.networkEventLimit??30,o=h(),i=p(),s=[],c=[],l=[];let u,d,m,g=!1,f=!1,w=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const y=await zt("native",async()=>await o.listNativePages());c.push(y.phaseResult);const b=y.result??[],v=b.map(e=>Se(o,e)),x=Ft(b,e.targetPageId,s);if(x&&(u=Se(o,x),w=x.targetId,l.push(jt(o,"native",x,void 0))),!y.phaseResult.success||"native"===n)return{success:y.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Ut(o,l,"native-watch",w,a,s),{success:y.phaseResult.success&&void 0!==w,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if(!x)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:v,browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};const S=await zt("browser-attach",async()=>await i.getBrowser());c.push(S.phaseResult);const C=S.result;g=S.phaseResult.success,await Dt(o,l,"browser-attach",w,s);const k=await Ut(o,l,"browser-attach-watch",w,a,s);if(k&&s.push("Browser attach was followed by a native URL change; treat this account/browser profile as unsafe for Playwright-backed zhipin tools."),!S.phaseResult.success||"browser-attach"===n)return{success:c.every(e=>e.success)&&!k,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if(k)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};const A=[];let I;"network-watch"===n&&void 0!==C&&(I=Wt(C,x),I?A.push(Ht(I,a,r)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",x);const R=await zt("page-attach",async()=>await o.getPage("zhipin"));c.push(R.phaseResult);const M=R.result;if(f=R.phaseResult.success,M&&(u=await Ce(o,M)),await Dt(o,l,"page-attach",w,s),!R.phaseResult.success||!M){if("network-watch"===n&&A.length>0){const e=await zt("network-watch",async()=>Kt(await Promise.all(A),r));c.push(e.phaseResult),e.result&&(d=e.result.networkEvents,m=e.result.navigationEvents)}return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,...void 0!==d?{networkEvents:d}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}if("page-attach"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){I!==M&&A.push(Ht(M,a,r));const e=await zt("network-watch",async()=>Kt(await Promise.all(A),r));return c.push(e.phaseResult),e.result&&(d=e.result.networkEvents,m=e.result.navigationEvents),await Dt(o,l,"network-watch",w,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,...void 0!==d?{networkEvents:d}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let P=[];if("storage-summary"===n){try{P=await Gt(M)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}a>0&&await qt(a)}const B=await zt("page-evaluate",async()=>await Yt(M));c.push(B.phaseResult);const E=B.result;if(await Dt(o,l,"page-evaluate",w,s),!B.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},phases:c,warnings:s};const _=await zt("detector-fingerprint",async()=>await Jt(M));c.push(_.phaseResult);const T=_.result;if(await Dt(o,l,"detector-fingerprint",w,s),!_.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==T?{detectorFingerprint:T}:{},phases:c,warnings:s};const q=await zt("storage-summary",async()=>await an(M,P));c.push(q.phaseResult);const $=q.result;return await Dt(o,l,"storage-summary",w,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==u?{targetPage:u}:{},browserAttached:g,pageAttached:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==T?{detectorFingerprint:T}:{},...void 0!==$?{storage:$}:{},phases:c,warnings:s}}});import{defineTool as on}from"@roll-agent/sdk";import{z as sn}from"zod";import{setTimeout as cn}from"node:timers/promises";async function ln(e,t=300,n=800){const a=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(a)}async function un(e){const t=Math.random();let n;n=t<.5?800+1200*Math.random():t<.8?500+300*Math.random():t<.95?2e3+2e3*Math.random():4e3+2e3*Math.random(),await e.waitForTimeout(Math.floor(n))}async function dn(e,t){const n=t?.minDistance??50,a=t?.maxDistance??200,r=t?.direction??"both",o=Math.floor(Math.random()*(a-n))+n,i="up"===r?-1:"down"===r||Math.random()>.5?1:-1;await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},o*i),await ln(e,200,500)}function mn(e=.3){return Math.random()<e}var gn=".geek-item.selected";async function fn(e){try{await e.waitForSelector(gn,{timeout:5e3})}catch{return null}const t=await e.evaluate(()=>{const e=document.querySelector(".geek-item.selected");if(!e)return null;const t=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"";return{conversationId:t,candidateId:e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??t,candidateName:e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name')?.textContent?.trim()??""}});return t&&"string"==typeof t.conversationId&&"string"==typeof t.candidateId&&0!==t.conversationId.length&&0!==t.candidateId.length?t:null}async function pn(e){const t=await e.evaluate(()=>{const e=[".chat-conversation",".conversation-box",".conversation-message"],t=[".base-info-single-detial .name-box",".base-info-content .name-box",".base-info-single-container .name-box",".base-info-content .base-name",".chat-user-name",".name-box",".base-name"];for(const n of e){const e=document.querySelector(n);if(e)for(const n of t){const t=e.querySelector(n)?.textContent?.trim()??"";if(t.length>0)return{candidateName:t}}}return null});return t&&"string"==typeof t.candidateName&&0!==t.candidateName.length?t:null}var hn="https://www.zhipin.com/web/geek/chat",wn=".chat-list-wrap, .geek-item",yn=new Set(["消息"]),bn="data-roll-chat-entry-target",vn="data-roll-chat-item-target";function xn(e){return e.trim().toLocaleLowerCase("zh-CN")}function Sn(e,t){const n=xn(e),a=xn(t);return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}function Cn(e,t){let n=0;for(const a of e)t.includes(a)&&(n+=1);return n}function kn(e,t){if(void 0!==t.conversationId){const n=e.find(e=>e.conversationId===t.conversationId);if(n)return n}const n=t.candidateName;if(n){const t=xn(n),a=e.filter(e=>e.name.length>0);let r=a.find(e=>xn(e.name)===t);if(r)return r;if(r=a.find(e=>{const n=xn(e.name);return n.includes(t)||t.includes(n)}),r)return r;const o=t.length<=2?1:t.length<=4?.75:.6;if(r=a.find(e=>{const n=xn(e.name);return Cn(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),r)return r}if(void 0!==t.index)return e[t.index]}function An(e){return e.includes("/web/geek/chat")||e.includes("/web/chat")}async function In(e,t=1e4){try{return await e.waitForSelector(wn,{timeout:t}),!0}catch{return!1}}async function Rn(e,t){const n=(await e.listAttachedPages()).find(e=>e!==t&&An(e.url()));if(n)return await e.selectAttachedPage("zhipin",e.getPageId(n))}async function Mn(e,t){await e.evaluate(e=>{document.querySelectorAll(`[${e}]`).forEach(t=>{t.removeAttribute(e)})},t).catch(()=>{})}async function Pn(e,t){const n=e.locator(t).first();await n.scrollIntoViewIfNeeded(),await K(e,n),await n.hover(),await ln(e,200,400),await Z(e,n),await n.click()}async function Bn(e){const t=await e.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},n=t=>e.messageLabels.some(e=>t===e||t.includes(e));document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const a=Array.from(document.querySelectorAll('a[href*="/web/geek/chat"], a[href*="/web/chat"]'));for(const n of a)if(t(n))return n.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`};const r=Array.from(document.querySelectorAll('a, button, [role="link"], [role="button"], span, div'));for(const a of r){if(n(a.textContent?.trim()??"")&&t(a))return a.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}}return{found:!1}},{markerAttr:bn,messageLabels:[...yn]});if(!t.found)return!1;try{return await Pn(e,t.selector),!0}finally{await Mn(e,bn)}}function En(e){return e instanceof Error&&/ERR_ABORTED/i.test(e.message)}async function _n(e){try{return await e.goto(hn,{waitUntil:"domcontentloaded"}),!0}catch(t){return!!En(t)&&(!!An(e.url())||await In(e,2e3))}}async function Tn(e,t){if(!await Bn(t))return!1;if(await In(t,5e3))return!0;const n=await Rn(e,t);return!!n&&await In(n,5e3)}async function qn(e,t){if(An(t.url())&&await In(t))return!0;const n=await Rn(e,t);if(n&&await In(n))return!0;if(await Tn(e,t))return!0;if(!await _n(t))return!1;if(await In(t))return!0;await cn(300);const a=await Rn(e,t);return!!a&&await In(a,5e3)}async function $n(e){return e.evaluate(()=>Array.from(document.querySelectorAll(".geek-item")).map((e,t)=>{const n=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"",a=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??n,r=e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name'),o=r?.textContent?.trim()??"",i=e.querySelector(".source-job")?.textContent?.trim()??"",s=e.querySelector(".badge-count"),c=parseInt(s?.textContent?.trim()??"0",10)||0;return{conversationId:n,candidateId:a,name:o,index:t,position:i,hasUnread:c>0||null!==e.querySelector(".red-dot"),unreadCount:c,lastMessageTime:e.querySelector(".time, .time-shadow")?.textContent?.trim()??"",messagePreview:(e.querySelector(".push-text, .chat-last-msg")?.textContent?.trim()??"").slice(0,100)}}))}async function Nn(e,t){const n=await e.evaluate(e=>{document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const t=Array.from(document.querySelectorAll(".geek-item")),n=t.find(t=>(t.getAttribute("data-id")??t.closest('[role="listitem"]')?.getAttribute("key")??"")===e.targetConversationId)??t[e.targetIndex];if(!n)return{found:!1};return(n.querySelector(".chat-item-content")??n).setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}},{markerAttr:vn,targetConversationId:t.conversationId,targetIndex:t.index});if(!n.found)return!1;try{return await Pn(e,n.selector),!0}finally{await Mn(e,vn)}}async function Ln(e,t){if(0===t.conversationId.length&&0===t.name.length)return await ln(e,500,900),!0;try{return await e.waitForFunction(e=>{const t=e=>e.trim().toLocaleLowerCase("zh-CN"),n=document.querySelector(".geek-item.selected"),a=n?.getAttribute("data-id")??n?.closest('[role="listitem"]')?.getAttribute("key")??"",r=0===e.conversationId.length||a===e.conversationId,o=(()=>{const e=[".chat-conversation",".conversation-box",".conversation-message"],t=[".base-info-single-detial .name-box",".base-info-content .name-box",".base-info-single-container .name-box",".base-info-content .base-name",".chat-user-name",".name-box",".base-name"];for(const n of e){const e=document.querySelector(n);if(e)for(const n of t){const t=e.querySelector(n)?.textContent?.trim()??"";if(t.length>0)return t}}return""})(),i=0===e.candidateName.length||((e,n)=>{const a=t(e),r=t(n);return a.length>0&&r.length>0&&(a===r||a.includes(r)||r.includes(a))})(e.candidateName,o);return r&&i},{conversationId:t.conversationId,candidateName:t.name},{timeout:5e3}),!0}catch{return await ln(e,800,1200),!1}}async function zn(e,t,n){if(void 0===n.conversationId&&void 0===n.candidateName&&void 0===n.index)return;if(!await qn(e,t))return{found:!1,conversationId:"",candidateId:"",name:"",index:-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const a=await e.getPage("zhipin"),r=kn(await $n(a),n);if(!r){return{found:!1,conversationId:"",candidateId:"",name:"",index:-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${n.conversationId??n.candidateName??`index ${n.index}`}`}}if(!await Nn(a,r))return{...r,found:!1,error:`打开候选人聊天失败: ${r.name||`index ${r.index}`}`};let o=await Ln(a,r);if(!o){await Nn(a,r)&&(o=await Ln(a,r))}if(!o)return{...r,found:!1,error:`打开候选人聊天后,右侧会话未同步切换到 ${r.name||r.conversationId}`};const i=await fn(a);if(!i||i.conversationId!==r.conversationId)return{...r,found:!1,error:`当前选中会话与目标会话不一致: ${r.name||r.conversationId}`};const s=await pn(a);return!(r.name.length>0)||s&&Sn(r.name,s.candidateName)?{...r,found:!0}:{...r,found:!1,error:`右侧聊天面板仍未切换到 ${r.name}`}}var Fn=["chat-list","chat-history","recommend-list"],jn={"chat-list":{surface:"chat-list",defaultDirection:"down",containerSelectors:[".chat-user .user-container",".chat-user .b-scroll-stable",".chat-list-wrap",".chat-user"],itemSelector:".geek-item",highlightSelector:".chat-user .user-container, .chat-list-wrap, .chat-user"},"chat-history":{surface:"chat-history",defaultDirection:"up",containerSelectors:[".conversation-message",".chat-message-list",".conversation-main",".conversation-box"],itemSelector:".chat-message-list > .message-item, .conversation-message .message-item",highlightSelector:".conversation-message, .chat-message-list, .conversation-main"},"recommend-list":{surface:"recommend-list",defaultDirection:"down",containerSelectors:[".recommend-list-wrap",".recommend-list",".candidate-list",".geek-list",".list-wrap",".recommendV2"],itemSelector:".candidate-card-wrap, [data-geek], .geek-item",highlightSelector:".candidate-card-wrap, [data-geek], .geek-item"}};function On(e){return jn[e]}var Dn=["target-count","boundary","no-new-items","max-steps"],Un=4,Vn=700,Hn=2,Wn=2;function Kn(e){return e.direction??"down"}function Zn(e){const t=e.steps??Un;return Math.max(0,Math.floor(t))}function Gn(e){return Math.max(0,Math.floor(e.settleMs??Vn))}function Yn(e,t){return"up"===t?e.atStart:e.atEnd}async function Jn(e,t){return await e.evaluate(e=>{const t=e=>{const t=window.getComputedStyle(e).overflowY;return"hidden"!==t&&"clip"!==t&&e.scrollHeight>e.clientHeight+2},{element:n,label:a,found:r}=(()=>{for(const n of e.containerSelectors){const e=document.querySelector(n);if(e&&t(e))return{element:e,label:n,found:!0}}const n=document.querySelector(e.itemSelector);let a=n?.parentElement??null;for(;a&&a!==document.body&&a!==document.documentElement;){if(t(a))return{element:a,label:"item-ancestor",found:!0};a=a.parentElement}const r=document.scrollingElement??document.documentElement;return{element:r,label:"document",found:r.scrollHeight>r.clientHeight+2}})(),o=Math.max(0,n.scrollTop),i=o<=2,s=o>=Math.max(0,n.scrollHeight-n.clientHeight)-2;return{containerFound:r,containerLabel:a,scrollTop:o,scrollHeight:n.scrollHeight,clientHeight:n.clientHeight,itemCount:document.querySelectorAll(e.itemSelector).length,atStart:i,atEnd:s}},t)}async function Xn(e,t,n={}){const a=Kn(n),r=Zn(n),o=Gn(n),i=n.stopOnBoundary??!0,s=await Jn(e,t);let c=s,l=0;for(let s=0;s<r&&(!i||!Yn(c,a));s++)c=await e.evaluate(e=>{const t=e=>{const t=window.getComputedStyle(e).overflowY;return"hidden"!==t&&"clip"!==t&&e.scrollHeight>e.clientHeight+2},{element:n,label:a,found:r}=(()=>{for(const n of e.containerSelectors){const e=document.querySelector(n);if(e&&t(e))return{element:e,label:n,found:!0}}const n=document.querySelector(e.itemSelector);let a=n?.parentElement??null;for(;a&&a!==document.body&&a!==document.documentElement;){if(t(a))return{element:a,label:"item-ancestor",found:!0};a=a.parentElement}const r=document.scrollingElement??document.documentElement;return{element:r,label:"document",found:r.scrollHeight>r.clientHeight+2}})(),o=e.distance??Math.max(120,Math.floor(.85*n.clientHeight)),i="up"===e.direction?-o:o;n.scrollBy({top:i,behavior:"auto"});const s=Math.max(0,n.scrollTop),c=Math.max(0,n.scrollHeight-n.clientHeight);return{containerFound:r,containerLabel:a,scrollTop:s,scrollHeight:n.scrollHeight,clientHeight:n.clientHeight,itemCount:document.querySelectorAll(e.itemSelector).length,atStart:s<=2,atEnd:s>=c-2}},{...t,direction:a,distance:n.distance}),l+=1,o>0&&await e.waitForTimeout(o),c=await Jn(e,t);return{success:s.containerFound||c.containerFound,direction:a,stepsRequested:r,stepsCompleted:l,reachedBoundary:Yn(c,a),before:s,after:c}}async function Qn(e,t,n,a,r={}){const o=Kn(r),i=Zn(r),s=Gn(r),c=r.targetCount,l=r.maxNoNewRounds??Hn,u=Math.max(0,Math.floor(r.boundaryLoadRetries??Wn)),d=Math.max(0,Math.floor(r.boundarySettleMs??s)),m=await Jn(e,t),g=new Map;let f=0,p=0,h=m,w=0,y="max-steps";const b=async()=>{let e=0;const t=await n();for(const n of t){const t=a(n);void 0!==t&&0!==t.length&&(g.has(t)?f+=1:(g.set(t,n),e+=1))}return e};await b();for(let n=0;n<i;n++){if(void 0!==c&&g.size>=c){y="target-count";break}if(Yn(h,o)){let n=!1;for(let a=0;a<u;a++){d>0&&await e.waitForTimeout(d);const a=await b(),r=await Jn(e,t),i=a>0||r.scrollHeight>h.scrollHeight||r.itemCount>h.itemCount||!Yn(r,o);if(h=r,i){a>0&&(p=0),n=!0;break}}if(!n){y="boundary";break}if(void 0!==c&&g.size>=c){y="target-count";break}}if(p>=l){y="no-new-items";break}const n=await Xn(e,t,{direction:o,steps:1,settleMs:s,...void 0!==r.distance?{distance:r.distance}:{},...void 0!==r.stopOnBoundary?{stopOnBoundary:r.stopOnBoundary}:{}});w+=n.stepsCompleted,h=n.after;p=await b()>0?0:p+1}return void 0!==c&&g.size>=c?y="target-count":w>=i&&(y="max-steps"),{success:m.containerFound||h.containerFound,direction:o,stepsRequested:i,stepsCompleted:w,reachedBoundary:Yn(h,o),before:m,after:h,items:[...g.values()],uniqueCount:g.size,duplicateCount:f,noNewRounds:p,stopReason:y}}function ea(e){return"function"==typeof e.page}function ta(e){return ea(e)?e.page():e}var na=class{page;target;constructor(e){this.page=ta(e),this.target=e}async retarget(e){if(e===this.target)return this.page=ta(e),this.target=e,!0;const t=this.page,n=this.target;return await ue(t,{target:n}),this.page=ta(e),this.target=e,!0}async begin(e,t="info"){return await ie(this.page,{label:e,tone:t,target:this.target})}async highlightSelector(e,t={}){return await ce(this.page,e,{...t,target:this.target})}async highlightLocator(e,t={}){return await se(this.page,e,{...t,target:this.target})}async succeed(e,t){return await le(this.page,{label:e,...void 0!==t?{lingerMs:t}:{},status:"success",target:this.target})}async fail(e,t){return await le(this.page,{label:e,...void 0!==t?{lingerMs:t}:{},status:"error",target:this.target})}async clear(){return await ue(this.page,{target:this.target})}},aa=sn.object({name:sn.string(),conversationId:sn.string(),candidateId:sn.string(),position:sn.string(),time:sn.string(),preview:sn.string(),unreadCount:sn.number(),hasUnread:sn.boolean(),index:sn.number()}),ra=sn.object({success:sn.boolean(),candidates:sn.array(aa),total:sn.number(),stats:sn.object({withName:sn.number(),withUnread:sn.number()})});function oa(e){return e.conversationId.length>0?e.conversationId:e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.position,e.lastMessageTime].join("|"):void 0}var ia=on({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:sn.object({limit:sn.number().optional().describe("最多返回条数"),onlyUnread:sn.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:sn.enum(["time","unreadCount","name"]).default("time"),autoScroll:sn.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:sn.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:ra,execute:async(e,t)=>{const n=e.onlyUnread??!1;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const a=h(),r=await a.getPage("zhipin"),o=new na(r),i=n?"正在读取未读消息列表":"正在读取消息列表";await o.begin("正在打开消息列表");try{const s=await qn(a,r),c=await a.getPage("zhipin");if(await o.retarget(c),!s)return await o.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};await o.begin(i),await o.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-list-wrap",{label:i,padding:8});const l=e.autoScroll??!0,u=e.maxScrolls??4,d=n||void 0===e.limit?{}:{targetCount:e.limit},m=(l&&u>0?(await Qn(c,On("chat-list"),()=>$n(c),oa,{direction:"down",steps:u,...d})).items:await $n(c)).map(e=>({name:e.name,conversationId:e.conversationId,candidateId:e.candidateId,position:e.position,time:e.lastMessageTime,preview:e.messagePreview,unreadCount:e.unreadCount,hasUnread:e.hasUnread,index:e.index}));let g=n?m.filter(e=>e.hasUnread):m;const f=e.sortBy??"time";"time"===f||("unreadCount"===f?g.sort((e,t)=>t.unreadCount-e.unreadCount):"name"===f&&g.sort((e,t)=>e.name.localeCompare(t.name))),void 0!==e.limit&&(g=g.slice(0,e.limit));const p={withName:m.filter(e=>e.name.length>0).length,withUnread:m.filter(e=>e.hasUnread).length};return await o.succeed(n?`已读取 ${g.length} 条未读消息`:`已读取 ${g.length} 条消息`),t.logger.info(`Found ${g.length} candidates (${p.withUnread} with unread)`),{success:!0,candidates:g,total:m.length,stats:p}}catch(e){throw await o.fail("读取消息列表失败"),e}}});import{defineTool as sa}from"@roll-agent/sdk";import{BrowserPageInfoSchema as ca}from"@roll-agent/browser";import{z as la}from"zod";var ua={login:{notLoggedIn:".header-login-btn"},unread:{container:".chat-list-wrap",listItem:'[role="listitem"]',geekItemWrap:".geek-item-wrap",item:".chat-item",unreadBadge:".badge-count",unreadBadgeNew:".badge-count.badge-count-common-less",unreadBadgeSpan:".badge-count span",unreadDot:".red-dot",figure:".figure",badge:".badge",candidateName:".candidate-name",candidateNameAlt:".chat-item-name",candidateNameSelectors:'[class*="name"], .nickname, .geek-name',candidateNameNew:".geek-name",jobTitle:".source-job",lastMessage:".push-text",lastMessageAlt:".chat-last-msg",messageTime:".time, .time-shadow",clickArea:".chat-item-content",unreadCandidates:".geek-item"},chat:{chatContainer:".chat-container",messageList:".message-list",messageItem:".message-item",messageContent:".message-content",messageText:".text-content",userMessage:".message-right",candidateMessage:".message-left",messageTime:".message-time",senderName:".sender-name",inputBox:".chat-input",inputTextarea:"textarea.chat-input",inputEditorId:"#boss-chat-editor-input",sendButton:".btn-send",conversationEditor:".conversation-editor",submitButton:".submit-content .submit",submitButtonActive:".submit-content .submit.active",submitContent:".submit-content",sendButtonAlt:".conversation-editor .submit-content",sendIcon:".submit-content .icon-send",systemMessage:".system-msg"},chatDetails:{candidateInfoContainer:".base-info-single-container, .base-info-content",candidateName:".name-box, .geek-name, .base-name",candidateInfoItem:".geek-info-item, .base-info-item, .base-info-single-detial > div",candidateTag:".geek-tag, .high-light-boss",communicationPosition:".position-name",communicationPositionAlt:".position-item:not(.expect) .value.high-light-boss",candidateExpectContainer:".position-item.expect",candidateExpectValue:".position-item.expect .value.job",candidateExpectSalary:".position-item.expect .high-light-orange",candidatePosition:".geek-position, .position-name",candidatePositionAlt:".position-content .value, .position-item .value",chatMessageContainer:".conversation-message, .message-list",messageItem:".message-item",messageTime:".message-time .time",messageTextSpan:".text span",systemMessage:".item-system",friendMessage:".item-friend",myMessage:".item-myself",resumeMessage:".item-resume",readStatus:".status-read"},exchangeWechat:{exchangeButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > span.operate-btn",exchangeButtonFallback:".operate-exchange-left .operate-btn",confirmDialog:".exchange-tooltip",confirmButton:".exchange-tooltip .btn-box .boss-btn-primary.boss-btn",confirmButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > div > div > span.boss-btn-primary.boss-btn",cancelButton:".exchange-tooltip .btn-box .boss-btn-outline.boss-btn",wechatCard:".message-card-top-wrap",wechatCardAlt:'[class*="d-top-text"]'},username:{primary:".nav-item.nav-logout .user-name",fallbacks:["#header > div > div > div.nav-item.nav-logout > div.top-profile-logout.ui-dropmenu.ui-dropmenu-drop-arrow > div.ui-dropmenu-label > div > span.user-name",".ui-dropmenu-label .user-name",".nav-logout .user-name","#header .user-name",".top-profile .user-name",".nav-user .user-name",".user-name",'[class*="user-name"]','[class*="username"]','[data-qa="user-name"]',".header-user-name","#header .label-name"]},nav:{sidebar:".side-wrap.side-wrap-v2",chatLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/index"]',recommendLink:'.side-wrap.side-wrap-v2 a[href*="/web/geek/recommend"]'},recommend:{iframe:"#recommendFrame",resumeIframe:'iframe[src*="c-resume"]',filterButton:".recommend-filter .filter-label, .filter-label-wrap .filter-label, .filter-label",filterPanel:".filter-panel",candidateItem:"[data-geek], .geek-item",candidateName:".name",candidateBaseInfo:".base-info",workExps:".timeline-wrap.work-exps",expectInfo:".row-flex, .timeline-wrap.expect",salaryWrap:".salary-wrap",tagsWrap:".tags-wrap",greetButton:".btn-greet, .op-btn",resumeCanvas:"div#resume > canvas#resume, canvas#resume",closeResumeBtn:".close-btn, .dialog-close",closeResumeBtnV2:".recommendV2 .close-btn"},candidateProfile:{panel:".candidate-info, .resume-info, .geek-info",name:".candidate-info .name, .geek-info .name",age:".candidate-info .age, .geek-info .age",gender:".candidate-info .gender, .geek-info .gender",experience:".candidate-info .experience, .geek-info .work-exp",education:".candidate-info .education, .geek-info .edu",expectedSalary:".candidate-info .salary, .geek-info .expect-salary",expectedPosition:".candidate-info .position, .geek-info .expect-position",activeTime:".candidate-info .active-time, .geek-info .active"}},da={chat:"沟通",recommend:"推荐牛人"};function ma(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function ga(e){for(const t of e)try{if(await t.count()>0&&await t.first().isVisible())return t.first()}catch{}return null}function fa(e){return da[e]}async function pa(e,t){const n=e.locator(ua.nav.sidebar).first(),a=fa(t),r=new RegExp(`^${ma(a)}(?:\\s|$)`),o="recommend"===t?[ua.nav.recommendLink]:[ua.nav.chatLink],i=[n.getByRole("link",{name:r}).first(),...o.map(e=>n.locator(e).first()),n.locator(`a:has-text("${a}")`).first(),e.getByRole("link",{name:r}).first()];return await ga(i)}function ha(e){return!!e.url().includes("/web/geek/recommend")||(null!==e.frame("recommendFrame")||e.frames().some(e=>e.url().includes("recommend")))}async function wa(e){if(e.url().includes("/web/chat/index"))return!0;try{const t=e.locator("#container.chat-container-private").first();return await t.count()>0&&await t.isVisible()}catch{return!1}}async function ya(e,t=1e4,n=250){const a=Date.now()+t;for(;Date.now()<a;){if(ha(e))return!0;if(e.isClosed())return!1;await e.waitForTimeout(Math.min(n,Math.max(a-Date.now(),0)))}return ha(e)}async function ba(e,t=1e4,n=250){const a=Date.now()+t;for(;Date.now()<a;){if(await wa(e))return!0;if(e.isClosed())return!1;await e.waitForTimeout(Math.min(n,Math.max(a-Date.now(),0)))}return await wa(e)}var va,xa=la.object({success:la.boolean(),alreadyOnChat:la.boolean(),usedSidebarClick:la.boolean(),chatReady:la.boolean(),page:ca.optional(),error:la.string().optional()});function Sa(){return{getContextManager:h,findZhipinSidebarSectionLink:pa,isZhipinChatSurfaceOpen:wa,waitForZhipinChatSurface:ba,moveVisualCursorToLocator:K,showVisualClickOnLocator:Z,randomDelay:ln,toAttachedPageInfo:Ce,createVisualActivitySession:e=>new na(e),...va}}async function Ca(e,t,n){return await t.toAttachedPageInfo(e,n)}async function ka(e,t,n,a,r,o){return await n.fail(r),{success:!1,...o,page:await Ca(e,t,a),error:r}}async function Aa(e,t,n,a){await n.scrollIntoViewIfNeeded(),await t.moveVisualCursorToLocator(e,n,{durationMs:110,settleMs:30}),await n.hover(),await t.randomDelay(e,100,180),await t.showVisualClickOnLocator(e,n,{pulseDurationMs:180}),await n.click(),a.info("Clicked Boss sidebar nav: 沟通")}var Ia=sa({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测。",input:la.object({}),output:xa,execute:async(e,t)=>{const n=Sa(),a=n.getContextManager();t.logger.info("Opening Boss chat page via sidebar navigation");const r=await a.getPage("zhipin");await r.bringToFront().catch(()=>{});const o=n.createVisualActivitySession(r),i="正在切换到沟通页";if(await o.begin(i),await o.highlightSelector(ua.nav.sidebar,{label:i,padding:10}),await n.isZhipinChatSurfaceOpen(r))return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,chatReady:!0,page:await Ca(a,n,r)};const s=await n.findZhipinSidebarSectionLink(r,"chat");if(!s)return await ka(a,n,o,r,"未找到沟通导航",{alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1});try{await Aa(r,n,s,t.logger)}catch(e){return await ka(a,n,o,r,e instanceof Error?e.message:"点击沟通导航失败",{alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1})}return await n.waitForZhipinChatSurface(r)?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!0,page:await Ca(a,n,r)}):await ka(a,n,o,r,"沟通页未就绪",{alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1})}});import{defineTool as Ra}from"@roll-agent/sdk";import{z as Ma}from"zod";var Pa=Ma.object({success:Ma.boolean(),conversationId:Ma.string(),candidateId:Ma.string(),candidateName:Ma.string(),index:Ma.number(),hasUnread:Ma.boolean(),unreadCount:Ma.number(),lastMessageTime:Ma.string(),messagePreview:Ma.string(),error:Ma.string().optional()}),Ba=Ra({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Ma.object({conversationId:Ma.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Ma.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Ma.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Ma.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Pa,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=h(),a=await n.getPage("zhipin");let r={conversationId:e.conversationId,candidateName:e.candidateName,index:e.index};if(e.preferUnread&&void 0===e.conversationId&&void 0===e.candidateName&&void 0===e.index){if(!await qn(n,a))return{success:!1,conversationId:"",candidateId:"",candidateName:"",index:-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const e=await n.getPage("zhipin"),t=(await $n(e)).find(e=>e.hasUnread);t&&(r={conversationId:t.conversationId,candidateName:t.name,index:t.index})}const o=await zn(n,a,r);return o&&o.found?(t.logger.info(`Opened chat with ${o.name} (index: ${o.index})`),{success:!0,conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.name,index:o.index,hasUnread:o.hasUnread,unreadCount:o.unreadCount,lastMessageTime:o.lastMessageTime,messagePreview:o.messagePreview}):{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:o?.error??`未找到候选人: ${e.candidateName??`index ${e.index}`}`}}});import{defineTool as Ea}from"@roll-agent/sdk";import{z as _a}from"zod";var Ta="·",qa=/[--—–]/;function $a(e){return(e??"").trim()}function Na(e){const t=$a(e);if(!t||!qa.test(t))return;const[n=""]=t.split(qa);return $a(n)||void 0}function La(e){const t=$a(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",a=""]=t.split(Ta).map(e=>$a(e));return{expectedLocation:n,expectedPosition:a}}function za(e){const t=$a(e.communicationPosition),{expectedLocation:n,expectedPosition:a}=La(e.expectedJobText),r=Na(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:a,...void 0!==r?{preferredBrand:r}:{}}}var Fa=_a.object({index:_a.number(),sender:_a.enum(["candidate","recruiter","system"]),messageType:_a.enum(["text","system","resume","wechat-exchange"]),content:_a.string(),time:_a.string()}),ja=_a.object({name:_a.string(),age:_a.string(),experience:_a.string(),education:_a.string(),communicationPosition:_a.string(),expectedPosition:_a.string(),expectedLocation:_a.string(),expectedSalary:_a.string(),tags:_a.array(_a.string())}),Oa=_a.object({success:_a.boolean(),conversationId:_a.string(),candidateId:_a.string(),candidateInfo:ja,preferredBrand:_a.string().optional(),chatMessages:_a.array(Fa),formattedHistory:_a.array(_a.string()),stats:_a.object({totalMessages:_a.number(),candidateMessages:_a.number(),recruiterMessages:_a.number(),systemMessages:_a.number()}),error:_a.string().optional()});function Da(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function Ua(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:Da(),chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function Va(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}var Ha=Ea({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:_a.object({conversationId:_a.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:_a.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:_a.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:_a.number().default(100).describe("最多返回的消息条数")}),output:Oa,execute:async(e,t)=>{const n=e.maxMessages??100,a=h(),r=await a.getPage("zhipin"),o=new na(r),i=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index?"正在打开目标聊天":"正在准备当前聊天",s="正在提取聊天记录",c=async(e,t)=>(await o.fail(e),Ua(t));await o.begin(i);try{const i=await zn(a,r,{conversationId:e.conversationId,candidateName:e.candidateName,index:e.index}),l=await a.getPage("zhipin");if(await o.retarget(l),i&&!i.found)return await c("打开聊天失败",i.error??"打开聊天失败");t.logger.info("Extracting candidate info"+(i?` for ${i.name}`:" (current window)")),await o.begin(s),await o.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:s,padding:12});const u=i?.name??e.candidateName??"",d=await pn(l);if(u.length>0&&(!d||!Va(u,d.candidateName)))return await c("聊天面板未同步",`右侧聊天面板未切换到 ${u}`);const m=await fn(l);if(!m)return await c("未识别当前会话","未能提取当前选中聊天的 conversationId/candidateId");if(i&&m.conversationId!==i.conversationId)return await c("当前会话不一致",`当前选中会话与目标会话不一致: ${i.name||i.conversationId}`);if(d&&m.candidateName.length>0&&!Va(m.candidateName,d.candidateName))return await c("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${d.candidateName}`);try{await l.waitForSelector(".chat-message-list .message-item, .conversation-message .message-item",{timeout:8e3})}catch{}const g=await l.evaluate(e=>{const t=document.querySelector(".chat-conversation")??document.querySelector(".conversation-box")??document,n=t.querySelector(".base-info-single-detial, .base-info-content, .base-info-single-container"),a=n?.querySelector(".name-box, .base-name, .chat-user-name, .geek-name")?.textContent?.trim()??"",r=n?n.querySelectorAll(":scope > div"):t.querySelectorAll(".geek-info-item, .base-info-item"),o=[];r.forEach(e=>{const t=e.textContent?.trim();t&&o.push(t)});const i=o.join(" "),s=i.match(/(\d{2,3})岁/),c=s?s[1]+"岁":"",l=i.match(/(\d+年(?:以上)?|应届生|在校生)/),u=l?.[1]??"",d=i.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1]??"";let m="";const g=t.querySelector(".position-name");if(g){const e=g.cloneNode(!0);e.querySelectorAll(".popover-wrap, .tooltip-job").forEach(e=>e.remove()),m=e.textContent?.trim()??""}let f="";const p=t.querySelector(".position-item.expect .value.job");p&&(f=p.textContent?.trim()??"");const h=t.querySelector(".position-item.expect .high-light-orange")?.textContent?.trim()??"",w=[];n&&n.querySelectorAll(".geek-tag, .base-info-item .high-light-boss").forEach(e=>{const t=e.textContent?.trim();t&&!t.includes("更换职位")&&t.length<20&&w.push(t)});const y=t.querySelectorAll(".chat-message-list > .message-item, .conversation-message .message-item"),b=/\d{1,2}:\d{2}(?::\d{2})?|\d{4}-\d{2}-\d{2}/,v=[];let x=0;return y.forEach(t=>{if(x>=e)return;const n=null!==t.querySelector(".item-friend"),a=null!==t.querySelector(".item-myself"),r=null!==t.querySelector(".item-system"),o=null!==t.querySelector(".item-resume"),i=null!==t.querySelector(".message-dialog-center");let s="system",c="text";n?s="candidate":a?s="recruiter":(r||i)&&(s="system",c="system"),o&&(c="resume");const l=t.querySelector(".message-card-top-wrap, [class*='d-top-text']");if(l){const e=l.textContent??"";(e.includes("微信")||e.includes("WeChat"))&&(c="wechat-exchange")}const u=t.querySelector(".message-time .time, .message-time"),d=(u?.textContent??"").match(b),m=d?d[0]:"";let g="";if("wechat-exchange"===c&&l){const e=l.textContent??"",t=e.match(/\b(\d{8,15})\b/),n=e.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);g=t?`[微信号: ${t[1]}]`:n?`[微信号: ${n[1]}]`:"[交换微信]"}else if(l){const e=t.querySelector(".message-card-top-title"),n=t.querySelector(".dialog-content, .message-card-top-text");g=(e?.textContent?.trim()??n?.textContent?.trim()??"").trim()}else{const e=t.querySelector(".text span, .text-content, .text");e&&(g=(e.textContent?.trim()??"").replace(b,"").replace("已读","").trim())}(g||"text"!==c)&&(v.push({index:x,sender:s,messageType:c,content:g,time:m}),x++)}),{candidateInfo:{name:a,age:c,experience:u,education:d,communicationPosition:m,expectedJobText:f,expectedSalary:h,tags:w},messages:v}},n),f=za({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),p=g.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),h={totalMessages:g.messages.length,candidateMessages:g.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:g.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:g.messages.filter(e=>"system"===e.sender).length};return await o.succeed(`已提取 ${h.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${h.totalMessages} messages`),{success:!0,conversationId:m.conversationId,candidateId:m.candidateId,candidateInfo:{name:g.candidateInfo.name,age:g.candidateInfo.age,experience:g.candidateInfo.experience,education:g.candidateInfo.education,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,expectedSalary:g.candidateInfo.expectedSalary,tags:g.candidateInfo.tags},...void 0!==f.preferredBrand?{preferredBrand:f.preferredBrand}:{},chatMessages:g.messages,formattedHistory:p,stats:h}}catch(e){throw await o.fail("提取聊天记录失败"),e}}});import{defineTool as Wa}from"@roll-agent/sdk";import{z as Ka}from"zod";var Za=30,Ga=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Ya(e){const t=e.trim();return!(0===t.length||t.length>Za)&&(!Ga.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Ja(e){const t=[],n=/(link|button|menuitem|img|heading)\s+"([^"]+)"/g;let a;for(;null!==(a=n.exec(e));){const e=a[2]?.trim()??"";e.length>0&&t.push({role:a[1]??"",name:e})}return t}function Xa(e){let t=e.priority;return Ga.has(e.text.trim())&&(t+=10),e.text.trim().length>10&&(t+=5),/^[\u4e00-\u9fff]{2,4}$/.test(e.text.trim())&&(t-=.5),void 0!==e.xRatio&&(t-=4*(e.xRatio-.5)),t}function Qa(e){const t=e.filter(e=>Ya(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),a=n.get(t)??new Set;a.add(e.strategy),n.set(t,a)}const a=t.map(e=>{let t=Xa(e);const a=n.get(e.text.trim())?.size??1;return a>1&&(t-=.5*a),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return a?{found:!0,username:a.evidence.text.trim(),strategy:a.evidence.strategy,source:a.evidence.source}:{found:!1}}async function er(e){const t=[e.getByRole("banner"),e.locator("header").first(),e.getByRole("navigation").first(),e.locator("#header")];for(const e of t)try{if(await e.count()>0&&await e.first().isVisible())return e.first()}catch{}return null}async function tr(e,t,n,a){const r=[],o=t.viewportSize(),i=o?.width??1280;try{const t=await e.getByRole(n).all();for(const e of t)try{if(!await e.isVisible())continue;const t=(await e.textContent()??"").trim();if(t.length>0&&t.length<=Za){let o;try{const t=await e.boundingBox();t&&(o=(t.x+t.width/2)/i)}catch{}r.push({text:t,strategy:a,priority:1,source:`role:${n}`,xRatio:o})}}catch{}}catch{}return r}async function nr(e){const t=[];try{const n=Ja(await e.ariaSnapshot({timeout:3e3}));for(const{role:e,name:a}of n)a.length>0&&a.length<=Za&&t.push({text:a,strategy:"aria-snapshot",priority:2,source:`aria:${e}:${a}`})}catch{}return t}async function ar(e){const t=[];try{const n=await e.evaluate((e,t)=>{const n=[],a=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;a.nextNode();){const e=a.currentNode.textContent?.trim();e&&e.length>0&&e.length<=t&&n.push(e)}return n},Za);for(const e of n)t.push({text:e,strategy:"leaf-text",priority:3,source:"leaf-text"})}catch{}return t}async function rr(e){const t=[];try{const n=[ua.username.primary,...ua.username.fallbacks],a=await e.evaluate(e=>e.map(e=>{try{return{selector:e,text:document.querySelector(e)?.textContent?.trim()??""}}catch{return{selector:e,text:""}}}),n);for(const{selector:e,text:n}of a)n.length>0&&n.length<=Za&&t.push({text:n,strategy:"css-fallback",priority:4,source:e})}catch{}return t}async function or(e){const t=await er(e),n=t?(await Promise.all([tr(t,e,"link","role-link"),tr(t,e,"button","role-button"),nr(t)])).flat():[],a=Qa(n);if(a.found){const e=a.username;if(new Set(n.filter(t=>t.text.trim()===e).map(e=>e.strategy)).size>=2)return n}const r=(await Promise.all([t?ar(t):Promise.resolve([]),rr(e)])).flat();return[...n,...r]}async function ir(e,t=or){const n=Qa(await t(e));if(!n.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return{platform:"zhipin",username:n.username,strategy:n.strategy,source:n.source}}function sr(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as cr}from"zod";var lr="reply-authority-service",ur="browser-use-agent/zhipin_send_reply",dr="zhipin",mr=60,gr=cr.object({platform:cr.literal(dr),username:cr.string().min(1),accountId:cr.string().min(1).optional()}),fr=cr.object({v:cr.literal(2),iss:cr.literal(lr),kid:cr.string().min(1),jti:cr.string().min(1),iat:cr.number().int(),exp:cr.number().int(),aud:cr.literal(ur),platform:cr.literal(dr),tenantId:cr.string().min(1),conversationId:cr.string().min(1),candidateId:cr.string().min(1),reply:cr.string(),policyVersion:cr.string().min(1),recruiterBinding:gr}),pr=cr.object({kid:cr.string().min(1),algorithm:cr.literal("Ed25519"),publicKey:cr.string().min(1),validFrom:cr.string().min(1),validUntil:cr.string().optional()}),hr=cr.object({keys:cr.array(pr)}),wr=new Map;function yr(e){for(const[t,n]of wr)n<e-mr&&wr.delete(t)}function br(e,t=Math.floor(Date.now()/1e3)){return yr(t),wr.has(e)}function vr(e,t,n=Math.floor(Date.now()/1e3)){yr(n),wr.set(e,t)}import{createPublicKey as xr,verify as Sr}from"node:crypto";var Cr=new Map,kr=null;function Ar(){const e=process.env.REPLY_AUTHORITY_KEYS_URL?.trim();if(!e)throw new Error("REPLY_AUTHORITY_KEYS_URL 未配置,browser-use-agent 无法拉取 Reply Authority 公钥。");return e}function Ir(e){return Cr=new Map(e.map(e=>[e.kid,e]))}async function Rr(){if(kr)return kr;kr=(async()=>{const e=await fetch(Ar()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Ir(hr.parse(t).keys)})();try{return await kr}finally{kr=null}}async function Mr(){await Rr()}async function Pr(e){const t=Cr.get(e);if(t)return t;const n=(await Rr()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function Br(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Er(e){const t=e.split("."),n=t[0],a=t[1];if(2!==t.length||void 0===n||void 0===a||0===n.length||0===a.length)throw new Error("Invalid signed envelope format");let r,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{r=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}Br(r);const i=fr.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:a}}function _r(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-mr)throw new Error("Envelope expired");if(e.iat>t+mr)throw new Error("Envelope issued in the future")}async function Tr(e,t=Math.floor(Date.now()/1e3)){const n=Er(e),a=await Pr(n.payload.kid),r=xr({key:Buffer.from(a.publicKey,"base64url"),format:"der",type:"spki"});if(!Sr(null,Buffer.from(n.payloadJson,"utf-8"),r,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return _r(n.payload,t),n.payload}var qr=Ka.object({success:Ka.boolean(),sentMessage:Ka.string(),error:Ka.string().optional()});function $r(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}var Nr=Wa({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Ka.object({signedEnvelope:Ka.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Ka.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Ka.number().optional().describe("候选人在列表中的索引(可选)")}),output:qr,execute:async(e,t)=>{let n="";if(!b()){const e="Reply Authority 公钥尚未成功预加载,当前无法发送签名回复。请检查启动日志、`REPLY_AUTHORITY_KEYS_URL` 配置,以及 `browser_status.replyAuthorityKeysLoaded`。";return t.logger.error(e),{success:!1,sentMessage:n,error:e}}const a=h(),r=await a.getPage("zhipin");let o=r;try{const i=await Tr(e.signedEnvelope);if(n=i.reply,br(i.jti))return{success:!1,sentMessage:n,error:"token 已消费,禁止重放"};const s=await zn(a,r,{conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(s&&!s.found)return{success:!1,sentMessage:n,error:s.error};if(!s){if(!await qn(a,r))return{success:!1,sentMessage:n,error:"消息列表未加载"}}o=await a.getPage("zhipin");const c=await pn(o),l=await fn(o);if(!l)return{success:!1,sentMessage:n,error:"未能提取当前聊天的 conversationId/candidateId"};if(c&&l.candidateName.length>0&&!$r(l.candidateName,c.candidateName))return{success:!1,sentMessage:n,error:`左侧选中会话与右侧聊天面板不一致: ${l.candidateName} / ${c.candidateName}`};if(l.conversationId!==i.conversationId||l.candidateId!==i.candidateId)return{success:!1,sentMessage:n,error:"发送目标与签名不匹配"};const u=await ir(o);if(!sr(u,i.recruiterBinding))return{success:!1,sentMessage:n,error:`recruiter 绑定不匹配:当前账号 ${u.username} 与签发时 ${i.recruiterBinding.username} 不一致`};t.logger.info(`Sending message (${n.length} chars) to ${l.candidateName||l.candidateId}`);const d="#boss-chat-editor-input, textarea.chat-input, .chat-input";await o.waitForSelector(d,{timeout:5e3});const m=o.locator(d).first();await K(o,m);await o.evaluate(e=>{const t=document.querySelector(e);return"true"===t?.getAttribute("contenteditable")},d)?(await m.focus(),await o.evaluate(e=>{const t=document.querySelector(e.sel);t&&(t.innerHTML=e.msg.split("\n").map(e=>`<p>${e}</p>`).join(""))},{sel:d,msg:n}),await m.dispatchEvent("input",{bubbles:!0})):await o.fill(d,n),await ln(o,200,500);const g=await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")});const e=[".submit-content .submit.active",".submit-content .submit",".submit-content",".btn-send"];for(const t of e){const e=document.querySelector(t);if(e&&e.offsetWidth>0)return{found:!0,selector:t}}const t=Array.from(document.querySelectorAll("span"));for(const e of t)if("发送"===e.textContent?.trim()&&e.offsetWidth>0)return e.setAttribute("data-roll-send-btn","true"),{found:!0,selector:'[data-roll-send-btn="true"]'};return{found:!1}});if(!g.found)return{success:!1,sentMessage:n,error:"未找到发送按钮"};const f=o.locator(g.selector).first();return await f.scrollIntoViewIfNeeded(),await K(o,f),await f.hover(),await un(o),await Z(o,f),await f.click(),await ln(o,500,1200),vr(i.jti,i.exp),t.logger.info("Message sent successfully"),{success:!0,sentMessage:n}}catch(e){return{success:!1,sentMessage:n,error:e instanceof Error?e.message:String(e)}}finally{await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")})}).catch(()=>{})}}});import{defineTool as Lr}from"@roll-agent/sdk";import{z as zr}from"zod";var Fr=zr.object({success:zr.boolean(),exchanged:zr.boolean(),wechatNumber:zr.string().optional(),error:zr.string().optional()}),jr="data-roll-wechat-btn",Or="data-roll-confirm-btn";function Dr(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}var Ur=Lr({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:zr.object({conversationId:zr.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:zr.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:zr.number().optional().describe("候选人在列表中的索引(可选)")}),output:Fr,execute:async(e,t)=>{const n=h(),a=await n.getPage("zhipin"),r=await zn(n,a,{conversationId:e.conversationId,candidateName:e.candidateName,index:e.index});if(r&&!r.found)return{success:!1,exchanged:!1,error:r.error};t.logger.info("Starting WeChat exchange"+(r?` with ${r.name}`:""));const o=await n.getPage("zhipin");try{const e=await fn(o);if(!e)return{success:!1,exchanged:!1,error:"未选中聊天联系人,无法点击当前聊天输入区的「换微信」按钮"};const n=await pn(o);if(n&&e.candidateName.length>0&&!Dr(e.candidateName,n.candidateName))return{success:!1,exchanged:!1,error:`左侧选中会话与右侧聊天面板不一致: ${e.candidateName} / ${n.candidateName}`};if(!(await o.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},n=e=>e.replace(/\s+/g,"").trim();document.querySelectorAll(`[${e}]`).forEach(t=>{t.removeAttribute(e)});const a=[".chat-conversation .conversation-operate .operate-exchange-left span.operate-btn",".chat-conversation .conversation-operate span.operate-btn",".conversation-box .conversation-operate .operate-exchange-left span.operate-btn",".conversation-box .conversation-operate span.operate-btn",".conversation-operate .operate-exchange-left span.operate-btn",".conversation-operate .operate-exchange-left span"];for(const r of a){const a=Array.from(document.querySelectorAll(r));for(const r of a){const a=n(r.textContent??"");if("换微信"===a&&t(r))return r.setAttribute(e,"true"),{found:!0,text:a}}}return{found:!1}},jr)).found)return{success:!1,exchanged:!1,error:"未找到当前聊天输入区的「换微信」按钮"};await ln(o,200,400),await G(o,`[${jr}="true"]`),await Y(o,`[${jr}="true"]`),await o.click(`[${jr}="true"]`),await o.evaluate(()=>{document.querySelector("[data-roll-wechat-btn]")?.removeAttribute("data-roll-wechat-btn")}),await ln(o,400,800);let a=!1;for(let e=0;e<8;e++){if(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=document.querySelector(".exchange-tooltip");if(t&&e(t))return!0;const n=document.querySelectorAll("div, section, aside");for(const t of Array.from(n)){if((t.textContent??"").includes("交换微信")&&t.querySelector(".boss-btn-primary, .boss-btn")&&e(t))return!0}return!1})){a=!0;break}await ln(o,400,800)}if(!a)return{success:!1,exchanged:!1,error:"确认对话框未弹出"};await un(o);if(!(await o.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0};document.querySelectorAll(`[${e}]`).forEach(t=>{t.removeAttribute(e)});const n=document.querySelector(".exchange-tooltip");if(n){const a=[".btn-box .boss-btn-primary.boss-btn",".btn-box span.boss-btn-primary","span.boss-btn-primary",".boss-btn-primary"];for(const r of a){const a=n.querySelector(r);if(a&&t(a))return a.setAttribute(e,"true"),{found:!0,text:a.textContent?.trim()??""}}}const a=document.querySelectorAll("div, section, aside");for(const n of Array.from(a)){if(!(n.textContent??"").includes("交换微信"))continue;const a=n.querySelectorAll("span.boss-btn-primary, button.boss-btn-primary, span.boss-btn, button.boss-btn");for(const n of Array.from(a)){const a=n.textContent?.trim()??"";if("确定"===a&&t(n))return n.setAttribute(e,"true"),{found:!0,text:a}}}return{found:!1}},Or)).found)return{success:!1,exchanged:!1,error:"未找到确认按钮"};await ln(o,200,400),await G(o,`[${Or}="true"]`),await Y(o,`[${Or}="true"]`),await o.click(`[${Or}="true"]`),await o.evaluate(()=>{document.querySelector("[data-roll-confirm-btn]")?.removeAttribute("data-roll-confirm-btn")}),await ln(o,1500,2500);const r=await o.evaluate(()=>{const e=[".message-card-top-wrap",'[class*="d-top-text"]',".message-card-top-title"];for(const t of e){const e=Array.from(document.querySelectorAll(t));for(let t=e.length-1;t>=0;t--){const n=e[t]?.textContent??"",a=n.match(/\b(\d{8,15})\b/);if(a)return a[1];const r=n.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);if(r)return r[1];const o=n.match(/\b([a-zA-Z][a-zA-Z0-9_-]{5,19})\b/);if(o&&!["微信","WeChat"].includes(o[1]))return o[1]}}const t=Array.from(document.querySelectorAll(".message-item"));for(let e=t.length-1;e>=0;e--){const n=t[e]?.querySelector('.message-card-top-wrap, [class*="d-top-text"]');if(n){const e=(n.textContent??"").match(/\b(\d{8,15})\b/);if(e)return e[1]}}return null});return t.logger.info("WeChat exchanged"+(r?`, number: ${r}`:"")),{success:!0,exchanged:!0,...null!==r?{wechatNumber:r}:{}}}catch(e){return{success:!1,exchanged:!1,error:e instanceof Error?e.message:String(e)}}finally{await o.evaluate(e=>{for(const t of e)document.querySelectorAll(`[${t}]`).forEach(e=>{e.removeAttribute(t)})},[jr,Or]).catch(()=>{})}}});import{defineTool as Vr}from"@roll-agent/sdk";import{z as Hr}from"zod";var Wr,Kr=Hr.object({success:Hr.boolean(),username:Hr.string(),usedSelector:Hr.string().optional(),usedStrategy:Hr.string().optional(),source:Hr.string().optional(),error:Hr.string().optional()});function Zr(){return{getContextManager:h,findHeaderScope:er,getCurrentZhipinRecruiterIdentity:ir,createVisualActivitySession:e=>new na(e),...Wr}}var Gr=Vr({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Hr.object({}),output:Kr,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Zr();let a;try{const e=n.getContextManager(),r=await e.getPage("zhipin");a=n.createVisualActivitySession(r),await r.bringToFront().catch(()=>{}),await a.begin("正在识别登录账号");const o=await n.findHeaderScope(r);o&&await a.highlightLocator(o,{label:"正在识别登录账号",padding:10});const i=await n.getCurrentZhipinRecruiterIdentity(r);return await a.succeed(`已识别账号:${i.username}`),t.logger.info(`Username: ${i.username} (strategy: ${i.strategy}, source: ${i.source})`),{success:!0,username:i.username,usedSelector:"css-fallback"===i.strategy?i.source:void 0,usedStrategy:i.strategy,source:i.source}}catch(e){return await(a?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}}});import{defineTool as Yr}from"@roll-agent/sdk";import{z as Jr}from"zod";var Xr=".candidate-card-wrap",Qr="[data-geek], .geek-item",eo=`${Xr}, ${Qr}`;function to(e){return e.evaluate(e=>document.querySelectorAll(e.primarySelector).length>0?e.primarySelector:e.fallbackSelector,{primarySelector:Xr,fallbackSelector:Qr})}function no(e){return e.frame("recommendFrame")??e.frames().find(e=>e.url().includes("recommend"))??e}async function ao(e,t=1e4){try{return await e.waitForSelector(eo,{timeout:t}),!0}catch{return!1}}async function ro(e,t){const n=await to(e),a=e.locator(n);if(await a.count()<=t)return{found:!1,cardSelector:n,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const r=a.nth(t),o=await r.evaluate(e=>{const t=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",n=e.querySelector(".name")?.textContent?.trim()??"",a=e.querySelector("button.btn.btn-greet");return{candidateId:t,name:n,hasGreetButton:null!==a&&a.offsetWidth>0}});return{found:!0,cardSelector:n,candidateId:o.candidateId,name:o.name,hasGreetButton:o.hasGreetButton}}var oo=Jr.object({containerFound:Jr.boolean(),containerLabel:Jr.string(),scrollTop:Jr.number(),scrollHeight:Jr.number(),clientHeight:Jr.number(),itemCount:Jr.number(),atStart:Jr.boolean(),atEnd:Jr.boolean()}),io=Jr.object({success:Jr.boolean(),surface:Jr.enum(Fn),direction:Jr.enum(["up","down"]),stepsRequested:Jr.number(),stepsCompleted:Jr.number(),reachedBoundary:Jr.boolean(),before:oo,after:oo,error:Jr.string().optional()});async function so(e){const t=h(),n=await t.getPage("zhipin");if("chat-list"===e){const e=await qn(t,n),a=await t.getPage("zhipin");return{target:a,session:new na(a),ready:e}}if("recommend-list"===e){const e=no(n),t=await ao(e);return{target:e,session:new na(e),ready:t}}try{const t=On(e);return await n.waitForSelector(t.itemSelector,{timeout:3e3}),{target:n,session:new na(n),ready:!0}}catch{return{target:n,session:new na(n),ready:!1}}}var co=Yr({name:"zhipin_scroll_view",description:"滚动 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list。",input:Jr.object({surface:Jr.enum(Fn).describe("要滚动的页面区域"),direction:Jr.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:Jr.number().int().min(1).max(20).default(1).describe("滚动步数"),distance:Jr.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:Jr.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:io,execute:async(e,t)=>{const n=On(e.surface),a=e.direction??n.defaultDirection,r=e.steps??1,o=e.settleMs??700,{target:i,session:s,ready:c}=await so(e.surface),l=`正在滚动 ${e.surface}`;if(await s.begin(l),await s.highlightSelector(n.highlightSelector,{label:l,padding:8}),!c){await s.fail("列表未加载");const t={containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0};return{success:!1,surface:e.surface,direction:a,stepsRequested:r,stepsCompleted:0,reachedBoundary:!0,before:t,after:t,error:"列表未加载"}}try{const c=await Xn(i,n,{direction:a,steps:r,settleMs:o,...void 0!==e.distance?{distance:e.distance}:{}});return await s.succeed(`已滚动 ${c.stepsCompleted}/${c.stepsRequested} 步`),t.logger.info(`Scrolled ${e.surface}: ${c.stepsCompleted}/${c.stepsRequested}, items ${c.before.itemCount} -> ${c.after.itemCount}`),{success:c.success,surface:e.surface,direction:c.direction,stepsRequested:c.stepsRequested,stepsCompleted:c.stepsCompleted,reachedBoundary:c.reachedBoundary,before:c.before,after:c.after}}catch(e){throw await s.fail("滚动失败"),e}}});import{defineTool as lo}from"@roll-agent/sdk";import{z as uo}from"zod";var mo=["不限","男","女"],go=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],fo=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","submit_failed","error"],po=16,ho=50,wo=650,yo=10,bo=.015,vo=["data-roll-recommend-filter-button","data-roll-recommend-filter-option","data-roll-recommend-filter-submit","data-roll-recommend-filter-age-track","data-roll-recommend-filter-age-min-handle","data-roll-recommend-filter-age-max-handle"],xo="active|selected|checked|current|choose|chosen",So="button, a, label, li, span, div, [role='button'], [role='radio']";async function Co(e,t,n,a){a&&(await a.moveToLocator(e,n,{durationMs:100,settleMs:25,target:t}),await a.showClickOnLocator(e,n,{pulseDurationMs:170,target:t}))}function ko(e,t,n={}){return{status:t,requested:e,...void 0!==n.applied?{applied:n.applied}:{},...void 0!==n.filterButtonText?{filterButtonText:n.filterButtonText}:{},...void 0!==n.error?{error:n.error}:{}}}async function Ao(e,t=1e4){try{return await e.waitForSelector(`${ua.recommend.filterButton}, .candidate-card-wrap, ${ua.recommend.candidateItem}`,{timeout:t}),!0}catch{return!1}}async function Io(e){try{const t=await e.locator(ua.recommend.filterButton).first().textContent({timeout:1e3});return t?.replace(/\s+/g," ").trim()}catch{return}}async function Ro(e){return await e.evaluate(()=>{const e=e=>(e??"").replace(/\s+/g," ").trim(),t=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},n=n=>{const a=Array.from(n.querySelectorAll("button, a, span, div, [role='button']")).filter(e=>t(e));for(const t of a){const n=e(t.textContent);if(/^(取消|不应用|否|关闭|稍后)$/.test(n))return t.click(),!0}return!1},a=Array.from(document.body.querySelectorAll("div, section, aside")).filter(e=>t(e)).sort((e,t)=>{const n=e.getBoundingClientRect(),a=t.getBoundingClientRect();return n.width*n.height-a.width*a.height});for(const t of a){const a=e(t.textContent);if(a.includes("是否应用上次")||a.includes("上次的筛选条件"))return n(t)}return!1})}async function Mo(e,t,n){return await e.evaluate(e=>{const t=e=>(e??"").replace(/\s+/g," ").trim(),n=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},a=e=>{const t=e.getBoundingClientRect();return t.width*t.height},r=(e,t)=>{let n=e;for(;n&&n!==t.parentElement;){const e=n.tagName.toLowerCase(),t=n.getAttribute("role")??"";if("button"===e||"a"===e||"label"===e||"li"===e||"button"===t||"radio"===t)return n;n=n.parentElement}return e};document.querySelectorAll(`[${e.markerAttribute}]`).forEach(t=>t.removeAttribute(e.markerAttribute));const o=Array.from(document.querySelectorAll(e.panelSelector)).filter(e=>n(e)).sort((e,t)=>a(e)-a(t))[0];if(!o)return!1;const i=Array.from(o.querySelectorAll("div, li, dl, dd, section, ul")).filter(a=>{const r=t(a.textContent);return n(a)&&r.includes(e.rowLabel)&&r.includes(e.optionLabel)}).sort((e,n)=>{const r=a(e)-a(n);return 0!==r?r:t(e.textContent).length-t(n.textContent).length});for(const o of i){const i=Array.from(o.querySelectorAll(e.clickableOptionSelector)).filter(e=>n(e)).filter(n=>t(n.textContent)===e.optionLabel).sort((e,t)=>a(e)-a(t))[0];if(i)return r(i,o).setAttribute(e.markerAttribute,"1"),!0}return!1},{panelSelector:ua.recommend.filterPanel,rowLabel:t,optionLabel:n,markerAttribute:"data-roll-recommend-filter-option",clickableOptionSelector:So})}async function Po(e,t,n,a,r){if(!await Mo(e,n,a))return!1;try{const n=e.locator('[data-roll-recommend-filter-option="1"]').first();return await Co(t,e,n,r),await n.click({timeout:2e3}),await e.waitForTimeout(120),!0}catch{return!1}}async function Bo(e){return await e.evaluate(e=>{const t=e=>(e??"").replace(/\s+/g," ").trim(),n=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},a=e=>{const t=e.getBoundingClientRect();return t.width*t.height},r=e=>"string"==typeof e.className?e.className:"",o=e=>{const t=r(e),n=e.getAttribute("role")??"";return/slider|range|track|bar/i.test(t)||"slider"===n},i=e=>{const t=e.getBoundingClientRect(),n=r(e);return"slider"===(e.getAttribute("role")??"")||/handle|handler|button|thumb|slider-btn|dot|point|circle|knob/i.test(n)&&t.width<=80&&t.height<=80};for(const t of e.markerAttributes)document.querySelectorAll(`[${t}]`).forEach(e=>e.removeAttribute(t));const s=Array.from(document.querySelectorAll(e.panelSelector)).filter(e=>n(e)).sort((e,t)=>a(e)-a(t))[0];if(!s)return{ok:!1,error:"未找到筛选面板"};const c=Array.from(s.querySelectorAll("div, li, section, dl, dd")).filter(e=>{const a=t(e.textContent);return n(e)&&a.includes("年龄")&&Array.from(e.querySelectorAll("*")).some(e=>o(e)||i(e))}).sort((e,n)=>{const r=a(e)-a(n);return 0!==r?r:t(e.textContent).length-t(n.textContent).length})[0];if(!c)return{ok:!1,error:"未找到年龄滑块"};const l=Array.from(c.querySelectorAll(".vue-slider-dot")).filter(e=>n(e)).sort((e,t)=>e.getBoundingClientRect().left-t.getBoundingClientRect().left),u=Array.from(c.querySelectorAll("*")).filter(e=>n(e)&&i(e)).sort((e,t)=>e.getBoundingClientRect().left-t.getBoundingClientRect().left),d=l.length>=2?l:u;if(d.length<2)return{ok:!1,error:"未找到年龄滑块双手柄"};const m=d[0],g=d[d.length-1];if(!m||!g)return{ok:!1,error:"未找到年龄滑块双手柄"};const f=m.getBoundingClientRect(),p=g.getBoundingClientRect(),h=Math.max(40,p.left-f.left),w=Array.from(c.querySelectorAll(".vue-slider-rail, .vue-slider")).filter(e=>{if(!n(e))return!1;const t=e.getBoundingClientRect();return t.width>=h&&t.height<=80}).sort((e,t)=>{const n=r(e),a=r(t);if(/vue-slider-rail/.test(n)&&!/vue-slider-rail/.test(a))return-1;if(!/vue-slider-rail/.test(n)&&/vue-slider-rail/.test(a))return 1;const o=e.getBoundingClientRect(),i=t.getBoundingClientRect(),s=o.height-i.height;return 0!==s?s:i.width-o.width}),y=Array.from(c.querySelectorAll("*")).filter(e=>{if(!n(e)||!o(e))return!1;const t=e.getBoundingClientRect();return t.width>=Math.max(80,h)&&t.height<=80&&t.left<=f.left+f.width&&t.right>=p.right-p.width}).sort((e,t)=>{const n=e.getBoundingClientRect(),a=t.getBoundingClientRect(),r=n.height-a.height;return 0!==r?r:a.width-n.width}),b=Array.from(c.querySelectorAll("*")).filter(e=>{if(!n(e)||!e.contains(m)||!e.contains(g))return!1;const t=e.getBoundingClientRect();return t.width>=h&&t.height<=140}).sort((e,t)=>{const n=a(e)-a(t);return 0!==n?n:e.getBoundingClientRect().height-t.getBoundingClientRect().height}),v=w[0]??y[0]??b[0];return v?(v.setAttribute("data-roll-recommend-filter-age-track","1"),m.setAttribute("data-roll-recommend-filter-age-min-handle","1"),g.setAttribute("data-roll-recommend-filter-age-max-handle","1"),{ok:!0,current:(e=>{const n=t(e),a=n.includes("年龄")?n.slice(n.indexOf("年龄")+2):n,r=Array.from(a.matchAll(/\d+/g),e=>Number.parseInt(e[0],10)).filter(e=>Number.isInteger(e)),o=r[0],i=a.includes("不限")?void 0:r[1];return{...void 0!==o?{ageMin:o}:{},...void 0!==i?{ageMax:i}:{}}})(t(c.textContent))}):{ok:!1,error:"未找到年龄滑块轨道"}},{panelSelector:ua.recommend.filterPanel,markerAttributes:vo})}async function Eo(e){return await e.evaluate(e=>{const t=e=>(e??"").replace(/\s+/g," ").trim(),n=e=>{if(e.includes("不限"))return;const t=e.match(/\d+/);return t?Number.parseInt(t[0],10):void 0},a=(e,t)=>{const n=e.style.left;if(n.endsWith("%")){const e=Number.parseFloat(n);if(Number.isFinite(e))return Math.max(0,Math.min(1,e/100))}const a=e.getBoundingClientRect(),r=t.getBoundingClientRect();if(!(r.width<=0))return Math.max(0,Math.min(1,(a.left+a.width/2-r.left)/r.width))},r=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},o=e=>{const t=e.getBoundingClientRect();return t.width*t.height},i=e=>{const t=(e=>"string"==typeof e.className?e.className:"")(e),n=e.getAttribute("role")??"";return/slider|range|track|bar/i.test(t)||"slider"===n},s=Array.from(document.querySelectorAll(e)).filter(e=>r(e)).sort((e,t)=>o(e)-o(t))[0];if(!s)return{};const c=Array.from(s.querySelectorAll("div, li, section, dl, dd")).filter(e=>{const n=t(e.textContent);return r(e)&&n.includes("年龄")&&(/\d+|不限/.test(n)||Array.from(e.querySelectorAll("*")).some(i))}).sort((e,n)=>{const a=o(e)-o(n);return 0!==a?a:t(e.textContent).length-t(n.textContent).length})[0];if(!c)return{};const l=Array.from(c.querySelectorAll(".vue-slider-dot")).filter(e=>r(e)).sort((e,t)=>e.getBoundingClientRect().left-t.getBoundingClientRect().left),u=c.querySelector(".vue-slider-rail, .vue-slider");if(l.length>=2){const e=l[0],r=l[l.length-1],o=t(e?.querySelector(".vue-slider-dot-tooltip-text")?.textContent),i=t(r?.querySelector(".vue-slider-dot-tooltip-text")?.textContent),s=n(o),c=n(i);return{...void 0!==s?{ageMin:s}:{},...void 0!==c?{ageMax:c}:{},...u&&e?{minRatio:a(e,u)}:{},...u&&r?{maxRatio:a(r,u)}:{}}}return(e=>{const n=t(e),a=n.includes("年龄")?n.slice(n.indexOf("年龄")+2):n,r=Array.from(a.matchAll(/\d+/g),e=>Number.parseInt(e[0],10)).filter(e=>Number.isInteger(e)),o=r[0],i=a.includes("不限")?void 0:r[1];return{...void 0!==o?{ageMin:o}:{},...void 0!==i?{ageMax:i}:{}}})(t(c.textContent))},ua.recommend.filterPanel)}async function _o(e,t,n,a,r){if(!(await Bo(e)).ok)return!1;const o=e.locator('[data-roll-recommend-filter-age-track="1"]').first(),i="min"===n?'[data-roll-recommend-filter-age-min-handle="1"]':'[data-roll-recommend-filter-age-max-handle="1"]',s=e.locator(i).first(),c=await o.boundingBox();if(!c)return!1;const l=Math.max(0,Math.min(c.width,c.width*a)),u=Math.max(1,c.height/2);try{return r&&await r.moveToLocator(t,s,{durationMs:90,settleMs:20,target:e}),await s.dragTo(o,{force:!0,targetPosition:{x:l,y:u},timeout:2e3}),await e.waitForTimeout(wo),!0}catch{return!1}}function To(e){return Math.max(0,Math.min(1,(e-po)/(ho-po)))}function qo(e,t,n){return Math.max(t,Math.min(n,e))}async function $o(e,t,n,a,r){const o=await Eo(e),i=o.minRatio??0,s=o.maxRatio??1;let c="min"===n?0:Math.min(1,i+bo),l="min"===n?Math.max(0,s-bo):1,u=qo(To(a),c,l);for(let o=0;o<yo;o+=1){if(!await _o(e,t,n,u,r))return!1;const o=await Eo(e),i="min"===n?o.ageMin:o.ageMax;if(i===a)return!0;void 0===i?"max"===n?l=u:c=u:i<a?c=u:l=u;const s=(c+l)/2;if(Math.abs(s-u)<.001)break;u=qo(s,c,l)}const d=await Eo(e);return"min"===n?d.ageMin===a:d.ageMax===a}function No(e,t,n){return e.ageMin===t&&e.ageMax===n}async function Lo(e,t,n,a){const r=n.ageMin??po,o=n.ageMax,i=await Bo(e);if(!i.ok)return{success:!1,error:i.error};if(!await _o(e,t,"max",1,a))return{success:!1,error:"年龄上限无法重置为不限"};if(!await $o(e,t,"min",r,a))return{success:!1,error:`年龄下限无法设置为 ${r}`};if(void 0===o){if(!await _o(e,t,"max",1,a))return{success:!1,error:"年龄上限无法设置为不限"}}else if(!await $o(e,t,"max",o,a))return{success:!1,error:`年龄上限无法设置为 ${o}`};const s=await Eo(e);if(!No(s,r,o)){const e=void 0===s.ageMax?"不限":String(s.ageMax);return{success:!1,error:`年龄筛选未精确生效,当前为 ${s.ageMin??"未知"}-${e}`}}return{success:!0,state:s}}async function zo(e){return await e.evaluate(()=>{const e=e=>(e??"").replace(/\s+/g," ").trim(),t=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},n=/(购买VIP|VIP账号|开通VIP|开启VIP|专享筛选特权|扫码支付|立即开通|支付金额)/;if(!Array.from(document.body.querySelectorAll("div, section, aside")).filter(a=>t(a)&&n.test(e(a.textContent))).sort((e,t)=>{const n=e.getBoundingClientRect(),a=t.getBoundingClientRect();return n.width*n.height-a.width*a.height})[0])return!1;const a=Array.from(document.querySelectorAll(".boss-dialog__close, .dialog-close, .close-btn, [class*='close'], button, span, i")).filter(e=>t(e));for(const t of a){const n=e(t.textContent),a="string"==typeof t.className?t.className:"";if("×"===n||"关闭"===n||/close/i.test(a)){t.click();break}}return!0})}async function Fo(e,t){return!!await zo(t)||!(t===e||!await zo(e))}async function jo(e,t,n){return await e.evaluate(e=>{const t=e=>(e??"").replace(/\s+/g," ").trim(),n=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity},a=e=>{const t=e.getBoundingClientRect();return t.width*t.height},r=Array.from(document.querySelectorAll(e.panelSelector)).filter(e=>n(e)).sort((e,t)=>a(e)-a(t))[0];if(!r)return e.fallback;const o=Array.from(r.querySelectorAll("div, li, dl, dd, section, ul")).filter(a=>{const r=t(a.textContent);return n(a)&&r.includes(e.rowLabel)}).sort((e,n)=>{const r=a(e)-a(n);return 0!==r?r:t(e.textContent).length-t(n.textContent).length})[0];if(!o)return e.fallback;return Array.from(o.querySelectorAll(e.clickableOptionSelector)).filter(t=>n(t)&&(t=>{const n="string"==typeof t.className?t.className:"";return new RegExp(e.selectedClassPattern,"i").test(n)||"true"===t.getAttribute("aria-checked")||"true"===t.getAttribute("aria-selected")})(t)).map(e=>t(e.textContent)).find(t=>""!==t&&t!==e.rowLabel)??e.fallback},{panelSelector:ua.recommend.filterPanel,rowLabel:t,fallback:n,selectedClassPattern:xo,clickableOptionSelector:So})}async function Oo(e,t,n){return{...void 0!==n.ageMin?{ageMin:n.ageMin}:{},...void 0!==n.ageMax?{ageMax:n.ageMax}:{},gender:await jo(e,"性别",t.gender),activity:await jo(e,"活跃度",t.activity)}}async function Do(e,t,n){if(!await e.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity};document.querySelectorAll(`[${e.markerAttribute}]`).forEach(t=>t.removeAttribute(e.markerAttribute));const n=Array.from(document.querySelectorAll(e.panelSelector)).filter(e=>t(e))[0];if(!n)return!1;const a=Array.from(n.querySelectorAll("button, a, span, div, [role='button']")).filter(e=>t(e)).find(e=>"确定"===(e.textContent??"").replace(/\s+/g," ").trim());return!!a&&(a.setAttribute(e.markerAttribute,"1"),!0)},{panelSelector:ua.recommend.filterPanel,markerAttribute:"data-roll-recommend-filter-submit"}))return!1;try{const a=e.locator('[data-roll-recommend-filter-submit="1"]').first();return await Co(t,e,a,n),await a.click({timeout:2e3}),await e.waitForSelector(ua.recommend.filterPanel,{state:"hidden",timeout:4e3}),await e.waitForTimeout(600),!0}catch{return!1}}async function Uo(e,t,n){const a=async()=>await e.evaluate(e=>{const t=e=>"string"==typeof e.className?e.className:"",n=e=>{const n=t(e),a=e.parentElement?t(e.parentElement):"",r=null!==e.closest(".recommend-filter, .filter-label-wrap, .filter-wrap")?"recommend-filter":"";let o=0;for(const e of[n,a,r])/recommend-filter/.test(e)?o+=3:/filter-label/.test(e)?o+=2:/filter/.test(e)&&(o+=1);return o};for(const t of e.markerAttributes)document.querySelectorAll(`[${t}]`).forEach(e=>e.removeAttribute(t));const a=[...Array.from(document.querySelectorAll(e.filterButtonSelector)),...Array.from(document.querySelectorAll("button, a, span, div, [role='button']")).filter(e=>/^筛选(?:·\d+)?$/.test((e.textContent??"").replace(/\s+/g," ").trim()))].filter(e=>(e=>{const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"none"!==n.display&&"hidden"!==n.visibility&&"0"!==n.opacity})(e)).sort((e,t)=>{const a=n(t)-n(e);if(0!==a)return a;const r=e.getBoundingClientRect(),o=t.getBoundingClientRect();return r.width*r.height-o.width*o.height})[0];return!!a&&(a.setAttribute(e.markerAttribute,"1"),!0)},{filterButtonSelector:ua.recommend.filterButton,markerAttribute:"data-roll-recommend-filter-button",markerAttributes:vo}),r=async()=>{try{const a=e.locator(ua.recommend.filterButton).first();if(await a.count()>0&&await a.isVisible())return await a.scrollIntoViewIfNeeded(),await Co(t,e,a,n),await a.click({timeout:2e3}),!0}catch{}if(!await a())return!1;const r=e.locator('[data-roll-recommend-filter-button="1"]').first();return await Co(t,e,r,n),await r.click({timeout:2e3}),!0};if(await(async()=>{try{const t=e.locator(ua.recommend.filterPanel).first();return await t.count()>0&&await t.isVisible()}catch{return!1}})())return!0;for(let t=0;t<3;t+=1){await Ro(e);try{if(await r())return await e.waitForSelector(ua.recommend.filterPanel,{state:"visible",timeout:4e3}),await Ro(e),!0}catch{}await e.waitForTimeout(300)}return!1}async function Vo(e,t,n,a){const r=await Ao(t,3e3);if(!await Uo(t,e,a))return ko(n,r?"filter_not_found":"recommend_not_ready",{error:r?"未找到或无法打开筛选按钮":"推荐牛人页未就绪"});if(await Fo(e,t))return ko(n,"requires_vip",{error:"筛选条件触发 VIP 弹窗"});if(!await Po(t,e,"性别",n.gender,a))return ko(n,"filter_not_found",{error:`未找到性别筛选项:${n.gender}`});if(!await Po(t,e,"活跃度",n.activity,a))return ko(n,"filter_not_found",{error:`未找到活跃度筛选项:${n.activity}`});const o=await Lo(t,e,n,a);if(!o.success)return ko(n,"age_not_applied",{error:o.error});if(await Fo(e,t))return ko(n,"requires_vip",{error:"年龄筛选触发 VIP 弹窗"});const i=await Oo(t,n,o.state);if(!await Do(t,e,a))return ko(n,"submit_failed",{applied:i,error:"筛选确认失败"});const s=await Io(t);return ko(n,"applied",{applied:i,...void 0!==s?{filterButtonText:s}:{}})}async function Ho(e,t,n,a){const r=await Vo(e,t,n,a);if(t!==e&&("filter_not_found"===r.status||"recommend_not_ready"===r.status)){const t=await Vo(e,e,n,a);if("filter_not_found"!==t.status&&"recommend_not_ready"!==t.status)return t}return r}var Wo,Ko=uo.object({ageMin:uo.number().int().min(16).optional(),ageMax:uo.number().int().min(16).optional(),gender:uo.enum(mo),activity:uo.enum(go)}),Zo=uo.object({ageMin:uo.number().optional(),ageMax:uo.number().optional(),gender:uo.string(),activity:uo.string()}),Go=uo.object({success:uo.boolean(),status:uo.enum(fo),requested:Ko,applied:Zo.optional(),filterButtonText:uo.string().optional(),error:uo.string().optional()}),Yo=uo.object({ageMin:uo.number().int().min(16).optional().describe("年龄下限;未传则重置为 16"),ageMax:uo.number().int().min(16).optional().describe("年龄上限;未传则重置为不限"),gender:uo.enum(mo).default("不限").describe("性别筛选,只支持:不限、男、女"),activity:uo.enum(go).default("不限").describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃")}).refine(e=>void 0===e.ageMin||void 0===e.ageMax||e.ageMin<=e.ageMax,{path:["ageMax"],message:"ageMax must be greater than or equal to ageMin"});function Jo(){return{getContextManager:h,getRecommendTarget:no,waitForRecommendFilterSurface:Ao,applyRecommendFilter:Ho,moveVisualCursorToLocator:K,showVisualClickOnLocator:Z,createVisualActivitySession:e=>new na(e),...Wo}}function Xo(e){const t=e.gender??"不限",n=e.activity??"不限";return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},gender:t,activity:n}}function Qo(e){return{success:"applied"===e.status,status:e.status,requested:e.requested,...void 0!==e.applied?{applied:e.applied}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var ei=lo({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页打开筛选面板,只设置年龄、性别、活跃度[单选] 三个维度并提交。",input:Yo,output:Go,execute:async(e,t)=>{const n=Jo(),a=Xo(e);t.logger.info(`Filtering Boss recommend candidates: gender=${a.gender}, activity=${a.activity}, ageMin=${a.ageMin??"16"}, ageMax=${a.ageMax??"不限"}`);const r=n.getContextManager(),o=await r.getPage("zhipin");await o.bringToFront().catch(()=>{});let i=n.getRecommendTarget(o);const s=n.createVisualActivitySession(i);await s.begin("正在打开推荐筛选");let c=await n.waitForRecommendFilterSurface(i);if(c||(i=n.getRecommendTarget(o),await s.retarget(i),c=await n.waitForRecommendFilterSurface(i,2500)),!c)return await s.fail("推荐牛人页未就绪"),Qo({status:"recommend_not_ready",requested:a,error:"推荐牛人页未就绪"});await s.retarget(i),await s.begin("正在设置推荐筛选"),await s.highlightSelector(ua.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const l=await n.applyRecommendFilter(o,i,a,{moveToLocator:n.moveVisualCursorToLocator,showClickOnLocator:n.showVisualClickOnLocator});return"applied"===l.status?await s.succeed("已应用推荐筛选"):await s.fail(l.error??l.status),Qo(l)}});import{defineTool as ti}from"@roll-agent/sdk";import{z as ni}from"zod";var ai,ri=ni.object({index:ni.number(),candidateId:ni.string(),name:ni.string(),age:ni.string(),experience:ni.string(),education:ni.string(),workStatus:ni.string(),company:ni.string(),currentPosition:ni.string(),expectedLocation:ni.string(),expectedPosition:ni.string(),expectedSalary:ni.string(),tags:ni.array(ni.string()),buttonText:ni.string()}),oi=ni.object({containerLabel:ni.string(),stepsRequested:ni.number(),stepsCompleted:ni.number(),reachedBoundary:ni.boolean(),stopReason:ni.enum(Dn),uniqueCount:ni.number(),duplicateCount:ni.number(),noNewRounds:ni.number(),beforeItemCount:ni.number(),afterItemCount:ni.number(),beforeScrollHeight:ni.number(),afterScrollHeight:ni.number()}),ii=ni.object({success:ni.boolean(),candidates:ni.array(ri),total:ni.number(),scrollStats:oi.optional(),error:ni.string().optional()});function si(){return{getContextManager:h,getRecommendTarget:no,waitForRecommendList:ao,createVisualActivitySession:e=>new na(e),...ai}}async function ci(e){return await e.evaluate(()=>{let e=Array.from(document.querySelectorAll(".candidate-card-wrap"));0===e.length&&(e=Array.from(document.querySelectorAll("[data-geek], .geek-item")));const t=[];return e.forEach((e,n)=>{const a=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",r=e.querySelector(".name")?.textContent?.trim()??"";let o="",i="",s="",c="";const l=e.querySelector(".base-info.join-text-wrap, .base-info");if(l){const e=[];if(l.querySelectorAll(":scope > *").forEach(t=>{const n=t.textContent?.trim();n&&e.push(n)}),e.length<=1&&(e.length=0,l.childNodes.forEach(t=>{if(t.nodeType===Node.TEXT_NODE){const n=t.textContent?.trim();n&&e.push(n)}})),e.length<=1){const t=l.textContent?.trim()??"";e.length=0,t.split(/[丨·|]/).forEach(t=>{const n=t.trim();n&&e.push(n)})}for(const t of e)!o&&t.includes("岁")?o=t:!i&&(t.includes("年")||t.includes("应届")||t.includes("在校"))?i=t:!s&&/(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(t)?s=t:!c&&/(在职|离职|在校)/.test(t)&&(c=t)}const u=e.querySelector(".timeline-wrap.work-exps .content.join-text-wrap")??e.querySelector(".timeline-wrap.work-exps .content"),d=(u?.textContent?.trim()??"").split("·").map(e=>e.trim()),m=d[0]??"",g=d[1]??"";let f="",p="";const h=e.querySelector(".row-flex:not(.geek-desc)");if(h){const e=h.querySelector(".label"),t=h.querySelector(".content"),n=e?.textContent??"";if((n.includes("期望")||n.includes("最近关注"))&&t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());f=e[0]??"",p=e[1]??""}}if(!f){const t=e.querySelector(".timeline-wrap.expect .content.join-text-wrap")??e.querySelector(".timeline-wrap.expect .content");if(t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());f=e[0]??"",p=e[1]??""}}const w=e.querySelector(".salary-wrap")?.textContent?.trim()??"",y=[];e.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span").forEach(e=>{const t=e.textContent?.trim();t&&y.push(t)});const b=e.querySelector("button.btn.btn-greet")?.textContent?.trim()??"";t.push({index:n,candidateId:a,name:r,age:o,experience:i,education:s,workStatus:c,company:m,currentPosition:g,expectedLocation:f,expectedPosition:p,expectedSalary:w,tags:y,buttonText:b})}),t})}function li(e){return e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.age,e.experience,e.expectedLocation,e.expectedPosition,e.expectedSalary].join("|"):void 0}var ui=ti({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:ni.object({maxResults:ni.number().optional().describe("最多返回条数"),autoScroll:ni.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:ni.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:ii,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page");const n=si(),a=n.getContextManager(),r=await a.getPage("zhipin");let o=n.getRecommendTarget(r);const i=n.createVisualActivitySession(o);await i.begin("正在打开推荐列表");const s=await n.waitForRecommendList(o);if(o=n.getRecommendTarget(r),await i.retarget(o),!s)return await i.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};try{const n="正在读取推荐列表";await i.begin(n),await i.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:n,padding:8});const a=e.autoScroll??!0,r=e.maxScrolls??4;let s,c;if(a&&r>0){const t=await Qn(o,On("recommend-list"),()=>ci(o),li,{direction:"down",steps:r,settleMs:900,maxNoNewRounds:4,boundaryLoadRetries:4,boundarySettleMs:1200,...void 0!==e.maxResults?{targetCount:e.maxResults}:{}});c=[...t.items],s={containerLabel:t.after.containerLabel,stepsRequested:t.stepsRequested,stepsCompleted:t.stepsCompleted,reachedBoundary:t.reachedBoundary,stopReason:t.stopReason,uniqueCount:t.uniqueCount,duplicateCount:t.duplicateCount,noNewRounds:t.noNewRounds,beforeItemCount:t.before.itemCount,afterItemCount:t.after.itemCount,beforeScrollHeight:t.before.scrollHeight,afterScrollHeight:t.after.scrollHeight}}else c=await ci(o);const l=void 0!==e.maxResults?c.slice(0,e.maxResults):c;return await i.succeed(`已读取 ${l.length} 位候选人`),t.logger.info(`Found ${l.length} candidates in recommend list`+(s?`, scroll stop: ${s.stopReason}`:"")),{success:!0,candidates:l,total:l.length,...void 0!==s?{scrollStats:s}:{}}}catch(e){throw await i.fail("读取推荐列表失败"),e}}});import{defineTool as di}from"@roll-agent/sdk";import{BrowserPageInfoSchema as mi}from"@roll-agent/browser";import{z as gi}from"zod";var fi,pi=gi.object({success:gi.boolean(),alreadyOnRecommend:gi.boolean(),usedSidebarClick:gi.boolean(),recommendReady:gi.boolean(),page:mi.optional(),error:gi.string().optional()});function hi(){return{getContextManager:h,getRecommendTarget:no,findZhipinSidebarSectionLink:pa,isZhipinRecommendSurfaceOpen:ha,waitForZhipinRecommendSurface:ya,moveVisualCursorToLocator:K,showVisualClickOnLocator:Z,randomDelay:ln,toAttachedPageInfo:Ce,createVisualActivitySession:e=>new na(e),...fi}}async function wi(e,t,n){return await t.toAttachedPageInfo(e,n)}async function yi(e,t,n,a,r,o){return await n.fail(r),{success:!1,...o,page:await wi(e,t,a),error:r}}async function bi(e,t,n,a){await n.scrollIntoViewIfNeeded(),await t.moveVisualCursorToLocator(e,n,{durationMs:110,settleMs:30}),await n.hover(),await t.randomDelay(e,100,180),await t.showVisualClickOnLocator(e,n,{pulseDurationMs:180}),await n.click(),a.info("Clicked Boss sidebar nav: 推荐牛人")}var vi=di({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:gi.object({}),output:pi,execute:async(e,t)=>{const n=hi(),a=n.getContextManager();t.logger.info("Opening Boss recommend page via sidebar navigation");const r=await a.getPage("zhipin");await r.bringToFront().catch(()=>{});const o=n.createVisualActivitySession(r),i="正在切换到推荐牛人页";if(await o.begin(i),await o.highlightSelector(ua.nav.sidebar,{label:i,padding:10}),n.isZhipinRecommendSurfaceOpen(r))return await o.retarget(n.getRecommendTarget(r)),await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await wi(a,n,r)};const s=await n.findZhipinSidebarSectionLink(r,"recommend");if(!s)return await yi(a,n,o,r,"未找到推荐牛人导航",{alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1});try{await bi(r,n,s,t.logger)}catch(e){return await yi(a,n,o,r,e instanceof Error?e.message:"点击推荐牛人导航失败",{alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1})}const c=await n.waitForZhipinRecommendSurface(r);return await o.retarget(n.getRecommendTarget(r)),c?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await wi(a,n,r)}):await yi(a,n,o,r,"推荐牛人页未就绪",{alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1})}});import{defineTool as xi}from"@roll-agent/sdk";import{z as Si}from"zod";var Ci,ki=Si.object({index:Si.number(),candidateName:Si.string(),candidateId:Si.string(),success:Si.boolean(),error:Si.string().optional()}),Ai=Si.object({success:Si.boolean(),results:Si.array(ki),summary:Si.object({total:Si.number(),succeeded:Si.number(),failed:Si.number()})});function Ii(){return{getContextManager:h,getRecommendTarget:no,waitForRecommendList:ao,inspectRecommendCard:ro,moveVisualCursorToLocator:K,showVisualClickOnLocator:Z,humanDelay:un,shouldAddRandomBehavior:mn,performRandomScroll:dn,createVisualActivitySession:e=>new na(e),...Ci}}var Ri=xi({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Si.object({indices:Si.array(Si.number()).describe("要打招呼的候选人索引列表")}),output:Ai,execute:async(e,t)=>{t.logger.info(`Saying hello to ${e.indices.length} candidates`);const n=Ii(),a=n.getContextManager(),r=await a.getPage("zhipin");let o=n.getRecommendTarget(r);const i=n.createVisualActivitySession(o),s=e.indices.length>1?"正在批量打招呼":"正在打招呼";await i.begin("正在打开推荐列表");const c=await n.waitForRecommendList(o);if(o=n.getRecommendTarget(r),await i.retarget(o),!c){await i.fail("推荐列表未加载");const t=e.indices.map(e=>({index:e,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:t,summary:{total:t.length,succeeded:0,failed:t.length}}}await i.begin(s),await i.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:s,padding:8});const l=[];for(const t of e.indices)try{const e=await n.inspectRecommendCard(o,t);if(e.found)if(e.hasGreetButton){const a=o.locator(e.cardSelector).nth(t),s=a.locator("button.btn.btn-greet").first();await i.highlightLocator(a,{label:`正在定位第 ${t+1} 位候选人`,padding:10}),await s.scrollIntoViewIfNeeded(),await n.moveVisualCursorToLocator(r,s,{durationMs:90,settleMs:20,target:o}),await s.hover(),await n.showVisualClickOnLocator(r,s,{pulseDurationMs:160,target:o}),await s.click(),l.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!0})}else l.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!1,error:"未找到打招呼按钮"});else l.push({index:t,candidateName:"",candidateId:"",success:!1,...void 0!==e.error?{error:e.error}:{}});await n.humanDelay(r),n.shouldAddRandomBehavior(.3)&&await n.performRandomScroll(r)}catch(e){l.push({index:t,candidateName:"",candidateId:"",success:!1,error:e instanceof Error?e.message:String(e)})}const u={total:l.length,succeeded:l.filter(e=>e.success).length,failed:l.filter(e=>!e.success).length};return 0===u.failed?await i.succeed(`已完成 ${u.succeeded}/${u.total} 位候选人`):await i.fail(`已完成 ${u.succeeded}/${u.total} 位候选人`),t.logger.info(`Say hello: ${u.succeeded}/${u.total} succeeded`),{success:0===u.failed,results:l,summary:u}}});import{defineTool as Mi}from"@roll-agent/sdk";import{z as Pi}from"zod";var Bi=Pi.object({success:Pi.boolean(),candidateName:Pi.string(),candidateId:Pi.string(),error:Pi.string().optional()}),Ei=Mi({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:Pi.object({index:Pi.number().describe("候选人在列表中的索引")}),output:Bi,execute:async(e,t)=>{t.logger.info(`Opening resume for candidate at index ${e.index}`);const n=h(),a=await n.getPage("zhipin"),r=no(a);if(!await ao(r))return{success:!1,candidateName:"",candidateId:"",error:"推荐列表未加载"};const o=await ro(r,e.index);if(!o.found)return{success:!1,candidateName:"",candidateId:"",error:o.error??`索引 ${e.index} 超出范围`};const i=r.locator(o.cardSelector).nth(e.index),s=await i.locator("[data-geek], .card-inner, .geek-item").count()>0?i.locator("[data-geek], .card-inner, .geek-item").first():i;return await s.scrollIntoViewIfNeeded(),await K(a,s,{target:r}),await s.hover(),await ln(a,200,400),await Z(a,s,{target:r}),await s.click(),await ln(a,1e3,2e3),t.logger.info(`Opened resume for ${o.name}`),{success:!0,candidateName:o.name,candidateId:o.candidateId}}});import{defineTool as _i}from"@roll-agent/sdk";import{z as Ti}from"zod";var qi=Ti.object({x:Ti.number(),y:Ti.number(),width:Ti.number(),height:Ti.number()}),$i=Ti.object({success:Ti.boolean(),screenshotArea:qi.optional(),canvasInfo:Ti.object({width:Ti.number(),height:Ti.number()}).optional(),error:Ti.string().optional()}),Ni=_i({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:Ti.object({}),output:$i,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=h(),a=await n.getPage("zhipin");try{const e=a.frame("recommendFrame")??a.frames().find(e=>e.url().includes("recommend"));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$('iframe[src*="c-resume"]');if(!n)return{success:!1,error:"未找到简历 iframe"};const r=await n.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector("canvas#resume, div#resume canvas",{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await r.evaluate(()=>{const e=document.querySelector("canvas#resume, div#resume canvas");if(!e)return null;const t=e.getBoundingClientRect();return{width:e.width,height:e.height,clientWidth:t.width,clientHeight:t.height,x:t.x,y:t.y}});if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await a.evaluate(()=>{const e=document.querySelector("#recommendFrame");if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),s=await e.evaluate(()=>{const e=document.querySelector('iframe[src*="c-resume"]');if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),c=(i?.x??0)+(s?.x??0),l=(i?.y??0)+(s?.y??0);return t.logger.info(`Canvas located at (${c+o.x}, ${l+o.y})`),{success:!0,screenshotArea:{x:Math.round(c+o.x),y:Math.round(l+o.y),width:Math.round(o.clientWidth),height:Math.round(o.clientHeight)},canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as Li}from"@roll-agent/sdk";import{z as zi}from"zod";var Fi=zi.object({success:zi.boolean(),closed:zi.boolean(),error:zi.string().optional()}),ji=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],Oi=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Di=Li({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:zi.object({}),output:Fi,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=h(),a=await n.getPage("zhipin"),r=a.frame("recommendFrame")??a.frames().find(e=>e.url().includes("recommend"));if(!await(async()=>{if(r)for(const e of ji){const t=r.locator(e).first();if(await t.isVisible())return await K(a,t,{target:r}),await Z(a,t,{target:r}),await t.click(),!0}for(const e of Oi){const t=a.locator(e).first();if(await t.isVisible())return await K(a,t),await Z(a,t),await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await a.waitForTimeout(300);const e=r?await r.$(".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog"):await a.$(".boss-popup__wrapper");if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as Ui}from"@roll-agent/sdk";import{z as Vi}from"zod";import{waitForSelector as Hi}from"@roll-agent/browser";var Wi={login:{qrCode:".login-qr img, .qr-code img",loginSuccess:".user-info, .header-user"},messageList:{container:".chat-list, .msg-list",item:".chat-item, .msg-item",candidateName:".chat-item .name, .msg-item .name",lastMessage:".chat-item .msg, .msg-item .content",unreadBadge:".chat-item .unread, .msg-item .badge",timestamp:".chat-item .time, .msg-item .time"},chat:{input:".chat-input textarea, .msg-input textarea",sendButton:".btn-send, .send-btn",messageItem:".chat-msg, .msg-bubble",messageText:".chat-msg .text, .msg-bubble .text"}};import{navigateTo as Ki,waitForSelector as Zi}from"@roll-agent/browser";var Gi="https://www.yupao.com",Yi=`${Gi}/chat`,Ji=`${Gi}/login`;async function Xi(e){e.url().includes("/chat")||await Ki(e,Yi),await Zi(e,Wi.messageList.container,{timeout:15e3})}async function Qi(e,t){const n=`${Gi}/chat?id=${encodeURIComponent(t)}`;await Ki(e,n),await Zi(e,Wi.chat.input,{timeout:15e3})}async function es(e,t){await Xi(e),await Hi(e,Wi.messageList.item,{timeout:1e4});const n=Wi.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],a=t.maxItems?e.slice(0,t.maxItems):e;for(const e of a){const a=e.querySelector(t.sel.candidateName),r=e.querySelector(t.sel.lastMessage),o=e.querySelector(t.sel.unreadBadge),i=e.querySelector(t.sel.timestamp),s=e.getAttribute("data-id")??e.getAttribute("data-conversation-id")??e.querySelector("a")?.getAttribute("href")?.match(/id=([^&]+)/)?.[1]??"";n.push({conversationId:s,candidateName:a?.textContent?.trim()??"",lastMessage:r?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var ts=Vi.object({limit:Vi.number().optional().describe("最多返回的消息条数")}),ns=Vi.object({conversationId:Vi.string(),candidateName:Vi.string(),lastMessage:Vi.string(),unreadCount:Vi.number(),timestamp:Vi.string()}),as=Vi.object({messages:Vi.array(ns),total:Vi.number()}),rs=Ui({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:ts,output:as,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=h(),a=await n.getPage("yupao"),r=await es(a,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as os}from"@roll-agent/sdk";import{z as is}from"zod";import{waitForSelector as ss,typeText as cs,clickElement as ls}from"@roll-agent/browser";async function us(e,t,n){try{return await Qi(e,t),await cs(e,Wi.chat.input,n),await ls(e,Wi.chat.sendButton),await ss(e,Wi.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}var ds=is.object({conversationId:is.string().describe("对话 ID"),message:is.string().describe("要发送的回复消息")}),ms=is.object({success:is.boolean(),conversationId:is.string(),sentMessage:is.string(),error:is.string().optional()}),gs=os({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:ds,output:ms,execute:async(e,t)=>{const{conversationId:n,message:a}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const r=h(),o=await r.getPage("yupao"),i=await us(o,n,a);return i.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${i.error}`),{success:i.success,conversationId:n,sentMessage:a,error:i.error}}}),fs=t("browser-use-agent");function ps(e){if(void 0!==e){if("true"===e)return!0;if("false"===e)return!1;throw new Error(`Expected boolean env value "true" or "false", received "${e}".`)}}function hs(e,t){if(void 0===e)return;const n=Number.parseInt(e,10);if(!Number.isInteger(n))throw new Error(`${t} must be an integer, received "${e}".`);return n}function ws(e){if(void 0===e)return;const t=JSON.parse(e);if(!Array.isArray(t)||!t.every(e=>"string"==typeof e))throw new Error("BROWSER_ARGS_JSON must be a JSON string array.");return t}function ys(){return n.parse({mode:process.env.BROWSER_MODE,headless:ps(process.env.BROWSER_HEADLESS),cdpUrl:process.env.BROWSER_CDP_URL,cdpHost:process.env.BROWSER_CDP_HOST,cdpPort:hs(process.env.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:process.env.BROWSER_CHANNEL,executablePath:process.env.BROWSER_EXECUTABLE_PATH,userDataDir:process.env.BROWSER_USER_DATA_DIR,args:ws(process.env.BROWSER_ARGS_JSON),sessionsDir:process.env.BROWSER_SESSIONS_DIR})}var bs=e({name:"browser-use-agent",tools:[me,Ie,Oe,Ze,tt,rn,ia,Ia,Ba,Ha,Nr,Ur,Gr,co,vi,ei,ui,Ri,Ei,Ni,Di,rs,gs,S]},{onShutdown:v});async function vs(){await f(ys());try{await Mr(),y(!0)}catch(e){y(!1),fs.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await bs.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}vs().catch(async e=>{fs.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await v().catch(()=>{}),process.exit(1)});
|
|
1
|
+
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{BrowserRuntimeConfigSchema as n}from"@roll-agent/browser";import{defineTool as a}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as o}from"@roll-agent/sdk";import{BrowserRuntime as i,BrowserContextManager as s,SessionStore as c}from"@roll-agent/browser";var l,d,u,m=!1,g=o("browser-use-agent");async function h(e){l||(m=!1,u=new c(e.sessionsDir),l=new i(e),await l.start(),d=new s(l,u))}function f(){if(!l)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return l}function p(){if(!d)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return d}function y(){if(!u)throw new Error("SessionStore not initialized. Call initRuntime() first.");return u}function b(e){m=e}function w(){return m}async function v(){const e=d,t=l,n=[];if(d=void 0,l=void 0,u=void 0,m=!1,e){g.info("Closing browser contexts...");try{await e.closeAll()}catch(e){n.push(new Error("Failed to close browser contexts",{cause:e})),g.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(t){g.info("Stopping browser process...");try{await t.stop()}catch(e){n.push(new Error("Failed to stop browser runtime",{cause:e})),g.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(g.info("Browser runtime shutdown complete"),n.length>0)throw new AggregateError(n,"Browser runtime shutdown failed")}var x=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),S=a({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:x,execute:async(e,t)=>{const n=f();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as C}from"@roll-agent/sdk";import{z as R}from"zod";import{BrowserStatusSchema as k}from"@roll-agent/browser";import{createHash as M}from"node:crypto";import{z as I}from"zod";var P=["REPLY_AUTHORITY_KEYS_URL","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],A=/^[0-9a-f]{8}$/,N=I.object({present:I.boolean(),fingerprint:I.string().regex(A).optional()}),T=I.record(N);function E(e,t=process.env){return Object.fromEntries(e.map(e=>{const n=t[e];return"string"==typeof n&&n.length>0?[e,{present:!0,fingerprint:B(n)}]:[e,{present:!1}]}))}function B(e){return M("sha256").update(e).digest("hex").slice(0,8)}var F,q,L=180,$=60,O=280,_="roll-agent-visual-cursor-root",D="__rollVisualCursorState";function z(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function j(){return void 0!==F?F:z(process.env.BROWSER_VISUAL_CURSOR)??!0}async function H(e){return await e.evaluate(e=>{const t=e.getBoundingClientRect();return t.width<=0||t.height<=0?null:{x:Math.round(t.left+t.width/2),y:Math.round(t.top+t.height/2)}})}async function J(e,t){await e.evaluate(e=>{const t="roll-agent-visual-cursor-root",n="roll-agent-visual-cursor-pointer",a="__rollVisualCursorState",r=(()=>{const e=document.getElementById(t);if(e)return e;const a=document.createElement("div");a.id=t,a.style.position="fixed",a.style.left="0",a.style.top="0",a.style.width="0",a.style.height="0",a.style.pointerEvents="none",a.style.zIndex="2147483647";const r=document.createElement("div");return r.id=n,r.setAttribute("aria-hidden","true"),r.style.position="fixed",r.style.left="0",r.style.top="0",r.style.width="24px",r.style.height="24px",r.style.opacity="0",r.style.transform="translate(-9999px, -9999px)",r.style.willChange="transform, opacity",r.style.filter="drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))",r.innerHTML='<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/></svg>',a.append(r),document.documentElement.append(a),a})(),o=r.querySelector(`#${n}`);if(!o)return;const i=window[a],s=e.point,c=e.durationMs,l=s.x-2,d=s.y-2;if(i?o.style.transition=`transform ${c}ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease`:(o.style.transition="opacity 120ms ease",o.style.transform=`translate(${l}px, ${d}px)`),o.style.opacity="1",o.style.transform=`translate(${l}px, ${d}px)`,window[a]={x:s.x,y:s.y},!e.clickPulse)return;const u=document.createElement("div");u.setAttribute("aria-hidden","true"),u.style.position="fixed",u.style.left="0",u.style.top="0",u.style.width="18px",u.style.height="18px",u.style.borderRadius="9999px",u.style.border="2px solid rgba(20, 184, 166, 0.95)",u.style.background="rgba(20, 184, 166, 0.18)",u.style.pointerEvents="none",u.style.opacity="0.9",u.style.transform=`translate(${s.x-9}px, ${s.y-9}px) scale(0.55)`,u.style.transition=`transform ${e.pulseDurationMs}ms ease, opacity ${e.pulseDurationMs}ms ease`,r.append(u),requestAnimationFrame(()=>{u.style.opacity="0",u.style.transform=`translate(${s.x-18}px, ${s.y-18}px) scale(2)`}),globalThis.setTimeout(()=>{u.remove()},e.pulseDurationMs+40)},{point:t.point,durationMs:t.durationMs,clickPulse:t.clickPulse,pulseDurationMs:t.pulseDurationMs})}function U(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function V(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:_,stateKey:D})}async function W(e,t={}){if(e.isClosed())return!1;let n=!1;for(const a of U(e))if(a!==t.preserveTarget)try{await V(a),n=!0}catch{}return n}async function K(e,t,n={}){if(!j()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const a=await H(t);if(!a)return!1;const r=n.durationMs??L,o=n.settleMs??$,i=n.target??e;return await W(e,{preserveTarget:i}),await J(n.target??e,{point:a,durationMs:r,clickPulse:!1,pulseDurationMs:O}),await e.waitForTimeout(Math.max(r+o,0)),!0}catch{return!1}}async function Y(e,t,n={}){if(!j()||e.isClosed())return!1;try{const a=await H(t);if(!a)return!1;const r=n.pulseDurationMs??O,o=n.target??e;return await W(e,{preserveTarget:o}),await J(o,{point:a,durationMs:0,clickPulse:!0,pulseDurationMs:r}),await e.waitForTimeout(r),!0}catch{return!1}}function G(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function X(){return void 0!==q?q:G(process.env.BROWSER_VISUAL_ACTIVITY)??!0}var Z=k.extend({replyAuthorityKeysLoaded:R.boolean(),visualCursorEnabled:R.boolean(),visualActivityEnabled:R.boolean(),effectiveEnvSources:T}),Q=C({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:R.object({}),output:Z,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=f(),a=p(),r=y(),o=n.isRunning(),{headless:i,mode:s}=n.getConfig(),c=a.getActivePlatforms(),l=[];for(const e of c){const t=a.getPageCount(e),o=a.getCurrentUrl(e);let i=null,s="unknown";if(n.shouldRestoreSessionSnapshot()){const[t,n]=await Promise.all([r.loadCookies(e),r.loadLocalStorage(e)]);i=void 0!==t&&t.length>0||void 0!==n&&Object.keys(n).length>0,s=i?"snapshot":"none"}else n.usesPersistentProfile()&&(i=null,s="profile");l.push({platform:e,pagesOpen:t,currentUrl:o,hasLoginState:i,loginStateSource:s})}return{running:o,headless:i,mode:s,activeSessions:l,replyAuthorityKeysLoaded:w(),visualCursorEnabled:j(),visualActivityEnabled:X(),effectiveEnvSources:E(P)}}});import{defineTool as ee}from"@roll-agent/sdk";import{BrowserPageInfoSchema as te,PlatformSchema as ne}from"@roll-agent/browser";import{z as ae}from"zod";import{PLATFORMS as re}from"@roll-agent/browser";var oe={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function ie(e){return new URL(oe[e]).host}function se(e,t){try{return new URL(e).host.includes(ie(t))}catch{return!1}}function ce(e){return re.find(t=>se(e,t))}function le(e,t){const n=ce(t.url)??null;return{pageId:t.targetId,url:t.url,title:t.title,boundPlatform:e.getBoundPlatformForNativePage(t.targetId)??null,detectedPlatform:n,isSelectedForPlatform:e.isNativePageSelected(t.targetId)}}async function de(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:ce(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var ue=ae.object({platform:ne.optional().describe("可选:仅返回指定平台相关的页面")}),me=ae.object({pages:ae.array(te)}),ge=ee({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:ue,output:me,execute:async(e,t)=>{const n=p();t.logger.info("Listing browser pages");const a=(await n.listNativePages()).map(e=>le(n,e));return{pages:void 0===e.platform?a:a.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{defineTool as he}from"@roll-agent/sdk";import{BrowserPageInfoSchema as fe}from"@roll-agent/browser";import{z as pe}from"zod";async function ye(e,t){return e.useTrackedPage(t,e=>se(e.url(),t))}async function be(e,t){return(await e.listNativePages()).find(e=>se(e.url,t))}async function we(e,t){const n=await be(e,t);if(n)return await e.activateNativePage(n.targetId),{page:n,reusedExistingPage:!0};return{page:await e.openNativePage(oe[t]),reusedExistingPage:!1}}var ve,xe=pe.object({url:pe.string().url().describe("要导航到的目标 URL")}),Se=pe.object({success:pe.boolean(),page:fe});function Ce(){return{getContextManager:p,detectPlatformFromUrl:ce,matchesPlatformHost:se,findTrackedPlatformPage:ye,toAttachedPageInfo:de,...ve}}async function Re(e,t,n){const a=(await e.listNativePages()).find(e=>t.matchesPlatformHost(e.url,n));if(a)return await e.selectNativePage(n,a.targetId),await e.getPage(n)}async function ke(e,t,n,a){const r=t.detectPlatformFromUrl(n);if(!r){const t=await e.getActivePage();if(!t)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return{page:t}}const o=await t.findTrackedPlatformPage(e,r);if(o)return a.info(`Reusing tracked ${r} page instead of navigating the current unrelated tab`),{page:o,platform:r};const i=await Re(e,t,r);if(i)return a.info(`Reusing native ${r} page instead of navigating the current unrelated tab`),{page:i,platform:r};const s=await e.getActivePage();if(!s)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return a.warn(`No existing ${r} page found; falling back to navigating the current active tab`),{page:s,platform:r}}async function Me(e,t,n){return await e.selectAttachedPage(n,e.getPageId(t))}async function Ie(e,t){e.url()!==t&&await e.goto(t,{waitUntil:"domcontentloaded"})}var Pe=he({name:"navigate_active_tab",description:"导航到指定 URL;若 URL 属于已知平台(Boss/鱼泡),优先复用已打开的平台页,避免把无关 tab 导航成第二个平台页。",input:xe,output:Se,execute:async(e,t)=>{const n=Ce(),a=n.getContextManager();t.logger.info(`Navigating active tab to ${e.url}`);const{page:r,platform:o}=await ke(a,n,e.url,t.logger);await r.bringToFront().catch(()=>{}),await Ie(r,e.url);const i=o??n.detectPlatformFromUrl(r.url()),s=i?await Me(a,r,i):r;return i?t.logger.info(`Bound navigated page to ${i}`):a.clearBindingForPage(r),{success:!0,page:await n.toAttachedPageInfo(a,s)}}});import{defineTool as Ae}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ne,PlatformSchema as Te}from"@roll-agent/browser";import{z as Ee}from"zod";var Be=Ee.object({platform:Te.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡")}),Fe=Ee.object({success:Ee.boolean(),page:Ne,reusedExistingTab:Ee.boolean()}),qe=Ae({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:Be,output:Fe,execute:async(e,t)=>{const{platform:n}=e,a=f(),r=p();t.logger.info(`Opening platform page for ${n}`);const{page:o,reusedExistingPage:i}=await we(a,n);return r.rememberNativePageSelection(n,o),{success:!0,page:le(r,o),reusedExistingTab:i}}});import{defineTool as Le}from"@roll-agent/sdk";import{BrowserPageInfoSchema as $e,PlatformSchema as Oe}from"@roll-agent/browser";import{z as _e}from"zod";var De=_e.object({platform:Oe.describe("要将该页面绑定为当前活跃页的平台"),pageId:_e.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),ze=_e.object({success:_e.boolean(),page:$e}),je=Le({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:De,output:ze,execute:async(e,t)=>{const n=p();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const a=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:le(n,a)}}});import{defineTool as He}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Je}from"@roll-agent/browser";import{z as Ue}from"zod";var Ve=["native","native-watch","native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],We=[...Ve,"native-ws-connect-watch","native-page-bring-front-watch","native-evaluate-url-no-runtime-enable-watch","native-dom-read-no-runtime-enable-watch","native-input-move-no-runtime-enable-watch","native-runtime-enable-watch","native-evaluate-url-watch","native-dom-read-watch","browser-attach-watch"],Ke=Ue.enum(["localStorage","sessionStorage"]),Ye=Ue.enum(["array","object","string","number","boolean","null"]),Ge=Ue.enum(["empty","json","string"]),Xe=Ue.enum(Ve),Ze=Ue.enum(We),Qe=Ue.enum(["request","response"]),et=Ue.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),tt=Ue.object({area:Ke,key:Ue.string(),valueLength:Ue.number().int().nonnegative(),valueKind:Ge,jsonKind:Ye.optional(),jsonTopLevelKeys:Ue.array(Ue.string()).optional(),jsonArrayLength:Ue.number().int().nonnegative().optional(),numericFields:Ue.record(Ue.number()).optional(),booleanFields:Ue.record(Ue.boolean()).optional(),arrayLengths:Ue.record(Ue.number().int().nonnegative()).optional()}),nt=Ue.object({name:Ue.string(),domain:Ue.string(),path:Ue.string(),expires:Ue.string(),valueLength:Ue.number().int().nonnegative(),httpOnly:Ue.boolean(),secure:Ue.boolean(),sameSite:Ue.string().optional()}),at=Ue.object({before:Ue.number().optional(),after:Ue.number().optional(),delta:Ue.number().optional()}),rt=Ue.object({before:Ue.boolean().optional(),after:Ue.boolean().optional()}),ot=Ue.object({before:Ue.number().int().nonnegative().optional(),after:Ue.number().int().nonnegative().optional(),delta:Ue.number().int().optional()}),it=Ue.object({area:Ke,key:Ue.string(),beforePresent:Ue.boolean(),afterPresent:Ue.boolean(),numericDeltas:Ue.record(at).optional(),booleanChanges:Ue.record(rt).optional(),arrayLengthDeltas:Ue.record(ot).optional()}),st=Ue.object({url:Ue.string(),title:Ue.string(),visibilityState:Ue.string(),hasFocus:Ue.boolean()}),ct=Ue.object({navigatorWebdriver:Ue.boolean().optional(),userAgentContainsHeadless:Ue.boolean(),languagesLength:Ue.number().int().nonnegative(),pluginsLength:Ue.number().int().nonnegative(),hasChromeRuntime:Ue.boolean(),permissionQueryIsNative:Ue.boolean().optional(),hasPlaywrightBinding:Ue.boolean(),hasPwInitScripts:Ue.boolean(),cdcKeys:Ue.array(Ue.string()),webdriverKeys:Ue.array(Ue.string()),automationLikeWindowKeys:Ue.array(Ue.string())}),lt=Ue.object({rootNodeId:Ue.number().int(),rootNodeName:Ue.string(),childNodeCount:Ue.number().int().nonnegative().optional(),bodyTextLength:Ue.number().int().nonnegative().optional(),elementCount:Ue.number().int().nonnegative().optional()}),dt=Ue.object({type:Ue.literal("mouseMoved"),x:Ue.number(),y:Ue.number()}),ut=Ue.object({targetId:Ue.string(),websocketUrlAvailable:Ue.boolean(),connected:Ue.boolean(),pageBroughtToFront:Ue.boolean().optional(),runtimeEnabled:Ue.boolean().optional(),evaluate:st.optional(),dom:lt.optional(),input:dt.optional()}),mt=Ue.object({phase:Xe,success:Ue.boolean(),durationMs:Ue.number().int().nonnegative(),error:Ue.string().optional()}),gt=Ue.object({phase:Ze,capturedAt:Ue.string(),targetFound:Ue.boolean(),page:Je.optional(),urlChangedFromPrevious:Ue.boolean(),titleChangedFromPrevious:Ue.boolean(),previousUrl:Ue.string().optional(),currentUrl:Ue.string().optional(),previousTitle:Ue.string().optional(),currentTitle:Ue.string().optional()}),ht=Ue.object({kind:Qe,reason:et,capturedAt:Ue.string(),url:Ue.string(),method:Ue.string().optional(),resourceType:Ue.string().optional(),status:Ue.number().int().optional()}),ft=Ue.object({capturedAt:Ue.string(),url:Ue.string()}),pt=Ue.object({phase:Xe.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:Ue.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:Ue.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:Ue.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),yt=Ue.object({success:Ue.boolean(),requestedPhase:Xe,mode:Ue.string(),nativePages:Ue.array(Je),targetPage:Je.optional(),browserAttached:Ue.boolean(),pageAttached:Ue.boolean(),nativeTimeline:Ue.array(gt),networkEvents:Ue.array(ht).optional(),navigationEvents:Ue.array(ft).optional(),nativeCdp:ut.optional(),evaluate:st.optional(),detectorFingerprint:ct.optional(),storage:Ue.object({localStorage:Ue.array(tt),sessionStorage:Ue.array(tt),cookies:Ue.array(nt),counterDiffs:Ue.array(it)}).optional(),phases:Ue.array(mt),warnings:Ue.array(Ue.string())}),bt=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],wt=new Set(bt),vt=["native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read"],xt=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],St={"native-ws-connect":"native-ws-connect-watch","native-page-bring-front":"native-page-bring-front-watch","native-evaluate-url-no-runtime-enable":"native-evaluate-url-no-runtime-enable-watch","native-dom-read-no-runtime-enable":"native-dom-read-no-runtime-enable-watch","native-input-move-no-runtime-enable":"native-input-move-no-runtime-enable-watch","native-runtime-enable":"native-runtime-enable-watch","native-evaluate-url":"native-evaluate-url-watch","native-dom-read":"native-dom-read-watch"};function Ct(e){if(null===e)return"null";if(Array.isArray(e))return"array";switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":default:return"string";case"object":return"object"}}function Rt(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function kt(e){return vt.includes(e)}function Mt(e){return xt.includes(e)}function It(e){try{return JSON.parse(e)}catch{return}}function Pt(e){const t=Object.fromEntries(Object.entries(e).filter(e=>"number"==typeof e[1])),n=Object.fromEntries(Object.entries(e).filter(e=>"boolean"==typeof e[1])),a=Object.fromEntries(Object.entries(e).filter(e=>Array.isArray(e[1])).map(([e,t])=>[e,t.length]));return{...Object.keys(t).length>0?{numericFields:t}:{},...Object.keys(n).length>0?{booleanFields:n}:{},...Object.keys(a).length>0?{arrayLengths:a}:{}}}function At(e){return new Promise(t=>{setTimeout(t,e)})}function Nt(e){return wt.has(e)}function Tt(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=It(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const a=Ct(n),r=Rt(n)&&Nt(t.key)?Pt(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:a,...Rt(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...r}}function Et(e){return{name:e.name,domain:e.domain,path:e.path,expires:e.expires>0?new Date(1e3*e.expires).toISOString():"Session",valueLength:e.value.length,httpOnly:e.httpOnly,secure:e.secure,...void 0!==e.sameSite?{sameSite:e.sameSite}:{}}}async function Bt(e,t){const n=Date.now();try{return{result:await t(),phaseResult:{phase:e,success:!0,durationMs:Date.now()-n}}}catch(t){return{phaseResult:{phase:e,success:!1,durationMs:Date.now()-n,error:t instanceof Error?t.message:String(t)}}}}function Ft(e,t,n){if(void 0!==t){const a=e.find(e=>e.targetId===t);return a?se(a.url,"zhipin")?a:void n.push(`targetPageId "${t}" is not a zhipin page.`):void n.push(`targetPageId "${t}" not found in native pages.`)}const a=e.filter(e=>se(e.url,"zhipin"));if(1===a.length)return a[0];0===a.length?n.push("No zhipin native page found. Open Boss first or pass targetPageId."):n.push("Multiple zhipin native pages found. Pass targetPageId to avoid ambiguity.")}function qt(e,t,n,a){const r=n?.url,o=n?.title,i=a?.currentUrl,s=a?.currentTitle;return{phase:t,capturedAt:(new Date).toISOString(),targetFound:void 0!==n,...void 0!==n?{page:le(e,n)}:{},urlChangedFromPrevious:void 0!==i&&void 0!==r&&i!==r,titleChangedFromPrevious:void 0!==s&&void 0!==o&&s!==o,...void 0!==i?{previousUrl:i}:{},...void 0!==r?{currentUrl:r}:{},...void 0!==s?{previousTitle:s}:{},...void 0!==o?{currentTitle:o}:{}}}async function Lt(e,t,n,a){const r=await e.listNativePages();return qt(e,t,void 0!==n?r.find(e=>e.targetId===n):r.filter(e=>se(e.url,"zhipin"))[0],a)}async function $t(e,t,n,a,r){try{const o=t[t.length-1],i=await Lt(e,n,a,o);t.push(i),i.urlChangedFromPrevious&&r.push(`Target URL changed after ${n}: ${i.previousUrl??"(unknown)"} -> ${i.currentUrl??"(missing)"}.`)}catch(e){r.push(`Failed to capture native snapshot after ${n}: ${e instanceof Error?e.message:String(e)}.`)}}async function Ot(e,t,n,a,r,o){const i=t.length,s=Date.now();for(;;){await $t(e,t,n,a,o);const i=r-(Date.now()-s);if(i<=0)break;await At(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function _t(e){if(!Rt(e))throw new Error("Native CDP evaluate did not return an object value.");const t=e.url,n=e.title,a=e.visibilityState,r=e.hasFocus;if("string"!=typeof t||"string"!=typeof n||"string"!=typeof a||"boolean"!=typeof r)throw new Error("Native CDP evaluate returned an unexpected page summary shape.");return{url:t,title:n,visibilityState:a,hasFocus:r}}function Dt(e,t){if(!Rt(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!Rt(n))throw new Error("Native CDP DOM.getDocument did not return a root node.");const a=n.nodeId,r=n.nodeName,o=n.childNodeCount;if("number"!=typeof a||"string"!=typeof r)throw new Error("Native CDP DOM root node has an unexpected shape.");const i=Rt(t)?t:{},s=i.bodyTextLength,c=i.elementCount;return{rootNodeId:a,rootNodeName:r,..."number"==typeof o?{childNodeCount:o}:{},..."number"==typeof s?{bodyTextLength:s}:{},..."number"==typeof c?{elementCount:c}:{}}}async function zt(e){return _t(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function jt(e){return Dt(await e.getDocument({depth:1,pierce:!1}),await e.evaluateJson('(() => ({\n bodyTextLength: document.body?.innerText?.length ?? 0,\n elementCount: document.querySelectorAll("*").length\n }))()'))}async function Ht(e){return Dt(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Jt(e){return await e.bringToFront(),!0}async function Ut(e){const t={type:"mouseMoved",x:0,y:0};return await e.dispatchMouseEvent({type:t.type,x:t.x,y:t.y}),t}async function Vt(e,t,n,a,r,o){await $t(e,t,n,a,o);const i=await Ot(e,t,St[n],a,r,o);return i&&o.push(`Native CDP phase ${n} was followed by a native URL change; stop before deeper native CDP operations.`),i}async function Wt(e,t,n,a){switch(e){case"native-page-bring-front":{const r=await Bt(e,async()=>await Jt(t));return n.push(r.phaseResult),void 0!==r.result&&(a.pageBroughtToFront=r.result),r.phaseResult}case"native-evaluate-url-no-runtime-enable":{const r=await Bt(e,async()=>await zt(t));return n.push(r.phaseResult),void 0!==r.result&&(a.evaluate=r.result),r.phaseResult}case"native-dom-read-no-runtime-enable":{const r=await Bt(e,async()=>await Ht(t));return n.push(r.phaseResult),void 0!==r.result&&(a.dom=r.result),r.phaseResult}case"native-input-move-no-runtime-enable":{const r=await Bt(e,async()=>await Ut(t));return n.push(r.phaseResult),void 0!==r.result&&(a.input=r.result),r.phaseResult}}}async function Kt(e){const t={targetId:e.target.targetId,websocketUrlAvailable:"string"==typeof e.target.webSocketDebuggerUrl&&e.target.webSocketDebuggerUrl.length>0,connected:!1},n=e.target.webSocketDebuggerUrl;let a;try{const r=await Bt("native-ws-connect",async()=>{if(void 0===n||0===n.length)throw new Error("Native CDP target does not expose webSocketDebuggerUrl.");return await e.runtime.connectNativePage(e.target,{allowUnsafeRuntimeEnableForDiagnostics:!0})});if(e.phases.push(r.phaseResult),a=r.result,t.connected=r.phaseResult.success,!r.phaseResult.success||void 0===a)return{summary:t,triggeredNavigation:!1};const o=a;let i=await Vt(e.ctxManager,e.nativeTimeline,"native-ws-connect",e.targetPageId,e.watchMs,e.warnings);if("native-ws-connect"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};if(Mt(e.requestedPhase))return await Wt(e.requestedPhase,o,e.phases,t),i=await Vt(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await Bt("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await Vt(e.ctxManager,e.nativeTimeline,"native-runtime-enable",e.targetPageId,e.watchMs,e.warnings),!s.phaseResult.success||"native-runtime-enable"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};const c=await Bt("native-evaluate-url",async()=>await zt(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await Vt(e.ctxManager,e.nativeTimeline,"native-evaluate-url",e.targetPageId,e.watchMs,e.warnings),!c.phaseResult.success||"native-evaluate-url"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};const l=await Bt("native-dom-read",async()=>await jt(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await Vt(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{a?.close()}}function Yt(e){const t=e.toLowerCase();return t.includes("device-action-report")?"device-action-report":t.includes("boss_risk_report")?"boss-risk-report":t.includes("apm-fe.zhipin.com")||t.includes("/wapi/zpapm/actionlog/")?"apm-action-log":t.includes("zhipin-security")||t.includes("/security/")?"zhipin-security":void 0}async function Gt(e,t,n){const a=[],r=[],o=e=>{a.length<n&&a.push(e)},i=e=>{const t=e.url(),n=Yt(t);void 0!==n&&o({kind:"request",reason:n,capturedAt:(new Date).toISOString(),url:t,method:e.method(),resourceType:e.resourceType()})},s=e=>{const t=e.url(),n=Yt(t);if(void 0===n)return;const a=e.request();o({kind:"response",reason:n,capturedAt:(new Date).toISOString(),url:t,method:a.method(),resourceType:a.resourceType(),status:e.status()})},c=e=>{r.push({capturedAt:(new Date).toISOString(),url:e.url()})};e.on("request",i),e.on("response",s),e.on("framenavigated",c);try{await At(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:a,navigationEvents:r}}function Xt(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&se(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function Zt(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function Qt(e,t){return await e.evaluate(e=>{const t="localStorage"===e?window.localStorage:window.sessionStorage;return Array.from({length:t.length},(e,n)=>{const a=t.key(n)??"";return{key:a,value:a.length>0?t.getItem(a)??"":""}}).filter(e=>e.key.length>0)},t)}async function en(e){return(await e.evaluate(e=>{const t=["localStorage","sessionStorage"],n=[];for(const a of t){const t="localStorage"===a?window.localStorage:window.sessionStorage;for(const r of e){const e=t.getItem(r);null!==e&&n.push({area:a,key:r,value:e})}}return n},bt)).map(e=>Tt(e.area,{key:e.key,value:e.value}))}async function tn(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function nn(e){return await e.evaluate(()=>{const e=window,t=Object.keys(e),n=t.filter(e=>e.startsWith("cdc_")||e.includes("_cdc_")).slice(0,20),a=t.filter(e=>e.toLowerCase().includes("webdriver")).slice(0,20),r=t.filter(e=>{const t=e.toLowerCase();return t.includes("playwright")||t.includes("puppeteer")||t.includes("selenium")||t.includes("chromedriver")}).slice(0,20),o=navigator.permissions?.query,i="function"==typeof o?o.toString().includes("[native code]"):void 0;return{..."boolean"==typeof navigator.webdriver?{navigatorWebdriver:navigator.webdriver}:{},userAgentContainsHeadless:navigator.userAgent.toLowerCase().includes("headless"),languagesLength:navigator.languages?.length??0,pluginsLength:navigator.plugins?.length??0,hasChromeRuntime:"object"==typeof e.chrome&&null!==e.chrome&&"runtime"in e.chrome,...void 0!==i?{permissionQueryIsNative:i}:{},hasPlaywrightBinding:"__playwright__binding__"in e,hasPwInitScripts:"__pwInitScripts"in e,cdcKeys:n,webdriverKeys:a,automationLikeWindowKeys:r}})}function an(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function rn(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function on(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function sn(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function cn(e,t){const n=an(e),a=an(t),r=[...new Set([...n.keys(),...a.keys()])],o=[];for(const e of r){const t=n.get(e),r=a.get(e),i=r??t;if(!i)continue;const s={};for(const e of rn(t?.numericFields,r?.numericFields)){const n=t?.numericFields?.[e],a=r?.numericFields?.[e];n!==a&&(s[e]=on(n,a))}const c={};for(const e of rn(t?.booleanFields,r?.booleanFields)){const n=t?.booleanFields?.[e],a=r?.booleanFields?.[e];n!==a&&(c[e]={...void 0!==n?{before:n}:{},...void 0!==a?{after:a}:{}})}const l={};for(const e of rn(t?.arrayLengths,r?.arrayLengths)){const n=t?.arrayLengths?.[e],a=r?.arrayLengths?.[e];n!==a&&(l[e]=sn(n,a))}void 0!==t&&void 0!==r&&0===Object.keys(s).length&&0===Object.keys(c).length&&0===Object.keys(l).length||o.push({area:i.area,key:i.key,beforePresent:void 0!==t,afterPresent:void 0!==r,...Object.keys(s).length>0?{numericDeltas:s}:{},...Object.keys(c).length>0?{booleanChanges:c}:{},...Object.keys(l).length>0?{arrayLengthDeltas:l}:{}})}return o}async function ln(e,t){const[n,a,r]=await Promise.all([Qt(e,"localStorage"),Qt(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>Nt(e.key)).map(e=>Tt("localStorage",e)),...a.filter(e=>Nt(e.key)).map(e=>Tt("sessionStorage",e))];return{localStorage:n.map(e=>Tt("localStorage",e)),sessionStorage:a.map(e=>Tt("sessionStorage",e)),cookies:r.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>Et(e)),counterDiffs:cn(t,o)}}var dn=He({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:pt,output:yt,execute:async(e,t)=>{const n=e.phase??"native",a=e.watchMs??3e3,r=e.networkEventLimit??30,o=p(),i=f(),s=[],c=[],l=[];let d,u,m,g=!1,h=!1,y=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const b=await Bt("native",async()=>await o.listNativePages());c.push(b.phaseResult);const w=b.result??[],v=w.map(e=>le(o,e)),x=Ft(w,e.targetPageId,s);if(x&&(d=le(o,x),y=x.targetId,l.push(qt(o,"native",x,void 0))),!b.phaseResult.success||"native"===n)return{success:b.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Ot(o,l,"native-watch",y,a,s),{success:b.phaseResult.success&&void 0!==y,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(!x)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:v,browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(kt(n)){const e=await Kt({requestedPhase:n,target:x,runtime:i,ctxManager:o,targetPageId:y,watchMs:a,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}const S=await Bt("browser-attach",async()=>await i.getBrowser());c.push(S.phaseResult);const C=S.result;g=S.phaseResult.success,await $t(o,l,"browser-attach",y,s);const R=await Ot(o,l,"browser-attach-watch",y,a,s);if(R&&s.push("Browser attach was followed by a native URL change; treat this account/browser profile as unsafe for Playwright-backed zhipin tools."),!S.phaseResult.success||"browser-attach"===n)return{success:c.every(e=>e.success)&&!R,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(R)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};const k=[];let M;"network-watch"===n&&void 0!==C&&(M=Xt(C,x),M?k.push(Gt(M,a,r)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",x);const I=await Bt("page-attach",async()=>await o.getPage("zhipin"));c.push(I.phaseResult);const P=I.result;if(h=I.phaseResult.success,P&&(d=await de(o,P)),await $t(o,l,"page-attach",y,s),!I.phaseResult.success||!P){if("network-watch"===n&&k.length>0){const e=await Bt("network-watch",async()=>Zt(await Promise.all(k),r));c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents)}return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}if("page-attach"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){M!==P&&k.push(Gt(P,a,r));const e=await Bt("network-watch",async()=>Zt(await Promise.all(k),r));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await $t(o,l,"network-watch",y,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let A=[];if("storage-summary"===n){try{A=await en(P)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}a>0&&await At(a)}const N=await Bt("page-evaluate",async()=>await tn(P));c.push(N.phaseResult);const T=N.result;if(await $t(o,l,"page-evaluate",y,s),!N.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,...void 0!==T?{evaluate:T}:{},phases:c,warnings:s};const E=await Bt("detector-fingerprint",async()=>await nn(P));c.push(E.phaseResult);const B=E.result;if(await $t(o,l,"detector-fingerprint",y,s),!E.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,...void 0!==T?{evaluate:T}:{},...void 0!==B?{detectorFingerprint:B}:{},phases:c,warnings:s};const F=await Bt("storage-summary",async()=>await ln(P,A));c.push(F.phaseResult);const q=F.result;return await $t(o,l,"storage-summary",y,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:v,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:h,nativeTimeline:l,...void 0!==T?{evaluate:T}:{},...void 0!==B?{detectorFingerprint:B}:{},...void 0!==q?{storage:q}:{},phases:c,warnings:s}}});import{defineTool as un}from"@roll-agent/sdk";import{z as mn}from"zod";var gn=14,hn=720,fn=180;function pn(e){return`(() => {\n const args = ${JSON.stringify(e)};\n const activityRootId = "roll-agent-visual-activity-root";\n const activityViewportId = "roll-agent-visual-activity-viewport";\n const activityRegionId = "roll-agent-visual-activity-region";\n const activityCapsuleId = "roll-agent-visual-activity-capsule";\n const activityDotId = "roll-agent-visual-activity-dot";\n const activityLabelId = "roll-agent-visual-activity-label";\n const cursorRootId = "roll-agent-visual-cursor-root";\n const cursorPointerId = "roll-agent-visual-cursor-pointer";\n const cursorStateKey = "__rollVisualCursorState";\n\n const themes = {\n info: {\n accent: "#14b8a6",\n accentSoft: "rgba(20, 184, 166, 0.42)",\n accentGlow: "rgba(20, 184, 166, 0.18)",\n capsuleBg: "rgba(15, 23, 42, 0.82)",\n capsuleBorder: "rgba(45, 212, 191, 0.38)",\n text: "#F8FAFC",\n dot: "#2DD4BF"\n },\n success: {\n accent: "#22c55e",\n accentSoft: "rgba(34, 197, 94, 0.42)",\n accentGlow: "rgba(34, 197, 94, 0.18)",\n capsuleBg: "rgba(10, 24, 16, 0.86)",\n capsuleBorder: "rgba(74, 222, 128, 0.38)",\n text: "#F0FDF4",\n dot: "#4ADE80"\n },\n error: {\n accent: "#f59e0b",\n accentSoft: "rgba(245, 158, 11, 0.42)",\n accentGlow: "rgba(245, 158, 11, 0.2)",\n capsuleBg: "rgba(41, 24, 10, 0.88)",\n capsuleBorder: "rgba(251, 191, 36, 0.4)",\n text: "#FFFBEB",\n dot: "#FBBF24"\n }\n };\n\n const readRect = (selector, padding) => {\n if (!selector) return null;\n const element = document.querySelector(selector);\n if (!element) return null;\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return null;\n const safePadding = Math.max(padding ?? 0, 0);\n const left = Math.max(rect.left - safePadding, 0);\n const top = Math.max(rect.top - safePadding, 0);\n const right = Math.min(rect.right + safePadding, window.innerWidth);\n const bottom = Math.min(rect.bottom + safePadding, window.innerHeight);\n return {\n x: Math.round(left),\n y: Math.round(top),\n width: Math.max(Math.round(right - left), 0),\n height: Math.max(Math.round(bottom - top), 0),\n centerX: Math.round(rect.left + rect.width / 2),\n centerY: Math.round(rect.top + rect.height / 2)\n };\n };\n\n const ensureActivityRoot = () => {\n let root = document.getElementById(activityRootId);\n if (root) return root;\n root = document.createElement("div");\n root.id = activityRootId;\n root.style.position = "fixed";\n root.style.inset = "0";\n root.style.pointerEvents = "none";\n root.style.zIndex = "2147483646";\n\n const viewport = document.createElement("div");\n viewport.id = activityViewportId;\n viewport.style.position = "fixed";\n viewport.style.inset = "10px";\n viewport.style.borderRadius = "20px";\n viewport.style.opacity = "0";\n viewport.style.transition = "opacity 180ms ease";\n\n const region = document.createElement("div");\n region.id = activityRegionId;\n region.style.position = "fixed";\n region.style.left = "0";\n region.style.top = "0";\n region.style.width = "0";\n region.style.height = "0";\n region.style.borderRadius = "18px";\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n region.style.transition =\n "transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease";\n\n const capsule = document.createElement("div");\n capsule.id = activityCapsuleId;\n capsule.style.position = "fixed";\n capsule.style.left = "50%";\n capsule.style.top = "20px";\n capsule.style.display = "inline-flex";\n capsule.style.alignItems = "center";\n capsule.style.gap = "10px";\n capsule.style.padding = "10px 14px";\n capsule.style.borderRadius = "999px";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n capsule.style.transition = "opacity 180ms ease, transform 220ms ease";\n capsule.style.backdropFilter = "blur(12px)";\n capsule.style.fontSize = "13px";\n capsule.style.fontWeight = "600";\n capsule.style.lineHeight = "18px";\n capsule.style.whiteSpace = "nowrap";\n\n const dot = document.createElement("div");\n dot.id = activityDotId;\n dot.style.width = "8px";\n dot.style.height = "8px";\n dot.style.borderRadius = "999px";\n\n const label = document.createElement("div");\n label.id = activityLabelId;\n label.setAttribute("aria-live", "polite");\n\n capsule.append(dot, label);\n root.append(viewport, region, capsule);\n document.documentElement.append(root);\n return root;\n };\n\n const applyTheme = (themeName) => {\n const theme = themes[themeName] ?? themes.info;\n const viewport = document.getElementById(activityViewportId);\n const region = document.getElementById(activityRegionId);\n const capsule = document.getElementById(activityCapsuleId);\n const dot = document.getElementById(activityDotId);\n if (!viewport || !region || !capsule || !dot) return;\n viewport.style.border = "1px solid " + theme.accentSoft;\n viewport.style.boxShadow =\n "inset 0 0 0 1px " + theme.accentSoft + ", 0 0 52px " + theme.accentGlow;\n region.style.border = "1px solid " + theme.accentSoft;\n region.style.background = theme.accentGlow;\n region.style.boxShadow =\n "0 0 0 1px " + theme.accentSoft + ", 0 16px 42px " + theme.accentGlow;\n capsule.style.border = "1px solid " + theme.capsuleBorder;\n capsule.style.background = theme.capsuleBg;\n capsule.style.color = theme.text;\n capsule.style.boxShadow =\n "0 18px 46px rgba(15, 23, 42, 0.24), 0 0 0 1px " + theme.capsuleBorder;\n dot.style.background = theme.dot;\n dot.style.boxShadow = "0 0 0 5px " + theme.accentGlow;\n };\n\n const renderActivity = () => {\n if (!args.activity) return;\n ensureActivityRoot();\n applyTheme(args.activity.tone ?? "info");\n const viewport = document.getElementById(activityViewportId);\n const region = document.getElementById(activityRegionId);\n const capsule = document.getElementById(activityCapsuleId);\n const label = document.getElementById(activityLabelId);\n if (!viewport || !region || !capsule || !label) return;\n\n if (args.activity.mode === "clear") {\n viewport.style.opacity = "0";\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n return;\n }\n\n if (typeof args.activity.label === "string") {\n label.textContent = args.activity.label;\n }\n\n viewport.style.opacity = args.activity.mode === "complete" ? "0.9" : "0.72";\n capsule.style.opacity = "1";\n capsule.style.transform = "translate(-50%, 0)";\n\n const rect = args.activity.selector\n ? readRect(args.activity.selector, args.activity.padding ?? 14)\n : null;\n if (rect) {\n region.style.width = rect.width + "px";\n region.style.height = rect.height + "px";\n region.style.transform = "translate(" + rect.x + "px, " + rect.y + "px)";\n region.style.opacity = "1";\n } else {\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n }\n\n if (args.activity.mode === "complete") {\n window.setTimeout(() => {\n viewport.style.opacity = "0";\n region.style.opacity = "0";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n }, Math.max(args.activity.lingerMs ?? 720, 0));\n }\n };\n\n const ensureCursorRoot = () => {\n let root = document.getElementById(cursorRootId);\n if (root) return root;\n root = document.createElement("div");\n root.id = cursorRootId;\n root.style.position = "fixed";\n root.style.left = "0";\n root.style.top = "0";\n root.style.width = "0";\n root.style.height = "0";\n root.style.pointerEvents = "none";\n root.style.zIndex = "2147483647";\n\n const pointer = document.createElement("div");\n pointer.id = cursorPointerId;\n pointer.setAttribute("aria-hidden", "true");\n pointer.style.position = "fixed";\n pointer.style.left = "0";\n pointer.style.top = "0";\n pointer.style.width = "24px";\n pointer.style.height = "24px";\n pointer.style.opacity = "0";\n pointer.style.transform = "translate(-9999px, -9999px)";\n pointer.style.willChange = "transform, opacity";\n pointer.style.filter = "drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))";\n pointer.innerHTML =\n '<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">' +\n '<path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/>' +\n '</svg>';\n\n root.append(pointer);\n document.documentElement.append(root);\n return root;\n };\n\n const readCursorPoint = () => {\n if (args.cursor?.point) {\n const x = Number(args.cursor.point.x);\n const y = Number(args.cursor.point.y);\n if (Number.isFinite(x) && Number.isFinite(y)) {\n return {\n centerX: Math.round(x),\n centerY: Math.round(y)\n };\n }\n }\n\n if (!args.cursor?.selector) return null;\n const rect = readRect(args.cursor.selector, 0);\n if (!rect) return null;\n return {\n centerX: rect.centerX,\n centerY: rect.centerY\n };\n };\n\n const renderCursor = () => {\n const point = readCursorPoint();\n if (!point) return;\n ensureCursorRoot();\n const pointer = document.getElementById(cursorPointerId);\n if (!pointer) return;\n const state = window[cursorStateKey];\n const targetX = point.centerX - 2;\n const targetY = point.centerY - 2;\n pointer.style.transition = state\n ? "transform " + (args.cursor.durationMs ?? 180) + "ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease"\n : "opacity 120ms ease";\n pointer.style.opacity = "1";\n pointer.style.transform = "translate(" + targetX + "px, " + targetY + "px)";\n window[cursorStateKey] = { x: point.centerX, y: point.centerY };\n };\n\n renderActivity();\n renderCursor();\n return true;\n })()`}var yn=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!X()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...X()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??gn,tone:t.tone??"info"}}:{},...j()?{cursor:{selector:e,durationMs:fn}}:{}};return("activity"in n||"cursor"in n)&&await this.render(n)}async highlightPoint(e,t,n={}){return!!j()&&await this.render({cursor:{point:{x:e,y:t},durationMs:n.durationMs??fn}})}async succeed(e,t=hn){return!!X()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=hn){return!!X()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!X()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(pn(e))}catch{return!1}}};import{setTimeout as bn}from"node:timers/promises";async function wn(e,t=300,n=800){const a=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(a)}function vn(e){return e.trim().toLocaleLowerCase("zh-CN")}function xn(e,t){let n=0;for(const a of e)t.includes(a)&&(n+=1);return n}function Sn(e,t){if(void 0!==t.conversationId){const n=e.find(e=>e.conversationId===t.conversationId);if(n)return n}const n=t.candidateName;if(n){const t=vn(n),a=e.filter(e=>e.name.length>0);let r=a.find(e=>vn(e.name)===t);if(r)return r;if(r=a.find(e=>{const n=vn(e.name);return n.includes(t)||t.includes(n)}),r)return r;const o=t.length<=2?1:t.length<=4?.75:.6;if(r=a.find(e=>{const n=vn(e.name);return xn(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),r)return r}if(void 0!==t.index)return e[t.index]}var Cn=["chat-list","chat-history","recommend-list"],Rn={"chat-list":{surface:"chat-list",defaultDirection:"down",containerSelectors:[".user-list.b-scroll-stable",".chat-user .user-list",".chat-user-list",".chat-list-wrapper",".chat-list-wrap",".chat-user .b-scroll-stable",".b-scroll-stable",".chat-user .user-container",".chat-user"],itemSelector:'.user-list.b-scroll-stable [role="listitem"], .geek-item',highlightSelector:".user-list.b-scroll-stable, .chat-user .user-container, .chat-user"},"chat-history":{surface:"chat-history",defaultDirection:"up",containerSelectors:[".conversation-message",".chat-message-list",".conversation-main",".conversation-box"],itemSelector:".chat-message-list > .message-item, .conversation-message .message-item",highlightSelector:".conversation-message, .chat-message-list, .conversation-main"},"recommend-list":{surface:"recommend-list",defaultDirection:"down",containerSelectors:["#recommend-list",".list-wrap.card-list-wrap",".card-list-wrap",".recommend-list-wrap",".recommend-list",".card-list",".candidate-list",".candidate-body",".geek-list",".list-wrap",".recommendV2"],itemSelector:".candidate-card-wrap, li.card-item, .geek-item",highlightSelector:".candidate-card-wrap, li.card-item, .geek-item"}};function kn(e){return Rn[e]}var Mn={login:{notLoggedIn:".header-login-btn"},unread:{container:".chat-list-wrap",listItem:'[role="listitem"]',geekItemWrap:".geek-item-wrap",item:".chat-item",unreadBadge:".badge-count",unreadBadgeNew:".badge-count.badge-count-common-less",unreadBadgeSpan:".badge-count span",unreadDot:".red-dot",figure:".figure",badge:".badge",candidateName:".candidate-name",candidateNameAlt:".chat-item-name",candidateNameSelectors:'[class*="name"], .nickname, .geek-name',candidateNameNew:".geek-name",jobTitle:".source-job",lastMessage:".push-text",lastMessageAlt:".chat-last-msg",messageTime:".time, .time-shadow",clickArea:".chat-item-content",unreadCandidates:".geek-item"},chat:{chatContainer:".chat-container",messageList:".message-list",messageItem:".message-item",messageContent:".message-content",messageText:".text-content",userMessage:".message-right",candidateMessage:".message-left",messageTime:".message-time",senderName:".sender-name",inputBox:".chat-input",inputTextarea:"textarea.chat-input",inputEditorId:"#boss-chat-editor-input",sendButton:".btn-send",conversationEditor:".conversation-editor",submitButton:".submit-content .submit",submitButtonActive:".submit-content .submit.active",submitContent:".submit-content",sendButtonAlt:".conversation-editor .submit-content",sendIcon:".submit-content .icon-send",systemMessage:".system-msg"},chatDetails:{candidateInfoContainer:".base-info-single-container, .base-info-content",candidateName:".name-box, .geek-name, .base-name",candidateInfoItem:".geek-info-item, .base-info-item, .base-info-single-detial > div",candidateTag:".geek-tag, .high-light-boss",communicationPosition:".position-name",communicationPositionAlt:".position-item:not(.expect) .value.high-light-boss",candidateExpectContainer:".position-item.expect",candidateExpectValue:".position-item.expect .value.job",candidateExpectSalary:".position-item.expect .high-light-orange",candidatePosition:".geek-position, .position-name",candidatePositionAlt:".position-content .value, .position-item .value",chatMessageContainer:".conversation-message, .message-list",messageItem:".message-item",messageTime:".message-time .time",messageTextSpan:".text span",systemMessage:".item-system",friendMessage:".item-friend",myMessage:".item-myself",resumeMessage:".item-resume",readStatus:".status-read"},exchangeWechat:{exchangeButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > span.operate-btn",exchangeButtonFallback:".operate-exchange-left .operate-btn",confirmDialog:".exchange-tooltip",confirmButton:".exchange-tooltip .btn-box .boss-btn-primary.boss-btn",confirmButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > div > div > span.boss-btn-primary.boss-btn",cancelButton:".exchange-tooltip .btn-box .boss-btn-outline.boss-btn",wechatCard:".message-card-top-wrap",wechatCardAlt:'[class*="d-top-text"]'},username:{primary:".nav-item.nav-logout .user-name",fallbacks:["#header > div > div > div.nav-item.nav-logout > div.top-profile-logout.ui-dropmenu.ui-dropmenu-drop-arrow > div.ui-dropmenu-label > div > span.user-name",".ui-dropmenu-label .user-name",".nav-logout .user-name","#header .user-name",".top-profile .user-name",".nav-user .user-name",".user-name",'[class*="user-name"]','[class*="username"]','[data-qa="user-name"]',".header-user-name","#header .label-name"]},nav:{sidebar:".side-wrap.side-wrap-v2",chatLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/index"]',recommendLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/recommend"]'},recommend:{iframe:"#recommendFrame",resumeIframe:'iframe[src*="c-resume"]',filterButton:".recommend-filter .filter-label, .filter-label-wrap .filter-label, .filter-label",filterPanel:".filter-panel",candidateItem:"[data-geek], .geek-item",candidateName:".name",candidateBaseInfo:".base-info",workExps:".timeline-wrap.work-exps",expectInfo:".row-flex, .timeline-wrap.expect",salaryWrap:".salary-wrap",tagsWrap:".tags-wrap",greetButton:".btn-greet, .op-btn",resumeCanvas:"div#resume > canvas#resume, canvas#resume",closeResumeBtn:".close-btn, .dialog-close",closeResumeBtnV2:".recommendV2 .close-btn"},candidateProfile:{panel:".candidate-info, .resume-info, .geek-info",name:".candidate-info .name, .geek-info .name",age:".candidate-info .age, .geek-info .age",gender:".candidate-info .gender, .geek-info .gender",experience:".candidate-info .experience, .geek-info .work-exp",education:".candidate-info .education, .geek-info .edu",expectedSalary:".candidate-info .salary, .geek-info .expect-salary",expectedPosition:".candidate-info .position, .geek-info .expect-position",activeTime:".candidate-info .active-time, .geek-info .active"}},In=30,Pn=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function An(e){const t=e.trim();return!(0===t.length||t.length>In)&&(!Pn.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Nn(e){let t=e.priority;return Pn.has(e.text.trim())&&(t+=10),e.text.trim().length>10&&(t+=5),/^[\u4e00-\u9fff]{2,4}$/.test(e.text.trim())&&(t-=.5),void 0!==e.xRatio&&(t-=4*(e.xRatio-.5)),t}function Tn(e){const t=e.filter(e=>An(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),a=n.get(t)??new Set;a.add(e.strategy),n.set(t,a)}const a=t.map(e=>{let t=Nn(e);const a=n.get(e.text.trim())?.size??1;return a>1&&(t-=.5*a),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return a?{found:!0,username:a.evidence.text.trim(),strategy:a.evidence.strategy,source:a.evidence.source}:{found:!1}}var En='.user-list.b-scroll-stable [role="listitem"], .geek-item',Bn='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',Fn=".candidate-card-wrap",qn="[data-geek], .geek-item",Ln=`${Fn}, ${qn}`,$n=["/web/chat/recommend"],On=250,_n=300,Dn=900,zn=1200,jn=4,Hn=4,Jn=250,Un=90,Vn=520,Wn=16,Kn=50,Yn=650,Gn=10,Xn=.015,Zn="button, a, label, li, span, div, [role='button'], [role='radio']";function Qn(e){return new Promise(t=>{setTimeout(t,e)})}async function ea(e){const t=Math.max(0,Math.floor(e??0));t>0&&await Qn(t)}function ta(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function na(e){return e.includes("/web/chat/index")}function aa(e){return e.trim().toLocaleLowerCase("zh-CN")}function ra(e,t){const n=aa(e),a=aa(t);return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}function oa(e){return"string"==typeof e?e:""}function ia(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function sa(e){return"boolean"==typeof e&&e}function ca(e){return Array.isArray(e)?e.map((e,t)=>{const n=ta(e)?e:{};return{conversationId:oa(n.conversationId),candidateId:oa(n.candidateId),name:oa(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:oa(n.position),hasUnread:sa(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:oa(n.lastMessageTime),messagePreview:oa(n.messagePreview)}}):[]}function la(e){return Array.isArray(e)?e.flatMap(e=>{if(!ta(e))return[];const t=e.text,n=e.strategy,a=e.priority,r=e.source,o=e.xRatio;return"string"!=typeof t||"role-link"!==n&&"role-button"!==n&&"leaf-text"!==n&&"css-fallback"!==n||"number"!=typeof a||"string"!=typeof r?[]:[{text:t,strategy:n,priority:a,source:r,..."number"==typeof o?{xRatio:o}:{}}]}):[]}function da(e){return Array.isArray(e)?e.map((e,t)=>{const n=ta(e)?e:{},a=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:oa(n.candidateId),name:oa(n.name),age:oa(n.age),experience:oa(n.experience),education:oa(n.education),workStatus:oa(n.workStatus),company:oa(n.company),currentPosition:oa(n.currentPosition),expectedLocation:oa(n.expectedLocation),expectedPosition:oa(n.expectedPosition),expectedSalary:oa(n.expectedSalary),tags:Array.isArray(a)?a.map(e=>oa(e)).filter(Boolean):[],buttonText:oa(n.buttonText)}}):[]}function ua(e){if(!ta(e))return null;const t=oa(e.conversationId),n=oa(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:oa(e.candidateName)}}function ma(e){if(!ta(e))return null;const t=oa(e.candidateName);return t.length>0?{candidateName:t}:null}function ga(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!ta(e))return[];const n=e.sender,a=e.messageType;return"candidate"!==n&&"recruiter"!==n&&"system"!==n||"text"!==a&&"system"!==a&&"resume"!==a&&"wechat-exchange"!==a?[]:[{index:"number"==typeof e.index&&Number.isInteger(e.index)?e.index:t,sender:n,messageType:a,content:oa(e.content),time:oa(e.time)}]}):[]}function ha(e){const t=ta(e)?e:{},n=t.tags;return{name:oa(t.name),age:oa(t.age),experience:oa(t.experience),education:oa(t.education),communicationPosition:oa(t.communicationPosition),expectedJobText:oa(t.expectedJobText),expectedSalary:oa(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>oa(e)).filter(Boolean):[]}}function fa(e){const t=ta(e)?e:{};return{selectedTarget:ua(t.selectedTarget),activePanel:ma(t.activePanel),candidateInfo:ha(t.candidateInfo),messages:ga(t.messages)}}function pa(e){return ta(e)?{found:sa(e.found),x:ia(e.x),y:ia(e.y)}:{found:!1,x:0,y:0}}function ya(e){return ta(e)?{found:sa(e.found),left:ia(e.left),top:ia(e.top)}:{found:!1,left:0,top:0}}function ba(e){return ta(e)?{found:sa(e.found),cardSelector:oa(e.cardSelector)||Fn,candidateId:oa(e.candidateId),name:oa(e.name),hasGreetButton:sa(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:Fn,candidateId:"",name:"",hasGreetButton:!1}}function wa(e){return ta(e)?{..."number"==typeof e.ageMin?{ageMin:e.ageMin}:{},..."number"==typeof e.ageMax?{ageMax:e.ageMax}:{},..."number"==typeof e.minRatio?{minRatio:e.minRatio}:{},..."number"==typeof e.maxRatio?{maxRatio:e.maxRatio}:{}}:{}}function va(e){return ta(e)&&!0===e.ok?{ok:!0,current:wa(e.current),trackLeft:ia(e.trackLeft),trackTop:ia(e.trackTop),trackWidth:ia(e.trackWidth),trackHeight:ia(e.trackHeight),minHandleX:ia(e.minHandleX),minHandleY:ia(e.minHandleY),maxHandleX:ia(e.maxHandleX),maxHandleY:ia(e.maxHandleY)}:{ok:!1,error:ta(e)&&oa(e.error)||"未找到年龄滑块"}}function xa(e){return ta(e)?{containerFound:sa(e.containerFound),containerLabel:oa(e.containerLabel),scrollTop:ia(e.scrollTop),scrollHeight:ia(e.scrollHeight),clientHeight:ia(e.clientHeight),itemCount:ia(e.itemCount),atStart:sa(e.atStart),atEnd:sa(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Sa(e){return[e,...(e.childFrames??[]).flatMap(e=>Sa(e))]}function Ca(e){const t=new Set,n=[];for(const a of e){const e=a.conversationId.length>0?`conversation:${a.conversationId}`:`fallback:${a.name}:${a.messagePreview}:${a.index}`;t.has(e)||(t.add(e),n.push(a))}return n}function Ra(e){return e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.age,e.experience,e.expectedLocation,e.expectedPosition,e.expectedSalary].join("|"):void 0}function ka(e,t,n){const a=t.filter(e=>se(e.url,"zhipin")),r=a.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(r&&na(r.url))return r;const e=a.filter(e=>na(e.url));if(1===e.length){const t=e[0];if(t)return t}if(r)throw new Error("Selected BOSS page is not a chat page; switch to chat page first.");if(e.length>1)throw new Error("Multiple BOSS chat pages found; select the target tab first.");throw new Error("No BOSS chat page found; switch to chat page first.")}if(r)return r;if(1===a.length){const e=a[0];if(e)return e}if(a.length>1)throw new Error("Multiple BOSS pages found; select the target tab first.");throw new Error("No BOSS page found.")}async function Ma(e={},t={}){const n=t.ctxManager??p(),a=t.runtime??f(),r=ka(n,await n.listNativePages(),e),o=await a.connectNativePage(r);return new Pa({target:r,controller:o})}var Ia,Pa=class{target;controller;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller}get targetId(){return this.target.targetId}async inspectPage(){return{...this.target,url:await this.url().catch(()=>this.target.url),title:await this.title().catch(()=>this.target.title)}}async url(){return await this.evaluateJson("location.href")}async title(){return await this.evaluateJson("document.title")}async waitForSelector(e,t=5e3){const n=Date.now();for(;Date.now()-n<t;){if(await this.evaluateJson(`document.querySelector(${JSON.stringify(e)}) !== null`).catch(()=>!1))return!0;await Qn(On)}return!1}async evaluateJson(e){return await this.controller.evaluateJson(e)}async bringToFront(){await this.controller.bringToFront()}async isChatSurfaceOpen(){return await this.evaluateJson(`location.href.includes("/web/chat/index") ||\n document.querySelector("#container.chat-container-private") !== null ||\n document.querySelector(${JSON.stringify(Bn)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await Qn(On)}return!1}async isRecommendSurfaceOpen(){const e=[Mn.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify($n)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (hasRecommendUrl) {\n return true;\n }\n\n if (href.includes("/web/chat/index")) {\n return false;\n }\n\n const recommendFrame = document.querySelector(${JSON.stringify(e)});\n return recommendFrame !== null;\n })()`)}async waitForRecommendSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isRecommendSurfaceOpen().catch(()=>!1))return!0;await Qn(On)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Sa(e).map(e=>e.frame),n=t.find(e=>"recommendFrame"===e.name),a=t.find(t=>t.id!==e.frame.id&&t.url.includes("recommend")),r=n??a;if(void 0===r)return;if(void 0!==this.recommendFrameContextId&&this.recommendFrameContextFrameId===r.id)return this.recommendFrameContextId;const o=await this.controller.createIsolatedWorld(r.id).catch(()=>{});return void 0!==o?(this.recommendFrameContextId=o,this.recommendFrameContextFrameId=r.id,o):void 0}async evaluateRecommendFrameJson(e){const t=await this.resolveRecommendFrameContextId();if(void 0!==t)return await this.controller.evaluateJson(e,{contextId:t}).catch(()=>{})}async readRecommendFrameOffset(){return ya(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Mn.recommend.iframe)}) ??\n document.querySelector('iframe[name="recommendFrame"]') ??\n document.querySelector('iframe[src*="recommend"]');\n if (!iframe) {\n return { found: false, left: 0, top: 0 };\n }\n const rect = iframe.getBoundingClientRect();\n return { found: rect.width > 0 && rect.height > 0, left: rect.left, top: rect.top };\n })()`).catch(()=>{}))}async resolveRecommendClickTarget(e){const t=pa(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=pa(await this.evaluateRecommendFrameJson(e));if(!n.found)return n;const a=await this.readRecommendFrameOffset();return a.found?{found:!0,x:Math.round(n.x+a.left),y:Math.round(n.y+a.top)}:n}async dispatchNativeClick(e,t={}){return!!e.found&&(await(t.onTargetResolved?.(e)),await ea(t.preClickDelayMs),await this.controller.dispatchMouseEvent({type:"mouseMoved",x:e.x,y:e.y,buttons:0}),await this.controller.dispatchMouseEvent({type:"mousePressed",x:e.x,y:e.y,button:"left",buttons:1,clickCount:1}),await Qn(t.pressDurationMs??Un),await this.controller.dispatchMouseEvent({type:"mouseReleased",x:e.x,y:e.y,button:"left",buttons:0,clickCount:1}),await Qn(t.settleMs??Jn),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(Ln)}) !== null`;return!!await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async waitForRecommendList(e=1e4){const t=Date.now();for(;Date.now()-t<e;)if(await this.isRecommendSurfaceOpen().catch(()=>!1)){if(await this.hasRecommendList().catch(()=>!1))return!0;await Qn(On)}else await Qn(On);return await this.hasRecommendList().catch(()=>!1)}async clickSidebarSection(e,t={}){const n="chat"===e?Mn.nav.chatLink:Mn.nav.recommendLink;if((await this.controller.locator(n).click({settleMs:Jn,...void 0!==t.onTargetResolved?{onTargetResolved:t.onTargetResolved}:{}})).success)return!0;const a=pa(await this.evaluateJson(`(() => {\n const labels = ${JSON.stringify("chat"===e?["沟通","消息"]:["推荐牛人"])};\n const normalizedLabels = labels.map((label) => label.replace(/\\s+/g, ""));\n const interactiveSelector = 'a, button, [role="link"], [role="button"]';\n const textSelector = 'span, div, li, a, button, [role="link"], [role="button"]';\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const normalizeText = (element) =>\n (element.textContent ?? "").replace(/\\s+/g, "").trim();\n const matchesExactLabel = (element) => {\n const text = normalizeText(element);\n return normalizedLabels.some((label) => text === label);\n };\n const matchesInteractiveLabel = (element) => {\n const text = normalizeText(element);\n return normalizedLabels.some(\n (label) =>\n text === label ||\n text.startsWith(label) ||\n (text.includes(label) && text.length <= label.length + 8),\n );\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const readCenter = (element) => {\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n };\n\n const sidebar = document.querySelector(${JSON.stringify(Mn.nav.sidebar)}) ?? document;\n const interactiveTargets = Array.from(sidebar.querySelectorAll(interactiveSelector))\n .filter((element) => visible(element) && matchesInteractiveLabel(element))\n .sort((left, right) => area(left) - area(right));\n if (interactiveTargets[0]) {\n return readCenter(interactiveTargets[0]);\n }\n\n const exactTextTargets = Array.from(sidebar.querySelectorAll(textSelector))\n .filter((element) => visible(element) && matchesExactLabel(element))\n .sort((left, right) => area(left) - area(right));\n if (exactTextTargets[0]) {\n return readCenter(exactTextTargets[0]);\n }\n\n return { found: false, x: 0, y: 0 };\n })()`));return!!a.found&&(await(t.onTargetResolved?.(a)),await this.controller.dispatchMouseEvent({type:"mouseMoved",x:a.x,y:a.y,buttons:0}),await this.controller.dispatchMouseEvent({type:"mousePressed",x:a.x,y:a.y,button:"left",buttons:1,clickCount:1}),await this.controller.dispatchMouseEvent({type:"mouseReleased",x:a.x,y:a.y,button:"left",buttons:0,clickCount:1}),await Qn(Jn),!0)}async scrollSurface(e,t={}){const n=kn(e),a=t.direction??n.defaultDirection,r=Math.max(0,Math.floor(t.steps??1)),o=Math.max(0,Math.floor(t.settleMs??700)),i=await this.inspectSurface(e);let s=i,c=0;for(let n=0;n<r;n+=1){if("chat-list"===e&&s.itemCount>0){const n=await this.scrollSurfaceWithWheel(e,a,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await Qn(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if("up"===a&&s.atStart||"down"===a&&s.atEnd){if(s.itemCount<=0)break;const n=await this.scrollSurfaceWithWheel(e,a,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await Qn(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if(s=await this.scrollSurfaceOnce(e,a,t.distance),c+=1,o>0){await Qn(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}}return{success:i.containerFound||s.containerFound||c>0,direction:a,stepsRequested:r,stepsCompleted:c,reachedBoundary:"up"===a?s.atStart:s.atEnd,before:i,after:s}}async readChatCandidates(e={}){const t=e.targetCount??20,n=e.autoScroll?e.maxScrolls??3:0,a=[];for(let e=0;e<=n;e+=1){a.push(...await this.readVisibleChatCandidates());const r=Ca(a);if(r.length>=t||e===n)return r;if(!(await this.scrollChatList()).ok)return r;await Qn(_n)}return Ca(a)}async openChat(e){if(await this.bringToFront().catch(()=>{}),!await this.isChatSurfaceOpen().catch(()=>!1)){if(!await this.clickSidebarSection("chat")||!await this.waitForChatSurface())return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"}}const t=Math.max(0,Math.floor(e.maxScrolls??4)),n=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index,a=[];for(let r=0;r<=t;r+=1){a.push(...await this.readVisibleChatCandidates());const o=Ca(a),i=!0!==e.preferUnread||n?Sn(o,e):o.find(e=>e.hasUnread);if(void 0!==i){if(!await this.clickChatCandidate(i))return{...i,found:!1,error:`未能点击候选人: ${i.name||i.conversationId}`};await Qn(Jn);let e=await this.waitForNativeChatReady(i);if(!e){await this.clickChatCandidate(i)&&(await Qn(Jn),e=await this.waitForNativeChatReady(i))}return e?{...i,found:!0}:{...i,found:!1,error:`打开候选人聊天后,右侧会话未同步切换到 ${i.name||i.conversationId}`}}if(r>=t)break;if(!(await this.scrollChatList()).ok)break;await Qn(_n)}return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${e.candidateName??`index ${e.index??0}`}`}}async readSelectedChatTarget(){return ua(await this.evaluateJson('(() => {\n const selected = document.querySelector(".geek-item.selected");\n if (!selected) {\n return null;\n }\n\n const conversationId =\n selected.getAttribute("data-id") ??\n selected.closest(\'[role="listitem"]\')?.getAttribute("key") ??\n "";\n const candidateId =\n selected.getAttribute("data-geek") ??\n selected.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n const candidateName =\n selected\n .querySelector(\'[class*="name"], .nickname, .geek-name, .candidate-name\')\n ?.textContent?.trim() ?? "";\n\n return { conversationId, candidateId, candidateName };\n })()'))}async readActiveChatPanel(){return ma(await this.evaluateJson('(() => {\n const rootSelectors = [".chat-conversation", ".conversation-box", ".conversation-message"];\n const nameSelectors = [\n ".base-info-single-detial .name-box",\n ".base-info-content .name-box",\n ".base-info-single-container .name-box",\n ".base-info-content .base-name",\n ".chat-user-name",\n ".name-box",\n ".base-name",\n ];\n\n for (const rootSelector of rootSelectors) {\n const root = document.querySelector(rootSelector);\n if (!root) continue;\n\n for (const nameSelector of nameSelectors) {\n const candidateName = root.querySelector(nameSelector)?.textContent?.trim() ?? "";\n if (candidateName.length > 0) {\n return { candidateName };\n }\n }\n }\n\n return null;\n })()'))}async waitForChatMessages(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('document.querySelector(".chat-message-list .message-item, .conversation-message .message-item") !== null').catch(()=>!1))return!0;await Qn(On)}return await this.evaluateJson('document.querySelector(".chat-message-list .message-item, .conversation-message .message-item") !== null').catch(()=>!1)}async readCandidateChatDetails(e){const t=Math.max(0,Math.floor(e));return fa(await this.evaluateJson(`(() => {\n const maxMsgs = ${JSON.stringify(t)};\n const selected = document.querySelector(".geek-item.selected");\n const selectedTarget = selected\n ? {\n conversationId:\n selected.getAttribute("data-id") ??\n selected.closest('[role="listitem"]')?.getAttribute("key") ??\n "",\n candidateId:\n selected.getAttribute("data-geek") ??\n selected.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n selected.getAttribute("data-id") ??\n selected.closest('[role="listitem"]')?.getAttribute("key") ??\n "",\n candidateName:\n selected\n .querySelector('[class*="name"], .nickname, .geek-name, .candidate-name')\n ?.textContent?.trim() ?? ""\n }\n : null;\n\n const conversationRoot =\n document.querySelector(".chat-conversation") ??\n document.querySelector(".conversation-box") ??\n document;\n\n const nameSelectors = [\n ".base-info-single-detial .name-box",\n ".base-info-content .name-box",\n ".base-info-single-container .name-box",\n ".base-info-content .base-name",\n ".chat-user-name",\n ".name-box",\n ".base-name"\n ];\n let activePanel = null;\n for (const nameSelector of nameSelectors) {\n const candidateName =\n conversationRoot.querySelector(nameSelector)?.textContent?.trim() ?? "";\n if (candidateName.length > 0) {\n activePanel = { candidateName };\n break;\n }\n }\n\n const detailArea = conversationRoot.querySelector(\n ".base-info-single-detial, .base-info-content, .base-info-single-container"\n );\n const name =\n detailArea\n ?.querySelector(".name-box, .base-name, .chat-user-name, .geek-name")\n ?.textContent?.trim() ?? "";\n\n const infoItems = detailArea\n ? detailArea.querySelectorAll(":scope > div")\n : conversationRoot.querySelectorAll(".geek-info-item, .base-info-item");\n const infoTexts = [];\n infoItems.forEach((el) => {\n const text = el.textContent?.trim();\n if (text) infoTexts.push(text);\n });\n const fullInfo = infoTexts.join(" ");\n\n const ageMatch = fullInfo.match(/(\\d{2,3})岁/);\n const age = ageMatch ? ageMatch[1] + "岁" : "";\n const expMatch = fullInfo.match(/(\\d+年(?:以上)?|应届生|在校生)/);\n const experience = expMatch?.[1] ?? "";\n const education = fullInfo.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1] ?? "";\n\n let communicationPosition = "";\n const posNameEl = conversationRoot.querySelector(".position-name");\n if (posNameEl) {\n const cloned = posNameEl.cloneNode(true);\n cloned.querySelectorAll(".popover-wrap, .tooltip-job").forEach((element) => element.remove());\n communicationPosition = cloned.textContent?.trim() ?? "";\n }\n\n let expectedJobText = "";\n const expectValue = conversationRoot.querySelector(".position-item.expect .value.job");\n if (expectValue) {\n expectedJobText = expectValue.textContent?.trim() ?? "";\n }\n const expectedSalary =\n conversationRoot\n .querySelector(".position-item.expect .high-light-orange")\n ?.textContent?.trim() ?? "";\n\n const tags = [];\n if (detailArea) {\n detailArea\n .querySelectorAll(".geek-tag, .base-info-item .high-light-boss")\n .forEach((element) => {\n const text = element.textContent?.trim();\n if (text && !text.includes("更换职位") && text.length < 20) tags.push(text);\n });\n }\n\n const msgItems = conversationRoot.querySelectorAll(\n ".chat-message-list > .message-item, .conversation-message .message-item"\n );\n const timeRegex = /\\d{1,2}:\\d{2}(?::\\d{2})?|\\d{4}-\\d{2}-\\d{2}/;\n const messages = [];\n let msgIdx = 0;\n\n msgItems.forEach((item) => {\n if (msgIdx >= maxMsgs) return;\n\n const hasFriend = item.querySelector(".item-friend") !== null;\n const hasMyself = item.querySelector(".item-myself") !== null;\n const hasSystem = item.querySelector(".item-system") !== null;\n const hasResume = item.querySelector(".item-resume") !== null;\n const hasDialog = item.querySelector(".message-dialog-center") !== null;\n\n let sender = "system";\n let messageType = "text";\n if (hasFriend) {\n sender = "candidate";\n } else if (hasMyself) {\n sender = "recruiter";\n } else if (hasSystem || hasDialog) {\n sender = "system";\n messageType = "system";\n }\n if (hasResume) messageType = "resume";\n\n const cardEl = item.querySelector(".message-card-top-wrap, [class*='d-top-text']");\n if (cardEl) {\n const cardText = cardEl.textContent ?? "";\n if (cardText.includes("微信") || cardText.includes("WeChat")) {\n messageType = "wechat-exchange";\n }\n }\n\n const timeEl = item.querySelector(".message-time .time, .message-time");\n const timeMatch = (timeEl?.textContent ?? "").match(timeRegex);\n const time = timeMatch ? timeMatch[0] : "";\n\n let content = "";\n if (messageType === "wechat-exchange" && cardEl) {\n const cardText = cardEl.textContent ?? "";\n const digitMatch = cardText.match(/\\b(\\d{8,15})\\b/);\n const wxMatch = cardText.match(/微信[::号]*\\s*([a-zA-Z0-9_-]{5,20})/);\n if (digitMatch) content = "[微信号: " + digitMatch[1] + "]";\n else if (wxMatch) content = "[微信号: " + wxMatch[1] + "]";\n else content = "[交换微信]";\n } else if (cardEl) {\n const titleEl = item.querySelector(".message-card-top-title");\n const descEl = item.querySelector(".dialog-content, .message-card-top-text");\n content = (titleEl?.textContent?.trim() ?? descEl?.textContent?.trim() ?? "").trim();\n } else {\n const textEl = item.querySelector(".text span, .text-content, .text");\n if (textEl) {\n content = (textEl.textContent?.trim() ?? "")\n .replace(timeRegex, "")\n .replace("已读", "")\n .trim();\n }\n }\n\n if (content || messageType !== "text") {\n messages.push({ index: msgIdx, sender, messageType, content, time });\n msgIdx += 1;\n }\n });\n\n return {\n selectedTarget,\n activePanel,\n candidateInfo: {\n name,\n age,\n experience,\n education,\n communicationPosition,\n expectedJobText,\n expectedSalary,\n tags\n },\n messages\n };\n })()`))}async inspectRecommendCard(e){const t=Math.max(0,Math.floor(e)),n=this.buildRecommendCardInspectionExpression(t),a=ba(await this.evaluateJson(n).catch(()=>{}));if(a.found)return a;const r=ba(await this.evaluateRecommendFrameJson(n));return r.found?r:a}async clickRecommendGreet(e,t={}){const n=await this.inspectRecommendCard(e);if(!n.found||!n.hasGreetButton)return{...n,clicked:!1,...n.found||void 0!==n.error?{}:{error:"索引超出范围"},...n.found&&!n.hasGreetButton?{error:"未找到打招呼按钮"}:{}};const a=await this.resolveRecommendClickTarget(this.buildRecommendGreetClickExpression(Math.max(0,Math.floor(e)))),r=await this.dispatchNativeClick(a,t);return{...n,clicked:r,...r?{}:{error:"未能点击打招呼按钮"}}}async exchangeWechat(e={}){const t=pa(await this.evaluateJson('(() => {\n const selectors = [\n ".chat-conversation .conversation-operate .operate-exchange-left span.operate-btn",\n ".chat-conversation .conversation-operate span.operate-btn",\n ".conversation-box .conversation-operate .operate-exchange-left span.operate-btn",\n ".conversation-box .conversation-operate span.operate-btn",\n ".conversation-operate .operate-exchange-left span.operate-btn",\n ".conversation-operate .operate-exchange-left span"\n ];\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n for (const element of Array.from(document.querySelectorAll(selector))) {\n if (normalize(element.textContent) !== "换微信" || !visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(t,e))return{success:!1,exchanged:!1,error:"未找到当前聊天输入区的「换微信」按钮"};if(!await this.waitForWechatExchangeDialog())return{success:!1,exchanged:!1,error:"确认对话框未弹出"};const n=pa(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const tooltip = document.querySelector(".exchange-tooltip");\n const selectorGroups = tooltip\n ? [\n [tooltip, ".btn-box .boss-btn-primary.boss-btn"],\n [tooltip, ".btn-box span.boss-btn-primary"],\n [tooltip, "span.boss-btn-primary"],\n [tooltip, ".boss-btn-primary"]\n ]\n : [];\n for (const [root, selector] of selectorGroups) {\n const button = root.querySelector(selector);\n if (button && visible(button)) {\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n\n for (const container of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = container.textContent ?? "";\n if (!text.includes("交换微信") || !visible(container)) continue;\n const buttons = container.querySelectorAll(\n "span.boss-btn-primary, button.boss-btn-primary, span.boss-btn, button.boss-btn"\n );\n for (const button of Array.from(buttons)) {\n if (normalize(button.textContent) !== "确定" || !visible(button)) continue;\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(n,e))return{success:!1,exchanged:!1,error:"未找到确认按钮"};await Qn(1800);const a=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==a?{wechatNumber:a}:{}}}async sendChatReply(e,t={}){const n=pa(await this.evaluateJson('(() => {\n const selectors = ["#boss-chat-editor-input", "textarea.chat-input", ".chat-input"];\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n const element = document.querySelector(selector);\n if (!element || !visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(n,t))return{success:!1,error:"未找到聊天输入框"};await this.selectAllFocusedText(),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await this.controller.insertText(e),await Qn(250);const a=pa(await this.evaluateJson('(() => {\n const selectors = [\n ".submit-content .submit.active",\n ".submit-content .submit",\n ".submit-content",\n ".btn-send"\n ];\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n const button = document.querySelector(selector);\n if (!button || !visible(button)) continue;\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n for (const span of Array.from(document.querySelectorAll("span, button"))) {\n if ((span.textContent ?? "").replace(/\\s+/g, "").trim() !== "发送" || !visible(span)) {\n continue;\n }\n const rect = span.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));return await this.dispatchNativeClick(a,t)?(await Qn(800),{success:!0}):{success:!1,error:"未找到发送按钮"}}async applyRecommendFilter(e,t={}){const n=await this.waitForRecommendFilterSurface(3e3);if(!await this.openRecommendFilterPanel(t))return{status:n?"filter_not_found":"recommend_not_ready",requested:e,error:n?"未找到或无法打开筛选按钮":"推荐牛人页未就绪"};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"筛选条件触发 VIP 弹窗"};if(!await this.clickRecommendFilterOption("性别",e.gender,t))return{status:"filter_not_found",requested:e,error:`未找到性别筛选项:${e.gender}`};if(!await this.clickRecommendFilterOption("活跃度",e.activity,t))return{status:"filter_not_found",requested:e,error:`未找到活跃度筛选项:${e.activity}`};const a=await this.setRecommendAgeRange(e,t);if(!a.success)return{status:"age_not_applied",requested:e,error:a.error};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"年龄筛选触发 VIP 弹窗"};const r=await this.readNativeAppliedFilterState(e,a.state);if(!await this.clickRecommendFilterSubmit(t))return{status:"submit_failed",requested:e,applied:r,error:"筛选确认失败"};const o=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:r,...void 0!==o?{filterButtonText:o}:{}}}async readRecommendCandidates(e={}){const t=e.targetCount,n=e.autoScroll?e.maxScrolls??4:0,a=await this.inspectSurface("recommend-list");let r=a;const o=new Map;let i=0,s=0,c=0,l="max-steps";const d=async()=>{let e=0;const t=await this.readVisibleRecommendCandidates();for(const n of t){const t=Ra(n);void 0!==t&&0!==t.length&&(o.has(t)?s+=1:(o.set(t,n),e+=1))}return e};await d();for(let e=0;e<n;e+=1){if(void 0!==t&&o.size>=t){l="target-count";break}if(r.atEnd){let e=!1;for(let t=0;t<jn;t+=1){await Qn(zn);const t=await d(),n=await this.inspectSurface("recommend-list"),a=t>0||n.scrollHeight>r.scrollHeight||n.itemCount>r.itemCount||!n.atEnd;if(r=n,a){t>0&&(c=0),e=!0;break}}if(!e){l="boundary";break}if(void 0!==t&&o.size>=t){l="target-count";break}}if(c>=Hn){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:Dn});r=e.after,i+=e.stepsCompleted;c=await d()>0?0:c+1}void 0!==t&&o.size>=t?l="target-count":i>=n&&(l="max-steps");const u=[...o.values()];return{success:a.containerFound,direction:"down",stepsRequested:n,stepsCompleted:i,reachedBoundary:r.atEnd,before:a,after:r,items:u,uniqueCount:u.length,duplicateCount:s,noNewRounds:c,stopReason:l}}async readUsernameEvidence(){return la(await this.evaluateJson(`(() => {\n const limit = ${In};\n const evidence = [];\n const viewportWidth = window.innerWidth || 1280;\n const push = (entry) => {\n const text = String(entry.text ?? "").trim();\n if (text.length > 0 && text.length <= limit) {\n evidence.push({ ...entry, text });\n }\n };\n const scope =\n document.querySelector("header") ??\n document.querySelector("#header") ??\n document.querySelector('[role="banner"]') ??\n document.querySelector('[role="navigation"]') ??\n document.body;\n\n if (scope) {\n for (const element of Array.from(scope.querySelectorAll('a, [role="link"]'))) {\n const text = element.textContent?.trim() ?? "";\n const rect = element.getBoundingClientRect();\n push({\n text,\n strategy: "role-link",\n priority: 1,\n source: "role:link",\n xRatio: (rect.x + rect.width / 2) / viewportWidth\n });\n }\n for (const element of Array.from(scope.querySelectorAll('button, [role="button"]'))) {\n const text = element.textContent?.trim() ?? "";\n const rect = element.getBoundingClientRect();\n push({\n text,\n strategy: "role-button",\n priority: 1,\n source: "role:button",\n xRatio: (rect.x + rect.width / 2) / viewportWidth\n });\n }\n\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const text = walker.currentNode.textContent?.trim() ?? "";\n push({\n text,\n strategy: "leaf-text",\n priority: 3,\n source: "leaf-text"\n });\n }\n }\n\n for (const selector of ${JSON.stringify([Mn.username.primary,...Mn.username.fallbacks])}) {\n try {\n push({\n text: document.querySelector(selector)?.textContent?.trim() ?? "",\n strategy: "css-fallback",\n priority: 4,\n source: selector\n });\n } catch {\n // Ignore invalid selectors from site-specific fallback list.\n }\n }\n\n return evidence;\n })()`))}close(){this.controller.close()}buildRecommendCardInspectionExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(Fn)};\n const fallbackSelector = ${JSON.stringify(qn)};\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify($n)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (!hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n found: false,\n cardSelector: primarySelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "推荐列表未加载"\n };\n }\n\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cardSelector = primaryCards.length > 0 ? primarySelector : fallbackSelector;\n const cards = primaryCards.length > 0 ? primaryCards : Array.from(root.querySelectorAll(cardSelector));\n if (cards.length <= index) {\n return {\n found: false,\n cardSelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "索引超出范围"\n };\n }\n\n const item = cards[index];\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n "";\n const name = item.querySelector(".name")?.textContent?.trim() ?? "";\n const greetButton =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n const rect = greetButton?.getBoundingClientRect();\n const hasGreetButton = Boolean(rect && rect.width > 0 && rect.height > 0);\n\n return { found: true, cardSelector, candidateId, name, hasGreetButton };\n })()`}buildRecommendGreetClickExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(Fn)};\n const fallbackSelector = ${JSON.stringify(qn)};\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cards =\n primaryCards.length > 0\n ? primaryCards\n : Array.from(root.querySelectorAll(fallbackSelector));\n const item = cards[index];\n if (!item) return { found: false, x: 0, y: 0 };\n const button =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n if (!button) return { found: false, x: 0, y: 0 };\n button.scrollIntoView({ block: "center", inline: "center" });\n const rect = button.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { found: false, x: 0, y: 0 };\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`}async waitForWechatExchangeDialog(e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const tooltip = document.querySelector(".exchange-tooltip");\n if (tooltip && visible(tooltip)) return true;\n for (const element of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = element.textContent ?? "";\n if (\n text.includes("交换微信") &&\n element.querySelector(".boss-btn-primary, .boss-btn") &&\n visible(element)\n ) {\n return true;\n }\n }\n return false;\n })()').catch(()=>!1))return!0;await Qn(450)}return!1}async readWechatNumber(){const e=await this.evaluateJson('(() => {\n const cardSelectors = [\n ".message-card-top-wrap",\n \'[class*="d-top-text"]\',\n ".message-card-top-title"\n ];\n const parse = (text) => {\n const digitMatch = text.match(/\\b(\\d{8,15})\\b/);\n if (digitMatch) return digitMatch[1];\n const wxMatch = text.match(/微信[::号]*\\s*([a-zA-Z0-9_-]{5,20})/);\n if (wxMatch) return wxMatch[1];\n const letterMatch = text.match(/\\b([a-zA-Z][a-zA-Z0-9_-]{5,19})\\b/);\n if (letterMatch && !["微信", "WeChat"].includes(letterMatch[1])) {\n return letterMatch[1];\n }\n return null;\n };\n\n for (const selector of cardSelectors) {\n const cards = Array.from(document.querySelectorAll(selector));\n for (let index = cards.length - 1; index >= 0; index -= 1) {\n const parsed = parse(cards[index]?.textContent ?? "");\n if (parsed) return parsed;\n }\n }\n\n const msgItems = Array.from(document.querySelectorAll(".message-item"));\n for (let index = msgItems.length - 1; index >= 0; index -= 1) {\n const card = msgItems[index]?.querySelector(\'.message-card-top-wrap, [class*="d-top-text"]\');\n if (!card) continue;\n const parsed = parse(card.textContent ?? "");\n if (parsed) return parsed;\n }\n return null;\n })()').catch(()=>null);return"string"==typeof e&&e.length>0?e:void 0}async selectAllFocusedText(){const e=[4,2];for(const t of e)await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"a",code:"KeyA",windowsVirtualKeyCode:65,nativeVirtualKeyCode:65,modifiers:t}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"a",code:"KeyA",windowsVirtualKeyCode:65,nativeVirtualKeyCode:65,modifiers:t}),await Qn(40)}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${Mn.recommend.filterButton}, ${Fn}, ${Mn.recommend.candidateItem}`)}) !== null;\n })()`,n=Date.now();for(;Date.now()-n<e;){if(await this.evaluateJson(t).catch(()=>!1)||await this.evaluateRecommendFrameJson(t))return!0;await Qn(On)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Mn.recommend.filterPanel)});\n if (!panel) return false;\n const rect = panel.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n })()`;return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async openRecommendFilterPanel(e){if(await this.isRecommendFilterPanelVisible())return!0;for(let t=0;t<3;t+=1){await this.dismissPreviousFilterPrompt(e);const t=await this.resolveRecommendClickTarget(`(() => {\n const filterButtonSelector = ${JSON.stringify(Mn.recommend.filterButton)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const scoreCandidate = (element) => {\n const classes = classText(element);\n const parentClasses = element.parentElement ? classText(element.parentElement) : "";\n const ancestorClasses =\n element.closest(".recommend-filter, .filter-label-wrap, .filter-wrap") !== null\n ? "recommend-filter"\n : "";\n let score = 0;\n for (const value of [classes, parentClasses, ancestorClasses]) {\n if (/recommend-filter/.test(value)) score += 3;\n else if (/filter-label/.test(value)) score += 2;\n else if (/filter/.test(value)) score += 1;\n }\n return score;\n };\n const selectorCandidates = Array.from(document.querySelectorAll(filterButtonSelector));\n const textCandidates = Array.from(\n document.querySelectorAll("button, a, span, div, [role='button']")\n ).filter((element) => /^筛选(?:·\\d+)?$/.test(normalize(element.textContent)));\n const candidate = [...selectorCandidates, ...textCandidates]\n .filter(visible)\n .sort((a, b) => {\n const scoreDelta = scoreCandidate(b) - scoreCandidate(a);\n if (scoreDelta !== 0) return scoreDelta;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return aRect.width * aRect.height - bRect.width * bRect.height;\n })[0];\n if (!candidate) return { found: false, x: 0, y: 0 };\n candidate.scrollIntoView({ block: "center", inline: "center" });\n const rect = candidate.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);if(await this.dispatchNativeClick(t,e)){const t=Date.now();for(;Date.now()-t<4e3;){if(await this.isRecommendFilterPanelVisible())return await this.dismissPreviousFilterPrompt(e),!0;await Qn(On)}}await Qn(300)}return!1}async dismissPreviousFilterPrompt(e){const t=await this.resolveRecommendClickTarget('(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const roots = Array.from(document.body.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return aRect.width * aRect.height - bRect.width * bRect.height;\n });\n for (const root of roots) {\n const text = normalize(root.textContent);\n if (!text.includes("是否应用上次") && !text.includes("上次的筛选条件")) continue;\n const candidates = Array.from(\n root.querySelectorAll("button, a, span, div, [role=\'button\']")\n ).filter(visible);\n for (const candidate of candidates) {\n const buttonText = normalize(candidate.textContent);\n if (/^(取消|不应用|否|关闭|稍后)$/.test(buttonText)) {\n const rect = candidate.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n }\n return { found: false, x: 0, y: 0 };\n })()');return await this.dispatchNativeClick(t,e)}async clickRecommendFilterOption(e,t,n){const a=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Mn.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Zn)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const resolveClickable = (element, row) => {\n let current = element;\n while (current && current !== row.parentElement) {\n const tag = current.tagName.toLowerCase();\n const role = current.getAttribute("role") ?? "";\n if (\n tag === "button" ||\n tag === "a" ||\n tag === "label" ||\n tag === "li" ||\n role === "button" ||\n role === "radio"\n ) {\n return current;\n }\n current = current.parentElement;\n }\n return element;\n };\n\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return { found: false, x: 0, y: 0 };\n\n const rows = Array.from(panel.querySelectorAll("div, li, dl, dd, section, ul"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return visible(element) && text.includes(rowLabel) && text.includes(optionLabel);\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n });\n\n for (const row of rows) {\n const option = Array.from(row.querySelectorAll(clickableSelector))\n .filter((element) => visible(element) && normalize(element.textContent) === optionLabel)\n .sort((a, b) => area(a) - area(b))[0];\n if (!option) continue;\n const clickable = resolveClickable(option, row);\n clickable.scrollIntoView({ block: "center", inline: "center" });\n const rect = clickable.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n\n return { found: false, x: 0, y: 0 };\n })()`);return await this.dispatchNativeClick(a,n)}async detectVipModal(){const e='(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const modalPattern =\n /(购买VIP|VIP账号|开通VIP|开启VIP|专享筛选特权|扫码支付|立即开通|支付金额)/;\n return Array.from(document.body.querySelectorAll("div, section, aside"))\n .some((element) => visible(element) && modalPattern.test(normalize(element.textContent)));\n })()';return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async readNativeAgeState(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Mn.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const parseAgeValue = (text) => {\n if (text.includes("不限")) return undefined;\n const match = text.match(/\\d+/);\n return match ? Number.parseInt(match[0], 10) : undefined;\n };\n const parseAgeState = (text) => {\n const normalized = normalize(text);\n const ageText = normalized.includes("年龄")\n ? normalized.slice(normalized.indexOf("年龄") + "年龄".length)\n : normalized;\n const numbers = Array.from(ageText.matchAll(/\\d+/g), (match) =>\n Number.parseInt(match[0], 10)\n ).filter((value) => Number.isInteger(value));\n const ageMin = numbers[0];\n const ageMax = ageText.includes("不限") ? undefined : numbers[1];\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {})\n };\n };\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const preferRailTrack = (elements) =>\n [...elements].sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const looksLikeSlider = (element) => {\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return /slider|range|track|bar/i.test(classes) || role === "slider";\n };\n const looksLikeHandle = (element) => {\n const rect = element.getBoundingClientRect();\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return (\n role === "slider" ||\n (/handle|handler|button|thumb|slider-btn|dot|point|circle|knob/i.test(classes) &&\n rect.width <= 80 &&\n rect.height <= 80)\n );\n };\n const readRatio = (dot, track) => {\n const styleLeft = dot.style.left;\n if (styleLeft.endsWith("%")) {\n const parsed = Number.parseFloat(styleLeft);\n if (Number.isFinite(parsed)) return Math.max(0, Math.min(1, parsed / 100));\n }\n const dotRect = dot.getBoundingClientRect();\n const trackRect = track.getBoundingClientRect();\n if (trackRect.width <= 0) return undefined;\n return Math.max(\n 0,\n Math.min(1, (dotRect.left + dotRect.width / 2 - trackRect.left) / trackRect.width)\n );\n };\n const ratioToAge = (ratio) =>\n Math.round(\n ${JSON.stringify(Wn)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(Kn)} -\n ${JSON.stringify(Wn)})\n );\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return {};\n const row = Array.from(panel.querySelectorAll("div, li, section, dl, dd"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return (\n visible(element) &&\n text.includes("年龄") &&\n (/\\d+|不限/.test(text) ||\n Array.from(element.querySelectorAll("*")).some(\n (child) => looksLikeSlider(child) || looksLikeHandle(child),\n ))\n );\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return {};\n const dots = Array.from(row.querySelectorAll(".vue-slider-dot"))\n .filter(visible)\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const track = preferRailTrack(\n Array.from(row.querySelectorAll(".vue-slider-rail, .vue-slider")).filter(visible)\n )[0];\n if (dots.length >= 2) {\n const minDot = dots[0];\n const maxDot = dots[dots.length - 1];\n const minText = normalize(minDot?.querySelector(".vue-slider-dot-tooltip-text")?.textContent);\n const maxText = normalize(maxDot?.querySelector(".vue-slider-dot-tooltip-text")?.textContent);\n const rowText = normalize(row.textContent);\n const rowState = parseAgeState(rowText);\n const minRatio = track && minDot ? readRatio(minDot, track) : undefined;\n const maxRatio = track && maxDot ? readRatio(maxDot, track) : undefined;\n const ageMin =\n parseAgeValue(minText) ??\n rowState.ageMin ??\n (minRatio !== undefined ? ratioToAge(minRatio) : undefined);\n let ageMax = parseAgeValue(maxText) ?? rowState.ageMax;\n if (\n ageMax === undefined &&\n !maxText.includes("不限") &&\n !rowText.includes("不限") &&\n maxRatio !== undefined\n ) {\n ageMax = ratioToAge(maxRatio);\n }\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {}),\n ...(minRatio !== undefined ? { minRatio } : {}),\n ...(maxRatio !== undefined ? { maxRatio } : {})\n };\n }\n return parseAgeState(normalize(row.textContent));\n })()`,t=wa(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:wa(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Mn.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const parseAgeState = (text) => {\n const normalized = normalize(text);\n const ageText = normalized.includes("年龄")\n ? normalized.slice(normalized.indexOf("年龄") + "年龄".length)\n : normalized;\n const numbers = Array.from(ageText.matchAll(/\\d+/g), (match) =>\n Number.parseInt(match[0], 10)\n ).filter((value) => Number.isInteger(value));\n const ageMin = numbers[0];\n const ageMax = ageText.includes("不限") ? undefined : numbers[1];\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {})\n };\n };\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const preferRailTrack = (elements) =>\n [...elements].sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const looksLikeSlider = (element) => {\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return /slider|range|track|bar/i.test(classes) || role === "slider";\n };\n const looksLikeHandle = (element) => {\n const rect = element.getBoundingClientRect();\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return (\n role === "slider" ||\n (/handle|handler|button|thumb|slider-btn|dot|point|circle|knob/i.test(classes) &&\n rect.width <= 80 &&\n rect.height <= 80)\n );\n };\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return { ok: false, error: "未找到筛选面板" };\n const row = Array.from(panel.querySelectorAll("div, li, section, dl, dd"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return (\n visible(element) &&\n text.includes("年龄") &&\n Array.from(element.querySelectorAll("*")).some(\n (child) => looksLikeSlider(child) || looksLikeHandle(child)\n )\n );\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return { ok: false, error: "未找到年龄滑块" };\n\n const vueSliderDots = Array.from(row.querySelectorAll(".vue-slider-dot"))\n .filter(visible)\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const fallbackHandles = Array.from(row.querySelectorAll("*"))\n .filter((element) => visible(element) && looksLikeHandle(element))\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const handles = vueSliderDots.length >= 2 ? vueSliderDots : fallbackHandles;\n if (handles.length < 2) {\n return { ok: false, error: "未找到年龄滑块双手柄" };\n }\n\n const minHandle = handles[0];\n const maxHandle = handles[handles.length - 1];\n const minRect = minHandle.getBoundingClientRect();\n const maxRect = maxHandle.getBoundingClientRect();\n const minDistance = Math.max(40, maxRect.left - minRect.left);\n const tracks = [\n ...preferRailTrack(Array.from(row.querySelectorAll(".vue-slider-rail, .vue-slider"))),\n ...Array.from(row.querySelectorAll("*")).filter(looksLikeSlider)\n ]\n .filter((element, index, array) => array.indexOf(element) === index)\n .filter((element) => {\n if (!visible(element)) return false;\n const rect = element.getBoundingClientRect();\n return (\n rect.width >= Math.max(80, minDistance) &&\n rect.height <= 100 &&\n rect.left <= minRect.left + minRect.width &&\n rect.right >= maxRect.right - maxRect.width\n );\n })\n .sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const track = tracks[0];\n if (!track) return { ok: false, error: "未找到年龄滑块轨道" };\n const trackRect = track.getBoundingClientRect();\n return {\n ok: true,\n current: parseAgeState(normalize(row.textContent)),\n trackLeft: trackRect.left,\n trackTop: trackRect.top,\n trackWidth: trackRect.width,\n trackHeight: trackRect.height,\n minHandleX: Math.round(minRect.left + minRect.width / 2),\n minHandleY: Math.round(minRect.top + minRect.height / 2),\n maxHandleX: Math.round(maxRect.left + maxRect.width / 2),\n maxHandleY: Math.round(maxRect.top + maxRect.height / 2)\n };\n })()`,t=va(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=va(await this.evaluateRecommendFrameJson(e));if(!n.ok)return n;const a=await this.readRecommendFrameOffset();return a.found?{...n,trackLeft:n.trackLeft+a.left,trackTop:n.trackTop+a.top,minHandleX:n.minHandleX+a.left,minHandleY:n.minHandleY+a.top,maxHandleX:n.maxHandleX+a.left,maxHandleY:n.maxHandleY+a.top}:n}async dispatchNativeDrag(e,t,n,a,r={}){await(r.onTargetResolved?.({found:!0,x:Math.round(e),y:Math.round(t)})),await this.controller.dispatchMouseEvent({type:"mouseMoved",x:Math.round(e),y:Math.round(t),buttons:0}),await this.controller.dispatchMouseEvent({type:"mousePressed",x:Math.round(e),y:Math.round(t),button:"left",buttons:1,clickCount:1}),await Qn(r.pressDurationMs??Un);for(let r=1;r<=18;r+=1){const o=r/18;await this.controller.dispatchMouseEvent({type:"mouseMoved",x:Math.round(e+(n-e)*o),y:Math.round(t+(a-t)*o),button:"left",buttons:1}),await Qn(24)}await this.controller.dispatchMouseEvent({type:"mouseReleased",x:Math.round(n),y:Math.round(a),button:"left",buttons:0,clickCount:1})}async dragAgeHandleToRatio(e,t,n){const a=await this.resolveNativeAgeSlider();if(!a.ok||a.trackWidth<=0)return!1;const r="min"===e?a.minHandleX:a.maxHandleX,o="min"===e?a.minHandleY:a.maxHandleY,i=a.trackLeft+Math.max(0,Math.min(1,t))*a.trackWidth,s=a.trackTop+Math.max(1,a.trackHeight/2);return await this.dispatchNativeDrag(r,o,i,s,n),await Qn(Yn),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-Wn)/(Kn-Wn)))}clampRatio(e,t,n){return Math.max(t,Math.min(n,e))}async setAgeHandleToNumber(e,t,n){const a=await this.readNativeAgeState(),r=a.minRatio??0,o=a.maxRatio??1;let i="min"===e?0:Math.min(1,r+Xn),s="min"===e?Math.max(0,o-Xn):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let a=0;a<Gn;a+=1){if(!await this.dragAgeHandleToRatio(e,c,n))return!1;const a=await this.readNativeAgeState(),r="min"===e?a.ageMin:a.ageMax;if(r===t)return!0;void 0===r?"max"===e?s=c:i=c:r<t?i=c:s=c;const o=(i+s)/2;if(Math.abs(o-c)<.001)break;c=this.clampRatio(o,i,s)}const l=await this.readNativeAgeState();return"min"===e?l.ageMin===t:l.ageMax===t}isDesiredAgeState(e,t,n){return e.ageMin===t&&e.ageMax===n}async setRecommendAgeRange(e,t){const n=e.ageMin??Wn,a=e.ageMax,r=await this.resolveNativeAgeSlider();if(!r.ok)return{success:!1,error:r.error};if(!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法重置为不限"};if(!await this.setAgeHandleToNumber("min",n,t))return{success:!1,error:`年龄下限无法设置为 ${n}`};if(void 0===a){if(void 0!==(await this.readNativeAgeState()).ageMax&&!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法设置为不限"}}else if(!await this.setAgeHandleToNumber("max",a,t))return{success:!1,error:`年龄上限无法设置为 ${a}`};const o=await this.readNativeAgeState();if(!this.isDesiredAgeState(o,n,a)){const e=void 0===o.ageMax?"不限":String(o.ageMax);return{success:!1,error:`年龄筛选未精确生效,当前为 ${o.ageMin??"未知"}-${e}`}}return{success:!0,state:o}}async readSelectedRecommendOptionText(e,t){const n=`(() => {\n const panelSelector = ${JSON.stringify(Mn.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Zn)};\n const selectedClassPattern = /active|selected|checked|current|choose|chosen/i;\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const isSelected = (element) => {\n const classes = typeof element.className === "string" ? element.className : "";\n return (\n selectedClassPattern.test(classes) ||\n element.getAttribute("aria-checked") === "true" ||\n element.getAttribute("aria-selected") === "true"\n );\n };\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return fallback;\n const row = Array.from(panel.querySelectorAll("div, li, dl, dd, section, ul"))\n .filter((element) => visible(element) && normalize(element.textContent).includes(rowLabel))\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return fallback;\n const selected = Array.from(row.querySelectorAll(clickableSelector))\n .filter((element) => visible(element) && isSelected(element))\n .map((element) => normalize(element.textContent))\n .find((text) => text !== "" && text !== rowLabel);\n return selected ?? fallback;\n })()`,a=await this.evaluateJson(n).catch(()=>"");if(a.length>0&&a!==t)return a;const r=await this.evaluateRecommendFrameJson(n);return"string"==typeof r&&r.length>0?r:t}async readNativeAppliedFilterState(e,t){return{...void 0!==t.ageMin?{ageMin:t.ageMin}:{},...void 0!==t.ageMax?{ageMax:t.ageMax}:{},gender:await this.readSelectedRecommendOptionText("性别",e.gender),activity:await this.readSelectedRecommendOptionText("活跃度",e.activity)}}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Mn.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const panel = Array.from(document.querySelectorAll(panelSelector)).filter(visible)[0];\n if (!panel) return { found: false, x: 0, y: 0 };\n const button = Array.from(\n panel.querySelectorAll("button, a, span, div, [role='button']")\n )\n .filter(visible)\n .find((element) => normalize(element.textContent) === "确定");\n if (!button) return { found: false, x: 0, y: 0 };\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);if(!await this.dispatchNativeClick(t,e))return!1;const n=Date.now();for(;Date.now()-n<4e3;){if(!await this.isRecommendFilterPanelVisible())return await Qn(600),!0;await Qn(On)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Mn.recommend.filterButton)});\n const text = element?.textContent?.replace(/\\s+/g, " ").trim() ?? "";\n return text.length > 0 ? text : null;\n })()`,t=await this.evaluateJson(e).catch(()=>null);if("string"==typeof t&&t.length>0)return t;const n=await this.evaluateRecommendFrameJson(e);return"string"==typeof n&&n.length>0?n:void 0}async readVisibleChatCandidates(){return ca(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(En)}));\n return items.map((item, idx) => {\n const conversationId =\n item.getAttribute("data-id") ??\n item.closest('[role="listitem"]')?.getAttribute("key") ??\n "";\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n const nameEl = item.querySelector(\n '[class*="name"], .nickname, .geek-name, .candidate-name'\n );\n const name = nameEl?.textContent?.trim() ?? "";\n const position = item.querySelector(".source-job")?.textContent?.trim() ?? "";\n const badgeEl = item.querySelector(".badge-count");\n const unreadCount = parseInt(badgeEl?.textContent?.trim() ?? "0", 10) || 0;\n const hasUnread = unreadCount > 0 || item.querySelector(".red-dot") !== null;\n const lastMessageTime =\n item.querySelector(".time, .time-shadow")?.textContent?.trim() ?? "";\n const messagePreview = (\n item.querySelector(".push-text, .chat-last-msg")?.textContent?.trim() ?? ""\n ).slice(0, 100);\n\n return {\n conversationId,\n candidateId,\n name,\n index: idx,\n position,\n hasUnread,\n unreadCount,\n lastMessageTime,\n messagePreview,\n };\n });\n })()`))}async waitForNativeChatReady(e,t=6e3){const n=Date.now();for(;Date.now()-n<t;){const t=await this.readSelectedChatTarget().catch(()=>null),n=await this.readActiveChatPanel().catch(()=>null),a=null!==t&&(e.conversationId.length>0&&t.conversationId===e.conversationId||0===e.conversationId.length&&e.candidateId.length>0&&t.candidateId===e.candidateId),r=0===e.name.length||null!==n&&ra(e.name,n.candidateName);if(a&&r)return!0;await Qn(On)}return!1}async clickChatCandidate(e){const t=pa(await this.evaluateJson(`(() => {\n const expected = ${JSON.stringify({conversationId:e.conversationId,candidateId:e.candidateId,index:e.index})};\n const items = Array.from(document.querySelectorAll(${JSON.stringify(En)}));\n const readCandidate = (item, idx) => {\n const conversationId =\n item.getAttribute("data-id") ??\n item.closest('[role="listitem"]')?.getAttribute("key") ??\n "";\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n return { item, idx, conversationId, candidateId };\n };\n\n const candidates = items.map(readCandidate);\n const matched =\n candidates.find((entry) =>\n expected.conversationId.length > 0 &&\n entry.conversationId === expected.conversationId\n ) ??\n candidates.find((entry) =>\n expected.candidateId.length > 0 &&\n entry.candidateId === expected.candidateId\n ) ??\n candidates.find((entry) => entry.idx === expected.index);\n\n if (!matched) {\n return { found: false, x: 0, y: 0 };\n }\n\n matched.item.scrollIntoView({ block: "center", inline: "center" });\n const rect = matched.item.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) {\n return { found: false, x: 0, y: 0 };\n }\n\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`));return!!t.found&&(await this.controller.dispatchMouseEvent({type:"mouseMoved",x:t.x,y:t.y,buttons:0}),await this.controller.dispatchMouseEvent({type:"mousePressed",x:t.x,y:t.y,button:"left",buttons:1,clickCount:1}),await this.controller.dispatchMouseEvent({type:"mouseReleased",x:t.x,y:t.y,button:"left",buttons:0,clickCount:1}),!0)}async readVisibleRecommendCandidates(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return[];const e=`(() => {\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify($n)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (!hasRecommendUrl && href.includes("/web/chat/index")) {\n return [];\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (!hasRecommendUrl && !iframe && !hasRecommendShell) {\n return [];\n }\n\n const root = iframe?.contentDocument ?? document;\n let items = Array.from(root.querySelectorAll(${JSON.stringify(Fn)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(qn)}));\n }\n\n return items.map((item, idx) => {\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n "";\n const name = item.querySelector(".name")?.textContent?.trim() ?? "";\n\n let age = "";\n let experience = "";\n let education = "";\n let workStatus = "";\n const baseInfoEl = item.querySelector(".base-info.join-text-wrap, .base-info");\n if (baseInfoEl) {\n let textParts = Array.from(baseInfoEl.querySelectorAll(":scope > *"))\n .map((child) => child.textContent?.trim() ?? "")\n .filter(Boolean);\n\n if (textParts.length <= 1) {\n textParts = [];\n baseInfoEl.childNodes.forEach((node) => {\n if (node.nodeType === Node.TEXT_NODE) {\n const text = node.textContent?.trim() ?? "";\n if (text) textParts.push(text);\n }\n });\n }\n\n if (textParts.length <= 1) {\n textParts = (baseInfoEl.textContent?.trim() ?? "")\n .split(/[丨·|]/)\n .map((text) => text.trim())\n .filter(Boolean);\n }\n\n for (const part of textParts) {\n if (!age && part.includes("岁")) {\n age = part;\n } else if (!experience && (part.includes("年") || part.includes("应届") || part.includes("在校"))) {\n experience = part;\n } else if (!education && /(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(part)) {\n education = part;\n } else if (!workStatus && /(在职|离职|在校)/.test(part)) {\n workStatus = part;\n }\n }\n }\n\n const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = (workExpEl?.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\n\n let expectedLocation = "";\n let expectedPosition = "";\n const expectRow = item.querySelector(".row-flex:not(.geek-desc)");\n if (expectRow) {\n const labelText = expectRow.querySelector(".label")?.textContent ?? "";\n const contentEl = expectRow.querySelector(".content");\n if ((labelText.includes("期望") || labelText.includes("最近关注")) && contentEl) {\n const parts = (contentEl.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n if (!expectedLocation) {\n const expectEl =\n item.querySelector(".timeline-wrap.expect .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.expect .content");\n if (expectEl) {\n const parts = (expectEl.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n\n const tags = Array.from(\n item.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span")\n )\n .map((tag) => tag.textContent?.trim() ?? "")\n .filter(Boolean);\n\n return {\n index: idx,\n candidateId,\n name,\n age,\n experience,\n education,\n workStatus,\n company,\n currentPosition,\n expectedLocation,\n expectedPosition,\n expectedSalary: item.querySelector(".salary-wrap")?.textContent?.trim() ?? "",\n tags,\n buttonText: item.querySelector("button.btn.btn-greet")?.textContent?.trim() ?? "",\n };\n });\n })()`,t=da(await this.evaluateJson(e));if(t.length>0)return t;return da(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=kn(e),n=`(() => {\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(t.containerSelectors)};\n const itemSelector = ${JSON.stringify(t.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify($n)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (surface === "recommend-list" && !hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (surface === "recommend-list" && !hasRecommendUrl && !iframe && !hasRecommendShell) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const readItemCount = () => {\n if (surface === "chat-list") {\n const chatList = root.querySelector(".user-list.b-scroll-stable");\n const roleItems = chatList?.querySelectorAll('[role="listitem"]');\n if (roleItems && roleItems.length > 0) return roleItems.length;\n }\n return root.querySelectorAll(itemSelector).length;\n };\n const itemCount = readItemCount();\n const isVisible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n const isScrollable = (element) => {\n if (!isVisible(element)) return false;\n const view = element.ownerDocument?.defaultView ?? window;\n const style = view.getComputedStyle(element);\n if (style.overflowY === "hidden" || style.overflowY === "clip") return false;\n return element.scrollHeight > element.clientHeight + 2;\n };\n const labelFor = (element, fallback) => {\n if (element === document.scrollingElement) return "document";\n if (element === root.scrollingElement) return root === document ? "document" : "frame-document";\n return (\n element.id ||\n Array.from(element.classList ?? []).join(".") ||\n fallback ||\n element.tagName.toLowerCase()\n );\n };\n const targets = [];\n const pushTarget = (element, fallback) => {\n if (element && !isVisible(element)) return;\n if (!element || targets.some((target) => target.element === element)) return;\n targets.push({ element, label: labelFor(element, fallback) });\n };\n const readCssPixel = (value) => {\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) ? parsed : 0;\n };\n const findChatListElement = (element) => {\n if (surface !== "chat-list") return null;\n if (element.matches?.(".user-list.b-scroll-stable")) return element;\n return (\n element.querySelector?.(".user-list.b-scroll-stable") ??\n element.closest?.(".user-list.b-scroll-stable") ??\n null\n );\n };\n const readChatListSnapshot = (target) => {\n const list = findChatListElement(target.element);\n if (!list || itemCount <= 0 || !isVisible(list)) return null;\n\n const nativeScrollTop = Math.max(0, list.scrollTop);\n const nativeScrollHeight = Math.max(0, list.scrollHeight);\n const nativeClientHeight = Math.max(0, list.clientHeight);\n const nativeMaxScrollTop = Math.max(0, nativeScrollHeight - nativeClientHeight);\n if (nativeScrollHeight > nativeClientHeight + 2) {\n return {\n containerFound: true,\n containerLabel: labelFor(list, ".user-list.b-scroll-stable"),\n scrollTop: Math.round(nativeScrollTop),\n scrollHeight: Math.round(nativeScrollHeight),\n clientHeight: Math.round(nativeClientHeight),\n itemCount,\n atStart: nativeScrollTop <= 1,\n atEnd: nativeScrollTop >= nativeMaxScrollTop - 1\n };\n }\n\n const listRect = list.getBoundingClientRect();\n if (listRect.width <= 0 || listRect.height <= 0) return null;\n const group = list.querySelector('[role="group"]');\n if (!group) return null;\n\n const view = list.ownerDocument?.defaultView ?? window;\n const groupStyle = view.getComputedStyle(group);\n const paddingTop = Math.max(0, readCssPixel(groupStyle.paddingTop));\n const paddingBottom = Math.max(0, readCssPixel(groupStyle.paddingBottom));\n const roleItems = Array.from(list.querySelectorAll('[role="listitem"]'));\n const renderedItems =\n roleItems.length > 0 ? roleItems : Array.from(list.querySelectorAll(".geek-item"));\n const renderedHeight = renderedItems.reduce((sum, item) => {\n const rect = item.getBoundingClientRect();\n return sum + Math.max(0, rect.height);\n }, 0);\n const virtualHeight = Math.max(\n Math.round(paddingTop + renderedHeight + paddingBottom),\n Math.round(listRect.height)\n );\n return {\n containerFound: true,\n containerLabel: labelFor(list, ".user-list.b-scroll-stable") + ":virtual",\n scrollTop: Math.round(paddingTop),\n scrollHeight: virtualHeight,\n clientHeight: Math.round(listRect.height),\n itemCount,\n atStart: paddingTop <= 1,\n atEnd: paddingBottom <= 1\n };\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element, selector);\n }\n } catch {}\n }\n\n const firstVisibleItem = Array.from(root.querySelectorAll(itemSelector)).find(isVisible);\n let current = firstVisibleItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current, "item-ancestor");\n current = current.parentElement;\n }\n\n if (surface === "recommend-list" && root !== document) {\n const outerFrame = iframe ?? window.frameElement;\n let outer = outerFrame?.parentElement ?? null;\n while (outer && outer !== document.body && outer !== document.documentElement) {\n pushTarget(outer, "iframe-ancestor");\n outer = outer.parentElement;\n }\n pushTarget(document.scrollingElement, "document");\n pushTarget(document.documentElement, "documentElement");\n pushTarget(document.body, "body");\n }\n pushTarget(root.scrollingElement, root === document ? "document" : "frame-document");\n\n for (const target of targets) {\n const chatListSnapshot = readChatListSnapshot(target);\n if (chatListSnapshot) return chatListSnapshot;\n }\n\n const scrollTarget = targets.find((target) => isScrollable(target.element));\n if (!scrollTarget) {\n return {\n containerFound: false,\n containerLabel: targets[0]?.label ?? "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount,\n atStart: true,\n atEnd: true\n };\n }\n\n const scrollable = scrollTarget.element;\n const max = Math.max(0, scrollable.scrollHeight - scrollable.clientHeight);\n return {\n containerFound: true,\n containerLabel: scrollTarget.label,\n scrollTop: scrollable.scrollTop,\n scrollHeight: scrollable.scrollHeight,\n clientHeight: scrollable.clientHeight,\n itemCount,\n atStart: scrollable.scrollTop <= 1,\n atEnd: scrollable.scrollTop >= max - 1\n };\n })()`,a=xa(await this.evaluateJson(n));if("recommend-list"!==e||a.itemCount>0)return a;const r=xa(await this.evaluateRecommendFrameJson(n));return r.itemCount>0?r:a}async scrollSurfaceWithWheel(e,t,n){const a=kn(e);await this.bringToFront();const r=pa(await this.evaluateJson(`(() => {\n const nativeWheelTarget = true;\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(a.containerSelectors)};\n const itemSelector = ${JSON.stringify(a.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;\n const targets = [];\n const pushTarget = (element) => {\n if (!element || targets.includes(element)) return;\n targets.push(element);\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element);\n }\n } catch {}\n }\n\n const firstItem = Array.from(root.querySelectorAll(itemSelector)).find((element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n });\n pushTarget(firstItem);\n let current = firstItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current);\n current = current.parentElement;\n }\n\n const readPoint = (element) => {\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return null;\n let left = rect.left;\n let top = rect.top;\n if (root !== document && iframe) {\n const frameRect = iframe.getBoundingClientRect();\n left += frameRect.left;\n top += frameRect.top;\n }\n const x = Math.round(Math.min(Math.max(left + rect.width / 2, 4), Math.max(4, viewportWidth - 4)));\n const y = Math.round(Math.min(Math.max(top + rect.height / 2, 4), Math.max(4, viewportHeight - 4)));\n return { found: true, x, y };\n };\n\n for (const element of targets) {\n const point = readPoint(element);\n if (point) return point;\n }\n\n return { found: false, x: 0, y: 0 };\n })()`));if(!r.found)return;await this.controller.dispatchMouseEvent({type:"mouseMoved",x:r.x,y:r.y,buttons:0}),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:r.x,y:r.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??Vn):n??Vn}),await Qn(120);const o=await this.inspectSurface(e);return o.containerFound?o:{...o,containerFound:!0,containerLabel:o.containerLabel.length>0?o.containerLabel:"native-wheel",atStart:"up"!==t&&o.atStart,atEnd:"down"!==t&&o.atEnd}}async scrollSurfaceOnce(e,t,n){const a=kn(e),r=`(() => {\n const surface = ${JSON.stringify(e)};\n const direction = ${JSON.stringify(t)};\n const explicitDistance = ${void 0!==n?JSON.stringify(n):"undefined"};\n const containerSelectors = ${JSON.stringify(a.containerSelectors)};\n const itemSelector = ${JSON.stringify(a.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(Mn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify($n)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (surface === "recommend-list" && !hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (surface === "recommend-list" && !hasRecommendUrl && !iframe && !hasRecommendShell) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const itemCount = root.querySelectorAll(itemSelector).length;\n const isVisible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const isScrollable = (element) => {\n if (!isVisible(element)) return false;\n const view = element.ownerDocument?.defaultView ?? window;\n const style = view.getComputedStyle(element);\n if (style.overflowY === "hidden" || style.overflowY === "clip") return false;\n return element.scrollHeight > element.clientHeight + 2;\n };\n const labelFor = (element, fallback) => {\n if (element === document.scrollingElement) return "document";\n if (element === root.scrollingElement) return root === document ? "document" : "frame-document";\n return (\n element.id ||\n Array.from(element.classList ?? []).join(".") ||\n fallback ||\n element.tagName.toLowerCase()\n );\n };\n const targets = [];\n const pushTarget = (element, fallback) => {\n if (element && !isVisible(element)) return;\n if (!element || targets.some((target) => target.element === element)) return;\n targets.push({ element, label: labelFor(element, fallback) });\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element, selector);\n }\n } catch {}\n }\n\n const firstVisibleItem = Array.from(root.querySelectorAll(itemSelector)).find(isVisible);\n let current = firstVisibleItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current, "item-ancestor");\n current = current.parentElement;\n }\n\n if (surface === "recommend-list" && root !== document) {\n const outerFrame = iframe ?? window.frameElement;\n let outer = outerFrame?.parentElement ?? null;\n while (outer && outer !== document.body && outer !== document.documentElement) {\n pushTarget(outer, "iframe-ancestor");\n outer = outer.parentElement;\n }\n pushTarget(document.scrollingElement, "document");\n pushTarget(document.documentElement, "documentElement");\n pushTarget(document.body, "body");\n }\n pushTarget(root.scrollingElement, root === document ? "document" : "frame-document");\n\n const scrollTarget = targets.find((target) => isScrollable(target.element));\n if (!scrollTarget) {\n return {\n containerFound: false,\n containerLabel: targets[0]?.label ?? "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount,\n atStart: true,\n atEnd: true\n };\n }\n\n const scrollable = scrollTarget.element;\n const max = Math.max(0, scrollable.scrollHeight - scrollable.clientHeight);\n const amount = explicitDistance ?? Math.max(320, Math.floor(scrollable.clientHeight * 0.85));\n scrollable.scrollTop =\n direction === "up"\n ? Math.max(0, scrollable.scrollTop - amount)\n : Math.min(max, scrollable.scrollTop + amount);\n\n return {\n containerFound: true,\n containerLabel: scrollTarget.label,\n scrollTop: scrollable.scrollTop,\n scrollHeight: scrollable.scrollHeight,\n clientHeight: scrollable.clientHeight,\n itemCount,\n atStart: scrollable.scrollTop <= 1,\n atEnd: scrollable.scrollTop >= max - 1\n };\n })()`,o=xa(await this.evaluateJson(r));if("recommend-list"!==e||o.itemCount>0)return o;const i=xa(await this.evaluateRecommendFrameJson(r));return i.itemCount>0?i:o}async scrollChatList(){const e=await this.scrollSurface("chat-list",{direction:"down",steps:1,settleMs:0});return{ok:e.success&&e.stepsCompleted>0,before:e.before.scrollTop,after:e.after.scrollTop,max:Math.max(0,e.after.scrollHeight-e.after.clientHeight)}}},Aa=mn.object({name:mn.string(),conversationId:mn.string(),candidateId:mn.string(),position:mn.string(),time:mn.string(),preview:mn.string(),unreadCount:mn.number(),hasUnread:mn.boolean(),index:mn.number()}),Na=mn.object({success:mn.boolean(),candidates:mn.array(Aa),total:mn.number(),stats:mn.object({withName:mn.number(),withUnread:mn.number()})});function Ta(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Ia}}function Ea(e){return e.conversationId.length>0?e.conversationId:e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.position,e.lastMessageTime].join("|"):void 0}function Ba(e){const t=new Set,n=[];for(const a of e){const e=Ea(a);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(a))}return n}var Fa=un({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:mn.object({limit:mn.number().optional().describe("最多返回条数"),onlyUnread:mn.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:mn.enum(["time","unreadCount","name"]).default("time"),autoScroll:mn.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:mn.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Na,execute:async(e,t)=>{const n=e.onlyUnread??!1;let a,r;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const o=Ta();try{a=await o.openNativePagePort({requireChatPage:!0}),r=o.createNativeVisualActivitySession(a),await r.begin("正在读取消息列表");if(!await a.waitForSelector('.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',5e3))return await r.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};const i=n?"正在读取未读消息列表":"正在读取消息列表";await r.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:i,padding:8});const s=e.autoScroll??!0,c=e.maxScrolls??4,l=Ba(await a.readChatCandidates({autoScroll:s,maxScrolls:c,...void 0===e.limit||n?{}:{targetCount:e.limit}})).map(e=>({name:e.name,conversationId:e.conversationId,candidateId:e.candidateId,position:e.position,time:e.lastMessageTime,preview:e.messagePreview,unreadCount:e.unreadCount,hasUnread:e.hasUnread,index:e.index}));let d=n?l.filter(e=>e.hasUnread):l;const u=e.sortBy??"time";"time"===u||("unreadCount"===u?d.sort((e,t)=>t.unreadCount-e.unreadCount):"name"===u&&d.sort((e,t)=>e.name.localeCompare(t.name))),void 0!==e.limit&&(d=d.slice(0,e.limit));const m={withName:l.filter(e=>e.name.length>0).length,withUnread:l.filter(e=>e.hasUnread).length};return await r.succeed(n?`已读取 ${d.length} 条未读消息`:`已读取 ${d.length} 条消息`),t.logger.info(`Found ${d.length} candidates (${m.withUnread} with unread)`),{success:!0,candidates:d,total:l.length,stats:m}}catch(e){return await(r?.fail("读取消息列表失败")),t.logger.warn(`Native zhipin message read failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}}}finally{a?.close()}}});import{BrowserPageInfoSchema as qa}from"@roll-agent/browser";import{defineTool as La}from"@roll-agent/sdk";import{z as $a}from"zod";var Oa,_a=$a.object({success:$a.boolean(),alreadyOnChat:$a.boolean(),usedSidebarClick:$a.boolean(),chatReady:$a.boolean(),page:qa.optional(),error:$a.string().optional()});function Da(){return{getContextManager:p,openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Oa}}async function za(e,t){return le(e,await t.inspectPage())}var ja=La({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测。",input:$a.object({}),output:_a,execute:async(e,t)=>{const n=Da(),a=n.getContextManager();let r,o;t.logger.info("Opening Boss chat page via native sidebar navigation");try{r=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(r),await r.bringToFront().catch(()=>{});const e="正在切换到沟通页";if(await o.begin(e),await o.highlightSelector(Mn.nav.sidebar,{label:e,padding:10}),await r.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,chatReady:!0,page:await za(a,r)};if(!await r.clickSidebarSection("chat",{onTargetResolved:async e=>{await(o?.highlightPoint?.(e.x,e.y))}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1,page:await za(a,r),error:"未找到沟通导航"};t.logger.info("Clicked Boss sidebar nav: 沟通");return await r.waitForChatSurface()?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!0,page:await za(a,r)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1,page:await za(a,r),error:"沟通页未就绪"})}catch(e){return await(o?.fail("切换沟通页失败")),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1,error:e instanceof Error?e.message:"切换沟通页失败"}}finally{r?.close()}}});import{defineTool as Ha}from"@roll-agent/sdk";import{z as Ja}from"zod";var Ua,Va=Ja.object({success:Ja.boolean(),conversationId:Ja.string(),candidateId:Ja.string(),candidateName:Ja.string(),index:Ja.number(),hasUnread:Ja.boolean(),unreadCount:Ja.number(),lastMessageTime:Ja.string(),messagePreview:Ja.string(),error:Ja.string().optional()});function Wa(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Ua}}var Ka=Ha({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Ja.object({conversationId:Ja.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Ja.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Ja.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Ja.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Va,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Wa();let a,r;try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await r.begin("正在打开目标聊天"),await r.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:"正在定位候选人",padding:8});const o=await a.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,preferUnread:e.preferUnread??!1});return o.found?(await r.succeed(`已打开 ${o.name||"目标"} 的聊天`),t.logger.info(`Opened chat with ${o.name} (index: ${o.index})`),{success:!0,conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.name,index:o.index,hasUnread:o.hasUnread,unreadCount:o.unreadCount,lastMessageTime:o.lastMessageTime,messagePreview:o.messagePreview}):(await r.fail("打开聊天失败"),{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:o.error??`未找到候选人: ${e.candidateName??`index ${e.index}`}`})}catch(n){return await(r?.fail("打开聊天失败")),t.logger.warn(`Native zhipin open chat failed: ${n instanceof Error?n.message:String(n)}`),{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:n instanceof Error?n.message:"打开聊天失败"}}finally{a?.close()}}});import{defineTool as Ya}from"@roll-agent/sdk";import{z as Ga}from"zod";var Xa="·",Za=/[--—–]/;function Qa(e){return(e??"").trim()}function er(e){const t=Qa(e);if(!t||!Za.test(t))return;const[n=""]=t.split(Za);return Qa(n)||void 0}function tr(e){const t=Qa(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",a=""]=t.split(Xa).map(e=>Qa(e));return{expectedLocation:n,expectedPosition:a}}function nr(e){const t=Qa(e.communicationPosition),{expectedLocation:n,expectedPosition:a}=tr(e.expectedJobText),r=er(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:a,...void 0!==r?{preferredBrand:r}:{}}}var ar,rr=Ga.object({index:Ga.number(),sender:Ga.enum(["candidate","recruiter","system"]),messageType:Ga.enum(["text","system","resume","wechat-exchange"]),content:Ga.string(),time:Ga.string()}),or=Ga.object({name:Ga.string(),age:Ga.string(),experience:Ga.string(),education:Ga.string(),communicationPosition:Ga.string(),expectedPosition:Ga.string(),expectedLocation:Ga.string(),expectedSalary:Ga.string(),tags:Ga.array(Ga.string())}),ir=Ga.object({success:Ga.boolean(),conversationId:Ga.string(),candidateId:Ga.string(),candidateInfo:or,preferredBrand:Ga.string().optional(),chatMessages:Ga.array(rr),formattedHistory:Ga.array(Ga.string()),stats:Ga.object({totalMessages:Ga.number(),candidateMessages:Ga.number(),recruiterMessages:Ga.number(),systemMessages:Ga.number()}),error:Ga.string().optional()});function sr(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function cr(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:sr(),chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function lr(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}function dr(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...ar}}var ur=Ya({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Ga.object({conversationId:Ga.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Ga.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Ga.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Ga.number().default(100).describe("最多返回的消息条数")}),output:ir,execute:async(e,t)=>{const n=e.maxMessages??100,a=dr(),r=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i;const s=r?"正在打开目标聊天":"正在准备当前聊天",c="正在提取聊天记录",l=async(e,t)=>(await(i?.fail(e)),cr(t));try{o=await a.openNativePagePort(),i=a.createNativeVisualActivitySession(o),await i.begin(s);const d=r?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==d&&!d.found)return await l("打开聊天失败",d.error??"打开聊天失败");if(void 0===d&&!await o.isChatSurfaceOpen().catch(()=>!1))return await l("当前不是沟通页","当前页面不是 BOSS 沟通页,无法读取当前聊天详情");t.logger.info("Extracting candidate info"+(void 0!==d?` for ${d.name}`:" (current window)")),await i.begin(c),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:c,padding:12});const u=d?.name??e.candidateName??"",m=await o.readActiveChatPanel();if(u.length>0&&(!m||!lr(u,m.candidateName)))return await l("聊天面板未同步",`右侧聊天面板未切换到 ${u}`);const g=await o.readSelectedChatTarget();if(!g)return await l("未识别当前会话","未能提取当前选中聊天的 conversationId/candidateId");if(!(void 0===d||d.conversationId.length>0&&g.conversationId===d.conversationId||0===d.conversationId.length&&d.candidateId.length>0&&g.candidateId===d.candidateId))return await l("当前会话不一致",`当前选中会话与目标会话不一致: ${d?.name||d?.conversationId||""}`);if(m&&g.candidateName.length>0&&!lr(g.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${g.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const h=await o.readCandidateChatDetails(n),f=nr({communicationPosition:h.candidateInfo.communicationPosition,expectedJobText:h.candidateInfo.expectedJobText}),p=h.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),y={totalMessages:h.messages.length,candidateMessages:h.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:h.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:h.messages.filter(e=>"system"===e.sender).length};return await i.succeed(`已提取 ${y.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${h.candidateInfo.name}: ${y.totalMessages} messages`),{success:!0,conversationId:g.conversationId,candidateId:g.candidateId,candidateInfo:{name:h.candidateInfo.name,age:h.candidateInfo.age,experience:h.candidateInfo.experience,education:h.candidateInfo.education,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,expectedSalary:h.candidateInfo.expectedSalary,tags:[...h.candidateInfo.tags]},...void 0!==f.preferredBrand?{preferredBrand:f.preferredBrand}:{},chatMessages:[...h.messages],formattedHistory:p,stats:y}}catch(e){return await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),cr(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as mr}from"@roll-agent/sdk";import{z as gr}from"zod";function hr(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as fr}from"zod";var pr="reply-authority-service",yr="browser-use-agent/zhipin_send_reply",br="zhipin",wr=60,vr=fr.object({platform:fr.literal(br),username:fr.string().min(1),accountId:fr.string().min(1).optional()}),xr=fr.object({v:fr.literal(2),iss:fr.literal(pr),kid:fr.string().min(1),jti:fr.string().min(1),iat:fr.number().int(),exp:fr.number().int(),aud:fr.literal(yr),platform:fr.literal(br),tenantId:fr.string().min(1),conversationId:fr.string().min(1),candidateId:fr.string().min(1),reply:fr.string(),policyVersion:fr.string().min(1),recruiterBinding:vr}),Sr=fr.object({kid:fr.string().min(1),algorithm:fr.literal("Ed25519"),publicKey:fr.string().min(1),validFrom:fr.string().min(1),validUntil:fr.string().optional()}),Cr=fr.object({keys:fr.array(Sr)}),Rr=new Map;function kr(e){for(const[t,n]of Rr)n<e-wr&&Rr.delete(t)}function Mr(e,t=Math.floor(Date.now()/1e3)){return kr(t),Rr.has(e)}function Ir(e,t,n=Math.floor(Date.now()/1e3)){kr(n),Rr.set(e,t)}import{createPublicKey as Pr,verify as Ar}from"node:crypto";var Nr=new Map,Tr=null;function Er(){const e=process.env.REPLY_AUTHORITY_KEYS_URL?.trim();if(!e)throw new Error("REPLY_AUTHORITY_KEYS_URL 未配置,browser-use-agent 无法拉取 Reply Authority 公钥。");return e}function Br(e){return Nr=new Map(e.map(e=>[e.kid,e]))}async function Fr(){if(Tr)return Tr;Tr=(async()=>{const e=await fetch(Er()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Br(Cr.parse(t).keys)})();try{return await Tr}finally{Tr=null}}async function qr(){await Fr()}async function Lr(e){const t=Nr.get(e);if(t)return t;const n=(await Fr()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function $r(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Or(e){const t=e.split("."),n=t[0],a=t[1];if(2!==t.length||void 0===n||void 0===a||0===n.length||0===a.length)throw new Error("Invalid signed envelope format");let r,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{r=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}$r(r);const i=xr.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:a}}function _r(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-wr)throw new Error("Envelope expired");if(e.iat>t+wr)throw new Error("Envelope issued in the future")}async function Dr(e,t=Math.floor(Date.now()/1e3)){const n=Or(e),a=await Lr(n.payload.kid),r=Pr({key:Buffer.from(a.publicKey,"base64url"),format:"der",type:"spki"});if(!Ar(null,Buffer.from(n.payloadJson,"utf-8"),r,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return _r(n.payload,t),n.payload}var zr,jr=gr.object({success:gr.boolean(),sentMessage:gr.string(),error:gr.string().optional()});function Hr(){return{getReplyAuthorityKeysLoaded:w,openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...zr}}function Jr(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}var Ur=mr({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:gr.object({signedEnvelope:gr.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:gr.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:gr.number().optional().describe("候选人在列表中的索引(可选)")}),output:jr,execute:async(e,t)=>{const n=Hr();let a,r,o="";if(!n.getReplyAuthorityKeysLoaded()){const e="Reply Authority 公钥尚未成功预加载,当前无法发送签名回复。请检查启动日志、`REPLY_AUTHORITY_KEYS_URL` 配置,以及 `browser_status.replyAuthorityKeysLoaded`。";return t.logger.error(e),{success:!1,sentMessage:o,error:e}}try{const i=await Dr(e.signedEnvelope);if(o=i.reply,Mr(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在发送回复");const s=await a.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!s.found)return await r.fail(s.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:s.error??"未找到目标聊天"};const c=await a.readActiveChatPanel(),l=await a.readSelectedChatTarget();if(null===l)return await r.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==c&&l.candidateName.length>0&&!Jr(l.candidateName,c.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${l.candidateName} / ${c.candidateName}`;return await r.fail(e),{success:!1,sentMessage:o,error:e}}if(l.conversationId!==i.conversationId||l.candidateId!==i.candidateId)return await r.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=Tn(await a.readUsernameEvidence());if(!d.found)return await r.fail("未找到用户名"),{success:!1,sentMessage:o,error:"未找到用户名,请确认当前页面已登录招聘者账号。"};const u={platform:"zhipin",username:d.username,strategy:d.strategy,source:d.source};if(!hr(u,i.recruiterBinding)){const e=`recruiter 绑定不匹配:当前账号 ${u.username} 与签发时 ${i.recruiterBinding.username} 不一致`;return await r.fail(e),{success:!1,sentMessage:o,error:e}}t.logger.info(`Sending native message (${o.length} chars) to ${l.candidateName||l.candidateId}`);const m=await a.sendChatReply(o,{onTargetResolved:async e=>{await(r?.highlightPoint(e.x,e.y))}});return m.success?(Ir(i.jti,i.exp),await r.succeed("已发送回复"),{success:!0,sentMessage:o}):(await r.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"})}catch(e){const t=e instanceof Error?e.message:String(e);return await(r?.fail(t)),{success:!1,sentMessage:o,error:t}}finally{a?.close()}}});import{defineTool as Vr}from"@roll-agent/sdk";import{z as Wr}from"zod";var Kr,Yr=Wr.object({success:Wr.boolean(),exchanged:Wr.boolean(),wechatNumber:Wr.string().optional(),error:Wr.string().optional()}),Gr=900,Xr=160,Zr=1100;function Qr(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Kr}}function eo(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),a=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}var to=Vr({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Wr.object({conversationId:Wr.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Wr.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Wr.number().optional().describe("候选人在列表中的索引(可选)")}),output:Yr,execute:async(e,t)=>{const n=Qr();let a,r;try{if(a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在换微信"),void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index){const t=await a.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await r.fail(t.error??"未找到目标聊天"),{success:!1,exchanged:!1,error:t.error??"未找到目标聊天"}}else if(!await a.isChatSurfaceOpen())return await r.fail("消息列表未加载"),{success:!1,exchanged:!1,error:"消息列表未加载"};const o=await a.readSelectedChatTarget();if(null===o)return await r.fail("未选中聊天联系人"),{success:!1,exchanged:!1,error:"未选中聊天联系人,无法点击当前聊天输入区的「换微信」按钮"};const i=await a.readActiveChatPanel();if(null!==i&&o.candidateName.length>0&&!eo(o.candidateName,i.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${o.candidateName} / ${i.candidateName}`;return await r.fail(e),{success:!1,exchanged:!1,error:e}}t.logger.info(`Starting native WeChat exchange with ${o.candidateName}`);const s=await a.exchangeWechat({preClickDelayMs:Gr,pressDurationMs:Xr,settleMs:Zr,onTargetResolved:async e=>{await(r?.highlightPoint(e.x,e.y))}});return s.success?(await r.succeed("已完成换微信"),s):(await r.fail(s.error??"换微信失败"),s)}catch(e){const t=e instanceof Error?e.message:String(e);return await(r?.fail(t)),{success:!1,exchanged:!1,error:t}}finally{a?.close()}}});import{defineTool as no}from"@roll-agent/sdk";import{z as ao}from"zod";var ro,oo=ao.object({success:ao.boolean(),username:ao.string(),usedSelector:ao.string().optional(),usedStrategy:ao.string().optional(),source:ao.string().optional(),error:ao.string().optional()});function io(){return{openNativePagePort:Ma,pickBestUsername:Tn,createNativeVisualActivitySession:e=>new yn(e),...ro}}var so=no({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:ao.object({}),output:oo,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=io();let a,r;try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await r.begin("正在识别登录账号"),await r.highlightSelector('header, #header, [role="banner"], [role="navigation"]',{label:"正在识别登录账号",padding:10});const e=n.pickBestUsername(await a.readUsernameEvidence());if(!e.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return await r.succeed(`已识别账号:${e.username}`),t.logger.info(`Username: ${e.username} (strategy: ${e.strategy}, source: ${e.source})`),{success:!0,username:e.username,usedSelector:"css-fallback"===e.strategy?e.source:void 0,usedStrategy:e.strategy,source:e.source}}catch(e){return await(r?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}finally{a?.close()}}});import{defineTool as co}from"@roll-agent/sdk";import{z as lo}from"zod";var uo,mo=lo.object({containerFound:lo.boolean(),containerLabel:lo.string(),scrollTop:lo.number(),scrollHeight:lo.number(),clientHeight:lo.number(),itemCount:lo.number(),atStart:lo.boolean(),atEnd:lo.boolean()}),go=lo.object({success:lo.boolean(),surface:lo.enum(Cn),direction:lo.enum(["up","down"]),stepsRequested:lo.number(),stepsCompleted:lo.number(),reachedBoundary:lo.boolean(),before:mo,after:mo,error:lo.string().optional()});function ho(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...uo}}function fo(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}var po=co({name:"zhipin_scroll_view",description:"滚动 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list。",input:lo.object({surface:lo.enum(Cn).describe("要滚动的页面区域"),direction:lo.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:lo.number().int().min(1).max(20).default(1).describe("滚动步数"),distance:lo.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:lo.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:go,execute:async(e,t)=>{const n=ho(),a=kn(e.surface),r=e.direction??a.defaultDirection,o=e.steps??1,i=e.settleMs??700;let s,c;try{s=await n.openNativePagePort({requireChatPage:"chat-list"===e.surface||"chat-history"===e.surface}),c=n.createNativeVisualActivitySession(s);const l=`正在滚动 ${e.surface}`;await c.begin(l),await c.highlightSelector(a.highlightSelector,{label:l,padding:8});const d=await s.scrollSurface(e.surface,{direction:r,steps:o,settleMs:i,...void 0!==e.distance?{distance:e.distance}:{}});return d.success?(await c.succeed(`已滚动 ${d.stepsCompleted}/${d.stepsRequested} 步`),t.logger.info(`Scrolled ${e.surface}: ${d.stepsCompleted}/${d.stepsRequested}, items ${d.before.itemCount} -> ${d.after.itemCount}`),{success:d.success,surface:e.surface,direction:d.direction,stepsRequested:d.stepsRequested,stepsCompleted:d.stepsCompleted,reachedBoundary:d.reachedBoundary,before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=fo();return{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as yo}from"@roll-agent/sdk";import{z as bo}from"zod";var wo,vo=["不限","男","女"],xo=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],So=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","submit_failed","error"],Co=bo.object({ageMin:bo.number().int().min(16).optional(),ageMax:bo.number().int().min(16).optional(),gender:bo.enum(vo),activity:bo.enum(xo)}),Ro=bo.object({ageMin:bo.number().optional(),ageMax:bo.number().optional(),gender:bo.string(),activity:bo.string()}),ko=bo.object({success:bo.boolean(),status:bo.enum(So),requested:Co,applied:Ro.optional(),filterButtonText:bo.string().optional(),error:bo.string().optional()}),Mo=bo.object({ageMin:bo.number().int().min(16).optional().describe("年龄下限;未传则重置为 16"),ageMax:bo.number().int().min(16).optional().describe("年龄上限;未传则重置为不限"),gender:bo.enum(vo).default("不限").describe("性别筛选,只支持:不限、男、女"),activity:bo.enum(xo).default("不限").describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃")}).refine(e=>void 0===e.ageMin||void 0===e.ageMax||e.ageMin<=e.ageMax,{path:["ageMax"],message:"ageMax must be greater than or equal to ageMin"}),Io=350,Po=130,Ao=600;function No(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...wo}}function To(e){const t=e.gender??"不限",n=e.activity??"不限";return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},gender:t,activity:n}}function Eo(e){return{success:"applied"===e.status,status:e.status,requested:e.requested,...void 0!==e.applied?{applied:e.applied}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var Bo=yo({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页打开筛选面板,只设置年龄、性别、活跃度[单选] 三个维度并提交。",input:Mo,output:ko,execute:async(e,t)=>{const n=No(),a=To(e);let r,o;t.logger.info(`Filtering Boss recommend candidates through native CDP: gender=${a.gender}, activity=${a.activity}, ageMin=${a.ageMin??"16"}, ageMax=${a.ageMax??"不限"}`);try{r=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(r),await r.bringToFront().catch(()=>{}),await o.begin("正在打开推荐筛选");if(!await r.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),Eo({status:"recommend_not_ready",requested:a,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Mn.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await r.applyRecommendFilter(a,{preClickDelayMs:Io,pressDurationMs:Po,settleMs:Ao,onTargetResolved:async e=>{await(o?.highlightPoint(e.x,e.y))}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),Eo(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),Eo({status:"error",requested:a,error:t})}finally{r?.close()}}});import{defineTool as Fo}from"@roll-agent/sdk";import{z as qo}from"zod";var Lo,$o=["target-count","boundary","no-new-items","max-steps"],Oo=qo.object({index:qo.number(),candidateId:qo.string(),name:qo.string(),age:qo.string(),experience:qo.string(),education:qo.string(),workStatus:qo.string(),company:qo.string(),currentPosition:qo.string(),expectedLocation:qo.string(),expectedPosition:qo.string(),expectedSalary:qo.string(),tags:qo.array(qo.string()),buttonText:qo.string()}),_o=qo.object({containerLabel:qo.string(),stepsRequested:qo.number(),stepsCompleted:qo.number(),reachedBoundary:qo.boolean(),stopReason:qo.enum($o),uniqueCount:qo.number(),duplicateCount:qo.number(),noNewRounds:qo.number(),beforeItemCount:qo.number(),afterItemCount:qo.number(),beforeScrollHeight:qo.number(),afterScrollHeight:qo.number()}),Do=qo.object({success:qo.boolean(),candidates:qo.array(Oo),total:qo.number(),scrollStats:_o.optional(),error:qo.string().optional()});function zo(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Lo}}var jo=Fo({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:qo.object({maxResults:qo.number().optional().describe("最多返回条数"),autoScroll:qo.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:qo.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Do,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=zo();let a,r;try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await r.begin("正在打开推荐列表");if(!await a.waitForRecommendList())return await r.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};const o="正在读取推荐列表";await r.begin(o),await r.highlightSelector(".candidate-card-wrap, li.card-item, [data-geek], .geek-item",{label:o,padding:8});const i=await a.readRecommendCandidates({autoScroll:e.autoScroll??!0,maxScrolls:e.maxScrolls??4,...void 0!==e.maxResults?{targetCount:e.maxResults}:{}}),s=void 0!==e.maxResults?i.items.slice(0,e.maxResults):[...i.items],c=!1===e.autoScroll?void 0:{containerLabel:i.after.containerLabel,stepsRequested:i.stepsRequested,stepsCompleted:i.stepsCompleted,reachedBoundary:i.reachedBoundary,stopReason:i.stopReason,uniqueCount:i.uniqueCount,duplicateCount:i.duplicateCount,noNewRounds:i.noNewRounds,beforeItemCount:i.before.itemCount,afterItemCount:i.after.itemCount,beforeScrollHeight:i.before.scrollHeight,afterScrollHeight:i.after.scrollHeight};return await r.succeed(`已读取 ${s.length} 位候选人`),t.logger.info(`Found ${s.length} candidates in recommend list`+(c?`, scroll stop: ${c.stopReason}`:"")),{success:!0,candidates:s,total:s.length,...void 0!==c?{scrollStats:c}:{}}}catch(e){return await(r?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{a?.close()}}});import{BrowserPageInfoSchema as Ho}from"@roll-agent/browser";import{defineTool as Jo}from"@roll-agent/sdk";import{z as Uo}from"zod";var Vo,Wo=Uo.object({success:Uo.boolean(),alreadyOnRecommend:Uo.boolean(),usedSidebarClick:Uo.boolean(),recommendReady:Uo.boolean(),page:Ho.optional(),error:Uo.string().optional()});function Ko(){return{getContextManager:p,openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),...Vo}}async function Yo(e,t){return le(e,await t.inspectPage())}var Go=Jo({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:Uo.object({}),output:Wo,execute:async(e,t)=>{const n=Ko(),a=n.getContextManager();let r,o;t.logger.info("Opening Boss recommend page via native sidebar navigation");try{r=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(r),await r.bringToFront().catch(()=>{});const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(Mn.nav.sidebar,{label:e,padding:10}),await r.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await Yo(a,r)};if(!await r.clickSidebarSection("recommend",{onTargetResolved:async e=>{await(o?.highlightPoint?.(e.x,e.y))}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await Yo(a,r),error:"未找到推荐牛人导航"};t.logger.info("Clicked Boss sidebar nav: 推荐牛人");return await r.waitForRecommendSurface()?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await Yo(a,r)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await Yo(a,r),error:"推荐牛人页未就绪"})}catch(e){return await(o?.fail("切换推荐牛人页失败")),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,error:e instanceof Error?e.message:"切换推荐牛人页失败"}}finally{r?.close()}}});import{defineTool as Xo}from"@roll-agent/sdk";import{z as Zo}from"zod";var Qo,ei=Zo.object({index:Zo.number(),candidateName:Zo.string(),candidateId:Zo.string(),success:Zo.boolean(),error:Zo.string().optional()}),ti=Zo.object({success:Zo.boolean(),results:Zo.array(ei),summary:Zo.object({total:Zo.number(),succeeded:Zo.number(),failed:Zo.number()})}),ni=[2400,3100,3800],ai=650,ri=140,oi=1200;function ii(){return{openNativePagePort:Ma,createNativeVisualActivitySession:e=>new yn(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...Qo}}var si=Xo({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Zo.object({indices:Zo.array(Zo.number()).min(1).describe("要打招呼的候选人索引列表")}),output:ti,execute:async(e,t)=>{t.logger.info(`Saying hello to ${e.indices.length} candidates through native CDP`);const n=ii();let a,r;try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await r.begin("正在打开推荐列表");if(!await a.waitForRecommendList()){await r.fail("推荐列表未加载");const t=e.indices.map(e=>({index:e,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:t,summary:{total:t.length,succeeded:0,failed:t.length}}}const t=e.indices.length>1?"正在批量打招呼":"正在打招呼";await r.begin(t),await r.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:t,padding:8});const o=[];for(const[t,i]of e.indices.entries()){if(t>0){const e=ni[(t-1)%ni.length]??ni[0];await n.sleep(e)}const e=await a.clickRecommendGreet(i,{preClickDelayMs:ai,pressDurationMs:ri,settleMs:oi,onTargetResolved:async e=>{await(r?.highlightPoint(e.x,e.y))}});o.push({index:i,candidateName:e.name,candidateId:e.candidateId,success:e.clicked,...void 0!==e.error?{error:e.error}:{}})}const i={total:o.length,succeeded:o.filter(e=>e.success).length,failed:o.filter(e=>!e.success).length};return 0===i.failed?await r.succeed(`已完成 ${i.succeeded}/${i.total} 位候选人`):await r.fail(`已完成 ${i.succeeded}/${i.total} 位候选人`),{success:0===i.failed,results:o,summary:i}}catch(t){const n=t instanceof Error?t.message:String(t);await(r?.fail(n));const a=e.indices.map(e=>({index:e,candidateName:"",candidateId:"",success:!1,error:n}));return{success:!1,results:a,summary:{total:a.length,succeeded:0,failed:a.length}}}finally{a?.close()}}});import{defineTool as ci}from"@roll-agent/sdk";import{z as li}from"zod";var di="recommendFrame",ui="recommend",mi="#recommendFrame",gi=".candidate-card-wrap",hi="[data-geek], .geek-item",fi=`${gi}, ${hi}`,pi="[data-geek], .card-inner, .geek-item",yi="[data-geek]",bi=".name",wi=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],vi=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],xi=".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog",Si=".boss-popup__wrapper",Ci='iframe[src*="c-resume"]',Ri="canvas#resume, div#resume canvas";function ki(e){return e.hasNamedRecommendFrame?"named-frame":e.hasRecommendUrlFrame?"recommend-url-frame":"main-page"}function Mi(e){return e>0?gi:hi}function Ii(e){return{candidateId:(e.ownDataGeek??e.childDataGeek??"").trim(),name:(e.nameText??"").trim()}}function Pi(e){const t=(e.recommendFrameRect?.x??0)+(e.resumeFrameRect?.x??0),n=(e.recommendFrameRect?.y??0)+(e.resumeFrameRect?.y??0);return{x:Math.round(t+e.canvasRect.x),y:Math.round(n+e.canvasRect.y),width:Math.round(e.canvasRect.width),height:Math.round(e.canvasRect.height)}}function Ai(e){return e.evaluate(e=>document.querySelectorAll(e).length,gi).then(Mi)}function Ni(e){const t=e.frame(di),n=e.frames().find(e=>e.url().includes(ui)),a=ki({hasNamedRecommendFrame:null!==t,hasRecommendUrlFrame:void 0!==n});return"named-frame"===a?t??e:"recommend-url-frame"===a?n??e:e}async function Ti(e,t=1e4){try{return await e.waitForSelector(fi,{timeout:t}),!0}catch{return!1}}async function Ei(e,t){const n=await Ai(e),a=e.locator(n);if(await a.count()<=t)return{found:!1,cardSelector:n,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const r=a.nth(t),o=await r.evaluate((e,t)=>{const n=e.getAttribute("data-geek")??e.querySelector(t.candidateIdSelector)?.getAttribute("data-geek")??"",a=e.querySelector(t.candidateNameSelector)?.textContent?.trim()??"",r=e.querySelector("button.btn.btn-greet");return{candidateId:n,name:a,hasGreetButton:null!==r&&r.offsetWidth>0}},{candidateIdSelector:yi,candidateNameSelector:bi}),i=Ii({ownDataGeek:o.candidateId,nameText:o.name});return{found:!0,cardSelector:n,candidateId:i.candidateId,name:i.name,hasGreetButton:o.hasGreetButton}}var Bi=li.object({success:li.boolean(),candidateName:li.string(),candidateId:li.string(),error:li.string().optional()}),Fi=ci({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:li.object({index:li.number().describe("候选人在列表中的索引")}),output:Bi,execute:async(e,t)=>{t.logger.info(`Opening resume for candidate at index ${e.index}`);const n=p(),a=await n.getPage("zhipin"),r=Ni(a);if(!await Ti(r))return{success:!1,candidateName:"",candidateId:"",error:"推荐列表未加载"};const o=await Ei(r,e.index);if(!o.found)return{success:!1,candidateName:"",candidateId:"",error:o.error??`索引 ${e.index} 超出范围`};const i=r.locator(o.cardSelector).nth(e.index),s=await i.locator(pi).count()>0?i.locator(pi).first():i;return await s.scrollIntoViewIfNeeded(),await K(a,s,{target:r}),await s.hover(),await wn(a,200,400),await Y(a,s,{target:r}),await s.click(),await wn(a,1e3,2e3),t.logger.info(`Opened resume for ${o.name}`),{success:!0,candidateName:o.name,candidateId:o.candidateId}}});import{defineTool as qi}from"@roll-agent/sdk";import{z as Li}from"zod";var $i=Li.object({x:Li.number(),y:Li.number(),width:Li.number(),height:Li.number()}),Oi=Li.object({success:Li.boolean(),screenshotArea:$i.optional(),canvasInfo:Li.object({width:Li.number(),height:Li.number()}).optional(),error:Li.string().optional()}),_i=qi({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:Li.object({}),output:Oi,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=p(),a=await n.getPage("zhipin");try{const e=a.frame(di)??a.frames().find(e=>e.url().includes(ui));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$(Ci);if(!n)return{success:!1,error:"未找到简历 iframe"};const r=await n.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector(Ri,{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await r.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{width:t.width,height:t.height,clientWidth:n.width,clientHeight:n.height,x:n.x,y:n.y}},Ri);if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await a.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},mi),s=Pi({recommendFrameRect:i,resumeFrameRect:await e.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},Ci),canvasRect:{x:o.x,y:o.y,width:o.clientWidth,height:o.clientHeight}});return t.logger.info(`Canvas located at (${s.x}, ${s.y})`),{success:!0,screenshotArea:s,canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as Di}from"@roll-agent/sdk";import{z as zi}from"zod";var ji=zi.object({success:zi.boolean(),closed:zi.boolean(),error:zi.string().optional()}),Hi=Di({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:zi.object({}),output:ji,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=p(),a=await n.getPage("zhipin"),r=a.frame(di)??a.frames().find(e=>e.url().includes(ui));if(!await(async()=>{if(r)for(const e of wi){const t=r.locator(e).first();if(await t.isVisible())return await K(a,t,{target:r}),await Y(a,t,{target:r}),await t.click(),!0}for(const e of vi){const t=a.locator(e).first();if(await t.isVisible())return await K(a,t),await Y(a,t),await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await a.waitForTimeout(300);const e=r?await r.$(xi):await a.$(Si);if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as Ji}from"@roll-agent/sdk";import{z as Ui}from"zod";import{waitForSelector as Vi}from"@roll-agent/browser";var Wi={login:{qrCode:".login-qr img, .qr-code img",loginSuccess:".user-info, .header-user"},messageList:{container:".chat-list, .msg-list",item:".chat-item, .msg-item",candidateName:".chat-item .name, .msg-item .name",lastMessage:".chat-item .msg, .msg-item .content",unreadBadge:".chat-item .unread, .msg-item .badge",timestamp:".chat-item .time, .msg-item .time"},chat:{input:".chat-input textarea, .msg-input textarea",sendButton:".btn-send, .send-btn",messageItem:".chat-msg, .msg-bubble",messageText:".chat-msg .text, .msg-bubble .text"}};import{navigateTo as Ki,waitForSelector as Yi}from"@roll-agent/browser";var Gi="https://www.yupao.com",Xi=`${Gi}/chat`,Zi=`${Gi}/login`;async function Qi(e){e.url().includes("/chat")||await Ki(e,Xi),await Yi(e,Wi.messageList.container,{timeout:15e3})}async function es(e,t){const n=`${Gi}/chat?id=${encodeURIComponent(t)}`;await Ki(e,n),await Yi(e,Wi.chat.input,{timeout:15e3})}async function ts(e,t){await Qi(e),await Vi(e,Wi.messageList.item,{timeout:1e4});const n=Wi.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],a=t.maxItems?e.slice(0,t.maxItems):e;for(const e of a){const a=e.querySelector(t.sel.candidateName),r=e.querySelector(t.sel.lastMessage),o=e.querySelector(t.sel.unreadBadge),i=e.querySelector(t.sel.timestamp),s=e.getAttribute("data-id")??e.getAttribute("data-conversation-id")??e.querySelector("a")?.getAttribute("href")?.match(/id=([^&]+)/)?.[1]??"";n.push({conversationId:s,candidateName:a?.textContent?.trim()??"",lastMessage:r?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var ns=Ui.object({limit:Ui.number().optional().describe("最多返回的消息条数")}),as=Ui.object({conversationId:Ui.string(),candidateName:Ui.string(),lastMessage:Ui.string(),unreadCount:Ui.number(),timestamp:Ui.string()}),rs=Ui.object({messages:Ui.array(as),total:Ui.number()}),os=Ji({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:ns,output:rs,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=p(),a=await n.getPage("yupao"),r=await ts(a,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as is}from"@roll-agent/sdk";import{z as ss}from"zod";import{waitForSelector as cs,typeText as ls,clickElement as ds}from"@roll-agent/browser";async function us(e,t,n){try{return await es(e,t),await ls(e,Wi.chat.input,n),await ds(e,Wi.chat.sendButton),await cs(e,Wi.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}var ms=ss.object({conversationId:ss.string().describe("对话 ID"),message:ss.string().describe("要发送的回复消息")}),gs=ss.object({success:ss.boolean(),conversationId:ss.string(),sentMessage:ss.string(),error:ss.string().optional()}),hs=is({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:ms,output:gs,execute:async(e,t)=>{const{conversationId:n,message:a}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const r=p(),o=await r.getPage("yupao"),i=await us(o,n,a);return i.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${i.error}`),{success:i.success,conversationId:n,sentMessage:a,error:i.error}}}),fs=t("browser-use-agent");function ps(e){if(void 0!==e){if("true"===e)return!0;if("false"===e)return!1;throw new Error(`Expected boolean env value "true" or "false", received "${e}".`)}}function ys(e,t){if(void 0===e)return;const n=Number.parseInt(e,10);if(!Number.isInteger(n))throw new Error(`${t} must be an integer, received "${e}".`);return n}function bs(e){if(void 0===e)return;const t=JSON.parse(e);if(!Array.isArray(t)||!t.every(e=>"string"==typeof e))throw new Error("BROWSER_ARGS_JSON must be a JSON string array.");return t}function ws(){return n.parse({mode:process.env.BROWSER_MODE,headless:ps(process.env.BROWSER_HEADLESS),cdpUrl:process.env.BROWSER_CDP_URL,cdpHost:process.env.BROWSER_CDP_HOST,cdpPort:ys(process.env.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:process.env.BROWSER_CHANNEL,executablePath:process.env.BROWSER_EXECUTABLE_PATH,userDataDir:process.env.BROWSER_USER_DATA_DIR,args:bs(process.env.BROWSER_ARGS_JSON),sessionsDir:process.env.BROWSER_SESSIONS_DIR})}var vs=e({name:"browser-use-agent",tools:[Q,ge,Pe,qe,je,dn,Fa,ja,Ka,ur,Ur,to,so,po,Go,Bo,jo,si,Fi,_i,Hi,os,hs,S]},{onShutdown:v});async function xs(){await h(ws());try{await qr(),b(!0)}catch(e){b(!1),fs.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await vs.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}xs().catch(async e=>{fs.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await v().catch(()=>{}),process.exit(1)});
|