@roll-agent/browser-use-agent 0.25.0 → 0.25.1

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/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{defineTool as n}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as a}from"@roll-agent/sdk";import"@roll-agent/browser";import{AsyncLocalStorage as o}from"node:async_hooks";import{access as i}from"node:fs/promises";import{constants as s}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{createHash as d}from"node:crypto";import{StructuredToolError as u}from"@roll-agent/sdk";import{BrowserContextManager as m,BrowserRuntime as p,BrowserRuntimeConfigSchema as g,probeBrowserRuntimeCdpHealth as f,SessionStore as h}from"@roll-agent/browser";import{execFileSync as y}from"node:child_process";var b,v={x:0,y:0,width:1920,height:1080},w=25,x=80,S=4,R=["Add-Type -AssemblyName System.Windows.Forms;","$area = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea;","[pscustomobject]@{ x = $area.X; y = $area.Y; width = $area.Width; height = $area.Height } | ConvertTo-Json -Compress"].join(" ");function C(){return b??=A(),b}function I(e){if(e<=1)return[1];if(2===e)return[2];if(3===e)return[3];if(4===e)return[2,2];const t=Math.ceil(e/S),n=Math.floor(e/t),r=e%t;return Array.from({length:t},(e,t)=>n+(t<r?1:0))}function k(e){if(!Number.isInteger(e.index)||e.index<0||!Number.isInteger(e.total)||e.total<=0||e.index>=e.total)throw new RangeError("Auto window layout index must be within the configured instance count.");const t=I(e.total),n=t.length;let r=0,a=e.index;for(const[e,n]of t.entries()){if(a<n){r=e;break}a-=n}const o=t[r]??1,i=e.workArea.x+Math.floor(e.workArea.width*a/o),s=e.workArea.x+Math.floor(e.workArea.width*(a+1)/o),c=e.workArea.y+Math.floor(e.workArea.height*r/n);return{x:i,y:c,width:s-i,height:e.workArea.y+Math.floor(e.workArea.height*(r+1)/n)-c}}function A(){const e=E(process.env.ROLL_BROWSER_WORK_AREA);if(void 0!==e)return e;if("darwin"===process.platform){const e=B();if(void 0!==e)return{x:e.x,y:e.y+w,width:e.width,height:Math.max(400,e.height-w-x)}}if("linux"===process.platform){const e=O();if(void 0!==e)return e}if("win32"===process.platform){const e=D();if(void 0!==e)return e}return v}function E(e){if(void 0===e||0===e.trim().length)return;const t=e.split(",").map(e=>Number.parseInt(e.trim(),10));if(4!==t.length||t.some(e=>!Number.isFinite(e)||e<0))return;const[n,r,a,o]=t;return a<=0||o<=0?void 0:{x:n,y:r,width:a,height:o}}function _(e){const t=N(e,/UI Looks like:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==t)return t;for(const t of e.split("\n")){if(!t.includes("Resolution:")||/\bRetina\b/i.test(t))continue;const e=N(t,/Resolution:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==e)return e}}function N(e,t){const n=e.match(t);if(void 0===n?.[1]||void 0===n[2])return;const r=Number.parseInt(n[1],10),a=Number.parseInt(n[2],10);return r<=0||a<=0?void 0:{x:0,y:0,width:r,height:a}}function M(e){const t=e.trim().replace(/^\uFEFF/,"");if(0===t.length)return;let n;try{n=JSON.parse(t)}catch{return}if(!P(n))return;const r=T(n.x),a=T(n.y),o=T(n.width),i=T(n.height);return void 0===r||void 0===a||void 0===o||void 0===i||o<=0||i<=0?void 0:{x:r,y:a,width:o,height:i}}function P(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function T(e){if("number"==typeof e&&Number.isInteger(e))return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e.trim());return Number.isInteger(t)?t:void 0}}function B(){try{return _(y("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function D(){try{return M(y("powershell.exe",["-NoProfile","-NonInteractive","-Command",R],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function O(){try{const e=y("xrandr",["--current"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).match(/(\d+)x(\d+)\+/);if(void 0===e?.[1]||void 0===e[2])return;const t=Number.parseInt(e[1],10),n=Number.parseInt(e[2],10);if(t<=0||n<=0)return;return{x:0,y:0,width:t,height:n}}catch{return}}var $,F,j,L,q="default",z=l(c(),".roll-agent","browser","sessions"),V=["#2563EB","#DC2626","#16A34A","#D97706","#7C3AED","#0891B2","#DB2777","#65A30D"],J=["stopped","not_running","not_found","failed"],U=new o,H=class{bundles=new Map;defaultInstanceId;startPromises=new Map;constructor(e,t){if(void 0===t||0===Object.keys(t.instances).length){const t=ne({id:q,runtimeConfig:e});return this.bundles.set(t.id,t),void(this.defaultInstanceId=t.id)}this.defaultInstanceId=t.defaultInstance;const n=Object.entries(t.instances);for(const[t,[r,a]]of n.entries()){const o=ne({id:r,runtimeConfig:Y(e,r,a,{index:t,total:n.length}),...void 0!==a.platform?{platform:a.platform}:{},...void 0!==a.trackingAgentId?{trackingAgentId:a.trackingAgentId}:{}});this.bundles.set(r,o)}}getDefaultInstanceId(){return this.defaultInstanceId}listBundles(){return[...this.bundles.values()]}isLegacySingleInstancePool(){return 1===this.bundles.size&&this.bundles.keys().next().value===q}resolvePrimaryInstanceId(){return this.defaultInstanceId??this.getOnlyInstanceId()}async ensureBundleStarted(e){const t=this.getBundle(e);return await this.ensureStarted(t.id),t}async ensureStarted(e){const t=this.bundles.get(e);if(void 0===t)throw new Error(`Browser instance "${e}" was not found.`);if(t.runtime.isRunning())return;const n=this.startPromises.get(e);if(void 0!==n)return void await n;const r=t.runtime.start().catch(async t=>{throw this.startPromises.delete(e),t});this.startPromises.set(e,r);try{await r}finally{this.startPromises.delete(e)}}getBundle(e){const t=e??U.getStore()??this.defaultInstanceId??this.getOnlyInstanceId();if(void 0===t)throw new u({code:"needs_input",message:"browserInstance is required because multiple browser instances are configured.",details:{availableInstances:[...this.bundles.keys()]}});const n=this.bundles.get(t);if(void 0===n)throw new u({code:"browser_instance_not_found",message:`Browser instance "${t}" was not found.`,details:{availableInstances:[...this.bundles.keys()]}});return n}async getInstanceStatuses(){return await Promise.all(this.listBundles().map(async e=>{const[t,n]=await Promise.all([f(e.config),re(e.config.userDataDir)]);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},mode:e.config.mode,cdp:t,profile:n,tracking:oe(e.trackingAgentId)}}))}async closeAll(){const e=(await this.closeInstances([...this.bundles.keys()])).filter(e=>"failed"===e.status).map(e=>new Error(`Failed to stop browser runtime for ${e.browserInstance}: ${e.message??"unknown error"}`));if(e.length>0)throw new AggregateError(e,"Browser instance pool shutdown failed")}async closeInstances(e){const t=[...new Set(e)];return await Promise.all(t.map(async e=>await this.closeInstance(e)))}async closeInstance(e){const t=this.bundles.get(e);if(void 0===t)return{browserInstance:e,status:"not_found",message:`Browser instance "${e}" was not found.`};const n=[],r=this.startPromises.get(e);if(void 0!==r)try{await r}catch(t){n.push(new Error(`Failed to finish browser startup for ${e}`,{cause:t}))}if(!t.runtime.isRunning())return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"not_running",mode:t.config.mode};try{await t.contextManager.closeAll()}catch(t){n.push(new Error(`Failed to close browser contexts for ${e}`,{cause:t}))}try{"managed-cdp"===t.config.mode?await t.runtime.stop():await t.runtime.disconnect()}catch(t){n.push(new Error(`Failed to stop browser runtime for ${e}`,{cause:t}))}return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"stopped",mode:t.config.mode}}getOnlyInstanceId(){if(1===this.bundles.size)return this.bundles.keys().next().value}};function W(e){return e.map(e=>void 0===e.cause?e.message:`${e.message}: ${K(e.cause)}`).join("; ")}function K(e){return e instanceof Error?e.message:String(e)}async function G(e,t){return await U.run(e,t)}function Y(e,t,n,r){const a=n.headless??e.headless,o=n.windowBounds??te({instance:n,headless:a,...r});return g.parse({...e,mode:n.mode,headless:a,instanceId:t,profileName:n.profileName??t,profileColor:n.profileColor??X(r.index),cdpUrl:n.cdpUrl,cdpHost:n.cdpHost,cdpPort:n.cdpPort,channel:n.channel,executablePath:n.executablePath,userDataDir:n.userDataDir,args:n.args,windowBounds:o,sessionsDir:n.sessionsDir??l(z,t)})}function X(e){return V[e]??Z(e)}function Z(e){return Q(137.508*e%360,.68,.45)}function Q(e,t,n){const r=(1-Math.abs(2*n-1))*t,a=e/60,o=r*(1-Math.abs(a%2-1));let i,s,c;[i,s,c]=a<1?[r,o,0]:a<2?[o,r,0]:a<3?[0,r,o]:a<4?[0,o,r]:a<5?[o,0,r]:[r,0,o];const l=n-r/2;return`#${ee(i+l)}${ee(s+l)}${ee(c+l)}`}function ee(e){return Math.round(255*e).toString(16).padStart(2,"0").toUpperCase()}function te(e){if(!(e.total<=1||e.headless||"managed-cdp"!==e.instance.mode))return k({index:e.index,total:e.total,workArea:C()})}function ne(e){const t=new h(e.runtimeConfig.sessionsDir),n=new p(e.runtimeConfig),r=new m(n,t);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},...void 0!==e.trackingAgentId?{trackingAgentId:e.trackingAgentId}:{},runtime:n,contextManager:r,sessionStore:t,config:e.runtimeConfig}}async function re(e){if(void 0===e)return{exists:!1,writable:!1};return{userDataDir:e,exists:await ae(e,s.F_OK),writable:await ae(e,s.W_OK)}}async function ae(e,t){try{return await i(e,t),!0}catch{return!1}}function oe(e){if(void 0!==e)return{source:"instance",agentIdFingerprint:ie(e)};const t=process.env.RECRUITMENT_EVENTS_DEFAULT_AGENT_ID?.trim();return void 0!==t&&t.length>0?{source:"default-env",agentIdFingerprint:ie(t)}:{source:"missing"}}function ie(e){return d("sha256").update(e).digest("hex").slice(0,8)}var se=!1,ce=a("browser-use-agent");async function le(e,t){if($||F)return;se=!1;const n=($=new H(e,t)).getDefaultInstanceId(),r=$.listBundles()[0],a=void 0!==n?$.getBundle(n):r;void 0!==a&&(F=a.runtime,j=a.contextManager,L=a.sessionStore)}function de(){if($)return $.getBundle().runtime;if(!F)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return F}function ue(){if($)return $.getBundle().contextManager;if(!j)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return j}function me(e){se=e}function pe(){return se}function ge(){if(!$)throw new Error("BrowserInstancePool not initialized. Call initRuntime() first.");return $}async function fe(){await ge().ensureBundleStarted()}function he(){return void 0!==$&&!$.isLegacySingleInstancePool()}function ye(e){if(!$)return e;let t;try{t=$.getBundle()}catch{return}return t.trackingAgentId??e}function be(){return ge().getBundle()}async function ve(){const e=$,t=j,n=F,r=[];if($=void 0,j=void 0,F=void 0,L=void 0,se=!1,e){ce.info("Closing browser instance pool...");try{await e.closeAll()}catch(e){r.push(new Error("Failed to close browser instance pool",{cause:e})),ce.error(`Failed to close browser instance pool: ${e instanceof Error?e.stack??e.message:String(e)}`)}}else if(t){ce.info("Closing browser contexts...");try{await t.closeAll()}catch(e){r.push(new Error("Failed to close browser contexts",{cause:e})),ce.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(!e&&n){ce.info("Stopping browser process...");try{await n.stop()}catch(e){r.push(new Error("Failed to stop browser runtime",{cause:e})),ce.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(ce.info("Browser runtime shutdown complete"),r.length>0)throw new AggregateError(r,"Browser runtime shutdown failed")}var we=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),xe=n({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:we,execute:async(e,t)=>{const n=de();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as Se}from"@roll-agent/sdk";import{z as Re}from"zod";import{BrowserSecurityConfigSchema as Ce,BrowserStatusSchema as Ie}from"@roll-agent/browser";import{createHash as ke}from"node:crypto";import{z as Ae}from"zod";var Ee,_e,Ne=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_TIMEOUT_MS","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_INSTANCES_JSON","BROWSER_USE_POLICY_JSON","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],Me=/^[0-9a-f]{8}$/,Pe=Ae.object({present:Ae.boolean(),fingerprint:Ae.string().regex(Me).optional()}),Te=Ae.record(Pe);function Be(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:De(n)}]:[e,{present:!1}]}))}function De(e){return ke("sha256").update(e).digest("hex").slice(0,8)}function Oe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function $e(){return void 0!==Ee?Ee:Oe(process.env.BROWSER_VISUAL_CURSOR)??!0}function Fe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function je(){return void 0!==_e?_e:Fe(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Le}from"@roll-agent/sdk";import{z as qe}from"zod";import{randomUUID as ze}from"node:crypto";import{z as Ve}from"zod";var Je=Ve.object({id:Ve.string().trim().min(1)}),Ue=new Map;function He(e=Date.now()){for(const[t,n]of Ue)n.expiresAtMs<=e&&Ue.delete(t)}function We(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function Ke(e,t,n=Date.now()){He(n);const r=ze(),a=n+t;return Ue.set(r,{id:r,tool:e.tool,target:e.target,digest:e.digest,...void 0!==e.summary?{summary:e.summary}:{},expiresAtMs:a}),{id:r,expiresAt:new Date(a).toISOString(),tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{},retryInput:{toolActionApproval:{id:r}}}}function Ge(e){return He(),!!Ye(e)&&(Ue.delete(e.approval.id),!0)}function Ye(e){He();const t=Ue.get(e.approval.id);return void 0!==t&&!!We(e.subject,t)}var Xe=["log","deny","confirm"],Ze=qe.enum(Xe),Qe=["zhipin_send_prepared_reply"],et=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],tt=qe.object({code:qe.enum(et),message:qe.string()}),nt=qe.object({policy:Ze}),rt=qe.object({approvalTtlMs:qe.number().int().positive().max(36e5).default(3e5),tools:qe.record(nt).default({})}),at=rt.parse({}),ot=new Set(Qe),it=at;function st(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 ct(e=process.env){try{return rt.parse(st(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 lt(e){it=rt.parse(e)}function dt(){return it}function ut(e){const t=e.toolPolicy??it,n=[];for(const e of Object.keys(t.tools))ot.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 mt(e,t){const n=it.tools[t.subject.tool],r=n?.policy??"log";if("log"===r)return e.logger.info(`Browser-use tool allowed by tool policy: ${t.subject.tool}`),{consumeApproval:()=>{}};if("deny"===r){const n="Tool execution denied by browser-use tool policy.";throw e.logger.warn(`${n} ${JSON.stringify(pt(t.subject,"tool_policy_deny"))}`),new Le({code:"action_denied",message:n,details:pt(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?Ye({approval:t.approval,subject:t.subject}):Ge({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&&Ge({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...pt(t.subject,"tool_policy_confirm"),approvalRequest:Ke(t.subject,it.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(pt(t.subject,"tool_policy_confirm"))}`),new Le({code:"needs_confirmation",message:a,details:o})}function pt(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var gt=Ie.extend({replyAuthorityKeysLoaded:Re.boolean(),visualCursorEnabled:Re.boolean(),visualActivityEnabled:Re.boolean(),security:Ce,toolPolicy:rt,policyWarnings:Re.array(tt),effectiveEnvSources:Te}),ft=Se({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:Re.object({}),output:gt,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=ge(),r=n.listBundles(),a=n.resolvePrimaryInstanceId(),o=r.find(e=>e.id===a)??r[0];if(void 0===o)throw new Error("BrowserInstancePool has no runtime bundles.");const i=r.some(e=>e.runtime.isRunning()),{headless:s,mode:c,security:l}=o.runtime.getConfig(),d=dt(),u=[];for(const e of r){const t=e.runtime,n=e.contextManager,r=e.sessionStore,a=n.getActivePlatforms();for(const o of a){const a=n.getPageCount(o),i=n.getCurrentUrl(o);let s=null,c="unknown";if(t.shouldRestoreSessionSnapshot()){const[e,t]=await Promise.all([r.loadCookies(o),r.loadLocalStorage(o)]);s=void 0!==e&&e.length>0||void 0!==t&&Object.keys(t).length>0,c=s?"snapshot":"none"}else t.usesPersistentProfile()&&(s=null,c="profile");u.push({browserInstance:e.id,platform:o,pagesOpen:a,currentUrl:i,hasLoginState:s,loginStateSource:c})}}return{running:i,headless:s,mode:c,activeSessions:u,defaultInstanceId:n.getDefaultInstanceId(),primaryInstanceId:a,instances:await n.getInstanceStatuses(),replyAuthorityKeysLoaded:pe(),visualCursorEnabled:$e(),visualActivityEnabled:je(),security:l,toolPolicy:d,policyWarnings:ut({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:Be(Ne)}}});import{BrowserRuntimeModeSchema as ht}from"@roll-agent/browser";import{StructuredToolError as yt,defineTool as bt}from"@roll-agent/sdk";import{z as vt}from"zod";var wt=vt.object({browserInstance:vt.string().trim().min(1).optional(),browserInstances:vt.array(vt.string().trim().min(1)).min(1).optional(),all:vt.boolean().optional()}).superRefine((e,t)=>{const n=void 0!==e.browserInstance||void 0!==e.browserInstances&&e.browserInstances.length>0;e.all&&n&&t.addIssue({code:vt.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),xt=vt.object({browserInstance:vt.string(),status:vt.enum(J),mode:ht.optional(),message:vt.string().optional()}),St=vt.object({ok:vt.boolean(),stopped:vt.number().int().nonnegative(),results:vt.array(xt)}),Rt=bt({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:wt,output:St,execute:async(e,t)=>{const n=ge(),r=Ct(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return It(await n.closeInstances(r))}});function Ct(e,t){if(!0===e.all)return t;const n=[...void 0!==e.browserInstance?[e.browserInstance]:[],...e.browserInstances??[]],r=[...new Set(n)];if(0===r.length)throw new yt({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function It(e){return{ok:e.every(e=>"not_found"!==e.status&&"failed"!==e.status),stopped:e.filter(e=>"stopped"===e.status).length,results:[...e]}}import{defineTool as kt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as At,PlatformSchema as Et}from"@roll-agent/browser";import{z as _t}from"zod";import{PLATFORMS as Nt}from"@roll-agent/browser";var Mt={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Pt(e){return new URL(Mt[e]).host}function Tt(e,t){try{return new URL(e).host.includes(Pt(t))}catch{return!1}}function Bt(e){return Nt.find(t=>Tt(e,t))}function Dt(e,t){const n=Bt(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 Ot(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:Bt(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var $t=_t.object({platform:Et.optional().describe("可选:仅返回指定平台相关的页面")}),Ft=_t.object({pages:_t.array(At)}),jt=kt({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:$t,output:Ft,execute:async(e,t)=>{const n=ue();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>Dt(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as Lt}from"node:timers/promises";import{defineTool as qt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as zt,BrowserPageInfoSchema as Vt}from"@roll-agent/browser";import{z as Jt}from"zod";import{StructuredToolError as Ut}from"@roll-agent/sdk";import{isBrowserActionPolicyError as Ht,preflightBrowserAction as Wt}from"@roll-agent/browser";import{randomUUID as Kt}from"node:crypto";var Gt=6e4,Yt=new Map;function Xt(e=Date.now()){for(const[t,n]of Yt)n.expiresAtMs<=e&&Yt.delete(t)}function Zt(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function Qt(e,t=Date.now()){Xt(t);const n=Kt(),r=t+Gt,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return Yt.set(n,a),{id:n,expiresAt:new Date(r).toISOString(),action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},retryInput:{browserActionApproval:{id:n}}}}function en(e){Xt();const t=Yt.get(e.approval.id);return void 0!==t&&(!!Zt(e.details,t)&&(Yt.delete(e.approval.id),!0))}function tn(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function nn(e,t,n={}){return{security:tn(t,n),approveAction:en,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function rn(e,t,n){const r=Wt({security:t.getConfig().security,action:n.action,target:n.target,...void 0!==n.url?{url:n.url}:{}});if(r.ok)return r.log&&e.logger.info(r.message),{approvedByConfirmation:!1};if("needs_confirmation"===r.code&&void 0!==n.approval&&en({approval:n.approval,details:r.details}))return e.logger.info(`Browser action approved by browserActionApproval: ${n.action} ${n.target}`),{approvedByConfirmation:!0};throw e.logger.warn(`${r.message} ${JSON.stringify(r.details)}`),new Ut({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:Qt(r.details)}:{}}})}function an(e){if(Ht(e))return new Ut({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:Qt(e.payload.details)}:{}}})}function on(){try{return de()}catch{return}}async function sn(e){await e.bringToFront().catch(()=>{})}async function cn(e,t={}){const n=t.runtime??on();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await sn(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await sn(e)}var ln,dn=15e3,un=250,mn=["/web/chat"],pn=Jt.object({url:Jt.string().url().describe("要导航到的目标 URL"),browserActionApproval:zt.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),gn=Jt.object({success:Jt.boolean(),page:Vt});function fn(){return{getContextManager:ue,getRuntime:de,detectPlatformFromUrl:Bt,matchesPlatformHost:Tt,toNativePageInfo:Dt,delay:Lt,...ln}}function hn(e){return"object"==typeof e&&null!==e}function yn(e){return!!hn(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function bn(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==mn.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function vn(e,t,n,r,a){const o=n.detectPlatformFromUrl(r),i=await t.listNativePages();if(o){const s=i.find(e=>n.matchesPlatformHost(e.url,o));if(s)return a.info(`Reusing native ${o} page for navigation`),await t.activateNativePage(s.targetId),e.rememberNativePageSelection(o,s),{page:s,platform:o};a.info(`Opening native ${o} page for navigation`);const c=await t.openNativePage(r);return e.rememberNativePageSelection(o,c),{page:c,platform:o}}return a.info("Opening a new native page for non-platform navigation"),{page:await t.openNativePage(r)}}async function wn(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!yn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function xn(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<dn;){try{const t=await wn(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Sn(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(un)}throw new Error(`Native navigation did not reach document ready state within ${dn}ms${o?`: ${o.message}`:""}`)}function Sn(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 Rn(e,t,n){t.url!==n&&await e.navigate(n)}async function Cn(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var In=qt({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:pn,output:gn,execute:async(e,t)=>{const n=fn(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);bn(e.url,o);const i=rn(t,a,{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 vn(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await Rn(c,s.page,e.url);const o=await xn(c,n,e.url,s.page.url),i=await Cn(a,s.page,o),l=s.platform??n.detectPlatformFromUrl(i.url);return l&&(r.rememberNativePageSelection(l,i),t.logger.info(`Bound native navigated page to ${l}`)),{success:!0,page:n.toNativePageInfo(r,i)}}finally{c.close()}}});import{BrowserActionApprovalSchema as kn,BrowserPageInfoSchema as An}from"@roll-agent/browser";import{defineTool as En}from"@roll-agent/sdk";import{z as _n}from"zod";import{setTimeout as Nn}from"node:timers/promises";var Mn=15e3,Pn=250,Tn="__rollReloadMark";function Bn(e){return"object"==typeof e&&null!==e}function Dn(e){if(!Bn(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function On(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function $n(e){const t=await e.evaluateJson(`(() => ({ mark: window.${Tn} ?? null, readyState: document.readyState }))()`);if(!Dn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function Fn(e,t){const n=(t.createToken??On)(),r=t.now??(()=>Date.now()),a=t.delay??Nn,o=t.timeoutMs??Mn,i=t.pollMs??Pn;await e.evaluateJson(`(() => { window.${Tn} = ${JSON.stringify(n)}; return true; })()`),await e.reload({url:t.url,...void 0!==t.ignoreCache?{ignoreCache:t.ignoreCache}:{}}),t.onReloadSent?.();const s=r();let c;for(;r()-s<o;){try{const t=await $n(e);if(t.mark!==n&&("interactive"===t.readyState||"complete"===t.readyState))return}catch(e){c=e instanceof Error?e:new Error(String(e))}await a(i)}throw new Error(`Native page reload did not swap document within ${String(o)}ms${c?`: ${c.message}`:""}`)}var jn,Ln=_n.object({ignoreCache:_n.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:kn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),qn=_n.object({success:_n.boolean(),reloaded:_n.boolean(),page:An});function zn(){return{getContextManager:ue,getRuntime:de,toNativePageInfo:Dt,reloadNativePageAndWaitForSwap:Fn,...jn}}function Vn(e,t){const n=t.filter(t=>e.isNativePageSelected(t.targetId));if(1===n.length){const e=n[0];if(e)return e}if(n.length>1)throw new Error("Multiple selected native tabs found; select a single tab before reloading.");if(1===t.length){const e=t[0];if(e)return e}if(0===t.length)throw new Error("No native page is open; navigate before reloading.");throw new Error("Multiple native pages are open and none is selected; select the target tab before reloading.")}var Jn=En({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:Ln,output:qn,execute:async(e,t)=>{const n=zn(),r=n.getContextManager(),a=n.getRuntime(),o=Vn(r,await a.listNativePages()),i=rn(t,a,{action:"navigate",target:o.url,url:o.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Reloading native tab ${o.targetId} (${o.url})`);const s=await a.connectNativePage(o,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:o.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:a}),await n.reloadNativePageAndWaitForSwap(s,{url:o.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{}});const t=(await a.listNativePages()).find(e=>e.targetId===o.targetId)??o;return{success:!0,reloaded:!0,page:n.toNativePageInfo(r,t)}}finally{s.close()}}});import{defineTool as Un}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Hn,BrowserPageInfoSchema as Wn,PlatformSchema as Kn}from"@roll-agent/browser";import{z as Gn}from"zod";async function Yn(e,t){return(await e.listNativePages()).find(e=>Tt(e.url,t))}async function Xn(e,t,n={}){const r=await Yn(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(Mt[t],n),reusedExistingPage:!1}}var Zn=Gn.object({platform:Kn.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:Hn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Qn=Gn.object({success:Gn.boolean(),page:Wn,reusedExistingTab:Gn.boolean()}),er=Un({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:Zn,output:Qn,execute:async(e,t)=>{const{platform:n}=e,r=de(),a=ue(),o=Mt[n],i=rn(t,r,{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 Xn(r,n,nn(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:Dt(a,s),reusedExistingTab:c}}});import{defineTool as tr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as nr,PlatformSchema as rr}from"@roll-agent/browser";import{z as ar}from"zod";var or=ar.object({platform:rr.describe("要将该页面绑定为当前活跃页的平台"),pageId:ar.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),ir=ar.object({success:ar.boolean(),page:nr}),sr=tr({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:or,output:ir,execute:async(e,t)=>{const n=ue();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const r=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:Dt(n,r)}}});import{defineTool as cr}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as lr,BrowserPageInfoSchema as dr,createBrowserAxSnapshot as ur}from"@roll-agent/browser";import{z as mr}from"zod";import{BrowserElementRefStore as pr}from"@roll-agent/browser";var gr=new pr;async function fr(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 hr}from"node:crypto";var yr="data-roll-browser-action-",br=120;function vr(){return`${yr}${hr().replaceAll("-","")}`}function wr(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),br)):br}function xr(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Sr(e){if(!xr(e)||!xr(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Rr(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Cr(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ir(e);return void 0===t?[]:[t]}):[]}function Ir(e){if(!xr(e))return;const t=e.nodeId,n=e.backendNodeId,r=Rr(e.attributes),a=Ir(e.contentDocument),o=Ir(e.templateContent);return{..."number"==typeof t&&Number.isInteger(t)?{nodeId:t}:{},..."number"==typeof n&&Number.isInteger(n)?{backendNodeId:n}:{},...void 0!==r?{attributes:r}:{},children:Cr(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Cr(e.pseudoElements),shadowRoots:Cr(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function kr(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 Ar(e){if(!xr(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 Er(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ar(e);return void 0===t?[]:[t]}):[]}function _r(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function Nr(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 Mr(e,t){const n=new Map;for(const r of t){const t=kr(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Pr(e,t){const n=new Map,r=t=>{const a=kr(e,t.attributes);void 0!==a&&void 0!==t.backendNodeId&&n.set(a,t.backendNodeId);for(const e of t.children)r(e);void 0!==t.contentDocument&&r(t.contentDocument);for(const e of t.pseudoElements)r(e);for(const e of t.shadowRoots)r(e);void 0!==t.templateContent&&r(t.templateContent)};return r(t),n}function Tr(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 Br(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 Dr(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!xr(n))return new Map;const r=Ir(n.root);return void 0===r?new Map:Pr(t,r)}const r=Sr(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Mr(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function Or(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=wr(n.maxCandidates??br);if(0===r)return[];const a=vr(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(yr.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=Er(await e.evaluateJson(Tr(a,r),i).catch(()=>[]));try{if(0===s.length)return[];const t=await Dr(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:_r(e),name:e.name,hints:Nr(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Br(a),i).catch(()=>{})}}var $r=28,Fr=90,jr=250,Lr=620,qr=8,zr=32,Vr=8;function Jr(e){return new Promise(t=>{setTimeout(t,e)})}function Ur(e,t,n){return Math.min(Math.max(e,t),n)}function Hr(e,t){return Math.max(0,Math.floor(e??t))}function Wr(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Kr(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function Gr(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function Yr(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 Xr(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function Zr(e,t){const n=Kr(e,t);if(n<=qr)return[e,t];const r=Ur(Math.round(n/30),Vr,zr),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=Ur(.08*n,4,28)*Xr(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=Gr(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+a*m+s*(p+g)),y:Math.round(e.y+o*m+c*(p+g))})}return d.push(t),d}function Qr(e,t){const n=Wr(t);return Zr(void 0===e?Yr(n):Wr(e),n)}var ea=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??Jr,this.defaultStepDelayMs=Hr(t.stepDelayMs,$r)}reset(e){this.lastPoint=void 0===e?void 0:Wr(e)}async moveTo(e,t={}){const n=Hr(t.stepDelayMs,this.defaultStepDelayMs),r=Qr(this.lastPoint,e),a=Math.max(r.length-1,0)*n;await(t.motionObserver?.previewMouseMotion({points:r,durationMs:a}));for(let e=0;e<r.length;e+=1){const a=r[e];void 0!==a&&(await this.dispatcher.dispatchMouseEvent({type:"mouseMoved",x:a.x,y:a.y,...void 0!==t.button?{button:t.button}:{},buttons:t.buttons??0}),e<r.length-1&&n>0&&await this.sleep(n))}return this.lastPoint=Wr(e),{points:r,durationMs:a}}async click(e,t={}){const n=t.button??"left",r=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:Wr(e),durationMs:Lr})),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:"left"===n?1:0,clickCount:r}),await this.delayIfPositive(t.pressDurationMs??Fr),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:0,clickCount:r}),await this.delayIfPositive(t.settleMs??jr),this.lastPoint=Wr(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??Fr),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=Wr(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},ta=14,na=720;function ra(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 normalizeActivityViewport = (viewport) => {\n viewport.style.position = "fixed";\n viewport.style.inset = "0";\n viewport.style.opacity = "0";\n viewport.style.pointerEvents = "none";\n viewport.style.display = "none";\n viewport.style.border = "0";\n viewport.style.boxShadow = "none";\n viewport.style.borderRadius = "0";\n viewport.style.transform = "none";\n viewport.style.transition = "none";\n viewport.style.background = "transparent";\n };\n\n const showActivityViewportFrame = (viewport, theme, mode) => {\n normalizeActivityViewport(viewport);\n viewport.style.display = "block";\n viewport.style.opacity = mode === "complete" ? "0.28" : "0.22";\n viewport.style.background =\n "linear-gradient(180deg, " +\n theme.accentGlow +\n " 0%, transparent 16%, transparent 84%, " +\n theme.accentGlow +\n " 100%), linear-gradient(90deg, " +\n theme.accentGlow +\n " 0%, transparent 12%, transparent 88%, " +\n theme.accentGlow +\n " 100%)";\n viewport.style.boxShadow =\n "inset 0 0 0 2px " + theme.accentSoft + ", inset 0 0 28px " + theme.accentGlow;\n };\n\n const ensureActivityRoot = () => {\n let root = document.getElementById(activityRootId);\n if (root) {\n const viewport = document.getElementById(activityViewportId);\n if (viewport) normalizeActivityViewport(viewport);\n return root;\n }\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 normalizeActivityViewport(viewport);\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, mode) => {\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 showActivityViewportFrame(viewport, theme, mode);\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 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 normalizeActivityViewport(viewport);\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 applyTheme(args.activity.tone ?? "info", args.activity.mode);\n\n if (typeof args.activity.label === "string") {\n label.textContent = args.activity.label;\n }\n\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 normalizeActivityViewport(viewport);\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 aa=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!je()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...je()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??ta,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){$e()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){$e()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!je()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(ra(e))}catch{return!1}}};function oa(e){return new aa(e)}async function ia(e,t,n){const r=new ea(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var sa=mr.object({pageId:mr.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:mr.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:mr.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:mr.boolean().default(!0).describe("默认 true:只返回可交互节点")}),ca=mr.object({page:dr,snapshot:lr});function la(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 da(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 ua(e,t){const n=la(t);return(await Promise.all(n.map(async t=>{const n=await da(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function ma(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function pa(e){return e.refs.reduce((e,t)=>Math.max(e,ma(t.ref)),0)}function ga(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ga(n.nodes,t)]})}function fa(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=fa(e.children??[],t),a=void 0===n?[]:fa(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function ha(e){let t=pa(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await ua(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){r=!0;break}if(void 0===c.node.ref||a.has(c.frameId))continue;a.add(c.frameId);const l=await Or(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await ur(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 ua(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=pa(d),n=Math.max(0,n-d.nodeCount),r=r||d.truncated}if(0===o.length)return{...e.snapshot,truncated:r};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?ga(e.snapshot.nodes,s):fa(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}var ya=cr({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:sa,output:ca,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}});t.logger.info(`Creating browser AX snapshot for page ${a.targetId}`);const o=await n.connectNativePage(a),i=oa(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await Or(o,s),l=await ur(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await ha({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return gr.saveSnapshot(a.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:Dt(r,a),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as ba,defineTool as va}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as wa,BrowserElementRefHandleSchema as xa,clickElementRef as Sa}from"@roll-agent/browser";import{z as Ra}from"zod";import{BrowserElementRefHandleSchema as Ca}from"@roll-agent/browser";import{z as Ia}from"zod";var ka=Ia.enum(["backend_node_id","role_name_nth"]),Aa=Ia.object({ref:Ca,role:Ia.string(),name:Ia.string(),x:Ia.number(),y:Ia.number(),resolvedBy:ka,backendNodeId:Ia.number().int().positive().optional(),frameId:Ia.string().optional(),disabled:Ia.boolean()}),Ea=Ia.object({success:Ia.literal(!0),ref:Ca,resolvedBy:ka,target:Aa}),_a=Ra.object({ref:xa.describe("browser_snapshot 返回的 @eN element ref"),pageId:Ra.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:wa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Na=va({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:_a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new ba({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在点击 ${e.ref}`);const r=await Sa({controller:s,elementRef:o,options:{clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),r}catch(t){await oa(s).fail(`点击 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as Ma,defineTool as Pa}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ta,BrowserElementRefHandleSchema as Ba,typeElementRef as Da}from"@roll-agent/browser";import{z as Oa}from"zod";var $a=Oa.object({ref:Ba.describe("browser_snapshot 返回的 @eN element ref"),text:Oa.string().describe("要输入的文本"),clear:Oa.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:Oa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Ta.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Fa=Pa({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:$a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new Ma({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在输入到 ${e.ref}`);const r=await Da({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),r}catch(t){await oa(s).fail(`输入到 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as ja}from"@roll-agent/sdk";import{BrowserPageInfoSchema as La}from"@roll-agent/browser";import{z as qa}from"zod";import{setTimeout as za}from"node:timers/promises";function Va(e){return e.trim().toLocaleLowerCase("zh-CN")}function Ja(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function Ua(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=Va(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>Va(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=Va(e.name);return n.includes(t)||t.includes(n)}),a)return a;const o=t.length<=2?1:t.length<=4?.75:.6;if(a=r.find(e=>{const n=Va(e.name);return Ja(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var Ha=["chat-list","chat-history","recommend-list"],Wa={"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 Ka(e){return Wa[e]}var Ga={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"}},Ya=["不限","男","女"],Xa=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],Za=["patch","replace"],Qa=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:Ya},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:Xa},{key:"major",label:"专业",selection:"multi",clearValue:"不限"},{key:"recentNotView",label:"近期没有看过",selection:"single",clearValue:"不限"},{key:"exchangeResumeWithColleague",label:"是否与同事交换简历",selection:"single",clearValue:"不限"},{key:"candidateKeywords",label:"牛人关键词",selection:"multi",clearValue:"不限"},{key:"school",label:"院校",selection:"multi",clearValue:"不限"},{key:"switchJobFrequency",label:"跳槽频率",selection:"single",clearValue:"不限"},{key:"intention",label:"求职意向",selection:"multi",clearValue:"不限"},{key:"salary",label:"薪资待遇",selection:"single",clearValue:"不限"},{key:"degree",label:"学历要求",selection:"multi",clearValue:"不限"},{key:"experience",label:"经验要求",selection:"multi",clearValue:"不限"},{key:"callPhone",label:"是否可拨打电话",selection:"single",clearValue:"不限"}],eo=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function to(e){const t=Qa.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function no(e){return void 0!==e.ageMin||void 0!==e.ageMax}var ro=".candidate-card-wrap",ao="[data-geek], .geek-item",oo=`${ro}, ${ao}`,io="[data-geek], .card-inner, .geek-item",so='.boss-popup__wrapper, .dialog-lib-resume, .boss-dialog, [data-type="boss-dialog"]',co=`${so}, .recommendV2`,lo=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],uo='iframe[src*="c-resume"]',mo="canvas#resume, div#resume canvas";function po(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)}}var go=30,fo=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function ho(e){const t=e.trim();return!(0===t.length||t.length>go)&&(!fo.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function yo(e){let t=e.priority;return fo.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 bo(e){const t=e.filter(e=>ho(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),r=n.get(t)??new Set;r.add(e.strategy),n.set(t,r)}const r=t.map(e=>{let t=yo(e);const r=n.get(e.text.trim())?.size??1;return r>1&&(t-=.5*r),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return r?{found:!0,username:r.evidence.text.trim(),strategy:r.evidence.strategy,source:r.evidence.source}:{found:!1}}var vo='.user-list.b-scroll-stable [role="listitem"], .geek-item',wo='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',xo=".candidate-card-wrap",So="[data-geek], .geek-item",Ro=`${xo}, ${So}`,Co=["/web/chat/recommend"],Io=250,ko=300,Ao=12,Eo=900,_o=1200,No=4,Mo=4,Po=250,To=90,Bo=30,Do=700,Oo=800,$o=520,Fo=16,jo=50,Lo=650,qo=10,zo=.015,Vo="button, a, label, li, span, div, [role='button'], [role='radio']",Jo=["visible","hidden","prerender","unloaded"],Uo=["not_chat_page"];function Ho(e){const t=/(初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/,n=/(\d{2,3}岁|(?:\d{2}年|\d{4}届|\d{4}年)?应届生|\d{2,4}年毕业|在校生|经验不限|无经验|\d+\s*年以上|[1一]年以内|\d+\s*-\s*\d+\s*年|\d+\s*年|初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/g;function r(e){return e.replace(/\s+/g," ").trim()}function a(e){return e.replace(/\s+/g,"")}function o(e){return String(Number.parseInt(e,10))}function i(e){const n=a(e).match(/^(\d{2,3})岁$/);if(void 0!==n?.[1])return{kind:"age",value:`${n[1]}岁`};const r=e.match(t);if(void 0!==r?.[1])return{kind:"education",value:r[1]};const i=function(e){const t=a(e);if(/^(?:\d{2}年|\d{4}届|\d{4}年)?应届生$/.test(t))return"应届生";if(/^\d{2,4}年毕业$/.test(t))return;if("在校生"===t)return"在校生";if("经验不限"===t||"无经验"===t)return t;if("一年以内"===t)return"1年以内";const n=t.match(/^(\d+)年以上$/);if(void 0!==n?.[1])return`${o(n[1])}年以上`;const r=t.match(/^(\d+)-(\d+)年$/);if(void 0!==r?.[1]&&void 0!==r[2])return`${o(r[1])}-${o(r[2])}年`;const i=t.match(/^(\d+)年$/);return void 0!==i?.[1]?`${o(i[1])}年`:"1年以内"===t?"1年以内":void 0}(e);return void 0!==i?{kind:"experience",value:i}:{kind:"ignored"}}const s=e.map(e=>r(e)).filter(e=>e.length>0).flatMap(e=>function(e){const t=[...r(e).matchAll(n)].map(e=>r(e[1]??"")).filter(e=>e.length>0);return t.length>0?t:e.split(/[丨|·\n\r\t]+/).map(e=>r(e)).filter(e=>e.length>0)}(e)).map((e,t)=>({index:t,token:i(e)})),c=s.find(e=>"age"===e.token.kind)?.token,l=s.find(e=>"education"===e.token.kind)?.token,d="age"===c?.kind?c.value:"",u="education"===l?.kind?l.value:"",m=s.find(e=>"age"===e.token.kind)?.index??-1,p=s.find(e=>"education"===e.token.kind)?.index??-1,g=s.flatMap(e=>"experience"===e.token.kind?[{index:e.index,experience:e.token.value}]:[]),f=g.find(e=>(m<0||e.index>m)&&(p<0||e.index<p))?.experience??g[0]?.experience??"";return{age:d,experience:function(e,t){const n=function(e){const t=e.match(/^(\d{2,3})岁$/);if(void 0===t?.[1])return;const n=Number.parseInt(t[1],10);return Number.isFinite(n)?n:void 0}(e),r=function(e){const t=a(e),n=t.match(/^(\d+)-(\d+)年$/);if(void 0!==n?.[2])return Number.parseInt(n[2],10);const r=t.match(/^(\d+)年以上$/)??t.match(/^(\d+)年$/);return void 0!==r?.[1]?Number.parseInt(r[1],10):"1年以内"===t?1:void 0}(t);return void 0===n||void 0===r||r<=Math.max(0,n-12)}(d,f)?f:"",education:u}}function Wo(e){return new Promise(t=>{setTimeout(t,e)})}function Ko(e){return{x:e.x,y:e.y}}function Go(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Yo(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function Xo(e){return e.trim().toLocaleLowerCase("zh-CN")}function Zo(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function Qo(e,t){const n=Xo(e),r=Xo(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function ei(e){return"string"==typeof e?e:""}function ti(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function ni(e){return"boolean"==typeof e&&e}function ri(e){return Array.isArray(e)?e.map((e,t)=>{const n=Go(e)?e:{};return{conversationId:ei(n.conversationId),candidateId:ei(n.candidateId),name:ei(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:ei(n.position),hasUnread:ni(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:ei(n.lastMessageTime),messagePreview:ei(n.messagePreview)}}):[]}function ai(e){return Array.isArray(e)?e.flatMap(e=>{if(!Go(e))return[];const t=e.text,n=e.strategy,r=e.priority,a=e.source,o=e.xRatio;return"string"!=typeof t||"role-link"!==n&&"role-button"!==n&&"leaf-text"!==n&&"css-fallback"!==n||"number"!=typeof r||"string"!=typeof a?[]:[{text:t,strategy:n,priority:r,source:a,..."number"==typeof o?{xRatio:o}:{}}]}):[]}function oi(e){return Array.isArray(e)?e.map((e,t)=>{const n=Go(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:ei(n.candidateId),name:ei(n.name),age:ei(n.age),experience:ei(n.experience),education:ei(n.education),workStatus:ei(n.workStatus),company:ei(n.company),currentPosition:ei(n.currentPosition),expectedLocation:ei(n.expectedLocation),expectedPosition:ei(n.expectedPosition),expectedSalary:ei(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>ei(e)).filter(Boolean):[],buttonText:ei(n.buttonText)}}):[]}function ii(e){return Array.isArray(e)?e.map((e,t)=>{const n=Go(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:ei(n.value),label:ei(n.label),isCurrent:ni(n.isCurrent)}}):[]}function si(e){return Go(e)?{found:ni(e.found),isOpen:ni(e.isOpen),currentLabel:ei(e.currentLabel),currentValue:ei(e.currentValue),options:ii(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function ci(e){if(!Go(e))return null;const t=ei(e.conversationId),n=ei(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:ei(e.candidateName)}}function li(e){if(!Go(e))return null;const t=ei(e.candidateName);return t.length>0?{candidateName:t}:null}function di(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!Go(e))return[];const n=e.sender,r=e.messageType;return"candidate"!==n&&"recruiter"!==n&&"system"!==n||"text"!==r&&"system"!==r&&"resume"!==r&&"wechat-exchange"!==r?[]:[{index:"number"==typeof e.index&&Number.isInteger(e.index)?e.index:t,sender:n,messageType:r,content:ei(e.content),time:ei(e.time)}]}):[]}function ui(e){const t=Go(e)?e:{},n=t.tags;return{name:ei(t.name),age:ei(t.age),experience:ei(t.experience),education:ei(t.education),communicationPosition:ei(t.communicationPosition),expectedJobText:ei(t.expectedJobText),expectedSalary:ei(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>ei(e)).filter(Boolean):[]}}function mi(e){const t=Go(e)?e:{};return{selectedTarget:ci(t.selectedTarget),activePanel:li(t.activePanel),candidateInfo:ui(t.candidateInfo),messages:di(t.messages)}}function pi(e){return Go(e)?{found:ni(e.found),x:ti(e.x),y:ti(e.y)}:{found:!1,x:0,y:0}}function gi(e){return Go(e)&&"number"==typeof e.x&&"number"==typeof e.y?{x:e.x,y:e.y}:null}function fi(e){if(Go(e)&&"number"==typeof e.width&&"number"==typeof e.height)return{width:e.width,height:e.height}}function hi(e){if(Go(e)&&"number"==typeof e.x&&"number"==typeof e.y&&"number"==typeof e.width&&"number"==typeof e.height)return{x:e.x,y:e.y,width:e.width,height:e.height}}function yi(e){return Go(e)?{dialogVisible:!0===e.dialogVisible,iframeFound:!0===e.iframeFound,iframeVisible:!0===e.iframeVisible,canvasReady:!0===e.canvasReady}:{dialogVisible:!1,iframeFound:!1,iframeVisible:!1,canvasReady:!1}}function bi(e,t,n){if(!Go(e)||!0!==e.found)return{found:!1,error:"无法读取简历 canvas 状态"};const r=hi(e.canvasRect);if(void 0===r)return{found:!1,error:"简历 canvas 坐标不完整"};const a=po({recommendFrameRect:n,resumeFrameRect:gi(e.resumeFrameRect),canvasRect:r}),o=Go(t)?t:{},i=o.canvasSize,s=Go(i)&&"number"==typeof i.width&&"number"==typeof i.height?{width:i.width,height:i.height}:{width:Math.round(r.width),height:Math.round(r.height)},c=o.dataUrl,l=o.dataUrlError;return{found:!0,screenshotArea:a,canvasSize:s,..."string"==typeof c&&c.startsWith("data:image/")?{dataUrl:c}:{},..."string"==typeof l?{dataUrlError:l}:{},...!0===o.blank?{blank:!0}:{}}}function vi(e){return Go(e)?{found:ni(e.found),left:ti(e.left),top:ti(e.top)}:{found:!1,left:0,top:0}}function wi(e){return Go(e)?{found:ni(e.found),cardSelector:ei(e.cardSelector)||xo,candidateId:ei(e.candidateId),name:ei(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:ni(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:xo,candidateId:"",name:"",hasGreetButton:!1}}function xi(e){return Go(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 Si(e){return Go(e)&&!0===e.ok?{ok:!0,current:xi(e.current),trackLeft:ti(e.trackLeft),trackTop:ti(e.trackTop),trackWidth:ti(e.trackWidth),trackHeight:ti(e.trackHeight),minHandleX:ti(e.minHandleX),minHandleY:ti(e.minHandleY),maxHandleX:ti(e.maxHandleX),maxHandleY:ti(e.maxHandleY)}:{ok:!1,error:Go(e)&&ei(e.error)||"未找到年龄滑块"}}function Ri(e){return Go(e)?{containerFound:ni(e.containerFound),containerLabel:ei(e.containerLabel),scrollTop:ti(e.scrollTop),scrollHeight:ti(e.scrollHeight),clientHeight:ti(e.clientHeight),itemCount:ti(e.itemCount),atStart:ni(e.atStart),atEnd:ni(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Ci(e){return[e,...(e.childFrames??[]).flatMap(e=>Ci(e))]}function Ii(e){const t=new Set,n=[];for(const r of e){const e=r.conversationId.length>0?`conversation:${r.conversationId}`:`fallback:${r.name}:${r.messagePreview}:${r.index}`;t.has(e)||(t.add(e),n.push(r))}return n}function ki(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 Ai(e){return Jo.some(t=>t===e)}function Ei(e,t){return`打开候选人聊天后,右侧会话未同步切换到 ${e.name||e.conversationId};${[`document.visibilityState=${t.visibilityState}`,`expectedConversationId=${e.conversationId||"<empty>"}`,`selectedConversationId=${t.selectedConversationId||"<none>"}`,`activePanelCandidateName=${t.activePanelCandidateName||"<none>"}`].join(", ")}${"hidden"===t.visibilityState?";页面当前不可见,可能已最小化、被完全遮挡或位于其他 macOS Space":""}`}function _i(e,t,n){const r=t.filter(e=>Tt(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&Yo(a.url))return a;const e=r.filter(e=>Yo(e.url));if(1===e.length){const t=e[0];if(t)return t}if(a)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(a)return a;if(1===r.length){const e=r[0];if(e)return e}if(r.length>1)throw new Error("Multiple BOSS pages found; select the target tab first.");throw new Error("No BOSS page found.")}async function Ni(e={},t={}){const n=t.ctxManager??ue(),r=t.runtime??de(),a=_i(n,await n.listNativePages(),e),o=await r.connectNativePage(a);return new Mi({target:a,controller:o})}var Mi=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new ea(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 Wo(Io)}return!1}async evaluateJson(e){return await this.controller.evaluateJson(e)}async bringToFront(){await this.controller.bringToFront()}async inspectChatReloadTarget(){const e=await this.url().catch(()=>this.target.url);return Yo(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await Fn(this.controller,{url:e.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{},...void 0!==e.onReloadSent?{onReloadSent:e.onReloadSent}:{}})}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(wo)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await Wo(Io)}return!1}async waitForChatListReady(e={},t=1e4){const n=Date.now(),r=e.expectedConversationId,a=async()=>{if(void 0===r)return!1;return(await this.readVisibleChatCandidates().catch(()=>[])).some(e=>e.conversationId===r)};if(!await this.waitForSelector(wo,t))return await a();if(void 0===r)return!0;for(;Date.now()-n<t;){const e=await this.readVisibleChatCandidates().catch(()=>[]);if(e.some(e=>e.conversationId===r))return!0;if(e.length>0)break;await Wo(Io)}for(let e=0;e<Ao&&Date.now()-n<t;e+=1){const e=await this.scrollChatList("up").catch(()=>{});if(void 0===e||!e.ok||e.after>=e.before)break;if(await Wo(ko),await a())return!0;if(e.after<=1)break}for(let e=0;e<=Ao&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=Ao)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await Wo(ko)}return await a()}async isRecommendSurfaceOpen(){const e=[Ga.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Co)};\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 Wo(Io)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Ci(e).map(e=>e.frame),n=t.find(e=>"recommendFrame"===e.name),r=t.find(t=>t.id!==e.frame.id&&t.url.includes("recommend")),a=n??r;if(void 0===a)return;if(void 0!==this.recommendFrameContextId&&this.recommendFrameContextFrameId===a.id)return this.recommendFrameContextId;const o=await this.controller.createIsolatedWorld(a.id).catch(()=>{});return void 0!==o?(this.recommendFrameContextId=o,this.recommendFrameContextFrameId=a.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 vi(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Ga.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=pi(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=pi(await this.evaluateRecommendFrameJson(e));if(!n.found)return n;const r=await this.readRecommendFrameOffset();return r.found?{found:!0,x:Math.round(n.x+r.left),y:Math.round(n.y+r.top)}:n}async dispatchNativeClick(e,t={}){return!!e.found&&(await this.mouse.click(Ko(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??To,settleMs:t.settleMs??Po}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(Ro)}) !== 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 Wo(Io)}else await Wo(Io);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=si(await this.evaluateJson(e).catch(()=>{}));return t.found?t:si(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 Wo(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 Wo(260),await this.selectAllFocusedText(),await Wo(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await Wo(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await Wo(500),!0;await Wo(350);for(const t of Array.from(e))await this.controller.insertText(t),await Wo(110);return await Wo(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 r=Zo(e.jobName),a=t.filter(e=>Zo(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>Zo(e.label).includes(r)))}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=Zo(e.jobName),r=Zo(t.label);return r===n||r.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 Wo(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 Wo(700);let r=await this.readRecommendJobSelectorState();const a=this.getCurrentRecommendJobOption(r);if(void 0!==a&&this.currentRecommendJobMatchesRequest(n,a)&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:a,selected:a,options:r.options,matchedCount:1};let o=this.selectRecommendJobMatch(n,r.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 Wo(900),r=await this.readRecommendJobSelectorState(),o=this.selectRecommendJobMatch(n,r.options));const s=this.getCurrentRecommendJobOption(r);if(void 0===o.selected)return{success:!1,status:"not_found",requested:n,...void 0!==s?{current:s}:{},options:r.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:r.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:r.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 Wo(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:r.options,matchedCount:o.matchedCount}}async clickSidebarSection(e,t={}){const n="chat"===e?Ga.nav.chatLink:Ga.nav.recommendLink,r=pi(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(Ga.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!!r.found&&await this.dispatchNativeClick(r,t)}async scrollSurface(e,t={}){const n=Ka(e),r=t.direction??n.defaultDirection,a=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<a;n+=1){if("chat-list"===e&&s.itemCount>0){const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await Wo(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if("up"===r&&s.atStart||"down"===r&&s.atEnd){if(s.itemCount<=0)break;const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await Wo(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if(s=await this.scrollSurfaceOnce(e,r,t.distance),c+=1,o>0){await Wo(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}}return{success:i.containerFound||s.containerFound||c>0,direction:r,stepsRequested:a,stepsCompleted:c,reachedBoundary:"up"===r?s.atStart:s.atEnd,before:i,after:s}}async readChatCandidates(e={}){const t=e.targetCount??20,n=e.autoScroll?e.maxScrolls??3:0,r=[];for(let e=0;e<=n;e+=1){r.push(...await this.readVisibleChatCandidates());const a=Ii(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await Wo(ko)}return Ii(r)}async openChat(e){if(await cn(this),!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?Ao:4))),r=async t=>{if(!await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}}))return{...t,found:!1,error:`未能点击候选人: ${t.name||t.conversationId}`};await Wo(Po);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await Wo(Po),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:Ei(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Ii(o),c=!0!==e.preferUnread||t?Ua(s,e):s.find(e=>e.hasUnread);if(void 0!==c)return await r(c);if(i>=a)break;if(!(await this.scrollChatList(n)).ok)break;await Wo(ko)}},o=await a("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 Wo(ko)}const e=await a("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 ci(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 li(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 Wo(Io)}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 mi(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\n const profile = (${Ho.toString()})(infoTexts);\n const age = profile.age;\n const experience = profile.experience;\n const education = profile.education;\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),r=wi(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=wi(await this.evaluateRecommendFrameJson(n));return a.found?a:r}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 r=await this.resolveRecommendClickTarget(this.buildRecommendGreetClickExpression(Math.max(0,Math.floor(e)))),a=await this.dispatchNativeClick(r,t);return{...n,clicked:a,...a?{}:{error:"未能点击打招呼按钮"}}}async clickRecommendCardSurface(e,t={}){const n=await this.resolveRecommendClickTarget(this.buildRecommendCardOpenClickExpression(Math.max(0,Math.floor(e))));return await this.dispatchNativeClick(n,t)}async waitForResumeDialog(e=1e4){const t=Date.now();let n=await this.readResumeDialogState();for(;!n.canvasReady&&Date.now()-t<e;)await Wo(Io),n=await this.readResumeDialogState();return n}async readResumeCanvasGeometry(){const e=this.buildResumeCanvasGeometryExpression();let t=!1,n=await this.evaluateJson(e).catch(()=>{});if(!Go(n)||!0!==n.found){const r=await this.evaluateRecommendFrameJson(e);void 0!==r&&(n=r,t=!0)}if(!Go(n)||!0!==n.found){const e=Go(n)?n.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const r=hi(n.canvasRect),a=fi(n.canvasSize);if(void 0===r||void 0===a)return{found:!1,error:"简历 canvas 坐标读取失败"};const o=gi(n.resumeFrameRect),i=t?await this.readRecommendFrameOffset():void 0;return{found:!0,screenshotArea:po({recommendFrameRect:void 0!==i&&i.found?{x:i.left,y:i.top}:null,resumeFrameRect:o??null,canvasRect:r}),canvasSize:a}}buildResumeCanvasGeometryExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(uo)};\n const canvasSelector = ${JSON.stringify(mo)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n return {\n found: true,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n },\n canvasSize: { width: canvas.width, height: canvas.height }\n };\n })()`}async captureResumeCanvas(e){await Wo(Oo);const t=this.buildResumeStitchInitExpression();let n=!1,r=await this.evaluateJson(t).catch(()=>{});if(!Go(r)||!0!==r.found){const e=await this.evaluateRecommendFrameJson(t);void 0!==e&&(r=e,n=!0)}if(!Go(r)||!0!==r.found){const e=Go(r)?r.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const a=async e=>n?await this.evaluateRecommendFrameJson(e):await this.evaluateJson(e).catch(()=>{}),o="number"==typeof r.maxScrollTotal&&r.maxScrollTotal>0?r.maxScrollTotal:0,i=this.buildResumeStitchStepExpression();for(let t=0;t<Bo;t++){await Wo(Do);const n=await a(i);if(!Go(n)||!0!==n.ok||!0===n.done)break;if(void 0!==e){const r="number"==typeof n.scrollTop?n.scrollTop:0;await Promise.resolve(e({round:t,scrolledPx:r,totalPx:o})).catch(()=>{})}}const s=await a(this.buildResumeStitchExportExpression()),c=n?await this.readRecommendFrameOffset():void 0;return bi(r,s,void 0!==c&&c.found?{x:c.left,y:c.top}:null)}async captureViewportClip(e,t=1){return await this.controller.captureScreenshot({format:"png",clip:{x:e.x,y:e.y,width:e.width,height:e.height,scale:t},timeoutMs:15e3})}async readResumeDialogClipArea(){const e=this.buildResumeDialogRectExpression(),t=hi(await this.evaluateJson(e).catch(()=>{}));if(void 0!==t)return t;const n=hi(await this.evaluateRecommendFrameJson(e));if(void 0===n)return;const r=await this.readRecommendFrameOffset();return r.found?{x:Math.round(n.x+r.left),y:Math.round(n.y+r.top),width:Math.round(n.width),height:Math.round(n.height)}:n}async closeResumeDialog(e={}){const t=await this.resolveRecommendClickTarget(this.buildResumeCloseTargetExpression());return t.found&&await this.dispatchNativeClick(t,e)&&await this.waitForResumeDialogGone()?{closed:!0,method:"close-button"}:(await this.controller.dispatchKeyEvent({type:"keyDown",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.waitForResumeDialogGone()?{closed:!0,method:"escape"}:{closed:!1,error:"简历弹窗未关闭"})}async readResumeDialogState(){const e=this.buildResumeDialogStateExpression(),t=yi(await this.evaluateJson(e).catch(()=>{}));if(t.iframeFound)return t;const n=yi(await this.evaluateRecommendFrameJson(e));return{dialogVisible:t.dialogVisible||n.dialogVisible,iframeFound:n.iframeFound,iframeVisible:n.iframeVisible,canvasReady:n.canvasReady}}async waitForResumeDialogGone(e=4e3){const t=Date.now();for(;Date.now()-t<e;){const e=await this.readResumeDialogState();if(!(e.iframeFound&&e.iframeVisible||e.dialogVisible))return!0;await Wo(Io)}return!1}async exchangeWechat(e={}){const t=pi(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=pi(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 Wo(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=pi(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 Wo(250);const r=pi(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(r,t)?(await Wo(800),{success:!0}):{success:!1,error:"未找到发送按钮"}}async applyRecommendFilter(e,t={}){if(!await this.waitForRecommendFilterSurface(3e3))return{status:"recommend_not_ready",requested:e,error:"推荐牛人页未就绪"};let n=e.location;if(void 0!==e.location){if(!await this.applyRecommendLocationFilter(e.location,t))return{status:"filter_not_found",requested:e,error:`未能设置地区筛选:${e.location.city}${void 0!==e.location.district?`-${e.location.district}`:""}`};n=e.location}if(!no(e)&&0===e.optionSelections.length){const t=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:{...void 0!==n?{location:n}:{},optionSelections:[]},...void 0!==t?{filterButtonText:t}:{}}}if(!await this.openRecommendFilterPanel(t))return{status:"filter_not_found",requested:e,error:"未找到或无法打开筛选按钮"};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"筛选条件触发 VIP 弹窗"};if("replace"===e.applyMode&&!await this.clickRecommendFilterClear(t))return{status:"clear_failed",requested:e,error:"筛选清除失败"};for(const n of e.optionSelections){if(!await this.clickRecommendFilterValues(n,t))return await this.detectVipModal()?{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}:{status:"filter_not_found",requested:e,error:`未找到${n.label}筛选项:${n.values.join("、")}`};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}}let r={};if(no(e)){const n=await this.setRecommendAgeRange(e,t);if(!n.success)return{status:"age_not_applied",requested:e,error:n.error};r=n.state}if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"年龄筛选触发 VIP 弹窗"};const a=await this.readNativeAppliedFilterState(e,r,n);if(!await this.clickRecommendFilterSubmit(t))return{status:"submit_failed",requested:e,applied:a,error:"筛选确认失败"};const o=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:a,...void 0!==o?{filterButtonText:o}:{}}}async readRecommendCandidates(e={}){const t=e.targetCount,n=e.autoScroll?e.maxScrolls??4:0,r=await this.inspectSurface("recommend-list");let a=r;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=ki(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(a.atEnd){let e=!1;for(let t=0;t<No;t+=1){await Wo(_o);const t=await d(),n=await this.inspectSurface("recommend-list"),r=t>0||n.scrollHeight>a.scrollHeight||n.itemCount>a.itemCount||!n.atEnd;if(a=n,r){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>=Mo){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:Eo});a=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:r.containerFound,direction:"down",stepsRequested:n,stepsCompleted:i,reachedBoundary:a.atEnd,before:r,after:a,items:u,uniqueCount:u.length,duplicateCount:s,noNewRounds:c,stopReason:l}}async readUsernameEvidence(){return ai(await this.evaluateJson(`(() => {\n const limit = ${go};\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([Ga.username.primary,...Ga.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(xo)};\n const fallbackSelector = ${JSON.stringify(So)};\n const iframe = document.querySelector(${JSON.stringify(Ga.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Co)};\n const parseCandidateProfileTokens = ${Ho.toString()};\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 const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n 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 })()`}buildRecommendCardTargetClickExpression(e,t){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(xo)};\n const fallbackSelector = ${JSON.stringify(So)};\n const iframe = document.querySelector(${JSON.stringify(Ga.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 target = ${t};\n if (!target) return { found: false, x: 0, y: 0 };\n target.scrollIntoView({ block: "center", inline: "center" });\n const rect = target.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 })()`}buildRecommendGreetClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,'item.querySelector("button.btn.btn-greet") ?? item.querySelector("button.btn-greet") ?? item.querySelector(".btn-greet") ?? item.querySelector(".op-btn")')}buildRecommendCardOpenClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,`item.querySelector(${JSON.stringify(io)}) ?? item`)}buildResumeDialogStateExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(uo)};\n const dialogSelector = ${JSON.stringify(so)};\n const canvasSelector = ${JSON.stringify(mo)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n let dialogVisible = false;\n let iframeFound = false;\n let iframeVisible = false;\n let canvasReady = false;\n const dialog = document.querySelector(dialogSelector);\n if (dialog && visible(dialog)) dialogVisible = true;\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (resumeIframe) {\n iframeFound = true;\n iframeVisible = visible(resumeIframe);\n const resumeDoc = resumeIframe.contentDocument;\n const canvas = resumeDoc ? resumeDoc.querySelector(canvasSelector) : null;\n if (canvas && canvas.width > 0 && canvas.height > 0) canvasReady = true;\n }\n return { dialogVisible, iframeFound, iframeVisible, canvasReady };\n })()`}buildResumeStitchInitExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(uo)};\n const canvasSelector = ${JSON.stringify(mo)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n\n const scrollers = [];\n let node = resumeIframe.parentElement;\n while (node) {\n if (node.scrollHeight > node.clientHeight + 20) scrollers.push(node);\n node = node.parentElement;\n }\n const rootScroller = document.scrollingElement;\n if (\n rootScroller &&\n rootScroller.scrollHeight > rootScroller.clientHeight + 20 &&\n !scrollers.includes(rootScroller)\n ) {\n scrollers.push(rootScroller);\n }\n\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n const dpr = canvasRect.width > 0 ? canvas.width / canvasRect.width : 1;\n const maxScrollTotal = scrollers.reduce(\n (sum, el) => sum + Math.max(0, el.scrollHeight - el.clientHeight),\n 0\n );\n const totalHeightPx = Math.max(\n Math.round((maxScrollTotal + canvasRect.height) * dpr),\n canvas.height\n );\n\n const stitched = resumeDoc.createElement("canvas");\n stitched.width = canvas.width;\n stitched.height = totalHeightPx;\n const context = stitched.getContext("2d");\n if (!context) return { found: false, error: "无法创建拼接 canvas 上下文" };\n context.fillStyle = "#ffffff";\n context.fillRect(0, 0, stitched.width, stitched.height);\n\n for (const el of scrollers) el.scrollTop = 0;\n globalThis.__rollResumeStitch = {\n stitched,\n context,\n canvas,\n scrollers,\n dpr,\n maxDrawnY: 0,\n scrollerIndex: 0,\n effectiveOffset: 0,\n pending: null,\n pendingFpBase: "",\n readFp: function () {\n try {\n if (!this.fpContext) {\n const fpCanvas = this.canvas.ownerDocument.createElement("canvas");\n fpCanvas.width = 32;\n fpCanvas.height = 32;\n this.fpContext = fpCanvas.getContext("2d", { willReadFrequently: true });\n }\n const fpContext = this.fpContext;\n if (!fpContext) return "noctx";\n fpContext.fillStyle = "#ffffff";\n fpContext.fillRect(0, 0, 32, 32);\n fpContext.drawImage(this.canvas, 0, 0, 32, 32);\n const data = fpContext.getImageData(0, 0, 32, 32).data;\n let hash = 0;\n for (let index = 0; index < data.length; index += 4) {\n hash = ((hash * 31 + data[index] + data[index + 1] + data[index + 2]) >>> 0);\n }\n return String(hash);\n } catch (error) {\n return "err";\n }\n }\n };\n return {\n found: true,\n needScroll: maxScrollTotal > 4,\n maxScrollTotal,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n }\n };\n })()`}buildResumeStitchStepExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n const fingerprint = state.readFp();\n\n if (state.pending) {\n const pendingScroller = state.scrollers[state.pending.index];\n const realDelta = pendingScroller\n ? pendingScroller.scrollTop - state.pending.before\n : 0;\n if (realDelta <= 2) {\n state.scrollerIndex = state.pending.index + 1;\n } else if (fingerprint !== state.pendingFpBase) {\n state.effectiveOffset += realDelta;\n } else {\n if (pendingScroller) pendingScroller.scrollTop = state.pending.before;\n state.scrollerIndex = state.pending.index + 1;\n }\n state.pending = null;\n }\n\n const drawY = Math.round(state.effectiveOffset * state.dpr);\n const drawnBottom = drawY + state.canvas.height;\n if (drawnBottom > state.stitched.height) {\n const grown = state.stitched.ownerDocument.createElement("canvas");\n grown.width = state.stitched.width;\n grown.height = drawnBottom;\n const grownContext = grown.getContext("2d");\n if (!grownContext) return { ok: false, error: "无法扩容拼接 canvas" };\n grownContext.fillStyle = "#ffffff";\n grownContext.fillRect(0, 0, grown.width, grown.height);\n grownContext.drawImage(state.stitched, 0, 0);\n state.stitched = grown;\n state.context = grownContext;\n }\n state.context.drawImage(state.canvas, 0, drawY);\n if (drawnBottom > state.maxDrawnY) state.maxDrawnY = drawnBottom;\n\n const scroller = state.scrollers[state.scrollerIndex];\n if (scroller) {\n const before = scroller.scrollTop;\n scroller.scrollTop = before + scroller.clientHeight;\n state.pending = { index: state.scrollerIndex, before };\n state.pendingFpBase = fingerprint;\n }\n return { ok: true, done: !scroller, scrollTop: state.effectiveOffset };\n })()'}buildResumeStitchExportExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n delete globalThis.__rollResumeStitch;\n let output = state.stitched;\n const finalHeight = Math.min(\n state.stitched.height,\n Math.max(state.maxDrawnY, state.canvas.height)\n );\n if (finalHeight < state.stitched.height) {\n const trimmed = state.stitched.ownerDocument.createElement("canvas");\n trimmed.width = state.stitched.width;\n trimmed.height = finalHeight;\n const trimContext = trimmed.getContext("2d");\n if (trimContext) {\n trimContext.drawImage(state.stitched, 0, 0);\n output = trimmed;\n }\n }\n let blank = true;\n const outputContext = output.getContext("2d");\n if (outputContext) {\n try {\n const stepX = Math.max(1, Math.floor(output.width / 16));\n const stepY = Math.max(1, Math.floor(output.height / 16));\n outer: for (let y = 0; y < output.height; y += stepY) {\n for (let x = 0; x < output.width; x += stepX) {\n const pixel = outputContext.getImageData(x, y, 1, 1).data;\n if (pixel[3] > 8 && (pixel[0] < 246 || pixel[1] < 246 || pixel[2] < 246)) {\n blank = false;\n break outer;\n }\n }\n }\n } catch (error) {\n blank = false;\n }\n }\n let dataUrl = null;\n let dataUrlError = null;\n try {\n dataUrl = output.toDataURL("image/png");\n } catch (error) {\n dataUrlError = error instanceof Error ? error.message : String(error);\n }\n for (const el of state.scrollers) el.scrollTop = 0;\n return {\n ok: true,\n dataUrl,\n dataUrlError,\n blank,\n canvasSize: { width: output.width, height: output.height }\n };\n })()'}buildResumeDialogRectExpression(){return`(() => {\n const dialogSelector = ${JSON.stringify(so)};\n let best = null;\n let bestArea = 0;\n for (const element of Array.from(document.querySelectorAll(dialogSelector))) {\n const rect = element.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (rect.width > 0 && rect.height > 0 && area > bestArea) {\n best = rect;\n bestArea = area;\n }\n }\n if (!best) return { found: false };\n return {\n found: true,\n x: Math.max(0, best.x),\n y: Math.max(0, best.y),\n width: best.width,\n height: best.height\n };\n })()`}buildResumeCloseTargetExpression(){return`(() => {\n const scopeSelector = ${JSON.stringify(co)};\n const closeSelectors = ${JSON.stringify([...lo])};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const scopes = Array.from(document.querySelectorAll(scopeSelector)).filter(visible);\n for (const scope of scopes) {\n for (const selector of closeSelectors) {\n for (const element of Array.from(scope.querySelectorAll(selector))) {\n if (!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 }\n return { found: false, x: 0, y: 0 };\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 Wo(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(){await this.evaluateJson('(() => {\n const el = document.activeElement;\n if (!el) return false;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {\n try {\n el.select();\n return true;\n } catch {\n return false;\n }\n }\n if (el.isContentEditable) {\n const range = document.createRange();\n range.selectNodeContents(el);\n const selection = window.getSelection();\n if (!selection) return false;\n selection.removeAllRanges();\n selection.addRange(range);\n return true;\n }\n return false;\n })()').catch(()=>{})}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${Ga.recommend.filterButton}, ${xo}, ${Ga.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 Wo(Io)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Ga.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(Ga.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 Wo(Io)}}await Wo(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 r=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Ga.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Vo)};\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(r,n)}async clickRecommendFilterValues(e,t){const n=Array.from(new Set(e.values.map(e=>e.trim()))).filter(e=>e.length>0);if(0===n.length)return!0;if("single"===e.selection){const r=n[0];return void 0===r||await this.clickRecommendFilterOption(e.label,r,t)}if(n.includes(e.clearValue))return await this.clickRecommendFilterOption(e.label,e.clearValue,t);if(!await this.clickRecommendFilterOption(e.label,e.clearValue,t))return!1;for(const r of n)if(!await this.clickRecommendFilterOption(e.label,r,t))return!1;return!0}async isRecommendLocationPanelVisible(){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 roots = Array.from(document.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 const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const explicitPanel = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .some((element) => visible(element) && isLocationPanel(element));\n if (explicitPanel) return true;\n return roots.some((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 120 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n })()';return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async openRecommendLocationPanel(e,t){if(await this.isRecommendLocationPanelVisible())return!0;const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedCity = ${JSON.stringify(e.city)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = normalizeCity(expectedCity);\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 centerY = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.top + rect.height / 2;\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 controls = Array.from(document.querySelectorAll("button, a, span, div, [role='button']"))\n .filter((element) => visible(element));\n const filterButton = controls\n .filter((element) => /^筛选(?:·\\d+)?$/.test(normalize(element.textContent)))\n .sort((a, b) => area(a) - area(b))[0];\n if (filterButton) {\n const filterRect = filterButton.getBoundingClientRect();\n const sameRowCandidates = controls\n .filter((element) => {\n if (element === filterButton) return false;\n const text = normalize(element.textContent);\n if (text.length === 0 || text.length > 12) return false;\n if (text.includes("_") || text.includes("筛选") || text.includes("推荐")) return false;\n const rect = element.getBoundingClientRect();\n return (\n rect.right <= filterRect.left + 8 &&\n Math.abs(centerY(element) - centerY(filterButton)) <= 36\n );\n })\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return Math.abs(filterRect.left - aRect.right) - Math.abs(filterRect.left - bRect.right);\n });\n if (sameRowCandidates[0]) return readCenter(sameRowCandidates[0]);\n }\n\n const cityCandidates = controls\n .filter((element) => {\n const text = normalizeCity(element.textContent);\n const rect = element.getBoundingClientRect();\n return text === expected && rect.top < 160;\n })\n .sort((a, b) => area(a) - area(b));\n if (cityCandidates[0]) return readCenter(cityCandidates[0]);\n\n return { found: false, x: 0, y: 0 };\n })()`);if(!await this.dispatchNativeClick(n,t))return!1;const r=Date.now();for(;Date.now()-r<4e3;){if(await this.isRecommendLocationPanelVisible())return!0;await Wo(Io)}return!1}async clickRecommendLocationValue(e,t,n){const r=await this.resolveRecommendClickTarget(`(() => {\n const kind = ${JSON.stringify(e)};\n const rawValue = ${JSON.stringify(t)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = kind === "city" ? normalizeCity(rawValue) : normalize(rawValue);\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 roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认") &&\n text.includes(expected)\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n if (!panel) return { found: false, x: 0, y: 0 };\n\n const panelRect = panel.getBoundingClientRect();\n const leftLimit = panelRect.left + panelRect.width * (kind === "city" ? 0 : 0.22);\n const rightLimit = panelRect.left + panelRect.width * (kind === "city" ? 0.34 : 0.72);\n const candidates = Array.from(\n panel.querySelectorAll("button, a, label, li, span, div, [role='button'], [role='checkbox']")\n )\n .filter((element) => {\n if (!visible(element)) return false;\n const text = kind === "city" ? normalizeCity(element.textContent) : normalize(element.textContent);\n if (text !== expected) return false;\n const rect = element.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n return centerX >= leftLimit && centerX <= rightLimit;\n })\n .sort((a, b) => area(a) - area(b));\n const candidate = candidates[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 })()`);return!!await this.dispatchNativeClick(r,n)&&(await Wo(250),!0)}async clickRecommendLocationSubmit(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 area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\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((element) => visible(element) && normalize(element.textContent) === "确认")\n .sort((a, b) => area(a) - area(b))[0];\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 })()');return!!await this.dispatchNativeClick(t,e)&&(await Wo(500),!0)}async applyRecommendLocationFilter(e,t){return!!await this.openRecommendLocationPanel(e,t)&&(!!await this.clickRecommendLocationValue("city",e.city,t)&&(!(void 0!==e.district&&!await this.clickRecommendLocationValue("district",e.district,t))&&await this.clickRecommendLocationSubmit(t)))}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(Ga.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(Fo)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(jo)} -\n ${JSON.stringify(Fo)})\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=xi(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:xi(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Ga.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=Si(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Si(await this.evaluateRecommendFrameJson(e));if(!n.ok)return n;const r=await this.readRecommendFrameOffset();return r.found?{...n,trackLeft:n.trackLeft+r.left,trackTop:n.trackTop+r.top,minHandleX:n.minHandleX+r.left,minHandleY:n.minHandleY+r.top,maxHandleX:n.maxHandleX+r.left,maxHandleY:n.maxHandleY+r.top}:n}async dispatchNativeDrag(e,t,n,r,a={}){await this.mouse.drag({x:e,y:t},{x:n,y:r},{...void 0!==a.motionObserver?{motionObserver:a.motionObserver}:{},pressDurationMs:a.pressDurationMs??To})}async dragAgeHandleToRatio(e,t,n){const r=await this.resolveNativeAgeSlider();if(!r.ok||r.trackWidth<=0)return!1;const a="min"===e?r.minHandleX:r.maxHandleX,o="min"===e?r.minHandleY:r.maxHandleY,i=r.trackLeft+Math.max(0,Math.min(1,t))*r.trackWidth,s=r.trackTop+Math.max(1,r.trackHeight/2);return await this.dispatchNativeDrag(a,o,i,s,n),await Wo(Lo),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-Fo)/(jo-Fo)))}clampRatio(e,t,n){return Math.max(t,Math.min(n,e))}async setAgeHandleToNumber(e,t,n){const r=await this.readNativeAgeState(),a=r.minRatio??0,o=r.maxRatio??1;let i="min"===e?0:Math.min(1,a+zo),s="min"===e?Math.max(0,o-zo):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<qo;r+=1){if(!await this.dragAgeHandleToRatio(e,c,n))return!1;const r=await this.readNativeAgeState(),a="min"===e?r.ageMin:r.ageMax;if(a===t)return!0;void 0===a?"max"===e?s=c:i=c:a<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??Fo,r=e.ageMax,a=await this.resolveNativeAgeSlider();if(!a.ok)return{success:!1,error:a.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===r){if(void 0!==(await this.readNativeAgeState()).ageMax&&!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法设置为不限"}}else if(!await this.setAgeHandleToNumber("max",r,t))return{success:!1,error:`年龄上限无法设置为 ${r}`};const o=await this.readNativeAgeState();if(!this.isDesiredAgeState(o,n,r)){const e=void 0===o.ageMax?"不限":String(o.ageMax);return{success:!1,error:`年龄筛选未精确生效,当前为 ${o.ageMin??"未知"}-${e}`}}return{success:!0,state:o}}async readSelectedRecommendOptionTexts(e,t){const n=`(() => {\n const panelSelector = ${JSON.stringify(Ga.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(Vo)};\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 .filter((text) => text !== "" && text !== rowLabel);\n return selected.length > 0 ? Array.from(new Set(selected)) : fallback;\n })()`,r=await this.evaluateJson(n).catch(()=>[]);if(r.length>0)return r;const a=await this.evaluateRecommendFrameJson(n);return Array.isArray(a)&&a.length>0?a:t}async readNativeAppliedFilterState(e,t,n){const r=await Promise.all(Qa.map(async t=>{const n=e.optionSelections.find(e=>e.fieldKey===t.key),r=n?.values??[];return{fieldKey:t.key,label:t.label,values:await this.readSelectedRecommendOptionTexts(t.label,r)}})),a=r.find(e=>"gender"===e.fieldKey)?.values[0],o=r.find(e=>"activity"===e.fieldKey)?.values[0];return{...void 0!==t.ageMin?{ageMin:t.ageMin}:{},...void 0!==t.ageMax?{ageMax:t.ageMax}:{},...void 0!==n?{location:n}:{},optionSelections:r,...void 0!==a?{gender:a}:{},...void 0!==o?{activity:o}:{}}}async clickRecommendFilterClear(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Ga.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 })()`);return!!await this.dispatchNativeClick(t,e)&&(await Wo(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Ga.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 Wo(600),!0;await Wo(Io)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Ga.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 ri(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(vo)}));\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),r=null!==t&&(e.conversationId.length>0&&t.conversationId===e.conversationId||0===e.conversationId.length&&e.candidateId.length>0&&t.candidateId===e.candidateId),a=0===e.name.length||null!==n&&Qo(e.name,n.candidateName);if(r&&a)return{ready:!0};await Wo(Io)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:Ai(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=pi(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(vo)}));\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(Ga.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Co)};\n const parseCandidateProfileTokens = ${Ho.toString()};\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(xo)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(So)}));\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 const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n 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=oi(await this.evaluateJson(e));if(t.length>0)return t;return oi(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=Ka(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(Ga.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Co)};\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 })()`,r=Ri(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=Ri(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=Ka(e);await cn(this);const a=pi(await this.evaluateJson(`(() => {\n const nativeWheelTarget = true;\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(Ga.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(!a.found)return;await this.mouse.moveTo(Ko(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??$o):n??$o}),await Wo(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 r=Ka(e),a=`(() => {\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(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(Ga.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Co)};\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=Ri(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=Ri(await this.evaluateRecommendFrameJson(a));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)}}},Pi=["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","resume-canvas","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],Ti=[...Pi,"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"],Bi=qa.enum(["localStorage","sessionStorage"]),Di=qa.enum(["array","object","string","number","boolean","null"]),Oi=qa.enum(["empty","json","string"]),$i=qa.enum(Pi),Fi=qa.enum(Ti),ji=qa.enum(["request","response"]),Li=qa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),qi=qa.object({area:Bi,key:qa.string(),valueLength:qa.number().int().nonnegative(),valueKind:Oi,jsonKind:Di.optional(),jsonTopLevelKeys:qa.array(qa.string()).optional(),jsonArrayLength:qa.number().int().nonnegative().optional(),numericFields:qa.record(qa.number()).optional(),booleanFields:qa.record(qa.boolean()).optional(),arrayLengths:qa.record(qa.number().int().nonnegative()).optional()}),zi=qa.object({name:qa.string(),domain:qa.string(),path:qa.string(),expires:qa.string(),valueLength:qa.number().int().nonnegative(),httpOnly:qa.boolean(),secure:qa.boolean(),sameSite:qa.string().optional()}),Vi=qa.object({before:qa.number().optional(),after:qa.number().optional(),delta:qa.number().optional()}),Ji=qa.object({before:qa.boolean().optional(),after:qa.boolean().optional()}),Ui=qa.object({before:qa.number().int().nonnegative().optional(),after:qa.number().int().nonnegative().optional(),delta:qa.number().int().optional()}),Hi=qa.object({area:Bi,key:qa.string(),beforePresent:qa.boolean(),afterPresent:qa.boolean(),numericDeltas:qa.record(Vi).optional(),booleanChanges:qa.record(Ji).optional(),arrayLengthDeltas:qa.record(Ui).optional()}),Wi=qa.object({url:qa.string(),title:qa.string(),visibilityState:qa.string(),hasFocus:qa.boolean()}),Ki=qa.object({navigatorWebdriver:qa.boolean().optional(),userAgentContainsHeadless:qa.boolean(),languagesLength:qa.number().int().nonnegative(),pluginsLength:qa.number().int().nonnegative(),hasChromeRuntime:qa.boolean(),permissionQueryIsNative:qa.boolean().optional(),hasPlaywrightBinding:qa.boolean(),hasPwInitScripts:qa.boolean(),cdcKeys:qa.array(qa.string()),webdriverKeys:qa.array(qa.string()),automationLikeWindowKeys:qa.array(qa.string())}),Gi=qa.object({rootNodeId:qa.number().int(),rootNodeName:qa.string(),childNodeCount:qa.number().int().nonnegative().optional(),bodyTextLength:qa.number().int().nonnegative().optional(),elementCount:qa.number().int().nonnegative().optional()}),Yi=qa.object({type:qa.literal("mouseMoved"),x:qa.number(),y:qa.number()}),Xi=qa.object({targetId:qa.string(),websocketUrlAvailable:qa.boolean(),connected:qa.boolean(),pageBroughtToFront:qa.boolean().optional(),runtimeEnabled:qa.boolean().optional(),evaluate:Wi.optional(),dom:Gi.optional(),input:Yi.optional()}),Zi=qa.object({phase:$i,success:qa.boolean(),durationMs:qa.number().int().nonnegative(),error:qa.string().optional()}),Qi=qa.object({phase:Fi,capturedAt:qa.string(),targetFound:qa.boolean(),page:La.optional(),urlChangedFromPrevious:qa.boolean(),titleChangedFromPrevious:qa.boolean(),previousUrl:qa.string().optional(),currentUrl:qa.string().optional(),previousTitle:qa.string().optional(),currentTitle:qa.string().optional()}),es=qa.object({kind:ji,reason:Li,capturedAt:qa.string(),url:qa.string(),method:qa.string().optional(),resourceType:qa.string().optional(),status:qa.number().int().optional()}),ts=qa.object({capturedAt:qa.string(),url:qa.string()}),ns=qa.object({phase:$i.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;resume-canvas 只读探测简历弹窗与 canvas 就绪状态和坐标;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:qa.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:qa.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:qa.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),rs=qa.object({success:qa.boolean(),requestedPhase:$i,mode:qa.string(),nativePages:qa.array(La),targetPage:La.optional(),browserAttached:qa.boolean(),pageAttached:qa.boolean(),nativeTimeline:qa.array(Qi),networkEvents:qa.array(es).optional(),navigationEvents:qa.array(ts).optional(),nativeCdp:Xi.optional(),resumeCanvas:qa.object({dialogVisible:qa.boolean(),iframeFound:qa.boolean(),iframeVisible:qa.boolean(),canvasReady:qa.boolean(),screenshotArea:qa.object({x:qa.number(),y:qa.number(),width:qa.number(),height:qa.number()}).optional(),canvasSize:qa.object({width:qa.number(),height:qa.number()}).optional(),error:qa.string().optional()}).optional(),evaluate:Wi.optional(),detectorFingerprint:Ki.optional(),storage:qa.object({localStorage:qa.array(qi),sessionStorage:qa.array(qi),cookies:qa.array(zi),counterDiffs:qa.array(Hi)}).optional(),phases:qa.array(Zi),warnings:qa.array(qa.string())}),as=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],os=new Set(as),is=["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"],ss=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],cs={"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 ls(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 ds(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function us(e){return is.includes(e)}function ms(e){return ss.includes(e)}function ps(e){try{return JSON.parse(e)}catch{return}}function gs(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])),r=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(r).length>0?{arrayLengths:r}:{}}}function fs(e){return new Promise(t=>{setTimeout(t,e)})}function hs(e){return os.has(e)}function ys(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=ps(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=ls(n),a=ds(n)&&hs(t.key)?gs(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,...ds(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function bs(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 vs(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 ws(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?Tt(r.url,"zhipin")?r:void n.push(`targetPageId "${t}" is not a zhipin page.`):void n.push(`targetPageId "${t}" not found in native pages.`)}const r=e.filter(e=>Tt(e.url,"zhipin"));if(1===r.length)return r[0];0===r.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 xs(e,t,n,r){const a=n?.url,o=n?.title,i=r?.currentUrl,s=r?.currentTitle;return{phase:t,capturedAt:(new Date).toISOString(),targetFound:void 0!==n,...void 0!==n?{page:Dt(e,n)}:{},urlChangedFromPrevious:void 0!==i&&void 0!==a&&i!==a,titleChangedFromPrevious:void 0!==s&&void 0!==o&&s!==o,...void 0!==i?{previousUrl:i}:{},...void 0!==a?{currentUrl:a}:{},...void 0!==s?{previousTitle:s}:{},...void 0!==o?{currentTitle:o}:{}}}async function Ss(e,t,n,r){const a=await e.listNativePages();return xs(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>Tt(e.url,"zhipin"))[0],r)}async function Rs(e,t,n,r,a){try{const o=t[t.length-1],i=await Ss(e,n,r,o);t.push(i),i.urlChangedFromPrevious&&a.push(`Target URL changed after ${n}: ${i.previousUrl??"(unknown)"} -> ${i.currentUrl??"(missing)"}.`)}catch(e){a.push(`Failed to capture native snapshot after ${n}: ${e instanceof Error?e.message:String(e)}.`)}}async function Cs(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await Rs(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await fs(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Is(e){if(!ds(e))throw new Error("Native CDP evaluate did not return an object value.");const t=e.url,n=e.title,r=e.visibilityState,a=e.hasFocus;if("string"!=typeof t||"string"!=typeof n||"string"!=typeof r||"boolean"!=typeof a)throw new Error("Native CDP evaluate returned an unexpected page summary shape.");return{url:t,title:n,visibilityState:r,hasFocus:a}}function ks(e,t){if(!ds(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!ds(n))throw new Error("Native CDP DOM.getDocument did not return a root node.");const r=n.nodeId,a=n.nodeName,o=n.childNodeCount;if("number"!=typeof r||"string"!=typeof a)throw new Error("Native CDP DOM root node has an unexpected shape.");const i=ds(t)?t:{},s=i.bodyTextLength,c=i.elementCount;return{rootNodeId:r,rootNodeName:a,..."number"==typeof o?{childNodeCount:o}:{},..."number"==typeof s?{bodyTextLength:s}:{},..."number"==typeof c?{elementCount:c}:{}}}async function As(e){return Is(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Es(e){return ks(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 _s(e){return ks(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Ns(e){return await e.bringToFront(),!0}async function Ms(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 Ps(e,t,n,r,a,o){await Rs(e,t,n,r,o);const i=await Cs(e,t,cs[n],r,a,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 Ts(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await vs(e,async()=>await Ns(t));return n.push(a.phaseResult),void 0!==a.result&&(r.pageBroughtToFront=a.result),a.phaseResult}case"native-evaluate-url-no-runtime-enable":{const a=await vs(e,async()=>await As(t));return n.push(a.phaseResult),void 0!==a.result&&(r.evaluate=a.result),a.phaseResult}case"native-dom-read-no-runtime-enable":{const a=await vs(e,async()=>await _s(t));return n.push(a.phaseResult),void 0!==a.result&&(r.dom=a.result),a.phaseResult}case"native-input-move-no-runtime-enable":{const a=await vs(e,async()=>await Ms(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function Bs(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 r;try{const a=await vs("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(a.phaseResult),r=a.result,t.connected=a.phaseResult.success,!a.phaseResult.success||void 0===r)return{summary:t,triggeredNavigation:!1};const o=r;let i=await Ps(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(ms(e.requestedPhase))return await Ts(e.requestedPhase,o,e.phases,t),i=await Ps(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await vs("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await Ps(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 vs("native-evaluate-url",async()=>await As(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await Ps(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 vs("native-dom-read",async()=>await Es(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await Ps(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function Ds(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 Os(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=Ds(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=Ds(t);if(void 0===n)return;const r=e.request();o({kind:"response",reason:n,capturedAt:(new Date).toISOString(),url:t,method:r.method(),resourceType:r.resourceType(),status:e.status()})},c=e=>{a.push({capturedAt:(new Date).toISOString(),url:e.url()})};e.on("request",i),e.on("response",s),e.on("framenavigated",c);try{await fs(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function $s(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Tt(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function Fs(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function js(e,t){return await e.evaluate(e=>{const t="localStorage"===e?window.localStorage:window.sessionStorage;return Array.from({length:t.length},(e,n)=>{const r=t.key(n)??"";return{key:r,value:r.length>0?t.getItem(r)??"":""}}).filter(e=>e.key.length>0)},t)}async function Ls(e){return(await e.evaluate(e=>{const t=["localStorage","sessionStorage"],n=[];for(const r of t){const t="localStorage"===r?window.localStorage:window.sessionStorage;for(const a of e){const e=t.getItem(a);null!==e&&n.push({area:r,key:a,value:e})}}return n},as)).map(e=>ys(e.area,{key:e.key,value:e.value}))}async function qs(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function zs(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),r=t.filter(e=>e.toLowerCase().includes("webdriver")).slice(0,20),a=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:r,automationLikeWindowKeys:a}})}function Vs(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function Js(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function Us(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function Hs(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function Ws(e,t){const n=Vs(e),r=Vs(t),a=[...new Set([...n.keys(),...r.keys()])],o=[];for(const e of a){const t=n.get(e),a=r.get(e),i=a??t;if(!i)continue;const s={};for(const e of Js(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=Us(n,r))}const c={};for(const e of Js(t?.booleanFields,a?.booleanFields)){const n=t?.booleanFields?.[e],r=a?.booleanFields?.[e];n!==r&&(c[e]={...void 0!==n?{before:n}:{},...void 0!==r?{after:r}:{}})}const l={};for(const e of Js(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=Hs(n,r))}void 0!==t&&void 0!==a&&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!==a,...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 Ks(e,t){const[n,r,a]=await Promise.all([js(e,"localStorage"),js(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>hs(e.key)).map(e=>ys("localStorage",e)),...r.filter(e=>hs(e.key)).map(e=>ys("sessionStorage",e))];return{localStorage:n.map(e=>ys("localStorage",e)),sessionStorage:r.map(e=>ys("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>bs(e)),counterDiffs:Ws(t,o)}}var Gs=ja({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:ns,output:rs,execute:async(e,t)=>{const n=e.phase??"native",r=e.watchMs??3e3,a=e.networkEventLimit??30,o=ue(),i=de(),s=[],c=[],l=[];let d,u,m,p=!1,g=!1,f=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const h=await vs("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>Dt(o,e)),v=ws(y,e.targetPageId,s);if(v&&(d=Dt(o,v),f=v.targetId,l.push(xs(o,"native",v,void 0))),!h.phaseResult.success||"native"===n)return{success:h.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Cs(o,l,"native-watch",f,r,s),{success:h.phaseResult.success&&void 0!==f,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(!v)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(us(n)){const e=await Bs({requestedPhase:n,target:v,runtime:i,ctxManager:o,targetPageId:f,watchMs:r,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}if("resume-canvas"===n){const e=await vs("resume-canvas",async()=>{const e=await Ni();try{const t=await e.waitForResumeDialog(1e3);return{dialogState:t,geometry:t.canvasReady?await e.readResumeCanvasGeometry():void 0}}finally{e.close()}});c.push(e.phaseResult);const t=e.result;return{success:e.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==t?{resumeCanvas:{dialogVisible:t.dialogState.dialogVisible,iframeFound:t.dialogState.iframeFound,iframeVisible:t.dialogState.iframeVisible,canvasReady:t.dialogState.canvasReady,...void 0!==t.geometry?.screenshotArea?{screenshotArea:t.geometry.screenshotArea}:{},...void 0!==t.geometry?.canvasSize?{canvasSize:t.geometry.canvasSize}:{},...void 0!==t.geometry?.error?{error:t.geometry.error}:{}}}:{},phases:c,warnings:s}}const w=await vs("browser-attach",async()=>await i.getBrowser());c.push(w.phaseResult);const x=w.result;p=w.phaseResult.success,await Rs(o,l,"browser-attach",f,s);const S=await Cs(o,l,"browser-attach-watch",f,r,s);if(S&&s.push("Browser attach was followed by a native URL change; treat this account/browser profile as unsafe for Playwright-backed zhipin tools."),!w.phaseResult.success||"browser-attach"===n)return{success:c.every(e=>e.success)&&!S,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(S)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};const R=[];let C;"network-watch"===n&&void 0!==x&&(C=$s(x,v),C?R.push(Os(C,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",v);const I=await vs("page-attach",async()=>await o.getPage("zhipin"));c.push(I.phaseResult);const k=I.result;if(g=I.phaseResult.success,k&&(d=await Ot(o,k)),await Rs(o,l,"page-attach",f,s),!I.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await vs("network-watch",async()=>Fs(await Promise.all(R),a));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:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,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:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){C!==k&&R.push(Os(k,r,a));const e=await vs("network-watch",async()=>Fs(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await Rs(o,l,"network-watch",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let A=[];if("storage-summary"===n){try{A=await Ls(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await fs(r)}const E=await vs("page-evaluate",async()=>await qs(k));c.push(E.phaseResult);const _=E.result;if(await Rs(o,l,"page-evaluate",f,s),!E.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},phases:c,warnings:s};const N=await vs("detector-fingerprint",async()=>await zs(k));c.push(N.phaseResult);const M=N.result;if(await Rs(o,l,"detector-fingerprint",f,s),!N.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await vs("storage-summary",async()=>await Ks(k,A));c.push(P.phaseResult);const T=P.result;return await Rs(o,l,"storage-summary",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as Ys}from"@roll-agent/sdk";import{z as Xs}from"zod";import{createHash as Zs}from"node:crypto";var Qs,ec,tc={fetch:fetch,env:process.env},nc="https://huajune.duliday.com",rc=new Set;function ac(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function oc(e,t){const n=ac(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 ic(e){const t=oc(e,"RECRUITMENT_EVENTS_ENABLED"),n=ac(e,"RECRUITMENT_EVENTS_API_BASE_URL")??nc,r=ac(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=ac(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function sc(e,t){const n=ye(e.defaultAgentId);if(void 0!==n)return n;he()&&cc(t,"missing-instance-tracking","Recruitment event skipped: select a browserInstance, configure tracking-agent-id on the active browser instance, or set RECRUITMENT_EVENTS_DEFAULT_AGENT_ID.")}function cc(e,t,n){rc.has(t)||(rc.add(t),e.warn(n))}function lc(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function dc(e){return"object"==typeof e&&null!==e}function uc(e,t){const n=e[t];return"string"==typeof n?n:void 0}function mc(e){if(!dc(e)||!dc(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>dc(e)&&"error"===uc(e,"status"));if(!dc(t))return;const n=uc(t,"idempotencyKey"),r=t.error,a=dc(r)?uc(r,"code"):void 0,o=dc(r)?uc(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function pc(e,t,n){const r=ic(n.env);if(!r.enabled)return;const a=sc(r,t);if(void 0===r.apiToken)return void cc(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(he()||cc(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_DEFAULT_AGENT_ID."));const o={events:[{...e,agentId:a,eventTime:e.eventTime??(new Date).toISOString()}]},i=await n.fetch(lc(r.apiBaseUrl),{method:"POST",headers:{Authorization:`Bearer ${r.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=mc(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function gc(e,t){return`${e}:${Zs("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function fc(e,t){(async()=>{const n=ec??(async(e,t)=>{await pc(e,t,{...tc,...Qs})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function hc(){return(new Date).toISOString().slice(0,10)}function yc(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function bc(e,t,n){const r=e?.candidateInfo,a=yc(r?.age),o=yc(r?.education),i=yc(r?.expectedSalary);return{name:yc(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function vc(e){return yc(e?.candidateInfo.communicationPosition)??""}function wc(e){const t=yc(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 xc(e){const t=yc(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?yc(n[0]):void 0}function Sc(e){return e.length>0?{job:{jobName:e}}:{}}function Rc(e,t){return e.trim().length>0&&t.trim().length>0}function Cc(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 Ic(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&Rc(n.name,n.position)&&fc({idempotencyKey:gc("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,hc()]),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 kc(e,t){const n=vc(e.candidateDetails),r=n,a=bc(e.candidateDetails,e.candidateName,r);if(!Rc(a.name,a.position))return;const o=xc(e.candidateDetails);fc({idempotencyKey:gc("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...Sc(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Ac(e,t){if(!e.clicked||0===e.name.length)return;const n=yc(e.expectedPosition);if(void 0===n||!Rc(e.name,n))return;const r=yc(e.age),a=yc(e.education),o=yc(e.expectedSalary),i=yc(e.expectedLocation);fc({idempotencyKey:gc("zhipin-candidate-contacted",[e.candidateId,e.name,hc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"candidate_contacted",candidate:{name:e.name,position:n,...void 0!==r?{age:r}:{},...void 0!==a?{education:a}:{},...void 0!==o?{expectedSalary:o}:{},...void 0!==i?{expectedLocation:i}:{}},details:{candidateId:e.candidateId}},t)}function Ec(e,t){const n=vc(e.candidateDetails),r=bc(e.candidateDetails,e.candidateName,n);if(!Rc(r.name,r.position))return;const a=xc(e.candidateDetails);fc({idempotencyKey:gc("zhipin-wechat-requested",[e.conversationId,e.candidateId,hc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...Sc(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 _c(e,t,n,r){const a=vc(e),o=wc(e),i=xc(e),s=bc(e,e.candidateInfo.name,o);if(!Rc(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=Cc(c);fc({idempotencyKey:gc("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...Sc(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var Nc,Mc=Xs.object({name:Xs.string(),conversationId:Xs.string(),candidateId:Xs.string(),position:Xs.string(),time:Xs.string(),preview:Xs.string(),unreadCount:Xs.number(),hasUnread:Xs.boolean(),index:Xs.number()}),Pc=Xs.object({success:Xs.boolean(),candidates:Xs.array(Mc),total:Xs.number(),stats:Xs.object({withName:Xs.number(),withUnread:Xs.number()})});function Tc(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...Nc}}function Bc(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 Dc(e){const t=new Set,n=[];for(const r of e){const e=Bc(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var Oc=Ys({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:Xs.object({limit:Xs.number().optional().describe("最多返回条数"),onlyUnread:Xs.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:Xs.enum(["time","unreadCount","name"]).default("time"),autoScroll:Xs.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:Xs.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Pc,execute:async(e,t)=>{const n=e.onlyUnread??!1;let r,a;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const o=Tc();try{r=await o.openNativePagePort({requireChatPage:!0}),a=o.createNativeVisualActivitySession(r),await a.begin("正在读取消息列表");if(!await r.waitForSelector('.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',5e3))return await a.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};const i=n?"正在读取未读消息列表":"正在读取消息列表";await a.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=Dc(await r.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 a.succeed(n?`已读取 ${d.length} 条未读消息`:`已读取 ${d.length} 条消息`),t.logger.info(`Found ${d.length} candidates (${m.withUnread} with unread)`),Ic(d,t.logger),{success:!0,candidates:d,total:l.length,stats:m}}catch(e){return await(a?.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{r?.close()}}});import{BrowserActionApprovalSchema as $c,BrowserPageInfoSchema as Fc}from"@roll-agent/browser";import{defineTool as jc,StructuredToolError as Lc}from"@roll-agent/sdk";import{z as qc}from"zod";var zc,Vc=qc.object({forceReload:qc.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:qc.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:$c.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),Jc=qc.object({success:qc.boolean(),alreadyOnChat:qc.boolean(),usedSidebarClick:qc.boolean(),usedReload:qc.boolean(),chatReady:qc.boolean(),reloadSkippedReason:qc.enum(Uo).optional(),page:Fc.optional(),error:qc.string().optional()});function Uc(){return{getContextManager:ue,getRuntime:de,openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...zc}}async function Hc(e,t){return Dt(e,await t.inspectPage())}var Wc=jc({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:Vc,output:Jc,execute:async(e,t)=>{const n=Uc(),r=n.getContextManager();let a,o,i=!1;t.logger.info("Opening Boss chat page via native sidebar navigation");try{if(a=await n.openNativePagePort(!0===e.forceReload?{requireChatPage:!0}:{}),o=n.createNativeVisualActivitySession(a),await cn(a),!0===e.forceReload){const s=await a.inspectChatReloadTarget();if(!s.ok)return await o.fail("当前不是沟通页"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:!1,chatReady:!1,reloadSkippedReason:s.skippedReason,page:await Hc(r,a),error:s.error};rn(t,n.getRuntime(),{action:"navigate",target:s.url,url:s.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),await o.begin("正在刷新沟通页"),t.logger.info("Reloading Boss chat page via native CDP Page.reload"),await a.reload({url:s.url,onReloadSent:()=>{i=!0}});return await a.waitForChatListReady({...void 0!==e.expectedConversationId?{expectedConversationId:e.expectedConversationId}:{}})?(await o.succeed("已刷新沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await Hc(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await Hc(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(Ga.nav.sidebar,{label:e,padding:10})}if(await a.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await Hc(r,a)};if(!await a.clickSidebarSection("chat",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await Hc(r,a),error:"未找到沟通导航"};t.logger.info("Clicked Boss sidebar nav: 沟通");return await a.waitForChatSurface()?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!0,page:await Hc(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await Hc(r,a),error:"沟通页未就绪"})}catch(e){if(e instanceof Lc)throw await(o?.fail("沟通页操作未获授权")),e;return await(o?.fail("切换沟通页失败")),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,error:e instanceof Error?e.message:"切换沟通页失败"}}finally{a?.close()}}});import{defineTool as Kc}from"@roll-agent/sdk";import{z as Gc}from"zod";var Yc,Xc=Gc.object({success:Gc.boolean(),conversationId:Gc.string(),candidateId:Gc.string(),candidateName:Gc.string(),index:Gc.number(),hasUnread:Gc.boolean(),unreadCount:Gc.number(),lastMessageTime:Gc.string(),messagePreview:Gc.string(),error:Gc.string().optional()});function Zc(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...Yc}}var Qc=Kc({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Gc.object({conversationId:Gc.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Gc.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Gc.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Gc.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Xc,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Zc();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开目标聊天"),await a.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:"正在定位候选人",padding:8});const o=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,preferUnread:e.preferUnread??!1,...void 0!==a?{motionObserver:a}:{}});return o.found?(await a.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 a.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(a?.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{r?.close()}}});import{defineTool as el}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as tl,prepareReplyContext as nl,ReplyAuthorityRequestError as rl}from"@roll-agent/reply-authority-client";import{z as al}from"zod";var ol="·",il=/[--—–]/,sl=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function cl(e){return(e??"").trim()}function ll(e){return e.some(e=>"candidate_expected_location"!==e.source)}function dl(e){const t=ll(e)?e.filter(e=>"candidate_expected_location"!==e.source):e,n=t.slice(0,3).map(e=>{const t="candidate_expected_location"===e.source?"(弱)":"";return`${e.text}${t}`}),r=t.length>3?` 等${String(t.length)}项`:"";return`${n.join("、")}${r}`}function ul(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function ml(e){const t=ul(e)?e.signals:e,n=ul(e)?e.analysisPath:void 0,r=ul(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=dl(t);return!ll(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function pl(e){const t=sl.exec(cl(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function gl(e){const t=cl(e);if(!t||sl.test(t))return;if(!il.test(t))return;const[n=""]=t.split(il);return cl(n)||void 0}function fl(e){const t=cl(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(ol).map(e=>cl(e));return{expectedLocation:n,expectedPosition:r}}function hl(e){const t=cl(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=fl(e.expectedJobText),a=gl(t),o=pl(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var yl,bl=al.object({index:al.number(),sender:al.enum(["candidate","recruiter","system"]),messageType:al.enum(["text","system","resume","wechat-exchange"]),content:al.string(),time:al.string()}),vl=al.object({name:al.string(),age:al.string(),experience:al.string(),education:al.string(),communicationPosition:al.string(),expectedPosition:al.string(),expectedLocation:al.string(),expectedSalary:al.string(),tags:al.array(al.string())}),wl=al.object({success:al.boolean(),conversationId:al.string(),candidateId:al.string(),candidateInfo:vl,preferredBrand:al.string().optional(),preferredBrandId:al.number().int().positive().optional(),locationSignals:al.array(tl).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:al.array(bl),formattedHistory:al.array(al.string()),stats:al.object({totalMessages:al.number(),candidateMessages:al.number(),recruiterMessages:al.number(),systemMessages:al.number()}),error:al.string().optional()});function xl(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function Sl(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:xl(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function Rl(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Cl(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),prepareReplyContext:nl,...yl}}function Il(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function kl(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Al(e){return{candidateMessage:kl(e.data.messages),conversationHistory:[...e.formattedHistory],candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==e.preferredBrand?{preferredBrand:e.preferredBrand}:{},...void 0!==e.preferredBrandId?{preferredBrandId:e.preferredBrandId}:{},target:{platform:"zhipin",conversationId:e.selectedTarget.conversationId,candidateId:e.selectedTarget.candidateId,recruiterUsername:e.recruiterUsername}}}var El=6e5,_l=0;function Nl(e){return e instanceof rl?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function Ml(e){Nl(e)&&(_l=Date.now()+El)}async function Pl(e){if(Date.now()<_l)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===kl(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=bo(await e.nativePage.readUsernameEvidence())}catch(t){return void e.logger.debug(`Skip Reply Authority prepare: recruiter username read failed: ${t instanceof Error?t.message:String(t)}`)}if(!t.found)return void e.logger.debug("Skip Reply Authority prepare: recruiter username was not detected.");const n=Al({data:e.data,selectedTarget:e.selectedTarget,recruiterUsername:t.username,formattedHistory:e.formattedHistory,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,preferredBrand:e.preferredBrand,preferredBrandId:e.preferredBrandId}),r=Date.now();try{e.deps.prepareReplyContext(n).then(t=>{e.logger.info(`Reply Authority prepare ${t.status} for ${e.selectedTarget.conversationId} in ${String(Date.now()-r)}ms`)}).catch(t=>{Ml(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){Ml(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var Tl=el({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:al.object({conversationId:al.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:al.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:al.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:al.number().default(100).describe("最多返回的消息条数")}),output:wl,execute:async(e,t)=>{const n=e.maxMessages??100,r=Cl(),a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i;const s=a?"正在打开目标聊天":"正在准备当前聊天",c="正在提取聊天记录",l=async(e,t)=>(await(i?.fail(e)),Sl(t));try{o=await r.openNativePagePort(),i=r.createNativeVisualActivitySession(o),await i.begin(s);const d=a?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||!Rl(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&&!Rl(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=hl({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=Il(g.messages),y={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(`已提取 ${y.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${y.totalMessages} messages`),_c(g,p.conversationId,p.candidateId,t.logger),await Pl({nativePage:o,deps:r,data:g,selectedTarget:p,formattedHistory:h,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,preferredBrand:f.preferredBrand,preferredBrandId:f.preferredBrandId,logger: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}:{},...void 0!==f.preferredBrandId?{preferredBrandId:f.preferredBrandId}:{},locationSignals:[],chatMessages:[...g.messages],formattedHistory:h,stats:y}}catch(e){return await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),Sl(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as Bl}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Dl,GenerateSignedReplyResponseSchema as Ol,ReasoningConfigSchema as $l,ReplyAuthorityRequestError as Fl,ReplyStreamFinalEventSchema as jl,ReplyStreamLocationResolvedEventSchema as Ll,streamGenerateSignedReply as ql}from"@roll-agent/reply-authority-client";import{z as zl}from"zod";import{ReplyGateAdvisoryCodeSchema as Vl}from"@roll-agent/reply-authority-client";import{z as Jl}from"zod";var Ul=["option_1","option_2"],Hl=["judge","orchestrator"],Wl=[...Hl,"service_recommended_fallback","explicit_no_judge"],Kl={RUBRIC_FETCH_FAILED:"rubric_fetch_failed",RUBRIC_MISMATCH:"rubric_mismatch",INVALID_VARIANT_SHAPE:"invalid_variant_shape",JUDGE_SAMPLING_FAILED:"judge_sampling_failed",JUDGE_OUTPUT_INVALID:"judge_output_invalid"},Gl=Jl.array(Vl).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),Yl=Jl.object({chosenOption:Jl.enum(Ul).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:Jl.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:Gl.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:Jl.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),Xl=Yl.omit({judgeModel:!0}).extend({confirmedFindingCodes:Gl.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as Zl}from"node:crypto";var Ql=new Map;function ed(e){for(const[t,n]of Ql)n.expiresAt<=e&&Ql.delete(t)}function td(e,t=Math.floor(Date.now()/1e3)){if(ed(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${Zl()}`,r={preparedReplyId:n,...e};return Ql.set(n,{...r,consumed:!1}),r}function nd(e,t=Math.floor(Date.now()/1e3)){const n=Ql.get(e);return void 0===n?(ed(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Ql.delete(e),ed(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Ql.set(e,{...n,consumed:!0}),{ok:!0,record:od(n)})}function rd(e,t=Math.floor(Date.now()/1e3)){const n=Ql.get(e);return void 0===n?(ed(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Ql.delete(e),ed(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:od(n)}}function ad(e,t,n=Math.floor(Date.now()/1e3)){const r=rd(e,n);if(!r.ok)return r;const a=Ql.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(Ql.set(e,{...a,judgement:t}),{ok:!0,record:od({...a,judgement:t})})}function od(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}:{},...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},...void 0!==e.variantGroup?{variantGroup:e.variantGroup}:{},...void 0!==e.judgement?{judgement:e.judgement}:{}}}import{CandidateInfoSchema as id}from"@roll-agent/reply-authority-client";import{z as sd}from"zod";var cd=1e3,ld=8,dd=500,ud=200,md=12,pd=100,gd=id.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),fd=sd.object({candidateMessage:sd.string().min(1).max(cd),recentConversation:sd.array(sd.string().min(1).max(dd)).max(ld),candidateInfo:gd.optional(),preferredBrand:sd.string().min(1).max(ud).optional()});function hd(e){return e.replace(/(?<!\d)1[3-9]\d{9}(?!\d)/g,"[手机号已隐藏]").replace(/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/g,"[邮箱已隐藏]").replace(/(微信|wechat|vx|v信)(?:号|是|[::]|\s)*[A-Za-z0-9][-_A-Za-z0-9]{5,19}/gi,"$1 [账号已隐藏]")}function yd(e,t){const n=hd(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function bd(e){if(void 0===e)return;const t={...void 0!==e.age?{age:yd(e.age,ud)}:{},...void 0!==e.experience?{experience:yd(e.experience,ud)}:{},...void 0!==e.education?{education:yd(e.education,ud)}:{},...void 0!==e.communicationPosition?{communicationPosition:yd(e.communicationPosition,ud)}:{},...void 0!==e.expectedPosition?{expectedPosition:yd(e.expectedPosition,ud)}:{},...void 0!==e.expectedLocation?{expectedLocation:yd(e.expectedLocation,ud)}:{},...void 0!==e.expectedSalary?{expectedSalary:yd(e.expectedSalary,ud)}:{},...void 0!==e.info?{info:e.info.map(e=>yd(e,pd)).filter(e=>e.length>0).slice(0,md)}:{}};return Object.keys(t).length>0?t:void 0}function vd(e){const t=yd(e.candidateMessage,cd);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>yd(e,dd)).filter(e=>e.length>0).slice(-ld),r=bd(e.candidateInfo),a=void 0!==e.preferredBrand?yd(e.preferredBrand,ud):void 0;return fd.parse({candidateMessage:t,recentConversation:n,...void 0!==r?{candidateInfo:r}:{},...void 0!==a&&a.length>0?{preferredBrand:a}:{}})}function wd(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 variantsId = "roll-agent-reply-preview-variants";\n const findingsId = "roll-agent-reply-preview-findings";\n const badgeId = "roll-agent-reply-preview-badge";\n const locationId = "roll-agent-reply-preview-location";\n const spinnerId = "roll-agent-reply-preview-spinner";\n const styleId = "roll-agent-reply-preview-style";\n const stateKey = "__rollAgentReplyPreviewState";\n const state = window[stateKey] || {};\n window[stateKey] = state;\n\n const cancelPendingRemove = () => {\n if (state.removeTimer) {\n window.clearTimeout(state.removeTimer);\n state.removeTimer = undefined;\n }\n };\n\n if (input.mode === "clear") {\n const existingRoot = document.getElementById(rootId);\n if (!existingRoot) return true;\n\n cancelPendingRemove();\n existingRoot.style.opacity = "0";\n existingRoot.style.transform = "translateY(8px) scale(0.98)";\n state.removeTimer = window.setTimeout(() => {\n if (document.getElementById(rootId) === existingRoot) {\n existingRoot.remove();\n }\n state.removeTimer = undefined;\n }, 240);\n return true;\n }\n\n const ensureRoot = () => {\n let root = document.getElementById(rootId);\n if (root) {\n cancelPendingRemove();\n return root;\n }\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 location = document.createElement("div");\n location.id = locationId;\n location.style.display = "none";\n location.style.flexWrap = "wrap";\n location.style.gap = "6px";\n location.style.fontSize = "11px";\n location.style.lineHeight = "16px";\n location.style.color = "#99F6E4";\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 const variants = document.createElement("div");\n variants.id = variantsId;\n variants.style.display = "none";\n variants.style.gridTemplateColumns = "repeat(2, minmax(0, 1fr))";\n variants.style.gap = "10px";\n variants.style.minHeight = "0";\n\n const findings = document.createElement("div");\n findings.id = findingsId;\n findings.style.display = "none";\n findings.style.flexDirection = "column";\n findings.style.gap = "4px";\n findings.style.paddingTop = "2px";\n findings.style.fontSize = "11px";\n findings.style.lineHeight = "16px";\n findings.style.color = "#CBD5E1";\n\n header.append(spinner, status);\n root.append(header, location, badge, draft, variants, findings);\n document.documentElement.append(root);\n return root;\n };\n\n const root = ensureRoot();\n const status = document.getElementById(statusId);\n const location = document.getElementById(locationId);\n const draft = document.getElementById(draftId);\n const variants = document.getElementById(variantsId);\n const findings = document.getElementById(findingsId);\n const badge = document.getElementById(badgeId);\n const spinner = document.getElementById(spinnerId);\n if (!status || !location || !draft || !variants || !findings || !badge || !spinner) return false;\n\n root.style.opacity = "1";\n root.style.transform = "translateY(0)";\n root.style.width = input.variantSelection\n ? "min(760px, calc(100vw - 40px))"\n : "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = input.variantSelection ? "58vh" : "42vh";\n\n if (typeof input.label === "string") {\n status.textContent = input.label;\n }\n\n if (typeof input.locationSummary === "string" && input.locationSummary.length > 0) {\n location.textContent = input.locationSummary;\n location.style.display = "flex";\n } else if (input.mode === "begin") {\n location.textContent = "";\n location.style.display = "none";\n }\n\n if (typeof input.draftText === "string") {\n draft.textContent = input.draftText;\n }\n\n const resetVariantSelection = () => {\n variants.replaceChildren();\n variants.style.display = "none";\n findings.replaceChildren();\n findings.style.display = "none";\n draft.style.display = "block";\n };\n\n const renderVariantSelection = (selection) => {\n variants.replaceChildren();\n findings.replaceChildren();\n draft.style.display = "none";\n variants.style.display = "grid";\n variants.style.gridTemplateColumns =\n window.innerWidth < 640 ? "minmax(0, 1fr)" : "repeat(2, minmax(0, 1fr))";\n\n for (const option of selection.options) {\n const card = document.createElement("div");\n card.style.minWidth = "0";\n card.style.maxHeight = "32vh";\n card.style.overflow = "hidden auto";\n card.style.padding = "10px";\n card.style.borderRadius = "8px";\n card.style.border = "1px solid rgba(148, 163, 184, 0.28)";\n card.style.background = "rgba(15, 23, 42, 0.66)";\n\n const title = document.createElement("div");\n title.textContent = option.option;\n title.style.marginBottom = "6px";\n title.style.fontSize = "12px";\n title.style.fontWeight = "700";\n title.style.lineHeight = "16px";\n title.style.color = "#A7F3D0";\n\n const body = document.createElement("div");\n body.textContent = option.suggestedReply;\n body.style.whiteSpace = "pre-wrap";\n body.style.fontSize = "13px";\n body.style.lineHeight = "20px";\n body.style.color = "#E2E8F0";\n\n card.append(title, body);\n variants.append(card);\n }\n\n if (selection.findings.length > 0) {\n findings.style.display = "flex";\n const title = document.createElement("div");\n title.textContent = "检查项";\n title.style.fontWeight = "700";\n title.style.color = "#FDE68A";\n findings.append(title);\n\n for (const finding of selection.findings) {\n const item = document.createElement("div");\n item.textContent = finding.description || finding.code;\n item.style.color = "#CBD5E1";\n findings.append(item);\n }\n } else {\n findings.style.display = "none";\n }\n };\n\n if (input.variantSelection && input.variantSelection.options.length > 0) {\n renderVariantSelection(input.variantSelection);\n } else if (\n input.mode === "begin" ||\n input.mode === "draft" ||\n input.mode === "final" ||\n input.mode === "complete" ||\n input.mode === "fail"\n ) {\n resetVariantSelection();\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 xd,Sd=class{target;constructor(e){this.target=e}async begin(e,t){return await this.render({mode:"begin",label:e,...void 0!==t&&t.length>0?{locationSummary:t}:{},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,n){return await this.render({mode:"complete",label:e,draftText:t,provisional:!1,...void 0!==n?{variantSelection:n}:{}})}async fail(e){return await this.render({mode:"fail",label:e})}async clear(){return await this.render({mode:"clear"})}async render(e){if(!je())return!1;try{return await this.target.evaluateJson(wd(e))}catch{return!1}}},Rd=zl.object({conversationId:zl.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:zl.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:zl.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:zl.number().default(100).describe("最多读取的聊天消息条数"),reasoning:$l.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),Cd=["rejected","timeout","server_error"],Id=zl.object({requestId:zl.string().optional(),elapsedMs:zl.number().int().min(0).optional(),clientTimeoutMs:zl.number().int().positive().optional(),lastStartedPhase:zl.string().min(1).optional(),activePhase:zl.string().min(1).optional(),phaseLatencies:zl.record(zl.number().int().min(0)).optional()}),kd=zl.object({success:zl.boolean(),preparedReplyId:zl.string().optional(),suggestedReply:zl.string().optional(),stage:zl.string().optional(),confidence:zl.number().optional(),expiresAt:zl.number().optional(),timing:zl.object({totalLatencyMs:zl.number().int().min(0),replyLatencyMs:zl.number().int().min(0).optional(),turnPlanningLatencyMs:zl.number().int().min(0).optional(),contextBuildingLatencyMs:zl.number().int().min(0).optional(),preparedContextHit:zl.boolean()}).optional(),gateRewritten:zl.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:zl.object({groupId:zl.string().min(1),options:zl.array(zl.object({option:zl.enum(Ul),suggestedReply:zl.string(),expiresAt:zl.number().int().min(0)})).min(2).max(2),findings:zl.array(zl.object({code:zl.string().min(1),description:zl.string().min(1)})),rubricVersion:zl.string().min(1),rubricHash:zl.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:zl.string().optional(),errorKind:zl.enum(Cd).optional()}).extend(Id.shape),Ad={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function Ed(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),createReplyPreviewVisualSession:e=>new Sd(e),streamGenerateSignedReply:ql,fetchReplyFeedbackRubric:Dl,savePreparedReply:td,random:Math.random,...xd}}function _d(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Nd(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function Md(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function Pd(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function Td(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Bd(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Dd(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function Od(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function $d(e){const t=Od(e.navigationUnreadCount);return t>0?t:"candidate"===Dd(e.data.messages)?.sender?1:0}function Fd(e){const t=hl({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:Bd(e.data.messages),conversationHistory:Td(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!==t.preferredBrandId?{preferredBrandId:t.preferredBrandId}:{},...void 0!==e.reasoning?{modelConfig:{reasoning:e.reasoning}}:{},target:{platform:"zhipin",conversationId:e.conversationId,candidateId:e.candidateId,recruiterUsername:e.recruiterUsername}}}function jd(e){return"phase.started"===e.type?Nd(e,"label")??Ad[Nd(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Nd(e,"toolName")?`: ${Nd(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Nd(e,"toolName")?`: ${Nd(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function Ld(e,t,n={}){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{},...n}}function qd(e,t){const n=t instanceof Fl?t:void 0,r=e.requestId??n?.meta.requestId,a=Object.fromEntries(e.phaseLatencies);return{...void 0!==r?{requestId:r}:{},...void 0!==e.startedAtMs?{elapsedMs:Math.max(0,Math.round(Date.now()-e.startedAtMs))}:{},...void 0!==n?{clientTimeoutMs:n.meta.timeoutMs}:{},...void 0!==e.lastStartedPhase?{lastStartedPhase:e.lastStartedPhase}:{},...void 0!==e.activePhase?{activePhase:e.activePhase}:{},...Object.keys(a).length>0?{phaseLatencies:a}:{}}}function zd(e){return e.replace(/\s+\(url=.*\)$/,"")}function Vd(e){const t=e instanceof Error?e.message:String(e);if(e instanceof Fl){const n=zd(t);if(422===e.statusCode)return{message:`回复未通过事实核验:${n}`,errorKind:"rejected"};if(504===e.statusCode||n.includes("请求超时"))return{message:`AI 响应超时:${n}`,errorKind:"timeout"};if(void 0!==e.statusCode&&e.statusCode>=500)return{message:`Reply Authority 服务端异常:${n}`,errorKind:"server_error"}}return{message:t}}function Jd(e){return e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(e<1e4?1:0)}s`}function Ud(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function Hd(e){const t=["回复已生成",`总 ${Jd(e.totalLatencyMs)}`];return void 0!==e.replyLatencyMs&&t.push(`生成 ${Jd(e.replyLatencyMs)}`),e.preparedContextHit&&t.push("预热命中"),e.gateRewritten&&t.push("终稿经安全门调整"),t.join(" · ")}function Wd(e,t=void 0,n=!1){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}:{},...void 0!==t?{timing:t}:{},...n?{gateRewritten:n}:{},..."judge_ready"===e.variantGroup?.state?{replyVariantSelection:{groupId:e.variantGroup.groupId,options:e.variantGroup.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply,expiresAt:e.envelopeExp})),findings:e.variantGroup.findings.map(e=>({code:e.code,description:e.description})),rubricVersion:e.variantGroup.rubricVersion,rubricHash:e.variantGroup.rubricHash}}:{}}}function Kd(e,t){const n=e.items[0],r=e.items[1];return void 0===n||void 0===r?e.items:t()<.5?[r,n]:[n,r]}function Gd(e){return{state:"not_learned",groupId:e.replyVariants.groupId,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},chosenVariant:"draft",reason:e.reason}}function Yd(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return Gd({...e,reason:Kl.RUBRIC_MISMATCH});const t=Kd(e.replyVariants,e.random);if(2!==t.length)return Gd({...e,reason:Kl.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return Gd({...e,reason:Kl.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:Ul[t]??"option_1",variant:e.variant,suggestedReply:e.suggestedReply,signedEnvelope:e.signedEnvelope,envelopeExp:e.envelopeExp})),a=r.find(t=>t.variant===e.replyVariants.recommended)?.option??"option_1";return{state:"judge_ready",groupId:e.replyVariants.groupId,options:r,findings:e.replyVariants.findings,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},recommendedOption:a,judgeContext:e.judgeContext}}var Xd=Bl({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:Rd,output:kd,execute:async(e,t)=>{const n=Ed(),r=e.maxMessages??100,a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;const c={startedAtMs:void 0,requestId:void 0,lastStartedPhase:void 0,activePhase:void 0,phaseLatencies:new Map};try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await cn(o),await i.begin(a?"正在打开目标聊天":"正在准备当前聊天");const l=a?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==l&&!l.found){const e=l.error??"打开聊天失败";return await i.fail(e),Ld(e)}if(void 0===l&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),Ld(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const d=l?.name??e.candidateName??"",u=await o.readActiveChatPanel();if(d.length>0&&(!u||!_d(d,u.candidateName))){const e=`右侧聊天面板未切换到 ${d}`;return await i.fail("聊天面板未同步"),Ld(e)}const m=await o.readSelectedChatTarget();if(!m){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),Ld(e)}if(null!==u&&m.candidateName.length>0&&!_d(m.candidateName,u.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`;return await i.fail("当前会话不一致"),Ld(e)}if(!(void 0===l||l.conversationId.length>0&&m.conversationId===l.conversationId||0===l.conversationId.length&&l.candidateId.length>0&&m.candidateId===l.candidateId)){const e=`当前选中会话与目标会话不一致: ${l?.name||l?.conversationId||""}`;return await i.fail("当前会话不一致"),Ld(e)}await o.waitForChatMessages();const p=await o.readCandidateChatDetails(r);if(null!==p.selectedTarget&&(p.selectedTarget.conversationId!==m.conversationId||p.selectedTarget.candidateId!==m.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${p.selectedTarget.conversationId}/${p.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),Ld(e)}if(0===Bd(p.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),Ld(e)}const g=bo(await o.readUsernameEvidence());if(!g.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),Ld(e)}const f=Fd({data:p,conversationId:m.conversationId,candidateId:m.candidateId,recruiterUsername:g.username,reasoning:e.reasoning}),h=$d({navigationUnreadCount:l?.unreadCount,data:p});t.logger.info(`Generating reply preview for ${m.candidateName||m.candidateId}`);const y=Date.now();c.startedAtMs=y,await s.begin("正在生成回复");let b,v,w,x="",S=!1;for await(const e of n.streamGenerateSignedReply(f)){if("stream.started"===e.type&&(c.requestId=Nd(e,"requestId"),b=Nd(e,"tenantId")),"phase.started"===e.type){const t=Nd(e,"phase");void 0!==t&&(c.lastStartedPhase=t,c.activePhase=t)}if("phase.completed"===e.type){const t=Nd(e,"phase"),n=Md(e,"latencyMs");void 0!==t&&void 0!==n&&(c.phaseLatencies.set(t,Math.round(n)),c.activePhase===t&&(c.activePhase=void 0))}"gate.completed"===e.type&&!0===Pd(e,"rewritten")&&(S=!0);const r=jd(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=Ll.safeParse(e);if(t.success){const e=ml({signals:t.data.signals,analysisPath:t.data.analysisPath,inquiryType:t.data.inquiryType});e.length>0&&await s.updateStatus(e)}}if("draft.started"===e.type&&(x="",await s.updateDraft(x,!0)),"draft.delta"===e.type){x+=Nd(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=jl.parse(e),a=Ol.parse(r),o=c.phaseLatencies.get("turn_planning"),i=c.phaseLatencies.get("context_building"),l=Math.max(0,Math.round(Date.now()-y)),d=void 0!==a.latencyMs?Math.round(a.latencyMs):void 0,u=a.replyVariants?.items.reduce((e,t)=>Math.min(e,t.envelopeExp),a.envelopeExp)??a.envelopeExp,p=Ud({turnPlanningLatencyMs:o,contextBuildingLatencyMs:i});let g;if(w={totalLatencyMs:l,...void 0!==d?{replyLatencyMs:d}:{},...void 0!==o?{turnPlanningLatencyMs:o}:{},...void 0!==i?{contextBuildingLatencyMs:i}:{},preparedContextHit:p},void 0!==a.replyVariants){if(void 0===b){const e="Reply Authority returned replyVariants without stream.started.tenantId; refusing to create a sendable prepared reply without feedback identity.";return t.logger.error(e),await s.fail("双稿反馈身份缺失"),Ld(e,void 0,qd(c))}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});g=Yd({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:m.conversationId,judgeContext:vd(f),random:n.random}),"not_learned"===g.state&&t.logger.warn(`Reply variants will use a non-learning terminal outcome: reason=${g.reason}`)}catch(e){const n=e instanceof Error?e.message:String(e);t.logger.warn(`Failed to fetch reply feedback rubric; using a non-learning terminal outcome. reason=${Kl.RUBRIC_FETCH_FAILED} detail=${n}`),g=Gd({replyVariants:a.replyVariants,tenantId:b,conversationId:m.conversationId,reason:Kl.RUBRIC_FETCH_FAILED})}}v=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:u,unreadCountBeforeReply:h,...void 0!==c.requestId?{requestId:c.requestId}:{},...void 0!==g?{variantGroup:g}:{}}),await s.complete(Hd({...w,gateRewritten:S}),a.suggestedReply,"judge_ready"===g?.state?{options:g.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})),findings:g.findings}:void 0)}}if(void 0===v){const e="Reply Authority stream 未返回 final";return await s.fail(e),Ld(e,void 0,qd(c))}return Wd(v,w,S)}catch(e){const t=Vd(e),n=qd(c,e);return await(s?.fail(t.message)),await(i?.fail(t.message)),Ld(t.message,t.errorKind,n)}finally{o?.close()}}});import{defineTool as Zd}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Qd}from"@roll-agent/reply-authority-client";import{z as eu}from"zod";var tu,nu="zhipin_judge_prepared_reply",ru=eu.object({success:eu.boolean(),variantDecision:Yl.optional(),decisionSource:eu.enum(Wl).optional(),fallback:eu.boolean().optional(),recommendedOption:eu.enum(Ul).optional(),error:eu.string().optional()}),au=eu.object({preparedReplyId:eu.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:eu.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),ou=new Map;function iu(){return{fetchReplyFeedbackRubric:Qd,...tu}}function su(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function cu(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return{kind:"fallback",source:"service_recommended_fallback",recommendedOption:n.recommendedOption,reason:t}}function lu(e){const t=e.trim(),n=/^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(t),r=n?.[1]?.trim()??t,a=r.indexOf("{"),o=r.lastIndexOf("}");if(a<0||o<a)throw new Error("judge 输出不是 JSON 对象");return JSON.parse(r.slice(a,o+1))}function du(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)return!1;const r=new Set(n.findings.map(e=>e.code));return t.every(e=>r.has(e))}function uu(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return["你是招聘回复双稿 judge。请只比较中性选项 option_1 / option_2,不要推断内部稿件身份。","两份 option 均已通过 Reply Authority 的事实硬门并完成签名;不得自行补充、改写或推断岗位事实。","请结合 candidateContext、当前回复阶段和冻结 rubric,比较目标贴合、合规、事实安全、语气、转化意图、候选人体验、简洁度与回归风险。","reason 必须指出候选人当前目标、胜出选项的具体优势,以及另一选项的具体不足;禁止只写“更好”“更自然”等空泛结论。","reason 不得复述姓名、电话、微信号、candidateId、conversationId 或大段候选人原话。","输出必须是 JSON 对象,且只包含:chosenOption、reason、confirmedFindingCodes。","confirmedFindingCodes 必须显式返回且只能来自 findings.code;只有逐项核实全部为误报时才返回空数组,禁止因不确定而省略。","confirmedFindingCodes 与 chosenOption 相互独立:只要某个 finding 指出的问题在任一 option 文本中真实存在,就必须勾选该 code,哪怕最终选择了包含该问题的 option。","若两稿差异很小,仍选择整体风险更低的一稿,并在 reason 中说明关键取舍。","",`stage: ${e.stage}`,`candidateContext: ${JSON.stringify(n.judgeContext??null)}`,`rubricVersion: ${n.rubricVersion}`,`rubricHash: ${n.rubricHash}`,`rubric: ${JSON.stringify(t.rubric)}`,`advisoryFindings: ${JSON.stringify(t.advisoryFindings)}`,`findings: ${JSON.stringify(n.findings)}`,`options: ${JSON.stringify(n.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})))}`].join("\n")}async function mu(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=iu();let o,i,s;try{o=await a.fetchReplyFeedbackRubric({tenantId:r.target.tenantId,rubricVersion:r.rubricVersion})}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Kl.RUBRIC_FETCH_FAILED} detail=${r}`),cu(e,Kl.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return cu(e,Kl.RUBRIC_MISMATCH);try{i=await t.llm.generateText(uu(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Kl.JUDGE_SAMPLING_FAILED} detail=${r}`),cu(e,Kl.JUDGE_SAMPLING_FAILED)}try{s=Xl.parse(lu(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Kl.JUDGE_OUTPUT_INVALID} detail=${r}`),cu(e,Kl.JUDGE_OUTPUT_INVALID)}return du(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:hd(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${Kl.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),cu(e,Kl.JUDGE_OUTPUT_INVALID))}async function pu(e,t,n="mcp-sampling"){if("judge_ready"!==e.variantGroup?.state)throw new Error("preparedReplyId 未包含双稿选项");if(void 0!==e.judgement)return e.judgement;const r=ou.get(e.preparedReplyId);if(void 0!==r)return r;const a=mu(e,t,n).then(n=>{const r=ad(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{ou.delete(e.preparedReplyId)});return ou.set(e.preparedReplyId,a),a}function gu(e){return"decision"===e.kind?{success:!0,variantDecision:e.decision,decisionSource:e.source}:{success:!0,fallback:!0,recommendedOption:e.recommendedOption,decisionSource:e.source,error:e.reason}}var fu=Zd({name:nu,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:au,output:ru,execute:async(e,t)=>{const n=rd(e.preparedReplyId);if(!n.ok)return{success:!1,error:su(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return ru.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return gu(await pu(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as hu}from"node:crypto";import{BrowserActionApprovalSchema as yu}from"@roll-agent/browser";import{postReplyFeedback as bu}from"@roll-agent/reply-authority-client";import{defineTool as vu}from"@roll-agent/sdk";import{z as wu}from"zod";import{createHash as xu}from"node:crypto";import{mkdirSync as Su}from"node:fs";import{homedir as Ru}from"node:os";import{dirname as Cu,resolve as Iu}from"node:path";import{DatabaseSync as ku}from"node:sqlite";import{postReplyFeedback as Au,ReplyFeedbackBodySchema as Eu}from"@roll-agent/reply-authority-client";import{createAgentLogger as _u}from"@roll-agent/sdk";var Nu,Mu,Pu=604800,Tu=3e4,Bu=1e3,Du=9e5,Ou=9e5,$u=50,Fu=["pending","delivered","dead"],ju=Fu.map(e=>`'${e}'`).join(", "),Lu=_u("browser-use-agent:reply-feedback-outbox");function qu(e){return"~"===e?Ru():e.startsWith("~/")?Iu(Ru(),e.slice(2)):Iu(e)}function zu(){return Iu(Ru(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function Vu(e,t){const n=process.env[e]?.trim();if(!n)return t;const r=Number(n);if(!Number.isSafeInteger(r)||r<=0)throw new Error(`${e} must be a positive integer.`);return r}function Ju(e){const t=e.retentionSeconds??Vu("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",Pu),n=e.flushIntervalMs??Vu("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",Tu),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??zu();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=qu(r),o=e.retryBaseDelayMs??Bu,i=e.authRetryDelayMs??Du;if(!Number.isSafeInteger(t)||t<=0)throw new Error("reply feedback outbox retentionSeconds must be a positive integer.");if(!Number.isSafeInteger(n)||n<=0)throw new Error("reply feedback outbox flushIntervalMs must be a positive integer.");if(!Number.isSafeInteger(o)||o<=0)throw new Error("reply feedback outbox retryBaseDelayMs must be a positive integer.");if(!Number.isSafeInteger(i)||i<=0)throw new Error("reply feedback outbox authRetryDelayMs must be a positive integer.");return{dbPath:a,retentionMs:1e3*t,flushIntervalMs:n,retryBaseDelayMs:o,authRetryDelayMs:i}}function Uu(e){if(null===e||"boolean"==typeof e||"number"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(Uu).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${Uu(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function Hu(e){return xu("sha256").update(e).digest("hex")}function Wu(e,t,n){if(void 0===n)return e+t;if(!Number.isSafeInteger(n)||n<0||n>Math.floor(Number.MAX_SAFE_INTEGER/1e3))throw new Error("feedbackExpiresAt must be a non-negative Unix timestamp in seconds.");return Math.min(e+t,1e3*n)}function Ku(e,t){return e.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE group_id = ?").get(t)}function Gu(e){return Eu.parse(JSON.parse(e.body_json))}function Yu(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function Xu(e){if("object"!=typeof e||null===e)return;const t=Reflect.get(e,"statusCode");return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Zu(e){return 401===e||403===e}function Qu(e){return!(void 0!==e&&!Zu(e))||(408===e||425===e||429===e||e>=500)}function em(e,t){if(Zu(t.last_status_code??void 0))return e.authRetryDelayMs;const n=Math.min(Math.max(t.attempt_count-1,0),10);return Math.min(e.retryBaseDelayMs*2**n,Ou)}function tm(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function nm(e,t){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead',\n last_error = 'Reply feedback outbox retention expired before delivery.',\n updated_at = ?,\n delete_after = ?\n WHERE state = 'pending' AND retry_expires_at <= ?").run(t,t+e.retentionMs,t),e.db.prepare("DELETE FROM reply_feedback_outbox WHERE state <> 'pending' AND delete_after <= ?").run(t)}function rm(e,t,n,r){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'delivered', delivered_status = ?, last_status_code = NULL,\n last_error = NULL, updated_at = ?, delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(n,r,r+e.retentionMs,t.group_id,t.body_hash)}function am(e,t,n,r){const a=Xu(n),o=Yu(n);if(Qu(a)&&r<t.retry_expires_at){const n=r+em(e,{...t,last_status_code:a??null});return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'pending', next_attempt_at = ?, last_status_code = ?, last_error = ?,\n updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,a??null,o,r,t.group_id,t.body_hash),e.logger.warn(`Reply feedback queued for retry: groupId=${t.group_id} statusCode=${a??"network"} nextAttemptAt=${new Date(n).toISOString()} error=${o}`),{status:"queued",error:o}}return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead', last_status_code = ?, last_error = ?, updated_at = ?,\n delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(a??null,o,r,r+e.retentionMs,t.group_id,t.body_hash),e.logger.error(`Reply feedback moved to dead state: groupId=${t.group_id} statusCode=${a??"network"} error=${o}`),{status:"failed",error:o}}async function om(e,t){const n=Date.now();if(n>=t.retry_expires_at)return am(e,t,new Error("Reply feedback outbox retention expired."),n);e.db.prepare("UPDATE reply_feedback_outbox\n SET attempt_count = attempt_count + 1, updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,t.group_id,t.body_hash);try{const n=await e.deliver(Gu(t));if("accepted"!==n.status&&"duplicate"!==n.status)throw new Error(`Unexpected reply feedback response status: ${String(n.status)}`);if(n.groupId!==t.group_id)throw new Error(`Reply feedback response groupId mismatch: expected ${t.group_id}, got ${n.groupId}.`);const r=Date.now();return rm(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return am(e,Ku(e.db,t.group_id)??t,n,Date.now())}}async function im(e){const t=Date.now();nm(e,t);const n=e.db.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE state = 'pending' AND next_attempt_at <= ?\n ORDER BY next_attempt_at ASC, rowid ASC\n LIMIT ?").all(t,$u);for(const t of n)await om(e,t)}function sm(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,tm(e,()=>im(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function cm(e){Su(Cu(e),{recursive:!0});const t=new ku(e);try{return t.exec(`\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n PRAGMA busy_timeout = 5000;\n CREATE TABLE IF NOT EXISTS reply_feedback_outbox (\n group_id TEXT PRIMARY KEY,\n body_json TEXT NOT NULL,\n body_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN (${ju})),\n attempt_count INTEGER NOT NULL DEFAULT 0,\n next_attempt_at INTEGER NOT NULL,\n retry_expires_at INTEGER NOT NULL,\n delete_after INTEGER NOT NULL,\n delivered_status TEXT CHECK (delivered_status IN ('accepted', 'duplicate')),\n last_status_code INTEGER,\n last_error TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE INDEX IF NOT EXISTS reply_feedback_outbox_due_idx\n ON reply_feedback_outbox (state, next_attempt_at);\n `),t}catch(e){throw t.close(),e}}function lm(e={}){if(Nu&&!Nu.closing)return Nu.deliver=e.deliver??Nu.deliver,void(Nu.logger=e.logger??Nu.logger);if(Nu?.closing||Mu)throw new Error("Reply feedback outbox is shutting down.");const t=Ju(e),n=cm(t.dbPath),r=setInterval(()=>{Nu&&sm(Nu)},t.flushIntervalMs);r.unref();const a={db:n,dbPath:t.dbPath,retentionMs:t.retentionMs,flushIntervalMs:t.flushIntervalMs,retryBaseDelayMs:t.retryBaseDelayMs,authRetryDelayMs:t.authRetryDelayMs,timer:r,operationQueue:Promise.resolve(),deliver:e.deliver??Au,logger:e.logger??Lu,closing:!1,backgroundFlushQueued:!1};Nu=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),sm(a)}async function dm(){if(Mu)return Mu;const e=Nu;return e?(e.closing=!0,clearInterval(e.timer),Mu=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),Nu===e&&(Nu=void 0)}})().finally(()=>{Mu=void 0})):void 0}async function um(e,t,n,r={}){Nu||lm({deliver:t,logger:n});const a=Nu;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=Eu.parse(e),i=Uu(o),s=Hu(i);return tm(a,async()=>{const e=Date.now(),t=Wu(e,a.retentionMs,r.feedbackExpiresAt);nm(a,e);const n=a.db.prepare("INSERT INTO reply_feedback_outbox (\n group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error,\n created_at, updated_at\n ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?, NULL, NULL, NULL, ?, ?)\n ON CONFLICT(group_id) DO NOTHING").run(o.groupId,i,s,e,t,t+a.retentionMs,e,e);let c=Ku(a.db,o.groupId);return c?c.body_hash!==s?{status:"failed",error:`Reply feedback group ${o.groupId} already has a different payload.`}:("pending"===c.state&&c.retry_expires_at>t&&(a.db.prepare("UPDATE reply_feedback_outbox\n SET retry_expires_at = ?, delete_after = MIN(delete_after, ?), updated_at = ?\n WHERE group_id = ? AND body_hash = ? AND state = 'pending'").run(t,t+a.retentionMs,e,c.group_id,c.body_hash),c=Ku(a.db,o.groupId)??c),"delivered"===c.state?{status:"duplicate"}:"dead"===c.state?{status:"failed",error:c.last_error??"Reply feedback is permanently dead."}:0===n.changes&&c.next_attempt_at>e?{status:"queued",...c.last_error?{error:c.last_error}:{}}:om(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}import{defineTool as mm}from"@roll-agent/sdk";import{z as pm}from"zod";function gm(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as fm}from"zod";var hm="reply-authority-service",ym="browser-use-agent/zhipin_send_reply",bm="zhipin",vm=60,wm=fm.object({platform:fm.literal(bm),username:fm.string().min(1),accountId:fm.string().min(1).optional()}),xm=fm.object({v:fm.literal(2),iss:fm.literal(hm),kid:fm.string().min(1),jti:fm.string().min(1),iat:fm.number().int(),exp:fm.number().int(),aud:fm.literal(ym),platform:fm.literal(bm),tenantId:fm.string().min(1),conversationId:fm.string().min(1),candidateId:fm.string().min(1),reply:fm.string(),policyVersion:fm.string().min(1),recruiterBinding:wm}),Sm=fm.object({kid:fm.string().min(1),algorithm:fm.literal("Ed25519"),publicKey:fm.string().min(1),validFrom:fm.string().min(1),validUntil:fm.string().optional()}),Rm=fm.object({keys:fm.array(Sm)}),Cm=new Map;function Im(e){for(const[t,n]of Cm)n<e-vm&&Cm.delete(t)}function km(e,t=Math.floor(Date.now()/1e3)){return Im(t),Cm.has(e)}function Am(e,t,n=Math.floor(Date.now()/1e3)){Im(n),Cm.set(e,t)}import{createPublicKey as Em,verify as _m}from"node:crypto";var Nm=new Map,Mm=null;function Pm(){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 Tm(e){return Nm=new Map(e.map(e=>[e.kid,e]))}async function Bm(){if(Mm)return Mm;Mm=(async()=>{const e=await fetch(Pm()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Tm(Rm.parse(t).keys)})();try{return await Mm}finally{Mm=null}}async function Dm(){await Bm()}async function Om(e){const t=Nm.get(e);if(t)return t;const n=(await Bm()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function $m(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Fm(e){const t=e.split("."),n=t[0],r=t[1];if(2!==t.length||void 0===n||void 0===r||0===n.length||0===r.length)throw new Error("Invalid signed envelope format");let a,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{a=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}$m(a);const i=xm.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function jm(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-vm)throw new Error("Envelope expired");if(e.iat>t+vm)throw new Error("Envelope issued in the future")}async function Lm(e,t=Math.floor(Date.now()/1e3)){const n=Fm(e),r=await Om(n.payload.kid),a=Em({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!_m(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return jm(n.payload,t),n.payload}var qm,zm=pm.object({success:pm.boolean(),sentMessage:pm.string(),error:pm.string().optional()});function Vm(){return{getReplyAuthorityKeysLoaded:pe,openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...qm}}function Jm(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Um(e){return"function"==typeof e.readCandidateChatDetails}async function Hm(e){if(Um(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function Wm(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function Km(e,t){return null===t||0===e.candidateName.length||Jm(e.candidateName,t.candidateName)}function Gm(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function Ym(e,t){const n=Vm();let r,a,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 Lm(e.signedEnvelope);if(o=i.reply,km(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await new Sd(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=Gm(e.unreadCountBeforeReply);if(!Wm(c,i)||!Km(c,s)){const t=await r.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:t.error??"未找到目标聊天"};l=Math.max(l,Gm(t.unreadCount)),s=await r.readActiveChatPanel(),c=await r.readSelectedChatTarget()}if(null===c)return await a.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==s&&c.candidateName.length>0&&!Jm(c.candidateName,s.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${c.candidateName} / ${s.candidateName}`;return await a.fail(e),{success:!1,sentMessage:o,error:e}}if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return await a.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=bo(await r.readUsernameEvidence());if(!d.found)return await a.fail("未找到用户名"),{success:!1,sentMessage:o,error:"未找到用户名,请确认当前页面已登录招聘者账号。"};const u={platform:"zhipin",username:d.username,strategy:d.strategy,source:d.source};if(!gm(u,i.recruiterBinding)){const e=`recruiter 绑定不匹配:当前账号 ${u.username} 与签发时 ${i.recruiterBinding.username} 不一致`;return await a.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 r.sendChatReply(o,{...void 0!==a?{motionObserver:a}:{}});if(!m.success)return await a.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"};const p=await Hm(r);return kc({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),Am(i.jti,i.exp),await a.succeed("已发送回复"),{success:!0,sentMessage:o}}catch(e){const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,sentMessage:o,error:t}}finally{r?.close()}}var Xm,Zm=mm({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:pm.object({signedEnvelope:pm.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:pm.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:pm.number().optional().describe("候选人在列表中的索引(可选)")}),output:zm,execute:async(e,t)=>await Ym(e,t)}),Qm="zhipin_send_prepared_reply",ep=80,tp=wu.object({success:wu.boolean(),sentMessage:wu.string(),chosenOption:wu.enum(Ul).optional(),decisionSource:wu.enum(Wl).optional(),decisionReason:wu.string().optional(),judgeModel:wu.string().optional(),feedbackExpected:wu.boolean().optional(),feedbackStatus:wu.enum(["accepted","duplicate","queued","skipped","failed"]).optional(),feedbackError:wu.string().optional(),error:wu.string().optional()}),np=wu.object({preparedReplyId:wu.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:Je.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:yu.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:Yl.optional().describe("可选的显式双稿选择;缺省时发送工具会自动执行并缓存默认 Judge。"),skipVariantJudge:wu.boolean().optional().describe("仅用于明确的应急降级:跳过双稿 Judge、发送服务端推荐稿并回传 not_learned 终态,但不产生学习样本。")}).superRefine((e,t)=>{void 0!==e.variantDecision&&!0===e.skipVariantJudge&&t.addIssue({code:wu.ZodIssueCode.custom,message:"variantDecision 与 skipVariantJudge 不能同时传入",path:["skipVariantJudge"]})});function rp(){return{sendSignedZhipinReply:Ym,postReplyFeedback:bu,submitReplyFeedback:um,...Xm}}function ap(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function op(e,t){const n=e=>void 0===e?void 0:[...e].sort();return e.chosenOption===t.chosenOption&&e.reason===t.reason&&JSON.stringify(n(e.confirmedFindingCodes))===JSON.stringify(n(t.confirmedFindingCodes))}function ip(e){return"decision"===e.kind?{kind:"decision",source:e.source,decision:e.decision}:{kind:"fallback",source:e.source,recommendedOption:e.recommendedOption,reason:e.reason}}async function sp(e,t,n){const r=e.variantGroup;return void 0===r?void 0!==t.variantDecision||!0===t.skipVariantJudge?{kind:"error",error:"当前 preparedReplyId 没有双稿选项,不能提交双稿选择或跳过 Judge"}:{kind:"single"}:"not_learned"===r.state?void 0!==t.variantDecision?{kind:"error",error:"当前 preparedReplyId 已降级为非学习终态,禁止提交学习 decision"}:{kind:"terminal_fallback",source:"service_recommended_fallback",chosenVariant:r.chosenVariant,reason:r.reason}:void 0!==t.variantDecision?void 0!==e.judgement?"fallback"===e.judgement.kind?{kind:"error",error:"当前 preparedReplyId 的默认 Judge 已降级,禁止事后伪造学习 decision"}:op(e.judgement.decision,t.variantDecision)?ip(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?ip(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:ip(await pu(e,n))}function cp(e,t){return"judge_ready"===e.variantGroup?.state?e.variantGroup.options.find(e=>e.option===t):void 0}function lp(e,t){if(void 0===t.confirmedFindingCodes)return!1;const n=e.variantGroup;if("judge_ready"!==n?.state)return!0;const r=new Set(n.findings.map(e=>e.code));return t.confirmedFindingCodes.some(e=>!r.has(e))}function dp(e,t){const n=e.variantGroup;if(void 0===n)return"single"!==t.kind?{ok:!1,error:"当前 preparedReplyId 没有双稿选项"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},record:e};if("not_learned"===n.state)return"terminal_fallback"!==t.kind?{ok:!1,error:"非学习双稿 preparedReplyId 缺少降级终态"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenVariant:t.chosenVariant,decisionSource:t.source,decisionReason:t.reason,record:e};if("single"===t.kind||"terminal_fallback"===t.kind)return{ok:!1,error:"双稿 preparedReplyId 缺少选择状态"};const r="decision"===t.kind?t.decision:void 0;if(void 0!==r&&lp(e,r))return{ok:!1,error:"variantDecision.confirmedFindingCodes 必须来自 replyVariantSelection.findings"};const a="decision"===t.kind?t.decision.chosenOption:t.recommendedOption,o=cp(e,a);if(void 0===o)return{ok:!1,error:`未找到双稿选项 ${a}`};const i="decision"===t.kind?t.decision.reason:t.reason;return{ok:!0,signedEnvelope:o.signedEnvelope,suggestedReply:o.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenOption:o.option,chosenVariant:o.variant,...void 0!==r?{variantDecision:r}:{},decisionSource:t.source,decisionReason:i,record:e}}function up(e){return`sha256:${hu("sha256").update(JSON.stringify([e.record.preparedReplyId,e.signedEnvelope,e.suggestedReply,e.record.expiresAt,e.unreadCountBeforeReply,e.record.variantGroup?.groupId,e.chosenOption,e.decisionSource,e.decisionReason,e.variantDecision?.confirmedFindingCodes,e.variantDecision?.judgeModel])).digest("hex")}`}function mp(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>ep?`${t.slice(0,ep-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function pp(e,t,n){const r=t.record.variantGroup;if(void 0===r)return{};if(void 0===t.chosenVariant||void 0===t.decisionSource||void 0===t.decisionReason)return{feedbackStatus:"failed",feedbackError:"Dual-draft send completed without terminal feedback metadata."};try{let a;if(void 0!==t.variantDecision){if("judge"!==t.decisionSource&&"orchestrator"!==t.decisionSource)throw new Error("Learning feedback has an invalid decision source.");a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"selected",decisionSource:t.decisionSource,...void 0!==t.variantDecision.confirmedFindingCodes?{confirmedFindingCodes:t.variantDecision.confirmedFindingCodes}:{},reason:t.variantDecision.reason,rubricVersion:r.rubricVersion,rubricHash:r.rubricHash,...void 0!==t.variantDecision.judgeModel?{judgeModel:t.variantDecision.judgeModel}:{}}}else{if("service_recommended_fallback"!==t.decisionSource&&"explicit_no_judge"!==t.decisionSource)throw new Error("Non-learning feedback has an invalid decision source.");const e=t.decisionReason.trim().slice(0,500);a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"not_learned",decisionSource:t.decisionSource,reason:e||"Dual-draft learning was intentionally skipped.",rubricVersion:r.rubricVersion,rubricHash:r.rubricHash}}const o=await e.submitReplyFeedback(a,e.postReplyFeedback,n.logger,void 0!==r.feedbackExpiresAt?{feedbackExpiresAt:r.feedbackExpiresAt}:{});return{feedbackStatus:o.status,...void 0!==o.error?{feedbackError:o.error}:{}}}catch(e){const t=e instanceof Error?e.message:String(e);return n.logger.error(`Reply feedback outbox failed after sending prepared reply: ${t}`),{feedbackStatus:"failed",feedbackError:t}}}function gp(e){return void 0===e.decisionSource?{}:{...void 0!==e.chosenOption?{chosenOption:e.chosenOption}:{},decisionSource:e.decisionSource,...void 0!==e.decisionReason?{decisionReason:e.decisionReason}:{},...void 0!==e.variantDecision?.judgeModel?{judgeModel:e.variantDecision.judgeModel}:{},feedbackExpected:void 0!==e.variantDecision}}var fp=vu({name:Qm,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:np,output:tp,execute:async(e,t)=>{const n=rp(),r=rd(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:ap(r.reason)};const a=await sp(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=dp(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=up(o),s=`${o.record.preparedReplyId}:${i}`,c=mt(t,{subject:{tool:Qm,target:o.record.preparedReplyId,summary:mp(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});rn(t,de(),{action:Qm,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=nd(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:ap(l.reason)};const d=dp(l.record,a);if(!d.ok)return{success:!1,sentMessage:"",error:d.error};const u=await n.sendSignedZhipinReply({signedEnvelope:d.signedEnvelope,...void 0!==d.unreadCountBeforeReply?{unreadCountBeforeReply:d.unreadCountBeforeReply}:{}},t);if(!u.success)return u;const m=await pp(n,d,t);return{...u,...gp(d),...m}}});import{defineTool as hp}from"@roll-agent/sdk";import{z as yp}from"zod";var bp,vp=yp.object({success:yp.boolean(),exchanged:yp.boolean(),wechatNumber:yp.string().optional(),error:yp.string().optional()}),wp=900,xp=160,Sp=1100;function Rp(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...bp}}function Cp(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Ip(e){return"function"==typeof e.readCandidateChatDetails}async function kp(e){if(Ip(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var Ap=hp({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:yp.object({conversationId:yp.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:yp.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:yp.number().optional().describe("候选人在列表中的索引(可选)")}),output:vp,execute:async(e,t)=>{const n=Rp();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在换微信"),void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index){const t=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,...void 0!==a?{motionObserver:a}:{}});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,exchanged:!1,error:t.error??"未找到目标聊天"}}else if(!await r.isChatSurfaceOpen())return await a.fail("消息列表未加载"),{success:!1,exchanged:!1,error:"消息列表未加载"};const o=await r.readSelectedChatTarget();if(null===o)return await a.fail("未选中聊天联系人"),{success:!1,exchanged:!1,error:"未选中聊天联系人,无法点击当前聊天输入区的「换微信」按钮"};const i=await r.readActiveChatPanel();if(null!==i&&o.candidateName.length>0&&!Cp(o.candidateName,i.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${o.candidateName} / ${i.candidateName}`;return await a.fail(e),{success:!1,exchanged:!1,error:e}}t.logger.info(`Starting native WeChat exchange with ${o.candidateName}`);const s=await kp(r),c=await r.exchangeWechat({preClickDelayMs:wp,pressDurationMs:xp,settleMs:Sp,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&Ec({conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.candidateName,...void 0!==c.wechatNumber?{wechatNumber:c.wechatNumber}:{},...void 0!==s?{candidateDetails:s}:{}},t.logger),await a.succeed("已完成换微信"),c):(await a.fail(c.error??"换微信失败"),c)}catch(e){const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,exchanged:!1,error:t}}finally{r?.close()}}});import{defineTool as Ep}from"@roll-agent/sdk";import{z as _p}from"zod";var Np,Mp=_p.object({success:_p.boolean(),username:_p.string(),usedSelector:_p.string().optional(),usedStrategy:_p.string().optional(),source:_p.string().optional(),error:_p.string().optional()});function Pp(){return{openNativePagePort:Ni,pickBestUsername:bo,createNativeVisualActivitySession:e=>new aa(e),...Np}}var Tp=Ep({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:_p.object({}),output:Mp,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Pp();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在识别登录账号"),await a.highlightSelector('header, #header, [role="banner"], [role="navigation"]',{label:"正在识别登录账号",padding:10});const e=n.pickBestUsername(await r.readUsernameEvidence());if(!e.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return await a.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(a?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}finally{r?.close()}}});import{defineTool as Bp}from"@roll-agent/sdk";import{z as Dp}from"zod";var Op,$p=["unknown","top","middle","bottom","only-page"],Fp=Dp.object({containerFound:Dp.boolean(),containerLabel:Dp.string(),scrollTop:Dp.number(),scrollHeight:Dp.number(),clientHeight:Dp.number(),itemCount:Dp.number(),atStart:Dp.boolean(),atEnd:Dp.boolean()}),jp=Dp.object({success:Dp.boolean(),surface:Dp.enum(Ha),direction:Dp.enum(["up","down"]),stepsRequested:Dp.number(),stepsCompleted:Dp.number(),reachedBoundary:Dp.boolean(),atTop:Dp.boolean(),atBottom:Dp.boolean(),canScrollUp:Dp.boolean(),canScrollDown:Dp.boolean(),position:Dp.enum($p),before:Fp,after:Fp,error:Dp.string().optional()});function Lp(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...Op}}function qp(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function zp(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function Vp(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:zp(e)}}var Jp=Bp({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:Dp.object({surface:Dp.enum(Ha).describe("要滚动的页面区域"),direction:Dp.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:Dp.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:Dp.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:Dp.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:jp,execute:async(e,t)=>{const n=Lp(),r=Ka(e.surface),a=e.direction??r.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(r.highlightSelector,{label:l,padding:8});const d=await s.scrollSurface(e.surface,{direction:a,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,...Vp(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Vp(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=qp();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Vp(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as Up}from"@roll-agent/sdk";import{z as Hp}from"zod";var Wp,Kp=Hp.object({applyMode:Hp.enum(Za),ageMin:Hp.number().int().min(16).optional(),ageMax:Hp.number().int().min(16).optional(),location:Hp.object({city:Hp.string(),district:Hp.string().optional()}).optional(),optionSelections:Hp.array(Hp.object({fieldKey:Hp.string(),label:Hp.string(),values:Hp.array(Hp.string()),selection:Hp.enum(["single","multi"]),clearValue:Hp.string()}))}),Gp=Hp.object({ageMin:Hp.number().optional(),ageMax:Hp.number().optional(),location:Hp.object({city:Hp.string(),district:Hp.string().optional()}).optional(),optionSelections:Hp.array(Hp.object({fieldKey:Hp.string(),label:Hp.string(),values:Hp.array(Hp.string())})),gender:Hp.string().optional(),activity:Hp.string().optional()}),Yp=Hp.object({success:Hp.boolean(),status:Hp.enum(eo),requested:Kp,applied:Gp.optional(),filterButtonText:Hp.string().optional(),error:Hp.string().optional()}),Xp=Hp.string().min(1),Zp=Hp.array(Xp).min(1),Qp=Hp.object({applyMode:Hp.enum(Za).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:Hp.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:Hp.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:Xp.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:Xp.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:Hp.enum(Ya).optional().describe("性别筛选:不限、男、女"),activity:Hp.enum(Xa).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:Zp.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:Xp.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:Xp.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:Zp.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:Zp.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:Xp.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:Zp.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:Xp.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:Zp.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:Zp.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:Xp.optional().describe("是否可拨打电话筛选,如:不限、可拨打")}).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"}).refine(e=>void 0===e.locationDistrict||void 0!==e.locationCity,{path:["locationCity"],message:"locationCity is required when locationDistrict is provided"}),eg=350,tg=130,ng=600;function rg(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...Wp}}function ag(e,t){const n=to(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function og(e){return void 0!==e.locationCity||void 0!==e.locationDistrict||void 0!==e.major||void 0!==e.recentNotView||void 0!==e.exchangeResumeWithColleague||void 0!==e.candidateKeywords||void 0!==e.school||void 0!==e.switchJobFrequency||void 0!==e.intention||void 0!==e.salary||void 0!==e.degree||void 0!==e.experience||void 0!==e.callPhone}function ig(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function sg(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(ag(t,r))}function cg(e){const t=[];if(void 0===e.applyMode&&!og(e)&&ig(e)||!og(e)&&!ig(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(ag("gender",[n])),t.push(ag("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}sg(t,"gender",e.gender),sg(t,"activity",e.activity),sg(t,"major",e.major),sg(t,"recentNotView",e.recentNotView),sg(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),sg(t,"candidateKeywords",e.candidateKeywords),sg(t,"school",e.school),sg(t,"switchJobFrequency",e.switchJobFrequency),sg(t,"intention",e.intention),sg(t,"salary",e.salary),sg(t,"degree",e.degree),sg(t,"experience",e.experience),sg(t,"callPhone",e.callPhone);const n=e.applyMode??"patch";return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.locationCity?{location:{city:e.locationCity,...void 0!==e.locationDistrict?{district:e.locationDistrict}:{}}}:{},applyMode:n,optionSelections:t}}function lg(e){return{applyMode:e.applyMode,...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values],selection:e.selection,clearValue:e.clearValue}))}}function dg(e){if(void 0!==e)return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values]})),...void 0!==e.gender?{gender:e.gender}:{},...void 0!==e.activity?{activity:e.activity}:{}}}function ug(e){const t=dg(e.applied);return{success:"applied"===e.status,status:e.status,requested:lg(e.requested),...void 0!==t?{applied:t}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var mg=Up({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:Qp,output:Yp,execute:async(e,t)=>{const n=rg(),r=cg(e);let a,o;t.logger.info(`Filtering Boss recommend candidates through native CDP: applyMode=${r.applyMode}, ageMin=${r.ageMin??"unchanged"}, ageMax=${r.ageMax??"unchanged"}, location=${void 0!==r.location?`${r.location.city}${void 0!==r.location.district?`-${r.location.district}`:""}`:"unchanged"}, fields=${r.optionSelections.map(e=>e.fieldKey).join(",")}`);try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),ug({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Ga.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:eg,pressDurationMs:tg,settleMs:ng,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),ug(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),ug({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as pg}from"@roll-agent/sdk";import{z as gg}from"zod";var fg=["target-count","boundary","no-new-items","max-steps"];import{AsyncLocalStorage as hg}from"node:async_hooks";var yg="@c",bg="@j",vg="legacy",wg=/^@?c([1-9]\d*)$/i,xg=/^@?j([1-9]\d*)$/i,Sg=new Map,Rg=new Map,Cg=new hg;function Ig(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${yg}${String(e+1)}`}function kg(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${bg}${String(e+1)}`}function Ag(e){const t=wg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Eg(e){const t=xg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function _g(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 Pg(e.candidateRef).index}function Ng(e){const t=e.map((e,t)=>({...e,candidateRef:Ig(t)}));return Sg.set(Fg(),new Map(t.map(e=>[e.candidateRef,e]))),t}function Mg(e){const t=e.map((e,t)=>({...e,jobRef:kg(t)}));return Rg.set(Fg(),new Map(t.map(e=>[e.jobRef,e]))),t}function Pg(e){const t=Ag(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=Ig(t);return Sg.get(Fg())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function Tg(e){const t=Eg(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=kg(t),r=Rg.get(Fg())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function Bg(e){const t=[];for(const n of e.indices??[])t.push({index:_g({index:n}),candidateRef:Ig(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Pg(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return Og(t)}function Dg(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)||$g(e.name)===$g(t.name))}function Og(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function $g(e){return e.trim().toLocaleLowerCase("zh-CN")}function Fg(){const e=Cg.getStore();if(void 0!==e)return e;try{return be().id}catch{return vg}}var jg,Lg=gg.object({index:gg.number(),candidateRef:gg.string(),candidateId:gg.string(),name:gg.string(),age:gg.string(),experience:gg.string(),education:gg.string(),workStatus:gg.string(),company:gg.string(),currentPosition:gg.string(),expectedLocation:gg.string(),expectedPosition:gg.string(),expectedSalary:gg.string(),tags:gg.array(gg.string()),buttonText:gg.string()}),qg=gg.object({containerLabel:gg.string(),stepsRequested:gg.number(),stepsCompleted:gg.number(),reachedBoundary:gg.boolean(),stopReason:gg.enum(fg),uniqueCount:gg.number(),duplicateCount:gg.number(),noNewRounds:gg.number(),beforeItemCount:gg.number(),afterItemCount:gg.number(),beforeScrollHeight:gg.number(),afterScrollHeight:gg.number()}),zg=gg.object({success:gg.boolean(),candidates:gg.array(Lg),total:gg.number(),scrollStats:qg.optional(),error:gg.string().optional()});function Vg(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...jg}}var Jg=pg({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:gg.object({maxResults:gg.number().optional().describe("最多返回条数"),autoScroll:gg.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:gg.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:zg,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=Vg();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开推荐列表");if(!await r.waitForRecommendList())return await a.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};const o="正在读取推荐列表";await a.begin(o),await a.highlightSelector(".candidate-card-wrap, li.card-item, [data-geek], .geek-item",{label:o,padding:8});const i=await r.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=Ng(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 a.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(a?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{r?.close()}}});import{defineTool as Ug}from"@roll-agent/sdk";import{z as Hg}from"zod";var Wg,Kg=["listed","recommend_not_ready","selector_not_found"],Gg=Hg.object({index:Hg.number(),jobRef:Hg.string().optional(),value:Hg.string(),label:Hg.string(),isCurrent:Hg.boolean()}),Yg=Hg.object({success:Hg.boolean(),status:Hg.enum(Kg),current:Gg.optional(),jobs:Hg.array(Gg),availableCount:Hg.number(),canSwitch:Hg.boolean(),error:Hg.string().optional()}),Xg=450,Zg=140,Qg=750;function ef(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...Wg}}function tf(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function nf(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function rf(e){const t=Mg(e.options),n=e.options.map((e,n)=>tf(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>nf(e,t))??tf(t)}return{success:e.success,status:e.status,...void 0!==r?{current:r}:{},jobs:n,availableCount:e.availableCount,canSwitch:e.canSwitch,...void 0!==e.error?{error:e.error}:{}}}var af=Ug({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:Hg.object({}),output:Yg,execute:async(e,t)=>{const n=ef();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:Xg,pressDurationMs:Zg,settleMs:Qg,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),rf(e)}catch(e){const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),rf({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as of}from"@roll-agent/browser";import{defineTool as sf}from"@roll-agent/sdk";import{z as cf}from"zod";var lf,df=cf.object({success:cf.boolean(),alreadyOnRecommend:cf.boolean(),usedSidebarClick:cf.boolean(),recommendReady:cf.boolean(),page:of.optional(),error:cf.string().optional()});function uf(){return{getContextManager:ue,openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...lf}}async function mf(e,t){return Dt(e,await t.inspectPage())}var pf=sf({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:cf.object({}),output:df,execute:async(e,t)=>{const n=uf(),r=n.getContextManager();let a,o;t.logger.info("Opening Boss recommend page via native sidebar navigation");try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(Ga.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await mf(r,a)};if(!await a.clickSidebarSection("recommend",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await mf(r,a),error:"未找到推荐牛人导航"};t.logger.info("Clicked Boss sidebar nav: 推荐牛人");return await a.waitForRecommendSurface()?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await mf(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await mf(r,a),error:"推荐牛人页未就绪"})}catch(e){return await(o?.fail("切换推荐牛人页失败")),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,error:e instanceof Error?e.message:"切换推荐牛人页失败"}}finally{a?.close()}}});import{defineTool as gf}from"@roll-agent/sdk";import{z as ff}from"zod";var hf,yf=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],bf=ff.object({index:ff.number(),value:ff.string(),label:ff.string(),isCurrent:ff.boolean()}),vf=ff.object({jobRef:ff.string().optional(),jobValue:ff.string().optional(),jobName:ff.string().optional(),index:ff.number().optional(),searchKeyword:ff.string().optional(),useSearch:ff.boolean().optional(),forceClick:ff.boolean().optional()}),wf=ff.object({success:ff.boolean(),status:ff.enum(yf),requested:vf,current:bf.optional(),selected:bf.optional(),options:ff.array(bf),matchedCount:ff.number(),error:ff.string().optional()}),xf=ff.object({jobRef:ff.string().regex(xg,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:ff.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:ff.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:ff.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:ff.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:ff.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:ff.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 至少需要提供一个"}),Sf=900,Rf=180,Cf=1400;function If(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),...hf}}function kf(e){if(void 0!==e.jobRef){const t=Tg(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 Af(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 Ef(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function _f(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Ef(e.current)}:{},...void 0!==e.selected?{selected:Ef(e.selected)}:{},options:e.options.map(Ef),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Nf=gf({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:xf,output:wf,execute:async(e,t)=>{const n=If();let r,a;try{const o=kf(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}`),r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:Sf,pressDurationMs:Rf,settleMs:Cf,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),_f(i)}catch(t){const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),_f({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Af(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as Mf}from"@roll-agent/sdk";import{z as Pf}from"zod";var Tf,Bf=Pf.object({index:Pf.number(),candidateRef:Pf.string(),candidateName:Pf.string(),candidateId:Pf.string(),success:Pf.boolean(),error:Pf.string().optional()}),Df=Pf.object({success:Pf.boolean(),results:Pf.array(Bf),summary:Pf.object({total:Pf.number(),succeeded:Pf.number(),failed:Pf.number()})}),Of=[1400,1800,2200],$f=450,Ff=140,jf=750,Lf=Pf.object({indices:Pf.array(Pf.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Pf.array(Pf.string().regex(wg,"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 qf(){return{openNativePagePort:Ni,createNativeVisualActivitySession:e=>new aa(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...Tf}}var zf=Mf({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Lf,output:Df,execute:async(e,t)=>{const n=Bg(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=qf();let a,o;try{a=await r.openNativePagePort(),o=r.createNativeVisualActivitySession(a),await o.begin("正在打开推荐列表");if(!await a.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=Of[(e-1)%Of.length]??Of[0];await r.sleep(t)}if(Vf(s)){const e=await a.inspectRecommendCard(s.index);if(!Dg(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 a.clickRecommendGreet(s.index,{preClickDelayMs:$f,pressDurationMs:Ff,settleMs:jf,...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}:{}}),Ac(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 r=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:t}));return{success:!1,results:r,summary:{total:r.length,succeeded:0,failed:r.length}}}finally{a?.close()}}});function Vf(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as Jf}from"@roll-agent/sdk";import{z as Uf}from"zod";var Hf,Wf=Uf.object({success:Uf.boolean(),candidateRef:Uf.string().optional(),candidateName:Uf.string(),candidateId:Uf.string(),resumeReady:Uf.boolean().optional(),error:Uf.string().optional()}),Kf=Uf.object({index:Uf.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:Uf.string().regex(wg,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef");function Gf(){return{openNativePagePort:Ni,...Hf}}var Yf=Jf({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:Kf,output:Wf,execute:async(e,t)=>{const n=Zf(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${String(r)} through native backend`);const o=Gf();let i,s;try{i=await o.openNativePagePort(),s=new aa(i),await s.begin("正在打开简历详情");if(!await i.waitForRecommendList())return await s.fail("推荐列表未加载"),Xf(a,"推荐列表未加载");const e=await i.inspectRecommendCard(r);if(!e.found){const t=e.error??`候选人引用 ${a} 超出范围`;return await s.fail(t),Xf(a,t)}if(Qf(n)&&!Dg(n,e)){const t=`候选人引用 ${a} 已过期,请重新获取推荐列表`;return await s.fail(t),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:t}}if(!await i.clickRecommendCardSurface(r))return await s.fail("未能点击候选人卡片"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:"未能点击候选人卡片"};const c=await i.waitForResumeDialog(12e3),l=c.canvasReady;return c.iframeFound?(await s.succeed(`已打开 ${e.name} 的简历`),t.logger.info(`Opened resume for ${e.name}`),{success:!0,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:l,...l?{}:{error:"简历弹窗已出现,但 canvas 尚未就绪"}}):(await s.fail("简历弹窗未出现"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:!1,error:"点击后简历弹窗未出现"})}catch(e){return await(s?.fail("打开简历失败")),t.logger.warn(`Native zhipin open resume failed: ${e instanceof Error?e.message:String(e)}`),Xf(a,"打开简历失败,请重试")}finally{i?.close()}}});function Xf(e,t){return{success:!1,...void 0!==e?{candidateRef:e}:{},candidateName:"",candidateId:"",error:t}}function Zf(e){if(void 0!==e.candidateRef)return Pg(e.candidateRef);const t=_g(e);return{index:t,candidateRef:Ig(t),candidateId:""}}function Qf(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{mkdir as eh,writeFile as th}from"node:fs/promises";import{tmpdir as nh}from"node:os";import{dirname as rh,join as ah}from"node:path";import{defineTool as oh}from"@roll-agent/sdk";import{z as ih}from"zod";var sh,ch="data:image/png;base64,",lh=ih.object({success:ih.boolean(),imagePath:ih.string().optional(),captureMode:ih.enum(["canvas-data-url","viewport-clip","dom-screenshot"]).optional(),canvasSize:ih.object({width:ih.number(),height:ih.number()}).optional(),note:ih.string().optional(),error:ih.string().optional(),mcpImages:ih.array(ih.object({data:ih.string(),mimeType:ih.string()})).optional().describe("截图的 MCP image content(base64);由 SDK 转为 image block 返回,调用方无需解析")});function dh(){return{openNativePagePort:Ni,writePngFile:async(e,t)=>{await eh(rh(e),{recursive:!0}),await th(e,Buffer.from(t,"base64"))},now:()=>Date.now(),...sh}}var uh=oh({name:"zhipin_capture_resume",description:"截取当前打开的简历详情弹窗中的 canvas 简历为 PNG 图片文件并返回 imagePath。简历内容是 canvas 图像渲染,无法从 DOM/文本读取;编排器应在调用后用自身的多模态图像能力读取该文件来理解简历内容。需先用 zhipin_open_resume 打开简历弹窗",input:ih.object({outputPath:ih.string().min(1).optional().describe("PNG 输出文件的绝对路径;缺省时写入系统临时目录")}),output:lh,execute:async(e,t)=>{t.logger.info("Capturing resume canvas through native backend");const n=dh();let r,a;try{r=await n.openNativePagePort(),a=new aa(r),await a.begin("正在读取简历");const o=await r.waitForResumeDialog(12e3);if(!o.iframeFound)return await a.fail("简历弹窗未打开"),{success:!1,error:"简历弹窗未打开,请先调用 zhipin_open_resume"};if(!o.canvasReady)return await a.fail("简历内容未加载完成"),{success:!1,error:"简历 canvas 未加载完成,请稍后重试"};const i=await r.captureResumeCanvas(async e=>{await(a?.begin(ph(e)))});if(!i.found||void 0===i.canvasSize)return await a.fail("未找到简历内容"),{success:!1,error:i.error??"未找到简历 canvas"};let s,c,l;if(!0!==i.blank&&void 0!==i.dataUrl&&i.dataUrl.startsWith(ch))s=i.dataUrl.slice(ch.length),c="canvas-data-url";else if(!0===i.blank){const e=await r.readResumeDialogClipArea();if(void 0===e)return await a.fail("未找到简历弹窗区域"),{success:!1,error:"简历 canvas 为空白且未找到弹窗区域,无法截图"};await gh(a),s=await r.captureViewportClip(e,2),c="dom-screenshot",l="此简历为 DOM 文本渲染(canvas 为空),已改为截取弹窗可见区域;若弹窗内有滚动内容可能未完整覆盖"}else{if(void 0===i.screenshotArea)return await a.fail("简历坐标不完整"),{success:!1,error:"简历 canvas 坐标不完整,无法截图"};await gh(a),s=await r.captureViewportClip(i.screenshotArea),c="viewport-clip",l=`canvas.toDataURL 不可用${void 0!==i.dataUrlError?`(${i.dataUrlError})`:""},已回退为视口区域截图,超出可视区的简历内容可能被截断`}const d=e.outputPath??ah(nh(),"roll-browser-use",`resume-${String(n.now())}.png`);return await n.writePngFile(d,s),await a.succeed("简历读取完成"),t.logger.info(`Resume captured to ${d} (${c})`),{success:!0,imagePath:d,captureMode:c,canvasSize:i.canvasSize,...void 0!==l?{note:l}:{},mcpImages:[{data:s,mimeType:"image/png"}]}}catch(e){return await(a?.fail("简历读取失败")),t.logger.warn(`Native zhipin capture resume failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,error:"简历读取失败,请重试"}}finally{r?.close()}}}),mh=240;function ph(e){if(e.totalPx>0){const t=Math.min(99,Math.round(e.scrolledPx/e.totalPx*100));return t>0?`正在读取简历 ${String(t)}%`:"正在读取简历"}return`正在读取简历 · 第 ${String(e.round+1)} 屏`}async function gh(e){await e.clear()&&await new Promise(e=>setTimeout(e,mh))}import{defineTool as fh}from"@roll-agent/sdk";import{z as hh}from"zod";var yh,bh=hh.object({success:hh.boolean(),closed:hh.boolean(),method:hh.enum(["close-button","escape"]).optional(),error:hh.string().optional()});function vh(){return{openNativePagePort:Ni,...yh}}var wh=fh({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:hh.object({}),output:bh,execute:async(e,t)=>{t.logger.info("Closing resume detail modal through native backend");const n=vh();let r;try{r=await n.openNativePagePort();const e=await r.closeResumeDialog();return e.closed?(t.logger.info(`Resume modal closed via ${e.method??"unknown"}`),{success:!0,closed:!0,...void 0!==e.method?{method:e.method}:{}}):{success:!1,closed:!1,error:e.error??"简历弹窗未关闭"}}finally{r?.close()}}});import{defineTool as xh}from"@roll-agent/sdk";import{z as Sh}from"zod";import{waitForSelector as Rh}from"@roll-agent/browser";var Ch={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 Ih,waitForSelector as kh}from"@roll-agent/browser";var Ah="https://www.yupao.com",Eh=`${Ah}/chat`,_h=`${Ah}/login`;async function Nh(e,t={}){e.url().includes("/chat")||await Ih(e,Eh,t),await kh(e,Ch.messageList.container,{timeout:15e3})}async function Mh(e,t,n={}){const r=`${Ah}/chat?id=${encodeURIComponent(t)}`;await Ih(e,r,n),await kh(e,Ch.chat.input,{timeout:15e3})}async function Ph(e,t){await Nh(e),await Rh(e,Ch.messageList.item,{timeout:1e4});const n=Ch.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],r=t.maxItems?e.slice(0,t.maxItems):e;for(const e of r){const r=e.querySelector(t.sel.candidateName),a=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:r?.textContent?.trim()??"",lastMessage:a?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var Th=Sh.object({limit:Sh.number().optional().describe("最多返回的消息条数")}),Bh=Sh.object({conversationId:Sh.string(),candidateName:Sh.string(),lastMessage:Sh.string(),unreadCount:Sh.number(),timestamp:Sh.string()}),Dh=Sh.object({messages:Sh.array(Bh),total:Sh.number()}),Oh=xh({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Th,output:Dh,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=ue(),r=await n.getPage("yupao"),a=await Ph(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});import{defineTool as $h}from"@roll-agent/sdk";import{z as Fh}from"zod";import{BrowserActionApprovalSchema as jh}from"@roll-agent/browser";import{clickElement as Lh,isBrowserActionPolicyError as qh,typeText as zh,waitForSelector as Vh}from"@roll-agent/browser";async function Jh(e,t,n,r={}){try{return await Mh(e,t,r),await zh(e,Ch.chat.input,n,r),await Lh(e,Ch.chat.sendButton,r),await Vh(e,Ch.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(qh(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var Uh=Fh.object({conversationId:Fh.string().describe("对话 ID"),message:Fh.string().describe("要发送的回复消息"),browserActionApproval:jh.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Hh=Fh.object({success:Fh.boolean(),conversationId:Fh.string(),sentMessage:Fh.string(),error:Fh.string().optional()}),Wh=$h({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:Uh,output:Hh,execute:async(e,t)=>{const{conversationId:n,message:r}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const a=ue(),o=de(),i=rn(t,o,{action:"yupao_send_reply",target:n,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await a.getPage("yupao");let c;try{c=await Jh(s,n,r,nn(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=an(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:r,error:c.error}}});import{BrowserRuntimeConfigSchema as Kh,BrowserRuntimeModeSchema as Gh,BrowserChannelSchema as Yh,BrowserProfileColorSchema as Xh,BrowserWindowBoundsSchema as Zh,PlatformSchema as Qh}from"@roll-agent/browser";import{z as ey}from"zod";var ty=ey.object({platform:Qh.optional(),mode:Gh.default("managed-cdp"),headless:ey.boolean().optional(),cdpUrl:ey.string().optional(),cdpHost:ey.string().default("127.0.0.1"),cdpPort:ey.number().int().min(1).max(65535).optional(),channel:Yh.default("chrome"),executablePath:ey.string().optional(),userDataDir:ey.string().trim().min(1),sessionsDir:ey.string().trim().min(1).optional(),args:ey.array(ey.string()).optional(),profileName:ey.string().trim().min(1).optional(),profileColor:Xh.optional(),windowBounds:Zh.optional(),trackingAgentId:ey.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:ey.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==e.mode&&"existing-session"!==e.mode||void 0!==e.cdpUrl||t.addIssue({code:ey.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),ny=ey.object({defaultInstance:ey.string().trim().min(1).optional(),instances:ey.record(ey.string(),ty).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:ey.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${e.defaultInstance}" is not declared in browser.instances`});const n=new Map,r=new Map;for(const[a,o]of Object.entries(e.instances)){if(void 0!==o.cdpPort){const e=n.get(o.cdpPort);void 0!==e?t.addIssue({code:ey.ZodIssueCode.custom,path:["instances",a,"cdpPort"],message:`cdpPort ${String(o.cdpPort)} is already used by browser instance "${e}"`}):n.set(o.cdpPort,a)}const e=r.get(o.userDataDir);void 0!==e?t.addIssue({code:ey.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function ry(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 ay(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 oy(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 iy(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 sy(e){if(void 0===e)return;let t;try{t=JSON.parse(e)}catch(e){throw new Error(`BROWSER_INSTANCES_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}return ny.parse(t)}function cy(e=process.env){return Kh.parse({mode:e.BROWSER_MODE,headless:ry(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:ay(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:oy(e.BROWSER_ARGS_JSON),profileColor:e.BROWSER_PROFILE_COLOR,sessionsDir:e.BROWSER_SESSIONS_DIR,security:iy(e.BROWSER_SECURITY_JSON)})}function ly(e=process.env){return sy(e.BROWSER_INSTANCES_JSON)}import{z as dy}from"zod";import{StructuredToolError as uy}from"@roll-agent/sdk";var my=new Map;async function py(e,t,n={}){const{signal:r,onWait:a}=n;hy(e,r,0);const o=my.get(e);let i;const s=new Promise(e=>{i=e});my.set(e,s);const c=performance.now();if(void 0!==o)try{await fy(o,r)}catch(t){throw o.then(()=>{i(),by(e,s)}),t instanceof gy&&yy(e,vy(c)),t}try{return void 0!==o&&a?.(vy(c)),hy(e,r,vy(c)),await t()}finally{i(),by(e,s)}}var gy=class extends Error{constructor(){super("Aborted while queued"),this.name="QueueAbortError"}};async function fy(e,t){if(void 0!==t){if(t.aborted)throw new gy;await new Promise((n,r)=>{const a=()=>{r(new gy)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new gy):n()})})}else await e}function hy(e,t,n){!0===t?.aborted&&yy(e,n)}function yy(e,t){throw new uy({code:"cancelled_while_queued",message:`Request was cancelled while waiting for another in-flight operation on browser instance "${e}". The tool did not execute.`,details:{browserInstance:e,queuedMs:Math.round(t)}})}function by(e,t){my.get(e)===t&&my.delete(e)}function vy(e){return performance.now()-e}import{StructuredToolError as wy}from"@roll-agent/sdk";function xy(e){if(void 0===e)return;const t=ge().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new wy({code:"platform_mismatch",message:`Tool platform "${e}" does not match browser instance "${t.id}" platform "${t.platform}".`,details:{browserInstance:t.id,expectedPlatform:t.platform,requestedPlatform:e}})}function Sy(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var Ry={browserInstance:dy.string().trim().min(1).optional().describe("可选 browser instance id;多浏览器实例时用于选择目标 profile/CDP runtime。")};function Cy(e){return e.startsWith("zhipin_")?"zhipin":e.startsWith("yupao_")?"yupao":void 0}function Iy(e,t={}){const n=Ay(e.input);if(void 0===n)return e;const r=t.startRuntime??!0,a=t.serializePageOps??!0,o=t.expectedPlatform??Cy(e.name);return{...e,input:n,execute:async(t,n)=>{const i=t;return await G(i.browserInstance,async()=>{void 0!==i.browserInstance&&ge().getBundle(),xy(ky(t,o));const s=async()=>(r&&await fe(),await e.execute(t,n));if(!a)return await s();const c=ge().getBundle().id;return await py(c,s,{...void 0!==n.signal?{signal:n.signal}:{},onWait:t=>{n.logger.info(`${e.name} waited ${Math.round(t)}ms for an in-flight operation on browser instance "${c}" (same-instance page operations are serialized)`)}})})}}}function ky(e,t){const n=Sy(e);return t??n}function Ay(e){if(e instanceof dy.ZodObject)return e.extend(Ry);if(e instanceof dy.ZodEffects){const t=Ay(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?dy.ZodEffects.createWithPreprocess(n.transform,t,e._def):dy.ZodEffects.create(t,n,e._def)}}var Ey=t("browser-use-agent");async function _y(){const e=(await Promise.allSettled([dm(),ve()])).filter(e=>"rejected"===e.status).map(e=>e.reason);if(e.length>0)throw new AggregateError(e,"Failed to fully shut down browser-use-agent")}function Ny(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e}var My=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function Py(e){return Ny(e.name)?e:Iy(e,{startRuntime:"browser_status"!==e.name,serializePageOps:!My.has(e.name)})}var Ty=e({name:"browser-use-agent",tools:[ft,jt,In,Jn,er,sr,ya,Na,Fa,Gs,Oc,Wc,Qc,Tl,Xd,fu,fp,Ap,Tp,Jp,pf,af,Nf,mg,Jg,zf,Yf,uh,wh,Oh,Wh,xe,Rt].map(Py)},{onShutdown:_y});async function By(){lt(ct()),lm({logger:Ey}),await le(cy(),ly());try{await Dm(),me(!0)}catch(e){me(!1),Ey.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Ty.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}By().catch(async e=>{Ey.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await _y().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 r}from"zod";import{createAgentLogger as a}from"@roll-agent/sdk";import"@roll-agent/browser";import{AsyncLocalStorage as o}from"node:async_hooks";import{access as i}from"node:fs/promises";import{constants as s}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{createHash as d}from"node:crypto";import{StructuredToolError as u}from"@roll-agent/sdk";import{BrowserContextManager as m,BrowserRuntime as p,BrowserRuntimeConfigSchema as g,probeBrowserRuntimeCdpHealth as f,SessionStore as h}from"@roll-agent/browser";import{execFileSync as y}from"node:child_process";var b,v={x:0,y:0,width:1920,height:1080},w=25,x=80,S=4,R=["Add-Type -AssemblyName System.Windows.Forms;","$area = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea;","[pscustomobject]@{ x = $area.X; y = $area.Y; width = $area.Width; height = $area.Height } | ConvertTo-Json -Compress"].join(" ");function C(){return b??=A(),b}function I(e){if(e<=1)return[1];if(2===e)return[2];if(3===e)return[3];if(4===e)return[2,2];const t=Math.ceil(e/S),n=Math.floor(e/t),r=e%t;return Array.from({length:t},(e,t)=>n+(t<r?1:0))}function k(e){if(!Number.isInteger(e.index)||e.index<0||!Number.isInteger(e.total)||e.total<=0||e.index>=e.total)throw new RangeError("Auto window layout index must be within the configured instance count.");const t=I(e.total),n=t.length;let r=0,a=e.index;for(const[e,n]of t.entries()){if(a<n){r=e;break}a-=n}const o=t[r]??1,i=e.workArea.x+Math.floor(e.workArea.width*a/o),s=e.workArea.x+Math.floor(e.workArea.width*(a+1)/o),c=e.workArea.y+Math.floor(e.workArea.height*r/n);return{x:i,y:c,width:s-i,height:e.workArea.y+Math.floor(e.workArea.height*(r+1)/n)-c}}function A(){const e=E(process.env.ROLL_BROWSER_WORK_AREA);if(void 0!==e)return e;if("darwin"===process.platform){const e=B();if(void 0!==e)return{x:e.x,y:e.y+w,width:e.width,height:Math.max(400,e.height-w-x)}}if("linux"===process.platform){const e=O();if(void 0!==e)return e}if("win32"===process.platform){const e=D();if(void 0!==e)return e}return v}function E(e){if(void 0===e||0===e.trim().length)return;const t=e.split(",").map(e=>Number.parseInt(e.trim(),10));if(4!==t.length||t.some(e=>!Number.isFinite(e)||e<0))return;const[n,r,a,o]=t;return a<=0||o<=0?void 0:{x:n,y:r,width:a,height:o}}function _(e){const t=N(e,/UI Looks like:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==t)return t;for(const t of e.split("\n")){if(!t.includes("Resolution:")||/\bRetina\b/i.test(t))continue;const e=N(t,/Resolution:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==e)return e}}function N(e,t){const n=e.match(t);if(void 0===n?.[1]||void 0===n[2])return;const r=Number.parseInt(n[1],10),a=Number.parseInt(n[2],10);return r<=0||a<=0?void 0:{x:0,y:0,width:r,height:a}}function M(e){const t=e.trim().replace(/^\uFEFF/,"");if(0===t.length)return;let n;try{n=JSON.parse(t)}catch{return}if(!P(n))return;const r=T(n.x),a=T(n.y),o=T(n.width),i=T(n.height);return void 0===r||void 0===a||void 0===o||void 0===i||o<=0||i<=0?void 0:{x:r,y:a,width:o,height:i}}function P(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function T(e){if("number"==typeof e&&Number.isInteger(e))return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e.trim());return Number.isInteger(t)?t:void 0}}function B(){try{return _(y("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function D(){try{return M(y("powershell.exe",["-NoProfile","-NonInteractive","-Command",R],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function O(){try{const e=y("xrandr",["--current"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).match(/(\d+)x(\d+)\+/);if(void 0===e?.[1]||void 0===e[2])return;const t=Number.parseInt(e[1],10),n=Number.parseInt(e[2],10);if(t<=0||n<=0)return;return{x:0,y:0,width:t,height:n}}catch{return}}var $,F,j,L,q="default",z=l(c(),".roll-agent","browser","sessions"),V=["#2563EB","#DC2626","#16A34A","#D97706","#7C3AED","#0891B2","#DB2777","#65A30D"],J=["stopped","not_running","not_found","failed"],U=new o,H=class{bundles=new Map;defaultInstanceId;startPromises=new Map;constructor(e,t){if(void 0===t||0===Object.keys(t.instances).length){const t=ne({id:q,runtimeConfig:e});return this.bundles.set(t.id,t),void(this.defaultInstanceId=t.id)}this.defaultInstanceId=t.defaultInstance;const n=Object.entries(t.instances);for(const[t,[r,a]]of n.entries()){const o=ne({id:r,runtimeConfig:Y(e,r,a,{index:t,total:n.length}),...void 0!==a.platform?{platform:a.platform}:{},...void 0!==a.trackingAgentId?{trackingAgentId:a.trackingAgentId}:{}});this.bundles.set(r,o)}}getDefaultInstanceId(){return this.defaultInstanceId}listBundles(){return[...this.bundles.values()]}isLegacySingleInstancePool(){return 1===this.bundles.size&&this.bundles.keys().next().value===q}resolvePrimaryInstanceId(){return this.defaultInstanceId??this.getOnlyInstanceId()}async ensureBundleStarted(e){const t=this.getBundle(e);return await this.ensureStarted(t.id),t}async ensureStarted(e){const t=this.bundles.get(e);if(void 0===t)throw new Error(`Browser instance "${e}" was not found.`);if(t.runtime.isRunning())return;const n=this.startPromises.get(e);if(void 0!==n)return void await n;const r=t.runtime.start().catch(async t=>{throw this.startPromises.delete(e),t});this.startPromises.set(e,r);try{await r}finally{this.startPromises.delete(e)}}getBundle(e){const t=e??U.getStore()??this.defaultInstanceId??this.getOnlyInstanceId();if(void 0===t)throw new u({code:"needs_input",message:"browserInstance is required because multiple browser instances are configured.",details:{availableInstances:[...this.bundles.keys()]}});const n=this.bundles.get(t);if(void 0===n)throw new u({code:"browser_instance_not_found",message:`Browser instance "${t}" was not found.`,details:{availableInstances:[...this.bundles.keys()]}});return n}async getInstanceStatuses(){return await Promise.all(this.listBundles().map(async e=>{const[t,n]=await Promise.all([f(e.config),re(e.config.userDataDir)]);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},mode:e.config.mode,cdp:t,profile:n,tracking:oe(e.trackingAgentId)}}))}async closeAll(){const e=(await this.closeInstances([...this.bundles.keys()])).filter(e=>"failed"===e.status).map(e=>new Error(`Failed to stop browser runtime for ${e.browserInstance}: ${e.message??"unknown error"}`));if(e.length>0)throw new AggregateError(e,"Browser instance pool shutdown failed")}async closeInstances(e){const t=[...new Set(e)];return await Promise.all(t.map(async e=>await this.closeInstance(e)))}async closeInstance(e){const t=this.bundles.get(e);if(void 0===t)return{browserInstance:e,status:"not_found",message:`Browser instance "${e}" was not found.`};const n=[],r=this.startPromises.get(e);if(void 0!==r)try{await r}catch(t){n.push(new Error(`Failed to finish browser startup for ${e}`,{cause:t}))}if(!t.runtime.isRunning())return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"not_running",mode:t.config.mode};try{await t.contextManager.closeAll()}catch(t){n.push(new Error(`Failed to close browser contexts for ${e}`,{cause:t}))}try{"managed-cdp"===t.config.mode?await t.runtime.stop():await t.runtime.disconnect()}catch(t){n.push(new Error(`Failed to stop browser runtime for ${e}`,{cause:t}))}return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"stopped",mode:t.config.mode}}getOnlyInstanceId(){if(1===this.bundles.size)return this.bundles.keys().next().value}};function W(e){return e.map(e=>void 0===e.cause?e.message:`${e.message}: ${K(e.cause)}`).join("; ")}function K(e){return e instanceof Error?e.message:String(e)}async function G(e,t){return await U.run(e,t)}function Y(e,t,n,r){const a=n.headless??e.headless,o=n.windowBounds??te({instance:n,headless:a,...r});return g.parse({...e,mode:n.mode,headless:a,instanceId:t,profileName:n.profileName??t,profileColor:n.profileColor??X(r.index),cdpUrl:n.cdpUrl,cdpHost:n.cdpHost,cdpPort:n.cdpPort,channel:n.channel,executablePath:n.executablePath,userDataDir:n.userDataDir,args:n.args,windowBounds:o,sessionsDir:n.sessionsDir??l(z,t)})}function X(e){return V[e]??Z(e)}function Z(e){return Q(137.508*e%360,.68,.45)}function Q(e,t,n){const r=(1-Math.abs(2*n-1))*t,a=e/60,o=r*(1-Math.abs(a%2-1));let i,s,c;[i,s,c]=a<1?[r,o,0]:a<2?[o,r,0]:a<3?[0,r,o]:a<4?[0,o,r]:a<5?[o,0,r]:[r,0,o];const l=n-r/2;return`#${ee(i+l)}${ee(s+l)}${ee(c+l)}`}function ee(e){return Math.round(255*e).toString(16).padStart(2,"0").toUpperCase()}function te(e){if(!(e.total<=1||e.headless||"managed-cdp"!==e.instance.mode))return k({index:e.index,total:e.total,workArea:C()})}function ne(e){const t=new h(e.runtimeConfig.sessionsDir),n=new p(e.runtimeConfig),r=new m(n,t);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},...void 0!==e.trackingAgentId?{trackingAgentId:e.trackingAgentId}:{},runtime:n,contextManager:r,sessionStore:t,config:e.runtimeConfig}}async function re(e){if(void 0===e)return{exists:!1,writable:!1};return{userDataDir:e,exists:await ae(e,s.F_OK),writable:await ae(e,s.W_OK)}}async function ae(e,t){try{return await i(e,t),!0}catch{return!1}}function oe(e){if(void 0!==e)return{source:"instance",agentIdFingerprint:ie(e)};const t=process.env.RECRUITMENT_EVENTS_DEFAULT_AGENT_ID?.trim();return void 0!==t&&t.length>0?{source:"default-env",agentIdFingerprint:ie(t)}:{source:"missing"}}function ie(e){return d("sha256").update(e).digest("hex").slice(0,8)}var se=!1,ce=a("browser-use-agent");async function le(e,t){if($||F)return;se=!1;const n=($=new H(e,t)).getDefaultInstanceId(),r=$.listBundles()[0],a=void 0!==n?$.getBundle(n):r;void 0!==a&&(F=a.runtime,j=a.contextManager,L=a.sessionStore)}function de(){if($)return $.getBundle().runtime;if(!F)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return F}function ue(){if($)return $.getBundle().contextManager;if(!j)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return j}function me(e){se=e}function pe(){return se}function ge(){if(!$)throw new Error("BrowserInstancePool not initialized. Call initRuntime() first.");return $}async function fe(){await ge().ensureBundleStarted()}function he(){return void 0!==$&&!$.isLegacySingleInstancePool()}function ye(e){if(!$)return e;let t;try{t=$.getBundle()}catch{return}return t.trackingAgentId??e}function be(){return ge().getBundle()}async function ve(){const e=$,t=j,n=F,r=[];if($=void 0,j=void 0,F=void 0,L=void 0,se=!1,e){ce.info("Closing browser instance pool...");try{await e.closeAll()}catch(e){r.push(new Error("Failed to close browser instance pool",{cause:e})),ce.error(`Failed to close browser instance pool: ${e instanceof Error?e.stack??e.message:String(e)}`)}}else if(t){ce.info("Closing browser contexts...");try{await t.closeAll()}catch(e){r.push(new Error("Failed to close browser contexts",{cause:e})),ce.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(!e&&n){ce.info("Stopping browser process...");try{await n.stop()}catch(e){r.push(new Error("Failed to stop browser runtime",{cause:e})),ce.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(ce.info("Browser runtime shutdown complete"),r.length>0)throw new AggregateError(r,"Browser runtime shutdown failed")}var we=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),xe=n({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:we,execute:async(e,t)=>{const n=de();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as Se}from"@roll-agent/sdk";import{z as Re}from"zod";import{BrowserSecurityConfigSchema as Ce,BrowserStatusSchema as Ie}from"@roll-agent/browser";import{createHash as ke}from"node:crypto";import{z as Ae}from"zod";var Ee,_e,Ne=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_TIMEOUT_MS","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_INSTANCES_JSON","BROWSER_USE_POLICY_JSON","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],Me=/^[0-9a-f]{8}$/,Pe=Ae.object({present:Ae.boolean(),fingerprint:Ae.string().regex(Me).optional()}),Te=Ae.record(Pe);function Be(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:De(n)}]:[e,{present:!1}]}))}function De(e){return ke("sha256").update(e).digest("hex").slice(0,8)}function Oe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function $e(){return void 0!==Ee?Ee:Oe(process.env.BROWSER_VISUAL_CURSOR)??!0}function Fe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function je(){return void 0!==_e?_e:Fe(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Le}from"@roll-agent/sdk";import{z as qe}from"zod";import{randomUUID as ze}from"node:crypto";import{z as Ve}from"zod";var Je=Ve.object({id:Ve.string().trim().min(1)}),Ue=new Map;function He(e=Date.now()){for(const[t,n]of Ue)n.expiresAtMs<=e&&Ue.delete(t)}function We(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function Ke(e,t,n=Date.now()){He(n);const r=ze(),a=n+t;return Ue.set(r,{id:r,tool:e.tool,target:e.target,digest:e.digest,...void 0!==e.summary?{summary:e.summary}:{},expiresAtMs:a}),{id:r,expiresAt:new Date(a).toISOString(),tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{},retryInput:{toolActionApproval:{id:r}}}}function Ge(e){return He(),!!Ye(e)&&(Ue.delete(e.approval.id),!0)}function Ye(e){He();const t=Ue.get(e.approval.id);return void 0!==t&&!!We(e.subject,t)}var Xe=["log","deny","confirm"],Ze=qe.enum(Xe),Qe=["zhipin_send_prepared_reply"],et=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],tt=qe.object({code:qe.enum(et),message:qe.string()}),nt=qe.object({policy:Ze}),rt=qe.object({approvalTtlMs:qe.number().int().positive().max(36e5).default(3e5),tools:qe.record(nt).default({})}),at=rt.parse({}),ot=new Set(Qe),it=at;function st(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 ct(e=process.env){try{return rt.parse(st(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 lt(e){it=rt.parse(e)}function dt(){return it}function ut(e){const t=e.toolPolicy??it,n=[];for(const e of Object.keys(t.tools))ot.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 mt(e,t){const n=it.tools[t.subject.tool],r=n?.policy??"log";if("log"===r)return e.logger.info(`Browser-use tool allowed by tool policy: ${t.subject.tool}`),{consumeApproval:()=>{}};if("deny"===r){const n="Tool execution denied by browser-use tool policy.";throw e.logger.warn(`${n} ${JSON.stringify(pt(t.subject,"tool_policy_deny"))}`),new Le({code:"action_denied",message:n,details:pt(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?Ye({approval:t.approval,subject:t.subject}):Ge({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&&Ge({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...pt(t.subject,"tool_policy_confirm"),approvalRequest:Ke(t.subject,it.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(pt(t.subject,"tool_policy_confirm"))}`),new Le({code:"needs_confirmation",message:a,details:o})}function pt(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var gt=Ie.extend({replyAuthorityKeysLoaded:Re.boolean(),visualCursorEnabled:Re.boolean(),visualActivityEnabled:Re.boolean(),security:Ce,toolPolicy:rt,policyWarnings:Re.array(tt),effectiveEnvSources:Te}),ft=Se({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:Re.object({}),output:gt,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=ge(),r=n.listBundles(),a=n.resolvePrimaryInstanceId(),o=r.find(e=>e.id===a)??r[0];if(void 0===o)throw new Error("BrowserInstancePool has no runtime bundles.");const i=r.some(e=>e.runtime.isRunning()),{headless:s,mode:c,security:l}=o.runtime.getConfig(),d=dt(),u=[];for(const e of r){const t=e.runtime,n=e.contextManager,r=e.sessionStore,a=n.getActivePlatforms();for(const o of a){const a=n.getPageCount(o),i=n.getCurrentUrl(o);let s=null,c="unknown";if(t.shouldRestoreSessionSnapshot()){const[e,t]=await Promise.all([r.loadCookies(o),r.loadLocalStorage(o)]);s=void 0!==e&&e.length>0||void 0!==t&&Object.keys(t).length>0,c=s?"snapshot":"none"}else t.usesPersistentProfile()&&(s=null,c="profile");u.push({browserInstance:e.id,platform:o,pagesOpen:a,currentUrl:i,hasLoginState:s,loginStateSource:c})}}return{running:i,headless:s,mode:c,activeSessions:u,defaultInstanceId:n.getDefaultInstanceId(),primaryInstanceId:a,instances:await n.getInstanceStatuses(),replyAuthorityKeysLoaded:pe(),visualCursorEnabled:$e(),visualActivityEnabled:je(),security:l,toolPolicy:d,policyWarnings:ut({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:Be(Ne)}}});import{BrowserRuntimeModeSchema as ht}from"@roll-agent/browser";import{StructuredToolError as yt,defineTool as bt}from"@roll-agent/sdk";import{z as vt}from"zod";var wt=vt.object({browserInstance:vt.string().trim().min(1).optional(),browserInstances:vt.array(vt.string().trim().min(1)).min(1).optional(),all:vt.boolean().optional()}).superRefine((e,t)=>{const n=void 0!==e.browserInstance||void 0!==e.browserInstances&&e.browserInstances.length>0;e.all&&n&&t.addIssue({code:vt.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),xt=vt.object({browserInstance:vt.string(),status:vt.enum(J),mode:ht.optional(),message:vt.string().optional()}),St=vt.object({ok:vt.boolean(),stopped:vt.number().int().nonnegative(),results:vt.array(xt)}),Rt=bt({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:wt,output:St,execute:async(e,t)=>{const n=ge(),r=Ct(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return It(await n.closeInstances(r))}});function Ct(e,t){if(!0===e.all)return t;const n=[...void 0!==e.browserInstance?[e.browserInstance]:[],...e.browserInstances??[]],r=[...new Set(n)];if(0===r.length)throw new yt({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function It(e){return{ok:e.every(e=>"not_found"!==e.status&&"failed"!==e.status),stopped:e.filter(e=>"stopped"===e.status).length,results:[...e]}}import{defineTool as kt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as At,PlatformSchema as Et}from"@roll-agent/browser";import{z as _t}from"zod";import{PLATFORMS as Nt}from"@roll-agent/browser";var Mt={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Pt(e){return new URL(Mt[e]).host}function Tt(e,t){try{return new URL(e).host.includes(Pt(t))}catch{return!1}}function Bt(e){return Nt.find(t=>Tt(e,t))}function Dt(e,t){const n=Bt(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 Ot(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:Bt(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var $t=_t.object({platform:Et.optional().describe("可选:仅返回指定平台相关的页面")}),Ft=_t.object({pages:_t.array(At)}),jt=kt({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:$t,output:Ft,execute:async(e,t)=>{const n=ue();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>Dt(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as Lt}from"node:timers/promises";import{defineTool as qt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as zt,BrowserPageInfoSchema as Vt}from"@roll-agent/browser";import{z as Jt}from"zod";import{StructuredToolError as Ut}from"@roll-agent/sdk";import{isBrowserActionPolicyError as Ht,preflightBrowserAction as Wt}from"@roll-agent/browser";import{randomUUID as Kt}from"node:crypto";var Gt=6e4,Yt=new Map;function Xt(e=Date.now()){for(const[t,n]of Yt)n.expiresAtMs<=e&&Yt.delete(t)}function Zt(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function Qt(e,t=Date.now()){Xt(t);const n=Kt(),r=t+Gt,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return Yt.set(n,a),{id:n,expiresAt:new Date(r).toISOString(),action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},retryInput:{browserActionApproval:{id:n}}}}function en(e){Xt();const t=Yt.get(e.approval.id);return void 0!==t&&(!!Zt(e.details,t)&&(Yt.delete(e.approval.id),!0))}function tn(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function nn(e,t,n={}){return{security:tn(t,n),approveAction:en,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function rn(e,t,n){const r=Wt({security:t.getConfig().security,action:n.action,target:n.target,...void 0!==n.url?{url:n.url}:{}});if(r.ok)return r.log&&e.logger.info(r.message),{approvedByConfirmation:!1};if("needs_confirmation"===r.code&&void 0!==n.approval&&en({approval:n.approval,details:r.details}))return e.logger.info(`Browser action approved by browserActionApproval: ${n.action} ${n.target}`),{approvedByConfirmation:!0};throw e.logger.warn(`${r.message} ${JSON.stringify(r.details)}`),new Ut({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:Qt(r.details)}:{}}})}function an(e){if(Ht(e))return new Ut({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:Qt(e.payload.details)}:{}}})}function on(){try{return de()}catch{return}}async function sn(e){await e.bringToFront().catch(()=>{})}async function cn(e,t={}){const n=t.runtime??on();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await sn(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await sn(e)}var ln,dn=15e3,un=250,mn=["/web/chat"],pn=Jt.object({url:Jt.string().url().describe("要导航到的目标 URL"),browserActionApproval:zt.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),gn=Jt.object({success:Jt.boolean(),page:Vt});function fn(){return{getContextManager:ue,getRuntime:de,detectPlatformFromUrl:Bt,matchesPlatformHost:Tt,toNativePageInfo:Dt,delay:Lt,...ln}}function hn(e){return"object"==typeof e&&null!==e}function yn(e){return!!hn(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function bn(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==mn.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function vn(e,t,n,r,a){const o=n.detectPlatformFromUrl(r),i=await t.listNativePages();if(o){const s=i.find(e=>n.matchesPlatformHost(e.url,o));if(s)return a.info(`Reusing native ${o} page for navigation`),await t.activateNativePage(s.targetId),e.rememberNativePageSelection(o,s),{page:s,platform:o};a.info(`Opening native ${o} page for navigation`);const c=await t.openNativePage(r);return e.rememberNativePageSelection(o,c),{page:c,platform:o}}return a.info("Opening a new native page for non-platform navigation"),{page:await t.openNativePage(r)}}async function wn(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!yn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function xn(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<dn;){try{const t=await wn(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Sn(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(un)}throw new Error(`Native navigation did not reach document ready state within ${dn}ms${o?`: ${o.message}`:""}`)}function Sn(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 Rn(e,t,n){t.url!==n&&await e.navigate(n)}async function Cn(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var In=qt({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:pn,output:gn,execute:async(e,t)=>{const n=fn(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);bn(e.url,o);const i=rn(t,a,{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 vn(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await Rn(c,s.page,e.url);const o=await xn(c,n,e.url,s.page.url),i=await Cn(a,s.page,o),l=s.platform??n.detectPlatformFromUrl(i.url);return l&&(r.rememberNativePageSelection(l,i),t.logger.info(`Bound native navigated page to ${l}`)),{success:!0,page:n.toNativePageInfo(r,i)}}finally{c.close()}}});import{BrowserActionApprovalSchema as kn,BrowserPageInfoSchema as An}from"@roll-agent/browser";import{defineTool as En}from"@roll-agent/sdk";import{z as _n}from"zod";import{setTimeout as Nn}from"node:timers/promises";var Mn=15e3,Pn=250,Tn="__rollReloadMark";function Bn(e){return"object"==typeof e&&null!==e}function Dn(e){if(!Bn(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function On(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function $n(e){const t=await e.evaluateJson(`(() => ({ mark: window.${Tn} ?? null, readyState: document.readyState }))()`);if(!Dn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function Fn(e,t){const n=(t.createToken??On)(),r=t.now??(()=>Date.now()),a=t.delay??Nn,o=t.timeoutMs??Mn,i=t.pollMs??Pn;await e.evaluateJson(`(() => { window.${Tn} = ${JSON.stringify(n)}; return true; })()`),await e.reload({url:t.url,...void 0!==t.ignoreCache?{ignoreCache:t.ignoreCache}:{}}),t.onReloadSent?.();const s=r();let c;for(;r()-s<o;){try{const t=await $n(e);if(t.mark!==n&&("interactive"===t.readyState||"complete"===t.readyState))return}catch(e){c=e instanceof Error?e:new Error(String(e))}await a(i)}throw new Error(`Native page reload did not swap document within ${String(o)}ms${c?`: ${c.message}`:""}`)}var jn,Ln=_n.object({ignoreCache:_n.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:kn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),qn=_n.object({success:_n.boolean(),reloaded:_n.boolean(),page:An});function zn(){return{getContextManager:ue,getRuntime:de,toNativePageInfo:Dt,reloadNativePageAndWaitForSwap:Fn,...jn}}function Vn(e,t){const n=t.filter(t=>e.isNativePageSelected(t.targetId));if(1===n.length){const e=n[0];if(e)return e}if(n.length>1)throw new Error("Multiple selected native tabs found; select a single tab before reloading.");if(1===t.length){const e=t[0];if(e)return e}if(0===t.length)throw new Error("No native page is open; navigate before reloading.");throw new Error("Multiple native pages are open and none is selected; select the target tab before reloading.")}var Jn=En({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:Ln,output:qn,execute:async(e,t)=>{const n=zn(),r=n.getContextManager(),a=n.getRuntime(),o=Vn(r,await a.listNativePages()),i=rn(t,a,{action:"navigate",target:o.url,url:o.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Reloading native tab ${o.targetId} (${o.url})`);const s=await a.connectNativePage(o,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:o.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:a}),await n.reloadNativePageAndWaitForSwap(s,{url:o.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{}});const t=(await a.listNativePages()).find(e=>e.targetId===o.targetId)??o;return{success:!0,reloaded:!0,page:n.toNativePageInfo(r,t)}}finally{s.close()}}});import{defineTool as Un}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Hn,BrowserPageInfoSchema as Wn,PlatformSchema as Kn}from"@roll-agent/browser";import{z as Gn}from"zod";async function Yn(e,t){return(await e.listNativePages()).find(e=>Tt(e.url,t))}async function Xn(e,t,n={}){const r=await Yn(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(Mt[t],n),reusedExistingPage:!1}}var Zn=Gn.object({platform:Kn.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:Hn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Qn=Gn.object({success:Gn.boolean(),page:Wn,reusedExistingTab:Gn.boolean()}),er=Un({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:Zn,output:Qn,execute:async(e,t)=>{const{platform:n}=e,r=de(),a=ue(),o=Mt[n],i=rn(t,r,{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 Xn(r,n,nn(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:Dt(a,s),reusedExistingTab:c}}});import{defineTool as tr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as nr,PlatformSchema as rr}from"@roll-agent/browser";import{z as ar}from"zod";var or=ar.object({platform:rr.describe("要将该页面绑定为当前活跃页的平台"),pageId:ar.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),ir=ar.object({success:ar.boolean(),page:nr}),sr=tr({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:or,output:ir,execute:async(e,t)=>{const n=ue();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const r=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:Dt(n,r)}}});import{defineTool as cr}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as lr,BrowserPageInfoSchema as dr,createBrowserAxSnapshot as ur}from"@roll-agent/browser";import{z as mr}from"zod";import{BrowserElementRefStore as pr}from"@roll-agent/browser";var gr=new pr;async function fr(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 hr}from"node:crypto";var yr="data-roll-browser-action-",br=120;function vr(){return`${yr}${hr().replaceAll("-","")}`}function wr(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),br)):br}function xr(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Sr(e){if(!xr(e)||!xr(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Rr(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Cr(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ir(e);return void 0===t?[]:[t]}):[]}function Ir(e){if(!xr(e))return;const t=e.nodeId,n=e.backendNodeId,r=Rr(e.attributes),a=Ir(e.contentDocument),o=Ir(e.templateContent);return{..."number"==typeof t&&Number.isInteger(t)?{nodeId:t}:{},..."number"==typeof n&&Number.isInteger(n)?{backendNodeId:n}:{},...void 0!==r?{attributes:r}:{},children:Cr(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Cr(e.pseudoElements),shadowRoots:Cr(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function kr(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 Ar(e){if(!xr(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 Er(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ar(e);return void 0===t?[]:[t]}):[]}function _r(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function Nr(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 Mr(e,t){const n=new Map;for(const r of t){const t=kr(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Pr(e,t){const n=new Map,r=t=>{const a=kr(e,t.attributes);void 0!==a&&void 0!==t.backendNodeId&&n.set(a,t.backendNodeId);for(const e of t.children)r(e);void 0!==t.contentDocument&&r(t.contentDocument);for(const e of t.pseudoElements)r(e);for(const e of t.shadowRoots)r(e);void 0!==t.templateContent&&r(t.templateContent)};return r(t),n}function Tr(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 Br(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 Dr(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!xr(n))return new Map;const r=Ir(n.root);return void 0===r?new Map:Pr(t,r)}const r=Sr(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Mr(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function Or(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=wr(n.maxCandidates??br);if(0===r)return[];const a=vr(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(yr.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=Er(await e.evaluateJson(Tr(a,r),i).catch(()=>[]));try{if(0===s.length)return[];const t=await Dr(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:_r(e),name:e.name,hints:Nr(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Br(a),i).catch(()=>{})}}var $r=28,Fr=90,jr=250,Lr=620,qr=8,zr=32,Vr=8;function Jr(e){return new Promise(t=>{setTimeout(t,e)})}function Ur(e,t,n){return Math.min(Math.max(e,t),n)}function Hr(e,t){return Math.max(0,Math.floor(e??t))}function Wr(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Kr(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function Gr(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function Yr(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 Xr(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function Zr(e,t){const n=Kr(e,t);if(n<=qr)return[e,t];const r=Ur(Math.round(n/30),Vr,zr),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=Ur(.08*n,4,28)*Xr(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=Gr(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+a*m+s*(p+g)),y:Math.round(e.y+o*m+c*(p+g))})}return d.push(t),d}function Qr(e,t){const n=Wr(t);return Zr(void 0===e?Yr(n):Wr(e),n)}var ea=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??Jr,this.defaultStepDelayMs=Hr(t.stepDelayMs,$r)}reset(e){this.lastPoint=void 0===e?void 0:Wr(e)}async moveTo(e,t={}){const n=Hr(t.stepDelayMs,this.defaultStepDelayMs),r=Qr(this.lastPoint,e),a=Math.max(r.length-1,0)*n;await(t.motionObserver?.previewMouseMotion({points:r,durationMs:a}));for(let e=0;e<r.length;e+=1){const a=r[e];void 0!==a&&(await this.dispatcher.dispatchMouseEvent({type:"mouseMoved",x:a.x,y:a.y,...void 0!==t.button?{button:t.button}:{},buttons:t.buttons??0}),e<r.length-1&&n>0&&await this.sleep(n))}return this.lastPoint=Wr(e),{points:r,durationMs:a}}async click(e,t={}){const n=t.button??"left",r=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:Wr(e),durationMs:Lr})),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:"left"===n?1:0,clickCount:r}),await this.delayIfPositive(t.pressDurationMs??Fr),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:0,clickCount:r}),await this.delayIfPositive(t.settleMs??jr),this.lastPoint=Wr(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??Fr),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=Wr(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},ta=14,na=720;function ra(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 normalizeActivityViewport = (viewport) => {\n viewport.style.position = "fixed";\n viewport.style.inset = "0";\n viewport.style.opacity = "0";\n viewport.style.pointerEvents = "none";\n viewport.style.display = "none";\n viewport.style.border = "0";\n viewport.style.boxShadow = "none";\n viewport.style.borderRadius = "0";\n viewport.style.transform = "none";\n viewport.style.transition = "none";\n viewport.style.background = "transparent";\n };\n\n const showActivityViewportFrame = (viewport, theme, mode) => {\n normalizeActivityViewport(viewport);\n viewport.style.display = "block";\n viewport.style.opacity = mode === "complete" ? "0.28" : "0.22";\n viewport.style.background =\n "linear-gradient(180deg, " +\n theme.accentGlow +\n " 0%, transparent 16%, transparent 84%, " +\n theme.accentGlow +\n " 100%), linear-gradient(90deg, " +\n theme.accentGlow +\n " 0%, transparent 12%, transparent 88%, " +\n theme.accentGlow +\n " 100%)";\n viewport.style.boxShadow =\n "inset 0 0 0 2px " + theme.accentSoft + ", inset 0 0 28px " + theme.accentGlow;\n };\n\n const ensureActivityRoot = () => {\n let root = document.getElementById(activityRootId);\n if (root) {\n const viewport = document.getElementById(activityViewportId);\n if (viewport) normalizeActivityViewport(viewport);\n return root;\n }\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 normalizeActivityViewport(viewport);\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, mode) => {\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 showActivityViewportFrame(viewport, theme, mode);\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 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 normalizeActivityViewport(viewport);\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 applyTheme(args.activity.tone ?? "info", args.activity.mode);\n\n if (typeof args.activity.label === "string") {\n label.textContent = args.activity.label;\n }\n\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 normalizeActivityViewport(viewport);\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 aa=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!je()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...je()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??ta,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){$e()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){$e()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!je()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(ra(e))}catch{return!1}}};function oa(e){return new aa(e)}async function ia(e,t,n){const r=new ea(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var sa=mr.object({pageId:mr.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:mr.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:mr.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:mr.boolean().default(!0).describe("默认 true:只返回可交互节点")}),ca=mr.object({page:dr,snapshot:lr});function la(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 da(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 ua(e,t){const n=la(t);return(await Promise.all(n.map(async t=>{const n=await da(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function ma(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function pa(e){return e.refs.reduce((e,t)=>Math.max(e,ma(t.ref)),0)}function ga(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ga(n.nodes,t)]})}function fa(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=fa(e.children??[],t),a=void 0===n?[]:fa(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function ha(e){let t=pa(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await ua(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){r=!0;break}if(void 0===c.node.ref||a.has(c.frameId))continue;a.add(c.frameId);const l=await Or(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await ur(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 ua(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=pa(d),n=Math.max(0,n-d.nodeCount),r=r||d.truncated}if(0===o.length)return{...e.snapshot,truncated:r};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?ga(e.snapshot.nodes,s):fa(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}var ya=cr({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:sa,output:ca,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}});t.logger.info(`Creating browser AX snapshot for page ${a.targetId}`);const o=await n.connectNativePage(a),i=oa(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await Or(o,s),l=await ur(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await ha({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return gr.saveSnapshot(a.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:Dt(r,a),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as ba,defineTool as va}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as wa,BrowserElementRefHandleSchema as xa,clickElementRef as Sa}from"@roll-agent/browser";import{z as Ra}from"zod";import{BrowserElementRefHandleSchema as Ca}from"@roll-agent/browser";import{z as Ia}from"zod";var ka=Ia.enum(["backend_node_id","role_name_nth"]),Aa=Ia.object({ref:Ca,role:Ia.string(),name:Ia.string(),x:Ia.number(),y:Ia.number(),resolvedBy:ka,backendNodeId:Ia.number().int().positive().optional(),frameId:Ia.string().optional(),disabled:Ia.boolean()}),Ea=Ia.object({success:Ia.literal(!0),ref:Ca,resolvedBy:ka,target:Aa}),_a=Ra.object({ref:xa.describe("browser_snapshot 返回的 @eN element ref"),pageId:Ra.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:wa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Na=va({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:_a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new ba({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在点击 ${e.ref}`);const r=await Sa({controller:s,elementRef:o,options:{clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),r}catch(t){await oa(s).fail(`点击 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as Ma,defineTool as Pa}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ta,BrowserElementRefHandleSchema as Ba,typeElementRef as Da}from"@roll-agent/browser";import{z as Oa}from"zod";var $a=Oa.object({ref:Ba.describe("browser_snapshot 返回的 @eN element ref"),text:Oa.string().describe("要输入的文本"),clear:Oa.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:Oa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Ta.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Fa=Pa({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:$a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new Ma({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在输入到 ${e.ref}`);const r=await Da({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),r}catch(t){await oa(s).fail(`输入到 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as ja}from"@roll-agent/sdk";import{BrowserPageInfoSchema as La}from"@roll-agent/browser";import{z as qa}from"zod";import{setTimeout as za}from"node:timers/promises";function Va(e){return e.trim().toLocaleLowerCase("zh-CN")}function Ja(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function Ua(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=Va(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>Va(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=Va(e.name);return n.includes(t)||t.includes(n)}),a)return a;const o=t.length<=2?1:t.length<=4?.75:.6;if(a=r.find(e=>{const n=Va(e.name);return Ja(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var Ha=["chat-list","chat-history","recommend-list"],Wa={"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 Ka(e){return Wa[e]}import{StructuredToolError as Ga,isStructuredToolError as Ya}from"@roll-agent/sdk";var Xa="zhipin_access_restricted",Za=["/web/passport/zp/403.html","/web/common/403.html","/web/passport/cm/403.html"],Qa=["/web/passport/zp/verify.html","/web/user/safe/verify-slider","/web/passport/cm/verify.html"],eo=["/web/passport/zp/security.html","/web/common/security-check.html"],to=["/web/user"],no="38",ro={31:"ip_block","-1000031":"ip_block",5002:"ip_block",32:"uid_block",5003:"uid_block",5004:"uid_block"},ao={"访问受限":"ip_block","安全验证":"verify"},oo={ip_block:"IP 级封禁:请等待页面标明的解封时间;刷新、换工具、换 browserInstance/profile 均无效。",uid_block:"账号级封禁:请等待账号解封;刷新、换工具、换 browserInstance/profile 均无效。",verify:"请在浏览器中手动完成验证并等待后再操作,不要立即重试。",security:"请在浏览器中手动完成安全检查并等待后再操作,不要立即重试。",anti_spider_login:"站点反爬拦截要求登录:请人工登录并等待后再操作,不要自动重试。"};function io(e){const t=e.url.trim(),n=(e.title??"").trim(),r=mo(t);if(uo(r,Za))return{kind:ro[po(t)??""]??"ip_block",url:t,title:n};if(uo(r,Qa))return{kind:"verify",url:t,title:n};if(uo(r,eo))return{kind:"security",url:t,title:n};if(uo(r,to)&&po(t)===no)return{kind:"anti_spider_login",url:t,title:n};for(const e of Object.keys(ao))if(n.includes(e))return{kind:ao[e],url:t,title:n};return null}function so(e){const t=io(e);if(null!==t)throw co(t)}function co(e){return new Ga({code:Xa,message:`BOSS 风控页已出现(${e.kind})。${oo[e.kind]}`,details:{kind:e.kind,url:e.url,title:e.title}})}function lo(e){if(Ya(e))throw e}function uo(e,t){return t.some(t=>e===t||e.startsWith(`${t}/`))}function mo(e){try{return new URL(e).pathname}catch{const t=e.split("#")[0]??e,n=t.split("?")[0]??t,r=n.indexOf("://"),a=r>=0?n.slice(r+3):n,o=a.indexOf("/");return o>=0?a.slice(o):a}}function po(e){try{return new URL(e).searchParams.get("code")??void 0}catch{const t=e.split("?")[1]?.split("#")[0];if(void 0===t||0===t.length)return;for(const e of t.split("&")){const t=e.indexOf("=");if("code"!==(t>=0?e.slice(0,t):e))continue;const n=t>=0?e.slice(t+1):"";try{return decodeURIComponent(n)}catch{return n}}return}}var go={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"}},fo=["不限","男","女"],ho=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],yo=["patch","replace"],bo=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:fo},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:ho},{key:"major",label:"专业",selection:"multi",clearValue:"不限"},{key:"recentNotView",label:"近期没有看过",selection:"single",clearValue:"不限"},{key:"exchangeResumeWithColleague",label:"是否与同事交换简历",selection:"single",clearValue:"不限"},{key:"candidateKeywords",label:"牛人关键词",selection:"multi",clearValue:"不限"},{key:"school",label:"院校",selection:"multi",clearValue:"不限"},{key:"switchJobFrequency",label:"跳槽频率",selection:"single",clearValue:"不限"},{key:"intention",label:"求职意向",selection:"multi",clearValue:"不限"},{key:"salary",label:"薪资待遇",selection:"single",clearValue:"不限"},{key:"degree",label:"学历要求",selection:"multi",clearValue:"不限"},{key:"experience",label:"经验要求",selection:"multi",clearValue:"不限"},{key:"callPhone",label:"是否可拨打电话",selection:"single",clearValue:"不限"}],vo=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function wo(e){const t=bo.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function xo(e){return void 0!==e.ageMin||void 0!==e.ageMax}var So=".candidate-card-wrap",Ro="[data-geek], .geek-item",Co=`${So}, ${Ro}`,Io="[data-geek], .card-inner, .geek-item",ko='.boss-popup__wrapper, .dialog-lib-resume, .boss-dialog, [data-type="boss-dialog"]',Ao=`${ko}, .recommendV2`,Eo=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],_o='iframe[src*="c-resume"]',No="canvas#resume, div#resume canvas";function Mo(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)}}var Po=30,To=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Bo(e){const t=e.trim();return!(0===t.length||t.length>Po)&&(!To.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Do(e){let t=e.priority;return To.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 Oo(e){const t=e.filter(e=>Bo(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),r=n.get(t)??new Set;r.add(e.strategy),n.set(t,r)}const r=t.map(e=>{let t=Do(e);const r=n.get(e.text.trim())?.size??1;return r>1&&(t-=.5*r),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return r?{found:!0,username:r.evidence.text.trim(),strategy:r.evidence.strategy,source:r.evidence.source}:{found:!1}}var $o='.user-list.b-scroll-stable [role="listitem"], .geek-item',Fo='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',jo=".candidate-card-wrap",Lo="[data-geek], .geek-item",qo=`${jo}, ${Lo}`,zo=["/web/chat/recommend"],Vo=250,Jo=300,Uo=12,Ho=900,Wo=1200,Ko=4,Go=4,Yo=250,Xo=90,Zo=30,Qo=700,ei=800,ti=520,ni=16,ri=50,ai=650,oi=10,ii=.015,si="button, a, label, li, span, div, [role='button'], [role='radio']",ci=["visible","hidden","prerender","unloaded"],li=["not_chat_page"];function di(e){const t=/(初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/,n=/(\d{2,3}岁|(?:\d{2}年|\d{4}届|\d{4}年)?应届生|\d{2,4}年毕业|在校生|经验不限|无经验|\d+\s*年以上|[1一]年以内|\d+\s*-\s*\d+\s*年|\d+\s*年|初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/g;function r(e){return e.replace(/\s+/g," ").trim()}function a(e){return e.replace(/\s+/g,"")}function o(e){return String(Number.parseInt(e,10))}function i(e){const n=a(e).match(/^(\d{2,3})岁$/);if(void 0!==n?.[1])return{kind:"age",value:`${n[1]}岁`};const r=e.match(t);if(void 0!==r?.[1])return{kind:"education",value:r[1]};const i=function(e){const t=a(e);if(/^(?:\d{2}年|\d{4}届|\d{4}年)?应届生$/.test(t))return"应届生";if(/^\d{2,4}年毕业$/.test(t))return;if("在校生"===t)return"在校生";if("经验不限"===t||"无经验"===t)return t;if("一年以内"===t)return"1年以内";const n=t.match(/^(\d+)年以上$/);if(void 0!==n?.[1])return`${o(n[1])}年以上`;const r=t.match(/^(\d+)-(\d+)年$/);if(void 0!==r?.[1]&&void 0!==r[2])return`${o(r[1])}-${o(r[2])}年`;const i=t.match(/^(\d+)年$/);return void 0!==i?.[1]?`${o(i[1])}年`:"1年以内"===t?"1年以内":void 0}(e);return void 0!==i?{kind:"experience",value:i}:{kind:"ignored"}}const s=e.map(e=>r(e)).filter(e=>e.length>0).flatMap(e=>function(e){const t=[...r(e).matchAll(n)].map(e=>r(e[1]??"")).filter(e=>e.length>0);return t.length>0?t:e.split(/[丨|·\n\r\t]+/).map(e=>r(e)).filter(e=>e.length>0)}(e)).map((e,t)=>({index:t,token:i(e)})),c=s.find(e=>"age"===e.token.kind)?.token,l=s.find(e=>"education"===e.token.kind)?.token,d="age"===c?.kind?c.value:"",u="education"===l?.kind?l.value:"",m=s.find(e=>"age"===e.token.kind)?.index??-1,p=s.find(e=>"education"===e.token.kind)?.index??-1,g=s.flatMap(e=>"experience"===e.token.kind?[{index:e.index,experience:e.token.value}]:[]),f=g.find(e=>(m<0||e.index>m)&&(p<0||e.index<p))?.experience??g[0]?.experience??"";return{age:d,experience:function(e,t){const n=function(e){const t=e.match(/^(\d{2,3})岁$/);if(void 0===t?.[1])return;const n=Number.parseInt(t[1],10);return Number.isFinite(n)?n:void 0}(e),r=function(e){const t=a(e),n=t.match(/^(\d+)-(\d+)年$/);if(void 0!==n?.[2])return Number.parseInt(n[2],10);const r=t.match(/^(\d+)年以上$/)??t.match(/^(\d+)年$/);return void 0!==r?.[1]?Number.parseInt(r[1],10):"1年以内"===t?1:void 0}(t);return void 0===n||void 0===r||r<=Math.max(0,n-12)}(d,f)?f:"",education:u}}function ui(e){return new Promise(t=>{setTimeout(t,e)})}function mi(e){return{x:e.x,y:e.y}}function pi(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function gi(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function fi(e){return e.trim().toLocaleLowerCase("zh-CN")}function hi(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function yi(e,t){const n=fi(e),r=fi(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function bi(e){return"string"==typeof e?e:""}function vi(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function wi(e){return"boolean"==typeof e&&e}function xi(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{};return{conversationId:bi(n.conversationId),candidateId:bi(n.candidateId),name:bi(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:bi(n.position),hasUnread:wi(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:bi(n.lastMessageTime),messagePreview:bi(n.messagePreview)}}):[]}function Si(e){return Array.isArray(e)?e.flatMap(e=>{if(!pi(e))return[];const t=e.text,n=e.strategy,r=e.priority,a=e.source,o=e.xRatio;return"string"!=typeof t||"role-link"!==n&&"role-button"!==n&&"leaf-text"!==n&&"css-fallback"!==n||"number"!=typeof r||"string"!=typeof a?[]:[{text:t,strategy:n,priority:r,source:a,..."number"==typeof o?{xRatio:o}:{}}]}):[]}function Ri(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:bi(n.candidateId),name:bi(n.name),age:bi(n.age),experience:bi(n.experience),education:bi(n.education),workStatus:bi(n.workStatus),company:bi(n.company),currentPosition:bi(n.currentPosition),expectedLocation:bi(n.expectedLocation),expectedPosition:bi(n.expectedPosition),expectedSalary:bi(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>bi(e)).filter(Boolean):[],buttonText:bi(n.buttonText)}}):[]}function Ci(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:bi(n.value),label:bi(n.label),isCurrent:wi(n.isCurrent)}}):[]}function Ii(e){return pi(e)?{found:wi(e.found),isOpen:wi(e.isOpen),currentLabel:bi(e.currentLabel),currentValue:bi(e.currentValue),options:Ci(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function ki(e){if(!pi(e))return null;const t=bi(e.conversationId),n=bi(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:bi(e.candidateName)}}function Ai(e){if(!pi(e))return null;const t=bi(e.candidateName);return t.length>0?{candidateName:t}:null}function Ei(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!pi(e))return[];const n=e.sender,r=e.messageType;return"candidate"!==n&&"recruiter"!==n&&"system"!==n||"text"!==r&&"system"!==r&&"resume"!==r&&"wechat-exchange"!==r?[]:[{index:"number"==typeof e.index&&Number.isInteger(e.index)?e.index:t,sender:n,messageType:r,content:bi(e.content),time:bi(e.time)}]}):[]}function _i(e){const t=pi(e)?e:{},n=t.tags;return{name:bi(t.name),age:bi(t.age),experience:bi(t.experience),education:bi(t.education),communicationPosition:bi(t.communicationPosition),expectedJobText:bi(t.expectedJobText),expectedSalary:bi(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>bi(e)).filter(Boolean):[]}}function Ni(e){const t=pi(e)?e:{};return{selectedTarget:ki(t.selectedTarget),activePanel:Ai(t.activePanel),candidateInfo:_i(t.candidateInfo),messages:Ei(t.messages)}}function Mi(e){return pi(e)?{found:wi(e.found),x:vi(e.x),y:vi(e.y)}:{found:!1,x:0,y:0}}function Pi(e){return pi(e)&&"number"==typeof e.x&&"number"==typeof e.y?{x:e.x,y:e.y}:null}function Ti(e){if(pi(e)&&"number"==typeof e.width&&"number"==typeof e.height)return{width:e.width,height:e.height}}function Bi(e){if(pi(e)&&"number"==typeof e.x&&"number"==typeof e.y&&"number"==typeof e.width&&"number"==typeof e.height)return{x:e.x,y:e.y,width:e.width,height:e.height}}function Di(e){return pi(e)?{dialogVisible:!0===e.dialogVisible,iframeFound:!0===e.iframeFound,iframeVisible:!0===e.iframeVisible,canvasReady:!0===e.canvasReady}:{dialogVisible:!1,iframeFound:!1,iframeVisible:!1,canvasReady:!1}}function Oi(e,t,n){if(!pi(e)||!0!==e.found)return{found:!1,error:"无法读取简历 canvas 状态"};const r=Bi(e.canvasRect);if(void 0===r)return{found:!1,error:"简历 canvas 坐标不完整"};const a=Mo({recommendFrameRect:n,resumeFrameRect:Pi(e.resumeFrameRect),canvasRect:r}),o=pi(t)?t:{},i=o.canvasSize,s=pi(i)&&"number"==typeof i.width&&"number"==typeof i.height?{width:i.width,height:i.height}:{width:Math.round(r.width),height:Math.round(r.height)},c=o.dataUrl,l=o.dataUrlError;return{found:!0,screenshotArea:a,canvasSize:s,..."string"==typeof c&&c.startsWith("data:image/")?{dataUrl:c}:{},..."string"==typeof l?{dataUrlError:l}:{},...!0===o.blank?{blank:!0}:{}}}function $i(e){return pi(e)?{found:wi(e.found),left:vi(e.left),top:vi(e.top)}:{found:!1,left:0,top:0}}function Fi(e){return pi(e)?{found:wi(e.found),cardSelector:bi(e.cardSelector)||jo,candidateId:bi(e.candidateId),name:bi(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:wi(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:jo,candidateId:"",name:"",hasGreetButton:!1}}function ji(e){return pi(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 Li(e){return pi(e)&&!0===e.ok?{ok:!0,current:ji(e.current),trackLeft:vi(e.trackLeft),trackTop:vi(e.trackTop),trackWidth:vi(e.trackWidth),trackHeight:vi(e.trackHeight),minHandleX:vi(e.minHandleX),minHandleY:vi(e.minHandleY),maxHandleX:vi(e.maxHandleX),maxHandleY:vi(e.maxHandleY)}:{ok:!1,error:pi(e)&&bi(e.error)||"未找到年龄滑块"}}function qi(e){return pi(e)?{containerFound:wi(e.containerFound),containerLabel:bi(e.containerLabel),scrollTop:vi(e.scrollTop),scrollHeight:vi(e.scrollHeight),clientHeight:vi(e.clientHeight),itemCount:vi(e.itemCount),atStart:wi(e.atStart),atEnd:wi(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function zi(e){return[e,...(e.childFrames??[]).flatMap(e=>zi(e))]}function Vi(e){const t=new Set,n=[];for(const r of e){const e=r.conversationId.length>0?`conversation:${r.conversationId}`:`fallback:${r.name}:${r.messagePreview}:${r.index}`;t.has(e)||(t.add(e),n.push(r))}return n}function Ji(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 Ui(e){return ci.some(t=>t===e)}function Hi(e,t){return`打开候选人聊天后,右侧会话未同步切换到 ${e.name||e.conversationId};${[`document.visibilityState=${t.visibilityState}`,`expectedConversationId=${e.conversationId||"<empty>"}`,`selectedConversationId=${t.selectedConversationId||"<none>"}`,`activePanelCandidateName=${t.activePanelCandidateName||"<none>"}`].join(", ")}${"hidden"===t.visibilityState?";页面当前不可见,可能已最小化、被完全遮挡或位于其他 macOS Space":""}`}function Wi(e,t,n){const r=t.filter(e=>Tt(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&gi(a.url))return a;const e=r.filter(e=>gi(e.url));if(1===e.length){const t=e[0];if(t)return t}if(a)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(a)return a;if(1===r.length){const e=r[0];if(e)return e}if(r.length>1)throw new Error("Multiple BOSS pages found; select the target tab first.");throw new Error("No BOSS page found.")}function Ki(e,t){const n=t.filter(e=>Tt(e.url,"zhipin")),r=n.find(t=>e.isNativePageSelected(t.targetId));return r||(1===n.length?n[0]:void 0)}async function Gi(e={},t={}){const n=t.ctxManager??ue(),r=t.runtime??de(),a=await n.listNativePages();if(!0!==e.skipRiskGate){const e=Ki(n,a);e&&so(e)}const o=Wi(n,a,e),i=await r.connectNativePage(o),s=new Yi({target:o,controller:i});if(!0!==e.skipRiskGate)try{await s.assertNotRestricted()}catch(e){throw s.close(),e}return s}var Yi=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new ea(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 assertNotRestricted(){let e;try{e=await this.url()}catch{throw new Error("无法读取当前页面地址,已中止后续操作(页面可能已跳转或失效)")}so({url:e,title:await this.title().catch(()=>"")})}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 ui(Vo)}return!1}async evaluateJson(e){return await this.controller.evaluateJson(e)}async bringToFront(){await this.controller.bringToFront()}async inspectChatReloadTarget(){const e=await this.url().catch(()=>this.target.url);return gi(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await Fn(this.controller,{url:e.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{},...void 0!==e.onReloadSent?{onReloadSent:e.onReloadSent}:{}})}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(Fo)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await ui(Vo)}return!1}async waitForChatListReady(e={},t=1e4){const n=Date.now(),r=e.expectedConversationId,a=async()=>{if(void 0===r)return!1;return(await this.readVisibleChatCandidates().catch(()=>[])).some(e=>e.conversationId===r)};if(!await this.waitForSelector(Fo,t))return await a();if(void 0===r)return!0;for(;Date.now()-n<t;){const e=await this.readVisibleChatCandidates().catch(()=>[]);if(e.some(e=>e.conversationId===r))return!0;if(e.length>0)break;await ui(Vo)}for(let e=0;e<Uo&&Date.now()-n<t;e+=1){const e=await this.scrollChatList("up").catch(()=>{});if(void 0===e||!e.ok||e.after>=e.before)break;if(await ui(Jo),await a())return!0;if(e.after<=1)break}for(let e=0;e<=Uo&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=Uo)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await ui(Jo)}return await a()}async isRecommendSurfaceOpen(){const e=[go.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\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 ui(Vo)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=zi(e).map(e=>e.frame),n=t.find(e=>"recommendFrame"===e.name),r=t.find(t=>t.id!==e.frame.id&&t.url.includes("recommend")),a=n??r;if(void 0===a)return;if(void 0!==this.recommendFrameContextId&&this.recommendFrameContextFrameId===a.id)return this.recommendFrameContextId;const o=await this.controller.createIsolatedWorld(a.id).catch(()=>{});return void 0!==o?(this.recommendFrameContextId=o,this.recommendFrameContextFrameId=a.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 $i(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(go.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=Mi(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Mi(await this.evaluateRecommendFrameJson(e));if(!n.found)return n;const r=await this.readRecommendFrameOffset();return r.found?{found:!0,x:Math.round(n.x+r.left),y:Math.round(n.y+r.top)}:n}async dispatchNativeClick(e,t={}){return!!e.found&&(await this.mouse.click(mi(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??Xo,settleMs:t.settleMs??Yo}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(qo)}) !== 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 ui(Vo)}else await ui(Vo);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=Ii(await this.evaluateJson(e).catch(()=>{}));return t.found?t:Ii(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 ui(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 ui(260),await this.selectAllFocusedText(),await ui(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await ui(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await ui(500),!0;await ui(350);for(const t of Array.from(e))await this.controller.insertText(t),await ui(110);return await ui(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 r=hi(e.jobName),a=t.filter(e=>hi(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>hi(e.label).includes(r)))}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=hi(e.jobName),r=hi(t.label);return r===n||r.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 ui(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 ui(700);let r=await this.readRecommendJobSelectorState();const a=this.getCurrentRecommendJobOption(r);if(void 0!==a&&this.currentRecommendJobMatchesRequest(n,a)&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:a,selected:a,options:r.options,matchedCount:1};let o=this.selectRecommendJobMatch(n,r.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 ui(900),r=await this.readRecommendJobSelectorState(),o=this.selectRecommendJobMatch(n,r.options));const s=this.getCurrentRecommendJobOption(r);if(void 0===o.selected)return{success:!1,status:"not_found",requested:n,...void 0!==s?{current:s}:{},options:r.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:r.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:r.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 ui(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:r.options,matchedCount:o.matchedCount}}async clickSidebarSection(e,t={}){const n="chat"===e?go.nav.chatLink:go.nav.recommendLink,r=Mi(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(go.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!!r.found&&await this.dispatchNativeClick(r,t)}async scrollSurface(e,t={}){const n=Ka(e),r=t.direction??n.defaultDirection,a=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<a;n+=1){if("chat-list"===e&&s.itemCount>0){const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if("up"===r&&s.atStart||"down"===r&&s.atEnd){if(s.itemCount<=0)break;const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if(s=await this.scrollSurfaceOnce(e,r,t.distance),c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}}return{success:i.containerFound||s.containerFound||c>0,direction:r,stepsRequested:a,stepsCompleted:c,reachedBoundary:"up"===r?s.atStart:s.atEnd,before:i,after:s}}async readChatCandidates(e={}){const t=e.targetCount??20,n=e.autoScroll?e.maxScrolls??3:0,r=[];for(let e=0;e<=n;e+=1){r.push(...await this.readVisibleChatCandidates());const a=Vi(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await ui(Jo)}return Vi(r)}async openChat(e){if(await cn(this),!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?Uo:4))),r=async t=>{if(!await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}}))return{...t,found:!1,error:`未能点击候选人: ${t.name||t.conversationId}`};await ui(Yo);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await ui(Yo),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:Hi(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Vi(o),c=!0!==e.preferUnread||t?Ua(s,e):s.find(e=>e.hasUnread);if(void 0!==c)return await r(c);if(i>=a)break;if(!(await this.scrollChatList(n)).ok)break;await ui(Jo)}},o=await a("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 ui(Jo)}const e=await a("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 ki(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 Ai(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 ui(Vo)}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 Ni(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\n const profile = (${di.toString()})(infoTexts);\n const age = profile.age;\n const experience = profile.experience;\n const education = profile.education;\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),r=Fi(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=Fi(await this.evaluateRecommendFrameJson(n));return a.found?a:r}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 r=await this.resolveRecommendClickTarget(this.buildRecommendGreetClickExpression(Math.max(0,Math.floor(e)))),a=await this.dispatchNativeClick(r,t);return{...n,clicked:a,...a?{}:{error:"未能点击打招呼按钮"}}}async clickRecommendCardSurface(e,t={}){const n=await this.resolveRecommendClickTarget(this.buildRecommendCardOpenClickExpression(Math.max(0,Math.floor(e))));return await this.dispatchNativeClick(n,t)}async waitForResumeDialog(e=1e4){const t=Date.now();let n=await this.readResumeDialogState();for(;!n.canvasReady&&Date.now()-t<e;)await ui(Vo),n=await this.readResumeDialogState();return n}async readResumeCanvasGeometry(){const e=this.buildResumeCanvasGeometryExpression();let t=!1,n=await this.evaluateJson(e).catch(()=>{});if(!pi(n)||!0!==n.found){const r=await this.evaluateRecommendFrameJson(e);void 0!==r&&(n=r,t=!0)}if(!pi(n)||!0!==n.found){const e=pi(n)?n.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const r=Bi(n.canvasRect),a=Ti(n.canvasSize);if(void 0===r||void 0===a)return{found:!1,error:"简历 canvas 坐标读取失败"};const o=Pi(n.resumeFrameRect),i=t?await this.readRecommendFrameOffset():void 0;return{found:!0,screenshotArea:Mo({recommendFrameRect:void 0!==i&&i.found?{x:i.left,y:i.top}:null,resumeFrameRect:o??null,canvasRect:r}),canvasSize:a}}buildResumeCanvasGeometryExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const canvasSelector = ${JSON.stringify(No)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n return {\n found: true,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n },\n canvasSize: { width: canvas.width, height: canvas.height }\n };\n })()`}async captureResumeCanvas(e){await ui(ei);const t=this.buildResumeStitchInitExpression();let n=!1,r=await this.evaluateJson(t).catch(()=>{});if(!pi(r)||!0!==r.found){const e=await this.evaluateRecommendFrameJson(t);void 0!==e&&(r=e,n=!0)}if(!pi(r)||!0!==r.found){const e=pi(r)?r.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const a=async e=>n?await this.evaluateRecommendFrameJson(e):await this.evaluateJson(e).catch(()=>{}),o="number"==typeof r.maxScrollTotal&&r.maxScrollTotal>0?r.maxScrollTotal:0,i=this.buildResumeStitchStepExpression();for(let t=0;t<Zo;t++){await ui(Qo);const n=await a(i);if(!pi(n)||!0!==n.ok||!0===n.done)break;if(void 0!==e){const r="number"==typeof n.scrollTop?n.scrollTop:0;await Promise.resolve(e({round:t,scrolledPx:r,totalPx:o})).catch(()=>{})}}const s=await a(this.buildResumeStitchExportExpression()),c=n?await this.readRecommendFrameOffset():void 0;return Oi(r,s,void 0!==c&&c.found?{x:c.left,y:c.top}:null)}async captureViewportClip(e,t=1){return await this.controller.captureScreenshot({format:"png",clip:{x:e.x,y:e.y,width:e.width,height:e.height,scale:t},timeoutMs:15e3})}async readResumeDialogClipArea(){const e=this.buildResumeDialogRectExpression(),t=Bi(await this.evaluateJson(e).catch(()=>{}));if(void 0!==t)return t;const n=Bi(await this.evaluateRecommendFrameJson(e));if(void 0===n)return;const r=await this.readRecommendFrameOffset();return r.found?{x:Math.round(n.x+r.left),y:Math.round(n.y+r.top),width:Math.round(n.width),height:Math.round(n.height)}:n}async closeResumeDialog(e={}){const t=await this.resolveRecommendClickTarget(this.buildResumeCloseTargetExpression());return t.found&&await this.dispatchNativeClick(t,e)&&await this.waitForResumeDialogGone()?{closed:!0,method:"close-button"}:(await this.controller.dispatchKeyEvent({type:"keyDown",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.waitForResumeDialogGone()?{closed:!0,method:"escape"}:{closed:!1,error:"简历弹窗未关闭"})}async readResumeDialogState(){const e=this.buildResumeDialogStateExpression(),t=Di(await this.evaluateJson(e).catch(()=>{}));if(t.iframeFound)return t;const n=Di(await this.evaluateRecommendFrameJson(e));return{dialogVisible:t.dialogVisible||n.dialogVisible,iframeFound:n.iframeFound,iframeVisible:n.iframeVisible,canvasReady:n.canvasReady}}async waitForResumeDialogGone(e=4e3){const t=Date.now();for(;Date.now()-t<e;){const e=await this.readResumeDialogState();if(!(e.iframeFound&&e.iframeVisible||e.dialogVisible))return!0;await ui(Vo)}return!1}async exchangeWechat(e={}){const t=Mi(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=Mi(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 ui(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=Mi(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 ui(250);const r=Mi(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(r,t)?(await ui(800),{success:!0}):{success:!1,error:"未找到发送按钮"}}async applyRecommendFilter(e,t={}){if(!await this.waitForRecommendFilterSurface(3e3))return{status:"recommend_not_ready",requested:e,error:"推荐牛人页未就绪"};let n=e.location;if(void 0!==e.location){if(!await this.applyRecommendLocationFilter(e.location,t))return{status:"filter_not_found",requested:e,error:`未能设置地区筛选:${e.location.city}${void 0!==e.location.district?`-${e.location.district}`:""}`};n=e.location}if(!xo(e)&&0===e.optionSelections.length){const t=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:{...void 0!==n?{location:n}:{},optionSelections:[]},...void 0!==t?{filterButtonText:t}:{}}}if(!await this.openRecommendFilterPanel(t))return{status:"filter_not_found",requested:e,error:"未找到或无法打开筛选按钮"};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"筛选条件触发 VIP 弹窗"};if("replace"===e.applyMode&&!await this.clickRecommendFilterClear(t))return{status:"clear_failed",requested:e,error:"筛选清除失败"};for(const n of e.optionSelections){if(!await this.clickRecommendFilterValues(n,t))return await this.detectVipModal()?{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}:{status:"filter_not_found",requested:e,error:`未找到${n.label}筛选项:${n.values.join("、")}`};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}}let r={};if(xo(e)){const n=await this.setRecommendAgeRange(e,t);if(!n.success)return{status:"age_not_applied",requested:e,error:n.error};r=n.state}if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"年龄筛选触发 VIP 弹窗"};const a=await this.readNativeAppliedFilterState(e,r,n);if(!await this.clickRecommendFilterSubmit(t))return{status:"submit_failed",requested:e,applied:a,error:"筛选确认失败"};const o=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:a,...void 0!==o?{filterButtonText:o}:{}}}async readRecommendCandidates(e={}){const t=e.targetCount,n=e.autoScroll?e.maxScrolls??4:0,r=await this.inspectSurface("recommend-list");let a=r;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=Ji(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(a.atEnd){let e=!1;for(let t=0;t<Ko;t+=1){await ui(Wo);const t=await d(),n=await this.inspectSurface("recommend-list"),r=t>0||n.scrollHeight>a.scrollHeight||n.itemCount>a.itemCount||!n.atEnd;if(a=n,r){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:Ho});a=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:r.containerFound,direction:"down",stepsRequested:n,stepsCompleted:i,reachedBoundary:a.atEnd,before:r,after:a,items:u,uniqueCount:u.length,duplicateCount:s,noNewRounds:c,stopReason:l}}async readUsernameEvidence(){return Si(await this.evaluateJson(`(() => {\n const limit = ${Po};\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([go.username.primary,...go.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(jo)};\n const fallbackSelector = ${JSON.stringify(Lo)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const parseCandidateProfileTokens = ${di.toString()};\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 const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n 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 })()`}buildRecommendCardTargetClickExpression(e,t){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(jo)};\n const fallbackSelector = ${JSON.stringify(Lo)};\n const iframe = document.querySelector(${JSON.stringify(go.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 target = ${t};\n if (!target) return { found: false, x: 0, y: 0 };\n target.scrollIntoView({ block: "center", inline: "center" });\n const rect = target.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 })()`}buildRecommendGreetClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,'item.querySelector("button.btn.btn-greet") ?? item.querySelector("button.btn-greet") ?? item.querySelector(".btn-greet") ?? item.querySelector(".op-btn")')}buildRecommendCardOpenClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,`item.querySelector(${JSON.stringify(Io)}) ?? item`)}buildResumeDialogStateExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const dialogSelector = ${JSON.stringify(ko)};\n const canvasSelector = ${JSON.stringify(No)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n let dialogVisible = false;\n let iframeFound = false;\n let iframeVisible = false;\n let canvasReady = false;\n const dialog = document.querySelector(dialogSelector);\n if (dialog && visible(dialog)) dialogVisible = true;\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (resumeIframe) {\n iframeFound = true;\n iframeVisible = visible(resumeIframe);\n const resumeDoc = resumeIframe.contentDocument;\n const canvas = resumeDoc ? resumeDoc.querySelector(canvasSelector) : null;\n if (canvas && canvas.width > 0 && canvas.height > 0) canvasReady = true;\n }\n return { dialogVisible, iframeFound, iframeVisible, canvasReady };\n })()`}buildResumeStitchInitExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const canvasSelector = ${JSON.stringify(No)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n\n const scrollers = [];\n let node = resumeIframe.parentElement;\n while (node) {\n if (node.scrollHeight > node.clientHeight + 20) scrollers.push(node);\n node = node.parentElement;\n }\n const rootScroller = document.scrollingElement;\n if (\n rootScroller &&\n rootScroller.scrollHeight > rootScroller.clientHeight + 20 &&\n !scrollers.includes(rootScroller)\n ) {\n scrollers.push(rootScroller);\n }\n\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n const dpr = canvasRect.width > 0 ? canvas.width / canvasRect.width : 1;\n const maxScrollTotal = scrollers.reduce(\n (sum, el) => sum + Math.max(0, el.scrollHeight - el.clientHeight),\n 0\n );\n const totalHeightPx = Math.max(\n Math.round((maxScrollTotal + canvasRect.height) * dpr),\n canvas.height\n );\n\n const stitched = resumeDoc.createElement("canvas");\n stitched.width = canvas.width;\n stitched.height = totalHeightPx;\n const context = stitched.getContext("2d");\n if (!context) return { found: false, error: "无法创建拼接 canvas 上下文" };\n context.fillStyle = "#ffffff";\n context.fillRect(0, 0, stitched.width, stitched.height);\n\n for (const el of scrollers) el.scrollTop = 0;\n globalThis.__rollResumeStitch = {\n stitched,\n context,\n canvas,\n scrollers,\n dpr,\n maxDrawnY: 0,\n scrollerIndex: 0,\n effectiveOffset: 0,\n pending: null,\n pendingFpBase: "",\n readFp: function () {\n try {\n if (!this.fpContext) {\n const fpCanvas = this.canvas.ownerDocument.createElement("canvas");\n fpCanvas.width = 32;\n fpCanvas.height = 32;\n this.fpContext = fpCanvas.getContext("2d", { willReadFrequently: true });\n }\n const fpContext = this.fpContext;\n if (!fpContext) return "noctx";\n fpContext.fillStyle = "#ffffff";\n fpContext.fillRect(0, 0, 32, 32);\n fpContext.drawImage(this.canvas, 0, 0, 32, 32);\n const data = fpContext.getImageData(0, 0, 32, 32).data;\n let hash = 0;\n for (let index = 0; index < data.length; index += 4) {\n hash = ((hash * 31 + data[index] + data[index + 1] + data[index + 2]) >>> 0);\n }\n return String(hash);\n } catch (error) {\n return "err";\n }\n }\n };\n return {\n found: true,\n needScroll: maxScrollTotal > 4,\n maxScrollTotal,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n }\n };\n })()`}buildResumeStitchStepExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n const fingerprint = state.readFp();\n\n if (state.pending) {\n const pendingScroller = state.scrollers[state.pending.index];\n const realDelta = pendingScroller\n ? pendingScroller.scrollTop - state.pending.before\n : 0;\n if (realDelta <= 2) {\n state.scrollerIndex = state.pending.index + 1;\n } else if (fingerprint !== state.pendingFpBase) {\n state.effectiveOffset += realDelta;\n } else {\n if (pendingScroller) pendingScroller.scrollTop = state.pending.before;\n state.scrollerIndex = state.pending.index + 1;\n }\n state.pending = null;\n }\n\n const drawY = Math.round(state.effectiveOffset * state.dpr);\n const drawnBottom = drawY + state.canvas.height;\n if (drawnBottom > state.stitched.height) {\n const grown = state.stitched.ownerDocument.createElement("canvas");\n grown.width = state.stitched.width;\n grown.height = drawnBottom;\n const grownContext = grown.getContext("2d");\n if (!grownContext) return { ok: false, error: "无法扩容拼接 canvas" };\n grownContext.fillStyle = "#ffffff";\n grownContext.fillRect(0, 0, grown.width, grown.height);\n grownContext.drawImage(state.stitched, 0, 0);\n state.stitched = grown;\n state.context = grownContext;\n }\n state.context.drawImage(state.canvas, 0, drawY);\n if (drawnBottom > state.maxDrawnY) state.maxDrawnY = drawnBottom;\n\n const scroller = state.scrollers[state.scrollerIndex];\n if (scroller) {\n const before = scroller.scrollTop;\n scroller.scrollTop = before + scroller.clientHeight;\n state.pending = { index: state.scrollerIndex, before };\n state.pendingFpBase = fingerprint;\n }\n return { ok: true, done: !scroller, scrollTop: state.effectiveOffset };\n })()'}buildResumeStitchExportExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n delete globalThis.__rollResumeStitch;\n let output = state.stitched;\n const finalHeight = Math.min(\n state.stitched.height,\n Math.max(state.maxDrawnY, state.canvas.height)\n );\n if (finalHeight < state.stitched.height) {\n const trimmed = state.stitched.ownerDocument.createElement("canvas");\n trimmed.width = state.stitched.width;\n trimmed.height = finalHeight;\n const trimContext = trimmed.getContext("2d");\n if (trimContext) {\n trimContext.drawImage(state.stitched, 0, 0);\n output = trimmed;\n }\n }\n let blank = true;\n const outputContext = output.getContext("2d");\n if (outputContext) {\n try {\n const stepX = Math.max(1, Math.floor(output.width / 16));\n const stepY = Math.max(1, Math.floor(output.height / 16));\n outer: for (let y = 0; y < output.height; y += stepY) {\n for (let x = 0; x < output.width; x += stepX) {\n const pixel = outputContext.getImageData(x, y, 1, 1).data;\n if (pixel[3] > 8 && (pixel[0] < 246 || pixel[1] < 246 || pixel[2] < 246)) {\n blank = false;\n break outer;\n }\n }\n }\n } catch (error) {\n blank = false;\n }\n }\n let dataUrl = null;\n let dataUrlError = null;\n try {\n dataUrl = output.toDataURL("image/png");\n } catch (error) {\n dataUrlError = error instanceof Error ? error.message : String(error);\n }\n for (const el of state.scrollers) el.scrollTop = 0;\n return {\n ok: true,\n dataUrl,\n dataUrlError,\n blank,\n canvasSize: { width: output.width, height: output.height }\n };\n })()'}buildResumeDialogRectExpression(){return`(() => {\n const dialogSelector = ${JSON.stringify(ko)};\n let best = null;\n let bestArea = 0;\n for (const element of Array.from(document.querySelectorAll(dialogSelector))) {\n const rect = element.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (rect.width > 0 && rect.height > 0 && area > bestArea) {\n best = rect;\n bestArea = area;\n }\n }\n if (!best) return { found: false };\n return {\n found: true,\n x: Math.max(0, best.x),\n y: Math.max(0, best.y),\n width: best.width,\n height: best.height\n };\n })()`}buildResumeCloseTargetExpression(){return`(() => {\n const scopeSelector = ${JSON.stringify(Ao)};\n const closeSelectors = ${JSON.stringify([...Eo])};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const scopes = Array.from(document.querySelectorAll(scopeSelector)).filter(visible);\n for (const scope of scopes) {\n for (const selector of closeSelectors) {\n for (const element of Array.from(scope.querySelectorAll(selector))) {\n if (!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 }\n return { found: false, x: 0, y: 0 };\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 ui(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(){await this.evaluateJson('(() => {\n const el = document.activeElement;\n if (!el) return false;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {\n try {\n el.select();\n return true;\n } catch {\n return false;\n }\n }\n if (el.isContentEditable) {\n const range = document.createRange();\n range.selectNodeContents(el);\n const selection = window.getSelection();\n if (!selection) return false;\n selection.removeAllRanges();\n selection.addRange(range);\n return true;\n }\n return false;\n })()').catch(()=>{})}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${go.recommend.filterButton}, ${jo}, ${go.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 ui(Vo)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(go.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(go.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 ui(Vo)}}await ui(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 r=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(si)};\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(r,n)}async clickRecommendFilterValues(e,t){const n=Array.from(new Set(e.values.map(e=>e.trim()))).filter(e=>e.length>0);if(0===n.length)return!0;if("single"===e.selection){const r=n[0];return void 0===r||await this.clickRecommendFilterOption(e.label,r,t)}if(n.includes(e.clearValue))return await this.clickRecommendFilterOption(e.label,e.clearValue,t);if(!await this.clickRecommendFilterOption(e.label,e.clearValue,t))return!1;for(const r of n)if(!await this.clickRecommendFilterOption(e.label,r,t))return!1;return!0}async isRecommendLocationPanelVisible(){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 roots = Array.from(document.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 const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const explicitPanel = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .some((element) => visible(element) && isLocationPanel(element));\n if (explicitPanel) return true;\n return roots.some((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 120 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n })()';return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async openRecommendLocationPanel(e,t){if(await this.isRecommendLocationPanelVisible())return!0;const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedCity = ${JSON.stringify(e.city)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = normalizeCity(expectedCity);\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 centerY = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.top + rect.height / 2;\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 controls = Array.from(document.querySelectorAll("button, a, span, div, [role='button']"))\n .filter((element) => visible(element));\n const filterButton = controls\n .filter((element) => /^筛选(?:·\\d+)?$/.test(normalize(element.textContent)))\n .sort((a, b) => area(a) - area(b))[0];\n if (filterButton) {\n const filterRect = filterButton.getBoundingClientRect();\n const sameRowCandidates = controls\n .filter((element) => {\n if (element === filterButton) return false;\n const text = normalize(element.textContent);\n if (text.length === 0 || text.length > 12) return false;\n if (text.includes("_") || text.includes("筛选") || text.includes("推荐")) return false;\n const rect = element.getBoundingClientRect();\n return (\n rect.right <= filterRect.left + 8 &&\n Math.abs(centerY(element) - centerY(filterButton)) <= 36\n );\n })\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return Math.abs(filterRect.left - aRect.right) - Math.abs(filterRect.left - bRect.right);\n });\n if (sameRowCandidates[0]) return readCenter(sameRowCandidates[0]);\n }\n\n const cityCandidates = controls\n .filter((element) => {\n const text = normalizeCity(element.textContent);\n const rect = element.getBoundingClientRect();\n return text === expected && rect.top < 160;\n })\n .sort((a, b) => area(a) - area(b));\n if (cityCandidates[0]) return readCenter(cityCandidates[0]);\n\n return { found: false, x: 0, y: 0 };\n })()`);if(!await this.dispatchNativeClick(n,t))return!1;const r=Date.now();for(;Date.now()-r<4e3;){if(await this.isRecommendLocationPanelVisible())return!0;await ui(Vo)}return!1}async clickRecommendLocationValue(e,t,n){const r=await this.resolveRecommendClickTarget(`(() => {\n const kind = ${JSON.stringify(e)};\n const rawValue = ${JSON.stringify(t)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = kind === "city" ? normalizeCity(rawValue) : normalize(rawValue);\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 roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认") &&\n text.includes(expected)\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n if (!panel) return { found: false, x: 0, y: 0 };\n\n const panelRect = panel.getBoundingClientRect();\n const leftLimit = panelRect.left + panelRect.width * (kind === "city" ? 0 : 0.22);\n const rightLimit = panelRect.left + panelRect.width * (kind === "city" ? 0.34 : 0.72);\n const candidates = Array.from(\n panel.querySelectorAll("button, a, label, li, span, div, [role='button'], [role='checkbox']")\n )\n .filter((element) => {\n if (!visible(element)) return false;\n const text = kind === "city" ? normalizeCity(element.textContent) : normalize(element.textContent);\n if (text !== expected) return false;\n const rect = element.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n return centerX >= leftLimit && centerX <= rightLimit;\n })\n .sort((a, b) => area(a) - area(b));\n const candidate = candidates[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 })()`);return!!await this.dispatchNativeClick(r,n)&&(await ui(250),!0)}async clickRecommendLocationSubmit(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 area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\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((element) => visible(element) && normalize(element.textContent) === "确认")\n .sort((a, b) => area(a) - area(b))[0];\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 })()');return!!await this.dispatchNativeClick(t,e)&&(await ui(500),!0)}async applyRecommendLocationFilter(e,t){return!!await this.openRecommendLocationPanel(e,t)&&(!!await this.clickRecommendLocationValue("city",e.city,t)&&(!(void 0!==e.district&&!await this.clickRecommendLocationValue("district",e.district,t))&&await this.clickRecommendLocationSubmit(t)))}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(go.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(ni)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(ri)} -\n ${JSON.stringify(ni)})\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=ji(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:ji(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(go.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=Li(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Li(await this.evaluateRecommendFrameJson(e));if(!n.ok)return n;const r=await this.readRecommendFrameOffset();return r.found?{...n,trackLeft:n.trackLeft+r.left,trackTop:n.trackTop+r.top,minHandleX:n.minHandleX+r.left,minHandleY:n.minHandleY+r.top,maxHandleX:n.maxHandleX+r.left,maxHandleY:n.maxHandleY+r.top}:n}async dispatchNativeDrag(e,t,n,r,a={}){await this.mouse.drag({x:e,y:t},{x:n,y:r},{...void 0!==a.motionObserver?{motionObserver:a.motionObserver}:{},pressDurationMs:a.pressDurationMs??Xo})}async dragAgeHandleToRatio(e,t,n){const r=await this.resolveNativeAgeSlider();if(!r.ok||r.trackWidth<=0)return!1;const a="min"===e?r.minHandleX:r.maxHandleX,o="min"===e?r.minHandleY:r.maxHandleY,i=r.trackLeft+Math.max(0,Math.min(1,t))*r.trackWidth,s=r.trackTop+Math.max(1,r.trackHeight/2);return await this.dispatchNativeDrag(a,o,i,s,n),await ui(ai),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-ni)/(ri-ni)))}clampRatio(e,t,n){return Math.max(t,Math.min(n,e))}async setAgeHandleToNumber(e,t,n){const r=await this.readNativeAgeState(),a=r.minRatio??0,o=r.maxRatio??1;let i="min"===e?0:Math.min(1,a+ii),s="min"===e?Math.max(0,o-ii):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<oi;r+=1){if(!await this.dragAgeHandleToRatio(e,c,n))return!1;const r=await this.readNativeAgeState(),a="min"===e?r.ageMin:r.ageMax;if(a===t)return!0;void 0===a?"max"===e?s=c:i=c:a<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??ni,r=e.ageMax,a=await this.resolveNativeAgeSlider();if(!a.ok)return{success:!1,error:a.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===r){if(void 0!==(await this.readNativeAgeState()).ageMax&&!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法设置为不限"}}else if(!await this.setAgeHandleToNumber("max",r,t))return{success:!1,error:`年龄上限无法设置为 ${r}`};const o=await this.readNativeAgeState();if(!this.isDesiredAgeState(o,n,r)){const e=void 0===o.ageMax?"不限":String(o.ageMax);return{success:!1,error:`年龄筛选未精确生效,当前为 ${o.ageMin??"未知"}-${e}`}}return{success:!0,state:o}}async readSelectedRecommendOptionTexts(e,t){const n=`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(si)};\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 .filter((text) => text !== "" && text !== rowLabel);\n return selected.length > 0 ? Array.from(new Set(selected)) : fallback;\n })()`,r=await this.evaluateJson(n).catch(()=>[]);if(r.length>0)return r;const a=await this.evaluateRecommendFrameJson(n);return Array.isArray(a)&&a.length>0?a:t}async readNativeAppliedFilterState(e,t,n){const r=await Promise.all(bo.map(async t=>{const n=e.optionSelections.find(e=>e.fieldKey===t.key),r=n?.values??[];return{fieldKey:t.key,label:t.label,values:await this.readSelectedRecommendOptionTexts(t.label,r)}})),a=r.find(e=>"gender"===e.fieldKey)?.values[0],o=r.find(e=>"activity"===e.fieldKey)?.values[0];return{...void 0!==t.ageMin?{ageMin:t.ageMin}:{},...void 0!==t.ageMax?{ageMax:t.ageMax}:{},...void 0!==n?{location:n}:{},optionSelections:r,...void 0!==a?{gender:a}:{},...void 0!==o?{activity:o}:{}}}async clickRecommendFilterClear(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.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 })()`);return!!await this.dispatchNativeClick(t,e)&&(await ui(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.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 ui(600),!0;await ui(Vo)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(go.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 xi(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify($o)}));\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),r=null!==t&&(e.conversationId.length>0&&t.conversationId===e.conversationId||0===e.conversationId.length&&e.candidateId.length>0&&t.candidateId===e.candidateId),a=0===e.name.length||null!==n&&yi(e.name,n.candidateName);if(r&&a)return{ready:!0};await ui(Vo)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:Ui(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=Mi(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($o)}));\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(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const parseCandidateProfileTokens = ${di.toString()};\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(jo)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(Lo)}));\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 const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n 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=Ri(await this.evaluateJson(e));if(t.length>0)return t;return Ri(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=Ka(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(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\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 })()`,r=qi(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=qi(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=Ka(e);await cn(this);const a=Mi(await this.evaluateJson(`(() => {\n const nativeWheelTarget = true;\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(go.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(!a.found)return;await this.mouse.moveTo(mi(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??ti):n??ti}),await ui(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 r=Ka(e),a=`(() => {\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(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\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=qi(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=qi(await this.evaluateRecommendFrameJson(a));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)}}},Xi=["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","resume-canvas","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],Zi=[...Xi,"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"],Qi=qa.enum(["localStorage","sessionStorage"]),es=qa.enum(["array","object","string","number","boolean","null"]),ts=qa.enum(["empty","json","string"]),ns=qa.enum(Xi),rs=qa.enum(Zi),as=qa.enum(["request","response"]),os=qa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),is=qa.object({area:Qi,key:qa.string(),valueLength:qa.number().int().nonnegative(),valueKind:ts,jsonKind:es.optional(),jsonTopLevelKeys:qa.array(qa.string()).optional(),jsonArrayLength:qa.number().int().nonnegative().optional(),numericFields:qa.record(qa.number()).optional(),booleanFields:qa.record(qa.boolean()).optional(),arrayLengths:qa.record(qa.number().int().nonnegative()).optional()}),ss=qa.object({name:qa.string(),domain:qa.string(),path:qa.string(),expires:qa.string(),valueLength:qa.number().int().nonnegative(),httpOnly:qa.boolean(),secure:qa.boolean(),sameSite:qa.string().optional()}),cs=qa.object({before:qa.number().optional(),after:qa.number().optional(),delta:qa.number().optional()}),ls=qa.object({before:qa.boolean().optional(),after:qa.boolean().optional()}),ds=qa.object({before:qa.number().int().nonnegative().optional(),after:qa.number().int().nonnegative().optional(),delta:qa.number().int().optional()}),us=qa.object({area:Qi,key:qa.string(),beforePresent:qa.boolean(),afterPresent:qa.boolean(),numericDeltas:qa.record(cs).optional(),booleanChanges:qa.record(ls).optional(),arrayLengthDeltas:qa.record(ds).optional()}),ms=qa.object({url:qa.string(),title:qa.string(),visibilityState:qa.string(),hasFocus:qa.boolean()}),ps=qa.object({navigatorWebdriver:qa.boolean().optional(),userAgentContainsHeadless:qa.boolean(),languagesLength:qa.number().int().nonnegative(),pluginsLength:qa.number().int().nonnegative(),hasChromeRuntime:qa.boolean(),permissionQueryIsNative:qa.boolean().optional(),hasPlaywrightBinding:qa.boolean(),hasPwInitScripts:qa.boolean(),cdcKeys:qa.array(qa.string()),webdriverKeys:qa.array(qa.string()),automationLikeWindowKeys:qa.array(qa.string())}),gs=qa.object({rootNodeId:qa.number().int(),rootNodeName:qa.string(),childNodeCount:qa.number().int().nonnegative().optional(),bodyTextLength:qa.number().int().nonnegative().optional(),elementCount:qa.number().int().nonnegative().optional()}),fs=qa.object({type:qa.literal("mouseMoved"),x:qa.number(),y:qa.number()}),hs=qa.object({targetId:qa.string(),websocketUrlAvailable:qa.boolean(),connected:qa.boolean(),pageBroughtToFront:qa.boolean().optional(),runtimeEnabled:qa.boolean().optional(),evaluate:ms.optional(),dom:gs.optional(),input:fs.optional()}),ys=qa.object({phase:ns,success:qa.boolean(),durationMs:qa.number().int().nonnegative(),error:qa.string().optional()}),bs=qa.object({phase:rs,capturedAt:qa.string(),targetFound:qa.boolean(),page:La.optional(),urlChangedFromPrevious:qa.boolean(),titleChangedFromPrevious:qa.boolean(),previousUrl:qa.string().optional(),currentUrl:qa.string().optional(),previousTitle:qa.string().optional(),currentTitle:qa.string().optional()}),vs=qa.object({kind:as,reason:os,capturedAt:qa.string(),url:qa.string(),method:qa.string().optional(),resourceType:qa.string().optional(),status:qa.number().int().optional()}),ws=qa.object({capturedAt:qa.string(),url:qa.string()}),xs=qa.object({phase:ns.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;resume-canvas 只读探测简历弹窗与 canvas 就绪状态和坐标;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:qa.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:qa.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:qa.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),Ss=qa.object({success:qa.boolean(),requestedPhase:ns,mode:qa.string(),nativePages:qa.array(La),targetPage:La.optional(),browserAttached:qa.boolean(),pageAttached:qa.boolean(),nativeTimeline:qa.array(bs),networkEvents:qa.array(vs).optional(),navigationEvents:qa.array(ws).optional(),nativeCdp:hs.optional(),resumeCanvas:qa.object({dialogVisible:qa.boolean(),iframeFound:qa.boolean(),iframeVisible:qa.boolean(),canvasReady:qa.boolean(),screenshotArea:qa.object({x:qa.number(),y:qa.number(),width:qa.number(),height:qa.number()}).optional(),canvasSize:qa.object({width:qa.number(),height:qa.number()}).optional(),error:qa.string().optional()}).optional(),evaluate:ms.optional(),detectorFingerprint:ps.optional(),storage:qa.object({localStorage:qa.array(is),sessionStorage:qa.array(is),cookies:qa.array(ss),counterDiffs:qa.array(us)}).optional(),phases:qa.array(ys),warnings:qa.array(qa.string())}),Rs=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Cs=new Set(Rs),Is=["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"],ks=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],As={"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 Es(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 _s(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ns(e){return Is.includes(e)}function Ms(e){return ks.includes(e)}function Ps(e){try{return JSON.parse(e)}catch{return}}function Ts(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])),r=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(r).length>0?{arrayLengths:r}:{}}}function Bs(e){return new Promise(t=>{setTimeout(t,e)})}function Ds(e){return Cs.has(e)}function Os(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=Ps(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=Es(n),a=_s(n)&&Ds(t.key)?Ts(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,..._s(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function $s(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 Fs(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 js(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?Tt(r.url,"zhipin")?r:void n.push(`targetPageId "${t}" is not a zhipin page.`):void n.push(`targetPageId "${t}" not found in native pages.`)}const r=e.filter(e=>Tt(e.url,"zhipin"));if(1===r.length)return r[0];0===r.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 Ls(e,t,n,r){const a=n?.url,o=n?.title,i=r?.currentUrl,s=r?.currentTitle;return{phase:t,capturedAt:(new Date).toISOString(),targetFound:void 0!==n,...void 0!==n?{page:Dt(e,n)}:{},urlChangedFromPrevious:void 0!==i&&void 0!==a&&i!==a,titleChangedFromPrevious:void 0!==s&&void 0!==o&&s!==o,...void 0!==i?{previousUrl:i}:{},...void 0!==a?{currentUrl:a}:{},...void 0!==s?{previousTitle:s}:{},...void 0!==o?{currentTitle:o}:{}}}async function qs(e,t,n,r){const a=await e.listNativePages();return Ls(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>Tt(e.url,"zhipin"))[0],r)}async function zs(e,t,n,r,a){try{const o=t[t.length-1],i=await qs(e,n,r,o);t.push(i),i.urlChangedFromPrevious&&a.push(`Target URL changed after ${n}: ${i.previousUrl??"(unknown)"} -> ${i.currentUrl??"(missing)"}.`)}catch(e){a.push(`Failed to capture native snapshot after ${n}: ${e instanceof Error?e.message:String(e)}.`)}}async function Vs(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await zs(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await Bs(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Js(e){if(!_s(e))throw new Error("Native CDP evaluate did not return an object value.");const t=e.url,n=e.title,r=e.visibilityState,a=e.hasFocus;if("string"!=typeof t||"string"!=typeof n||"string"!=typeof r||"boolean"!=typeof a)throw new Error("Native CDP evaluate returned an unexpected page summary shape.");return{url:t,title:n,visibilityState:r,hasFocus:a}}function Us(e,t){if(!_s(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!_s(n))throw new Error("Native CDP DOM.getDocument did not return a root node.");const r=n.nodeId,a=n.nodeName,o=n.childNodeCount;if("number"!=typeof r||"string"!=typeof a)throw new Error("Native CDP DOM root node has an unexpected shape.");const i=_s(t)?t:{},s=i.bodyTextLength,c=i.elementCount;return{rootNodeId:r,rootNodeName:a,..."number"==typeof o?{childNodeCount:o}:{},..."number"==typeof s?{bodyTextLength:s}:{},..."number"==typeof c?{elementCount:c}:{}}}async function Hs(e){return Js(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Ws(e){return Us(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 Ks(e){return Us(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Gs(e){return await e.bringToFront(),!0}async function Ys(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 Xs(e,t,n,r,a,o){await zs(e,t,n,r,o);const i=await Vs(e,t,As[n],r,a,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 Zs(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await Fs(e,async()=>await Gs(t));return n.push(a.phaseResult),void 0!==a.result&&(r.pageBroughtToFront=a.result),a.phaseResult}case"native-evaluate-url-no-runtime-enable":{const a=await Fs(e,async()=>await Hs(t));return n.push(a.phaseResult),void 0!==a.result&&(r.evaluate=a.result),a.phaseResult}case"native-dom-read-no-runtime-enable":{const a=await Fs(e,async()=>await Ks(t));return n.push(a.phaseResult),void 0!==a.result&&(r.dom=a.result),a.phaseResult}case"native-input-move-no-runtime-enable":{const a=await Fs(e,async()=>await Ys(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function Qs(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 r;try{const a=await Fs("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(a.phaseResult),r=a.result,t.connected=a.phaseResult.success,!a.phaseResult.success||void 0===r)return{summary:t,triggeredNavigation:!1};const o=r;let i=await Xs(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(Ms(e.requestedPhase))return await Zs(e.requestedPhase,o,e.phases,t),i=await Xs(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await Fs("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await Xs(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 Fs("native-evaluate-url",async()=>await Hs(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await Xs(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 Fs("native-dom-read",async()=>await Ws(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await Xs(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function ec(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 tc(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=ec(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=ec(t);if(void 0===n)return;const r=e.request();o({kind:"response",reason:n,capturedAt:(new Date).toISOString(),url:t,method:r.method(),resourceType:r.resourceType(),status:e.status()})},c=e=>{a.push({capturedAt:(new Date).toISOString(),url:e.url()})};e.on("request",i),e.on("response",s),e.on("framenavigated",c);try{await Bs(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function nc(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Tt(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function rc(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function ac(e,t){return await e.evaluate(e=>{const t="localStorage"===e?window.localStorage:window.sessionStorage;return Array.from({length:t.length},(e,n)=>{const r=t.key(n)??"";return{key:r,value:r.length>0?t.getItem(r)??"":""}}).filter(e=>e.key.length>0)},t)}async function oc(e){return(await e.evaluate(e=>{const t=["localStorage","sessionStorage"],n=[];for(const r of t){const t="localStorage"===r?window.localStorage:window.sessionStorage;for(const a of e){const e=t.getItem(a);null!==e&&n.push({area:r,key:a,value:e})}}return n},Rs)).map(e=>Os(e.area,{key:e.key,value:e.value}))}async function ic(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function sc(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),r=t.filter(e=>e.toLowerCase().includes("webdriver")).slice(0,20),a=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:r,automationLikeWindowKeys:a}})}function cc(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function lc(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function dc(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function uc(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function mc(e,t){const n=cc(e),r=cc(t),a=[...new Set([...n.keys(),...r.keys()])],o=[];for(const e of a){const t=n.get(e),a=r.get(e),i=a??t;if(!i)continue;const s={};for(const e of lc(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=dc(n,r))}const c={};for(const e of lc(t?.booleanFields,a?.booleanFields)){const n=t?.booleanFields?.[e],r=a?.booleanFields?.[e];n!==r&&(c[e]={...void 0!==n?{before:n}:{},...void 0!==r?{after:r}:{}})}const l={};for(const e of lc(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=uc(n,r))}void 0!==t&&void 0!==a&&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!==a,...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 pc(e,t){const[n,r,a]=await Promise.all([ac(e,"localStorage"),ac(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>Ds(e.key)).map(e=>Os("localStorage",e)),...r.filter(e=>Ds(e.key)).map(e=>Os("sessionStorage",e))];return{localStorage:n.map(e=>Os("localStorage",e)),sessionStorage:r.map(e=>Os("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>$s(e)),counterDiffs:mc(t,o)}}var gc=ja({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:xs,output:Ss,execute:async(e,t)=>{const n=e.phase??"native",r=e.watchMs??3e3,a=e.networkEventLimit??30,o=ue(),i=de(),s=[],c=[],l=[];let d,u,m,p=!1,g=!1,f=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const h=await Fs("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>Dt(o,e)),v=js(y,e.targetPageId,s);if(v&&(d=Dt(o,v),f=v.targetId,l.push(Ls(o,"native",v,void 0))),!h.phaseResult.success||"native"===n)return{success:h.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Vs(o,l,"native-watch",f,r,s),{success:h.phaseResult.success&&void 0!==f,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(!v)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(Ns(n)){const e=await Qs({requestedPhase:n,target:v,runtime:i,ctxManager:o,targetPageId:f,watchMs:r,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}if("resume-canvas"===n){const e=await Fs("resume-canvas",async()=>{const e=await Gi({skipRiskGate:!0});try{const t=await e.waitForResumeDialog(1e3);return{dialogState:t,geometry:t.canvasReady?await e.readResumeCanvasGeometry():void 0}}finally{e.close()}});c.push(e.phaseResult);const t=e.result;return{success:e.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==t?{resumeCanvas:{dialogVisible:t.dialogState.dialogVisible,iframeFound:t.dialogState.iframeFound,iframeVisible:t.dialogState.iframeVisible,canvasReady:t.dialogState.canvasReady,...void 0!==t.geometry?.screenshotArea?{screenshotArea:t.geometry.screenshotArea}:{},...void 0!==t.geometry?.canvasSize?{canvasSize:t.geometry.canvasSize}:{},...void 0!==t.geometry?.error?{error:t.geometry.error}:{}}}:{},phases:c,warnings:s}}const w=await Fs("browser-attach",async()=>await i.getBrowser());c.push(w.phaseResult);const x=w.result;p=w.phaseResult.success,await zs(o,l,"browser-attach",f,s);const S=await Vs(o,l,"browser-attach-watch",f,r,s);if(S&&s.push("Browser attach was followed by a native URL change; treat this account/browser profile as unsafe for Playwright-backed zhipin tools."),!w.phaseResult.success||"browser-attach"===n)return{success:c.every(e=>e.success)&&!S,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(S)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};const R=[];let C;"network-watch"===n&&void 0!==x&&(C=nc(x,v),C?R.push(tc(C,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",v);const I=await Fs("page-attach",async()=>await o.getPage("zhipin"));c.push(I.phaseResult);const k=I.result;if(g=I.phaseResult.success,k&&(d=await Ot(o,k)),await zs(o,l,"page-attach",f,s),!I.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await Fs("network-watch",async()=>rc(await Promise.all(R),a));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:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,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:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){C!==k&&R.push(tc(k,r,a));const e=await Fs("network-watch",async()=>rc(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await zs(o,l,"network-watch",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let A=[];if("storage-summary"===n){try{A=await oc(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await Bs(r)}const E=await Fs("page-evaluate",async()=>await ic(k));c.push(E.phaseResult);const _=E.result;if(await zs(o,l,"page-evaluate",f,s),!E.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},phases:c,warnings:s};const N=await Fs("detector-fingerprint",async()=>await sc(k));c.push(N.phaseResult);const M=N.result;if(await zs(o,l,"detector-fingerprint",f,s),!N.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await Fs("storage-summary",async()=>await pc(k,A));c.push(P.phaseResult);const T=P.result;return await zs(o,l,"storage-summary",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as fc}from"@roll-agent/sdk";import{z as hc}from"zod";import{createHash as yc}from"node:crypto";var bc,vc,wc={fetch:fetch,env:process.env},xc="https://huajune.duliday.com",Sc=new Set;function Rc(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function Cc(e,t){const n=Rc(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 Ic(e){const t=Cc(e,"RECRUITMENT_EVENTS_ENABLED"),n=Rc(e,"RECRUITMENT_EVENTS_API_BASE_URL")??xc,r=Rc(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=Rc(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function kc(e,t){const n=ye(e.defaultAgentId);if(void 0!==n)return n;he()&&Ac(t,"missing-instance-tracking","Recruitment event skipped: select a browserInstance, configure tracking-agent-id on the active browser instance, or set RECRUITMENT_EVENTS_DEFAULT_AGENT_ID.")}function Ac(e,t,n){Sc.has(t)||(Sc.add(t),e.warn(n))}function Ec(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function _c(e){return"object"==typeof e&&null!==e}function Nc(e,t){const n=e[t];return"string"==typeof n?n:void 0}function Mc(e){if(!_c(e)||!_c(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>_c(e)&&"error"===Nc(e,"status"));if(!_c(t))return;const n=Nc(t,"idempotencyKey"),r=t.error,a=_c(r)?Nc(r,"code"):void 0,o=_c(r)?Nc(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function Pc(e,t,n){const r=Ic(n.env);if(!r.enabled)return;const a=kc(r,t);if(void 0===r.apiToken)return void Ac(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(he()||Ac(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_DEFAULT_AGENT_ID."));const o={events:[{...e,agentId:a,eventTime:e.eventTime??(new Date).toISOString()}]},i=await n.fetch(Ec(r.apiBaseUrl),{method:"POST",headers:{Authorization:`Bearer ${r.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=Mc(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function Tc(e,t){return`${e}:${yc("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function Bc(e,t){(async()=>{const n=vc??(async(e,t)=>{await Pc(e,t,{...wc,...bc})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function Dc(){return(new Date).toISOString().slice(0,10)}function Oc(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function $c(e,t,n){const r=e?.candidateInfo,a=Oc(r?.age),o=Oc(r?.education),i=Oc(r?.expectedSalary);return{name:Oc(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function Fc(e){return Oc(e?.candidateInfo.communicationPosition)??""}function jc(e){const t=Oc(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 Lc(e){const t=Oc(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?Oc(n[0]):void 0}function qc(e){return e.length>0?{job:{jobName:e}}:{}}function zc(e,t){return e.trim().length>0&&t.trim().length>0}function Vc(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 Jc(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&zc(n.name,n.position)&&Bc({idempotencyKey:Tc("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,Dc()]),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 Uc(e,t){const n=Fc(e.candidateDetails),r=n,a=$c(e.candidateDetails,e.candidateName,r);if(!zc(a.name,a.position))return;const o=Lc(e.candidateDetails);Bc({idempotencyKey:Tc("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...qc(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Hc(e,t){if(!e.clicked||0===e.name.length)return;const n=Oc(e.expectedPosition);if(void 0===n||!zc(e.name,n))return;const r=Oc(e.age),a=Oc(e.education),o=Oc(e.expectedSalary),i=Oc(e.expectedLocation);Bc({idempotencyKey:Tc("zhipin-candidate-contacted",[e.candidateId,e.name,Dc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"candidate_contacted",candidate:{name:e.name,position:n,...void 0!==r?{age:r}:{},...void 0!==a?{education:a}:{},...void 0!==o?{expectedSalary:o}:{},...void 0!==i?{expectedLocation:i}:{}},details:{candidateId:e.candidateId}},t)}function Wc(e,t){const n=Fc(e.candidateDetails),r=$c(e.candidateDetails,e.candidateName,n);if(!zc(r.name,r.position))return;const a=Lc(e.candidateDetails);Bc({idempotencyKey:Tc("zhipin-wechat-requested",[e.conversationId,e.candidateId,Dc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...qc(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 Kc(e,t,n,r){const a=Fc(e),o=jc(e),i=Lc(e),s=$c(e,e.candidateInfo.name,o);if(!zc(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=Vc(c);Bc({idempotencyKey:Tc("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...qc(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var Gc,Yc=hc.object({name:hc.string(),conversationId:hc.string(),candidateId:hc.string(),position:hc.string(),time:hc.string(),preview:hc.string(),unreadCount:hc.number(),hasUnread:hc.boolean(),index:hc.number()}),Xc=hc.object({success:hc.boolean(),candidates:hc.array(Yc),total:hc.number(),stats:hc.object({withName:hc.number(),withUnread:hc.number()})});function Zc(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Gc}}function Qc(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 el(e){const t=new Set,n=[];for(const r of e){const e=Qc(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var tl=fc({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:hc.object({limit:hc.number().optional().describe("最多返回条数"),onlyUnread:hc.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:hc.enum(["time","unreadCount","name"]).default("time"),autoScroll:hc.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:hc.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Xc,execute:async(e,t)=>{const n=e.onlyUnread??!1;let r,a;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const o=Zc();try{r=await o.openNativePagePort({requireChatPage:!0}),a=o.createNativeVisualActivitySession(r),await a.begin("正在读取消息列表");if(!await r.waitForSelector('.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',5e3))return await a.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};const i=n?"正在读取未读消息列表":"正在读取消息列表";await a.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=el(await r.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 a.succeed(n?`已读取 ${d.length} 条未读消息`:`已读取 ${d.length} 条消息`),t.logger.info(`Found ${d.length} candidates (${m.withUnread} with unread)`),Jc(d,t.logger),{success:!0,candidates:d,total:l.length,stats:m}}catch(e){return lo(e),await(a?.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{r?.close()}}});import{BrowserActionApprovalSchema as nl,BrowserPageInfoSchema as rl}from"@roll-agent/browser";import{defineTool as al}from"@roll-agent/sdk";import{z as ol}from"zod";var il,sl=ol.object({forceReload:ol.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:ol.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:nl.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),cl=ol.object({success:ol.boolean(),alreadyOnChat:ol.boolean(),usedSidebarClick:ol.boolean(),usedReload:ol.boolean(),chatReady:ol.boolean(),reloadSkippedReason:ol.enum(li).optional(),page:rl.optional(),error:ol.string().optional()});function ll(){return{getContextManager:ue,getRuntime:de,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...il}}async function dl(e,t){return Dt(e,await t.inspectPage())}var ul=al({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:sl,output:cl,execute:async(e,t)=>{const n=ll(),r=n.getContextManager();let a,o,i=!1;t.logger.info("Opening Boss chat page via native sidebar navigation");try{if(a=await n.openNativePagePort(!0===e.forceReload?{requireChatPage:!0}:{}),o=n.createNativeVisualActivitySession(a),await cn(a),!0===e.forceReload){const s=await a.inspectChatReloadTarget();if(!s.ok)return await o.fail("当前不是沟通页"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:!1,chatReady:!1,reloadSkippedReason:s.skippedReason,page:await dl(r,a),error:s.error};rn(t,n.getRuntime(),{action:"navigate",target:s.url,url:s.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),await o.begin("正在刷新沟通页"),t.logger.info("Reloading Boss chat page via native CDP Page.reload"),await a.reload({url:s.url,onReloadSent:()=>{i=!0}});return await a.waitForChatListReady({...void 0!==e.expectedConversationId?{expectedConversationId:e.expectedConversationId}:{}})?(await o.succeed("已刷新沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await dl(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await dl(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(go.nav.sidebar,{label:e,padding:10})}if(await a.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await dl(r,a)};if(!await a.clickSidebarSection("chat",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await dl(r,a),error:"未找到沟通导航"};t.logger.info("Clicked Boss sidebar nav: 沟通");return await a.waitForChatSurface()?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!0,page:await dl(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await dl(r,a),error:"沟通页未就绪"})}catch(e){return lo(e),await(o?.fail("切换沟通页失败")),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,error:e instanceof Error?e.message:"切换沟通页失败"}}finally{a?.close()}}});import{defineTool as ml}from"@roll-agent/sdk";import{z as pl}from"zod";var gl,fl=pl.object({success:pl.boolean(),conversationId:pl.string(),candidateId:pl.string(),candidateName:pl.string(),index:pl.number(),hasUnread:pl.boolean(),unreadCount:pl.number(),lastMessageTime:pl.string(),messagePreview:pl.string(),error:pl.string().optional()});function hl(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...gl}}var yl=ml({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:pl.object({conversationId:pl.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:pl.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:pl.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:pl.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:fl,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=hl();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开目标聊天"),await a.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:"正在定位候选人",padding:8});const o=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,preferUnread:e.preferUnread??!1,...void 0!==a?{motionObserver:a}:{}});return o.found?(await a.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 a.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 lo(n),await(a?.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{r?.close()}}});import{defineTool as bl}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as vl,prepareReplyContext as wl,ReplyAuthorityRequestError as xl}from"@roll-agent/reply-authority-client";import{z as Sl}from"zod";var Rl="·",Cl=/[--—–]/,Il=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function kl(e){return(e??"").trim()}function Al(e){return e.some(e=>"candidate_expected_location"!==e.source)}function El(e){const t=Al(e)?e.filter(e=>"candidate_expected_location"!==e.source):e,n=t.slice(0,3).map(e=>{const t="candidate_expected_location"===e.source?"(弱)":"";return`${e.text}${t}`}),r=t.length>3?` 等${String(t.length)}项`:"";return`${n.join("、")}${r}`}function _l(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function Nl(e){const t=_l(e)?e.signals:e,n=_l(e)?e.analysisPath:void 0,r=_l(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=El(t);return!Al(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function Ml(e){const t=Il.exec(kl(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function Pl(e){const t=kl(e);if(!t||Il.test(t))return;if(!Cl.test(t))return;const[n=""]=t.split(Cl);return kl(n)||void 0}function Tl(e){const t=kl(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(Rl).map(e=>kl(e));return{expectedLocation:n,expectedPosition:r}}function Bl(e){const t=kl(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=Tl(e.expectedJobText),a=Pl(t),o=Ml(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var Dl,Ol=Sl.object({index:Sl.number(),sender:Sl.enum(["candidate","recruiter","system"]),messageType:Sl.enum(["text","system","resume","wechat-exchange"]),content:Sl.string(),time:Sl.string()}),$l=Sl.object({name:Sl.string(),age:Sl.string(),experience:Sl.string(),education:Sl.string(),communicationPosition:Sl.string(),expectedPosition:Sl.string(),expectedLocation:Sl.string(),expectedSalary:Sl.string(),tags:Sl.array(Sl.string())}),Fl=Sl.object({success:Sl.boolean(),conversationId:Sl.string(),candidateId:Sl.string(),candidateInfo:$l,preferredBrand:Sl.string().optional(),preferredBrandId:Sl.number().int().positive().optional(),locationSignals:Sl.array(vl).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:Sl.array(Ol),formattedHistory:Sl.array(Sl.string()),stats:Sl.object({totalMessages:Sl.number(),candidateMessages:Sl.number(),recruiterMessages:Sl.number(),systemMessages:Sl.number()}),error:Sl.string().optional()});function jl(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function Ll(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:jl(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function ql(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function zl(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),prepareReplyContext:wl,...Dl}}function Vl(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Jl(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Ul(e){return{candidateMessage:Jl(e.data.messages),conversationHistory:[...e.formattedHistory],candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==e.preferredBrand?{preferredBrand:e.preferredBrand}:{},...void 0!==e.preferredBrandId?{preferredBrandId:e.preferredBrandId}:{},target:{platform:"zhipin",conversationId:e.selectedTarget.conversationId,candidateId:e.selectedTarget.candidateId,recruiterUsername:e.recruiterUsername}}}var Hl=6e5,Wl=0;function Kl(e){return e instanceof xl?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function Gl(e){Kl(e)&&(Wl=Date.now()+Hl)}async function Yl(e){if(Date.now()<Wl)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===Jl(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=Oo(await e.nativePage.readUsernameEvidence())}catch(t){return void e.logger.debug(`Skip Reply Authority prepare: recruiter username read failed: ${t instanceof Error?t.message:String(t)}`)}if(!t.found)return void e.logger.debug("Skip Reply Authority prepare: recruiter username was not detected.");const n=Ul({data:e.data,selectedTarget:e.selectedTarget,recruiterUsername:t.username,formattedHistory:e.formattedHistory,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,preferredBrand:e.preferredBrand,preferredBrandId:e.preferredBrandId}),r=Date.now();try{e.deps.prepareReplyContext(n).then(t=>{e.logger.info(`Reply Authority prepare ${t.status} for ${e.selectedTarget.conversationId} in ${String(Date.now()-r)}ms`)}).catch(t=>{Gl(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){Gl(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var Xl=bl({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Sl.object({conversationId:Sl.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Sl.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Sl.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Sl.number().default(100).describe("最多返回的消息条数")}),output:Fl,execute:async(e,t)=>{const n=e.maxMessages??100,r=zl(),a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i;const s=a?"正在打开目标聊天":"正在准备当前聊天",c="正在提取聊天记录",l=async(e,t)=>(await(i?.fail(e)),Ll(t));try{o=await r.openNativePagePort(),i=r.createNativeVisualActivitySession(o),await i.begin(s);const d=a?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||!ql(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&&!ql(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=Bl({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=Vl(g.messages),y={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(`已提取 ${y.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${y.totalMessages} messages`),Kc(g,p.conversationId,p.candidateId,t.logger),await Yl({nativePage:o,deps:r,data:g,selectedTarget:p,formattedHistory:h,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,preferredBrand:f.preferredBrand,preferredBrandId:f.preferredBrandId,logger: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}:{},...void 0!==f.preferredBrandId?{preferredBrandId:f.preferredBrandId}:{},locationSignals:[],chatMessages:[...g.messages],formattedHistory:h,stats:y}}catch(e){return lo(e),await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),Ll(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as Zl}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Ql,GenerateSignedReplyResponseSchema as ed,ReasoningConfigSchema as td,ReplyAuthorityRequestError as nd,ReplyStreamFinalEventSchema as rd,ReplyStreamLocationResolvedEventSchema as ad,streamGenerateSignedReply as od}from"@roll-agent/reply-authority-client";import{z as id}from"zod";import{ReplyGateAdvisoryCodeSchema as sd}from"@roll-agent/reply-authority-client";import{z as cd}from"zod";var ld=["option_1","option_2"],dd=["judge","orchestrator"],ud=[...dd,"service_recommended_fallback","explicit_no_judge"],md={RUBRIC_FETCH_FAILED:"rubric_fetch_failed",RUBRIC_MISMATCH:"rubric_mismatch",INVALID_VARIANT_SHAPE:"invalid_variant_shape",JUDGE_SAMPLING_FAILED:"judge_sampling_failed",JUDGE_OUTPUT_INVALID:"judge_output_invalid"},pd=cd.array(sd).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),gd=cd.object({chosenOption:cd.enum(ld).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:cd.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:pd.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:cd.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),fd=gd.omit({judgeModel:!0}).extend({confirmedFindingCodes:pd.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as hd}from"node:crypto";var yd=new Map;function bd(e){for(const[t,n]of yd)n.expiresAt<=e&&yd.delete(t)}function vd(e,t=Math.floor(Date.now()/1e3)){if(bd(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${hd()}`,r={preparedReplyId:n,...e};return yd.set(n,{...r,consumed:!1}),r}function wd(e,t=Math.floor(Date.now()/1e3)){const n=yd.get(e);return void 0===n?(bd(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(yd.delete(e),bd(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(yd.set(e,{...n,consumed:!0}),{ok:!0,record:Rd(n)})}function xd(e,t=Math.floor(Date.now()/1e3)){const n=yd.get(e);return void 0===n?(bd(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(yd.delete(e),bd(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:Rd(n)}}function Sd(e,t,n=Math.floor(Date.now()/1e3)){const r=xd(e,n);if(!r.ok)return r;const a=yd.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(yd.set(e,{...a,judgement:t}),{ok:!0,record:Rd({...a,judgement:t})})}function Rd(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}:{},...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},...void 0!==e.variantGroup?{variantGroup:e.variantGroup}:{},...void 0!==e.judgement?{judgement:e.judgement}:{}}}import{CandidateInfoSchema as Cd}from"@roll-agent/reply-authority-client";import{z as Id}from"zod";var kd=1e3,Ad=8,Ed=500,_d=200,Nd=12,Md=100,Pd=Cd.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),Td=Id.object({candidateMessage:Id.string().min(1).max(kd),recentConversation:Id.array(Id.string().min(1).max(Ed)).max(Ad),candidateInfo:Pd.optional(),preferredBrand:Id.string().min(1).max(_d).optional()});function Bd(e){return e.replace(/(?<!\d)1[3-9]\d{9}(?!\d)/g,"[手机号已隐藏]").replace(/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/g,"[邮箱已隐藏]").replace(/(微信|wechat|vx|v信)(?:号|是|[::]|\s)*[A-Za-z0-9][-_A-Za-z0-9]{5,19}/gi,"$1 [账号已隐藏]")}function Dd(e,t){const n=Bd(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function Od(e){if(void 0===e)return;const t={...void 0!==e.age?{age:Dd(e.age,_d)}:{},...void 0!==e.experience?{experience:Dd(e.experience,_d)}:{},...void 0!==e.education?{education:Dd(e.education,_d)}:{},...void 0!==e.communicationPosition?{communicationPosition:Dd(e.communicationPosition,_d)}:{},...void 0!==e.expectedPosition?{expectedPosition:Dd(e.expectedPosition,_d)}:{},...void 0!==e.expectedLocation?{expectedLocation:Dd(e.expectedLocation,_d)}:{},...void 0!==e.expectedSalary?{expectedSalary:Dd(e.expectedSalary,_d)}:{},...void 0!==e.info?{info:e.info.map(e=>Dd(e,Md)).filter(e=>e.length>0).slice(0,Nd)}:{}};return Object.keys(t).length>0?t:void 0}function $d(e){const t=Dd(e.candidateMessage,kd);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>Dd(e,Ed)).filter(e=>e.length>0).slice(-Ad),r=Od(e.candidateInfo),a=void 0!==e.preferredBrand?Dd(e.preferredBrand,_d):void 0;return Td.parse({candidateMessage:t,recentConversation:n,...void 0!==r?{candidateInfo:r}:{},...void 0!==a&&a.length>0?{preferredBrand:a}:{}})}function Fd(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 variantsId = "roll-agent-reply-preview-variants";\n const findingsId = "roll-agent-reply-preview-findings";\n const badgeId = "roll-agent-reply-preview-badge";\n const locationId = "roll-agent-reply-preview-location";\n const spinnerId = "roll-agent-reply-preview-spinner";\n const styleId = "roll-agent-reply-preview-style";\n const stateKey = "__rollAgentReplyPreviewState";\n const state = window[stateKey] || {};\n window[stateKey] = state;\n\n const cancelPendingRemove = () => {\n if (state.removeTimer) {\n window.clearTimeout(state.removeTimer);\n state.removeTimer = undefined;\n }\n };\n\n if (input.mode === "clear") {\n const existingRoot = document.getElementById(rootId);\n if (!existingRoot) return true;\n\n cancelPendingRemove();\n existingRoot.style.opacity = "0";\n existingRoot.style.transform = "translateY(8px) scale(0.98)";\n state.removeTimer = window.setTimeout(() => {\n if (document.getElementById(rootId) === existingRoot) {\n existingRoot.remove();\n }\n state.removeTimer = undefined;\n }, 240);\n return true;\n }\n\n const ensureRoot = () => {\n let root = document.getElementById(rootId);\n if (root) {\n cancelPendingRemove();\n return root;\n }\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 location = document.createElement("div");\n location.id = locationId;\n location.style.display = "none";\n location.style.flexWrap = "wrap";\n location.style.gap = "6px";\n location.style.fontSize = "11px";\n location.style.lineHeight = "16px";\n location.style.color = "#99F6E4";\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 const variants = document.createElement("div");\n variants.id = variantsId;\n variants.style.display = "none";\n variants.style.gridTemplateColumns = "repeat(2, minmax(0, 1fr))";\n variants.style.gap = "10px";\n variants.style.minHeight = "0";\n\n const findings = document.createElement("div");\n findings.id = findingsId;\n findings.style.display = "none";\n findings.style.flexDirection = "column";\n findings.style.gap = "4px";\n findings.style.paddingTop = "2px";\n findings.style.fontSize = "11px";\n findings.style.lineHeight = "16px";\n findings.style.color = "#CBD5E1";\n\n header.append(spinner, status);\n root.append(header, location, badge, draft, variants, findings);\n document.documentElement.append(root);\n return root;\n };\n\n const root = ensureRoot();\n const status = document.getElementById(statusId);\n const location = document.getElementById(locationId);\n const draft = document.getElementById(draftId);\n const variants = document.getElementById(variantsId);\n const findings = document.getElementById(findingsId);\n const badge = document.getElementById(badgeId);\n const spinner = document.getElementById(spinnerId);\n if (!status || !location || !draft || !variants || !findings || !badge || !spinner) return false;\n\n root.style.opacity = "1";\n root.style.transform = "translateY(0)";\n root.style.width = input.variantSelection\n ? "min(760px, calc(100vw - 40px))"\n : "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = input.variantSelection ? "58vh" : "42vh";\n\n if (typeof input.label === "string") {\n status.textContent = input.label;\n }\n\n if (typeof input.locationSummary === "string" && input.locationSummary.length > 0) {\n location.textContent = input.locationSummary;\n location.style.display = "flex";\n } else if (input.mode === "begin") {\n location.textContent = "";\n location.style.display = "none";\n }\n\n if (typeof input.draftText === "string") {\n draft.textContent = input.draftText;\n }\n\n const resetVariantSelection = () => {\n variants.replaceChildren();\n variants.style.display = "none";\n findings.replaceChildren();\n findings.style.display = "none";\n draft.style.display = "block";\n };\n\n const renderVariantSelection = (selection) => {\n variants.replaceChildren();\n findings.replaceChildren();\n draft.style.display = "none";\n variants.style.display = "grid";\n variants.style.gridTemplateColumns =\n window.innerWidth < 640 ? "minmax(0, 1fr)" : "repeat(2, minmax(0, 1fr))";\n\n for (const option of selection.options) {\n const card = document.createElement("div");\n card.style.minWidth = "0";\n card.style.maxHeight = "32vh";\n card.style.overflow = "hidden auto";\n card.style.padding = "10px";\n card.style.borderRadius = "8px";\n card.style.border = "1px solid rgba(148, 163, 184, 0.28)";\n card.style.background = "rgba(15, 23, 42, 0.66)";\n\n const title = document.createElement("div");\n title.textContent = option.option;\n title.style.marginBottom = "6px";\n title.style.fontSize = "12px";\n title.style.fontWeight = "700";\n title.style.lineHeight = "16px";\n title.style.color = "#A7F3D0";\n\n const body = document.createElement("div");\n body.textContent = option.suggestedReply;\n body.style.whiteSpace = "pre-wrap";\n body.style.fontSize = "13px";\n body.style.lineHeight = "20px";\n body.style.color = "#E2E8F0";\n\n card.append(title, body);\n variants.append(card);\n }\n\n if (selection.findings.length > 0) {\n findings.style.display = "flex";\n const title = document.createElement("div");\n title.textContent = "检查项";\n title.style.fontWeight = "700";\n title.style.color = "#FDE68A";\n findings.append(title);\n\n for (const finding of selection.findings) {\n const item = document.createElement("div");\n item.textContent = finding.description || finding.code;\n item.style.color = "#CBD5E1";\n findings.append(item);\n }\n } else {\n findings.style.display = "none";\n }\n };\n\n if (input.variantSelection && input.variantSelection.options.length > 0) {\n renderVariantSelection(input.variantSelection);\n } else if (\n input.mode === "begin" ||\n input.mode === "draft" ||\n input.mode === "final" ||\n input.mode === "complete" ||\n input.mode === "fail"\n ) {\n resetVariantSelection();\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 jd,Ld=class{target;constructor(e){this.target=e}async begin(e,t){return await this.render({mode:"begin",label:e,...void 0!==t&&t.length>0?{locationSummary:t}:{},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,n){return await this.render({mode:"complete",label:e,draftText:t,provisional:!1,...void 0!==n?{variantSelection:n}:{}})}async fail(e){return await this.render({mode:"fail",label:e})}async clear(){return await this.render({mode:"clear"})}async render(e){if(!je())return!1;try{return await this.target.evaluateJson(Fd(e))}catch{return!1}}},qd=id.object({conversationId:id.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:id.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:id.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:id.number().default(100).describe("最多读取的聊天消息条数"),reasoning:td.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),zd=["rejected","timeout","server_error"],Vd=id.object({requestId:id.string().optional(),elapsedMs:id.number().int().min(0).optional(),clientTimeoutMs:id.number().int().positive().optional(),lastStartedPhase:id.string().min(1).optional(),activePhase:id.string().min(1).optional(),phaseLatencies:id.record(id.number().int().min(0)).optional()}),Jd=id.object({success:id.boolean(),preparedReplyId:id.string().optional(),suggestedReply:id.string().optional(),stage:id.string().optional(),confidence:id.number().optional(),expiresAt:id.number().optional(),timing:id.object({totalLatencyMs:id.number().int().min(0),replyLatencyMs:id.number().int().min(0).optional(),turnPlanningLatencyMs:id.number().int().min(0).optional(),contextBuildingLatencyMs:id.number().int().min(0).optional(),preparedContextHit:id.boolean()}).optional(),gateRewritten:id.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:id.object({groupId:id.string().min(1),options:id.array(id.object({option:id.enum(ld),suggestedReply:id.string(),expiresAt:id.number().int().min(0)})).min(2).max(2),findings:id.array(id.object({code:id.string().min(1),description:id.string().min(1)})),rubricVersion:id.string().min(1),rubricHash:id.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:id.string().optional(),errorKind:id.enum(zd).optional()}).extend(Vd.shape),Ud={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function Hd(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),createReplyPreviewVisualSession:e=>new Ld(e),streamGenerateSignedReply:od,fetchReplyFeedbackRubric:Ql,savePreparedReply:vd,random:Math.random,...jd}}function Wd(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Kd(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function Gd(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function Yd(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function Xd(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Zd(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Qd(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function eu(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function tu(e){const t=eu(e.navigationUnreadCount);return t>0?t:"candidate"===Qd(e.data.messages)?.sender?1:0}function nu(e){const t=Bl({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:Zd(e.data.messages),conversationHistory:Xd(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!==t.preferredBrandId?{preferredBrandId:t.preferredBrandId}:{},...void 0!==e.reasoning?{modelConfig:{reasoning:e.reasoning}}:{},target:{platform:"zhipin",conversationId:e.conversationId,candidateId:e.candidateId,recruiterUsername:e.recruiterUsername}}}function ru(e){return"phase.started"===e.type?Kd(e,"label")??Ud[Kd(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Kd(e,"toolName")?`: ${Kd(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Kd(e,"toolName")?`: ${Kd(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function au(e,t,n={}){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{},...n}}function ou(e,t){const n=t instanceof nd?t:void 0,r=e.requestId??n?.meta.requestId,a=Object.fromEntries(e.phaseLatencies);return{...void 0!==r?{requestId:r}:{},...void 0!==e.startedAtMs?{elapsedMs:Math.max(0,Math.round(Date.now()-e.startedAtMs))}:{},...void 0!==n?{clientTimeoutMs:n.meta.timeoutMs}:{},...void 0!==e.lastStartedPhase?{lastStartedPhase:e.lastStartedPhase}:{},...void 0!==e.activePhase?{activePhase:e.activePhase}:{},...Object.keys(a).length>0?{phaseLatencies:a}:{}}}function iu(e){return e.replace(/\s+\(url=.*\)$/,"")}function su(e){const t=e instanceof Error?e.message:String(e);if(e instanceof nd){const n=iu(t);if(422===e.statusCode)return{message:`回复未通过事实核验:${n}`,errorKind:"rejected"};if(504===e.statusCode||n.includes("请求超时"))return{message:`AI 响应超时:${n}`,errorKind:"timeout"};if(void 0!==e.statusCode&&e.statusCode>=500)return{message:`Reply Authority 服务端异常:${n}`,errorKind:"server_error"}}return{message:t}}function cu(e){return e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(e<1e4?1:0)}s`}function lu(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function du(e){const t=["回复已生成",`总 ${cu(e.totalLatencyMs)}`];return void 0!==e.replyLatencyMs&&t.push(`生成 ${cu(e.replyLatencyMs)}`),e.preparedContextHit&&t.push("预热命中"),e.gateRewritten&&t.push("终稿经安全门调整"),t.join(" · ")}function uu(e,t=void 0,n=!1){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}:{},...void 0!==t?{timing:t}:{},...n?{gateRewritten:n}:{},..."judge_ready"===e.variantGroup?.state?{replyVariantSelection:{groupId:e.variantGroup.groupId,options:e.variantGroup.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply,expiresAt:e.envelopeExp})),findings:e.variantGroup.findings.map(e=>({code:e.code,description:e.description})),rubricVersion:e.variantGroup.rubricVersion,rubricHash:e.variantGroup.rubricHash}}:{}}}function mu(e,t){const n=e.items[0],r=e.items[1];return void 0===n||void 0===r?e.items:t()<.5?[r,n]:[n,r]}function pu(e){return{state:"not_learned",groupId:e.replyVariants.groupId,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},chosenVariant:"draft",reason:e.reason}}function gu(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return pu({...e,reason:md.RUBRIC_MISMATCH});const t=mu(e.replyVariants,e.random);if(2!==t.length)return pu({...e,reason:md.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return pu({...e,reason:md.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:ld[t]??"option_1",variant:e.variant,suggestedReply:e.suggestedReply,signedEnvelope:e.signedEnvelope,envelopeExp:e.envelopeExp})),a=r.find(t=>t.variant===e.replyVariants.recommended)?.option??"option_1";return{state:"judge_ready",groupId:e.replyVariants.groupId,options:r,findings:e.replyVariants.findings,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},recommendedOption:a,judgeContext:e.judgeContext}}var fu=Zl({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:qd,output:Jd,execute:async(e,t)=>{const n=Hd(),r=e.maxMessages??100,a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;const c={startedAtMs:void 0,requestId:void 0,lastStartedPhase:void 0,activePhase:void 0,phaseLatencies:new Map};try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await cn(o),await i.begin(a?"正在打开目标聊天":"正在准备当前聊天");const l=a?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==l&&!l.found){const e=l.error??"打开聊天失败";return await i.fail(e),au(e)}if(void 0===l&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),au(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const d=l?.name??e.candidateName??"",u=await o.readActiveChatPanel();if(d.length>0&&(!u||!Wd(d,u.candidateName))){const e=`右侧聊天面板未切换到 ${d}`;return await i.fail("聊天面板未同步"),au(e)}const m=await o.readSelectedChatTarget();if(!m){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),au(e)}if(null!==u&&m.candidateName.length>0&&!Wd(m.candidateName,u.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`;return await i.fail("当前会话不一致"),au(e)}if(!(void 0===l||l.conversationId.length>0&&m.conversationId===l.conversationId||0===l.conversationId.length&&l.candidateId.length>0&&m.candidateId===l.candidateId)){const e=`当前选中会话与目标会话不一致: ${l?.name||l?.conversationId||""}`;return await i.fail("当前会话不一致"),au(e)}await o.waitForChatMessages();const p=await o.readCandidateChatDetails(r);if(null!==p.selectedTarget&&(p.selectedTarget.conversationId!==m.conversationId||p.selectedTarget.candidateId!==m.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${p.selectedTarget.conversationId}/${p.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),au(e)}if(0===Zd(p.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),au(e)}const g=Oo(await o.readUsernameEvidence());if(!g.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),au(e)}const f=nu({data:p,conversationId:m.conversationId,candidateId:m.candidateId,recruiterUsername:g.username,reasoning:e.reasoning}),h=tu({navigationUnreadCount:l?.unreadCount,data:p});t.logger.info(`Generating reply preview for ${m.candidateName||m.candidateId}`);const y=Date.now();c.startedAtMs=y,await s.begin("正在生成回复");let b,v,w,x="",S=!1;for await(const e of n.streamGenerateSignedReply(f)){if("stream.started"===e.type&&(c.requestId=Kd(e,"requestId"),b=Kd(e,"tenantId")),"phase.started"===e.type){const t=Kd(e,"phase");void 0!==t&&(c.lastStartedPhase=t,c.activePhase=t)}if("phase.completed"===e.type){const t=Kd(e,"phase"),n=Gd(e,"latencyMs");void 0!==t&&void 0!==n&&(c.phaseLatencies.set(t,Math.round(n)),c.activePhase===t&&(c.activePhase=void 0))}"gate.completed"===e.type&&!0===Yd(e,"rewritten")&&(S=!0);const r=ru(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=ad.safeParse(e);if(t.success){const e=Nl({signals:t.data.signals,analysisPath:t.data.analysisPath,inquiryType:t.data.inquiryType});e.length>0&&await s.updateStatus(e)}}if("draft.started"===e.type&&(x="",await s.updateDraft(x,!0)),"draft.delta"===e.type){x+=Kd(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=rd.parse(e),a=ed.parse(r),o=c.phaseLatencies.get("turn_planning"),i=c.phaseLatencies.get("context_building"),l=Math.max(0,Math.round(Date.now()-y)),d=void 0!==a.latencyMs?Math.round(a.latencyMs):void 0,u=a.replyVariants?.items.reduce((e,t)=>Math.min(e,t.envelopeExp),a.envelopeExp)??a.envelopeExp,p=lu({turnPlanningLatencyMs:o,contextBuildingLatencyMs:i});let g;if(w={totalLatencyMs:l,...void 0!==d?{replyLatencyMs:d}:{},...void 0!==o?{turnPlanningLatencyMs:o}:{},...void 0!==i?{contextBuildingLatencyMs:i}:{},preparedContextHit:p},void 0!==a.replyVariants){if(void 0===b){const e="Reply Authority returned replyVariants without stream.started.tenantId; refusing to create a sendable prepared reply without feedback identity.";return t.logger.error(e),await s.fail("双稿反馈身份缺失"),au(e,void 0,ou(c))}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});g=gu({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:m.conversationId,judgeContext:$d(f),random:n.random}),"not_learned"===g.state&&t.logger.warn(`Reply variants will use a non-learning terminal outcome: reason=${g.reason}`)}catch(e){const n=e instanceof Error?e.message:String(e);t.logger.warn(`Failed to fetch reply feedback rubric; using a non-learning terminal outcome. reason=${md.RUBRIC_FETCH_FAILED} detail=${n}`),g=pu({replyVariants:a.replyVariants,tenantId:b,conversationId:m.conversationId,reason:md.RUBRIC_FETCH_FAILED})}}v=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:u,unreadCountBeforeReply:h,...void 0!==c.requestId?{requestId:c.requestId}:{},...void 0!==g?{variantGroup:g}:{}}),await s.complete(du({...w,gateRewritten:S}),a.suggestedReply,"judge_ready"===g?.state?{options:g.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})),findings:g.findings}:void 0)}}if(void 0===v){const e="Reply Authority stream 未返回 final";return await s.fail(e),au(e,void 0,ou(c))}return uu(v,w,S)}catch(e){lo(e);const t=su(e),n=ou(c,e);return await(s?.fail(t.message)),await(i?.fail(t.message)),au(t.message,t.errorKind,n)}finally{o?.close()}}});import{defineTool as hu}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as yu}from"@roll-agent/reply-authority-client";import{z as bu}from"zod";var vu,wu="zhipin_judge_prepared_reply",xu=bu.object({success:bu.boolean(),variantDecision:gd.optional(),decisionSource:bu.enum(ud).optional(),fallback:bu.boolean().optional(),recommendedOption:bu.enum(ld).optional(),error:bu.string().optional()}),Su=bu.object({preparedReplyId:bu.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:bu.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),Ru=new Map;function Cu(){return{fetchReplyFeedbackRubric:yu,...vu}}function Iu(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function ku(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return{kind:"fallback",source:"service_recommended_fallback",recommendedOption:n.recommendedOption,reason:t}}function Au(e){const t=e.trim(),n=/^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(t),r=n?.[1]?.trim()??t,a=r.indexOf("{"),o=r.lastIndexOf("}");if(a<0||o<a)throw new Error("judge 输出不是 JSON 对象");return JSON.parse(r.slice(a,o+1))}function Eu(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)return!1;const r=new Set(n.findings.map(e=>e.code));return t.every(e=>r.has(e))}function _u(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return["你是招聘回复双稿 judge。请只比较中性选项 option_1 / option_2,不要推断内部稿件身份。","两份 option 均已通过 Reply Authority 的事实硬门并完成签名;不得自行补充、改写或推断岗位事实。","请结合 candidateContext、当前回复阶段和冻结 rubric,比较目标贴合、合规、事实安全、语气、转化意图、候选人体验、简洁度与回归风险。","reason 必须指出候选人当前目标、胜出选项的具体优势,以及另一选项的具体不足;禁止只写“更好”“更自然”等空泛结论。","reason 不得复述姓名、电话、微信号、candidateId、conversationId 或大段候选人原话。","输出必须是 JSON 对象,且只包含:chosenOption、reason、confirmedFindingCodes。","confirmedFindingCodes 必须显式返回且只能来自 findings.code;只有逐项核实全部为误报时才返回空数组,禁止因不确定而省略。","confirmedFindingCodes 与 chosenOption 相互独立:只要某个 finding 指出的问题在任一 option 文本中真实存在,就必须勾选该 code,哪怕最终选择了包含该问题的 option。","若两稿差异很小,仍选择整体风险更低的一稿,并在 reason 中说明关键取舍。","",`stage: ${e.stage}`,`candidateContext: ${JSON.stringify(n.judgeContext??null)}`,`rubricVersion: ${n.rubricVersion}`,`rubricHash: ${n.rubricHash}`,`rubric: ${JSON.stringify(t.rubric)}`,`advisoryFindings: ${JSON.stringify(t.advisoryFindings)}`,`findings: ${JSON.stringify(n.findings)}`,`options: ${JSON.stringify(n.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})))}`].join("\n")}async function Nu(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=Cu();let o,i,s;try{o=await a.fetchReplyFeedbackRubric({tenantId:r.target.tenantId,rubricVersion:r.rubricVersion})}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.RUBRIC_FETCH_FAILED} detail=${r}`),ku(e,md.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return ku(e,md.RUBRIC_MISMATCH);try{i=await t.llm.generateText(_u(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_SAMPLING_FAILED} detail=${r}`),ku(e,md.JUDGE_SAMPLING_FAILED)}try{s=fd.parse(Au(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_OUTPUT_INVALID} detail=${r}`),ku(e,md.JUDGE_OUTPUT_INVALID)}return Eu(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:Bd(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),ku(e,md.JUDGE_OUTPUT_INVALID))}async function Mu(e,t,n="mcp-sampling"){if("judge_ready"!==e.variantGroup?.state)throw new Error("preparedReplyId 未包含双稿选项");if(void 0!==e.judgement)return e.judgement;const r=Ru.get(e.preparedReplyId);if(void 0!==r)return r;const a=Nu(e,t,n).then(n=>{const r=Sd(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{Ru.delete(e.preparedReplyId)});return Ru.set(e.preparedReplyId,a),a}function Pu(e){return"decision"===e.kind?{success:!0,variantDecision:e.decision,decisionSource:e.source}:{success:!0,fallback:!0,recommendedOption:e.recommendedOption,decisionSource:e.source,error:e.reason}}var Tu=hu({name:wu,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:Su,output:xu,execute:async(e,t)=>{const n=xd(e.preparedReplyId);if(!n.ok)return{success:!1,error:Iu(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return xu.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return Pu(await Mu(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as Bu}from"node:crypto";import{BrowserActionApprovalSchema as Du}from"@roll-agent/browser";import{postReplyFeedback as Ou}from"@roll-agent/reply-authority-client";import{defineTool as $u}from"@roll-agent/sdk";import{z as Fu}from"zod";import{createHash as ju}from"node:crypto";import{mkdirSync as Lu}from"node:fs";import{homedir as qu}from"node:os";import{dirname as zu,resolve as Vu}from"node:path";import{DatabaseSync as Ju}from"node:sqlite";import{postReplyFeedback as Uu,ReplyFeedbackBodySchema as Hu}from"@roll-agent/reply-authority-client";import{createAgentLogger as Wu}from"@roll-agent/sdk";var Ku,Gu,Yu=604800,Xu=3e4,Zu=1e3,Qu=9e5,em=9e5,tm=50,nm=["pending","delivered","dead"],rm=nm.map(e=>`'${e}'`).join(", "),am=Wu("browser-use-agent:reply-feedback-outbox");function om(e){return"~"===e?qu():e.startsWith("~/")?Vu(qu(),e.slice(2)):Vu(e)}function im(){return Vu(qu(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function sm(e,t){const n=process.env[e]?.trim();if(!n)return t;const r=Number(n);if(!Number.isSafeInteger(r)||r<=0)throw new Error(`${e} must be a positive integer.`);return r}function cm(e){const t=e.retentionSeconds??sm("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",Yu),n=e.flushIntervalMs??sm("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",Xu),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??im();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=om(r),o=e.retryBaseDelayMs??Zu,i=e.authRetryDelayMs??Qu;if(!Number.isSafeInteger(t)||t<=0)throw new Error("reply feedback outbox retentionSeconds must be a positive integer.");if(!Number.isSafeInteger(n)||n<=0)throw new Error("reply feedback outbox flushIntervalMs must be a positive integer.");if(!Number.isSafeInteger(o)||o<=0)throw new Error("reply feedback outbox retryBaseDelayMs must be a positive integer.");if(!Number.isSafeInteger(i)||i<=0)throw new Error("reply feedback outbox authRetryDelayMs must be a positive integer.");return{dbPath:a,retentionMs:1e3*t,flushIntervalMs:n,retryBaseDelayMs:o,authRetryDelayMs:i}}function lm(e){if(null===e||"boolean"==typeof e||"number"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(lm).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${lm(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function dm(e){return ju("sha256").update(e).digest("hex")}function um(e,t,n){if(void 0===n)return e+t;if(!Number.isSafeInteger(n)||n<0||n>Math.floor(Number.MAX_SAFE_INTEGER/1e3))throw new Error("feedbackExpiresAt must be a non-negative Unix timestamp in seconds.");return Math.min(e+t,1e3*n)}function mm(e,t){return e.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE group_id = ?").get(t)}function pm(e){return Hu.parse(JSON.parse(e.body_json))}function gm(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function fm(e){if("object"!=typeof e||null===e)return;const t=Reflect.get(e,"statusCode");return"number"==typeof t&&Number.isInteger(t)?t:void 0}function hm(e){return 401===e||403===e}function ym(e){return!(void 0!==e&&!hm(e))||(408===e||425===e||429===e||e>=500)}function bm(e,t){if(hm(t.last_status_code??void 0))return e.authRetryDelayMs;const n=Math.min(Math.max(t.attempt_count-1,0),10);return Math.min(e.retryBaseDelayMs*2**n,em)}function vm(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function wm(e,t){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead',\n last_error = 'Reply feedback outbox retention expired before delivery.',\n updated_at = ?,\n delete_after = ?\n WHERE state = 'pending' AND retry_expires_at <= ?").run(t,t+e.retentionMs,t),e.db.prepare("DELETE FROM reply_feedback_outbox WHERE state <> 'pending' AND delete_after <= ?").run(t)}function xm(e,t,n,r){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'delivered', delivered_status = ?, last_status_code = NULL,\n last_error = NULL, updated_at = ?, delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(n,r,r+e.retentionMs,t.group_id,t.body_hash)}function Sm(e,t,n,r){const a=fm(n),o=gm(n);if(ym(a)&&r<t.retry_expires_at){const n=r+bm(e,{...t,last_status_code:a??null});return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'pending', next_attempt_at = ?, last_status_code = ?, last_error = ?,\n updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,a??null,o,r,t.group_id,t.body_hash),e.logger.warn(`Reply feedback queued for retry: groupId=${t.group_id} statusCode=${a??"network"} nextAttemptAt=${new Date(n).toISOString()} error=${o}`),{status:"queued",error:o}}return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead', last_status_code = ?, last_error = ?, updated_at = ?,\n delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(a??null,o,r,r+e.retentionMs,t.group_id,t.body_hash),e.logger.error(`Reply feedback moved to dead state: groupId=${t.group_id} statusCode=${a??"network"} error=${o}`),{status:"failed",error:o}}async function Rm(e,t){const n=Date.now();if(n>=t.retry_expires_at)return Sm(e,t,new Error("Reply feedback outbox retention expired."),n);e.db.prepare("UPDATE reply_feedback_outbox\n SET attempt_count = attempt_count + 1, updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,t.group_id,t.body_hash);try{const n=await e.deliver(pm(t));if("accepted"!==n.status&&"duplicate"!==n.status)throw new Error(`Unexpected reply feedback response status: ${String(n.status)}`);if(n.groupId!==t.group_id)throw new Error(`Reply feedback response groupId mismatch: expected ${t.group_id}, got ${n.groupId}.`);const r=Date.now();return xm(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return Sm(e,mm(e.db,t.group_id)??t,n,Date.now())}}async function Cm(e){const t=Date.now();wm(e,t);const n=e.db.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE state = 'pending' AND next_attempt_at <= ?\n ORDER BY next_attempt_at ASC, rowid ASC\n LIMIT ?").all(t,tm);for(const t of n)await Rm(e,t)}function Im(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,vm(e,()=>Cm(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function km(e){Lu(zu(e),{recursive:!0});const t=new Ju(e);try{return t.exec(`\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n PRAGMA busy_timeout = 5000;\n CREATE TABLE IF NOT EXISTS reply_feedback_outbox (\n group_id TEXT PRIMARY KEY,\n body_json TEXT NOT NULL,\n body_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN (${rm})),\n attempt_count INTEGER NOT NULL DEFAULT 0,\n next_attempt_at INTEGER NOT NULL,\n retry_expires_at INTEGER NOT NULL,\n delete_after INTEGER NOT NULL,\n delivered_status TEXT CHECK (delivered_status IN ('accepted', 'duplicate')),\n last_status_code INTEGER,\n last_error TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE INDEX IF NOT EXISTS reply_feedback_outbox_due_idx\n ON reply_feedback_outbox (state, next_attempt_at);\n `),t}catch(e){throw t.close(),e}}function Am(e={}){if(Ku&&!Ku.closing)return Ku.deliver=e.deliver??Ku.deliver,void(Ku.logger=e.logger??Ku.logger);if(Ku?.closing||Gu)throw new Error("Reply feedback outbox is shutting down.");const t=cm(e),n=km(t.dbPath),r=setInterval(()=>{Ku&&Im(Ku)},t.flushIntervalMs);r.unref();const a={db:n,dbPath:t.dbPath,retentionMs:t.retentionMs,flushIntervalMs:t.flushIntervalMs,retryBaseDelayMs:t.retryBaseDelayMs,authRetryDelayMs:t.authRetryDelayMs,timer:r,operationQueue:Promise.resolve(),deliver:e.deliver??Uu,logger:e.logger??am,closing:!1,backgroundFlushQueued:!1};Ku=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),Im(a)}async function Em(){if(Gu)return Gu;const e=Ku;return e?(e.closing=!0,clearInterval(e.timer),Gu=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),Ku===e&&(Ku=void 0)}})().finally(()=>{Gu=void 0})):void 0}async function _m(e,t,n,r={}){Ku||Am({deliver:t,logger:n});const a=Ku;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=Hu.parse(e),i=lm(o),s=dm(i);return vm(a,async()=>{const e=Date.now(),t=um(e,a.retentionMs,r.feedbackExpiresAt);wm(a,e);const n=a.db.prepare("INSERT INTO reply_feedback_outbox (\n group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error,\n created_at, updated_at\n ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?, NULL, NULL, NULL, ?, ?)\n ON CONFLICT(group_id) DO NOTHING").run(o.groupId,i,s,e,t,t+a.retentionMs,e,e);let c=mm(a.db,o.groupId);return c?c.body_hash!==s?{status:"failed",error:`Reply feedback group ${o.groupId} already has a different payload.`}:("pending"===c.state&&c.retry_expires_at>t&&(a.db.prepare("UPDATE reply_feedback_outbox\n SET retry_expires_at = ?, delete_after = MIN(delete_after, ?), updated_at = ?\n WHERE group_id = ? AND body_hash = ? AND state = 'pending'").run(t,t+a.retentionMs,e,c.group_id,c.body_hash),c=mm(a.db,o.groupId)??c),"delivered"===c.state?{status:"duplicate"}:"dead"===c.state?{status:"failed",error:c.last_error??"Reply feedback is permanently dead."}:0===n.changes&&c.next_attempt_at>e?{status:"queued",...c.last_error?{error:c.last_error}:{}}:Rm(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}import{defineTool as Nm}from"@roll-agent/sdk";import{z as Mm}from"zod";function Pm(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as Tm}from"zod";var Bm="reply-authority-service",Dm="browser-use-agent/zhipin_send_reply",Om="zhipin",$m=60,Fm=Tm.object({platform:Tm.literal(Om),username:Tm.string().min(1),accountId:Tm.string().min(1).optional()}),jm=Tm.object({v:Tm.literal(2),iss:Tm.literal(Bm),kid:Tm.string().min(1),jti:Tm.string().min(1),iat:Tm.number().int(),exp:Tm.number().int(),aud:Tm.literal(Dm),platform:Tm.literal(Om),tenantId:Tm.string().min(1),conversationId:Tm.string().min(1),candidateId:Tm.string().min(1),reply:Tm.string(),policyVersion:Tm.string().min(1),recruiterBinding:Fm}),Lm=Tm.object({kid:Tm.string().min(1),algorithm:Tm.literal("Ed25519"),publicKey:Tm.string().min(1),validFrom:Tm.string().min(1),validUntil:Tm.string().optional()}),qm=Tm.object({keys:Tm.array(Lm)}),zm=new Map;function Vm(e){for(const[t,n]of zm)n<e-$m&&zm.delete(t)}function Jm(e,t=Math.floor(Date.now()/1e3)){return Vm(t),zm.has(e)}function Um(e,t,n=Math.floor(Date.now()/1e3)){Vm(n),zm.set(e,t)}import{createPublicKey as Hm,verify as Wm}from"node:crypto";var Km=new Map,Gm=null;function Ym(){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 Xm(e){return Km=new Map(e.map(e=>[e.kid,e]))}async function Zm(){if(Gm)return Gm;Gm=(async()=>{const e=await fetch(Ym()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Xm(qm.parse(t).keys)})();try{return await Gm}finally{Gm=null}}async function Qm(){await Zm()}async function ep(e){const t=Km.get(e);if(t)return t;const n=(await Zm()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function tp(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function np(e){const t=e.split("."),n=t[0],r=t[1];if(2!==t.length||void 0===n||void 0===r||0===n.length||0===r.length)throw new Error("Invalid signed envelope format");let a,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{a=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}tp(a);const i=jm.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function rp(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-$m)throw new Error("Envelope expired");if(e.iat>t+$m)throw new Error("Envelope issued in the future")}async function ap(e,t=Math.floor(Date.now()/1e3)){const n=np(e),r=await ep(n.payload.kid),a=Hm({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!Wm(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return rp(n.payload,t),n.payload}var op,ip=Mm.object({success:Mm.boolean(),sentMessage:Mm.string(),error:Mm.string().optional()});function sp(){return{getReplyAuthorityKeysLoaded:pe,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...op}}function cp(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function lp(e){return"function"==typeof e.readCandidateChatDetails}async function dp(e){if(lp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function up(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function mp(e,t){return null===t||0===e.candidateName.length||cp(e.candidateName,t.candidateName)}function pp(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function gp(e,t){const n=sp();let r,a,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 ap(e.signedEnvelope);if(o=i.reply,Jm(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await new Ld(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=pp(e.unreadCountBeforeReply);if(!up(c,i)||!mp(c,s)){const t=await r.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:t.error??"未找到目标聊天"};l=Math.max(l,pp(t.unreadCount)),s=await r.readActiveChatPanel(),c=await r.readSelectedChatTarget()}if(null===c)return await a.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==s&&c.candidateName.length>0&&!cp(c.candidateName,s.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${c.candidateName} / ${s.candidateName}`;return await a.fail(e),{success:!1,sentMessage:o,error:e}}if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return await a.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=Oo(await r.readUsernameEvidence());if(!d.found)return await a.fail("未找到用户名"),{success:!1,sentMessage:o,error:"未找到用户名,请确认当前页面已登录招聘者账号。"};const u={platform:"zhipin",username:d.username,strategy:d.strategy,source:d.source};if(!Pm(u,i.recruiterBinding)){const e=`recruiter 绑定不匹配:当前账号 ${u.username} 与签发时 ${i.recruiterBinding.username} 不一致`;return await a.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 r.sendChatReply(o,{...void 0!==a?{motionObserver:a}:{}});if(!m.success)return await a.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"};const p=await dp(r);return Uc({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),Um(i.jti,i.exp),await a.succeed("已发送回复"),{success:!0,sentMessage:o}}catch(e){lo(e);const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,sentMessage:o,error:t}}finally{r?.close()}}var fp,hp=Nm({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Mm.object({signedEnvelope:Mm.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Mm.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Mm.number().optional().describe("候选人在列表中的索引(可选)")}),output:ip,execute:async(e,t)=>await gp(e,t)}),yp="zhipin_send_prepared_reply",bp=80,vp=Fu.object({success:Fu.boolean(),sentMessage:Fu.string(),chosenOption:Fu.enum(ld).optional(),decisionSource:Fu.enum(ud).optional(),decisionReason:Fu.string().optional(),judgeModel:Fu.string().optional(),feedbackExpected:Fu.boolean().optional(),feedbackStatus:Fu.enum(["accepted","duplicate","queued","skipped","failed"]).optional(),feedbackError:Fu.string().optional(),error:Fu.string().optional()}),wp=Fu.object({preparedReplyId:Fu.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:Je.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:Du.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:gd.optional().describe("可选的显式双稿选择;缺省时发送工具会自动执行并缓存默认 Judge。"),skipVariantJudge:Fu.boolean().optional().describe("仅用于明确的应急降级:跳过双稿 Judge、发送服务端推荐稿并回传 not_learned 终态,但不产生学习样本。")}).superRefine((e,t)=>{void 0!==e.variantDecision&&!0===e.skipVariantJudge&&t.addIssue({code:Fu.ZodIssueCode.custom,message:"variantDecision 与 skipVariantJudge 不能同时传入",path:["skipVariantJudge"]})});function xp(){return{sendSignedZhipinReply:gp,postReplyFeedback:Ou,submitReplyFeedback:_m,...fp}}function Sp(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function Rp(e,t){const n=e=>void 0===e?void 0:[...e].sort();return e.chosenOption===t.chosenOption&&e.reason===t.reason&&JSON.stringify(n(e.confirmedFindingCodes))===JSON.stringify(n(t.confirmedFindingCodes))}function Cp(e){return"decision"===e.kind?{kind:"decision",source:e.source,decision:e.decision}:{kind:"fallback",source:e.source,recommendedOption:e.recommendedOption,reason:e.reason}}async function Ip(e,t,n){const r=e.variantGroup;return void 0===r?void 0!==t.variantDecision||!0===t.skipVariantJudge?{kind:"error",error:"当前 preparedReplyId 没有双稿选项,不能提交双稿选择或跳过 Judge"}:{kind:"single"}:"not_learned"===r.state?void 0!==t.variantDecision?{kind:"error",error:"当前 preparedReplyId 已降级为非学习终态,禁止提交学习 decision"}:{kind:"terminal_fallback",source:"service_recommended_fallback",chosenVariant:r.chosenVariant,reason:r.reason}:void 0!==t.variantDecision?void 0!==e.judgement?"fallback"===e.judgement.kind?{kind:"error",error:"当前 preparedReplyId 的默认 Judge 已降级,禁止事后伪造学习 decision"}:Rp(e.judgement.decision,t.variantDecision)?Cp(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?Cp(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:Cp(await Mu(e,n))}function kp(e,t){return"judge_ready"===e.variantGroup?.state?e.variantGroup.options.find(e=>e.option===t):void 0}function Ap(e,t){if(void 0===t.confirmedFindingCodes)return!1;const n=e.variantGroup;if("judge_ready"!==n?.state)return!0;const r=new Set(n.findings.map(e=>e.code));return t.confirmedFindingCodes.some(e=>!r.has(e))}function Ep(e,t){const n=e.variantGroup;if(void 0===n)return"single"!==t.kind?{ok:!1,error:"当前 preparedReplyId 没有双稿选项"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},record:e};if("not_learned"===n.state)return"terminal_fallback"!==t.kind?{ok:!1,error:"非学习双稿 preparedReplyId 缺少降级终态"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenVariant:t.chosenVariant,decisionSource:t.source,decisionReason:t.reason,record:e};if("single"===t.kind||"terminal_fallback"===t.kind)return{ok:!1,error:"双稿 preparedReplyId 缺少选择状态"};const r="decision"===t.kind?t.decision:void 0;if(void 0!==r&&Ap(e,r))return{ok:!1,error:"variantDecision.confirmedFindingCodes 必须来自 replyVariantSelection.findings"};const a="decision"===t.kind?t.decision.chosenOption:t.recommendedOption,o=kp(e,a);if(void 0===o)return{ok:!1,error:`未找到双稿选项 ${a}`};const i="decision"===t.kind?t.decision.reason:t.reason;return{ok:!0,signedEnvelope:o.signedEnvelope,suggestedReply:o.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenOption:o.option,chosenVariant:o.variant,...void 0!==r?{variantDecision:r}:{},decisionSource:t.source,decisionReason:i,record:e}}function _p(e){return`sha256:${Bu("sha256").update(JSON.stringify([e.record.preparedReplyId,e.signedEnvelope,e.suggestedReply,e.record.expiresAt,e.unreadCountBeforeReply,e.record.variantGroup?.groupId,e.chosenOption,e.decisionSource,e.decisionReason,e.variantDecision?.confirmedFindingCodes,e.variantDecision?.judgeModel])).digest("hex")}`}function Np(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>bp?`${t.slice(0,bp-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function Mp(e,t,n){const r=t.record.variantGroup;if(void 0===r)return{};if(void 0===t.chosenVariant||void 0===t.decisionSource||void 0===t.decisionReason)return{feedbackStatus:"failed",feedbackError:"Dual-draft send completed without terminal feedback metadata."};try{let a;if(void 0!==t.variantDecision){if("judge"!==t.decisionSource&&"orchestrator"!==t.decisionSource)throw new Error("Learning feedback has an invalid decision source.");a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"selected",decisionSource:t.decisionSource,...void 0!==t.variantDecision.confirmedFindingCodes?{confirmedFindingCodes:t.variantDecision.confirmedFindingCodes}:{},reason:t.variantDecision.reason,rubricVersion:r.rubricVersion,rubricHash:r.rubricHash,...void 0!==t.variantDecision.judgeModel?{judgeModel:t.variantDecision.judgeModel}:{}}}else{if("service_recommended_fallback"!==t.decisionSource&&"explicit_no_judge"!==t.decisionSource)throw new Error("Non-learning feedback has an invalid decision source.");const e=t.decisionReason.trim().slice(0,500);a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"not_learned",decisionSource:t.decisionSource,reason:e||"Dual-draft learning was intentionally skipped.",rubricVersion:r.rubricVersion,rubricHash:r.rubricHash}}const o=await e.submitReplyFeedback(a,e.postReplyFeedback,n.logger,void 0!==r.feedbackExpiresAt?{feedbackExpiresAt:r.feedbackExpiresAt}:{});return{feedbackStatus:o.status,...void 0!==o.error?{feedbackError:o.error}:{}}}catch(e){const t=e instanceof Error?e.message:String(e);return n.logger.error(`Reply feedback outbox failed after sending prepared reply: ${t}`),{feedbackStatus:"failed",feedbackError:t}}}function Pp(e){return void 0===e.decisionSource?{}:{...void 0!==e.chosenOption?{chosenOption:e.chosenOption}:{},decisionSource:e.decisionSource,...void 0!==e.decisionReason?{decisionReason:e.decisionReason}:{},...void 0!==e.variantDecision?.judgeModel?{judgeModel:e.variantDecision.judgeModel}:{},feedbackExpected:void 0!==e.variantDecision}}var Tp=$u({name:yp,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:wp,output:vp,execute:async(e,t)=>{const n=xp(),r=xd(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:Sp(r.reason)};const a=await Ip(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=Ep(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=_p(o),s=`${o.record.preparedReplyId}:${i}`,c=mt(t,{subject:{tool:yp,target:o.record.preparedReplyId,summary:Np(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});rn(t,de(),{action:yp,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=wd(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:Sp(l.reason)};const d=Ep(l.record,a);if(!d.ok)return{success:!1,sentMessage:"",error:d.error};const u=await n.sendSignedZhipinReply({signedEnvelope:d.signedEnvelope,...void 0!==d.unreadCountBeforeReply?{unreadCountBeforeReply:d.unreadCountBeforeReply}:{}},t);if(!u.success)return u;const m=await Mp(n,d,t);return{...u,...Pp(d),...m}}});import{defineTool as Bp}from"@roll-agent/sdk";import{z as Dp}from"zod";var Op,$p=Dp.object({success:Dp.boolean(),exchanged:Dp.boolean(),wechatNumber:Dp.string().optional(),error:Dp.string().optional()}),Fp=900,jp=160,Lp=1100;function qp(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Op}}function zp(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Vp(e){return"function"==typeof e.readCandidateChatDetails}async function Jp(e){if(Vp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var Up=Bp({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Dp.object({conversationId:Dp.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Dp.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Dp.number().optional().describe("候选人在列表中的索引(可选)")}),output:$p,execute:async(e,t)=>{const n=qp();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在换微信"),void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index){const t=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,...void 0!==a?{motionObserver:a}:{}});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,exchanged:!1,error:t.error??"未找到目标聊天"}}else if(!await r.isChatSurfaceOpen())return await a.fail("消息列表未加载"),{success:!1,exchanged:!1,error:"消息列表未加载"};const o=await r.readSelectedChatTarget();if(null===o)return await a.fail("未选中聊天联系人"),{success:!1,exchanged:!1,error:"未选中聊天联系人,无法点击当前聊天输入区的「换微信」按钮"};const i=await r.readActiveChatPanel();if(null!==i&&o.candidateName.length>0&&!zp(o.candidateName,i.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${o.candidateName} / ${i.candidateName}`;return await a.fail(e),{success:!1,exchanged:!1,error:e}}t.logger.info(`Starting native WeChat exchange with ${o.candidateName}`);const s=await Jp(r),c=await r.exchangeWechat({preClickDelayMs:Fp,pressDurationMs:jp,settleMs:Lp,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&Wc({conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.candidateName,...void 0!==c.wechatNumber?{wechatNumber:c.wechatNumber}:{},...void 0!==s?{candidateDetails:s}:{}},t.logger),await a.succeed("已完成换微信"),c):(await a.fail(c.error??"换微信失败"),c)}catch(e){lo(e);const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,exchanged:!1,error:t}}finally{r?.close()}}});import{defineTool as Hp}from"@roll-agent/sdk";import{z as Wp}from"zod";var Kp,Gp=Wp.object({success:Wp.boolean(),username:Wp.string(),usedSelector:Wp.string().optional(),usedStrategy:Wp.string().optional(),source:Wp.string().optional(),error:Wp.string().optional()});function Yp(){return{openNativePagePort:Gi,pickBestUsername:Oo,createNativeVisualActivitySession:e=>new aa(e),...Kp}}var Xp=Hp({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Wp.object({}),output:Gp,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Yp();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在识别登录账号"),await a.highlightSelector('header, #header, [role="banner"], [role="navigation"]',{label:"正在识别登录账号",padding:10});const e=n.pickBestUsername(await r.readUsernameEvidence());if(!e.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return await a.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 lo(e),await(a?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}finally{r?.close()}}});import{defineTool as Zp}from"@roll-agent/sdk";import{z as Qp}from"zod";var eg,tg=["unknown","top","middle","bottom","only-page"],ng=Qp.object({containerFound:Qp.boolean(),containerLabel:Qp.string(),scrollTop:Qp.number(),scrollHeight:Qp.number(),clientHeight:Qp.number(),itemCount:Qp.number(),atStart:Qp.boolean(),atEnd:Qp.boolean()}),rg=Qp.object({success:Qp.boolean(),surface:Qp.enum(Ha),direction:Qp.enum(["up","down"]),stepsRequested:Qp.number(),stepsCompleted:Qp.number(),reachedBoundary:Qp.boolean(),atTop:Qp.boolean(),atBottom:Qp.boolean(),canScrollUp:Qp.boolean(),canScrollDown:Qp.boolean(),position:Qp.enum(tg),before:ng,after:ng,error:Qp.string().optional()});function ag(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...eg}}function og(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function ig(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function sg(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:ig(e)}}var cg=Zp({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:Qp.object({surface:Qp.enum(Ha).describe("要滚动的页面区域"),direction:Qp.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:Qp.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:Qp.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:Qp.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:rg,execute:async(e,t)=>{const n=ag(),r=Ka(e.surface),a=e.direction??r.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(r.highlightSelector,{label:l,padding:8});const d=await s.scrollSurface(e.surface,{direction:a,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,...sg(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...sg(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){lo(t),await(c?.fail("滚动失败"));const n=og();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...sg(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as lg}from"@roll-agent/sdk";import{z as dg}from"zod";var ug,mg=dg.object({applyMode:dg.enum(yo),ageMin:dg.number().int().min(16).optional(),ageMax:dg.number().int().min(16).optional(),location:dg.object({city:dg.string(),district:dg.string().optional()}).optional(),optionSelections:dg.array(dg.object({fieldKey:dg.string(),label:dg.string(),values:dg.array(dg.string()),selection:dg.enum(["single","multi"]),clearValue:dg.string()}))}),pg=dg.object({ageMin:dg.number().optional(),ageMax:dg.number().optional(),location:dg.object({city:dg.string(),district:dg.string().optional()}).optional(),optionSelections:dg.array(dg.object({fieldKey:dg.string(),label:dg.string(),values:dg.array(dg.string())})),gender:dg.string().optional(),activity:dg.string().optional()}),gg=dg.object({success:dg.boolean(),status:dg.enum(vo),requested:mg,applied:pg.optional(),filterButtonText:dg.string().optional(),error:dg.string().optional()}),fg=dg.string().min(1),hg=dg.array(fg).min(1),yg=dg.object({applyMode:dg.enum(yo).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:dg.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:dg.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:fg.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:fg.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:dg.enum(fo).optional().describe("性别筛选:不限、男、女"),activity:dg.enum(ho).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:hg.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:fg.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:fg.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:hg.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:hg.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:fg.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:hg.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:fg.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:hg.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:hg.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:fg.optional().describe("是否可拨打电话筛选,如:不限、可拨打")}).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"}).refine(e=>void 0===e.locationDistrict||void 0!==e.locationCity,{path:["locationCity"],message:"locationCity is required when locationDistrict is provided"}),bg=350,vg=130,wg=600;function xg(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...ug}}function Sg(e,t){const n=wo(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function Rg(e){return void 0!==e.locationCity||void 0!==e.locationDistrict||void 0!==e.major||void 0!==e.recentNotView||void 0!==e.exchangeResumeWithColleague||void 0!==e.candidateKeywords||void 0!==e.school||void 0!==e.switchJobFrequency||void 0!==e.intention||void 0!==e.salary||void 0!==e.degree||void 0!==e.experience||void 0!==e.callPhone}function Cg(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function Ig(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(Sg(t,r))}function kg(e){const t=[];if(void 0===e.applyMode&&!Rg(e)&&Cg(e)||!Rg(e)&&!Cg(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(Sg("gender",[n])),t.push(Sg("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}Ig(t,"gender",e.gender),Ig(t,"activity",e.activity),Ig(t,"major",e.major),Ig(t,"recentNotView",e.recentNotView),Ig(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),Ig(t,"candidateKeywords",e.candidateKeywords),Ig(t,"school",e.school),Ig(t,"switchJobFrequency",e.switchJobFrequency),Ig(t,"intention",e.intention),Ig(t,"salary",e.salary),Ig(t,"degree",e.degree),Ig(t,"experience",e.experience),Ig(t,"callPhone",e.callPhone);const n=e.applyMode??"patch";return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.locationCity?{location:{city:e.locationCity,...void 0!==e.locationDistrict?{district:e.locationDistrict}:{}}}:{},applyMode:n,optionSelections:t}}function Ag(e){return{applyMode:e.applyMode,...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values],selection:e.selection,clearValue:e.clearValue}))}}function Eg(e){if(void 0!==e)return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values]})),...void 0!==e.gender?{gender:e.gender}:{},...void 0!==e.activity?{activity:e.activity}:{}}}function _g(e){const t=Eg(e.applied);return{success:"applied"===e.status,status:e.status,requested:Ag(e.requested),...void 0!==t?{applied:t}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var Ng=lg({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:yg,output:gg,execute:async(e,t)=>{const n=xg(),r=kg(e);let a,o;t.logger.info(`Filtering Boss recommend candidates through native CDP: applyMode=${r.applyMode}, ageMin=${r.ageMin??"unchanged"}, ageMax=${r.ageMax??"unchanged"}, location=${void 0!==r.location?`${r.location.city}${void 0!==r.location.district?`-${r.location.district}`:""}`:"unchanged"}, fields=${r.optionSelections.map(e=>e.fieldKey).join(",")}`);try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),_g({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(go.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:bg,pressDurationMs:vg,settleMs:wg,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),_g(e)}catch(e){lo(e);const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),_g({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as Mg}from"@roll-agent/sdk";import{z as Pg}from"zod";var Tg=["target-count","boundary","no-new-items","max-steps"];import{AsyncLocalStorage as Bg}from"node:async_hooks";var Dg="@c",Og="@j",$g="legacy",Fg=/^@?c([1-9]\d*)$/i,jg=/^@?j([1-9]\d*)$/i,Lg=new Map,qg=new Map,zg=new Bg;function Vg(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${Dg}${String(e+1)}`}function Jg(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${Og}${String(e+1)}`}function Ug(e){const t=Fg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Hg(e){const t=jg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Wg(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 Yg(e.candidateRef).index}function Kg(e){const t=e.map((e,t)=>({...e,candidateRef:Vg(t)}));return Lg.set(nf(),new Map(t.map(e=>[e.candidateRef,e]))),t}function Gg(e){const t=e.map((e,t)=>({...e,jobRef:Jg(t)}));return qg.set(nf(),new Map(t.map(e=>[e.jobRef,e]))),t}function Yg(e){const t=Ug(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=Vg(t);return Lg.get(nf())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function Xg(e){const t=Hg(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=Jg(t),r=qg.get(nf())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function Zg(e){const t=[];for(const n of e.indices??[])t.push({index:Wg({index:n}),candidateRef:Vg(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Yg(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return ef(t)}function Qg(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)||tf(e.name)===tf(t.name))}function ef(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function tf(e){return e.trim().toLocaleLowerCase("zh-CN")}function nf(){const e=zg.getStore();if(void 0!==e)return e;try{return be().id}catch{return $g}}var rf,af=Pg.object({index:Pg.number(),candidateRef:Pg.string(),candidateId:Pg.string(),name:Pg.string(),age:Pg.string(),experience:Pg.string(),education:Pg.string(),workStatus:Pg.string(),company:Pg.string(),currentPosition:Pg.string(),expectedLocation:Pg.string(),expectedPosition:Pg.string(),expectedSalary:Pg.string(),tags:Pg.array(Pg.string()),buttonText:Pg.string()}),of=Pg.object({containerLabel:Pg.string(),stepsRequested:Pg.number(),stepsCompleted:Pg.number(),reachedBoundary:Pg.boolean(),stopReason:Pg.enum(Tg),uniqueCount:Pg.number(),duplicateCount:Pg.number(),noNewRounds:Pg.number(),beforeItemCount:Pg.number(),afterItemCount:Pg.number(),beforeScrollHeight:Pg.number(),afterScrollHeight:Pg.number()}),sf=Pg.object({success:Pg.boolean(),candidates:Pg.array(af),total:Pg.number(),scrollStats:of.optional(),error:Pg.string().optional()});function cf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...rf}}var lf=Mg({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:Pg.object({maxResults:Pg.number().optional().describe("最多返回条数"),autoScroll:Pg.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:Pg.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:sf,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=cf();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开推荐列表");if(!await r.waitForRecommendList())return await a.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};const o="正在读取推荐列表";await a.begin(o),await a.highlightSelector(".candidate-card-wrap, li.card-item, [data-geek], .geek-item",{label:o,padding:8});const i=await r.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=Kg(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 a.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 lo(e),await(a?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{r?.close()}}});import{defineTool as df}from"@roll-agent/sdk";import{z as uf}from"zod";var mf,pf=["listed","recommend_not_ready","selector_not_found"],gf=uf.object({index:uf.number(),jobRef:uf.string().optional(),value:uf.string(),label:uf.string(),isCurrent:uf.boolean()}),ff=uf.object({success:uf.boolean(),status:uf.enum(pf),current:gf.optional(),jobs:uf.array(gf),availableCount:uf.number(),canSwitch:uf.boolean(),error:uf.string().optional()}),hf=450,yf=140,bf=750;function vf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...mf}}function wf(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function xf(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Sf(e){const t=Gg(e.options),n=e.options.map((e,n)=>wf(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>xf(e,t))??wf(t)}return{success:e.success,status:e.status,...void 0!==r?{current:r}:{},jobs:n,availableCount:e.availableCount,canSwitch:e.canSwitch,...void 0!==e.error?{error:e.error}:{}}}var Rf=df({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:uf.object({}),output:ff,execute:async(e,t)=>{const n=vf();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:hf,pressDurationMs:yf,settleMs:bf,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),Sf(e)}catch(e){lo(e);const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),Sf({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as Cf}from"@roll-agent/browser";import{defineTool as If}from"@roll-agent/sdk";import{z as kf}from"zod";var Af,Ef=kf.object({success:kf.boolean(),alreadyOnRecommend:kf.boolean(),usedSidebarClick:kf.boolean(),recommendReady:kf.boolean(),page:Cf.optional(),error:kf.string().optional()});function _f(){return{getContextManager:ue,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Af}}async function Nf(e,t){return Dt(e,await t.inspectPage())}var Mf=If({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:kf.object({}),output:Ef,execute:async(e,t)=>{const n=_f(),r=n.getContextManager();let a,o;t.logger.info("Opening Boss recommend page via native sidebar navigation");try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(go.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await Nf(r,a)};if(!await a.clickSidebarSection("recommend",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await Nf(r,a),error:"未找到推荐牛人导航"};t.logger.info("Clicked Boss sidebar nav: 推荐牛人");return await a.waitForRecommendSurface()?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await Nf(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await Nf(r,a),error:"推荐牛人页未就绪"})}catch(e){return lo(e),await(o?.fail("切换推荐牛人页失败")),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,error:e instanceof Error?e.message:"切换推荐牛人页失败"}}finally{a?.close()}}});import{defineTool as Pf}from"@roll-agent/sdk";import{z as Tf}from"zod";var Bf,Df=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],Of=Tf.object({index:Tf.number(),value:Tf.string(),label:Tf.string(),isCurrent:Tf.boolean()}),$f=Tf.object({jobRef:Tf.string().optional(),jobValue:Tf.string().optional(),jobName:Tf.string().optional(),index:Tf.number().optional(),searchKeyword:Tf.string().optional(),useSearch:Tf.boolean().optional(),forceClick:Tf.boolean().optional()}),Ff=Tf.object({success:Tf.boolean(),status:Tf.enum(Df),requested:$f,current:Of.optional(),selected:Of.optional(),options:Tf.array(Of),matchedCount:Tf.number(),error:Tf.string().optional()}),jf=Tf.object({jobRef:Tf.string().regex(jg,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:Tf.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:Tf.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:Tf.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:Tf.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:Tf.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:Tf.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 至少需要提供一个"}),Lf=900,qf=180,zf=1400;function Vf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Bf}}function Jf(e){if(void 0!==e.jobRef){const t=Xg(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 Uf(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 Hf(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function Wf(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Hf(e.current)}:{},...void 0!==e.selected?{selected:Hf(e.selected)}:{},options:e.options.map(Hf),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Kf=Pf({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:jf,output:Ff,execute:async(e,t)=>{const n=Vf();let r,a;try{const o=Jf(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}`),r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:Lf,pressDurationMs:qf,settleMs:zf,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),Wf(i)}catch(t){lo(t);const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),Wf({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Uf(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as Gf}from"@roll-agent/sdk";import{z as Yf}from"zod";var Xf,Zf=Yf.object({index:Yf.number(),candidateRef:Yf.string(),candidateName:Yf.string(),candidateId:Yf.string(),success:Yf.boolean(),error:Yf.string().optional()}),Qf=Yf.object({success:Yf.boolean(),results:Yf.array(Zf),summary:Yf.object({total:Yf.number(),succeeded:Yf.number(),failed:Yf.number()})}),eh=[1400,1800,2200],th=450,nh=140,rh=750,ah=Yf.object({indices:Yf.array(Yf.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Yf.array(Yf.string().regex(Fg,"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 oh(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...Xf}}var ih=Gf({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:ah,output:Qf,execute:async(e,t)=>{const n=Zg(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=oh();let a,o;try{a=await r.openNativePagePort(),o=r.createNativeVisualActivitySession(a),await o.begin("正在打开推荐列表");if(!await a.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=eh[(e-1)%eh.length]??eh[0];await r.sleep(t)}if(sh(s)){const e=await a.inspectRecommendCard(s.index);if(!Qg(s,e)){await a.assertNotRestricted(),i.push({index:s.index,candidateRef:s.candidateRef,candidateName:e.name,candidateId:e.candidateId,success:!1,error:`候选人引用 ${s.candidateRef} 已过期,请重新获取推荐列表`});continue}}const n=await a.clickRecommendGreet(s.index,{preClickDelayMs:th,pressDurationMs:nh,settleMs:rh,...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}:{}}),Hc(n,t.logger),n.clicked||await a.assertNotRestricted()}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){lo(e);const t=e instanceof Error?e.message:String(e);await(o?.fail(t));const r=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:t}));return{success:!1,results:r,summary:{total:r.length,succeeded:0,failed:r.length}}}finally{a?.close()}}});function sh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as ch}from"@roll-agent/sdk";import{z as lh}from"zod";var dh,uh=lh.object({success:lh.boolean(),candidateRef:lh.string().optional(),candidateName:lh.string(),candidateId:lh.string(),resumeReady:lh.boolean().optional(),error:lh.string().optional()}),mh=lh.object({index:lh.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:lh.string().regex(Fg,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef");function ph(){return{openNativePagePort:Gi,...dh}}var gh=ch({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:mh,output:uh,execute:async(e,t)=>{const n=hh(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${String(r)} through native backend`);const o=ph();let i,s;try{i=await o.openNativePagePort(),s=new aa(i),await s.begin("正在打开简历详情");if(!await i.waitForRecommendList())return await s.fail("推荐列表未加载"),fh(a,"推荐列表未加载");const e=await i.inspectRecommendCard(r);if(!e.found){const t=e.error??`候选人引用 ${a} 超出范围`;return await s.fail(t),fh(a,t)}if(yh(n)&&!Qg(n,e)){const t=`候选人引用 ${a} 已过期,请重新获取推荐列表`;return await s.fail(t),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:t}}if(!await i.clickRecommendCardSurface(r))return await s.fail("未能点击候选人卡片"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:"未能点击候选人卡片"};const c=await i.waitForResumeDialog(12e3),l=c.canvasReady;return c.iframeFound?(await s.succeed(`已打开 ${e.name} 的简历`),t.logger.info(`Opened resume for ${e.name}`),{success:!0,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:l,...l?{}:{error:"简历弹窗已出现,但 canvas 尚未就绪"}}):(await s.fail("简历弹窗未出现"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:!1,error:"点击后简历弹窗未出现"})}catch(e){return lo(e),await(s?.fail("打开简历失败")),t.logger.warn(`Native zhipin open resume failed: ${e instanceof Error?e.message:String(e)}`),fh(a,"打开简历失败,请重试")}finally{i?.close()}}});function fh(e,t){return{success:!1,...void 0!==e?{candidateRef:e}:{},candidateName:"",candidateId:"",error:t}}function hh(e){if(void 0!==e.candidateRef)return Yg(e.candidateRef);const t=Wg(e);return{index:t,candidateRef:Vg(t),candidateId:""}}function yh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{mkdir as bh,writeFile as vh}from"node:fs/promises";import{tmpdir as wh}from"node:os";import{dirname as xh,isAbsolute as Sh,join as Rh}from"node:path";import{defineTool as Ch}from"@roll-agent/sdk";import{z as Ih}from"zod";var kh,Ah="data:image/png;base64,",Eh=Ih.object({success:Ih.boolean(),imagePath:Ih.string().optional(),captureMode:Ih.enum(["canvas-data-url","viewport-clip","dom-screenshot"]).optional(),canvasSize:Ih.object({width:Ih.number(),height:Ih.number()}).optional(),note:Ih.string().optional(),error:Ih.string().optional(),mcpImages:Ih.array(Ih.object({data:Ih.string(),mimeType:Ih.string()})).optional().describe("截图的 MCP image content(base64);由 SDK 转为 image block 返回,调用方无需解析")});function _h(){return{openNativePagePort:Gi,writePngFile:async(e,t)=>{await bh(xh(e),{recursive:!0}),await vh(e,Buffer.from(t,"base64"))},now:()=>Date.now(),...kh}}var Nh=Ch({name:"zhipin_capture_resume",description:"截取当前打开的简历详情弹窗中的 canvas 简历为 PNG 图片文件并返回 imagePath。简历内容是 canvas 图像渲染,无法从 DOM/文本读取;编排器应在调用后用自身的多模态图像能力读取该文件来理解简历内容。需先用 zhipin_open_resume 打开简历弹窗",input:Ih.object({outputPath:Ih.string().min(1).refine(e=>Sh(e),{message:"outputPath 必须是绝对路径"}).optional().describe("PNG 输出文件的绝对路径;缺省时写入系统临时目录")}),output:Eh,execute:async(e,t)=>{t.logger.info("Capturing resume canvas through native backend");const n=_h();let r,a;try{r=await n.openNativePagePort(),a=new aa(r),await a.begin("正在读取简历");const o=await r.waitForResumeDialog(12e3);if(!o.iframeFound)return await a.fail("简历弹窗未打开"),{success:!1,error:"简历弹窗未打开,请先调用 zhipin_open_resume"};if(!o.canvasReady)return await a.fail("简历内容未加载完成"),{success:!1,error:"简历 canvas 未加载完成,请稍后重试"};const i=await r.captureResumeCanvas(async e=>{await(a?.begin(Ph(e)))});if(!i.found||void 0===i.canvasSize)return await a.fail("未找到简历内容"),{success:!1,error:i.error??"未找到简历 canvas"};let s,c,l;if(await r.assertNotRestricted(),!0!==i.blank&&void 0!==i.dataUrl&&i.dataUrl.startsWith(Ah))s=i.dataUrl.slice(Ah.length),c="canvas-data-url";else if(!0===i.blank){const e=await r.readResumeDialogClipArea();if(void 0===e)return await a.fail("未找到简历弹窗区域"),{success:!1,error:"简历 canvas 为空白且未找到弹窗区域,无法截图"};await Th(a),s=await r.captureViewportClip(e,2),c="dom-screenshot",l="此简历为 DOM 文本渲染(canvas 为空),已改为截取弹窗可见区域;若弹窗内有滚动内容可能未完整覆盖"}else{if(void 0===i.screenshotArea)return await a.fail("简历坐标不完整"),{success:!1,error:"简历 canvas 坐标不完整,无法截图"};await Th(a),s=await r.captureViewportClip(i.screenshotArea),c="viewport-clip",l=`canvas.toDataURL 不可用${void 0!==i.dataUrlError?`(${i.dataUrlError})`:""},已回退为视口区域截图,超出可视区的简历内容可能被截断`}const d=e.outputPath??Rh(wh(),"roll-browser-use",`resume-${String(n.now())}.png`);return await n.writePngFile(d,s),await a.succeed("简历读取完成"),t.logger.info(`Resume captured to ${d} (${c})`),{success:!0,imagePath:d,captureMode:c,canvasSize:i.canvasSize,...void 0!==l?{note:l}:{},mcpImages:[{data:s,mimeType:"image/png"}]}}catch(e){return lo(e),await(a?.fail("简历读取失败")),t.logger.warn(`Native zhipin capture resume failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,error:"简历读取失败,请重试"}}finally{r?.close()}}}),Mh=240;function Ph(e){if(e.totalPx>0){const t=Math.min(99,Math.round(e.scrolledPx/e.totalPx*100));return t>0?`正在读取简历 ${String(t)}%`:"正在读取简历"}return`正在读取简历 · 第 ${String(e.round+1)} 屏`}async function Th(e){await e.clear()&&await new Promise(e=>setTimeout(e,Mh))}import{defineTool as Bh}from"@roll-agent/sdk";import{z as Dh}from"zod";var Oh,$h=Dh.object({success:Dh.boolean(),closed:Dh.boolean(),method:Dh.enum(["close-button","escape"]).optional(),error:Dh.string().optional()});function Fh(){return{openNativePagePort:Gi,...Oh}}var jh=Bh({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:Dh.object({}),output:$h,execute:async(e,t)=>{t.logger.info("Closing resume detail modal through native backend");const n=Fh();let r;try{r=await n.openNativePagePort();const e=await r.closeResumeDialog();return e.closed?(t.logger.info(`Resume modal closed via ${e.method??"unknown"}`),{success:!0,closed:!0,...void 0!==e.method?{method:e.method}:{}}):{success:!1,closed:!1,error:e.error??"简历弹窗未关闭"}}finally{r?.close()}}});import{defineTool as Lh}from"@roll-agent/sdk";import{z as qh}from"zod";import{waitForSelector as zh}from"@roll-agent/browser";var Vh={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 Jh,waitForSelector as Uh}from"@roll-agent/browser";var Hh="https://www.yupao.com",Wh=`${Hh}/chat`,Kh=`${Hh}/login`;async function Gh(e,t={}){e.url().includes("/chat")||await Jh(e,Wh,t),await Uh(e,Vh.messageList.container,{timeout:15e3})}async function Yh(e,t,n={}){const r=`${Hh}/chat?id=${encodeURIComponent(t)}`;await Jh(e,r,n),await Uh(e,Vh.chat.input,{timeout:15e3})}async function Xh(e,t){await Gh(e),await zh(e,Vh.messageList.item,{timeout:1e4});const n=Vh.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],r=t.maxItems?e.slice(0,t.maxItems):e;for(const e of r){const r=e.querySelector(t.sel.candidateName),a=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:r?.textContent?.trim()??"",lastMessage:a?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var Zh=qh.object({limit:qh.number().optional().describe("最多返回的消息条数")}),Qh=qh.object({conversationId:qh.string(),candidateName:qh.string(),lastMessage:qh.string(),unreadCount:qh.number(),timestamp:qh.string()}),ey=qh.object({messages:qh.array(Qh),total:qh.number()}),ty=Lh({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Zh,output:ey,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=ue(),r=await n.getPage("yupao"),a=await Xh(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});import{defineTool as ny}from"@roll-agent/sdk";import{z as ry}from"zod";import{BrowserActionApprovalSchema as ay}from"@roll-agent/browser";import{clickElement as oy,isBrowserActionPolicyError as iy,typeText as sy,waitForSelector as cy}from"@roll-agent/browser";async function ly(e,t,n,r={}){try{return await Yh(e,t,r),await sy(e,Vh.chat.input,n,r),await oy(e,Vh.chat.sendButton,r),await cy(e,Vh.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(iy(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var dy=ry.object({conversationId:ry.string().describe("对话 ID"),message:ry.string().describe("要发送的回复消息"),browserActionApproval:ay.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),uy=ry.object({success:ry.boolean(),conversationId:ry.string(),sentMessage:ry.string(),error:ry.string().optional()}),my=ny({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:dy,output:uy,execute:async(e,t)=>{const{conversationId:n,message:r}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const a=ue(),o=de(),i=rn(t,o,{action:"yupao_send_reply",target:n,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await a.getPage("yupao");let c;try{c=await ly(s,n,r,nn(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=an(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:r,error:c.error}}});import{BrowserRuntimeConfigSchema as py,BrowserRuntimeModeSchema as gy,BrowserChannelSchema as fy,BrowserProfileColorSchema as hy,BrowserWindowBoundsSchema as yy,PlatformSchema as by}from"@roll-agent/browser";import{z as vy}from"zod";var wy=vy.object({platform:by.optional(),mode:gy.default("managed-cdp"),headless:vy.boolean().optional(),cdpUrl:vy.string().optional(),cdpHost:vy.string().default("127.0.0.1"),cdpPort:vy.number().int().min(1).max(65535).optional(),channel:fy.default("chrome"),executablePath:vy.string().optional(),userDataDir:vy.string().trim().min(1),sessionsDir:vy.string().trim().min(1).optional(),args:vy.array(vy.string()).optional(),profileName:vy.string().trim().min(1).optional(),profileColor:hy.optional(),windowBounds:yy.optional(),trackingAgentId:vy.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:vy.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==e.mode&&"existing-session"!==e.mode||void 0!==e.cdpUrl||t.addIssue({code:vy.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),xy=vy.object({defaultInstance:vy.string().trim().min(1).optional(),instances:vy.record(vy.string(),wy).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:vy.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${e.defaultInstance}" is not declared in browser.instances`});const n=new Map,r=new Map;for(const[a,o]of Object.entries(e.instances)){if(void 0!==o.cdpPort){const e=n.get(o.cdpPort);void 0!==e?t.addIssue({code:vy.ZodIssueCode.custom,path:["instances",a,"cdpPort"],message:`cdpPort ${String(o.cdpPort)} is already used by browser instance "${e}"`}):n.set(o.cdpPort,a)}const e=r.get(o.userDataDir);void 0!==e?t.addIssue({code:vy.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function Sy(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 Ry(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 Cy(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 Iy(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 ky(e){if(void 0===e)return;let t;try{t=JSON.parse(e)}catch(e){throw new Error(`BROWSER_INSTANCES_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}return xy.parse(t)}function Ay(e=process.env){return py.parse({mode:e.BROWSER_MODE,headless:Sy(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:Ry(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:Cy(e.BROWSER_ARGS_JSON),profileColor:e.BROWSER_PROFILE_COLOR,sessionsDir:e.BROWSER_SESSIONS_DIR,security:Iy(e.BROWSER_SECURITY_JSON)})}function Ey(e=process.env){return ky(e.BROWSER_INSTANCES_JSON)}import{z as _y}from"zod";import{StructuredToolError as Ny}from"@roll-agent/sdk";var My=new Map;async function Py(e,t,n={}){const{signal:r,onWait:a}=n;Dy(e,r,0);const o=My.get(e);let i;const s=new Promise(e=>{i=e});My.set(e,s);const c=performance.now();if(void 0!==o)try{await By(o,r)}catch(t){throw o.then(()=>{i(),$y(e,s)}),t instanceof Ty&&Oy(e,Fy(c)),t}try{return void 0!==o&&a?.(Fy(c)),Dy(e,r,Fy(c)),await t()}finally{i(),$y(e,s)}}var Ty=class extends Error{constructor(){super("Aborted while queued"),this.name="QueueAbortError"}};async function By(e,t){if(void 0!==t){if(t.aborted)throw new Ty;await new Promise((n,r)=>{const a=()=>{r(new Ty)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new Ty):n()})})}else await e}function Dy(e,t,n){!0===t?.aborted&&Oy(e,n)}function Oy(e,t){throw new Ny({code:"cancelled_while_queued",message:`Request was cancelled while waiting for another in-flight operation on browser instance "${e}". The tool did not execute.`,details:{browserInstance:e,queuedMs:Math.round(t)}})}function $y(e,t){My.get(e)===t&&My.delete(e)}function Fy(e){return performance.now()-e}import{StructuredToolError as jy}from"@roll-agent/sdk";function Ly(e){if(void 0===e)return;const t=ge().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new jy({code:"platform_mismatch",message:`Tool platform "${e}" does not match browser instance "${t.id}" platform "${t.platform}".`,details:{browserInstance:t.id,expectedPlatform:t.platform,requestedPlatform:e}})}function qy(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var zy={browserInstance:_y.string().trim().min(1).optional().describe("可选 browser instance id;多浏览器实例时用于选择目标 profile/CDP runtime。")};function Vy(e){return e.startsWith("zhipin_")?"zhipin":e.startsWith("yupao_")?"yupao":void 0}function Jy(e,t={}){const n=Hy(e.input);if(void 0===n)return e;const r=t.startRuntime??!0,a=t.serializePageOps??!0,o=t.expectedPlatform??Vy(e.name);return{...e,input:n,execute:async(t,n)=>{const i=t;return await G(i.browserInstance,async()=>{void 0!==i.browserInstance&&ge().getBundle(),Ly(Uy(t,o));const s=async()=>(r&&await fe(),await e.execute(t,n));if(!a)return await s();const c=ge().getBundle().id;return await Py(c,s,{...void 0!==n.signal?{signal:n.signal}:{},onWait:t=>{n.logger.info(`${e.name} waited ${Math.round(t)}ms for an in-flight operation on browser instance "${c}" (same-instance page operations are serialized)`)}})})}}}function Uy(e,t){const n=qy(e);return t??n}function Hy(e){if(e instanceof _y.ZodObject)return e.extend(zy);if(e instanceof _y.ZodEffects){const t=Hy(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?_y.ZodEffects.createWithPreprocess(n.transform,t,e._def):_y.ZodEffects.create(t,n,e._def)}}var Wy=t("browser-use-agent");async function Ky(){const e=(await Promise.allSettled([Em(),ve()])).filter(e=>"rejected"===e.status).map(e=>e.reason);if(e.length>0)throw new AggregateError(e,"Failed to fully shut down browser-use-agent")}function Gy(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e}var Yy=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function Xy(e){return Gy(e.name)?e:Jy(e,{startRuntime:"browser_status"!==e.name,serializePageOps:!Yy.has(e.name)})}var Zy=e({name:"browser-use-agent",tools:[ft,jt,In,Jn,er,sr,ya,Na,Fa,gc,tl,ul,yl,Xl,fu,Tu,Tp,Up,Xp,cg,Mf,Rf,Kf,Ng,lf,ih,gh,Nh,jh,ty,my,xe,Rt].map(Xy)},{onShutdown:Ky});async function Qy(){lt(ct()),Am({logger:Wy}),await le(Ay(),Ey());try{await Qm(),me(!0)}catch(e){me(!1),Wy.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Zy.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Qy().catch(async e=>{Wy.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await Ky().catch(()=>{}),process.exit(1)});