@roll-agent/browser-use-agent 0.10.0 → 0.12.0
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 -25
- package/dist/browser-action-approval.d.ts +14 -0
- package/dist/browser-security.d.ts +20 -0
- package/dist/browser-use-policy.d.ts +56 -0
- package/dist/diagnostics/effective-env.d.ts +1 -1
- package/dist/element-ref-store.d.ts +2 -0
- package/dist/index.js +1 -1
- package/dist/pages/platform-page.d.ts +2 -2
- package/dist/pages/yupao/chat.d.ts +2 -2
- package/dist/pages/yupao/navigation.d.ts +4 -4
- package/dist/reply-authority/prepared-reply-store.d.ts +2 -0
- package/dist/reply-authority/schemas.d.ts +2 -2
- package/dist/runtime-config.d.ts +2 -0
- package/dist/tool-action-approval.d.ts +35 -0
- package/dist/tools/browser-dom-action-candidates.d.ts +8 -0
- package/dist/tools/browser-native-page.d.ts +6 -0
- package/dist/tools/browser-ref-schemas.d.ts +99 -0
- package/dist/tools/browser-ref-visual.d.ts +5 -0
- package/dist/tools/browser-snapshot.d.ts +33 -0
- package/dist/tools/browser-status.d.ts +19 -3
- package/dist/tools/click-ref.d.ts +22 -0
- package/dist/tools/list-pages.d.ts +1 -1
- package/dist/tools/navigate-active-tab.d.ts +4 -1
- package/dist/tools/open-platform.d.ts +4 -1
- package/dist/tools/select-page.d.ts +1 -1
- package/dist/tools/type-ref.d.ts +24 -0
- package/dist/tools/yupao-send-reply.d.ts +3 -0
- package/dist/tools/zhipin-diagnose-browser-state.d.ts +7 -7
- package/dist/tools/zhipin-generate-reply-preview.d.ts +5 -0
- package/dist/tools/zhipin-get-candidate-info.d.ts +2 -2
- package/dist/tools/zhipin-open-chat-page.d.ts +1 -1
- package/dist/tools/zhipin-open-recommend-page.d.ts +1 -1
- package/dist/tools/zhipin-send-prepared-reply.d.ts +12 -0
- package/package.json +4 -4
- package/references/env.yaml +8 -0
- package/references/generic-browser-refs.md +151 -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,d,u,m=!1,g=o("browser-use-agent");async function p(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 h(){if(!d)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return d}function b(){if(!u)throw new Error("SessionStore not initialized. Call initRuntime() first.");return u}function y(e){m=e}function v(){return m}async function w(){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 R}from"@roll-agent/sdk";import{z as C}from"zod";import{BrowserStatusSchema as I}from"@roll-agent/browser";import{createHash as k}from"node:crypto";import{z as M}from"zod";var N=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_KEYS_URL","RECRUITMENT_EVENTS_ENABLED","RECRUITMENT_EVENTS_API_BASE_URL","RECRUITMENT_EVENTS_API_TOKEN","RECRUITMENT_EVENTS_DEFAULT_AGENT_ID","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],P=/^[0-9a-f]{8}$/,T=M.object({present:M.boolean(),fingerprint:M.string().regex(P).optional()}),E=M.record(T);function A(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:_(n)}]:[e,{present:!1}]}))}function _(e){return k("sha256").update(e).digest("hex").slice(0,8)}var B,q,F=180,O=60,$=280,j="roll-agent-visual-cursor-root",L="__rollVisualCursorState";function D(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function z(){return void 0!==B?B:D(process.env.BROWSER_VISUAL_CURSOR)??!0}async function J(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 V(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 H(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function U(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:j,stateKey:L})}async function K(e,t={}){if(e.isClosed())return!1;let n=!1;for(const a of H(e))if(a!==t.preserveTarget)try{await U(a),n=!0}catch{}return n}async function W(e,t,n={}){if(!z()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const a=await J(t);if(!a)return!1;const r=n.durationMs??F,o=n.settleMs??O,i=n.target??e;return await K(e,{preserveTarget:i}),await V(n.target??e,{point:a,durationMs:r,clickPulse:!1,pulseDurationMs:$}),await e.waitForTimeout(Math.max(r+o,0)),!0}catch{return!1}}async function Y(e,t,n={}){if(!z()||e.isClosed())return!1;try{const a=await J(t);if(!a)return!1;const r=n.pulseDurationMs??$,o=n.target??e;return await K(e,{preserveTarget:o}),await V(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=I.extend({replyAuthorityKeysLoaded:C.boolean(),visualCursorEnabled:C.boolean(),visualActivityEnabled:C.boolean(),effectiveEnvSources:E}),Q=R({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:C.object({}),output:Z,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=f(),a=h(),r=b(),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:v(),visualCursorEnabled:z(),visualActivityEnabled:X(),effectiveEnvSources:A(N)}}});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=h();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{setTimeout as pe}from"node:timers/promises";import{defineTool as fe}from"@roll-agent/sdk";import{BrowserPageInfoSchema as he}from"@roll-agent/browser";import{z as be}from"zod";var ye,ve=15e3,we=250,xe=["/web/chat"],Se=be.object({url:be.string().url().describe("要导航到的目标 URL")}),Re=be.object({success:be.boolean(),page:he});function Ce(){return{getContextManager:h,getRuntime:f,detectPlatformFromUrl:ce,matchesPlatformHost:se,toNativePageInfo:le,delay:pe,...ye}}function Ie(e){return"object"==typeof e&&null!==e}function ke(e){return!!Ie(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function Me(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==xe.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function Ne(e,t,n,a,r){const o=n.detectPlatformFromUrl(a),i=await t.listNativePages();if(o){const s=i.find(e=>n.matchesPlatformHost(e.url,o));if(s)return r.info(`Reusing native ${o} page for navigation`),await t.activateNativePage(s.targetId),e.rememberNativePageSelection(o,s),{page:s,platform:o};r.info(`Opening native ${o} page for navigation`);const c=await t.openNativePage(a);return e.rememberNativePageSelection(o,c),{page:c,platform:o}}return r.info("Opening a new native page for non-platform navigation"),{page:await t.openNativePage(a)}}async function Pe(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!ke(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function Te(e,t,n,a){const r=Date.now();let o;for(;Date.now()-r<ve;){try{const t=await Pe(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Ee(t.url,n,a))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(we)}throw new Error(`Native navigation did not reach document ready state within ${ve}ms${o?`: ${o.message}`:""}`)}function Ee(e,t,n){if(e===t)return!0;try{const t=new URL(e);return("http:"===t.protocol||"https:"===t.protocol)&&e!==n}catch{return!1}}async function Ae(e,t,n){t.url!==n&&await e.navigate(n)}async function _e(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var Be=fe({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:Se,output:Re,execute:async(e,t)=>{const n=Ce(),a=n.getContextManager(),r=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);Me(e.url,o),t.logger.info(`Native navigating to ${e.url}`);const i=await Ne(a,r,n,e.url,t.logger),s=await r.connectNativePage(i.page);try{await s.bringToFront().catch(()=>{}),await Ae(s,i.page,e.url);const o=await Te(s,n,e.url,i.page.url),c=await _e(r,i.page,o),l=i.platform??n.detectPlatformFromUrl(c.url);return l&&(a.rememberNativePageSelection(l,c),t.logger.info(`Bound native navigated page to ${l}`)),{success:!0,page:n.toNativePageInfo(a,c)}}finally{s.close()}}});import{defineTool as qe}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Fe,PlatformSchema as Oe}from"@roll-agent/browser";import{z as $e}from"zod";async function je(e,t){return(await e.listNativePages()).find(e=>se(e.url,t))}async function Le(e,t){const n=await je(e,t);if(n)return await e.activateNativePage(n.targetId),{page:n,reusedExistingPage:!0};return{page:await e.openNativePage(oe[t]),reusedExistingPage:!1}}var De=$e.object({platform:Oe.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡")}),ze=$e.object({success:$e.boolean(),page:Fe,reusedExistingTab:$e.boolean()}),Je=qe({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:De,output:ze,execute:async(e,t)=>{const{platform:n}=e,a=f(),r=h();t.logger.info(`Opening platform page for ${n}`);const{page:o,reusedExistingPage:i}=await Le(a,n);return r.rememberNativePageSelection(n,o),{success:!0,page:le(r,o),reusedExistingTab:i}}});import{defineTool as Ve}from"@roll-agent/sdk";import{BrowserPageInfoSchema as He,PlatformSchema as Ue}from"@roll-agent/browser";import{z as Ke}from"zod";var We=Ke.object({platform:Ue.describe("要将该页面绑定为当前活跃页的平台"),pageId:Ke.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),Ye=Ke.object({success:Ke.boolean(),page:He}),Ge=Ve({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:We,output:Ye,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:le(n,a)}}});import{defineTool as Xe}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ze}from"@roll-agent/browser";import{z as Qe}from"zod";var et=["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"],tt=[...et,"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"],nt=Qe.enum(["localStorage","sessionStorage"]),at=Qe.enum(["array","object","string","number","boolean","null"]),rt=Qe.enum(["empty","json","string"]),ot=Qe.enum(et),it=Qe.enum(tt),st=Qe.enum(["request","response"]),ct=Qe.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),lt=Qe.object({area:nt,key:Qe.string(),valueLength:Qe.number().int().nonnegative(),valueKind:rt,jsonKind:at.optional(),jsonTopLevelKeys:Qe.array(Qe.string()).optional(),jsonArrayLength:Qe.number().int().nonnegative().optional(),numericFields:Qe.record(Qe.number()).optional(),booleanFields:Qe.record(Qe.boolean()).optional(),arrayLengths:Qe.record(Qe.number().int().nonnegative()).optional()}),dt=Qe.object({name:Qe.string(),domain:Qe.string(),path:Qe.string(),expires:Qe.string(),valueLength:Qe.number().int().nonnegative(),httpOnly:Qe.boolean(),secure:Qe.boolean(),sameSite:Qe.string().optional()}),ut=Qe.object({before:Qe.number().optional(),after:Qe.number().optional(),delta:Qe.number().optional()}),mt=Qe.object({before:Qe.boolean().optional(),after:Qe.boolean().optional()}),gt=Qe.object({before:Qe.number().int().nonnegative().optional(),after:Qe.number().int().nonnegative().optional(),delta:Qe.number().int().optional()}),pt=Qe.object({area:nt,key:Qe.string(),beforePresent:Qe.boolean(),afterPresent:Qe.boolean(),numericDeltas:Qe.record(ut).optional(),booleanChanges:Qe.record(mt).optional(),arrayLengthDeltas:Qe.record(gt).optional()}),ft=Qe.object({url:Qe.string(),title:Qe.string(),visibilityState:Qe.string(),hasFocus:Qe.boolean()}),ht=Qe.object({navigatorWebdriver:Qe.boolean().optional(),userAgentContainsHeadless:Qe.boolean(),languagesLength:Qe.number().int().nonnegative(),pluginsLength:Qe.number().int().nonnegative(),hasChromeRuntime:Qe.boolean(),permissionQueryIsNative:Qe.boolean().optional(),hasPlaywrightBinding:Qe.boolean(),hasPwInitScripts:Qe.boolean(),cdcKeys:Qe.array(Qe.string()),webdriverKeys:Qe.array(Qe.string()),automationLikeWindowKeys:Qe.array(Qe.string())}),bt=Qe.object({rootNodeId:Qe.number().int(),rootNodeName:Qe.string(),childNodeCount:Qe.number().int().nonnegative().optional(),bodyTextLength:Qe.number().int().nonnegative().optional(),elementCount:Qe.number().int().nonnegative().optional()}),yt=Qe.object({type:Qe.literal("mouseMoved"),x:Qe.number(),y:Qe.number()}),vt=Qe.object({targetId:Qe.string(),websocketUrlAvailable:Qe.boolean(),connected:Qe.boolean(),pageBroughtToFront:Qe.boolean().optional(),runtimeEnabled:Qe.boolean().optional(),evaluate:ft.optional(),dom:bt.optional(),input:yt.optional()}),wt=Qe.object({phase:ot,success:Qe.boolean(),durationMs:Qe.number().int().nonnegative(),error:Qe.string().optional()}),xt=Qe.object({phase:it,capturedAt:Qe.string(),targetFound:Qe.boolean(),page:Ze.optional(),urlChangedFromPrevious:Qe.boolean(),titleChangedFromPrevious:Qe.boolean(),previousUrl:Qe.string().optional(),currentUrl:Qe.string().optional(),previousTitle:Qe.string().optional(),currentTitle:Qe.string().optional()}),St=Qe.object({kind:st,reason:ct,capturedAt:Qe.string(),url:Qe.string(),method:Qe.string().optional(),resourceType:Qe.string().optional(),status:Qe.number().int().optional()}),Rt=Qe.object({capturedAt:Qe.string(),url:Qe.string()}),Ct=Qe.object({phase:ot.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:Qe.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:Qe.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:Qe.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),It=Qe.object({success:Qe.boolean(),requestedPhase:ot,mode:Qe.string(),nativePages:Qe.array(Ze),targetPage:Ze.optional(),browserAttached:Qe.boolean(),pageAttached:Qe.boolean(),nativeTimeline:Qe.array(xt),networkEvents:Qe.array(St).optional(),navigationEvents:Qe.array(Rt).optional(),nativeCdp:vt.optional(),evaluate:ft.optional(),detectorFingerprint:ht.optional(),storage:Qe.object({localStorage:Qe.array(lt),sessionStorage:Qe.array(lt),cookies:Qe.array(dt),counterDiffs:Qe.array(pt)}).optional(),phases:Qe.array(wt),warnings:Qe.array(Qe.string())}),kt=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Mt=new Set(kt),Nt=["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"],Pt=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],Tt={"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 Et(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 At(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function _t(e){return Nt.includes(e)}function Bt(e){return Pt.includes(e)}function qt(e){try{return JSON.parse(e)}catch{return}}function Ft(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 Ot(e){return new Promise(t=>{setTimeout(t,e)})}function $t(e){return Mt.has(e)}function jt(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=qt(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const a=Et(n),r=At(n)&&$t(t.key)?Ft(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:a,...At(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 Dt(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 zt(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 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: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 Vt(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=>se(e.url,"zhipin"))[0],a)}async function Ht(e,t,n,a,r){try{const o=t[t.length-1],i=await Vt(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 Ht(e,t,n,a,o);const i=r-(Date.now()-s);if(i<=0)break;await Ot(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Kt(e){if(!At(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 Wt(e,t){if(!At(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!At(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=At(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 Yt(e){return Kt(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Gt(e){return Wt(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 Xt(e){return Wt(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Zt(e){return await e.bringToFront(),!0}async function Qt(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 en(e,t,n,a,r,o){await Ht(e,t,n,a,o);const i=await Ut(e,t,Tt[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 tn(e,t,n,a){switch(e){case"native-page-bring-front":{const r=await Dt(e,async()=>await Zt(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 Dt(e,async()=>await Yt(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 Dt(e,async()=>await Xt(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 Dt(e,async()=>await Qt(t));return n.push(r.phaseResult),void 0!==r.result&&(a.input=r.result),r.phaseResult}}}async function nn(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 Dt("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 en(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(Bt(e.requestedPhase))return await tn(e.requestedPhase,o,e.phases,t),i=await en(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await Dt("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await en(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 Dt("native-evaluate-url",async()=>await Yt(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await en(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 Dt("native-dom-read",async()=>await Gt(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await en(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{a?.close()}}function an(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 rn(e,t,n){const a=[],r=[],o=e=>{a.length<n&&a.push(e)},i=e=>{const t=e.url(),n=an(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=an(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 Ot(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:a,navigationEvents:r}}function on(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 sn(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function cn(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 ln(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},kt)).map(e=>jt(e.area,{key:e.key,value:e.value}))}async function dn(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function un(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 mn(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function gn(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function pn(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function fn(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function hn(e,t){const n=mn(e),a=mn(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 gn(t?.numericFields,r?.numericFields)){const n=t?.numericFields?.[e],a=r?.numericFields?.[e];n!==a&&(s[e]=pn(n,a))}const c={};for(const e of gn(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 gn(t?.arrayLengths,r?.arrayLengths)){const n=t?.arrayLengths?.[e],a=r?.arrayLengths?.[e];n!==a&&(l[e]=fn(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 bn(e,t){const[n,a,r]=await Promise.all([cn(e,"localStorage"),cn(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>$t(e.key)).map(e=>jt("localStorage",e)),...a.filter(e=>$t(e.key)).map(e=>jt("sessionStorage",e))];return{localStorage:n.map(e=>jt("localStorage",e)),sessionStorage:a.map(e=>jt("sessionStorage",e)),cookies:r.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>Lt(e)),counterDiffs:hn(t,o)}}var yn=Xe({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:Ct,output:It,execute:async(e,t)=>{const n=e.phase??"native",a=e.watchMs??3e3,r=e.networkEventLimit??30,o=h(),i=f(),s=[],c=[],l=[];let d,u,m,g=!1,p=!1,b=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const y=await Dt("native",async()=>await o.listNativePages());c.push(y.phaseResult);const v=y.result??[],w=v.map(e=>le(o,e)),x=zt(v,e.targetPageId,s);if(x&&(d=le(o,x),b=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:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Ut(o,l,"native-watch",b,a,s),{success:y.phaseResult.success&&void 0!==b,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};if(!x)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:w,browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};if(_t(n)){const e=await nn({requestedPhase:n,target:x,runtime:i,ctxManager:o,targetPageId:b,watchMs:a,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}const S=await Dt("browser-attach",async()=>await i.getBrowser());c.push(S.phaseResult);const R=S.result;g=S.phaseResult.success,await Ht(o,l,"browser-attach",b,s);const C=await Ut(o,l,"browser-attach-watch",b,a,s);if(C&&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)&&!C,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};if(C)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};const I=[];let k;"network-watch"===n&&void 0!==R&&(k=on(R,x),k?I.push(rn(k,a,r)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",x);const M=await Dt("page-attach",async()=>await o.getPage("zhipin"));c.push(M.phaseResult);const N=M.result;if(p=M.phaseResult.success,N&&(d=await de(o,N)),await Ht(o,l,"page-attach",b,s),!M.phaseResult.success||!N){if("network-watch"===n&&I.length>0){const e=await Dt("network-watch",async()=>sn(await Promise.all(I),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:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,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:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){k!==N&&I.push(rn(N,a,r));const e=await Dt("network-watch",async()=>sn(await Promise.all(I),r));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await Ht(o,l,"network-watch",b,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let P=[];if("storage-summary"===n){try{P=await ln(N)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}a>0&&await Ot(a)}const T=await Dt("page-evaluate",async()=>await dn(N));c.push(T.phaseResult);const E=T.result;if(await Ht(o,l,"page-evaluate",b,s),!T.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},phases:c,warnings:s};const A=await Dt("detector-fingerprint",async()=>await un(N));c.push(A.phaseResult);const _=A.result;if(await Ht(o,l,"detector-fingerprint",b,s),!A.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==_?{detectorFingerprint:_}:{},phases:c,warnings:s};const B=await Dt("storage-summary",async()=>await bn(N,P));c.push(B.phaseResult);const q=B.result;return await Ht(o,l,"storage-summary",b,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:g,pageAttached:p,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==_?{detectorFingerprint:_}:{},...void 0!==q?{storage:q}:{},phases:c,warnings:s}}});import{defineTool as vn}from"@roll-agent/sdk";import{z as wn}from"zod";var xn=14,Sn=720;function Rn(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 readCursorPath = () => {\n if (!Array.isArray(args.cursor?.path?.points)) return null;\n const points = args.cursor.path.points\n .map((point) => {\n const x = Number(point?.x);\n const y = Number(point?.y);\n return Number.isFinite(x) && Number.isFinite(y)\n ? { centerX: Math.round(x), centerY: Math.round(y) }\n : null;\n })\n .filter((point) => point !== null);\n return points.length > 0 ? points : null;\n };\n\n const readCursorClick = () => {\n const point = args.cursor?.click?.point;\n const x = Number(point?.x);\n const y = Number(point?.y);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return {\n point: { centerX: Math.round(x), centerY: Math.round(y) },\n durationMs: Math.max(Number(args.cursor?.click?.durationMs ?? 620), 0)\n };\n };\n\n const renderClickPulse = (root, point, durationMs) => {\n const createPulse = (size, border, background, shadow, startScale, endScale, delayMs) => {\n const radius = size / 2;\n const pulse = document.createElement("div");\n pulse.setAttribute("aria-hidden", "true");\n pulse.style.position = "fixed";\n pulse.style.left = "0";\n pulse.style.top = "0";\n pulse.style.width = size + "px";\n pulse.style.height = size + "px";\n pulse.style.borderRadius = "9999px";\n pulse.style.border = border;\n pulse.style.background = background;\n pulse.style.boxShadow = shadow;\n pulse.style.pointerEvents = "none";\n pulse.style.opacity = "0.96";\n pulse.style.transformOrigin = "center center";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + startScale + ")";\n pulse.style.transition =\n "transform " + durationMs + "ms cubic-bezier(0.16, 1, 0.3, 1), opacity " + durationMs + "ms ease-out";\n root.append(pulse);\n\n window.setTimeout(() => {\n window.requestAnimationFrame(() => {\n pulse.style.opacity = "0";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + endScale + ")";\n });\n }, delayMs);\n\n window.setTimeout(() => {\n pulse.remove();\n }, durationMs + delayMs + 80);\n };\n\n createPulse(\n 28,\n "3px solid rgba(45, 212, 191, 0.98)",\n "rgba(20, 184, 166, 0.24)",\n "0 0 0 5px rgba(20, 184, 166, 0.18), 0 0 26px rgba(20, 184, 166, 0.34)",\n 0.55,\n 2.35,\n 0\n );\n createPulse(\n 14,\n "2px solid rgba(255, 255, 255, 0.95)",\n "rgba(255, 255, 255, 0.34)",\n "0 0 18px rgba(45, 212, 191, 0.45)",\n 0.7,\n 1.55,\n 65\n );\n };\n\n const renderCursor = () => {\n const path = readCursorPath();\n const click = readCursorClick();\n const point = path ? path[path.length - 1] : click?.point ?? null;\n if (!point) return;\n const root = ensureCursorRoot();\n const pointer = document.getElementById(cursorPointerId);\n if (!pointer) return;\n const targetX = point.centerX - 2;\n const targetY = point.centerY - 2;\n if (path && path.length > 1 && typeof pointer.animate === "function") {\n for (const animation of pointer.getAnimations()) {\n animation.cancel();\n }\n pointer.style.opacity = "1";\n const keyframes = path.map((entry) => ({\n transform: "translate(" + (entry.centerX - 2) + "px, " + (entry.centerY - 2) + "px)"\n }));\n const duration = Math.max(args.cursor.path.durationMs ?? 180, 0);\n const animation = pointer.animate(keyframes, {\n duration,\n easing: "cubic-bezier(0.22, 1, 0.36, 1)",\n fill: "forwards"\n });\n animation.onfinish = () => {\n pointer.style.transform = "translate(" + targetX + "px, " + targetY + "px)";\n };\n window[cursorStateKey] = { x: point.centerX, y: point.centerY };\n if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n return;\n }\n pointer.style.transition = "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 if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n };\n\n renderActivity();\n renderCursor();\n return true;\n })()`}var Cn=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??xn,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){z()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){z()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=Sn){return!!X()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=Sn){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(Rn(e))}catch{return!1}}},In=28,kn=90,Mn=250,Nn=620,Pn=8,Tn=32,En=8;function An(e){return new Promise(t=>{setTimeout(t,e)})}function _n(e,t,n){return Math.min(Math.max(e,t),n)}function Bn(e,t){return Math.max(0,Math.floor(e??t))}function qn(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Fn(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function On(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function $n(e){const t=e.x>=96?-72:72,n=e.y>=72?-36:36;return{x:Math.max(1,e.x+t),y:Math.max(1,e.y+n)}}function jn(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function Ln(e,t){const n=Fn(e,t);if(n<=Pn)return[e,t];const a=_n(Math.round(n/30),En,Tn),r=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=r/i,l=_n(.08*n,4,28)*jn(e,t),d=[e];for(let i=1;i<a;i+=1){const u=i/a,m=On(u),g=Math.sin(Math.PI*u)*l,p=Math.sin(.13*(e.x+t.y+17*i))*Math.min(1.8,n/180);d.push({x:Math.round(e.x+r*m+s*(g+p)),y:Math.round(e.y+o*m+c*(g+p))})}return d.push(t),d}function Dn(e,t){const n=qn(t);return Ln(void 0===e?$n(n):qn(e),n)}var zn=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??An,this.defaultStepDelayMs=Bn(t.stepDelayMs,In)}reset(e){this.lastPoint=void 0===e?void 0:qn(e)}async moveTo(e,t={}){const n=Bn(t.stepDelayMs,this.defaultStepDelayMs),a=Dn(this.lastPoint,e),r=Math.max(a.length-1,0)*n;await(t.motionObserver?.previewMouseMotion({points:a,durationMs:r}));for(let e=0;e<a.length;e+=1){const r=a[e];void 0!==r&&(await this.dispatcher.dispatchMouseEvent({type:"mouseMoved",x:r.x,y:r.y,...void 0!==t.button?{button:t.button}:{},buttons:t.buttons??0}),e<a.length-1&&n>0&&await this.sleep(n))}return this.lastPoint=qn(e),{points:a,durationMs:r}}async click(e,t={}){const n=t.button??"left",a=t.clickCount??1;await this.moveTo(e,{button:"none",buttons:0,...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{}}),await this.delayIfPositive(t.preClickDelayMs),await(t.motionObserver?.previewMouseClick?.({point:qn(e),durationMs:Nn})),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:"left"===n?1:0,clickCount:a}),await this.delayIfPositive(t.pressDurationMs??kn),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:0,clickCount:a}),await this.delayIfPositive(t.settleMs??Mn),this.lastPoint=qn(e)}async drag(e,t,n={}){await this.moveTo(e,{button:"none",buttons:0,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:"left",buttons:1,clickCount:1}),await this.delayIfPositive(n.pressDurationMs??kn),this.reset(e),await this.moveTo(t,{button:"left",buttons:1,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(t.x),y:Math.round(t.y),button:"left",buttons:0,clickCount:1}),this.lastPoint=qn(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}};import{setTimeout as Jn}from"node:timers/promises";async function Vn(e,t=300,n=800){const a=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(a)}function Hn(e){return e.trim().toLocaleLowerCase("zh-CN")}function Un(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=Hn(n),a=e.filter(e=>e.name.length>0);let r=a.find(e=>Hn(e.name)===t);if(r)return r;if(r=a.find(e=>{const n=Hn(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=Hn(e.name);return Un(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),r)return r}if(void 0!==t.index)return e[t.index]}var Wn=["chat-list","chat-history","recommend-list"],Yn={"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 Gn(e){return Yn[e]}var Xn={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"}},Zn=30,Qn=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function ea(e){const t=e.trim();return!(0===t.length||t.length>Zn)&&(!Qn.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function ta(e){let t=e.priority;return Qn.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 na(e){const t=e.filter(e=>ea(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=ta(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 aa='.user-list.b-scroll-stable [role="listitem"], .geek-item',ra='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',oa=".candidate-card-wrap",ia="[data-geek], .geek-item",sa=`${oa}, ${ia}`,ca=["/web/chat/recommend"],la=250,da=300,ua=900,ma=1200,ga=4,pa=4,fa=250,ha=90,ba=520,ya=16,va=50,wa=650,xa=10,Sa=.015,Ra="button, a, label, li, span, div, [role='button'], [role='radio']";function Ca(e){return new Promise(t=>{setTimeout(t,e)})}function Ia(e){return{x:e.x,y:e.y}}function ka(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ma(e){return e.includes("/web/chat/index")}function Na(e){return e.trim().toLocaleLowerCase("zh-CN")}function Pa(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function Ta(e,t){const n=Na(e),a=Na(t);return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}function Ea(e){return"string"==typeof e?e:""}function Aa(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function _a(e){return"boolean"==typeof e&&e}function Ba(e){return Array.isArray(e)?e.map((e,t)=>{const n=ka(e)?e:{};return{conversationId:Ea(n.conversationId),candidateId:Ea(n.candidateId),name:Ea(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:Ea(n.position),hasUnread:_a(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:Ea(n.lastMessageTime),messagePreview:Ea(n.messagePreview)}}):[]}function qa(e){return Array.isArray(e)?e.flatMap(e=>{if(!ka(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 Fa(e){return Array.isArray(e)?e.map((e,t)=>{const n=ka(e)?e:{},a=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:Ea(n.candidateId),name:Ea(n.name),age:Ea(n.age),experience:Ea(n.experience),education:Ea(n.education),workStatus:Ea(n.workStatus),company:Ea(n.company),currentPosition:Ea(n.currentPosition),expectedLocation:Ea(n.expectedLocation),expectedPosition:Ea(n.expectedPosition),expectedSalary:Ea(n.expectedSalary),tags:Array.isArray(a)?a.map(e=>Ea(e)).filter(Boolean):[],buttonText:Ea(n.buttonText)}}):[]}function Oa(e){return Array.isArray(e)?e.map((e,t)=>{const n=ka(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:Ea(n.value),label:Ea(n.label),isCurrent:_a(n.isCurrent)}}):[]}function $a(e){return ka(e)?{found:_a(e.found),isOpen:_a(e.isOpen),currentLabel:Ea(e.currentLabel),currentValue:Ea(e.currentValue),options:Oa(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function ja(e){if(!ka(e))return null;const t=Ea(e.conversationId),n=Ea(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:Ea(e.candidateName)}}function La(e){if(!ka(e))return null;const t=Ea(e.candidateName);return t.length>0?{candidateName:t}:null}function Da(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!ka(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:Ea(e.content),time:Ea(e.time)}]}):[]}function za(e){const t=ka(e)?e:{},n=t.tags;return{name:Ea(t.name),age:Ea(t.age),experience:Ea(t.experience),education:Ea(t.education),communicationPosition:Ea(t.communicationPosition),expectedJobText:Ea(t.expectedJobText),expectedSalary:Ea(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>Ea(e)).filter(Boolean):[]}}function Ja(e){const t=ka(e)?e:{};return{selectedTarget:ja(t.selectedTarget),activePanel:La(t.activePanel),candidateInfo:za(t.candidateInfo),messages:Da(t.messages)}}function Va(e){return ka(e)?{found:_a(e.found),x:Aa(e.x),y:Aa(e.y)}:{found:!1,x:0,y:0}}function Ha(e){return ka(e)?{found:_a(e.found),left:Aa(e.left),top:Aa(e.top)}:{found:!1,left:0,top:0}}function Ua(e){return ka(e)?{found:_a(e.found),cardSelector:Ea(e.cardSelector)||oa,candidateId:Ea(e.candidateId),name:Ea(e.name),..."string"==typeof e.age?{age:e.age}:{},..."string"==typeof e.experience?{experience:e.experience}:{},..."string"==typeof e.education?{education:e.education}:{},..."string"==typeof e.workStatus?{workStatus:e.workStatus}:{},..."string"==typeof e.company?{company:e.company}:{},..."string"==typeof e.currentPosition?{currentPosition:e.currentPosition}:{},..."string"==typeof e.expectedLocation?{expectedLocation:e.expectedLocation}:{},..."string"==typeof e.expectedPosition?{expectedPosition:e.expectedPosition}:{},..."string"==typeof e.expectedSalary?{expectedSalary:e.expectedSalary}:{},hasGreetButton:_a(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:oa,candidateId:"",name:"",hasGreetButton:!1}}function Ka(e){return ka(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 Wa(e){return ka(e)&&!0===e.ok?{ok:!0,current:Ka(e.current),trackLeft:Aa(e.trackLeft),trackTop:Aa(e.trackTop),trackWidth:Aa(e.trackWidth),trackHeight:Aa(e.trackHeight),minHandleX:Aa(e.minHandleX),minHandleY:Aa(e.minHandleY),maxHandleX:Aa(e.maxHandleX),maxHandleY:Aa(e.maxHandleY)}:{ok:!1,error:ka(e)&&Ea(e.error)||"未找到年龄滑块"}}function Ya(e){return ka(e)?{containerFound:_a(e.containerFound),containerLabel:Ea(e.containerLabel),scrollTop:Aa(e.scrollTop),scrollHeight:Aa(e.scrollHeight),clientHeight:Aa(e.clientHeight),itemCount:Aa(e.itemCount),atStart:_a(e.atStart),atEnd:_a(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Ga(e){return[e,...(e.childFrames??[]).flatMap(e=>Ga(e))]}function Xa(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 Za(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 Qa(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&&Ma(r.url))return r;const e=a.filter(e=>Ma(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 er(e={},t={}){const n=t.ctxManager??h(),a=t.runtime??f(),r=Qa(n,await n.listNativePages(),e),o=await a.connectNativePage(r);return new tr({target:r,controller:o})}var tr=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new zn(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 Ca(la)}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(ra)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await Ca(la)}return!1}async isRecommendSurfaceOpen(){const e=[Xn.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(ca)};\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 Ca(la)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Ga(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 Ha(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Xn.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=Va(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Va(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 this.mouse.click(Ia(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??ha,settleMs:t.settleMs??fa}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(sa)}) !== 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 Ca(la)}else await Ca(la);return await this.hasRecommendList().catch(()=>!1)}async readRecommendJobSelectorState(){const e='(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const text = (element) => (element.textContent ?? "").replace(/\\s+/g, " ").trim();\n const wrap = document.querySelector(".job-selecter-wrap");\n if (!wrap) {\n return {\n found: false,\n isOpen: false,\n currentLabel: "",\n currentValue: "",\n options: []\n };\n }\n\n const list = wrap.querySelector(".ui-dropmenu-list");\n const options = Array.from(wrap.querySelectorAll(".job-list .job-item")).map(\n (item, index) => {\n const labelElement = item.querySelector(".label") ?? item;\n return {\n index,\n value: item.getAttribute("value") ?? item.getAttribute("data-value") ?? "",\n label: text(labelElement),\n isCurrent: item.classList.contains("curr")\n };\n },\n );\n const current = options.find((option) => option.isCurrent);\n const label = wrap.querySelector(".ui-dropmenu-label");\n return {\n found: true,\n isOpen: Boolean(list && visible(list)),\n currentLabel: current?.label ?? (label ? text(label) : ""),\n currentValue: current?.value ?? "",\n options\n };\n })()',t=$a(await this.evaluateJson(e).catch(()=>{}));return t.found?t:$a(await this.evaluateRecommendFrameJson(e))}async openRecommendJobSelector(e={}){if((await this.readRecommendJobSelectorState()).isOpen)return!0;const t=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const wrap = document.querySelector(".job-selecter-wrap");\n const label = wrap?.querySelector(".ui-dropmenu-label") ?? wrap;\n if (!label || !visible(label)) {\n return { found: false, x: 0, y: 0 };\n }\n label.scrollIntoView({ block: "center", inline: "center" });\n const rect = label.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 })()');return!!await this.dispatchNativeClick(t,e)&&(await Ca(900),(await this.readRecommendJobSelectorState()).isOpen)}async setRecommendJobSearch(e,t={}){const n=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const input = document.querySelector(".job-selecter-wrap .top-chat-search input.ipt.chat-job-search");\n if (!(input instanceof HTMLInputElement) || !visible(input)) {\n return { found: false, x: 0, y: 0 };\n }\n input.scrollIntoView({ block: "center", inline: "center" });\n const rect = input.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(n,t))return!1;if(await Ca(260),await this.selectAllFocusedText(),await Ca(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await Ca(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await Ca(500),!0;await Ca(350);for(const t of Array.from(e))await this.controller.insertText(t),await Ca(110);return await Ca(650),!0}selectRecommendJobMatch(e,t){const n=e=>{const t=e[0];return{...void 0!==t?{selected:t}:{},matchedCount:e.length}};if(void 0!==e.jobValue){return n(t.filter(t=>t.value===e.jobValue))}if(void 0!==e.jobName){const a=Pa(e.jobName),r=t.filter(e=>Pa(e.label)===a);if(r.length>0)return n(r);return n(t.filter(e=>Pa(e.label).includes(a)))}if(void 0!==e.index){return n(t.filter(t=>t.index===e.index))}return{matchedCount:0}}getCurrentRecommendJobOption(e){const t=e.options.find(e=>e.isCurrent);return void 0!==t?t:0!==e.currentLabel.length||0!==e.currentValue.length?{index:-1,value:e.currentValue,label:e.currentLabel,isCurrent:!0}:void 0}currentRecommendJobMatchesRequest(e,t){if(void 0===t)return!1;if(void 0!==e.jobValue&&t.value.length>0)return t.value===e.jobValue;if(void 0!==e.jobName&&t.label.length>0){const n=Pa(e.jobName),a=Pa(t.label);return a===n||a.includes(n)}return void 0!==e.index&&t.index===e.index}hasRecommendJobAlternative(e,t){return t.some(n=>!n.isCurrent&&(void 0===e?t.length>1:n.value.length>0&&e.value.length>0?n.value!==e.value:n.label!==e.label||n.index!==e.index))}async clickRecommendJobOption(e,t={}){const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedValue = ${JSON.stringify(e.value)};\n const expectedIndex = ${JSON.stringify(e.index)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const items = Array.from(document.querySelectorAll(".job-selecter-wrap .job-list .job-item"));\n const item = items.find((candidate, index) => {\n const value = candidate.getAttribute("value") ?? candidate.getAttribute("data-value") ?? "";\n return expectedValue.length > 0 ? value === expectedValue : index === expectedIndex;\n });\n if (!item || !visible(item)) {\n return { found: false, x: 0, y: 0 };\n }\n item.scrollIntoView({ block: "center", inline: "center" });\n const rect = item.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 })()`);return await this.dispatchNativeClick(n,t)}async listRecommendJobs(e={}){if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",options:[],availableCount:0,canSwitch:!1,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(e)){const e=await this.readRecommendJobSelectorState(),t=this.getCurrentRecommendJobOption(e);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",...void 0!==t?{current:t}:{},options:e.options,availableCount:e.options.length,canSwitch:!1,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",e),await Ca(700);const t=await this.readRecommendJobSelectorState(),n=this.getCurrentRecommendJobOption(t);return{success:!0,status:"listed",...void 0!==n?{current:n}:{},options:t.options,availableCount:t.options.length,canSwitch:this.hasRecommendJobAlternative(n,t.options)}}async selectRecommendJob(e,t={}){const n={...e};if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",requested:n,options:[],matchedCount:0,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(t)){const e=await this.readRecommendJobSelectorState(),t=e.options.find(e=>e.isCurrent);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",requested:n,...void 0!==t?{current:t}:{},options:e.options,matchedCount:0,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",t),await Ca(700);let a=await this.readRecommendJobSelectorState();const r=this.getCurrentRecommendJobOption(a);if(void 0!==r&&this.currentRecommendJobMatchesRequest(n,r)&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:r,selected:r,options:a.options,matchedCount:1};let o=this.selectRecommendJobMatch(n,a.options);const i=n.searchKeyword??n.jobName;void 0===o.selected&&!1!==n.useSearch&&void 0!==i&&i.trim().length>0&&await this.setRecommendJobSearch(i,t)&&(await Ca(900),a=await this.readRecommendJobSelectorState(),o=this.selectRecommendJobMatch(n,a.options));const s=this.getCurrentRecommendJobOption(a);if(void 0===o.selected)return{success:!1,status:"not_found",requested:n,...void 0!==s?{current:s}:{},options:a.options,matchedCount:o.matchedCount,error:"未找到匹配的招聘岗位"};if(o.selected.isCurrent&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:o.selected,selected:o.selected,options:a.options,matchedCount:o.matchedCount};if(!await this.clickRecommendJobOption(o.selected,t))return{success:!1,status:"selector_not_found",requested:n,...void 0!==s?{current:s}:{},selected:o.selected,options:a.options,matchedCount:o.matchedCount,error:"未能点击匹配的招聘岗位"};let c=await this.readRecommendJobSelectorState();for(let e=0;e<6;e+=1){const e=this.getCurrentRecommendJobOption(c);if(e?.value===o.selected.value||e?.label===o.selected.label)break;await Ca(600),c=await this.readRecommendJobSelectorState()}const l=c.options.find(e=>e.value===o.selected?.value)??o.selected;return{success:!0,status:"selected",requested:n,current:l,selected:l,options:c.options.length>0?c.options:a.options,matchedCount:o.matchedCount}}async clickSidebarSection(e,t={}){const n="chat"===e?Xn.nav.chatLink:Xn.nav.recommendLink,a=Va(await this.evaluateJson(`(() => {\n const selector = ${JSON.stringify(n)};\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(Xn.nav.sidebar)}) ?? document;\n const selectorTargets = Array.from(document.querySelectorAll(selector))\n .filter((element) => visible(element))\n .sort((left, right) => area(left) - area(right));\n if (selectorTargets[0]) {\n return readCenter(selectorTargets[0]);\n }\n\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 this.dispatchNativeClick(a,t)}async scrollSurface(e,t={}){const n=Gn(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 Ca(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 Ca(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 Ca(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=Xa(a);if(r.length>=t||e===n)return r;if(!(await this.scrollChatList()).ok)return r;await Ca(da)}return Xa(a)}async openChat(e){if(await this.bringToFront().catch(()=>{}),!await this.isChatSurfaceOpen().catch(()=>!1)){if(!await this.clickSidebarSection("chat",{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})||!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=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index,n=Math.max(0,Math.floor(e.maxScrolls??(t?12:4))),a=async t=>{if(!await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}}))return{...t,found:!1,error:`未能点击候选人: ${t.name||t.conversationId}`};await Ca(fa);let n=await this.waitForNativeChatReady(t);if(!n){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await Ca(fa),n=await this.waitForNativeChatReady(t))}return n?{...t,found:!0}:{...t,found:!1,error:`打开候选人聊天后,右侧会话未同步切换到 ${t.name||t.conversationId}`}},r=async(n,r)=>{const o=[];for(let i=0;i<=r;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Xa(o),c=!0!==e.preferUnread||t?Kn(s,e):s.find(e=>e.hasUnread);if(void 0!==c)return await a(c);if(i>=r)break;if(!(await this.scrollChatList(n)).ok)break;await Ca(da)}},o=await r("down",t?0:n);if(void 0!==o)return o;if(t&&n>0){for(let e=0;e<n;e+=1){const e=await this.scrollChatList("up");if(!e.ok||e.after<=1||e.after>=e.before)break;await Ca(da)}const e=await r("down",n);if(void 0!==e)return e}return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${e.candidateName??(void 0!==e.conversationId?`conversationId ${e.conversationId}`:`index ${e.index??0}`)}`}}async readSelectedChatTarget(){return ja(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 La(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 Ca(la)}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 Ja(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=Ua(await this.evaluateJson(n).catch(()=>{}));if(a.found)return a;const r=Ua(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=Va(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=Va(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 Ca(1800);const a=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==a?{wechatNumber:a}:{}}}async sendChatReply(e,t={}){const n=Va(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 Ca(250);const a=Va(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 Ca(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=Za(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<ga;t+=1){await Ca(ma);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>=pa){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:ua});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 qa(await this.evaluateJson(`(() => {\n const limit = ${Zn};\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([Xn.username.primary,...Xn.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(oa)};\n const fallbackSelector = ${JSON.stringify(ia)};\n const iframe = document.querySelector(${JSON.stringify(Xn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(ca)};\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 splitParts = (text) => (text ?? "")\n .split(/[丨·|]/)\n .map((part) => part.trim())\n .filter(Boolean);\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 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 = splitParts(baseInfoEl.textContent?.trim() ?? "");\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 const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = splitParts(workExpEl?.textContent?.trim() ?? "");\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\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 = splitParts(contentEl.textContent?.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 = splitParts(expectEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\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 {\n found: true,\n cardSelector,\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 hasGreetButton\n };\n })()`}buildRecommendGreetClickExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(oa)};\n const fallbackSelector = ${JSON.stringify(ia)};\n const iframe = document.querySelector(${JSON.stringify(Xn.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 Ca(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 Ca(40)}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${Xn.recommend.filterButton}, ${oa}, ${Xn.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 Ca(la)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Xn.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(Xn.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 Ca(la)}}await Ca(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(Xn.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Ra)};\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(Xn.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(ya)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(va)} -\n ${JSON.stringify(ya)})\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=Ka(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:Ka(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Xn.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=Wa(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Wa(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 this.mouse.drag({x:e,y:t},{x:n,y:a},{...void 0!==r.motionObserver?{motionObserver:r.motionObserver}:{},pressDurationMs:r.pressDurationMs??ha})}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 Ca(wa),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-ya)/(va-ya)))}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+Sa),s="min"===e?Math.max(0,o-Sa):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let a=0;a<xa;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??ya,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(Xn.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Ra)};\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(Xn.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 Ca(600),!0;await Ca(la)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Xn.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 Ba(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(aa)}));\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&&Ta(e.name,n.candidateName);if(a&&r)return!0;await Ca(la)}return!1}async clickChatCandidate(e,t={}){const n=Va(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(aa)}));\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!!n.found&&await this.dispatchNativeClick(n,t)}async readVisibleRecommendCandidates(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return[];const e=`(() => {\n const iframe = document.querySelector(${JSON.stringify(Xn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(ca)};\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(oa)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(ia)}));\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=Fa(await this.evaluateJson(e));if(t.length>0)return t;return Fa(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=Gn(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(Xn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(ca)};\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=Ya(await this.evaluateJson(n));if("recommend-list"!==e||a.itemCount>0)return a;const r=Ya(await this.evaluateRecommendFrameJson(n));return r.itemCount>0?r:a}async scrollSurfaceWithWheel(e,t,n){const a=Gn(e);await this.bringToFront();const r=Va(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(Xn.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.mouse.moveTo(Ia(r)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:r.x,y:r.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??ba):n??ba}),await Ca(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=Gn(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(Xn.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(ca)};\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=Ya(await this.evaluateJson(r));if("recommend-list"!==e||o.itemCount>0)return o;const i=Ya(await this.evaluateRecommendFrameJson(r));return i.itemCount>0?i:o}async scrollChatList(e="down"){const t=await this.scrollSurface("chat-list",{direction:e,steps:1,settleMs:0});return{ok:t.success&&t.stepsCompleted>0,before:t.before.scrollTop,after:t.after.scrollTop,max:Math.max(0,t.after.scrollHeight-t.after.clientHeight)}}};import{createHash as nr}from"node:crypto";var ar,rr,or={fetch:fetch,env:process.env},ir="https://huajune.duliday.com",sr=new Set;function cr(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function lr(e,t){const n=cr(e,t);if(void 0!==n){if("true"===n)return!0;if("false"===n)return!1;throw new Error(`Expected boolean env value "true" or "false" for ${t}, received "${n}".`)}}function dr(e){const t=lr(e,"RECRUITMENT_EVENTS_ENABLED"),n=cr(e,"RECRUITMENT_EVENTS_API_BASE_URL")??ir,a=cr(e,"RECRUITMENT_EVENTS_API_TOKEN"),r=cr(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==a?{apiToken:a}:{},...void 0!==r?{defaultAgentId:r}:{}}}function ur(e){return e.defaultAgentId}function mr(e,t,n){sr.has(t)||(sr.add(t),e.warn(n))}function gr(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function pr(e){return"object"==typeof e&&null!==e}function fr(e,t){const n=e[t];return"string"==typeof n?n:void 0}function hr(e){if(!pr(e)||!pr(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>pr(e)&&"error"===fr(e,"status"));if(!pr(t))return;const n=fr(t,"idempotencyKey"),a=t.error,r=pr(a)?fr(a,"code"):void 0,o=pr(a)?fr(a,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==r?` ${r}`:""}${void 0!==o?`: ${o}`:""}`}async function br(e,t,n){const a=dr(n.env);if(!a.enabled)return;const r=ur(a);if(void 0===a.apiToken||void 0===r)return void mr(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN and RECRUITMENT_EVENTS_DEFAULT_AGENT_ID.");const o={events:[{...e,agentId:r,eventTime:e.eventTime??(new Date).toISOString()}]},i=await n.fetch(gr(a.apiBaseUrl),{method:"POST",headers:{Authorization:`Bearer ${a.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!i.ok){const e=await i.text().catch(()=>"");throw new Error(`Recruitment events API request failed (${i.status})${e.length>0?`: ${e}`:""}`)}const s=hr(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function yr(e,t){return`${e}:${nr("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function vr(e,t){(async()=>{const n=rr??(async(e,t)=>{await br(e,t,{...or,...ar})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function wr(){return(new Date).toISOString().slice(0,10)}function xr(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function Sr(e,t,n){const a=e?.candidateInfo,r=xr(a?.age),o=xr(a?.education),i=xr(a?.expectedSalary);return{name:xr(a?.name)??t,position:n,...void 0!==r?{age:r}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function Rr(e){return xr(e?.candidateInfo.communicationPosition)??""}function Cr(e){const t=xr(e?.candidateInfo.expectedJobText);if(void 0===t)return"";const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?n[1]??"":t}function Ir(e){const t=xr(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?xr(n[0]):void 0}function kr(e){return e.length>0?{job:{jobName:e}}:{}}function Mr(e,t){return e.trim().length>0&&t.trim().length>0}function Nr(e){const t=e.content.trim(),n=/微信(?:号)?\s*[::]?\s*([A-Za-z0-9_-]{5,})/.exec(t);return void 0!==n?.[1]?n[1]:/^[A-Za-z][A-Za-z0-9_-]{5,}$/.test(t)?t:void 0}function Pr(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&Mr(n.name,n.position)&&vr({idempotencyKey:yr("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,wr()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_received",candidate:{name:n.name,position:n.position},job:{jobId:0,jobName:n.position},details:{unreadCount:n.unreadCount>0?n.unreadCount:1,lastMessagePreview:n.preview,conversationId:n.conversationId,candidateId:n.candidateId}},t)}function Tr(e,t){const n=Rr(e.candidateDetails),a=n,r=Sr(e.candidateDetails,e.candidateName,a);if(!Mr(r.name,r.position))return;const o=Ir(e.candidateDetails);vr({idempotencyKey:yr("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...r,...void 0!==o?{expectedLocation:o}:{}},...kr(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Er(e,t){if(!e.clicked||0===e.name.length)return;const n=xr(e.expectedPosition);if(void 0===n||!Mr(e.name,n))return;const a=xr(e.age),r=xr(e.education),o=xr(e.expectedSalary),i=xr(e.expectedLocation);vr({idempotencyKey:yr("zhipin-candidate-contacted",[e.candidateId,e.name,wr()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"candidate_contacted",candidate:{name:e.name,position:n,...void 0!==a?{age:a}:{},...void 0!==r?{education:r}:{},...void 0!==o?{expectedSalary:o}:{},...void 0!==i?{expectedLocation:i}:{}},details:{candidateId:e.candidateId}},t)}function Ar(e,t){const n=Rr(e.candidateDetails),a=Sr(e.candidateDetails,e.candidateName,n);if(!Mr(a.name,a.position))return;const r=Ir(e.candidateDetails);vr({idempotencyKey:yr("zhipin-wechat-requested",[e.conversationId,e.candidateId,wr()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...a,...void 0!==r?{expectedLocation:r}:{}},...kr(n),details:{exchangeType:e.exchangeType??(void 0!==e.wechatNumber?"accepted":"requested"),conversationId:e.conversationId,candidateId:e.candidateId,...void 0!==e.wechatNumber?{wechatNumber:e.wechatNumber}:{}}},t)}function _r(e,t,n,a){const r=Rr(e),o=Cr(e),i=Ir(e),s=Sr(e,e.candidateInfo.name,o);if(!Mr(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=Nr(c);vr({idempotencyKey:yr("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...kr(r),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},a)}var Br,qr=wn.object({name:wn.string(),conversationId:wn.string(),candidateId:wn.string(),position:wn.string(),time:wn.string(),preview:wn.string(),unreadCount:wn.number(),hasUnread:wn.boolean(),index:wn.number()}),Fr=wn.object({success:wn.boolean(),candidates:wn.array(qr),total:wn.number(),stats:wn.object({withName:wn.number(),withUnread:wn.number()})});function Or(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Br}}function $r(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 jr(e){const t=new Set,n=[];for(const a of e){const e=$r(a);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(a))}return n}var Lr=vn({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:wn.object({limit:wn.number().optional().describe("最多返回条数"),onlyUnread:wn.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:wn.enum(["time","unreadCount","name"]).default("time"),autoScroll:wn.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:wn.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Fr,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=Or();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=jr(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)`),Pr(d,t.logger),{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 Dr}from"@roll-agent/browser";import{defineTool as zr}from"@roll-agent/sdk";import{z as Jr}from"zod";var Vr,Hr=Jr.object({success:Jr.boolean(),alreadyOnChat:Jr.boolean(),usedSidebarClick:Jr.boolean(),chatReady:Jr.boolean(),page:Dr.optional(),error:Jr.string().optional()});function Ur(){return{getContextManager:h,openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Vr}}async function Kr(e,t){return le(e,await t.inspectPage())}var Wr=zr({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测。",input:Jr.object({}),output:Hr,execute:async(e,t)=>{const n=Ur(),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(Xn.nav.sidebar,{label:e,padding:10}),await r.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,chatReady:!0,page:await Kr(a,r)};if(!await r.clickSidebarSection("chat",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1,page:await Kr(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 Kr(a,r)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1,page:await Kr(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 Yr}from"@roll-agent/sdk";import{z as Gr}from"zod";var Xr,Zr=Gr.object({success:Gr.boolean(),conversationId:Gr.string(),candidateId:Gr.string(),candidateName:Gr.string(),index:Gr.number(),hasUnread:Gr.boolean(),unreadCount:Gr.number(),lastMessageTime:Gr.string(),messagePreview:Gr.string(),error:Gr.string().optional()});function Qr(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Xr}}var eo=Yr({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Gr.object({conversationId:Gr.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Gr.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Gr.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Gr.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Zr,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Qr();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,...void 0!==r?{motionObserver:r}:{}});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 to}from"@roll-agent/sdk";import{z as no}from"zod";var ao="·",ro=/[--—–]/;function oo(e){return(e??"").trim()}function io(e){const t=oo(e);if(!t||!ro.test(t))return;const[n=""]=t.split(ro);return oo(n)||void 0}function so(e){const t=oo(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",a=""]=t.split(ao).map(e=>oo(e));return{expectedLocation:n,expectedPosition:a}}function co(e){const t=oo(e.communicationPosition),{expectedLocation:n,expectedPosition:a}=so(e.expectedJobText),r=io(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:a,...void 0!==r?{preferredBrand:r}:{}}}var lo,uo=no.object({index:no.number(),sender:no.enum(["candidate","recruiter","system"]),messageType:no.enum(["text","system","resume","wechat-exchange"]),content:no.string(),time:no.string()}),mo=no.object({name:no.string(),age:no.string(),experience:no.string(),education:no.string(),communicationPosition:no.string(),expectedPosition:no.string(),expectedLocation:no.string(),expectedSalary:no.string(),tags:no.array(no.string())}),go=no.object({success:no.boolean(),conversationId:no.string(),candidateId:no.string(),candidateInfo:mo,preferredBrand:no.string().optional(),chatMessages:no.array(uo),formattedHistory:no.array(no.string()),stats:no.object({totalMessages:no.number(),candidateMessages:no.number(),recruiterMessages:no.number(),systemMessages:no.number()}),error:no.string().optional()});function po(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function fo(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:po(),chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function ho(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 bo(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...lo}}var yo=to({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:no.object({conversationId:no.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:no.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:no.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:no.number().default(100).describe("最多返回的消息条数")}),output:go,execute:async(e,t)=>{const n=e.maxMessages??100,a=bo(),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)),fo(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||!ho(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&&!ho(g.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${g.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const p=await o.readCandidateChatDetails(n),f=co({communicationPosition:p.candidateInfo.communicationPosition,expectedJobText:p.candidateInfo.expectedJobText}),h=p.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),b={totalMessages:p.messages.length,candidateMessages:p.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:p.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:p.messages.filter(e=>"system"===e.sender).length};return await i.succeed(`已提取 ${b.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${p.candidateInfo.name}: ${b.totalMessages} messages`),_r(p,g.conversationId,g.candidateId,t.logger),{success:!0,conversationId:g.conversationId,candidateId:g.candidateId,candidateInfo:{name:p.candidateInfo.name,age:p.candidateInfo.age,experience:p.candidateInfo.experience,education:p.candidateInfo.education,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,expectedSalary:p.candidateInfo.expectedSalary,tags:[...p.candidateInfo.tags]},...void 0!==f.preferredBrand?{preferredBrand:f.preferredBrand}:{},chatMessages:[...p.messages],formattedHistory:h,stats:b}}catch(e){return await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),fo(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as vo}from"@roll-agent/sdk";import{GenerateSignedReplyResponseSchema as wo,ReplyStreamFinalEventSchema as xo,streamGenerateSignedReply as So}from"@roll-agent/reply-authority-client";import{z as Ro}from"zod";import{randomUUID as Co}from"node:crypto";var Io=new Map;function ko(e){for(const[t,n]of Io)n.expiresAt<=e&&Io.delete(t)}function Mo(e,t=Math.floor(Date.now()/1e3)){if(ko(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${Co()}`,a={preparedReplyId:n,...e};return Io.set(n,{...a,consumed:!1}),a}function No(e,t=Math.floor(Date.now()/1e3)){const n=Io.get(e);return void 0===n?(ko(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Io.delete(e),ko(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Io.set(e,{...n,consumed:!0}),{ok:!0,record:{preparedReplyId:n.preparedReplyId,signedEnvelope:n.signedEnvelope,suggestedReply:n.suggestedReply,stage:n.stage,confidence:n.confidence,expiresAt:n.expiresAt,...void 0!==n.requestId?{requestId:n.requestId}:{}}})}function Po(e){return`(() => {\n const input = ${JSON.stringify(e)};\n const rootId = "roll-agent-reply-preview-root";\n const statusId = "roll-agent-reply-preview-status";\n const draftId = "roll-agent-reply-preview-draft";\n const badgeId = "roll-agent-reply-preview-badge";\n const spinnerId = "roll-agent-reply-preview-spinner";\n const styleId = "roll-agent-reply-preview-style";\n\n if (input.mode === "clear") {\n const existingRoot = document.getElementById(rootId);\n if (!existingRoot) return true;\n\n existingRoot.style.opacity = "0";\n existingRoot.style.transform = "translateY(8px) scale(0.98)";\n window.setTimeout(() => existingRoot.remove(), 240);\n return true;\n }\n\n const ensureRoot = () => {\n let root = document.getElementById(rootId);\n if (root) return root;\n\n if (!document.getElementById(styleId)) {\n const style = document.createElement("style");\n style.id = styleId;\n style.textContent = "@keyframes rollAgentReplyPreviewSpin { to { transform: rotate(360deg); } }";\n document.head.append(style);\n }\n\n root = document.createElement("div");\n root.id = rootId;\n root.style.position = "fixed";\n root.style.right = "20px";\n root.style.bottom = "20px";\n root.style.width = "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = "42vh";\n root.style.zIndex = "2147483646";\n root.style.pointerEvents = "none";\n root.style.display = "flex";\n root.style.flexDirection = "column";\n root.style.gap = "10px";\n root.style.padding = "14px";\n root.style.borderRadius = "16px";\n root.style.border = "1px solid rgba(45, 212, 191, 0.32)";\n root.style.background = "rgba(15, 23, 42, 0.88)";\n root.style.color = "#F8FAFC";\n root.style.boxShadow = "0 22px 58px rgba(15, 23, 42, 0.34)";\n root.style.backdropFilter = "blur(14px)";\n root.style.fontFamily =\n "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";\n root.style.opacity = "0";\n root.style.transform = "translateY(8px)";\n root.style.transition = "opacity 180ms ease, transform 220ms ease";\n\n const header = document.createElement("div");\n header.style.display = "flex";\n header.style.alignItems = "center";\n header.style.gap = "8px";\n header.style.minWidth = "0";\n\n const spinner = document.createElement("div");\n spinner.id = spinnerId;\n spinner.setAttribute("aria-hidden", "true");\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.flex = "0 0 auto";\n spinner.style.borderRadius = "999px";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.transition =\n "border-color 160ms ease, background 160ms ease, box-shadow 160ms ease, transform 160ms ease";\n\n const status = document.createElement("div");\n status.id = statusId;\n status.style.minWidth = "0";\n status.style.fontSize = "13px";\n status.style.fontWeight = "700";\n status.style.lineHeight = "18px";\n status.style.overflow = "hidden";\n status.style.textOverflow = "ellipsis";\n status.style.whiteSpace = "nowrap";\n\n const badge = document.createElement("div");\n badge.id = badgeId;\n badge.style.alignSelf = "flex-start";\n badge.style.fontSize = "11px";\n badge.style.lineHeight = "16px";\n badge.style.padding = "3px 8px";\n badge.style.borderRadius = "999px";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n badge.textContent = "临时草稿";\n\n const draft = document.createElement("div");\n draft.id = draftId;\n draft.style.maxHeight = "30vh";\n draft.style.overflow = "hidden auto";\n draft.style.whiteSpace = "pre-wrap";\n draft.style.fontSize = "14px";\n draft.style.lineHeight = "21px";\n draft.style.color = "#E2E8F0";\n\n header.append(spinner, status);\n root.append(header, badge, draft);\n document.documentElement.append(root);\n return root;\n };\n\n const root = ensureRoot();\n const status = document.getElementById(statusId);\n const draft = document.getElementById(draftId);\n const badge = document.getElementById(badgeId);\n const spinner = document.getElementById(spinnerId);\n if (!status || !draft || !badge || !spinner) return false;\n\n root.style.opacity = "1";\n root.style.transform = "translateY(0)";\n\n if (typeof input.label === "string") {\n status.textContent = input.label;\n }\n\n if (typeof input.draftText === "string") {\n draft.textContent = input.draftText;\n }\n\n if (input.mode === "final" || input.mode === "complete") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.borderTopColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.background = "#22C55E";\n spinner.style.boxShadow = "0 0 0 3px rgba(34, 197, 94, 0.14)";\n badge.textContent = "最终回复";\n badge.style.background = "rgba(34, 197, 94, 0.16)";\n badge.style.color = "#BBF7D0";\n root.style.borderColor = "rgba(74, 222, 128, 0.34)";\n } else if (input.mode === "fail") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.borderTopColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.background = "#F59E0B";\n spinner.style.boxShadow = "0 0 0 3px rgba(245, 158, 11, 0.14)";\n badge.textContent = "生成失败";\n badge.style.background = "rgba(245, 158, 11, 0.18)";\n badge.style.color = "#FDE68A";\n root.style.borderColor = "rgba(251, 191, 36, 0.4)";\n } else {\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.background = "transparent";\n spinner.style.boxShadow = "none";\n badge.textContent = input.provisional === false ? "最终回复" : "临时草稿";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n root.style.borderColor = "rgba(45, 212, 191, 0.32)";\n }\n\n return true;\n })()`}var To,Eo=class{target;constructor(e){this.target=e}async begin(e){return await this.render({mode:"begin",label:e,draftText:"",provisional:!0})}async updateStatus(e){return await this.render({mode:"status",label:e})}async updateDraft(e,t){return await this.render({mode:t?"draft":"final",draftText:e,provisional:t})}async complete(e,t){return await this.render({mode:"complete",label:e,draftText:t,provisional:!1})}async fail(e){return await this.render({mode:"fail",label:e})}async clear(){return await this.render({mode:"clear"})}async render(e){if(!X())return!1;try{return await this.target.evaluateJson(Po(e))}catch{return!1}}},Ao=Ro.object({conversationId:Ro.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Ro.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:Ro.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Ro.number().default(100).describe("最多读取的聊天消息条数")}),_o=Ro.object({success:Ro.boolean(),preparedReplyId:Ro.string().optional(),suggestedReply:Ro.string().optional(),stage:Ro.string().optional(),confidence:Ro.number().optional(),expiresAt:Ro.number().optional(),requestId:Ro.string().optional(),error:Ro.string().optional()}),Bo={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function qo(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),createReplyPreviewVisualSession:e=>new Eo(e),streamGenerateSignedReply:So,savePreparedReply:Mo,...To}}function Fo(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 Oo(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function $o(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function jo(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Lo(e){const t=co({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:jo(e.data.messages),conversationHistory:$o(e.data.messages),candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:t.communicationPosition,expectedPosition:t.expectedPosition,expectedLocation:t.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==t.preferredBrand?{preferredBrand:t.preferredBrand}:{},target:{platform:"zhipin",conversationId:e.conversationId,candidateId:e.candidateId,recruiterUsername:e.recruiterUsername}}}function Do(e){return"phase.started"===e.type?Oo(e,"label")??Bo[Oo(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Oo(e,"toolName")?`: ${Oo(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Oo(e,"toolName")?`: ${Oo(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function zo(e){return{success:!1,error:e}}function Jo(e){return{success:!0,preparedReplyId:e.preparedReplyId,suggestedReply:e.suggestedReply,stage:e.stage,confidence:e.confidence,expiresAt:e.expiresAt,...void 0!==e.requestId?{requestId:e.requestId}:{}}}var Vo=vo({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:Ao,output:_o,execute:async(e,t)=>{const n=qo(),a=e.maxMessages??100,r=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await o.bringToFront().catch(()=>{}),await i.begin(r?"正在打开目标聊天":"正在准备当前聊天");const c=r?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==c&&!c.found){const e=c.error??"打开聊天失败";return await i.fail(e),zo(e)}if(void 0===c&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),zo(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const l=c?.name??e.candidateName??"",d=await o.readActiveChatPanel();if(l.length>0&&(!d||!Fo(l,d.candidateName))){const e=`右侧聊天面板未切换到 ${l}`;return await i.fail("聊天面板未同步"),zo(e)}const u=await o.readSelectedChatTarget();if(!u){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),zo(e)}if(null!==d&&u.candidateName.length>0&&!Fo(u.candidateName,d.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${u.candidateName} / ${d.candidateName}`;return await i.fail("当前会话不一致"),zo(e)}if(!(void 0===c||c.conversationId.length>0&&u.conversationId===c.conversationId||0===c.conversationId.length&&c.candidateId.length>0&&u.candidateId===c.candidateId)){const e=`当前选中会话与目标会话不一致: ${c?.name||c?.conversationId||""}`;return await i.fail("当前会话不一致"),zo(e)}await o.waitForChatMessages();const m=await o.readCandidateChatDetails(a);if(null!==m.selectedTarget&&(m.selectedTarget.conversationId!==u.conversationId||m.selectedTarget.candidateId!==u.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${m.selectedTarget.conversationId}/${m.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),zo(e)}const g=na(await o.readUsernameEvidence());if(!g.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),zo(e)}const p=Lo({data:m,conversationId:u.conversationId,candidateId:u.candidateId,recruiterUsername:g.username});if(0===p.candidateMessage.length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),zo(e)}t.logger.info(`Generating reply preview for ${u.candidateName||u.candidateId}`),await i.begin("正在生成回复"),await s.begin("正在生成回复");let f,h,b="";for await(const e of n.streamGenerateSignedReply(p)){"stream.started"===e.type&&(f=Oo(e,"requestId"));const t=Do(e);if(void 0!==t&&(await i.begin(t),await s.updateStatus(t)),"draft.started"===e.type&&(b="",await s.updateDraft(b,!0)),"draft.delta"===e.type){b+=Oo(e,"delta")??"",await s.updateDraft(b,!0)}if("final"===e.type){const t=xo.parse(e),a=wo.parse(t);h=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:a.envelopeExp,...void 0!==f?{requestId:f}:{}}),await s.complete("回复已生成",a.suggestedReply)}}if(void 0===h){const e="Reply Authority stream 未返回 final";return await s.fail(e),await i.fail(e),zo(e)}return await i.succeed("回复已生成"),Jo(h)}catch(e){const t=e instanceof Error?e.message:String(e);return await(s?.fail(t)),await(i?.fail(t)),zo(t)}finally{o?.close()}}});import{defineTool as Ho}from"@roll-agent/sdk";import{z as Uo}from"zod";import{defineTool as Ko}from"@roll-agent/sdk";import{z as Wo}from"zod";function Yo(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as Go}from"zod";var Xo="reply-authority-service",Zo="browser-use-agent/zhipin_send_reply",Qo="zhipin",ei=60,ti=Go.object({platform:Go.literal(Qo),username:Go.string().min(1),accountId:Go.string().min(1).optional()}),ni=Go.object({v:Go.literal(2),iss:Go.literal(Xo),kid:Go.string().min(1),jti:Go.string().min(1),iat:Go.number().int(),exp:Go.number().int(),aud:Go.literal(Zo),platform:Go.literal(Qo),tenantId:Go.string().min(1),conversationId:Go.string().min(1),candidateId:Go.string().min(1),reply:Go.string(),policyVersion:Go.string().min(1),recruiterBinding:ti}),ai=Go.object({kid:Go.string().min(1),algorithm:Go.literal("Ed25519"),publicKey:Go.string().min(1),validFrom:Go.string().min(1),validUntil:Go.string().optional()}),ri=Go.object({keys:Go.array(ai)}),oi=new Map;function ii(e){for(const[t,n]of oi)n<e-ei&&oi.delete(t)}function si(e,t=Math.floor(Date.now()/1e3)){return ii(t),oi.has(e)}function ci(e,t,n=Math.floor(Date.now()/1e3)){ii(n),oi.set(e,t)}import{createPublicKey as li,verify as di}from"node:crypto";var ui=new Map,mi=null;function gi(){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 pi(e){return ui=new Map(e.map(e=>[e.kid,e]))}async function fi(){if(mi)return mi;mi=(async()=>{const e=await fetch(gi()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return pi(ri.parse(t).keys)})();try{return await mi}finally{mi=null}}async function hi(){await fi()}async function bi(e){const t=ui.get(e);if(t)return t;const n=(await fi()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function yi(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function vi(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")}yi(r);const i=ni.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:a}}function wi(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-ei)throw new Error("Envelope expired");if(e.iat>t+ei)throw new Error("Envelope issued in the future")}async function xi(e,t=Math.floor(Date.now()/1e3)){const n=vi(e),a=await bi(n.payload.kid),r=li({key:Buffer.from(a.publicKey,"base64url"),format:"der",type:"spki"});if(!di(null,Buffer.from(n.payloadJson,"utf-8"),r,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return wi(n.payload,t),n.payload}var Si,Ri=Wo.object({success:Wo.boolean(),sentMessage:Wo.string(),error:Wo.string().optional()});function Ci(){return{getReplyAuthorityKeysLoaded:v,openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Si}}function Ii(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 ki(e){return"function"==typeof e.readCandidateChatDetails}async function Mi(e){if(ki(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function Ni(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function Pi(e,t){return null===t||0===e.candidateName.length||Ii(e.candidateName,t.candidateName)}async function Ti(e,t){const n=Ci();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 xi(e.signedEnvelope);if(o=i.reply,si(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await new Eo(a).clear(),await r.begin("正在发送回复");let s=await a.readActiveChatPanel().catch(()=>null),c=await a.readSelectedChatTarget().catch(()=>null),l=0;if(!Ni(c,i)||!Pi(c,s)){const t=await a.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await r.fail(t.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:t.error??"未找到目标聊天"};l=t.unreadCount,s=await a.readActiveChatPanel(),c=await a.readSelectedChatTarget()}if(null===c)return await r.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==s&&c.candidateName.length>0&&!Ii(c.candidateName,s.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${c.candidateName} / ${s.candidateName}`;return await r.fail(e),{success:!1,sentMessage:o,error:e}}if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return await r.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=na(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(!Yo(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 ${c.candidateName||c.candidateId}`);const m=await a.sendChatReply(o,{...void 0!==r?{motionObserver:r}:{}});if(!m.success)return await r.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"};const g=await Mi(a);return Tr({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==g?{candidateDetails:g}:{}},t.logger),ci(i.jti,i.exp),await r.succeed("已发送回复"),{success:!0,sentMessage:o}}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()}}var Ei=Ko({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Wo.object({signedEnvelope:Wo.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Wo.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Wo.number().optional().describe("候选人在列表中的索引(可选)")}),output:Ri,execute:async(e,t)=>await Ti(e,t)}),Ai=Uo.object({success:Uo.boolean(),sentMessage:Uo.string(),error:Uo.string().optional()});function _i(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}var Bi=Ho({name:"zhipin_send_prepared_reply",description:"发送由 zhipin_generate_reply_preview 生成的预备回复;只接收 preparedReplyId,不接收 signedEnvelope。",input:Uo.object({preparedReplyId:Uo.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回")}),output:Ai,execute:async(e,t)=>{const n=No(e.preparedReplyId);return n.ok?await Ti({signedEnvelope:n.record.signedEnvelope},t):{success:!1,sentMessage:"",error:_i(n.reason)}}});import{defineTool as qi}from"@roll-agent/sdk";import{z as Fi}from"zod";var Oi,$i=Fi.object({success:Fi.boolean(),exchanged:Fi.boolean(),wechatNumber:Fi.string().optional(),error:Fi.string().optional()}),ji=900,Li=160,Di=1100;function zi(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Oi}}function Ji(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 Vi(e){return"function"==typeof e.readCandidateChatDetails}async function Hi(e){if(Vi(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var Ui=qi({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Fi.object({conversationId:Fi.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Fi.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Fi.number().optional().describe("候选人在列表中的索引(可选)")}),output:$i,execute:async(e,t)=>{const n=zi();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,...void 0!==r?{motionObserver:r}:{}});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&&!Ji(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 Hi(a),c=await a.exchangeWechat({preClickDelayMs:ji,pressDurationMs:Li,settleMs:Di,...void 0!==r?{motionObserver:r}:{}});return c.success?(c.exchanged&&Ar({conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.candidateName,...void 0!==c.wechatNumber?{wechatNumber:c.wechatNumber}:{},...void 0!==s?{candidateDetails:s}:{}},t.logger),await r.succeed("已完成换微信"),c):(await r.fail(c.error??"换微信失败"),c)}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 Ki}from"@roll-agent/sdk";import{z as Wi}from"zod";var Yi,Gi=Wi.object({success:Wi.boolean(),username:Wi.string(),usedSelector:Wi.string().optional(),usedStrategy:Wi.string().optional(),source:Wi.string().optional(),error:Wi.string().optional()});function Xi(){return{openNativePagePort:er,pickBestUsername:na,createNativeVisualActivitySession:e=>new Cn(e),...Yi}}var Zi=Ki({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Wi.object({}),output:Gi,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Xi();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 Qi}from"@roll-agent/sdk";import{z as es}from"zod";var ts,ns=["unknown","top","middle","bottom","only-page"],as=es.object({containerFound:es.boolean(),containerLabel:es.string(),scrollTop:es.number(),scrollHeight:es.number(),clientHeight:es.number(),itemCount:es.number(),atStart:es.boolean(),atEnd:es.boolean()}),rs=es.object({success:es.boolean(),surface:es.enum(Wn),direction:es.enum(["up","down"]),stepsRequested:es.number(),stepsCompleted:es.number(),reachedBoundary:es.boolean(),atTop:es.boolean(),atBottom:es.boolean(),canScrollUp:es.boolean(),canScrollDown:es.boolean(),position:es.enum(ns),before:as,after:as,error:es.string().optional()});function os(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...ts}}function is(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function ss(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function cs(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:ss(e)}}var ls=Qi({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:es.object({surface:es.enum(Wn).describe("要滚动的页面区域"),direction:es.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:es.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:es.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:es.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:rs,execute:async(e,t)=>{const n=os(),a=Gn(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,...cs(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...cs(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=is();return{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...cs(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as ds}from"@roll-agent/sdk";import{z as us}from"zod";var ms,gs=["不限","男","女"],ps=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],fs=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","submit_failed","error"],hs=us.object({ageMin:us.number().int().min(16).optional(),ageMax:us.number().int().min(16).optional(),gender:us.enum(gs),activity:us.enum(ps)}),bs=us.object({ageMin:us.number().optional(),ageMax:us.number().optional(),gender:us.string(),activity:us.string()}),ys=us.object({success:us.boolean(),status:us.enum(fs),requested:hs,applied:bs.optional(),filterButtonText:us.string().optional(),error:us.string().optional()}),vs=us.object({ageMin:us.number().int().min(16).optional().describe("年龄下限;未传则重置为 16"),ageMax:us.number().int().min(16).optional().describe("年龄上限;未传则重置为不限"),gender:us.enum(gs).default("不限").describe("性别筛选,只支持:不限、男、女"),activity:us.enum(ps).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"}),ws=350,xs=130,Ss=600;function Rs(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...ms}}function Cs(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 Is(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 ks=ds({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页打开筛选面板,只设置年龄、性别、活跃度[单选] 三个维度并提交。",input:vs,output:ys,execute:async(e,t)=>{const n=Rs(),a=Cs(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("推荐牛人页未就绪"),Is({status:"recommend_not_ready",requested:a,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Xn.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await r.applyRecommendFilter(a,{preClickDelayMs:ws,pressDurationMs:xs,settleMs:Ss,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),Is(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),Is({status:"error",requested:a,error:t})}finally{r?.close()}}});import{defineTool as Ms}from"@roll-agent/sdk";import{z as Ns}from"zod";var Ps=["target-count","boundary","no-new-items","max-steps"],Ts="@c",Es="@j",As=/^@?c([1-9]\d*)$/i,_s=/^@?j([1-9]\d*)$/i,Bs=new Map,qs=new Map;function Fs(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${Ts}${String(e+1)}`}function Os(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${Es}${String(e+1)}`}function $s(e){const t=As.exec(e.trim()),n=t?.[1];if(!n)return;const a=Number(n);return Number.isInteger(a)?a-1:void 0}function js(e){const t=_s.exec(e.trim()),n=t?.[1];if(!n)return;const a=Number(n);return Number.isInteger(a)?a-1:void 0}function Ls(e){if(void 0!==e.index){if(!Number.isInteger(e.index)||e.index<0)throw new Error("index 必须是从 0 开始的非负整数");return e.index}if(void 0===e.candidateRef)throw new Error("必须提供 index 或 candidateRef");return Js(e.candidateRef).index}function Ds(e){const t=e.map((e,t)=>({...e,candidateRef:Fs(t)}));return Bs=new Map(t.map(e=>[e.candidateRef,e])),t}function zs(e){const t=e.map((e,t)=>({...e,jobRef:Os(t)}));return qs=new Map(t.map(e=>[e.jobRef,e])),t}function Js(e){const t=$s(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=Fs(t);return Bs.get(n)??{index:t,candidateRef:n,candidateId:""}}function Vs(e){const t=js(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=Os(t),a=qs.get(n);if(void 0===a)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return a}function Hs(e){const t=[];for(const n of e.indices??[])t.push({index:Ls({index:n}),candidateRef:Fs(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Js(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return Ks(t)}function Us(e,t){return!1!==t.found&&(e.candidateId.length>0&&t.candidateId.length>0?e.candidateId===t.candidateId:!(e.name&&t.name&&t.name.length>0)||Ws(e.name)===Ws(t.name))}function Ks(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function Ws(e){return e.trim().toLocaleLowerCase("zh-CN")}var Ys,Gs=Ns.object({index:Ns.number(),candidateRef:Ns.string(),candidateId:Ns.string(),name:Ns.string(),age:Ns.string(),experience:Ns.string(),education:Ns.string(),workStatus:Ns.string(),company:Ns.string(),currentPosition:Ns.string(),expectedLocation:Ns.string(),expectedPosition:Ns.string(),expectedSalary:Ns.string(),tags:Ns.array(Ns.string()),buttonText:Ns.string()}),Xs=Ns.object({containerLabel:Ns.string(),stepsRequested:Ns.number(),stepsCompleted:Ns.number(),reachedBoundary:Ns.boolean(),stopReason:Ns.enum(Ps),uniqueCount:Ns.number(),duplicateCount:Ns.number(),noNewRounds:Ns.number(),beforeItemCount:Ns.number(),afterItemCount:Ns.number(),beforeScrollHeight:Ns.number(),afterScrollHeight:Ns.number()}),Zs=Ns.object({success:Ns.boolean(),candidates:Ns.array(Gs),total:Ns.number(),scrollStats:Xs.optional(),error:Ns.string().optional()});function Qs(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Ys}}var ec=Ms({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:Ns.object({maxResults:Ns.number().optional().describe("最多返回条数"),autoScroll:Ns.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:Ns.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Zs,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=Qs();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=Ds(s),l=s.map((e,t)=>{const n=c[t];if(!n)throw new Error(`候选人引用生成失败:position ${String(t)}`);return{...e,candidateRef:n.candidateRef}}),d=!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(`已读取 ${l.length} 位候选人`),t.logger.info(`Found ${l.length} candidates in recommend list`+(d?`, scroll stop: ${d.stopReason}`:"")),{success:!0,candidates:l,total:l.length,...void 0!==d?{scrollStats:d}:{}}}catch(e){return await(r?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{a?.close()}}});import{defineTool as tc}from"@roll-agent/sdk";import{z as nc}from"zod";var ac,rc=["listed","recommend_not_ready","selector_not_found"],oc=nc.object({index:nc.number(),jobRef:nc.string().optional(),value:nc.string(),label:nc.string(),isCurrent:nc.boolean()}),ic=nc.object({success:nc.boolean(),status:nc.enum(rc),current:oc.optional(),jobs:nc.array(oc),availableCount:nc.number(),canSwitch:nc.boolean(),error:nc.string().optional()}),sc=450,cc=140,lc=750;function dc(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...ac}}function uc(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function mc(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function gc(e){const t=zs(e.options),n=e.options.map((e,n)=>uc(e,t[n]?.jobRef));let a;if(void 0!==e.current){const t=e.current;a=n.find(e=>mc(e,t))??uc(t)}return{success:e.success,status:e.status,...void 0!==a?{current:a}:{},jobs:n,availableCount:e.availableCount,canSwitch:e.canSwitch,...void 0!==e.error?{error:e.error}:{}}}var pc=tc({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:nc.object({}),output:ic,execute:async(e,t)=>{const n=dc();let a,r;t.logger.info("Listing Boss recommend jobs through native CDP");try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在读取推荐岗位"),await r.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await a.listRecommendJobs({preClickDelayMs:sc,pressDurationMs:cc,settleMs:lc,...void 0!==r?{motionObserver:r}:{}});return e.success?await r.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await r.fail(e.error??"读取推荐岗位失败"),gc(e)}catch(e){const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(r?.fail(t)),gc({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{a?.close()}}});import{BrowserPageInfoSchema as fc}from"@roll-agent/browser";import{defineTool as hc}from"@roll-agent/sdk";import{z as bc}from"zod";var yc,vc=bc.object({success:bc.boolean(),alreadyOnRecommend:bc.boolean(),usedSidebarClick:bc.boolean(),recommendReady:bc.boolean(),page:fc.optional(),error:bc.string().optional()});function wc(){return{getContextManager:h,openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...yc}}async function xc(e,t){return le(e,await t.inspectPage())}var Sc=hc({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:bc.object({}),output:vc,execute:async(e,t)=>{const n=wc(),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(Xn.nav.sidebar,{label:e,padding:10}),await r.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await xc(a,r)};if(!await r.clickSidebarSection("recommend",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await xc(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 xc(a,r)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await xc(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 Rc}from"@roll-agent/sdk";import{z as Cc}from"zod";var Ic,kc=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],Mc=Cc.object({index:Cc.number(),value:Cc.string(),label:Cc.string(),isCurrent:Cc.boolean()}),Nc=Cc.object({jobRef:Cc.string().optional(),jobValue:Cc.string().optional(),jobName:Cc.string().optional(),index:Cc.number().optional(),searchKeyword:Cc.string().optional(),useSearch:Cc.boolean().optional(),forceClick:Cc.boolean().optional()}),Pc=Cc.object({success:Cc.boolean(),status:Cc.enum(kc),requested:Nc,current:Mc.optional(),selected:Mc.optional(),options:Cc.array(Mc),matchedCount:Cc.number(),error:Cc.string().optional()}),Tc=Cc.object({jobRef:Cc.string().regex(_s,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:Cc.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:Cc.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:Cc.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:Cc.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:Cc.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:Cc.boolean().default(!1).describe("目标岗位已选中时是否仍点击一次岗位项;默认 false,避免无意义重复点击")}).refine(e=>void 0!==e.jobRef||void 0!==e.jobValue||void 0!==e.jobName||void 0!==e.index,{path:["jobRef"],message:"jobRef、jobValue、jobName、index 至少需要提供一个"}),Ec=900,Ac=180,_c=1400;function Bc(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),...Ic}}function qc(e){if(void 0!==e.jobRef){const t=Vs(e.jobRef);return{jobRef:t.jobRef,...t.value.length>0?{jobValue:t.value}:{},...0===t.value.length&&t.label.length>0?{jobName:t.label}:{},index:t.index,...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}return{...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Fc(e){return{...void 0!==e.jobRef?{jobRef:e.jobRef}:{},...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Oc(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function $c(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Oc(e.current)}:{},...void 0!==e.selected?{selected:Oc(e.selected)}:{},options:e.options.map(Oc),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var jc=Rc({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:Tc,output:Pc,execute:async(e,t)=>{const n=Bc();let a,r;try{const o=qc(e);t.logger.info(`Selecting Boss recommend job: ref=${o.jobRef??"N/A"}, value=${o.jobValue??"N/A"}, name=${o.jobName??"N/A"}, index=${o.index??"N/A"}, forceClick=${!0===o.forceClick}`),a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在选择推荐岗位"),await r.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await a.selectRecommendJob(o,{preClickDelayMs:Ec,pressDurationMs:Ac,settleMs:_c,...void 0!==r?{motionObserver:r}:{}});return i.success?await r.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await r.fail(i.error??"选择推荐岗位失败"),$c(i)}catch(t){const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(r?.fail(n)),$c({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Fc(e),options:[],matchedCount:0,error:n})}finally{a?.close()}}});import{defineTool as Lc}from"@roll-agent/sdk";import{z as Dc}from"zod";var zc,Jc=Dc.object({index:Dc.number(),candidateRef:Dc.string(),candidateName:Dc.string(),candidateId:Dc.string(),success:Dc.boolean(),error:Dc.string().optional()}),Vc=Dc.object({success:Dc.boolean(),results:Dc.array(Jc),summary:Dc.object({total:Dc.number(),succeeded:Dc.number(),failed:Dc.number()})}),Hc=[1400,1800,2200],Uc=450,Kc=140,Wc=750,Yc=Dc.object({indices:Dc.array(Dc.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Dc.array(Dc.string().regex(As,"candidateRef 应类似 @c1")).min(1).optional().describe("要打招呼的候选人语义引用列表,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>(e.indices?.length??0)>0||(e.candidateRefs?.length??0)>0,"必须提供 indices 或 candidateRefs");function Gc(){return{openNativePagePort:er,createNativeVisualActivitySession:e=>new Cn(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...zc}}var Xc=Lc({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Yc,output:Vc,execute:async(e,t)=>{const n=Hs(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const a=Gc();let r,o;try{r=await a.openNativePagePort(),o=a.createNativeVisualActivitySession(r),await o.begin("正在打开推荐列表");if(!await r.waitForRecommendList()){await o.fail("推荐列表未加载");const e=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:e,summary:{total:e.length,succeeded:0,failed:e.length}}}const e=n.length>1?"正在批量打招呼":"正在打招呼";await o.begin(e),await o.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:e,padding:8});const i=[];for(const[e,s]of n.entries()){if(e>0){const t=Hc[(e-1)%Hc.length]??Hc[0];await a.sleep(t)}if(Zc(s)){const e=await r.inspectRecommendCard(s.index);if(!Us(s,e)){i.push({index:s.index,candidateRef:s.candidateRef,candidateName:e.name,candidateId:e.candidateId,success:!1,error:`候选人引用 ${s.candidateRef} 已过期,请重新获取推荐列表`});continue}}const n=await r.clickRecommendGreet(s.index,{preClickDelayMs:Uc,pressDurationMs:Kc,settleMs:Wc,...void 0!==o?{motionObserver:o}:{}});i.push({index:s.index,candidateRef:s.candidateRef,candidateName:n.name,candidateId:n.candidateId,success:n.clicked,...void 0!==n.error?{error:n.error}:{}}),Er(n,t.logger)}const s={total:i.length,succeeded:i.filter(e=>e.success).length,failed:i.filter(e=>!e.success).length};return 0===s.failed?await o.succeed(`已完成 ${s.succeeded}/${s.total} 位候选人`):await o.fail(`已完成 ${s.succeeded}/${s.total} 位候选人`),{success:0===s.failed,results:i,summary:s}}catch(e){const t=e instanceof Error?e.message:String(e);await(o?.fail(t));const a=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:t}));return{success:!1,results:a,summary:{total:a.length,succeeded:0,failed:a.length}}}finally{r?.close()}}});function Zc(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as Qc}from"@roll-agent/sdk";import{z as el}from"zod";var tl="recommendFrame",nl="recommend",al="#recommendFrame",rl=".candidate-card-wrap",ol="[data-geek], .geek-item",il=`${rl}, ${ol}`,sl="[data-geek], .card-inner, .geek-item",cl="[data-geek]",ll=".name",dl=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],ul=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],ml=".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog",gl=".boss-popup__wrapper",pl='iframe[src*="c-resume"]',fl="canvas#resume, div#resume canvas";function hl(e){return e.hasNamedRecommendFrame?"named-frame":e.hasRecommendUrlFrame?"recommend-url-frame":"main-page"}function bl(e){return e>0?rl:ol}function yl(e){return{candidateId:(e.ownDataGeek??e.childDataGeek??"").trim(),name:(e.nameText??"").trim()}}function vl(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 wl(e){return e.evaluate(e=>document.querySelectorAll(e).length,rl).then(bl)}function xl(e){const t=e.frame(tl),n=e.frames().find(e=>e.url().includes(nl)),a=hl({hasNamedRecommendFrame:null!==t,hasRecommendUrlFrame:void 0!==n});return"named-frame"===a?t??e:"recommend-url-frame"===a?n??e:e}async function Sl(e,t=1e4){try{return await e.waitForSelector(il,{timeout:t}),!0}catch{return!1}}async function Rl(e,t){const n=await wl(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:cl,candidateNameSelector:ll}),i=yl({ownDataGeek:o.candidateId,nameText:o.name});return{found:!0,cardSelector:n,candidateId:i.candidateId,name:i.name,hasGreetButton:o.hasGreetButton}}var Cl=el.object({success:el.boolean(),candidateRef:el.string().optional(),candidateName:el.string(),candidateId:el.string(),error:el.string().optional()}),Il=el.object({index:el.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:el.string().regex(As,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef"),kl=Qc({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:Il,output:Cl,execute:async(e,t)=>{const n=Ml(e),a=n.index,r=n.candidateRef;t.logger.info(`Opening resume for candidate ${r} at index ${a}`);const o=h(),i=await o.getPage("zhipin"),s=xl(i);if(!await Sl(s))return{success:!1,candidateRef:r,candidateName:"",candidateId:"",error:"推荐列表未加载"};const c=await Rl(s,a);if(!c.found)return{success:!1,candidateRef:r,candidateName:"",candidateId:"",error:c.error??`候选人引用 ${r} 超出范围`};if(Nl(n)&&!Us(n,c))return{success:!1,candidateRef:r,candidateName:c.name,candidateId:c.candidateId,error:`候选人引用 ${r} 已过期,请重新获取推荐列表`};const l=s.locator(c.cardSelector).nth(a),d=await l.locator(sl).count()>0?l.locator(sl).first():l;return await d.scrollIntoViewIfNeeded(),await W(i,d,{target:s}),await d.hover(),await Vn(i,200,400),await Y(i,d,{target:s}),await d.click(),await Vn(i,1e3,2e3),t.logger.info(`Opened resume for ${c.name}`),{success:!0,candidateRef:r,candidateName:c.name,candidateId:c.candidateId}}});function Ml(e){if(void 0!==e.candidateRef)return Js(e.candidateRef);const t=Ls(e);return{index:t,candidateRef:Fs(t),candidateId:""}}function Nl(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as Pl}from"@roll-agent/sdk";import{z as Tl}from"zod";var El=Tl.object({x:Tl.number(),y:Tl.number(),width:Tl.number(),height:Tl.number()}),Al=Tl.object({success:Tl.boolean(),screenshotArea:El.optional(),canvasInfo:Tl.object({width:Tl.number(),height:Tl.number()}).optional(),error:Tl.string().optional()}),_l=Pl({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:Tl.object({}),output:Al,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(tl)??a.frames().find(e=>e.url().includes(nl));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$(pl);if(!n)return{success:!1,error:"未找到简历 iframe"};const r=await n.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector(fl,{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}},fl);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}},al),s=vl({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}},pl),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 Bl}from"@roll-agent/sdk";import{z as ql}from"zod";var Fl=ql.object({success:ql.boolean(),closed:ql.boolean(),error:ql.string().optional()}),Ol=Bl({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:ql.object({}),output:Fl,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=h(),a=await n.getPage("zhipin"),r=a.frame(tl)??a.frames().find(e=>e.url().includes(nl));if(!await(async()=>{if(r)for(const e of dl){const t=r.locator(e).first();if(await t.isVisible())return await W(a,t,{target:r}),await Y(a,t,{target:r}),await t.click(),!0}for(const e of ul){const t=a.locator(e).first();if(await t.isVisible())return await W(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.$(ml):await a.$(gl);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 $l}from"@roll-agent/sdk";import{z as jl}from"zod";import{waitForSelector as Ll}from"@roll-agent/browser";var Dl={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 zl,waitForSelector as Jl}from"@roll-agent/browser";var Vl="https://www.yupao.com",Hl=`${Vl}/chat`,Ul=`${Vl}/login`;async function Kl(e){e.url().includes("/chat")||await zl(e,Hl),await Jl(e,Dl.messageList.container,{timeout:15e3})}async function Wl(e,t){const n=`${Vl}/chat?id=${encodeURIComponent(t)}`;await zl(e,n),await Jl(e,Dl.chat.input,{timeout:15e3})}async function Yl(e,t){await Kl(e),await Ll(e,Dl.messageList.item,{timeout:1e4});const n=Dl.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 Gl=jl.object({limit:jl.number().optional().describe("最多返回的消息条数")}),Xl=jl.object({conversationId:jl.string(),candidateName:jl.string(),lastMessage:jl.string(),unreadCount:jl.number(),timestamp:jl.string()}),Zl=jl.object({messages:jl.array(Xl),total:jl.number()}),Ql=$l({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Gl,output:Zl,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=h(),a=await n.getPage("yupao"),r=await Yl(a,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as ed}from"@roll-agent/sdk";import{z as td}from"zod";import{waitForSelector as nd,typeText as ad,clickElement as rd}from"@roll-agent/browser";async function od(e,t,n){try{return await Wl(e,t),await ad(e,Dl.chat.input,n),await rd(e,Dl.chat.sendButton),await nd(e,Dl.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}var id=td.object({conversationId:td.string().describe("对话 ID"),message:td.string().describe("要发送的回复消息")}),sd=td.object({success:td.boolean(),conversationId:td.string(),sentMessage:td.string(),error:td.string().optional()}),cd=ed({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:id,output:sd,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 od(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}}}),ld=t("browser-use-agent");function dd(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 ud(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 md(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 gd(){return n.parse({mode:process.env.BROWSER_MODE,headless:dd(process.env.BROWSER_HEADLESS),cdpUrl:process.env.BROWSER_CDP_URL,cdpHost:process.env.BROWSER_CDP_HOST,cdpPort:ud(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:md(process.env.BROWSER_ARGS_JSON),sessionsDir:process.env.BROWSER_SESSIONS_DIR})}var pd=e({name:"browser-use-agent",tools:[Q,ge,Be,Je,Ge,yn,Lr,Wr,eo,yo,Vo,Bi,Ui,Zi,ls,Sc,pc,jc,ks,ec,Xc,kl,_l,Ol,Ql,cd,S]},{onShutdown:w});async function fd(){await p(gd());try{await hi(),y(!0)}catch(e){y(!1),ld.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await pd.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}fd().catch(async e=>{ld.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await w().catch(()=>{}),process.exit(1)});
|
|
1
|
+
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{defineTool as n}from"@roll-agent/sdk";import{z as a}from"zod";import{createAgentLogger as r}from"@roll-agent/sdk";import{BrowserRuntime as o,BrowserContextManager as i,SessionStore as s}from"@roll-agent/browser";var c,l,d,u=!1,m=r("browser-use-agent");async function p(e){c||(u=!1,d=new s(e.sessionsDir),c=new o(e),await c.start(),l=new i(c,d))}function g(){if(!c)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return c}function f(){if(!l)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return l}function h(){if(!d)throw new Error("SessionStore not initialized. Call initRuntime() first.");return d}function b(e){u=e}function y(){return u}async function v(){const e=l,t=c,n=[];if(l=void 0,c=void 0,d=void 0,u=!1,e){m.info("Closing browser contexts...");try{await e.closeAll()}catch(e){n.push(new Error("Failed to close browser contexts",{cause:e})),m.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(t){m.info("Stopping browser process...");try{await t.stop()}catch(e){n.push(new Error("Failed to stop browser runtime",{cause:e})),m.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(m.info("Browser runtime shutdown complete"),n.length>0)throw new AggregateError(n,"Browser runtime shutdown failed")}var w=a.object({success:a.boolean(),mode:a.string(),connected:a.boolean()}),x=n({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:a.object({}),output:w,execute:async(e,t)=>{const n=g();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as S}from"@roll-agent/sdk";import{z as C}from"zod";import{BrowserSecurityConfigSchema as R,BrowserStatusSchema as I}from"@roll-agent/browser";import{createHash as k}from"node:crypto";import{z as A}from"zod";var N=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_KEYS_URL","RECRUITMENT_EVENTS_ENABLED","RECRUITMENT_EVENTS_API_BASE_URL","RECRUITMENT_EVENTS_API_TOKEN","RECRUITMENT_EVENTS_DEFAULT_AGENT_ID","BROWSER_SECURITY_JSON","BROWSER_USE_POLICY_JSON","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],M=/^[0-9a-f]{8}$/,E=A.object({present:A.boolean(),fingerprint:A.string().regex(M).optional()}),P=A.record(E);function T(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:_(n)}]:[e,{present:!1}]}))}function _(e){return k("sha256").update(e).digest("hex").slice(0,8)}var B,O,$=180,q=60,D=280,j="roll-agent-visual-cursor-root",F="__rollVisualCursorState";function L(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function z(){return void 0!==B?B:L(process.env.BROWSER_VISUAL_CURSOR)??!0}async function J(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 H(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 V(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function U(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:j,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 U(a),n=!0}catch{}return n}async function K(e,t,n={}){if(!z()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const a=await J(t);if(!a)return!1;const r=n.durationMs??$,o=n.settleMs??q,i=n.target??e;return await W(e,{preserveTarget:i}),await H(n.target??e,{point:a,durationMs:r,clickPulse:!1,pulseDurationMs:D}),await e.waitForTimeout(Math.max(r+o,0)),!0}catch{return!1}}async function Y(e,t,n={}){if(!z()||e.isClosed())return!1;try{const a=await J(t);if(!a)return!1;const r=n.pulseDurationMs??D,o=n.target??e;return await W(e,{preserveTarget:o}),await H(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!==O?O:G(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Z}from"@roll-agent/sdk";import{z as Q}from"zod";import{randomUUID as ee}from"node:crypto";import{z as te}from"zod";var ne=te.object({id:te.string().trim().min(1)}),ae=new Map;function re(e=Date.now()){for(const[t,n]of ae)n.expiresAtMs<=e&&ae.delete(t)}function oe(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function ie(e,t,n=Date.now()){re(n);const a=ee(),r=n+t;return ae.set(a,{id:a,tool:e.tool,target:e.target,digest:e.digest,...void 0!==e.summary?{summary:e.summary}:{},expiresAtMs:r}),{id:a,expiresAt:new Date(r).toISOString(),tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{},retryInput:{toolActionApproval:{id:a}}}}function se(e){return re(),!!ce(e)&&(ae.delete(e.approval.id),!0)}function ce(e){re();const t=ae.get(e.approval.id);return void 0!==t&&!!oe(e.subject,t)}var le=["log","deny","confirm"],de=Q.enum(le),ue=["zhipin_send_prepared_reply"],me=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],pe=Q.object({code:Q.enum(me),message:Q.string()}),ge=Q.object({policy:de}),fe=Q.object({approvalTtlMs:Q.number().int().positive().max(36e5).default(3e5),tools:Q.record(ge).default({})}),he=fe.parse({}),be=new Set(ue),ye=he;function ve(e){if(void 0===e)return{};try{return JSON.parse(e)}catch(e){throw new Error(`BROWSER_USE_POLICY_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}}function we(e=process.env){try{return fe.parse(ve(e.BROWSER_USE_POLICY_JSON))}catch(e){if(e instanceof Error&&e.message.startsWith("BROWSER_USE_POLICY_JSON must be valid JSON"))throw e;throw new Error(`BROWSER_USE_POLICY_JSON is invalid: ${e instanceof Error?e.message:String(e)}`)}}function xe(e){ye=fe.parse(e)}function Se(){return ye}function Ce(e){const t=e.toolPolicy??ye,n=[];for(const e of Object.keys(t.tools))be.has(e)||n.push({code:"unknown_tool_policy",message:`BROWSER_USE_POLICY_JSON 配置了未接入 tool-level policy 的工具: ${e}`});return"confirm"===e.browserSecurity.actionPolicy&&Object.values(t.tools).some(e=>"confirm"===e.policy)&&n.push({code:"double_confirmation",message:"BROWSER_SECURITY_JSON.actionPolicy=confirm 与 tool policy confirm 同时启用,可能导致双重确认。"}),"confirm"!==e.browserSecurity.actionPolicy&&"deny"!==e.browserSecurity.actionPolicy||n.push({code:"browser_action_policy_not_recommended",message:"BROWSER_SECURITY_JSON.actionPolicy=confirm/deny 是高级调试模式,Boss 日常编排建议使用 actionPolicy=log。"}),n}function Re(e,t){const n=ye.tools[t.subject.tool],a=n?.policy??"log";if("log"===a)return e.logger.info(`Browser-use tool allowed by tool policy: ${t.subject.tool}`),{consumeApproval:()=>{}};if("deny"===a){const n="Tool execution denied by browser-use tool policy.";throw e.logger.warn(`${n} ${JSON.stringify(Ie(t.subject,"tool_policy_deny"))}`),new Z({code:"action_denied",message:n,details:Ie(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?ce({approval:t.approval,subject:t.subject}):se({approval:t.approval,subject:t.subject})))return e.logger.info(`Browser-use tool approved by toolActionApproval: ${t.subject.tool}`),{consumeApproval:()=>{!0===t.deferApprovalConsumption&&void 0!==t.approval&&se({approval:t.approval,subject:t.subject})}};const r="Tool execution requires confirmation by browser-use tool policy.",o={...Ie(t.subject,"tool_policy_confirm"),approvalRequest:ie(t.subject,ye.approvalTtlMs)};throw e.logger.warn(`${r} ${JSON.stringify(Ie(t.subject,"tool_policy_confirm"))}`),new Z({code:"needs_confirmation",message:r,details:o})}function Ie(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var ke=I.extend({replyAuthorityKeysLoaded:C.boolean(),visualCursorEnabled:C.boolean(),visualActivityEnabled:C.boolean(),security:R,toolPolicy:fe,policyWarnings:C.array(pe),effectiveEnvSources:P}),Ae=S({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:C.object({}),output:ke,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=g(),a=f(),r=h(),o=n.isRunning(),{headless:i,mode:s,security:c}=n.getConfig(),l=Se(),d=a.getActivePlatforms(),u=[];for(const e of d){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");u.push({platform:e,pagesOpen:t,currentUrl:o,hasLoginState:i,loginStateSource:s})}return{running:o,headless:i,mode:s,activeSessions:u,replyAuthorityKeysLoaded:y(),visualCursorEnabled:z(),visualActivityEnabled:X(),security:c,toolPolicy:l,policyWarnings:Ce({browserSecurity:c,toolPolicy:l}),effectiveEnvSources:T(N)}}});import{defineTool as Ne}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Me,PlatformSchema as Ee}from"@roll-agent/browser";import{z as Pe}from"zod";import{PLATFORMS as Te}from"@roll-agent/browser";var _e={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Be(e){return new URL(_e[e]).host}function Oe(e,t){try{return new URL(e).host.includes(Be(t))}catch{return!1}}function $e(e){return Te.find(t=>Oe(e,t))}function qe(e,t){const n=$e(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:$e(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var je=Pe.object({platform:Ee.optional().describe("可选:仅返回指定平台相关的页面")}),Fe=Pe.object({pages:Pe.array(Me)}),Le=Ne({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:je,output:Fe,execute:async(e,t)=>{const n=f();t.logger.info("Listing browser pages");const a=(await n.listNativePages()).map(e=>qe(n,e));return{pages:void 0===e.platform?a:a.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as ze}from"node:timers/promises";import{defineTool as Je}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as He,BrowserPageInfoSchema as Ve}from"@roll-agent/browser";import{z as Ue}from"zod";import{StructuredToolError as We}from"@roll-agent/sdk";import{isBrowserActionPolicyError as Ke,preflightBrowserAction as Ye}from"@roll-agent/browser";import{randomUUID as Ge}from"node:crypto";var Xe=6e4,Ze=new Map;function Qe(e=Date.now()){for(const[t,n]of Ze)n.expiresAtMs<=e&&Ze.delete(t)}function et(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function tt(e,t=Date.now()){Qe(t);const n=Ge(),a=t+Xe,r={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:a};return Ze.set(n,r),{id:n,expiresAt:new Date(a).toISOString(),action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},retryInput:{browserActionApproval:{id:n}}}}function nt(e){Qe();const t=Ze.get(e.approval.id);return void 0!==t&&(!!et(e.details,t)&&(Ze.delete(e.approval.id),!0))}function at(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function rt(e,t,n={}){return{security:at(t,n),approveAction:nt,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function ot(e,t,n){const a=Ye({security:t.getConfig().security,action:n.action,target:n.target,...void 0!==n.url?{url:n.url}:{}});if(a.ok)return a.log&&e.logger.info(a.message),{approvedByConfirmation:!1};if("needs_confirmation"===a.code&&void 0!==n.approval&&nt({approval:n.approval,details:a.details}))return e.logger.info(`Browser action approved by browserActionApproval: ${n.action} ${n.target}`),{approvedByConfirmation:!0};throw e.logger.warn(`${a.message} ${JSON.stringify(a.details)}`),new We({code:a.code,message:a.message,details:{...a.details,..."needs_confirmation"===a.code?{approvalRequest:tt(a.details)}:{}}})}function it(e){if(Ke(e))return new We({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:tt(e.payload.details)}:{}}})}var st,ct=15e3,lt=250,dt=["/web/chat"],ut=Ue.object({url:Ue.string().url().describe("要导航到的目标 URL"),browserActionApproval:He.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),mt=Ue.object({success:Ue.boolean(),page:Ve});function pt(){return{getContextManager:f,getRuntime:g,detectPlatformFromUrl:$e,matchesPlatformHost:Oe,toNativePageInfo:qe,delay:ze,...st}}function gt(e){return"object"==typeof e&&null!==e}function ft(e){return!!gt(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function ht(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==dt.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function bt(e,t,n,a,r){const o=n.detectPlatformFromUrl(a),i=await t.listNativePages();if(o){const s=i.find(e=>n.matchesPlatformHost(e.url,o));if(s)return r.info(`Reusing native ${o} page for navigation`),await t.activateNativePage(s.targetId),e.rememberNativePageSelection(o,s),{page:s,platform:o};r.info(`Opening native ${o} page for navigation`);const c=await t.openNativePage(a);return e.rememberNativePageSelection(o,c),{page:c,platform:o}}return r.info("Opening a new native page for non-platform navigation"),{page:await t.openNativePage(a)}}async function yt(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!ft(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function vt(e,t,n,a){const r=Date.now();let o;for(;Date.now()-r<ct;){try{const t=await yt(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&wt(t.url,n,a))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(lt)}throw new Error(`Native navigation did not reach document ready state within ${ct}ms${o?`: ${o.message}`:""}`)}function wt(e,t,n){if(e===t)return!0;try{const t=new URL(e);return("http:"===t.protocol||"https:"===t.protocol)&&e!==n}catch{return!1}}async function xt(e,t,n){t.url!==n&&await e.navigate(n)}async function St(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var Ct=Je({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:ut,output:mt,execute:async(e,t)=>{const n=pt(),a=n.getContextManager(),r=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);ht(e.url,o);const i=ot(t,r,{action:"navigate",target:e.url,url:e.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Native navigating to ${e.url}`);const s=await bt(a,r,n,e.url,t.logger),c=await r.connectNativePage(s.page,{...rt(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await c.bringToFront().catch(()=>{}),await xt(c,s.page,e.url);const o=await vt(c,n,e.url,s.page.url),i=await St(r,s.page,o),l=s.platform??n.detectPlatformFromUrl(i.url);return l&&(a.rememberNativePageSelection(l,i),t.logger.info(`Bound native navigated page to ${l}`)),{success:!0,page:n.toNativePageInfo(a,i)}}finally{c.close()}}});import{defineTool as Rt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as It,BrowserPageInfoSchema as kt,PlatformSchema as At}from"@roll-agent/browser";import{z as Nt}from"zod";async function Mt(e,t){return(await e.listNativePages()).find(e=>Oe(e.url,t))}async function Et(e,t,n={}){const a=await Mt(e,t);if(a)return await e.activateNativePage(a.targetId),{page:a,reusedExistingPage:!0};return{page:await e.openNativePage(_e[t],n),reusedExistingPage:!1}}var Pt=Nt.object({platform:At.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:It.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Tt=Nt.object({success:Nt.boolean(),page:kt,reusedExistingTab:Nt.boolean()}),_t=Rt({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:Pt,output:Tt,execute:async(e,t)=>{const{platform:n}=e,a=g(),r=f(),o=_e[n],i=ot(t,a,{action:"navigate",target:o,url:o,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Opening platform page for ${n}`);const{page:s,reusedExistingPage:c}=await Et(a,n,rt(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return r.rememberNativePageSelection(n,s),{success:!0,page:qe(r,s),reusedExistingTab:c}}});import{defineTool as Bt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ot,PlatformSchema as $t}from"@roll-agent/browser";import{z as qt}from"zod";var Dt=qt.object({platform:$t.describe("要将该页面绑定为当前活跃页的平台"),pageId:qt.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),jt=qt.object({success:qt.boolean(),page:Ot}),Ft=Bt({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:Dt,output:jt,execute:async(e,t)=>{const n=f();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const a=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:qe(n,a)}}});import{defineTool as Lt}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as zt,BrowserPageInfoSchema as Jt,createBrowserAxSnapshot as Ht}from"@roll-agent/browser";import{z as Vt}from"zod";import{BrowserElementRefStore as Ut}from"@roll-agent/browser";var Wt=new Ut;async function Kt(e){const t=await e.runtime.listNativePages();if(void 0!==e.pageId){const n=t.find(t=>t.targetId===e.pageId);if(void 0===n)throw new Error(`Page "${e.pageId}" not found. Run list_pages to inspect pageId values.`);return n}const n=t.filter(t=>e.ctxManager.isNativePageSelected(t.targetId));if(1===n.length&&void 0!==n[0])return n[0];if(1===t.length&&void 0!==t[0])return t[0];throw new Error("browser_snapshot/click_ref/type_ref need pageId when multiple native pages are open. Run list_pages first.")}import{randomUUID as Yt}from"node:crypto";var Gt="data-roll-browser-action-",Xt=120;function Zt(){return`${Gt}${Yt().replaceAll("-","")}`}function Qt(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),Xt)):Xt}function en(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function tn(e){if(!en(e)||!en(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function nn(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function an(e){return Array.isArray(e)?e.flatMap(e=>{const t=rn(e);return void 0===t?[]:[t]}):[]}function rn(e){if(!en(e))return;const t=e.nodeId,n=e.backendNodeId,a=nn(e.attributes),r=rn(e.contentDocument),o=rn(e.templateContent);return{..."number"==typeof t&&Number.isInteger(t)?{nodeId:t}:{},..."number"==typeof n&&Number.isInteger(n)?{backendNodeId:n}:{},...void 0!==a?{attributes:a}:{},children:an(e.children),...void 0!==r?{contentDocument:r}:{},pseudoElements:an(e.pseudoElements),shadowRoots:an(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function on(e,t){if(void 0!==t)for(let n=0;n<t.length-1;n+=2)if(t[n]===e)return t[n+1]}function sn(e){if(!en(e))return;const t=e.marker,n=e.name;return"string"==typeof t&&"string"==typeof n&&0!==n.trim().length?{marker:t,name:n.trim(),disabled:!0===e.disabled,hasClassHint:!0===e.hasClassHint,hasCursorPointer:!0===e.hasCursorPointer,hasOnClick:!0===e.hasOnClick,hasTabIndex:!0===e.hasTabIndex,isEditable:!0===e.isEditable}:void 0}function cn(e){return Array.isArray(e)?e.flatMap(e=>{const t=sn(e);return void 0===t?[]:[t]}):[]}function ln(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function dn(e){const t=[];return e.hasCursorPointer&&t.push("cursor:pointer"),e.hasOnClick&&t.push("onclick"),e.hasTabIndex&&t.push("tabindex"),e.isEditable&&t.push("contenteditable"),e.hasClassHint&&t.push("class:action"),t}function un(e,t){const n=new Map;for(const a of t){const t=on(e,a.attributes);void 0!==t&&void 0!==a.backendNodeId&&n.set(t,a.backendNodeId)}return n}function mn(e,t){const n=new Map,a=t=>{const r=on(e,t.attributes);void 0!==r&&void 0!==t.backendNodeId&&n.set(r,t.backendNodeId);for(const e of t.children)a(e);void 0!==t.contentDocument&&a(t.contentDocument);for(const e of t.pseudoElements)a(e);for(const e of t.shadowRoots)a(e);void 0!==t.templateContent&&a(t.templateContent)};return a(t),n}function pn(e,t){return`(() => {\n const markerAttribute = ${JSON.stringify(e)};\n const maxCandidates = ${JSON.stringify(t)};\n const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();\n const classTextOf = (element) => String(element.getAttribute("class") ?? "");\n const directTextOf = (element) => normalize(Array.from(element.childNodes)\n .filter((node) => node.nodeType === Node.TEXT_NODE)\n .map((node) => node.textContent ?? "")\n .join(" "));\n const visibleTextOf = (element) => normalize(\n element instanceof HTMLElement ? element.innerText : element.textContent\n );\n const hasCompositeOptionHint = (element) => {\n const pattern = /dropdown|menu|option|select|item/i;\n let current = element;\n for (let depth = 0; current && depth < 4; depth += 1) {\n if (pattern.test(classTextOf(current))) return true;\n current = current.parentElement;\n }\n return false;\n };\n const domActionNameOf = (element) => {\n const direct = directTextOf(element);\n if (direct) return direct;\n if (element.childElementCount === 0) return visibleTextOf(element);\n if (hasCompositeOptionHint(element)) return visibleTextOf(element);\n return "";\n };\n const isVisible = (element) => {\n if (element.closest("[hidden], [aria-hidden=\\"true\\"]")) return false;\n const style = window.getComputedStyle(element);\n if (style.visibility === "hidden" || style.display === "none") return false;\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const isDisabled = (element) =>\n element.matches(":disabled") ||\n element.getAttribute("aria-disabled") === "true" ||\n element.getAttribute("disabled") !== null;\n const isNativeSemantic = (element) => {\n const tag = element.tagName.toLowerCase();\n return (\n (tag === "a" && element.hasAttribute("href")) ||\n tag === "button" ||\n tag === "input" ||\n tag === "textarea" ||\n tag === "select" ||\n tag === "option" ||\n tag === "summary" ||\n element.hasAttribute("role")\n );\n };\n const hasNearbyClassHint = (element) => {\n const pattern = /btn|button|click|dropdown|tab|tabs|filter|menu|nav|option|select|switch|toggle/i;\n let current = element;\n for (let depth = 0; current && depth < 4; depth += 1) {\n if (pattern.test(classTextOf(current))) return true;\n current = current.parentElement;\n }\n return false;\n };\n const isCandidate = (element) => {\n if (!isVisible(element) || isNativeSemantic(element)) return false;\n const tag = element.tagName.toLowerCase();\n if (!["span", "div", "li", "label", "em", "i", "b", "strong"].includes(tag)) return false;\n const name = domActionNameOf(element);\n if (name.length === 0 || name.length > 100) return false;\n const style = window.getComputedStyle(element);\n const hasCursorPointer = style.cursor === "pointer";\n const hasOnClick = element.hasAttribute("onclick") || element.onclick !== null;\n const hasTabIndex = element.getAttribute("tabindex") !== null && element.getAttribute("tabindex") !== "-1";\n const contentEditable = element.getAttribute("contenteditable");\n const isEditable = contentEditable === "" || contentEditable === "true";\n const hasClassHint = hasNearbyClassHint(element);\n\n if (!hasCursorPointer && !hasOnClick && !hasTabIndex && !isEditable && !hasClassHint) {\n return false;\n }\n\n if (hasCursorPointer && !hasOnClick && !hasTabIndex && !isEditable && !hasClassHint) {\n const parent = element.parentElement;\n if (parent && window.getComputedStyle(parent).cursor === "pointer") return false;\n }\n return true;\n };\n const output = [];\n for (const element of document.querySelectorAll("body *")) {\n if (!isCandidate(element)) continue;\n if (element.closest("[" + markerAttribute + "]")) continue;\n const marker = String(output.length);\n const style = window.getComputedStyle(element);\n const contentEditable = element.getAttribute("contenteditable");\n element.setAttribute(markerAttribute, marker);\n output.push({\n marker,\n name: domActionNameOf(element),\n disabled: isDisabled(element),\n hasClassHint: hasNearbyClassHint(element),\n hasCursorPointer: style.cursor === "pointer",\n hasOnClick: element.hasAttribute("onclick") || element.onclick !== null,\n hasTabIndex: element.getAttribute("tabindex") !== null && element.getAttribute("tabindex") !== "-1",\n isEditable: contentEditable === "" || contentEditable === "true"\n });\n if (output.length >= maxCandidates) break;\n }\n return output;\n })()`}function gn(e){return`(() => {\n const markerAttribute = ${JSON.stringify(e)};\n const elements = document.querySelectorAll("[" + markerAttribute + "]");\n for (const element of elements) {\n element.removeAttribute(markerAttribute);\n }\n return elements.length;\n })()`}async function fn(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!en(n))return new Map;const a=rn(n.root);return void 0===a?new Map:mn(t,a)}const a=tn(await e.getDocument({depth:0}));if(void 0===a)return new Map;const r=await e.querySelectorAllByNodeId({nodeId:a,selector:`[${t}]`});return un(t,await Promise.all(r.map(async t=>await e.describeNode({nodeId:t}))))}async function hn(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,a=Qt(n.maxCandidates??Xt);if(0===a)return[];const r=Zt(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${r.slice(Gt.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=cn(await e.evaluateJson(pn(r,a),i).catch(()=>[]));try{if(0===s.length)return[];const t=await fn(e,r,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:ln(e),name:e.name,hints:dn(e),disabled:e.disabled}]})}finally{await e.evaluateJson(gn(r),i).catch(()=>{})}}var bn=28,yn=90,vn=250,wn=620,xn=8,Sn=32,Cn=8;function Rn(e){return new Promise(t=>{setTimeout(t,e)})}function In(e,t,n){return Math.min(Math.max(e,t),n)}function kn(e,t){return Math.max(0,Math.floor(e??t))}function An(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Nn(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function Mn(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function En(e){const t=e.x>=96?-72:72,n=e.y>=72?-36:36;return{x:Math.max(1,e.x+t),y:Math.max(1,e.y+n)}}function Pn(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function Tn(e,t){const n=Nn(e,t);if(n<=xn)return[e,t];const a=In(Math.round(n/30),Cn,Sn),r=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=r/i,l=In(.08*n,4,28)*Pn(e,t),d=[e];for(let i=1;i<a;i+=1){const u=i/a,m=Mn(u),p=Math.sin(Math.PI*u)*l,g=Math.sin(.13*(e.x+t.y+17*i))*Math.min(1.8,n/180);d.push({x:Math.round(e.x+r*m+s*(p+g)),y:Math.round(e.y+o*m+c*(p+g))})}return d.push(t),d}function _n(e,t){const n=An(t);return Tn(void 0===e?En(n):An(e),n)}var Bn=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??Rn,this.defaultStepDelayMs=kn(t.stepDelayMs,bn)}reset(e){this.lastPoint=void 0===e?void 0:An(e)}async moveTo(e,t={}){const n=kn(t.stepDelayMs,this.defaultStepDelayMs),a=_n(this.lastPoint,e),r=Math.max(a.length-1,0)*n;await(t.motionObserver?.previewMouseMotion({points:a,durationMs:r}));for(let e=0;e<a.length;e+=1){const r=a[e];void 0!==r&&(await this.dispatcher.dispatchMouseEvent({type:"mouseMoved",x:r.x,y:r.y,...void 0!==t.button?{button:t.button}:{},buttons:t.buttons??0}),e<a.length-1&&n>0&&await this.sleep(n))}return this.lastPoint=An(e),{points:a,durationMs:r}}async click(e,t={}){const n=t.button??"left",a=t.clickCount??1;await this.moveTo(e,{button:"none",buttons:0,...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{}}),await this.delayIfPositive(t.preClickDelayMs),await(t.motionObserver?.previewMouseClick?.({point:An(e),durationMs:wn})),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:"left"===n?1:0,clickCount:a}),await this.delayIfPositive(t.pressDurationMs??yn),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:0,clickCount:a}),await this.delayIfPositive(t.settleMs??vn),this.lastPoint=An(e)}async drag(e,t,n={}){await this.moveTo(e,{button:"none",buttons:0,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:"left",buttons:1,clickCount:1}),await this.delayIfPositive(n.pressDurationMs??yn),this.reset(e),await this.moveTo(t,{button:"left",buttons:1,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(t.x),y:Math.round(t.y),button:"left",buttons:0,clickCount:1}),this.lastPoint=An(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},On=14,$n=720;function qn(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 readCursorPath = () => {\n if (!Array.isArray(args.cursor?.path?.points)) return null;\n const points = args.cursor.path.points\n .map((point) => {\n const x = Number(point?.x);\n const y = Number(point?.y);\n return Number.isFinite(x) && Number.isFinite(y)\n ? { centerX: Math.round(x), centerY: Math.round(y) }\n : null;\n })\n .filter((point) => point !== null);\n return points.length > 0 ? points : null;\n };\n\n const readCursorClick = () => {\n const point = args.cursor?.click?.point;\n const x = Number(point?.x);\n const y = Number(point?.y);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return {\n point: { centerX: Math.round(x), centerY: Math.round(y) },\n durationMs: Math.max(Number(args.cursor?.click?.durationMs ?? 620), 0)\n };\n };\n\n const renderClickPulse = (root, point, durationMs) => {\n const createPulse = (size, border, background, shadow, startScale, endScale, delayMs) => {\n const radius = size / 2;\n const pulse = document.createElement("div");\n pulse.setAttribute("aria-hidden", "true");\n pulse.style.position = "fixed";\n pulse.style.left = "0";\n pulse.style.top = "0";\n pulse.style.width = size + "px";\n pulse.style.height = size + "px";\n pulse.style.borderRadius = "9999px";\n pulse.style.border = border;\n pulse.style.background = background;\n pulse.style.boxShadow = shadow;\n pulse.style.pointerEvents = "none";\n pulse.style.opacity = "0.96";\n pulse.style.transformOrigin = "center center";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + startScale + ")";\n pulse.style.transition =\n "transform " + durationMs + "ms cubic-bezier(0.16, 1, 0.3, 1), opacity " + durationMs + "ms ease-out";\n root.append(pulse);\n\n window.setTimeout(() => {\n window.requestAnimationFrame(() => {\n pulse.style.opacity = "0";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + endScale + ")";\n });\n }, delayMs);\n\n window.setTimeout(() => {\n pulse.remove();\n }, durationMs + delayMs + 80);\n };\n\n createPulse(\n 28,\n "3px solid rgba(45, 212, 191, 0.98)",\n "rgba(20, 184, 166, 0.24)",\n "0 0 0 5px rgba(20, 184, 166, 0.18), 0 0 26px rgba(20, 184, 166, 0.34)",\n 0.55,\n 2.35,\n 0\n );\n createPulse(\n 14,\n "2px solid rgba(255, 255, 255, 0.95)",\n "rgba(255, 255, 255, 0.34)",\n "0 0 18px rgba(45, 212, 191, 0.45)",\n 0.7,\n 1.55,\n 65\n );\n };\n\n const renderCursor = () => {\n const path = readCursorPath();\n const click = readCursorClick();\n const point = path ? path[path.length - 1] : click?.point ?? null;\n if (!point) return;\n const root = ensureCursorRoot();\n const pointer = document.getElementById(cursorPointerId);\n if (!pointer) return;\n const targetX = point.centerX - 2;\n const targetY = point.centerY - 2;\n if (path && path.length > 1 && typeof pointer.animate === "function") {\n for (const animation of pointer.getAnimations()) {\n animation.cancel();\n }\n pointer.style.opacity = "1";\n const keyframes = path.map((entry) => ({\n transform: "translate(" + (entry.centerX - 2) + "px, " + (entry.centerY - 2) + "px)"\n }));\n const duration = Math.max(args.cursor.path.durationMs ?? 180, 0);\n const animation = pointer.animate(keyframes, {\n duration,\n easing: "cubic-bezier(0.22, 1, 0.36, 1)",\n fill: "forwards"\n });\n animation.onfinish = () => {\n pointer.style.transform = "translate(" + targetX + "px, " + targetY + "px)";\n };\n window[cursorStateKey] = { x: point.centerX, y: point.centerY };\n if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n return;\n }\n pointer.style.transition = "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 if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n };\n\n renderActivity();\n renderCursor();\n return true;\n })()`}var Dn=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??On,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){z()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){z()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=$n){return!!X()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=$n){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(qn(e))}catch{return!1}}};function jn(e){return new Dn(e)}async function Fn(e,t,n){const a=new Bn(e);await a.click({x:n.x,y:n.y},{motionObserver:t})}var Ln=Vt.object({pageId:Vt.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:Vt.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:Vt.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:Vt.boolean().default(!0).describe("默认 true:只返回可交互节点")}),zn=Vt.object({page:Jt,snapshot:zt});function Jn(e){const t=[],n=e=>{"iframe"===e.role.toLowerCase()&&void 0!==e.backendNodeId&&t.push(e);for(const t of e.children??[])n(t)};for(const t of e)n(t);return t}async function Hn(e,t){if(void 0===t.backendNodeId)return;const n=await e.describeNode({backendNodeId:t.backendNodeId,depth:1}).catch(()=>{});return n?.contentDocumentFrameId??n?.frameId}async function Vn(e,t){const n=Jn(t);return(await Promise.all(n.map(async t=>{const n=await Hn(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function Un(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function Wn(e){return e.refs.reduce((e,t)=>Math.max(e,Un(t.ref)),0)}function Kn(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...Kn(n.nodes,t)]})}function Yn(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),a=Yn(e.children??[],t),r=void 0===n?[]:Yn(n.nodes,t),o=void 0===n?a:[...a,...r];return 0===o.length?e:{...e,children:o}})}async function Gn(e){let t=Wn(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),a=e.snapshot.truncated;const r=new Set,o=[],i=[...await Vn(e.controller,e.snapshot.nodes)];for(let s=0;s<i.length;s+=1){const c=i[s];if(void 0===c)continue;if(n<=0){a=!0;break}if(void 0===c.node.ref||r.has(c.frameId))continue;r.add(c.frameId);const l=await hn(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await Ht(e.controller,{depthOffset:c.node.depth+1,domActionHints:l,frameId:c.frameId,initialRefCount:t,interactiveOnly:e.snapshot.interactiveOnly,maxNodes:n,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}).catch(()=>{});if(void 0===d||0===d.nodeCount||0===d.refs.length)continue;const u=await Vn(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=Wn(d),n=Math.max(0,n-d.nodeCount),a=a||d.truncated}if(0===o.length)return{...e.snapshot,truncated:a};const s=new Map(o.map(e=>[e.iframeRef,e.snapshot])),c=o.flatMap(e=>e.snapshot.refs),l=o.reduce((e,t)=>e+t.snapshot.nodeCount,0);return{...e.snapshot,nodes:e.snapshot.interactiveOnly?Kn(e.snapshot.nodes,s):Yn(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:a}}var Xn=Lt({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:Ln,output:zn,execute:async(e,t)=>{const n=g(),a=f(),r=await Kt({runtime:n,ctxManager:a,...void 0!==e.pageId?{pageId:e.pageId}:{}});t.logger.info(`Creating browser AX snapshot for page ${r.targetId}`);const o=await n.connectNativePage(r),i=jn(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await hn(o,s),l=await Ht(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await Gn({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return Wt.saveSnapshot(r.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:qe(a,r),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as Zn,defineTool as Qn}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as ea,BrowserElementRefHandleSchema as ta,clickElementRef as na}from"@roll-agent/browser";import{z as aa}from"zod";import{BrowserElementRefHandleSchema as ra}from"@roll-agent/browser";import{z as oa}from"zod";var ia=oa.enum(["backend_node_id","role_name_nth"]),sa=oa.object({ref:ra,role:oa.string(),name:oa.string(),x:oa.number(),y:oa.number(),resolvedBy:ia,backendNodeId:oa.number().int().positive().optional(),frameId:oa.string().optional(),disabled:oa.boolean()}),ca=oa.object({success:oa.literal(!0),ref:ra,resolvedBy:ia,target:sa}),la=aa.object({ref:ta.describe("browser_snapshot 返回的 @eN element ref"),pageId:aa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:ea.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),da=Qn({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:la,output:ca,execute:async(e,t)=>{const n=g(),a=f(),r=await Kt({runtime:n,ctxManager:a,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=Wt.getRef(r.targetId,e.ref);if(void 0===o)throw new Zn({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:r.targetId}});const i=ot(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(r,{...rt(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await s.bringToFront().catch(()=>{});const t=jn(s);await t.begin(`正在点击 ${e.ref}`);const n=await na({controller:s,elementRef:o,options:{clickTarget:async e=>{await Fn(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),n}catch(t){await jn(s).fail(`点击 ${e.ref} 失败`);const n=it(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as ua,defineTool as ma}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as pa,BrowserElementRefHandleSchema as ga,typeElementRef as fa}from"@roll-agent/browser";import{z as ha}from"zod";var ba=ha.object({ref:ga.describe("browser_snapshot 返回的 @eN element ref"),text:ha.string().describe("要输入的文本"),clear:ha.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:ha.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:pa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),ya=ma({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:ba,output:ca,execute:async(e,t)=>{const n=g(),a=f(),r=await Kt({runtime:n,ctxManager:a,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=Wt.getRef(r.targetId,e.ref);if(void 0===o)throw new ua({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:r.targetId}});const i=ot(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(r,{...rt(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await s.bringToFront().catch(()=>{});const t=jn(s);await t.begin(`正在输入到 ${e.ref}`);const n=await fa({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await Fn(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),n}catch(t){await jn(s).fail(`输入到 ${e.ref} 失败`);const n=it(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as va}from"@roll-agent/sdk";import{BrowserPageInfoSchema as wa}from"@roll-agent/browser";import{z as xa}from"zod";var Sa=["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"],Ca=[...Sa,"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"],Ra=xa.enum(["localStorage","sessionStorage"]),Ia=xa.enum(["array","object","string","number","boolean","null"]),ka=xa.enum(["empty","json","string"]),Aa=xa.enum(Sa),Na=xa.enum(Ca),Ma=xa.enum(["request","response"]),Ea=xa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),Pa=xa.object({area:Ra,key:xa.string(),valueLength:xa.number().int().nonnegative(),valueKind:ka,jsonKind:Ia.optional(),jsonTopLevelKeys:xa.array(xa.string()).optional(),jsonArrayLength:xa.number().int().nonnegative().optional(),numericFields:xa.record(xa.number()).optional(),booleanFields:xa.record(xa.boolean()).optional(),arrayLengths:xa.record(xa.number().int().nonnegative()).optional()}),Ta=xa.object({name:xa.string(),domain:xa.string(),path:xa.string(),expires:xa.string(),valueLength:xa.number().int().nonnegative(),httpOnly:xa.boolean(),secure:xa.boolean(),sameSite:xa.string().optional()}),_a=xa.object({before:xa.number().optional(),after:xa.number().optional(),delta:xa.number().optional()}),Ba=xa.object({before:xa.boolean().optional(),after:xa.boolean().optional()}),Oa=xa.object({before:xa.number().int().nonnegative().optional(),after:xa.number().int().nonnegative().optional(),delta:xa.number().int().optional()}),$a=xa.object({area:Ra,key:xa.string(),beforePresent:xa.boolean(),afterPresent:xa.boolean(),numericDeltas:xa.record(_a).optional(),booleanChanges:xa.record(Ba).optional(),arrayLengthDeltas:xa.record(Oa).optional()}),qa=xa.object({url:xa.string(),title:xa.string(),visibilityState:xa.string(),hasFocus:xa.boolean()}),Da=xa.object({navigatorWebdriver:xa.boolean().optional(),userAgentContainsHeadless:xa.boolean(),languagesLength:xa.number().int().nonnegative(),pluginsLength:xa.number().int().nonnegative(),hasChromeRuntime:xa.boolean(),permissionQueryIsNative:xa.boolean().optional(),hasPlaywrightBinding:xa.boolean(),hasPwInitScripts:xa.boolean(),cdcKeys:xa.array(xa.string()),webdriverKeys:xa.array(xa.string()),automationLikeWindowKeys:xa.array(xa.string())}),ja=xa.object({rootNodeId:xa.number().int(),rootNodeName:xa.string(),childNodeCount:xa.number().int().nonnegative().optional(),bodyTextLength:xa.number().int().nonnegative().optional(),elementCount:xa.number().int().nonnegative().optional()}),Fa=xa.object({type:xa.literal("mouseMoved"),x:xa.number(),y:xa.number()}),La=xa.object({targetId:xa.string(),websocketUrlAvailable:xa.boolean(),connected:xa.boolean(),pageBroughtToFront:xa.boolean().optional(),runtimeEnabled:xa.boolean().optional(),evaluate:qa.optional(),dom:ja.optional(),input:Fa.optional()}),za=xa.object({phase:Aa,success:xa.boolean(),durationMs:xa.number().int().nonnegative(),error:xa.string().optional()}),Ja=xa.object({phase:Na,capturedAt:xa.string(),targetFound:xa.boolean(),page:wa.optional(),urlChangedFromPrevious:xa.boolean(),titleChangedFromPrevious:xa.boolean(),previousUrl:xa.string().optional(),currentUrl:xa.string().optional(),previousTitle:xa.string().optional(),currentTitle:xa.string().optional()}),Ha=xa.object({kind:Ma,reason:Ea,capturedAt:xa.string(),url:xa.string(),method:xa.string().optional(),resourceType:xa.string().optional(),status:xa.number().int().optional()}),Va=xa.object({capturedAt:xa.string(),url:xa.string()}),Ua=xa.object({phase:Aa.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:xa.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:xa.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:xa.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),Wa=xa.object({success:xa.boolean(),requestedPhase:Aa,mode:xa.string(),nativePages:xa.array(wa),targetPage:wa.optional(),browserAttached:xa.boolean(),pageAttached:xa.boolean(),nativeTimeline:xa.array(Ja),networkEvents:xa.array(Ha).optional(),navigationEvents:xa.array(Va).optional(),nativeCdp:La.optional(),evaluate:qa.optional(),detectorFingerprint:Da.optional(),storage:xa.object({localStorage:xa.array(Pa),sessionStorage:xa.array(Pa),cookies:xa.array(Ta),counterDiffs:xa.array($a)}).optional(),phases:xa.array(za),warnings:xa.array(xa.string())}),Ka=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Ya=new Set(Ka),Ga=["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"],Xa=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],Za={"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 Qa(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 er(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function tr(e){return Ga.includes(e)}function nr(e){return Xa.includes(e)}function ar(e){try{return JSON.parse(e)}catch{return}}function rr(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 or(e){return new Promise(t=>{setTimeout(t,e)})}function ir(e){return Ya.has(e)}function sr(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=ar(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const a=Qa(n),r=er(n)&&ir(t.key)?rr(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:a,...er(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...r}}function cr(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 lr(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 dr(e,t,n){if(void 0!==t){const a=e.find(e=>e.targetId===t);return a?Oe(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=>Oe(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 ur(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:qe(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 mr(e,t,n,a){const r=await e.listNativePages();return ur(e,t,void 0!==n?r.find(e=>e.targetId===n):r.filter(e=>Oe(e.url,"zhipin"))[0],a)}async function pr(e,t,n,a,r){try{const o=t[t.length-1],i=await mr(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 gr(e,t,n,a,r,o){const i=t.length,s=Date.now();for(;;){await pr(e,t,n,a,o);const i=r-(Date.now()-s);if(i<=0)break;await or(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function fr(e){if(!er(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 hr(e,t){if(!er(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!er(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=er(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 br(e){return fr(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function yr(e){return hr(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 vr(e){return hr(await e.getDocument({depth:1,pierce:!1}),void 0)}async function wr(e){return await e.bringToFront(),!0}async function xr(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 Sr(e,t,n,a,r,o){await pr(e,t,n,a,o);const i=await gr(e,t,Za[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 Cr(e,t,n,a){switch(e){case"native-page-bring-front":{const r=await lr(e,async()=>await wr(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 lr(e,async()=>await br(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 lr(e,async()=>await vr(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 lr(e,async()=>await xr(t));return n.push(r.phaseResult),void 0!==r.result&&(a.input=r.result),r.phaseResult}}}async function Rr(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 lr("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 Sr(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(nr(e.requestedPhase))return await Cr(e.requestedPhase,o,e.phases,t),i=await Sr(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await lr("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await Sr(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 lr("native-evaluate-url",async()=>await br(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await Sr(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 lr("native-dom-read",async()=>await yr(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await Sr(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{a?.close()}}function Ir(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 kr(e,t,n){const a=[],r=[],o=e=>{a.length<n&&a.push(e)},i=e=>{const t=e.url(),n=Ir(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=Ir(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 or(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:a,navigationEvents:r}}function Ar(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Oe(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function Nr(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function Mr(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 Er(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},Ka)).map(e=>sr(e.area,{key:e.key,value:e.value}))}async function Pr(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function Tr(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 _r(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function Br(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function Or(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function $r(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function qr(e,t){const n=_r(e),a=_r(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 Br(t?.numericFields,r?.numericFields)){const n=t?.numericFields?.[e],a=r?.numericFields?.[e];n!==a&&(s[e]=Or(n,a))}const c={};for(const e of Br(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 Br(t?.arrayLengths,r?.arrayLengths)){const n=t?.arrayLengths?.[e],a=r?.arrayLengths?.[e];n!==a&&(l[e]=$r(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 Dr(e,t){const[n,a,r]=await Promise.all([Mr(e,"localStorage"),Mr(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>ir(e.key)).map(e=>sr("localStorage",e)),...a.filter(e=>ir(e.key)).map(e=>sr("sessionStorage",e))];return{localStorage:n.map(e=>sr("localStorage",e)),sessionStorage:a.map(e=>sr("sessionStorage",e)),cookies:r.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>cr(e)),counterDiffs:qr(t,o)}}var jr=va({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:Ua,output:Wa,execute:async(e,t)=>{const n=e.phase??"native",a=e.watchMs??3e3,r=e.networkEventLimit??30,o=f(),i=g(),s=[],c=[],l=[];let d,u,m,p=!1,h=!1,b=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const y=await lr("native",async()=>await o.listNativePages());c.push(y.phaseResult);const v=y.result??[],w=v.map(e=>qe(o,e)),x=dr(v,e.targetPageId,s);if(x&&(d=qe(o,x),b=x.targetId,l.push(ur(o,"native",x,void 0))),!y.phaseResult.success||"native"===n)return{success:y.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await gr(o,l,"native-watch",b,a,s),{success:y.phaseResult.success&&void 0!==b,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(!x)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:w,browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(tr(n)){const e=await Rr({requestedPhase:n,target:x,runtime:i,ctxManager:o,targetPageId:b,watchMs:a,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}const S=await lr("browser-attach",async()=>await i.getBrowser());c.push(S.phaseResult);const C=S.result;p=S.phaseResult.success,await pr(o,l,"browser-attach",b,s);const R=await gr(o,l,"browser-attach-watch",b,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:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if(R)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};const I=[];let k;"network-watch"===n&&void 0!==C&&(k=Ar(C,x),k?I.push(kr(k,a,r)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",x);const A=await lr("page-attach",async()=>await o.getPage("zhipin"));c.push(A.phaseResult);const N=A.result;if(h=A.phaseResult.success,N&&(d=await De(o,N)),await pr(o,l,"page-attach",b,s),!A.phaseResult.success||!N){if("network-watch"===n&&I.length>0){const e=await lr("network-watch",async()=>Nr(await Promise.all(I),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:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,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:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){k!==N&&I.push(kr(N,a,r));const e=await lr("network-watch",async()=>Nr(await Promise.all(I),r));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await pr(o,l,"network-watch",b,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let M=[];if("storage-summary"===n){try{M=await Er(N)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}a>0&&await or(a)}const E=await lr("page-evaluate",async()=>await Pr(N));c.push(E.phaseResult);const P=E.result;if(await pr(o,l,"page-evaluate",b,s),!E.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,...void 0!==P?{evaluate:P}:{},phases:c,warnings:s};const T=await lr("detector-fingerprint",async()=>await Tr(N));c.push(T.phaseResult);const _=T.result;if(await pr(o,l,"detector-fingerprint",b,s),!T.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,...void 0!==P?{evaluate:P}:{},...void 0!==_?{detectorFingerprint:_}:{},phases:c,warnings:s};const B=await lr("storage-summary",async()=>await Dr(N,M));c.push(B.phaseResult);const O=B.result;return await pr(o,l,"storage-summary",b,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:w,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:h,nativeTimeline:l,...void 0!==P?{evaluate:P}:{},...void 0!==_?{detectorFingerprint:_}:{},...void 0!==O?{storage:O}:{},phases:c,warnings:s}}});import{defineTool as Fr}from"@roll-agent/sdk";import{z as Lr}from"zod";import{setTimeout as zr}from"node:timers/promises";async function Jr(e,t=300,n=800){const a=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(a)}function Hr(e){return e.trim().toLocaleLowerCase("zh-CN")}function Vr(e,t){let n=0;for(const a of e)t.includes(a)&&(n+=1);return n}function Ur(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=Hr(n),a=e.filter(e=>e.name.length>0);let r=a.find(e=>Hr(e.name)===t);if(r)return r;if(r=a.find(e=>{const n=Hr(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=Hr(e.name);return Vr(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),r)return r}if(void 0!==t.index)return e[t.index]}var Wr=["chat-list","chat-history","recommend-list"],Kr={"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 Yr(e){return Kr[e]}var Gr={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"}},Xr=30,Zr=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Qr(e){const t=e.trim();return!(0===t.length||t.length>Xr)&&(!Zr.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function eo(e){let t=e.priority;return Zr.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 to(e){const t=e.filter(e=>Qr(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=eo(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 no='.user-list.b-scroll-stable [role="listitem"], .geek-item',ao='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',ro=".candidate-card-wrap",oo="[data-geek], .geek-item",io=`${ro}, ${oo}`,so=["/web/chat/recommend"],co=250,lo=300,uo=900,mo=1200,po=4,go=4,fo=250,ho=90,bo=520,yo=16,vo=50,wo=650,xo=10,So=.015,Co="button, a, label, li, span, div, [role='button'], [role='radio']";function Ro(e){return new Promise(t=>{setTimeout(t,e)})}function Io(e){return{x:e.x,y:e.y}}function ko(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ao(e){return e.includes("/web/chat/index")}function No(e){return e.trim().toLocaleLowerCase("zh-CN")}function Mo(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function Eo(e,t){const n=No(e),a=No(t);return n.length>0&&a.length>0&&(n===a||n.includes(a)||a.includes(n))}function Po(e){return"string"==typeof e?e:""}function To(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function _o(e){return"boolean"==typeof e&&e}function Bo(e){return Array.isArray(e)?e.map((e,t)=>{const n=ko(e)?e:{};return{conversationId:Po(n.conversationId),candidateId:Po(n.candidateId),name:Po(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:Po(n.position),hasUnread:_o(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:Po(n.lastMessageTime),messagePreview:Po(n.messagePreview)}}):[]}function Oo(e){return Array.isArray(e)?e.flatMap(e=>{if(!ko(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 $o(e){return Array.isArray(e)?e.map((e,t)=>{const n=ko(e)?e:{},a=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:Po(n.candidateId),name:Po(n.name),age:Po(n.age),experience:Po(n.experience),education:Po(n.education),workStatus:Po(n.workStatus),company:Po(n.company),currentPosition:Po(n.currentPosition),expectedLocation:Po(n.expectedLocation),expectedPosition:Po(n.expectedPosition),expectedSalary:Po(n.expectedSalary),tags:Array.isArray(a)?a.map(e=>Po(e)).filter(Boolean):[],buttonText:Po(n.buttonText)}}):[]}function qo(e){return Array.isArray(e)?e.map((e,t)=>{const n=ko(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:Po(n.value),label:Po(n.label),isCurrent:_o(n.isCurrent)}}):[]}function Do(e){return ko(e)?{found:_o(e.found),isOpen:_o(e.isOpen),currentLabel:Po(e.currentLabel),currentValue:Po(e.currentValue),options:qo(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function jo(e){if(!ko(e))return null;const t=Po(e.conversationId),n=Po(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:Po(e.candidateName)}}function Fo(e){if(!ko(e))return null;const t=Po(e.candidateName);return t.length>0?{candidateName:t}:null}function Lo(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!ko(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:Po(e.content),time:Po(e.time)}]}):[]}function zo(e){const t=ko(e)?e:{},n=t.tags;return{name:Po(t.name),age:Po(t.age),experience:Po(t.experience),education:Po(t.education),communicationPosition:Po(t.communicationPosition),expectedJobText:Po(t.expectedJobText),expectedSalary:Po(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>Po(e)).filter(Boolean):[]}}function Jo(e){const t=ko(e)?e:{};return{selectedTarget:jo(t.selectedTarget),activePanel:Fo(t.activePanel),candidateInfo:zo(t.candidateInfo),messages:Lo(t.messages)}}function Ho(e){return ko(e)?{found:_o(e.found),x:To(e.x),y:To(e.y)}:{found:!1,x:0,y:0}}function Vo(e){return ko(e)?{found:_o(e.found),left:To(e.left),top:To(e.top)}:{found:!1,left:0,top:0}}function Uo(e){return ko(e)?{found:_o(e.found),cardSelector:Po(e.cardSelector)||ro,candidateId:Po(e.candidateId),name:Po(e.name),..."string"==typeof e.age?{age:e.age}:{},..."string"==typeof e.experience?{experience:e.experience}:{},..."string"==typeof e.education?{education:e.education}:{},..."string"==typeof e.workStatus?{workStatus:e.workStatus}:{},..."string"==typeof e.company?{company:e.company}:{},..."string"==typeof e.currentPosition?{currentPosition:e.currentPosition}:{},..."string"==typeof e.expectedLocation?{expectedLocation:e.expectedLocation}:{},..."string"==typeof e.expectedPosition?{expectedPosition:e.expectedPosition}:{},..."string"==typeof e.expectedSalary?{expectedSalary:e.expectedSalary}:{},hasGreetButton:_o(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:ro,candidateId:"",name:"",hasGreetButton:!1}}function Wo(e){return ko(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 Ko(e){return ko(e)&&!0===e.ok?{ok:!0,current:Wo(e.current),trackLeft:To(e.trackLeft),trackTop:To(e.trackTop),trackWidth:To(e.trackWidth),trackHeight:To(e.trackHeight),minHandleX:To(e.minHandleX),minHandleY:To(e.minHandleY),maxHandleX:To(e.maxHandleX),maxHandleY:To(e.maxHandleY)}:{ok:!1,error:ko(e)&&Po(e.error)||"未找到年龄滑块"}}function Yo(e){return ko(e)?{containerFound:_o(e.containerFound),containerLabel:Po(e.containerLabel),scrollTop:To(e.scrollTop),scrollHeight:To(e.scrollHeight),clientHeight:To(e.clientHeight),itemCount:To(e.itemCount),atStart:_o(e.atStart),atEnd:_o(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Go(e){return[e,...(e.childFrames??[]).flatMap(e=>Go(e))]}function Xo(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 Zo(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 Qo(e,t,n){const a=t.filter(e=>Oe(e.url,"zhipin")),r=a.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(r&&Ao(r.url))return r;const e=a.filter(e=>Ao(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 ei(e={},t={}){const n=t.ctxManager??f(),a=t.runtime??g(),r=Qo(n,await n.listNativePages(),e),o=await a.connectNativePage(r);return new ti({target:r,controller:o})}var ti=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new Bn(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 Ro(co)}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(ao)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await Ro(co)}return!1}async isRecommendSurfaceOpen(){const e=[Gr.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(so)};\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 Ro(co)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Go(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 Vo(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Gr.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=Ho(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Ho(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 this.mouse.click(Io(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??ho,settleMs:t.settleMs??fo}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(io)}) !== 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 Ro(co)}else await Ro(co);return await this.hasRecommendList().catch(()=>!1)}async readRecommendJobSelectorState(){const e='(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const text = (element) => (element.textContent ?? "").replace(/\\s+/g, " ").trim();\n const wrap = document.querySelector(".job-selecter-wrap");\n if (!wrap) {\n return {\n found: false,\n isOpen: false,\n currentLabel: "",\n currentValue: "",\n options: []\n };\n }\n\n const list = wrap.querySelector(".ui-dropmenu-list");\n const options = Array.from(wrap.querySelectorAll(".job-list .job-item")).map(\n (item, index) => {\n const labelElement = item.querySelector(".label") ?? item;\n return {\n index,\n value: item.getAttribute("value") ?? item.getAttribute("data-value") ?? "",\n label: text(labelElement),\n isCurrent: item.classList.contains("curr")\n };\n },\n );\n const current = options.find((option) => option.isCurrent);\n const label = wrap.querySelector(".ui-dropmenu-label");\n return {\n found: true,\n isOpen: Boolean(list && visible(list)),\n currentLabel: current?.label ?? (label ? text(label) : ""),\n currentValue: current?.value ?? "",\n options\n };\n })()',t=Do(await this.evaluateJson(e).catch(()=>{}));return t.found?t:Do(await this.evaluateRecommendFrameJson(e))}async openRecommendJobSelector(e={}){if((await this.readRecommendJobSelectorState()).isOpen)return!0;const t=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const wrap = document.querySelector(".job-selecter-wrap");\n const label = wrap?.querySelector(".ui-dropmenu-label") ?? wrap;\n if (!label || !visible(label)) {\n return { found: false, x: 0, y: 0 };\n }\n label.scrollIntoView({ block: "center", inline: "center" });\n const rect = label.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 })()');return!!await this.dispatchNativeClick(t,e)&&(await Ro(900),(await this.readRecommendJobSelectorState()).isOpen)}async setRecommendJobSearch(e,t={}){const n=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const input = document.querySelector(".job-selecter-wrap .top-chat-search input.ipt.chat-job-search");\n if (!(input instanceof HTMLInputElement) || !visible(input)) {\n return { found: false, x: 0, y: 0 };\n }\n input.scrollIntoView({ block: "center", inline: "center" });\n const rect = input.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(n,t))return!1;if(await Ro(260),await this.selectAllFocusedText(),await Ro(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await Ro(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await Ro(500),!0;await Ro(350);for(const t of Array.from(e))await this.controller.insertText(t),await Ro(110);return await Ro(650),!0}selectRecommendJobMatch(e,t){const n=e=>{const t=e[0];return{...void 0!==t?{selected:t}:{},matchedCount:e.length}};if(void 0!==e.jobValue){return n(t.filter(t=>t.value===e.jobValue))}if(void 0!==e.jobName){const a=Mo(e.jobName),r=t.filter(e=>Mo(e.label)===a);if(r.length>0)return n(r);return n(t.filter(e=>Mo(e.label).includes(a)))}if(void 0!==e.index){return n(t.filter(t=>t.index===e.index))}return{matchedCount:0}}getCurrentRecommendJobOption(e){const t=e.options.find(e=>e.isCurrent);return void 0!==t?t:0!==e.currentLabel.length||0!==e.currentValue.length?{index:-1,value:e.currentValue,label:e.currentLabel,isCurrent:!0}:void 0}currentRecommendJobMatchesRequest(e,t){if(void 0===t)return!1;if(void 0!==e.jobValue&&t.value.length>0)return t.value===e.jobValue;if(void 0!==e.jobName&&t.label.length>0){const n=Mo(e.jobName),a=Mo(t.label);return a===n||a.includes(n)}return void 0!==e.index&&t.index===e.index}hasRecommendJobAlternative(e,t){return t.some(n=>!n.isCurrent&&(void 0===e?t.length>1:n.value.length>0&&e.value.length>0?n.value!==e.value:n.label!==e.label||n.index!==e.index))}async clickRecommendJobOption(e,t={}){const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedValue = ${JSON.stringify(e.value)};\n const expectedIndex = ${JSON.stringify(e.index)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const items = Array.from(document.querySelectorAll(".job-selecter-wrap .job-list .job-item"));\n const item = items.find((candidate, index) => {\n const value = candidate.getAttribute("value") ?? candidate.getAttribute("data-value") ?? "";\n return expectedValue.length > 0 ? value === expectedValue : index === expectedIndex;\n });\n if (!item || !visible(item)) {\n return { found: false, x: 0, y: 0 };\n }\n item.scrollIntoView({ block: "center", inline: "center" });\n const rect = item.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 })()`);return await this.dispatchNativeClick(n,t)}async listRecommendJobs(e={}){if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",options:[],availableCount:0,canSwitch:!1,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(e)){const e=await this.readRecommendJobSelectorState(),t=this.getCurrentRecommendJobOption(e);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",...void 0!==t?{current:t}:{},options:e.options,availableCount:e.options.length,canSwitch:!1,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",e),await Ro(700);const t=await this.readRecommendJobSelectorState(),n=this.getCurrentRecommendJobOption(t);return{success:!0,status:"listed",...void 0!==n?{current:n}:{},options:t.options,availableCount:t.options.length,canSwitch:this.hasRecommendJobAlternative(n,t.options)}}async selectRecommendJob(e,t={}){const n={...e};if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",requested:n,options:[],matchedCount:0,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(t)){const e=await this.readRecommendJobSelectorState(),t=e.options.find(e=>e.isCurrent);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",requested:n,...void 0!==t?{current:t}:{},options:e.options,matchedCount:0,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",t),await Ro(700);let a=await this.readRecommendJobSelectorState();const r=this.getCurrentRecommendJobOption(a);if(void 0!==r&&this.currentRecommendJobMatchesRequest(n,r)&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:r,selected:r,options:a.options,matchedCount:1};let o=this.selectRecommendJobMatch(n,a.options);const i=n.searchKeyword??n.jobName;void 0===o.selected&&!1!==n.useSearch&&void 0!==i&&i.trim().length>0&&await this.setRecommendJobSearch(i,t)&&(await Ro(900),a=await this.readRecommendJobSelectorState(),o=this.selectRecommendJobMatch(n,a.options));const s=this.getCurrentRecommendJobOption(a);if(void 0===o.selected)return{success:!1,status:"not_found",requested:n,...void 0!==s?{current:s}:{},options:a.options,matchedCount:o.matchedCount,error:"未找到匹配的招聘岗位"};if(o.selected.isCurrent&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:o.selected,selected:o.selected,options:a.options,matchedCount:o.matchedCount};if(!await this.clickRecommendJobOption(o.selected,t))return{success:!1,status:"selector_not_found",requested:n,...void 0!==s?{current:s}:{},selected:o.selected,options:a.options,matchedCount:o.matchedCount,error:"未能点击匹配的招聘岗位"};let c=await this.readRecommendJobSelectorState();for(let e=0;e<6;e+=1){const e=this.getCurrentRecommendJobOption(c);if(e?.value===o.selected.value||e?.label===o.selected.label)break;await Ro(600),c=await this.readRecommendJobSelectorState()}const l=c.options.find(e=>e.value===o.selected?.value)??o.selected;return{success:!0,status:"selected",requested:n,current:l,selected:l,options:c.options.length>0?c.options:a.options,matchedCount:o.matchedCount}}async clickSidebarSection(e,t={}){const n="chat"===e?Gr.nav.chatLink:Gr.nav.recommendLink,a=Ho(await this.evaluateJson(`(() => {\n const selector = ${JSON.stringify(n)};\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(Gr.nav.sidebar)}) ?? document;\n const selectorTargets = Array.from(document.querySelectorAll(selector))\n .filter((element) => visible(element))\n .sort((left, right) => area(left) - area(right));\n if (selectorTargets[0]) {\n return readCenter(selectorTargets[0]);\n }\n\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 this.dispatchNativeClick(a,t)}async scrollSurface(e,t={}){const n=Yr(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 Ro(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 Ro(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 Ro(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=Xo(a);if(r.length>=t||e===n)return r;if(!(await this.scrollChatList()).ok)return r;await Ro(lo)}return Xo(a)}async openChat(e){if(await this.bringToFront().catch(()=>{}),!await this.isChatSurfaceOpen().catch(()=>!1)){if(!await this.clickSidebarSection("chat",{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})||!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=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index,n=Math.max(0,Math.floor(e.maxScrolls??(t?12:4))),a=async t=>{if(!await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}}))return{...t,found:!1,error:`未能点击候选人: ${t.name||t.conversationId}`};await Ro(fo);let n=await this.waitForNativeChatReady(t);if(!n){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await Ro(fo),n=await this.waitForNativeChatReady(t))}return n?{...t,found:!0}:{...t,found:!1,error:`打开候选人聊天后,右侧会话未同步切换到 ${t.name||t.conversationId}`}},r=async(n,r)=>{const o=[];for(let i=0;i<=r;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Xo(o),c=!0!==e.preferUnread||t?Ur(s,e):s.find(e=>e.hasUnread);if(void 0!==c)return await a(c);if(i>=r)break;if(!(await this.scrollChatList(n)).ok)break;await Ro(lo)}},o=await r("down",t?0:n);if(void 0!==o)return o;if(t&&n>0){for(let e=0;e<n;e+=1){const e=await this.scrollChatList("up");if(!e.ok||e.after<=1||e.after>=e.before)break;await Ro(lo)}const e=await r("down",n);if(void 0!==e)return e}return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${e.candidateName??(void 0!==e.conversationId?`conversationId ${e.conversationId}`:`index ${e.index??0}`)}`}}async readSelectedChatTarget(){return jo(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 Fo(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 Ro(co)}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 Jo(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=Uo(await this.evaluateJson(n).catch(()=>{}));if(a.found)return a;const r=Uo(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=Ho(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=Ho(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 Ro(1800);const a=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==a?{wechatNumber:a}:{}}}async sendChatReply(e,t={}){const n=Ho(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 Ro(250);const a=Ho(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 Ro(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=Zo(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<po;t+=1){await Ro(mo);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>=go){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:uo});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 Oo(await this.evaluateJson(`(() => {\n const limit = ${Xr};\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([Gr.username.primary,...Gr.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(ro)};\n const fallbackSelector = ${JSON.stringify(oo)};\n const iframe = document.querySelector(${JSON.stringify(Gr.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(so)};\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 splitParts = (text) => (text ?? "")\n .split(/[丨·|]/)\n .map((part) => part.trim())\n .filter(Boolean);\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 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 = splitParts(baseInfoEl.textContent?.trim() ?? "");\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 const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = splitParts(workExpEl?.textContent?.trim() ?? "");\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\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 = splitParts(contentEl.textContent?.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 = splitParts(expectEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\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 {\n found: true,\n cardSelector,\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 hasGreetButton\n };\n })()`}buildRecommendGreetClickExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(ro)};\n const fallbackSelector = ${JSON.stringify(oo)};\n const iframe = document.querySelector(${JSON.stringify(Gr.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 Ro(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 Ro(40)}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${Gr.recommend.filterButton}, ${ro}, ${Gr.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 Ro(co)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Gr.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(Gr.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 Ro(co)}}await Ro(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(Gr.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Co)};\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(Gr.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(yo)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(vo)} -\n ${JSON.stringify(yo)})\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=Wo(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:Wo(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Gr.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=Ko(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Ko(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 this.mouse.drag({x:e,y:t},{x:n,y:a},{...void 0!==r.motionObserver?{motionObserver:r.motionObserver}:{},pressDurationMs:r.pressDurationMs??ho})}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 Ro(wo),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-yo)/(vo-yo)))}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+So),s="min"===e?Math.max(0,o-So):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let a=0;a<xo;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??yo,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(Gr.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Co)};\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(Gr.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 Ro(600),!0;await Ro(co)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Gr.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 Bo(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(no)}));\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&&Eo(e.name,n.candidateName);if(a&&r)return!0;await Ro(co)}return!1}async clickChatCandidate(e,t={}){const n=Ho(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(no)}));\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!!n.found&&await this.dispatchNativeClick(n,t)}async readVisibleRecommendCandidates(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return[];const e=`(() => {\n const iframe = document.querySelector(${JSON.stringify(Gr.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(so)};\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(ro)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(oo)}));\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=$o(await this.evaluateJson(e));if(t.length>0)return t;return $o(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=Yr(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(Gr.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(so)};\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=Yo(await this.evaluateJson(n));if("recommend-list"!==e||a.itemCount>0)return a;const r=Yo(await this.evaluateRecommendFrameJson(n));return r.itemCount>0?r:a}async scrollSurfaceWithWheel(e,t,n){const a=Yr(e);await this.bringToFront();const r=Ho(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(Gr.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.mouse.moveTo(Io(r)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:r.x,y:r.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??bo):n??bo}),await Ro(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=Yr(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(Gr.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(so)};\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=Yo(await this.evaluateJson(r));if("recommend-list"!==e||o.itemCount>0)return o;const i=Yo(await this.evaluateRecommendFrameJson(r));return i.itemCount>0?i:o}async scrollChatList(e="down"){const t=await this.scrollSurface("chat-list",{direction:e,steps:1,settleMs:0});return{ok:t.success&&t.stepsCompleted>0,before:t.before.scrollTop,after:t.after.scrollTop,max:Math.max(0,t.after.scrollHeight-t.after.clientHeight)}}};import{createHash as ni}from"node:crypto";var ai,ri,oi={fetch:fetch,env:process.env},ii="https://huajune.duliday.com",si=new Set;function ci(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function li(e,t){const n=ci(e,t);if(void 0!==n){if("true"===n)return!0;if("false"===n)return!1;throw new Error(`Expected boolean env value "true" or "false" for ${t}, received "${n}".`)}}function di(e){const t=li(e,"RECRUITMENT_EVENTS_ENABLED"),n=ci(e,"RECRUITMENT_EVENTS_API_BASE_URL")??ii,a=ci(e,"RECRUITMENT_EVENTS_API_TOKEN"),r=ci(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==a?{apiToken:a}:{},...void 0!==r?{defaultAgentId:r}:{}}}function ui(e){return e.defaultAgentId}function mi(e,t,n){si.has(t)||(si.add(t),e.warn(n))}function pi(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function gi(e){return"object"==typeof e&&null!==e}function fi(e,t){const n=e[t];return"string"==typeof n?n:void 0}function hi(e){if(!gi(e)||!gi(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>gi(e)&&"error"===fi(e,"status"));if(!gi(t))return;const n=fi(t,"idempotencyKey"),a=t.error,r=gi(a)?fi(a,"code"):void 0,o=gi(a)?fi(a,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==r?` ${r}`:""}${void 0!==o?`: ${o}`:""}`}async function bi(e,t,n){const a=di(n.env);if(!a.enabled)return;const r=ui(a);if(void 0===a.apiToken||void 0===r)return void mi(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN and RECRUITMENT_EVENTS_DEFAULT_AGENT_ID.");const o={events:[{...e,agentId:r,eventTime:e.eventTime??(new Date).toISOString()}]},i=await n.fetch(pi(a.apiBaseUrl),{method:"POST",headers:{Authorization:`Bearer ${a.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!i.ok){const e=await i.text().catch(()=>"");throw new Error(`Recruitment events API request failed (${i.status})${e.length>0?`: ${e}`:""}`)}const s=hi(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function yi(e,t){return`${e}:${ni("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function vi(e,t){(async()=>{const n=ri??(async(e,t)=>{await bi(e,t,{...oi,...ai})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function wi(){return(new Date).toISOString().slice(0,10)}function xi(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function Si(e,t,n){const a=e?.candidateInfo,r=xi(a?.age),o=xi(a?.education),i=xi(a?.expectedSalary);return{name:xi(a?.name)??t,position:n,...void 0!==r?{age:r}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function Ci(e){return xi(e?.candidateInfo.communicationPosition)??""}function Ri(e){const t=xi(e?.candidateInfo.expectedJobText);if(void 0===t)return"";const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?n[1]??"":t}function Ii(e){const t=xi(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?xi(n[0]):void 0}function ki(e){return e.length>0?{job:{jobName:e}}:{}}function Ai(e,t){return e.trim().length>0&&t.trim().length>0}function Ni(e){const t=e.content.trim(),n=/微信(?:号)?\s*[::]?\s*([A-Za-z0-9_-]{5,})/.exec(t);return void 0!==n?.[1]?n[1]:/^[A-Za-z][A-Za-z0-9_-]{5,}$/.test(t)?t:void 0}function Mi(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&Ai(n.name,n.position)&&vi({idempotencyKey:yi("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,wi()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_received",candidate:{name:n.name,position:n.position},job:{jobId:0,jobName:n.position},details:{unreadCount:n.unreadCount>0?n.unreadCount:1,lastMessagePreview:n.preview,conversationId:n.conversationId,candidateId:n.candidateId}},t)}function Ei(e,t){const n=Ci(e.candidateDetails),a=n,r=Si(e.candidateDetails,e.candidateName,a);if(!Ai(r.name,r.position))return;const o=Ii(e.candidateDetails);vi({idempotencyKey:yi("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...r,...void 0!==o?{expectedLocation:o}:{}},...ki(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Pi(e,t){if(!e.clicked||0===e.name.length)return;const n=xi(e.expectedPosition);if(void 0===n||!Ai(e.name,n))return;const a=xi(e.age),r=xi(e.education),o=xi(e.expectedSalary),i=xi(e.expectedLocation);vi({idempotencyKey:yi("zhipin-candidate-contacted",[e.candidateId,e.name,wi()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"candidate_contacted",candidate:{name:e.name,position:n,...void 0!==a?{age:a}:{},...void 0!==r?{education:r}:{},...void 0!==o?{expectedSalary:o}:{},...void 0!==i?{expectedLocation:i}:{}},details:{candidateId:e.candidateId}},t)}function Ti(e,t){const n=Ci(e.candidateDetails),a=Si(e.candidateDetails,e.candidateName,n);if(!Ai(a.name,a.position))return;const r=Ii(e.candidateDetails);vi({idempotencyKey:yi("zhipin-wechat-requested",[e.conversationId,e.candidateId,wi()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...a,...void 0!==r?{expectedLocation:r}:{}},...ki(n),details:{exchangeType:e.exchangeType??(void 0!==e.wechatNumber?"accepted":"requested"),conversationId:e.conversationId,candidateId:e.candidateId,...void 0!==e.wechatNumber?{wechatNumber:e.wechatNumber}:{}}},t)}function _i(e,t,n,a){const r=Ci(e),o=Ri(e),i=Ii(e),s=Si(e,e.candidateInfo.name,o);if(!Ai(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=Ni(c);vi({idempotencyKey:yi("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...ki(r),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},a)}var Bi,Oi=Lr.object({name:Lr.string(),conversationId:Lr.string(),candidateId:Lr.string(),position:Lr.string(),time:Lr.string(),preview:Lr.string(),unreadCount:Lr.number(),hasUnread:Lr.boolean(),index:Lr.number()}),$i=Lr.object({success:Lr.boolean(),candidates:Lr.array(Oi),total:Lr.number(),stats:Lr.object({withName:Lr.number(),withUnread:Lr.number()})});function qi(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Bi}}function Di(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 ji(e){const t=new Set,n=[];for(const a of e){const e=Di(a);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(a))}return n}var Fi=Fr({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:Lr.object({limit:Lr.number().optional().describe("最多返回条数"),onlyUnread:Lr.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:Lr.enum(["time","unreadCount","name"]).default("time"),autoScroll:Lr.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:Lr.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:$i,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=qi();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=ji(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)`),Mi(d,t.logger),{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 Li}from"@roll-agent/browser";import{defineTool as zi}from"@roll-agent/sdk";import{z as Ji}from"zod";var Hi,Vi=Ji.object({success:Ji.boolean(),alreadyOnChat:Ji.boolean(),usedSidebarClick:Ji.boolean(),chatReady:Ji.boolean(),page:Li.optional(),error:Ji.string().optional()});function Ui(){return{getContextManager:f,openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Hi}}async function Wi(e,t){return qe(e,await t.inspectPage())}var Ki=zi({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测。",input:Ji.object({}),output:Vi,execute:async(e,t)=>{const n=Ui(),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(Gr.nav.sidebar,{label:e,padding:10}),await r.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,chatReady:!0,page:await Wi(a,r)};if(!await r.clickSidebarSection("chat",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1,page:await Wi(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 Wi(a,r)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1,page:await Wi(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 Yi}from"@roll-agent/sdk";import{z as Gi}from"zod";var Xi,Zi=Gi.object({success:Gi.boolean(),conversationId:Gi.string(),candidateId:Gi.string(),candidateName:Gi.string(),index:Gi.number(),hasUnread:Gi.boolean(),unreadCount:Gi.number(),lastMessageTime:Gi.string(),messagePreview:Gi.string(),error:Gi.string().optional()});function Qi(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Xi}}var es=Yi({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Gi.object({conversationId:Gi.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Gi.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Gi.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Gi.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Zi,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Qi();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,...void 0!==r?{motionObserver:r}:{}});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 ts}from"@roll-agent/sdk";import{z as ns}from"zod";var as="·",rs=/[--—–]/;function os(e){return(e??"").trim()}function is(e){const t=os(e);if(!t||!rs.test(t))return;const[n=""]=t.split(rs);return os(n)||void 0}function ss(e){const t=os(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",a=""]=t.split(as).map(e=>os(e));return{expectedLocation:n,expectedPosition:a}}function cs(e){const t=os(e.communicationPosition),{expectedLocation:n,expectedPosition:a}=ss(e.expectedJobText),r=is(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:a,...void 0!==r?{preferredBrand:r}:{}}}var ls,ds=ns.object({index:ns.number(),sender:ns.enum(["candidate","recruiter","system"]),messageType:ns.enum(["text","system","resume","wechat-exchange"]),content:ns.string(),time:ns.string()}),us=ns.object({name:ns.string(),age:ns.string(),experience:ns.string(),education:ns.string(),communicationPosition:ns.string(),expectedPosition:ns.string(),expectedLocation:ns.string(),expectedSalary:ns.string(),tags:ns.array(ns.string())}),ms=ns.object({success:ns.boolean(),conversationId:ns.string(),candidateId:ns.string(),candidateInfo:us,preferredBrand:ns.string().optional(),chatMessages:ns.array(ds),formattedHistory:ns.array(ns.string()),stats:ns.object({totalMessages:ns.number(),candidateMessages:ns.number(),recruiterMessages:ns.number(),systemMessages:ns.number()}),error:ns.string().optional()});function ps(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function gs(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:ps(),chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function fs(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 hs(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...ls}}var bs=ts({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:ns.object({conversationId:ns.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:ns.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:ns.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:ns.number().default(100).describe("最多返回的消息条数")}),output:ms,execute:async(e,t)=>{const n=e.maxMessages??100,a=hs(),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)),gs(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||!fs(u,m.candidateName)))return await l("聊天面板未同步",`右侧聊天面板未切换到 ${u}`);const p=await o.readSelectedChatTarget();if(!p)return await l("未识别当前会话","未能提取当前选中聊天的 conversationId/candidateId");if(!(void 0===d||d.conversationId.length>0&&p.conversationId===d.conversationId||0===d.conversationId.length&&d.candidateId.length>0&&p.candidateId===d.candidateId))return await l("当前会话不一致",`当前选中会话与目标会话不一致: ${d?.name||d?.conversationId||""}`);if(m&&p.candidateName.length>0&&!fs(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=cs({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=g.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),b={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 i.succeed(`已提取 ${b.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${b.totalMessages} messages`),_i(g,p.conversationId,p.candidateId,t.logger),{success:!0,conversationId:p.conversationId,candidateId:p.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:h,stats:b}}catch(e){return await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),gs(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as ys}from"@roll-agent/sdk";import{GenerateSignedReplyResponseSchema as vs,ReasoningConfigSchema as ws,ReplyStreamFinalEventSchema as xs,streamGenerateSignedReply as Ss}from"@roll-agent/reply-authority-client";import{z as Cs}from"zod";import{randomUUID as Rs}from"node:crypto";var Is=new Map;function ks(e){for(const[t,n]of Is)n.expiresAt<=e&&Is.delete(t)}function As(e,t=Math.floor(Date.now()/1e3)){if(ks(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${Rs()}`,a={preparedReplyId:n,...e};return Is.set(n,{...a,consumed:!1}),a}function Ns(e,t=Math.floor(Date.now()/1e3)){const n=Is.get(e);return void 0===n?(ks(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Is.delete(e),ks(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Is.set(e,{...n,consumed:!0}),{ok:!0,record:Es(n)})}function Ms(e,t=Math.floor(Date.now()/1e3)){const n=Is.get(e);return void 0===n?(ks(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Is.delete(e),ks(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:Es(n)}}function Es(e){return{preparedReplyId:e.preparedReplyId,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,stage:e.stage,confidence:e.confidence,expiresAt:e.expiresAt,...void 0!==e.requestId?{requestId:e.requestId}:{}}}function Ps(e){return`(() => {\n const input = ${JSON.stringify(e)};\n const rootId = "roll-agent-reply-preview-root";\n const statusId = "roll-agent-reply-preview-status";\n const draftId = "roll-agent-reply-preview-draft";\n const badgeId = "roll-agent-reply-preview-badge";\n const spinnerId = "roll-agent-reply-preview-spinner";\n const styleId = "roll-agent-reply-preview-style";\n\n if (input.mode === "clear") {\n const existingRoot = document.getElementById(rootId);\n if (!existingRoot) return true;\n\n existingRoot.style.opacity = "0";\n existingRoot.style.transform = "translateY(8px) scale(0.98)";\n window.setTimeout(() => existingRoot.remove(), 240);\n return true;\n }\n\n const ensureRoot = () => {\n let root = document.getElementById(rootId);\n if (root) return root;\n\n if (!document.getElementById(styleId)) {\n const style = document.createElement("style");\n style.id = styleId;\n style.textContent = "@keyframes rollAgentReplyPreviewSpin { to { transform: rotate(360deg); } }";\n document.head.append(style);\n }\n\n root = document.createElement("div");\n root.id = rootId;\n root.style.position = "fixed";\n root.style.right = "20px";\n root.style.bottom = "20px";\n root.style.width = "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = "42vh";\n root.style.zIndex = "2147483646";\n root.style.pointerEvents = "none";\n root.style.display = "flex";\n root.style.flexDirection = "column";\n root.style.gap = "10px";\n root.style.padding = "14px";\n root.style.borderRadius = "16px";\n root.style.border = "1px solid rgba(45, 212, 191, 0.32)";\n root.style.background = "rgba(15, 23, 42, 0.88)";\n root.style.color = "#F8FAFC";\n root.style.boxShadow = "0 22px 58px rgba(15, 23, 42, 0.34)";\n root.style.backdropFilter = "blur(14px)";\n root.style.fontFamily =\n "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";\n root.style.opacity = "0";\n root.style.transform = "translateY(8px)";\n root.style.transition = "opacity 180ms ease, transform 220ms ease";\n\n const header = document.createElement("div");\n header.style.display = "flex";\n header.style.alignItems = "center";\n header.style.gap = "8px";\n header.style.minWidth = "0";\n\n const spinner = document.createElement("div");\n spinner.id = spinnerId;\n spinner.setAttribute("aria-hidden", "true");\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.flex = "0 0 auto";\n spinner.style.borderRadius = "999px";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.transition =\n "border-color 160ms ease, background 160ms ease, box-shadow 160ms ease, transform 160ms ease";\n\n const status = document.createElement("div");\n status.id = statusId;\n status.style.minWidth = "0";\n status.style.fontSize = "13px";\n status.style.fontWeight = "700";\n status.style.lineHeight = "18px";\n status.style.overflow = "hidden";\n status.style.textOverflow = "ellipsis";\n status.style.whiteSpace = "nowrap";\n\n const badge = document.createElement("div");\n badge.id = badgeId;\n badge.style.alignSelf = "flex-start";\n badge.style.fontSize = "11px";\n badge.style.lineHeight = "16px";\n badge.style.padding = "3px 8px";\n badge.style.borderRadius = "999px";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n badge.textContent = "临时草稿";\n\n const draft = document.createElement("div");\n draft.id = draftId;\n draft.style.maxHeight = "30vh";\n draft.style.overflow = "hidden auto";\n draft.style.whiteSpace = "pre-wrap";\n draft.style.fontSize = "14px";\n draft.style.lineHeight = "21px";\n draft.style.color = "#E2E8F0";\n\n header.append(spinner, status);\n root.append(header, badge, draft);\n document.documentElement.append(root);\n return root;\n };\n\n const root = ensureRoot();\n const status = document.getElementById(statusId);\n const draft = document.getElementById(draftId);\n const badge = document.getElementById(badgeId);\n const spinner = document.getElementById(spinnerId);\n if (!status || !draft || !badge || !spinner) return false;\n\n root.style.opacity = "1";\n root.style.transform = "translateY(0)";\n\n if (typeof input.label === "string") {\n status.textContent = input.label;\n }\n\n if (typeof input.draftText === "string") {\n draft.textContent = input.draftText;\n }\n\n if (input.mode === "final" || input.mode === "complete") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.borderTopColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.background = "#22C55E";\n spinner.style.boxShadow = "0 0 0 3px rgba(34, 197, 94, 0.14)";\n badge.textContent = "最终回复";\n badge.style.background = "rgba(34, 197, 94, 0.16)";\n badge.style.color = "#BBF7D0";\n root.style.borderColor = "rgba(74, 222, 128, 0.34)";\n } else if (input.mode === "fail") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.borderTopColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.background = "#F59E0B";\n spinner.style.boxShadow = "0 0 0 3px rgba(245, 158, 11, 0.14)";\n badge.textContent = "生成失败";\n badge.style.background = "rgba(245, 158, 11, 0.18)";\n badge.style.color = "#FDE68A";\n root.style.borderColor = "rgba(251, 191, 36, 0.4)";\n } else {\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.background = "transparent";\n spinner.style.boxShadow = "none";\n badge.textContent = input.provisional === false ? "最终回复" : "临时草稿";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n root.style.borderColor = "rgba(45, 212, 191, 0.32)";\n }\n\n return true;\n })()`}var Ts,_s=class{target;constructor(e){this.target=e}async begin(e){return await this.render({mode:"begin",label:e,draftText:"",provisional:!0})}async updateStatus(e){return await this.render({mode:"status",label:e})}async updateDraft(e,t){return await this.render({mode:t?"draft":"final",draftText:e,provisional:t})}async complete(e,t){return await this.render({mode:"complete",label:e,draftText:t,provisional:!1})}async fail(e){return await this.render({mode:"fail",label:e})}async clear(){return await this.render({mode:"clear"})}async render(e){if(!X())return!1;try{return await this.target.evaluateJson(Ps(e))}catch{return!1}}},Bs=Cs.object({conversationId:Cs.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Cs.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:Cs.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Cs.number().default(100).describe("最多读取的聊天消息条数"),reasoning:ws.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),Os=Cs.object({success:Cs.boolean(),preparedReplyId:Cs.string().optional(),suggestedReply:Cs.string().optional(),stage:Cs.string().optional(),confidence:Cs.number().optional(),expiresAt:Cs.number().optional(),requestId:Cs.string().optional(),error:Cs.string().optional()}),$s={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function qs(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),createReplyPreviewVisualSession:e=>new _s(e),streamGenerateSignedReply:Ss,savePreparedReply:As,...Ts}}function Ds(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 js(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function Fs(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Ls(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function zs(e){const t=cs({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:Ls(e.data.messages),conversationHistory:Fs(e.data.messages),candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:t.communicationPosition,expectedPosition:t.expectedPosition,expectedLocation:t.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==t.preferredBrand?{preferredBrand:t.preferredBrand}:{},...void 0!==e.reasoning?{modelConfig:{reasoning:e.reasoning}}:{},target:{platform:"zhipin",conversationId:e.conversationId,candidateId:e.candidateId,recruiterUsername:e.recruiterUsername}}}function Js(e){return"phase.started"===e.type?js(e,"label")??$s[js(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(js(e,"toolName")?`: ${js(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(js(e,"toolName")?`: ${js(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function Hs(e){return{success:!1,error:e}}function Vs(e){return{success:!0,preparedReplyId:e.preparedReplyId,suggestedReply:e.suggestedReply,stage:e.stage,confidence:e.confidence,expiresAt:e.expiresAt,...void 0!==e.requestId?{requestId:e.requestId}:{}}}var Us=ys({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:Bs,output:Os,execute:async(e,t)=>{const n=qs(),a=e.maxMessages??100,r=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await o.bringToFront().catch(()=>{}),await i.begin(r?"正在打开目标聊天":"正在准备当前聊天");const c=r?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==c&&!c.found){const e=c.error??"打开聊天失败";return await i.fail(e),Hs(e)}if(void 0===c&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),Hs(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const l=c?.name??e.candidateName??"",d=await o.readActiveChatPanel();if(l.length>0&&(!d||!Ds(l,d.candidateName))){const e=`右侧聊天面板未切换到 ${l}`;return await i.fail("聊天面板未同步"),Hs(e)}const u=await o.readSelectedChatTarget();if(!u){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),Hs(e)}if(null!==d&&u.candidateName.length>0&&!Ds(u.candidateName,d.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${u.candidateName} / ${d.candidateName}`;return await i.fail("当前会话不一致"),Hs(e)}if(!(void 0===c||c.conversationId.length>0&&u.conversationId===c.conversationId||0===c.conversationId.length&&c.candidateId.length>0&&u.candidateId===c.candidateId)){const e=`当前选中会话与目标会话不一致: ${c?.name||c?.conversationId||""}`;return await i.fail("当前会话不一致"),Hs(e)}await o.waitForChatMessages();const m=await o.readCandidateChatDetails(a);if(null!==m.selectedTarget&&(m.selectedTarget.conversationId!==u.conversationId||m.selectedTarget.candidateId!==u.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${m.selectedTarget.conversationId}/${m.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),Hs(e)}const p=to(await o.readUsernameEvidence());if(!p.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),Hs(e)}const g=zs({data:m,conversationId:u.conversationId,candidateId:u.candidateId,recruiterUsername:p.username,reasoning:e.reasoning});if(0===g.candidateMessage.length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),Hs(e)}t.logger.info(`Generating reply preview for ${u.candidateName||u.candidateId}`),await i.begin("正在生成回复"),await s.begin("正在生成回复");let f,h,b="";for await(const e of n.streamGenerateSignedReply(g)){"stream.started"===e.type&&(f=js(e,"requestId"));const t=Js(e);if(void 0!==t&&(await i.begin(t),await s.updateStatus(t)),"draft.started"===e.type&&(b="",await s.updateDraft(b,!0)),"draft.delta"===e.type){b+=js(e,"delta")??"",await s.updateDraft(b,!0)}if("final"===e.type){const t=xs.parse(e),a=vs.parse(t);h=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:a.envelopeExp,...void 0!==f?{requestId:f}:{}}),await s.complete("回复已生成",a.suggestedReply)}}if(void 0===h){const e="Reply Authority stream 未返回 final";return await s.fail(e),await i.fail(e),Hs(e)}return await i.succeed("回复已生成"),Vs(h)}catch(e){const t=e instanceof Error?e.message:String(e);return await(s?.fail(t)),await(i?.fail(t)),Hs(t)}finally{o?.close()}}});import{createHash as Ws}from"node:crypto";import{defineTool as Ks}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ys}from"@roll-agent/browser";import{z as Gs}from"zod";import{defineTool as Xs}from"@roll-agent/sdk";import{z as Zs}from"zod";function Qs(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as ec}from"zod";var tc="reply-authority-service",nc="browser-use-agent/zhipin_send_reply",ac="zhipin",rc=60,oc=ec.object({platform:ec.literal(ac),username:ec.string().min(1),accountId:ec.string().min(1).optional()}),ic=ec.object({v:ec.literal(2),iss:ec.literal(tc),kid:ec.string().min(1),jti:ec.string().min(1),iat:ec.number().int(),exp:ec.number().int(),aud:ec.literal(nc),platform:ec.literal(ac),tenantId:ec.string().min(1),conversationId:ec.string().min(1),candidateId:ec.string().min(1),reply:ec.string(),policyVersion:ec.string().min(1),recruiterBinding:oc}),sc=ec.object({kid:ec.string().min(1),algorithm:ec.literal("Ed25519"),publicKey:ec.string().min(1),validFrom:ec.string().min(1),validUntil:ec.string().optional()}),cc=ec.object({keys:ec.array(sc)}),lc=new Map;function dc(e){for(const[t,n]of lc)n<e-rc&&lc.delete(t)}function uc(e,t=Math.floor(Date.now()/1e3)){return dc(t),lc.has(e)}function mc(e,t,n=Math.floor(Date.now()/1e3)){dc(n),lc.set(e,t)}import{createPublicKey as pc,verify as gc}from"node:crypto";var fc=new Map,hc=null;function bc(){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 yc(e){return fc=new Map(e.map(e=>[e.kid,e]))}async function vc(){if(hc)return hc;hc=(async()=>{const e=await fetch(bc()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return yc(cc.parse(t).keys)})();try{return await hc}finally{hc=null}}async function wc(){await vc()}async function xc(e){const t=fc.get(e);if(t)return t;const n=(await vc()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function Sc(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Cc(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")}Sc(r);const i=ic.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:a}}function Rc(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-rc)throw new Error("Envelope expired");if(e.iat>t+rc)throw new Error("Envelope issued in the future")}async function Ic(e,t=Math.floor(Date.now()/1e3)){const n=Cc(e),a=await xc(n.payload.kid),r=pc({key:Buffer.from(a.publicKey,"base64url"),format:"der",type:"spki"});if(!gc(null,Buffer.from(n.payloadJson,"utf-8"),r,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return Rc(n.payload,t),n.payload}var kc,Ac=Zs.object({success:Zs.boolean(),sentMessage:Zs.string(),error:Zs.string().optional()});function Nc(){return{getReplyAuthorityKeysLoaded:y,openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...kc}}function Mc(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 Ec(e){return"function"==typeof e.readCandidateChatDetails}async function Pc(e){if(Ec(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function Tc(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function _c(e,t){return null===t||0===e.candidateName.length||Mc(e.candidateName,t.candidateName)}async function Bc(e,t){const n=Nc();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 Ic(e.signedEnvelope);if(o=i.reply,uc(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await new _s(a).clear(),await r.begin("正在发送回复");let s=await a.readActiveChatPanel().catch(()=>null),c=await a.readSelectedChatTarget().catch(()=>null),l=0;if(!Tc(c,i)||!_c(c,s)){const t=await a.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await r.fail(t.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:t.error??"未找到目标聊天"};l=t.unreadCount,s=await a.readActiveChatPanel(),c=await a.readSelectedChatTarget()}if(null===c)return await r.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==s&&c.candidateName.length>0&&!Mc(c.candidateName,s.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${c.candidateName} / ${s.candidateName}`;return await r.fail(e),{success:!1,sentMessage:o,error:e}}if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return await r.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=to(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(!Qs(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 ${c.candidateName||c.candidateId}`);const m=await a.sendChatReply(o,{...void 0!==r?{motionObserver:r}:{}});if(!m.success)return await r.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"};const p=await Pc(a);return Ei({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),mc(i.jti,i.exp),await r.succeed("已发送回复"),{success:!0,sentMessage:o}}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()}}var Oc,$c=Xs({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Zs.object({signedEnvelope:Zs.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Zs.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Zs.number().optional().describe("候选人在列表中的索引(可选)")}),output:Ac,execute:async(e,t)=>await Bc(e,t)}),qc="zhipin_send_prepared_reply",Dc=80,jc=Gs.object({success:Gs.boolean(),sentMessage:Gs.string(),error:Gs.string().optional()}),Fc=Gs.object({preparedReplyId:Gs.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:ne.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:Ys.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")});function Lc(){return{sendSignedZhipinReply:Bc,...Oc}}function zc(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function Jc(e){return`sha256:${Ws("sha256").update(JSON.stringify([e.preparedReplyId,e.signedEnvelope,e.suggestedReply,e.expiresAt])).digest("hex")}`}function Hc(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>Dc?`${t.slice(0,Dc-1)}…`:t;return n.length>0?`发送预备回复: ${n}`:"发送预备回复"}var Vc=Ks({name:qc,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;只接收 preparedReplyId,不接收 signedEnvelope。",input:Fc,output:jc,execute:async(e,t)=>{const n=Lc(),a=Ms(e.preparedReplyId);if(!a.ok)return{success:!1,sentMessage:"",error:zc(a.reason)};const r=Re(t,{subject:{tool:qc,target:a.record.preparedReplyId,summary:Hc(a.record),digest:Jc(a.record)},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});ot(t,g(),{action:qc,target:a.record.preparedReplyId,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),r.consumeApproval();const o=Ns(e.preparedReplyId);return o.ok?await n.sendSignedZhipinReply({signedEnvelope:o.record.signedEnvelope},t):{success:!1,sentMessage:"",error:zc(o.reason)}}});import{defineTool as Uc}from"@roll-agent/sdk";import{z as Wc}from"zod";var Kc,Yc=Wc.object({success:Wc.boolean(),exchanged:Wc.boolean(),wechatNumber:Wc.string().optional(),error:Wc.string().optional()}),Gc=900,Xc=160,Zc=1100;function Qc(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Kc}}function el(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 tl(e){return"function"==typeof e.readCandidateChatDetails}async function nl(e){if(tl(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var al=Uc({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Wc.object({conversationId:Wc.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Wc.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Wc.number().optional().describe("候选人在列表中的索引(可选)")}),output:Yc,execute:async(e,t)=>{const n=Qc();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,...void 0!==r?{motionObserver:r}:{}});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&&!el(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 nl(a),c=await a.exchangeWechat({preClickDelayMs:Gc,pressDurationMs:Xc,settleMs:Zc,...void 0!==r?{motionObserver:r}:{}});return c.success?(c.exchanged&&Ti({conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.candidateName,...void 0!==c.wechatNumber?{wechatNumber:c.wechatNumber}:{},...void 0!==s?{candidateDetails:s}:{}},t.logger),await r.succeed("已完成换微信"),c):(await r.fail(c.error??"换微信失败"),c)}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 rl}from"@roll-agent/sdk";import{z as ol}from"zod";var il,sl=ol.object({success:ol.boolean(),username:ol.string(),usedSelector:ol.string().optional(),usedStrategy:ol.string().optional(),source:ol.string().optional(),error:ol.string().optional()});function cl(){return{openNativePagePort:ei,pickBestUsername:to,createNativeVisualActivitySession:e=>new Dn(e),...il}}var ll=rl({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:ol.object({}),output:sl,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=cl();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 dl}from"@roll-agent/sdk";import{z as ul}from"zod";var ml,pl=["unknown","top","middle","bottom","only-page"],gl=ul.object({containerFound:ul.boolean(),containerLabel:ul.string(),scrollTop:ul.number(),scrollHeight:ul.number(),clientHeight:ul.number(),itemCount:ul.number(),atStart:ul.boolean(),atEnd:ul.boolean()}),fl=ul.object({success:ul.boolean(),surface:ul.enum(Wr),direction:ul.enum(["up","down"]),stepsRequested:ul.number(),stepsCompleted:ul.number(),reachedBoundary:ul.boolean(),atTop:ul.boolean(),atBottom:ul.boolean(),canScrollUp:ul.boolean(),canScrollDown:ul.boolean(),position:ul.enum(pl),before:gl,after:gl,error:ul.string().optional()});function hl(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...ml}}function bl(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function yl(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function vl(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:yl(e)}}var wl=dl({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:ul.object({surface:ul.enum(Wr).describe("要滚动的页面区域"),direction:ul.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:ul.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:ul.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:ul.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:fl,execute:async(e,t)=>{const n=hl(),a=Yr(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,...vl(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...vl(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=bl();return{success:!1,surface:e.surface,direction:r,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...vl(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as xl}from"@roll-agent/sdk";import{z as Sl}from"zod";var Cl,Rl=["不限","男","女"],Il=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],kl=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","submit_failed","error"],Al=Sl.object({ageMin:Sl.number().int().min(16).optional(),ageMax:Sl.number().int().min(16).optional(),gender:Sl.enum(Rl),activity:Sl.enum(Il)}),Nl=Sl.object({ageMin:Sl.number().optional(),ageMax:Sl.number().optional(),gender:Sl.string(),activity:Sl.string()}),Ml=Sl.object({success:Sl.boolean(),status:Sl.enum(kl),requested:Al,applied:Nl.optional(),filterButtonText:Sl.string().optional(),error:Sl.string().optional()}),El=Sl.object({ageMin:Sl.number().int().min(16).optional().describe("年龄下限;未传则重置为 16"),ageMax:Sl.number().int().min(16).optional().describe("年龄上限;未传则重置为不限"),gender:Sl.enum(Rl).default("不限").describe("性别筛选,只支持:不限、男、女"),activity:Sl.enum(Il).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"}),Pl=350,Tl=130,_l=600;function Bl(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Cl}}function Ol(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 $l(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 ql=xl({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页打开筛选面板,只设置年龄、性别、活跃度[单选] 三个维度并提交。",input:El,output:Ml,execute:async(e,t)=>{const n=Bl(),a=Ol(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("推荐牛人页未就绪"),$l({status:"recommend_not_ready",requested:a,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Gr.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await r.applyRecommendFilter(a,{preClickDelayMs:Pl,pressDurationMs:Tl,settleMs:_l,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),$l(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),$l({status:"error",requested:a,error:t})}finally{r?.close()}}});import{defineTool as Dl}from"@roll-agent/sdk";import{z as jl}from"zod";var Fl=["target-count","boundary","no-new-items","max-steps"],Ll="@c",zl="@j",Jl=/^@?c([1-9]\d*)$/i,Hl=/^@?j([1-9]\d*)$/i,Vl=new Map,Ul=new Map;function Wl(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${Ll}${String(e+1)}`}function Kl(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${zl}${String(e+1)}`}function Yl(e){const t=Jl.exec(e.trim()),n=t?.[1];if(!n)return;const a=Number(n);return Number.isInteger(a)?a-1:void 0}function Gl(e){const t=Hl.exec(e.trim()),n=t?.[1];if(!n)return;const a=Number(n);return Number.isInteger(a)?a-1:void 0}function Xl(e){if(void 0!==e.index){if(!Number.isInteger(e.index)||e.index<0)throw new Error("index 必须是从 0 开始的非负整数");return e.index}if(void 0===e.candidateRef)throw new Error("必须提供 index 或 candidateRef");return ed(e.candidateRef).index}function Zl(e){const t=e.map((e,t)=>({...e,candidateRef:Wl(t)}));return Vl=new Map(t.map(e=>[e.candidateRef,e])),t}function Ql(e){const t=e.map((e,t)=>({...e,jobRef:Kl(t)}));return Ul=new Map(t.map(e=>[e.jobRef,e])),t}function ed(e){const t=Yl(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=Wl(t);return Vl.get(n)??{index:t,candidateRef:n,candidateId:""}}function td(e){const t=Gl(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=Kl(t),a=Ul.get(n);if(void 0===a)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return a}function nd(e){const t=[];for(const n of e.indices??[])t.push({index:Xl({index:n}),candidateRef:Wl(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(ed(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return rd(t)}function ad(e,t){return!1!==t.found&&(e.candidateId.length>0&&t.candidateId.length>0?e.candidateId===t.candidateId:!(e.name&&t.name&&t.name.length>0)||od(e.name)===od(t.name))}function rd(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function od(e){return e.trim().toLocaleLowerCase("zh-CN")}var id,sd=jl.object({index:jl.number(),candidateRef:jl.string(),candidateId:jl.string(),name:jl.string(),age:jl.string(),experience:jl.string(),education:jl.string(),workStatus:jl.string(),company:jl.string(),currentPosition:jl.string(),expectedLocation:jl.string(),expectedPosition:jl.string(),expectedSalary:jl.string(),tags:jl.array(jl.string()),buttonText:jl.string()}),cd=jl.object({containerLabel:jl.string(),stepsRequested:jl.number(),stepsCompleted:jl.number(),reachedBoundary:jl.boolean(),stopReason:jl.enum(Fl),uniqueCount:jl.number(),duplicateCount:jl.number(),noNewRounds:jl.number(),beforeItemCount:jl.number(),afterItemCount:jl.number(),beforeScrollHeight:jl.number(),afterScrollHeight:jl.number()}),ld=jl.object({success:jl.boolean(),candidates:jl.array(sd),total:jl.number(),scrollStats:cd.optional(),error:jl.string().optional()});function dd(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...id}}var ud=Dl({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:jl.object({maxResults:jl.number().optional().describe("最多返回条数"),autoScroll:jl.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:jl.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:ld,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=dd();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=Zl(s),l=s.map((e,t)=>{const n=c[t];if(!n)throw new Error(`候选人引用生成失败:position ${String(t)}`);return{...e,candidateRef:n.candidateRef}}),d=!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(`已读取 ${l.length} 位候选人`),t.logger.info(`Found ${l.length} candidates in recommend list`+(d?`, scroll stop: ${d.stopReason}`:"")),{success:!0,candidates:l,total:l.length,...void 0!==d?{scrollStats:d}:{}}}catch(e){return await(r?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{a?.close()}}});import{defineTool as md}from"@roll-agent/sdk";import{z as pd}from"zod";var gd,fd=["listed","recommend_not_ready","selector_not_found"],hd=pd.object({index:pd.number(),jobRef:pd.string().optional(),value:pd.string(),label:pd.string(),isCurrent:pd.boolean()}),bd=pd.object({success:pd.boolean(),status:pd.enum(fd),current:hd.optional(),jobs:pd.array(hd),availableCount:pd.number(),canSwitch:pd.boolean(),error:pd.string().optional()}),yd=450,vd=140,wd=750;function xd(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...gd}}function Sd(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function Cd(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Rd(e){const t=Ql(e.options),n=e.options.map((e,n)=>Sd(e,t[n]?.jobRef));let a;if(void 0!==e.current){const t=e.current;a=n.find(e=>Cd(e,t))??Sd(t)}return{success:e.success,status:e.status,...void 0!==a?{current:a}:{},jobs:n,availableCount:e.availableCount,canSwitch:e.canSwitch,...void 0!==e.error?{error:e.error}:{}}}var Id=md({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:pd.object({}),output:bd,execute:async(e,t)=>{const n=xd();let a,r;t.logger.info("Listing Boss recommend jobs through native CDP");try{a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在读取推荐岗位"),await r.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await a.listRecommendJobs({preClickDelayMs:yd,pressDurationMs:vd,settleMs:wd,...void 0!==r?{motionObserver:r}:{}});return e.success?await r.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await r.fail(e.error??"读取推荐岗位失败"),Rd(e)}catch(e){const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(r?.fail(t)),Rd({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{a?.close()}}});import{BrowserPageInfoSchema as kd}from"@roll-agent/browser";import{defineTool as Ad}from"@roll-agent/sdk";import{z as Nd}from"zod";var Md,Ed=Nd.object({success:Nd.boolean(),alreadyOnRecommend:Nd.boolean(),usedSidebarClick:Nd.boolean(),recommendReady:Nd.boolean(),page:kd.optional(),error:Nd.string().optional()});function Pd(){return{getContextManager:f,openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...Md}}async function Td(e,t){return qe(e,await t.inspectPage())}var _d=Ad({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:Nd.object({}),output:Ed,execute:async(e,t)=>{const n=Pd(),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(Gr.nav.sidebar,{label:e,padding:10}),await r.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await Td(a,r)};if(!await r.clickSidebarSection("recommend",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await Td(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 Td(a,r)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await Td(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 Bd}from"@roll-agent/sdk";import{z as Od}from"zod";var $d,qd=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],Dd=Od.object({index:Od.number(),value:Od.string(),label:Od.string(),isCurrent:Od.boolean()}),jd=Od.object({jobRef:Od.string().optional(),jobValue:Od.string().optional(),jobName:Od.string().optional(),index:Od.number().optional(),searchKeyword:Od.string().optional(),useSearch:Od.boolean().optional(),forceClick:Od.boolean().optional()}),Fd=Od.object({success:Od.boolean(),status:Od.enum(qd),requested:jd,current:Dd.optional(),selected:Dd.optional(),options:Od.array(Dd),matchedCount:Od.number(),error:Od.string().optional()}),Ld=Od.object({jobRef:Od.string().regex(Hl,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:Od.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:Od.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:Od.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:Od.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:Od.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:Od.boolean().default(!1).describe("目标岗位已选中时是否仍点击一次岗位项;默认 false,避免无意义重复点击")}).refine(e=>void 0!==e.jobRef||void 0!==e.jobValue||void 0!==e.jobName||void 0!==e.index,{path:["jobRef"],message:"jobRef、jobValue、jobName、index 至少需要提供一个"}),zd=900,Jd=180,Hd=1400;function Vd(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),...$d}}function Ud(e){if(void 0!==e.jobRef){const t=td(e.jobRef);return{jobRef:t.jobRef,...t.value.length>0?{jobValue:t.value}:{},...0===t.value.length&&t.label.length>0?{jobName:t.label}:{},index:t.index,...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}return{...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Wd(e){return{...void 0!==e.jobRef?{jobRef:e.jobRef}:{},...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Kd(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function Yd(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Kd(e.current)}:{},...void 0!==e.selected?{selected:Kd(e.selected)}:{},options:e.options.map(Kd),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Gd=Bd({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:Ld,output:Fd,execute:async(e,t)=>{const n=Vd();let a,r;try{const o=Ud(e);t.logger.info(`Selecting Boss recommend job: ref=${o.jobRef??"N/A"}, value=${o.jobValue??"N/A"}, name=${o.jobName??"N/A"}, index=${o.index??"N/A"}, forceClick=${!0===o.forceClick}`),a=await n.openNativePagePort(),r=n.createNativeVisualActivitySession(a),await a.bringToFront().catch(()=>{}),await r.begin("正在选择推荐岗位"),await r.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await a.selectRecommendJob(o,{preClickDelayMs:zd,pressDurationMs:Jd,settleMs:Hd,...void 0!==r?{motionObserver:r}:{}});return i.success?await r.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await r.fail(i.error??"选择推荐岗位失败"),Yd(i)}catch(t){const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(r?.fail(n)),Yd({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Wd(e),options:[],matchedCount:0,error:n})}finally{a?.close()}}});import{defineTool as Xd}from"@roll-agent/sdk";import{z as Zd}from"zod";var Qd,eu=Zd.object({index:Zd.number(),candidateRef:Zd.string(),candidateName:Zd.string(),candidateId:Zd.string(),success:Zd.boolean(),error:Zd.string().optional()}),tu=Zd.object({success:Zd.boolean(),results:Zd.array(eu),summary:Zd.object({total:Zd.number(),succeeded:Zd.number(),failed:Zd.number()})}),nu=[1400,1800,2200],au=450,ru=140,ou=750,iu=Zd.object({indices:Zd.array(Zd.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Zd.array(Zd.string().regex(Jl,"candidateRef 应类似 @c1")).min(1).optional().describe("要打招呼的候选人语义引用列表,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>(e.indices?.length??0)>0||(e.candidateRefs?.length??0)>0,"必须提供 indices 或 candidateRefs");function su(){return{openNativePagePort:ei,createNativeVisualActivitySession:e=>new Dn(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...Qd}}var cu=Xd({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:iu,output:tu,execute:async(e,t)=>{const n=nd(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const a=su();let r,o;try{r=await a.openNativePagePort(),o=a.createNativeVisualActivitySession(r),await o.begin("正在打开推荐列表");if(!await r.waitForRecommendList()){await o.fail("推荐列表未加载");const e=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:e,summary:{total:e.length,succeeded:0,failed:e.length}}}const e=n.length>1?"正在批量打招呼":"正在打招呼";await o.begin(e),await o.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:e,padding:8});const i=[];for(const[e,s]of n.entries()){if(e>0){const t=nu[(e-1)%nu.length]??nu[0];await a.sleep(t)}if(lu(s)){const e=await r.inspectRecommendCard(s.index);if(!ad(s,e)){i.push({index:s.index,candidateRef:s.candidateRef,candidateName:e.name,candidateId:e.candidateId,success:!1,error:`候选人引用 ${s.candidateRef} 已过期,请重新获取推荐列表`});continue}}const n=await r.clickRecommendGreet(s.index,{preClickDelayMs:au,pressDurationMs:ru,settleMs:ou,...void 0!==o?{motionObserver:o}:{}});i.push({index:s.index,candidateRef:s.candidateRef,candidateName:n.name,candidateId:n.candidateId,success:n.clicked,...void 0!==n.error?{error:n.error}:{}}),Pi(n,t.logger)}const s={total:i.length,succeeded:i.filter(e=>e.success).length,failed:i.filter(e=>!e.success).length};return 0===s.failed?await o.succeed(`已完成 ${s.succeeded}/${s.total} 位候选人`):await o.fail(`已完成 ${s.succeeded}/${s.total} 位候选人`),{success:0===s.failed,results:i,summary:s}}catch(e){const t=e instanceof Error?e.message:String(e);await(o?.fail(t));const a=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:t}));return{success:!1,results:a,summary:{total:a.length,succeeded:0,failed:a.length}}}finally{r?.close()}}});function lu(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as du}from"@roll-agent/sdk";import{z as uu}from"zod";var mu="recommendFrame",pu="recommend",gu="#recommendFrame",fu=".candidate-card-wrap",hu="[data-geek], .geek-item",bu=`${fu}, ${hu}`,yu="[data-geek], .card-inner, .geek-item",vu="[data-geek]",wu=".name",xu=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],Su=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Cu=".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog",Ru=".boss-popup__wrapper",Iu='iframe[src*="c-resume"]',ku="canvas#resume, div#resume canvas";function Au(e){return e.hasNamedRecommendFrame?"named-frame":e.hasRecommendUrlFrame?"recommend-url-frame":"main-page"}function Nu(e){return e>0?fu:hu}function Mu(e){return{candidateId:(e.ownDataGeek??e.childDataGeek??"").trim(),name:(e.nameText??"").trim()}}function Eu(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 Pu(e){return e.evaluate(e=>document.querySelectorAll(e).length,fu).then(Nu)}function Tu(e){const t=e.frame(mu),n=e.frames().find(e=>e.url().includes(pu)),a=Au({hasNamedRecommendFrame:null!==t,hasRecommendUrlFrame:void 0!==n});return"named-frame"===a?t??e:"recommend-url-frame"===a?n??e:e}async function _u(e,t=1e4){try{return await e.waitForSelector(bu,{timeout:t}),!0}catch{return!1}}async function Bu(e,t){const n=await Pu(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:vu,candidateNameSelector:wu}),i=Mu({ownDataGeek:o.candidateId,nameText:o.name});return{found:!0,cardSelector:n,candidateId:i.candidateId,name:i.name,hasGreetButton:o.hasGreetButton}}var Ou=uu.object({success:uu.boolean(),candidateRef:uu.string().optional(),candidateName:uu.string(),candidateId:uu.string(),error:uu.string().optional()}),$u=uu.object({index:uu.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:uu.string().regex(Jl,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef"),qu=du({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:$u,output:Ou,execute:async(e,t)=>{const n=Du(e),a=n.index,r=n.candidateRef;t.logger.info(`Opening resume for candidate ${r} at index ${a}`);const o=f(),i=await o.getPage("zhipin"),s=Tu(i);if(!await _u(s))return{success:!1,candidateRef:r,candidateName:"",candidateId:"",error:"推荐列表未加载"};const c=await Bu(s,a);if(!c.found)return{success:!1,candidateRef:r,candidateName:"",candidateId:"",error:c.error??`候选人引用 ${r} 超出范围`};if(ju(n)&&!ad(n,c))return{success:!1,candidateRef:r,candidateName:c.name,candidateId:c.candidateId,error:`候选人引用 ${r} 已过期,请重新获取推荐列表`};const l=s.locator(c.cardSelector).nth(a),d=await l.locator(yu).count()>0?l.locator(yu).first():l;return await d.scrollIntoViewIfNeeded(),await K(i,d,{target:s}),await d.hover(),await Jr(i,200,400),await Y(i,d,{target:s}),await d.click(),await Jr(i,1e3,2e3),t.logger.info(`Opened resume for ${c.name}`),{success:!0,candidateRef:r,candidateName:c.name,candidateId:c.candidateId}}});function Du(e){if(void 0!==e.candidateRef)return ed(e.candidateRef);const t=Xl(e);return{index:t,candidateRef:Wl(t),candidateId:""}}function ju(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as Fu}from"@roll-agent/sdk";import{z as Lu}from"zod";var zu=Lu.object({x:Lu.number(),y:Lu.number(),width:Lu.number(),height:Lu.number()}),Ju=Lu.object({success:Lu.boolean(),screenshotArea:zu.optional(),canvasInfo:Lu.object({width:Lu.number(),height:Lu.number()}).optional(),error:Lu.string().optional()}),Hu=Fu({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:Lu.object({}),output:Ju,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=f(),a=await n.getPage("zhipin");try{const e=a.frame(mu)??a.frames().find(e=>e.url().includes(pu));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$(Iu);if(!n)return{success:!1,error:"未找到简历 iframe"};const r=await n.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector(ku,{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}},ku);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}},gu),s=Eu({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}},Iu),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 Vu}from"@roll-agent/sdk";import{z as Uu}from"zod";var Wu=Uu.object({success:Uu.boolean(),closed:Uu.boolean(),error:Uu.string().optional()}),Ku=Vu({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:Uu.object({}),output:Wu,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=f(),a=await n.getPage("zhipin"),r=a.frame(mu)??a.frames().find(e=>e.url().includes(pu));if(!await(async()=>{if(r)for(const e of xu){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 Su){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.$(Cu):await a.$(Ru);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 Yu}from"@roll-agent/sdk";import{z as Gu}from"zod";import{waitForSelector as Xu}from"@roll-agent/browser";var Zu={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 Qu,waitForSelector as em}from"@roll-agent/browser";var tm="https://www.yupao.com",nm=`${tm}/chat`,am=`${tm}/login`;async function rm(e,t={}){e.url().includes("/chat")||await Qu(e,nm,t),await em(e,Zu.messageList.container,{timeout:15e3})}async function om(e,t,n={}){const a=`${tm}/chat?id=${encodeURIComponent(t)}`;await Qu(e,a,n),await em(e,Zu.chat.input,{timeout:15e3})}async function im(e,t){await rm(e),await Xu(e,Zu.messageList.item,{timeout:1e4});const n=Zu.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 sm=Gu.object({limit:Gu.number().optional().describe("最多返回的消息条数")}),cm=Gu.object({conversationId:Gu.string(),candidateName:Gu.string(),lastMessage:Gu.string(),unreadCount:Gu.number(),timestamp:Gu.string()}),lm=Gu.object({messages:Gu.array(cm),total:Gu.number()}),dm=Yu({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:sm,output:lm,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=f(),a=await n.getPage("yupao"),r=await im(a,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as um}from"@roll-agent/sdk";import{z as mm}from"zod";import{BrowserActionApprovalSchema as pm}from"@roll-agent/browser";import{clickElement as gm,isBrowserActionPolicyError as fm,typeText as hm,waitForSelector as bm}from"@roll-agent/browser";async function ym(e,t,n,a={}){try{return await om(e,t,a),await hm(e,Zu.chat.input,n,a),await gm(e,Zu.chat.sendButton,a),await bm(e,Zu.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(fm(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var vm=mm.object({conversationId:mm.string().describe("对话 ID"),message:mm.string().describe("要发送的回复消息"),browserActionApproval:pm.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),wm=mm.object({success:mm.boolean(),conversationId:mm.string(),sentMessage:mm.string(),error:mm.string().optional()}),xm=um({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:vm,output:wm,execute:async(e,t)=>{const{conversationId:n,message:a}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const r=f(),o=g(),i=ot(t,o,{action:"yupao_send_reply",target:n,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await r.getPage("yupao");let c;try{c=await ym(s,n,a,rt(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=it(e);if(void 0!==t)throw t;throw e}return c.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${c.error}`),{success:c.success,conversationId:n,sentMessage:a,error:c.error}}});import{BrowserRuntimeConfigSchema as Sm}from"@roll-agent/browser";function Cm(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 Rm(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 Im(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 km(e){if(void 0!==e)try{return JSON.parse(e)}catch(e){throw new Error(`BROWSER_SECURITY_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}}function Am(e=process.env){return Sm.parse({mode:e.BROWSER_MODE,headless:Cm(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:Rm(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:Im(e.BROWSER_ARGS_JSON),sessionsDir:e.BROWSER_SESSIONS_DIR,security:km(e.BROWSER_SECURITY_JSON)})}var Nm=t("browser-use-agent"),Mm=e({name:"browser-use-agent",tools:[Ae,Le,Ct,_t,Ft,Xn,da,ya,jr,Fi,Ki,es,bs,Us,Vc,al,ll,wl,_d,Id,Gd,ql,ud,cu,qu,Hu,Ku,dm,xm,x]},{onShutdown:v});async function Em(){xe(we()),await p(Am());try{await wc(),b(!0)}catch(e){b(!1),Nm.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Mm.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Em().catch(async e=>{Nm.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await v().catch(()=>{}),process.exit(1)});
|