@roll-agent/browser-use-agent 0.23.0 → 0.24.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 I(){return b??=A(),b}function C(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=C(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=_(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=D();if(void 0!==e)return e}if("win32"===process.platform){const e=O();if(void 0!==e)return e}return v}function _(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(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 E(y("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function O(){try{return M(y("powershell.exe",["-NoProfile","-NonInteractive","-Command",R],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function D(){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 $,j,L,F,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:I()})}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($||j)return;se=!1;const n=($=new H(e,t)).getDefaultInstanceId(),r=$.listBundles()[0],a=void 0!==n?$.getBundle(n):r;void 0!==a&&(j=a.runtime,L=a.contextManager,F=a.sessionStore)}function de(){if($)return $.getBundle().runtime;if(!j)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return j}function ue(){if($)return $.getBundle().contextManager;if(!L)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return L}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=L,n=j,r=[];if($=void 0,L=void 0,j=void 0,F=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 Ie,BrowserStatusSchema as Ce}from"@roll-agent/browser";import{createHash as ke}from"node:crypto";import{z as Ae}from"zod";var _e=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_KEYS_URL","RECRUITMENT_EVENTS_ENABLED","RECRUITMENT_EVENTS_API_BASE_URL","RECRUITMENT_EVENTS_API_TOKEN","RECRUITMENT_EVENTS_DEFAULT_AGENT_ID","BROWSER_SECURITY_JSON","BROWSER_INSTANCES_JSON","BROWSER_USE_POLICY_JSON","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],Ee=/^[0-9a-f]{8}$/,Ne=Ae.object({present:Ae.boolean(),fingerprint:Ae.string().regex(Ee).optional()}),Me=Ae.record(Ne);function Pe(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:Te(n)}]:[e,{present:!1}]}))}function Te(e){return ke("sha256").update(e).digest("hex").slice(0,8)}var Be,Oe,De=180,$e=60,je=280,Le="roll-agent-visual-cursor-root",Fe="__rollVisualCursorState";function qe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function ze(){return void 0!==Be?Be:qe(process.env.BROWSER_VISUAL_CURSOR)??!0}async function Ve(e){return await e.evaluate(e=>{const t=e.getBoundingClientRect();return t.width<=0||t.height<=0?null:{x:Math.round(t.left+t.width/2),y:Math.round(t.top+t.height/2)}})}async function Je(e,t){await e.evaluate(e=>{const t="roll-agent-visual-cursor-root",n="roll-agent-visual-cursor-pointer",r="__rollVisualCursorState",a=(()=>{const e=document.getElementById(t);if(e)return e;const r=document.createElement("div");r.id=t,r.style.position="fixed",r.style.left="0",r.style.top="0",r.style.width="0",r.style.height="0",r.style.pointerEvents="none",r.style.zIndex="2147483647";const a=document.createElement("div");return a.id=n,a.setAttribute("aria-hidden","true"),a.style.position="fixed",a.style.left="0",a.style.top="0",a.style.width="24px",a.style.height="24px",a.style.opacity="0",a.style.transform="translate(-9999px, -9999px)",a.style.willChange="transform, opacity",a.style.filter="drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))",a.innerHTML='<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/></svg>',r.append(a),document.documentElement.append(r),r})(),o=a.querySelector(`#${n}`);if(!o)return;const i=window[r],s=e.point,c=e.durationMs,l=s.x-2,d=s.y-2;if(i?o.style.transition=`transform ${c}ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease`:(o.style.transition="opacity 120ms ease",o.style.transform=`translate(${l}px, ${d}px)`),o.style.opacity="1",o.style.transform=`translate(${l}px, ${d}px)`,window[r]={x:s.x,y:s.y},!e.clickPulse)return;const u=document.createElement("div");u.setAttribute("aria-hidden","true"),u.style.position="fixed",u.style.left="0",u.style.top="0",u.style.width="18px",u.style.height="18px",u.style.borderRadius="9999px",u.style.border="2px solid rgba(20, 184, 166, 0.95)",u.style.background="rgba(20, 184, 166, 0.18)",u.style.pointerEvents="none",u.style.opacity="0.9",u.style.transform=`translate(${s.x-9}px, ${s.y-9}px) scale(0.55)`,u.style.transition=`transform ${e.pulseDurationMs}ms ease, opacity ${e.pulseDurationMs}ms ease`,a.append(u),requestAnimationFrame(()=>{u.style.opacity="0",u.style.transform=`translate(${s.x-18}px, ${s.y-18}px) scale(2)`}),globalThis.setTimeout(()=>{u.remove()},e.pulseDurationMs+40)},{point:t.point,durationMs:t.durationMs,clickPulse:t.clickPulse,pulseDurationMs:t.pulseDurationMs})}function Ue(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function He(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:Le,stateKey:Fe})}async function We(e,t={}){if(e.isClosed())return!1;let n=!1;for(const r of Ue(e))if(r!==t.preserveTarget)try{await He(r),n=!0}catch{}return n}async function Ke(e,t,n={}){if(!ze()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const r=await Ve(t);if(!r)return!1;const a=n.durationMs??De,o=n.settleMs??$e,i=n.target??e;return await We(e,{preserveTarget:i}),await Je(n.target??e,{point:r,durationMs:a,clickPulse:!1,pulseDurationMs:je}),await e.waitForTimeout(Math.max(a+o,0)),!0}catch{return!1}}async function Ge(e,t,n={}){if(!ze()||e.isClosed())return!1;try{const r=await Ve(t);if(!r)return!1;const a=n.pulseDurationMs??je,o=n.target??e;return await We(e,{preserveTarget:o}),await Je(o,{point:r,durationMs:0,clickPulse:!0,pulseDurationMs:a}),await e.waitForTimeout(a),!0}catch{return!1}}function Ye(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function Xe(){return void 0!==Oe?Oe:Ye(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Ze}from"@roll-agent/sdk";import{z as Qe}from"zod";import{randomUUID as et}from"node:crypto";import{z as tt}from"zod";var nt=tt.object({id:tt.string().trim().min(1)}),rt=new Map;function at(e=Date.now()){for(const[t,n]of rt)n.expiresAtMs<=e&&rt.delete(t)}function ot(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function it(e,t,n=Date.now()){at(n);const r=et(),a=n+t;return rt.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 st(e){return at(),!!ct(e)&&(rt.delete(e.approval.id),!0)}function ct(e){at();const t=rt.get(e.approval.id);return void 0!==t&&!!ot(e.subject,t)}var lt=["log","deny","confirm"],dt=Qe.enum(lt),ut=["zhipin_send_prepared_reply"],mt=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],pt=Qe.object({code:Qe.enum(mt),message:Qe.string()}),gt=Qe.object({policy:dt}),ft=Qe.object({approvalTtlMs:Qe.number().int().positive().max(36e5).default(3e5),tools:Qe.record(gt).default({})}),ht=ft.parse({}),yt=new Set(ut),bt=ht;function vt(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 wt(e=process.env){try{return ft.parse(vt(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 xt(e){bt=ft.parse(e)}function St(){return bt}function Rt(e){const t=e.toolPolicy??bt,n=[];for(const e of Object.keys(t.tools))yt.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 It(e,t){const n=bt.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(Ct(t.subject,"tool_policy_deny"))}`),new Ze({code:"action_denied",message:n,details:Ct(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?ct({approval:t.approval,subject:t.subject}):st({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&&st({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...Ct(t.subject,"tool_policy_confirm"),approvalRequest:it(t.subject,bt.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(Ct(t.subject,"tool_policy_confirm"))}`),new Ze({code:"needs_confirmation",message:a,details:o})}function Ct(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var kt=Ce.extend({replyAuthorityKeysLoaded:Re.boolean(),visualCursorEnabled:Re.boolean(),visualActivityEnabled:Re.boolean(),security:Ie,toolPolicy:ft,policyWarnings:Re.array(pt),effectiveEnvSources:Me}),At=Se({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:Re.object({}),output:kt,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=St(),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:ze(),visualActivityEnabled:Xe(),security:l,toolPolicy:d,policyWarnings:Rt({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:Pe(_e)}}});import{BrowserRuntimeModeSchema as _t}from"@roll-agent/browser";import{StructuredToolError as Et,defineTool as Nt}from"@roll-agent/sdk";import{z as Mt}from"zod";var Pt=Mt.object({browserInstance:Mt.string().trim().min(1).optional(),browserInstances:Mt.array(Mt.string().trim().min(1)).min(1).optional(),all:Mt.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:Mt.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),Tt=Mt.object({browserInstance:Mt.string(),status:Mt.enum(J),mode:_t.optional(),message:Mt.string().optional()}),Bt=Mt.object({ok:Mt.boolean(),stopped:Mt.number().int().nonnegative(),results:Mt.array(Tt)}),Ot=Nt({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:Pt,output:Bt,execute:async(e,t)=>{const n=ge(),r=Dt(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return $t(await n.closeInstances(r))}});function Dt(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 Et({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function $t(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 jt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Lt,PlatformSchema as Ft}from"@roll-agent/browser";import{z as qt}from"zod";import{PLATFORMS as zt}from"@roll-agent/browser";var Vt={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Jt(e){return new URL(Vt[e]).host}function Ut(e,t){try{return new URL(e).host.includes(Jt(t))}catch{return!1}}function Ht(e){return zt.find(t=>Ut(e,t))}function Wt(e,t){const n=Ht(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 Kt(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:Ht(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var Gt=qt.object({platform:Ft.optional().describe("可选:仅返回指定平台相关的页面")}),Yt=qt.object({pages:qt.array(Lt)}),Xt=jt({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:Gt,output:Yt,execute:async(e,t)=>{const n=ue();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>Wt(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as Zt}from"node:timers/promises";import{defineTool as Qt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as en,BrowserPageInfoSchema as tn}from"@roll-agent/browser";import{z as nn}from"zod";import{StructuredToolError as rn}from"@roll-agent/sdk";import{isBrowserActionPolicyError as an,preflightBrowserAction as on}from"@roll-agent/browser";import{randomUUID as sn}from"node:crypto";var cn=6e4,ln=new Map;function dn(e=Date.now()){for(const[t,n]of ln)n.expiresAtMs<=e&&ln.delete(t)}function un(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function mn(e,t=Date.now()){dn(t);const n=sn(),r=t+cn,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return ln.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 pn(e){dn();const t=ln.get(e.approval.id);return void 0!==t&&(!!un(e.details,t)&&(ln.delete(e.approval.id),!0))}function gn(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function fn(e,t,n={}){return{security:gn(t,n),approveAction:pn,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function hn(e,t,n){const r=on({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&&pn({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 rn({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:mn(r.details)}:{}}})}function yn(e){if(an(e))return new rn({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:mn(e.payload.details)}:{}}})}function bn(){try{return de()}catch{return}}async function vn(e){await e.bringToFront().catch(()=>{})}async function wn(e,t={}){const n=t.runtime??bn();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await vn(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await vn(e)}var xn,Sn=15e3,Rn=250,In=["/web/chat"],Cn=nn.object({url:nn.string().url().describe("要导航到的目标 URL"),browserActionApproval:en.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),kn=nn.object({success:nn.boolean(),page:tn});function An(){return{getContextManager:ue,getRuntime:de,detectPlatformFromUrl:Ht,matchesPlatformHost:Ut,toNativePageInfo:Wt,delay:Zt,...xn}}function _n(e){return"object"==typeof e&&null!==e}function En(e){return!!_n(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function Nn(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==In.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function Mn(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 Pn(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!En(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function Tn(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<Sn;){try{const t=await Pn(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Bn(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(Rn)}throw new Error(`Native navigation did not reach document ready state within ${Sn}ms${o?`: ${o.message}`:""}`)}function Bn(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 On(e,t,n){t.url!==n&&await e.navigate(n)}async function Dn(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var $n=Qt({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:Cn,output:kn,execute:async(e,t)=>{const n=An(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);Nn(e.url,o);const i=hn(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 Mn(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...fn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await On(c,s.page,e.url);const o=await Tn(c,n,e.url,s.page.url),i=await Dn(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 jn,BrowserPageInfoSchema as Ln}from"@roll-agent/browser";import{defineTool as Fn}from"@roll-agent/sdk";import{z as qn}from"zod";import{setTimeout as zn}from"node:timers/promises";var Vn=15e3,Jn=250,Un="__rollReloadMark";function Hn(e){return"object"==typeof e&&null!==e}function Wn(e){if(!Hn(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function Kn(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function Gn(e){const t=await e.evaluateJson(`(() => ({ mark: window.${Un} ?? null, readyState: document.readyState }))()`);if(!Wn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function Yn(e,t){const n=(t.createToken??Kn)(),r=t.now??(()=>Date.now()),a=t.delay??zn,o=t.timeoutMs??Vn,i=t.pollMs??Jn;await e.evaluateJson(`(() => { window.${Un} = ${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 Gn(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 Xn,Zn=qn.object({ignoreCache:qn.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:jn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Qn=qn.object({success:qn.boolean(),reloaded:qn.boolean(),page:Ln});function er(){return{getContextManager:ue,getRuntime:de,toNativePageInfo:Wt,reloadNativePageAndWaitForSwap:Yn,...Xn}}function tr(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 nr=Fn({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:Zn,output:Qn,execute:async(e,t)=>{const n=er(),r=n.getContextManager(),a=n.getRuntime(),o=tr(r,await a.listNativePages()),i=hn(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,{...fn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({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 rr}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as ar,BrowserPageInfoSchema as or,PlatformSchema as ir}from"@roll-agent/browser";import{z as sr}from"zod";async function cr(e,t){return(await e.listNativePages()).find(e=>Ut(e.url,t))}async function lr(e,t,n={}){const r=await cr(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(Vt[t],n),reusedExistingPage:!1}}var dr=sr.object({platform:ir.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:ar.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),ur=sr.object({success:sr.boolean(),page:or,reusedExistingTab:sr.boolean()}),mr=rr({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:dr,output:ur,execute:async(e,t)=>{const{platform:n}=e,r=de(),a=ue(),o=Vt[n],i=hn(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 lr(r,n,fn(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:Wt(a,s),reusedExistingTab:c}}});import{defineTool as pr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as gr,PlatformSchema as fr}from"@roll-agent/browser";import{z as hr}from"zod";var yr=hr.object({platform:fr.describe("要将该页面绑定为当前活跃页的平台"),pageId:hr.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),br=hr.object({success:hr.boolean(),page:gr}),vr=pr({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:yr,output:br,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:Wt(n,r)}}});import{defineTool as wr}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as xr,BrowserPageInfoSchema as Sr,createBrowserAxSnapshot as Rr}from"@roll-agent/browser";import{z as Ir}from"zod";import{BrowserElementRefStore as Cr}from"@roll-agent/browser";var kr=new Cr;async function Ar(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 _r}from"node:crypto";var Er="data-roll-browser-action-",Nr=120;function Mr(){return`${Er}${_r().replaceAll("-","")}`}function Pr(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),Nr)):Nr}function Tr(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Br(e){if(!Tr(e)||!Tr(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Or(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Dr(e){return Array.isArray(e)?e.flatMap(e=>{const t=$r(e);return void 0===t?[]:[t]}):[]}function $r(e){if(!Tr(e))return;const t=e.nodeId,n=e.backendNodeId,r=Or(e.attributes),a=$r(e.contentDocument),o=$r(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:Dr(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Dr(e.pseudoElements),shadowRoots:Dr(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function jr(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 Lr(e){if(!Tr(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 Fr(e){return Array.isArray(e)?e.flatMap(e=>{const t=Lr(e);return void 0===t?[]:[t]}):[]}function qr(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function zr(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 Vr(e,t){const n=new Map;for(const r of t){const t=jr(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Jr(e,t){const n=new Map,r=t=>{const a=jr(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 Ur(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 Hr(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 Wr(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!Tr(n))return new Map;const r=$r(n.root);return void 0===r?new Map:Jr(t,r)}const r=Br(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Vr(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function Kr(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=Pr(n.maxCandidates??Nr);if(0===r)return[];const a=Mr(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(Er.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=Fr(await e.evaluateJson(Ur(a,r),i).catch(()=>[]));try{if(0===s.length)return[];const t=await Wr(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:qr(e),name:e.name,hints:zr(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Hr(a),i).catch(()=>{})}}var Gr=28,Yr=90,Xr=250,Zr=620,Qr=8,ea=32,ta=8;function na(e){return new Promise(t=>{setTimeout(t,e)})}function ra(e,t,n){return Math.min(Math.max(e,t),n)}function aa(e,t){return Math.max(0,Math.floor(e??t))}function oa(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function ia(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function sa(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function ca(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 la(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function da(e,t){const n=ia(e,t);if(n<=Qr)return[e,t];const r=ra(Math.round(n/30),ta,ea),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=ra(.08*n,4,28)*la(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=sa(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 ua(e,t){const n=oa(t);return da(void 0===e?ca(n):oa(e),n)}var ma=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??na,this.defaultStepDelayMs=aa(t.stepDelayMs,Gr)}reset(e){this.lastPoint=void 0===e?void 0:oa(e)}async moveTo(e,t={}){const n=aa(t.stepDelayMs,this.defaultStepDelayMs),r=ua(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=oa(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:oa(e),durationMs:Zr})),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??Yr),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??Xr),this.lastPoint=oa(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??Yr),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=oa(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},pa=14,ga=720;function fa(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 ha=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!Xe()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...Xe()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??pa,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){ze()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){ze()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=ga){return!!Xe()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=ga){return!!Xe()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!Xe()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(fa(e))}catch{return!1}}};function ya(e){return new ha(e)}async function ba(e,t,n){const r=new ma(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var va=Ir.object({pageId:Ir.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:Ir.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:Ir.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:Ir.boolean().default(!0).describe("默认 true:只返回可交互节点")}),wa=Ir.object({page:Sr,snapshot:xr});function xa(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 Sa(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 Ra(e,t){const n=xa(t);return(await Promise.all(n.map(async t=>{const n=await Sa(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function Ia(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function Ca(e){return e.refs.reduce((e,t)=>Math.max(e,Ia(t.ref)),0)}function ka(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ka(n.nodes,t)]})}function Aa(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=Aa(e.children??[],t),a=void 0===n?[]:Aa(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function _a(e){let t=Ca(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await Ra(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 Kr(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await Rr(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 Ra(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=Ca(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?ka(e.snapshot.nodes,s):Aa(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}var Ea=wr({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:va,output:wa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({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=ya(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await Kr(o,s),l=await Rr(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await _a({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return kr.saveSnapshot(a.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:Wt(r,a),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as Na,defineTool as Ma}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Pa,BrowserElementRefHandleSchema as Ta,clickElementRef as Ba}from"@roll-agent/browser";import{z as Oa}from"zod";import{BrowserElementRefHandleSchema as Da}from"@roll-agent/browser";import{z as $a}from"zod";var ja=$a.enum(["backend_node_id","role_name_nth"]),La=$a.object({ref:Da,role:$a.string(),name:$a.string(),x:$a.number(),y:$a.number(),resolvedBy:ja,backendNodeId:$a.number().int().positive().optional(),frameId:$a.string().optional(),disabled:$a.boolean()}),Fa=$a.object({success:$a.literal(!0),ref:Da,resolvedBy:ja,target:La}),qa=Oa.object({ref:Ta.describe("browser_snapshot 返回的 @eN element ref"),pageId:Oa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Pa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),za=Ma({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:qa,output:Fa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=kr.getRef(a.targetId,e.ref);if(void 0===o)throw new Na({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=hn(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...fn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=ya(s);await t.begin(`正在点击 ${e.ref}`);const r=await Ba({controller:s,elementRef:o,options:{clickTarget:async e=>{await ba(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),r}catch(t){await ya(s).fail(`点击 ${e.ref} 失败`);const n=yn(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as Va,defineTool as Ja}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ua,BrowserElementRefHandleSchema as Ha,typeElementRef as Wa}from"@roll-agent/browser";import{z as Ka}from"zod";var Ga=Ka.object({ref:Ha.describe("browser_snapshot 返回的 @eN element ref"),text:Ka.string().describe("要输入的文本"),clear:Ka.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:Ka.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Ua.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Ya=Ja({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:Ga,output:Fa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=kr.getRef(a.targetId,e.ref);if(void 0===o)throw new Va({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=hn(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...fn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=ya(s);await t.begin(`正在输入到 ${e.ref}`);const r=await Wa({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await ba(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),r}catch(t){await ya(s).fail(`输入到 ${e.ref} 失败`);const n=yn(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as Xa}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Za}from"@roll-agent/browser";import{z as Qa}from"zod";var eo=["native","native-watch","native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],to=[...eo,"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"],no=Qa.enum(["localStorage","sessionStorage"]),ro=Qa.enum(["array","object","string","number","boolean","null"]),ao=Qa.enum(["empty","json","string"]),oo=Qa.enum(eo),io=Qa.enum(to),so=Qa.enum(["request","response"]),co=Qa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),lo=Qa.object({area:no,key:Qa.string(),valueLength:Qa.number().int().nonnegative(),valueKind:ao,jsonKind:ro.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()}),uo=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()}),mo=Qa.object({before:Qa.number().optional(),after:Qa.number().optional(),delta:Qa.number().optional()}),po=Qa.object({before:Qa.boolean().optional(),after:Qa.boolean().optional()}),go=Qa.object({before:Qa.number().int().nonnegative().optional(),after:Qa.number().int().nonnegative().optional(),delta:Qa.number().int().optional()}),fo=Qa.object({area:no,key:Qa.string(),beforePresent:Qa.boolean(),afterPresent:Qa.boolean(),numericDeltas:Qa.record(mo).optional(),booleanChanges:Qa.record(po).optional(),arrayLengthDeltas:Qa.record(go).optional()}),ho=Qa.object({url:Qa.string(),title:Qa.string(),visibilityState:Qa.string(),hasFocus:Qa.boolean()}),yo=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())}),bo=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()}),vo=Qa.object({type:Qa.literal("mouseMoved"),x:Qa.number(),y:Qa.number()}),wo=Qa.object({targetId:Qa.string(),websocketUrlAvailable:Qa.boolean(),connected:Qa.boolean(),pageBroughtToFront:Qa.boolean().optional(),runtimeEnabled:Qa.boolean().optional(),evaluate:ho.optional(),dom:bo.optional(),input:vo.optional()}),xo=Qa.object({phase:oo,success:Qa.boolean(),durationMs:Qa.number().int().nonnegative(),error:Qa.string().optional()}),So=Qa.object({phase:io,capturedAt:Qa.string(),targetFound:Qa.boolean(),page:Za.optional(),urlChangedFromPrevious:Qa.boolean(),titleChangedFromPrevious:Qa.boolean(),previousUrl:Qa.string().optional(),currentUrl:Qa.string().optional(),previousTitle:Qa.string().optional(),currentTitle:Qa.string().optional()}),Ro=Qa.object({kind:so,reason:co,capturedAt:Qa.string(),url:Qa.string(),method:Qa.string().optional(),resourceType:Qa.string().optional(),status:Qa.number().int().optional()}),Io=Qa.object({capturedAt:Qa.string(),url:Qa.string()}),Co=Qa.object({phase:oo.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;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 事件数。")}),ko=Qa.object({success:Qa.boolean(),requestedPhase:oo,mode:Qa.string(),nativePages:Qa.array(Za),targetPage:Za.optional(),browserAttached:Qa.boolean(),pageAttached:Qa.boolean(),nativeTimeline:Qa.array(So),networkEvents:Qa.array(Ro).optional(),navigationEvents:Qa.array(Io).optional(),nativeCdp:wo.optional(),evaluate:ho.optional(),detectorFingerprint:yo.optional(),storage:Qa.object({localStorage:Qa.array(lo),sessionStorage:Qa.array(lo),cookies:Qa.array(uo),counterDiffs:Qa.array(fo)}).optional(),phases:Qa.array(xo),warnings:Qa.array(Qa.string())}),Ao=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],_o=new Set(Ao),Eo=["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"],No=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],Mo={"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 Po(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 To(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Bo(e){return Eo.includes(e)}function Oo(e){return No.includes(e)}function Do(e){try{return JSON.parse(e)}catch{return}}function $o(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 jo(e){return new Promise(t=>{setTimeout(t,e)})}function Lo(e){return _o.has(e)}function Fo(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=Do(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=Po(n),a=To(n)&&Lo(t.key)?$o(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,...To(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function qo(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 zo(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 Vo(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?Ut(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=>Ut(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 Jo(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:Wt(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 Uo(e,t,n,r){const a=await e.listNativePages();return Jo(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>Ut(e.url,"zhipin"))[0],r)}async function Ho(e,t,n,r,a){try{const o=t[t.length-1],i=await Uo(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 Wo(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await Ho(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await jo(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Ko(e){if(!To(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 Go(e,t){if(!To(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!To(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=To(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 Yo(e){return Ko(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Xo(e){return Go(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 Zo(e){return Go(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Qo(e){return await e.bringToFront(),!0}async function ei(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 ti(e,t,n,r,a,o){await Ho(e,t,n,r,o);const i=await Wo(e,t,Mo[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 ni(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await zo(e,async()=>await Qo(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 zo(e,async()=>await Yo(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 zo(e,async()=>await Zo(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 zo(e,async()=>await ei(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function ri(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 zo("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 ti(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(Oo(e.requestedPhase))return await ni(e.requestedPhase,o,e.phases,t),i=await ti(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await zo("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await ti(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 zo("native-evaluate-url",async()=>await Yo(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await ti(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 zo("native-dom-read",async()=>await Xo(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await ti(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function ai(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 oi(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=ai(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=ai(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 jo(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function ii(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Ut(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function si(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function ci(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 li(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},Ao)).map(e=>Fo(e.area,{key:e.key,value:e.value}))}async function di(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function ui(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 mi(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function pi(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function gi(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function fi(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function hi(e,t){const n=mi(e),r=mi(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 pi(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=gi(n,r))}const c={};for(const e of pi(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 pi(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=fi(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 yi(e,t){const[n,r,a]=await Promise.all([ci(e,"localStorage"),ci(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>Lo(e.key)).map(e=>Fo("localStorage",e)),...r.filter(e=>Lo(e.key)).map(e=>Fo("sessionStorage",e))];return{localStorage:n.map(e=>Fo("localStorage",e)),sessionStorage:r.map(e=>Fo("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>qo(e)),counterDiffs:hi(t,o)}}var bi=Xa({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:Co,output:ko,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 zo("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>Wt(o,e)),v=Vo(y,e.targetPageId,s);if(v&&(d=Wt(o,v),f=v.targetId,l.push(Jo(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 Wo(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(Bo(n)){const e=await ri({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}}const w=await zo("browser-attach",async()=>await i.getBrowser());c.push(w.phaseResult);const x=w.result;p=w.phaseResult.success,await Ho(o,l,"browser-attach",f,s);const S=await Wo(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 I;"network-watch"===n&&void 0!==x&&(I=ii(x,v),I?R.push(oi(I,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",v);const C=await zo("page-attach",async()=>await o.getPage("zhipin"));c.push(C.phaseResult);const k=C.result;if(g=C.phaseResult.success,k&&(d=await Kt(o,k)),await Ho(o,l,"page-attach",f,s),!C.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await zo("network-watch",async()=>si(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){I!==k&&R.push(oi(k,r,a));const e=await zo("network-watch",async()=>si(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await Ho(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 li(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await jo(r)}const _=await zo("page-evaluate",async()=>await di(k));c.push(_.phaseResult);const E=_.result;if(await Ho(o,l,"page-evaluate",f,s),!_.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!==E?{evaluate:E}:{},phases:c,warnings:s};const N=await zo("detector-fingerprint",async()=>await ui(k));c.push(N.phaseResult);const M=N.result;if(await Ho(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!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await zo("storage-summary",async()=>await yi(k,A));c.push(P.phaseResult);const T=P.result;return await Ho(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!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as vi}from"@roll-agent/sdk";import{z as wi}from"zod";import{setTimeout as xi}from"node:timers/promises";async function Si(e,t=300,n=800){const r=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(r)}function Ri(e){return e.trim().toLocaleLowerCase("zh-CN")}function Ii(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function Ci(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=Ri(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>Ri(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=Ri(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=Ri(e.name);return Ii(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var ki=["chat-list","chat-history","recommend-list"],Ai={"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 _i(e){return Ai[e]}var Ei={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"}},Ni=["不限","男","女"],Mi=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],Pi=["patch","replace"],Ti=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:Ni},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:Mi},{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:"不限"}],Bi=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function Oi(e){const t=Ti.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function Di(e){return void 0!==e.ageMin||void 0!==e.ageMax}var $i=30,ji=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Li(e){const t=e.trim();return!(0===t.length||t.length>$i)&&(!ji.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Fi(e){let t=e.priority;return ji.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 qi(e){const t=e.filter(e=>Li(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=Fi(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 zi='.user-list.b-scroll-stable [role="listitem"], .geek-item',Vi='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',Ji=".candidate-card-wrap",Ui="[data-geek], .geek-item",Hi=`${Ji}, ${Ui}`,Wi=["/web/chat/recommend"],Ki=250,Gi=300,Yi=12,Xi=900,Zi=1200,Qi=4,es=4,ts=250,ns=90,rs=520,as=16,os=50,is=650,ss=10,cs=.015,ls="button, a, label, li, span, div, [role='button'], [role='radio']",ds=["visible","hidden","prerender","unloaded"],us=["not_chat_page"];function ms(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 ps(e){return new Promise(t=>{setTimeout(t,e)})}function gs(e){return{x:e.x,y:e.y}}function fs(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function hs(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function ys(e){return e.trim().toLocaleLowerCase("zh-CN")}function bs(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function vs(e,t){const n=ys(e),r=ys(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function ws(e){return"string"==typeof e?e:""}function xs(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function Ss(e){return"boolean"==typeof e&&e}function Rs(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{};return{conversationId:ws(n.conversationId),candidateId:ws(n.candidateId),name:ws(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:ws(n.position),hasUnread:Ss(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:ws(n.lastMessageTime),messagePreview:ws(n.messagePreview)}}):[]}function Is(e){return Array.isArray(e)?e.flatMap(e=>{if(!fs(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 Cs(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:ws(n.candidateId),name:ws(n.name),age:ws(n.age),experience:ws(n.experience),education:ws(n.education),workStatus:ws(n.workStatus),company:ws(n.company),currentPosition:ws(n.currentPosition),expectedLocation:ws(n.expectedLocation),expectedPosition:ws(n.expectedPosition),expectedSalary:ws(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>ws(e)).filter(Boolean):[],buttonText:ws(n.buttonText)}}):[]}function ks(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:ws(n.value),label:ws(n.label),isCurrent:Ss(n.isCurrent)}}):[]}function As(e){return fs(e)?{found:Ss(e.found),isOpen:Ss(e.isOpen),currentLabel:ws(e.currentLabel),currentValue:ws(e.currentValue),options:ks(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function _s(e){if(!fs(e))return null;const t=ws(e.conversationId),n=ws(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:ws(e.candidateName)}}function Es(e){if(!fs(e))return null;const t=ws(e.candidateName);return t.length>0?{candidateName:t}:null}function Ns(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!fs(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:ws(e.content),time:ws(e.time)}]}):[]}function Ms(e){const t=fs(e)?e:{},n=t.tags;return{name:ws(t.name),age:ws(t.age),experience:ws(t.experience),education:ws(t.education),communicationPosition:ws(t.communicationPosition),expectedJobText:ws(t.expectedJobText),expectedSalary:ws(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>ws(e)).filter(Boolean):[]}}function Ps(e){const t=fs(e)?e:{};return{selectedTarget:_s(t.selectedTarget),activePanel:Es(t.activePanel),candidateInfo:Ms(t.candidateInfo),messages:Ns(t.messages)}}function Ts(e){return fs(e)?{found:Ss(e.found),x:xs(e.x),y:xs(e.y)}:{found:!1,x:0,y:0}}function Bs(e){return fs(e)?{found:Ss(e.found),left:xs(e.left),top:xs(e.top)}:{found:!1,left:0,top:0}}function Os(e){return fs(e)?{found:Ss(e.found),cardSelector:ws(e.cardSelector)||Ji,candidateId:ws(e.candidateId),name:ws(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:Ss(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:Ji,candidateId:"",name:"",hasGreetButton:!1}}function Ds(e){return fs(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 $s(e){return fs(e)&&!0===e.ok?{ok:!0,current:Ds(e.current),trackLeft:xs(e.trackLeft),trackTop:xs(e.trackTop),trackWidth:xs(e.trackWidth),trackHeight:xs(e.trackHeight),minHandleX:xs(e.minHandleX),minHandleY:xs(e.minHandleY),maxHandleX:xs(e.maxHandleX),maxHandleY:xs(e.maxHandleY)}:{ok:!1,error:fs(e)&&ws(e.error)||"未找到年龄滑块"}}function js(e){return fs(e)?{containerFound:Ss(e.containerFound),containerLabel:ws(e.containerLabel),scrollTop:xs(e.scrollTop),scrollHeight:xs(e.scrollHeight),clientHeight:xs(e.clientHeight),itemCount:xs(e.itemCount),atStart:Ss(e.atStart),atEnd:Ss(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Ls(e){return[e,...(e.childFrames??[]).flatMap(e=>Ls(e))]}function Fs(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 qs(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 zs(e){return ds.some(t=>t===e)}function Vs(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 Js(e,t,n){const r=t.filter(e=>Ut(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&hs(a.url))return a;const e=r.filter(e=>hs(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 Us(e={},t={}){const n=t.ctxManager??ue(),r=t.runtime??de(),a=Js(n,await n.listNativePages(),e),o=await r.connectNativePage(a);return new Hs({target:a,controller:o})}var Hs=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new ma(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 ps(Ki)}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 hs(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await Yn(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(Vi)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await ps(Ki)}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(Vi,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 ps(Ki)}for(let e=0;e<Yi&&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 ps(Gi),await a())return!0;if(e.after<=1)break}for(let e=0;e<=Yi&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=Yi)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await ps(Gi)}return await a()}async isRecommendSurfaceOpen(){const e=[Ei.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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 ps(Ki)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Ls(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 Bs(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Ei.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=Ts(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Ts(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(gs(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??ns,settleMs:t.settleMs??ts}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(Hi)}) !== 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 ps(Ki)}else await ps(Ki);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=As(await this.evaluateJson(e).catch(()=>{}));return t.found?t:As(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 ps(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 ps(260),await this.selectAllFocusedText(),await ps(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await ps(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await ps(500),!0;await ps(350);for(const t of Array.from(e))await this.controller.insertText(t),await ps(110);return await ps(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=bs(e.jobName),a=t.filter(e=>bs(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>bs(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=bs(e.jobName),r=bs(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 ps(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 ps(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 ps(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 ps(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?Ei.nav.chatLink:Ei.nav.recommendLink,r=Ts(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(Ei.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=_i(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 ps(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 ps(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 ps(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=Fs(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await ps(Gi)}return Fs(r)}async openChat(e){if(await wn(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?Yi: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 ps(ts);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await ps(ts),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:Vs(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Fs(o),c=!0!==e.preferUnread||t?Ci(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 ps(Gi)}},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 ps(Gi)}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 _s(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 Es(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 ps(Ki)}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 Ps(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 = (${ms.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=Os(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=Os(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 exchangeWechat(e={}){const t=Ts(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=Ts(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 ps(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=Ts(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 ps(250);const r=Ts(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 ps(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(!Di(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(Di(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=qs(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<Qi;t+=1){await ps(Zi);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>=es){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:Xi});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 Is(await this.evaluateJson(`(() => {\n const limit = ${$i};\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([Ei.username.primary,...Ei.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(Ji)};\n const fallbackSelector = ${JSON.stringify(Ui)};\n const iframe = document.querySelector(${JSON.stringify(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\n const parseCandidateProfileTokens = ${ms.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 })()`}buildRecommendGreetClickExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(Ji)};\n const fallbackSelector = ${JSON.stringify(Ui)};\n const iframe = document.querySelector(${JSON.stringify(Ei.recommend.iframe)});\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cards =\n primaryCards.length > 0\n ? primaryCards\n : Array.from(root.querySelectorAll(fallbackSelector));\n const item = cards[index];\n if (!item) return { found: false, x: 0, y: 0 };\n const button =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n if (!button) return { found: false, x: 0, y: 0 };\n button.scrollIntoView({ block: "center", inline: "center" });\n const rect = button.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { found: false, x: 0, y: 0 };\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`}async waitForWechatExchangeDialog(e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const tooltip = document.querySelector(".exchange-tooltip");\n if (tooltip && visible(tooltip)) return true;\n for (const element of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = element.textContent ?? "";\n if (\n text.includes("交换微信") &&\n element.querySelector(".boss-btn-primary, .boss-btn") &&\n visible(element)\n ) {\n return true;\n }\n }\n return false;\n })()').catch(()=>!1))return!0;await ps(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(`${Ei.recommend.filterButton}, ${Ji}, ${Ei.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 ps(Ki)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Ei.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(Ei.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 ps(Ki)}}await ps(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(Ei.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(ls)};\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 ps(Ki)}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 ps(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 ps(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(Ei.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(as)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(os)} -\n ${JSON.stringify(as)})\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=Ds(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:Ds(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Ei.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=$s(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=$s(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??ns})}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 ps(is),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-as)/(os-as)))}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+cs),s="min"===e?Math.max(0,o-cs):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<ss;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??as,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(Ei.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(ls)};\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(Ti.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(Ei.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 ps(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Ei.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 ps(600),!0;await ps(Ki)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Ei.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 Rs(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(zi)}));\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&&vs(e.name,n.candidateName);if(r&&a)return{ready:!0};await ps(Ki)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:zs(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=Ts(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(zi)}));\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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\n const parseCandidateProfileTokens = ${ms.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(Ji)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(Ui)}));\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=Cs(await this.evaluateJson(e));if(t.length>0)return t;return Cs(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=_i(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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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=js(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=js(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=_i(e);await wn(this);const a=Ts(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(Ei.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(gs(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??rs):n??rs}),await ps(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=_i(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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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=js(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=js(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)}}};import{createHash as Ws}from"node:crypto";var Ks,Gs,Ys={fetch:fetch,env:process.env},Xs="https://huajune.duliday.com",Zs=new Set;function Qs(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function ec(e,t){const n=Qs(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 tc(e){const t=ec(e,"RECRUITMENT_EVENTS_ENABLED"),n=Qs(e,"RECRUITMENT_EVENTS_API_BASE_URL")??Xs,r=Qs(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=Qs(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function nc(e,t){const n=ye(e.defaultAgentId);if(void 0!==n)return n;he()&&rc(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 rc(e,t,n){Zs.has(t)||(Zs.add(t),e.warn(n))}function ac(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function oc(e){return"object"==typeof e&&null!==e}function ic(e,t){const n=e[t];return"string"==typeof n?n:void 0}function sc(e){if(!oc(e)||!oc(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>oc(e)&&"error"===ic(e,"status"));if(!oc(t))return;const n=ic(t,"idempotencyKey"),r=t.error,a=oc(r)?ic(r,"code"):void 0,o=oc(r)?ic(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function cc(e,t,n){const r=tc(n.env);if(!r.enabled)return;const a=nc(r,t);if(void 0===r.apiToken)return void rc(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(he()||rc(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(ac(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=sc(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function lc(e,t){return`${e}:${Ws("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function dc(e,t){(async()=>{const n=Gs??(async(e,t)=>{await cc(e,t,{...Ys,...Ks})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function uc(){return(new Date).toISOString().slice(0,10)}function mc(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function pc(e,t,n){const r=e?.candidateInfo,a=mc(r?.age),o=mc(r?.education),i=mc(r?.expectedSalary);return{name:mc(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function gc(e){return mc(e?.candidateInfo.communicationPosition)??""}function fc(e){const t=mc(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 hc(e){const t=mc(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?mc(n[0]):void 0}function yc(e){return e.length>0?{job:{jobName:e}}:{}}function bc(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 wc(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&bc(n.name,n.position)&&dc({idempotencyKey:lc("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,uc()]),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 xc(e,t){const n=gc(e.candidateDetails),r=n,a=pc(e.candidateDetails,e.candidateName,r);if(!bc(a.name,a.position))return;const o=hc(e.candidateDetails);dc({idempotencyKey:lc("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...yc(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Sc(e,t){if(!e.clicked||0===e.name.length)return;const n=mc(e.expectedPosition);if(void 0===n||!bc(e.name,n))return;const r=mc(e.age),a=mc(e.education),o=mc(e.expectedSalary),i=mc(e.expectedLocation);dc({idempotencyKey:lc("zhipin-candidate-contacted",[e.candidateId,e.name,uc()]),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 Rc(e,t){const n=gc(e.candidateDetails),r=pc(e.candidateDetails,e.candidateName,n);if(!bc(r.name,r.position))return;const a=hc(e.candidateDetails);dc({idempotencyKey:lc("zhipin-wechat-requested",[e.conversationId,e.candidateId,uc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...yc(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 Ic(e,t,n,r){const a=gc(e),o=fc(e),i=hc(e),s=pc(e,e.candidateInfo.name,o);if(!bc(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=vc(c);dc({idempotencyKey:lc("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...yc(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var Cc,kc=wi.object({name:wi.string(),conversationId:wi.string(),candidateId:wi.string(),position:wi.string(),time:wi.string(),preview:wi.string(),unreadCount:wi.number(),hasUnread:wi.boolean(),index:wi.number()}),Ac=wi.object({success:wi.boolean(),candidates:wi.array(kc),total:wi.number(),stats:wi.object({withName:wi.number(),withUnread:wi.number()})});function _c(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Cc}}function Ec(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 Nc(e){const t=new Set,n=[];for(const r of e){const e=Ec(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var Mc=vi({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:wi.object({limit:wi.number().optional().describe("最多返回条数"),onlyUnread:wi.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:wi.enum(["time","unreadCount","name"]).default("time"),autoScroll:wi.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:wi.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Ac,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=_c();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=Nc(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)`),wc(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 Pc,BrowserPageInfoSchema as Tc}from"@roll-agent/browser";import{defineTool as Bc,StructuredToolError as Oc}from"@roll-agent/sdk";import{z as Dc}from"zod";var $c,jc=Dc.object({forceReload:Dc.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:Dc.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:Pc.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),Lc=Dc.object({success:Dc.boolean(),alreadyOnChat:Dc.boolean(),usedSidebarClick:Dc.boolean(),usedReload:Dc.boolean(),chatReady:Dc.boolean(),reloadSkippedReason:Dc.enum(us).optional(),page:Tc.optional(),error:Dc.string().optional()});function Fc(){return{getContextManager:ue,getRuntime:de,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...$c}}async function qc(e,t){return Wt(e,await t.inspectPage())}var zc=Bc({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:jc,output:Lc,execute:async(e,t)=>{const n=Fc(),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 wn(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 qc(r,a),error:s.error};hn(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 qc(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await qc(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(Ei.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 qc(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 qc(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 qc(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await qc(r,a),error:"沟通页未就绪"})}catch(e){if(e instanceof Oc)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 Vc}from"@roll-agent/sdk";import{z as Jc}from"zod";var Uc,Hc=Jc.object({success:Jc.boolean(),conversationId:Jc.string(),candidateId:Jc.string(),candidateName:Jc.string(),index:Jc.number(),hasUnread:Jc.boolean(),unreadCount:Jc.number(),lastMessageTime:Jc.string(),messagePreview:Jc.string(),error:Jc.string().optional()});function Wc(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Uc}}var Kc=Vc({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Jc.object({conversationId:Jc.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Jc.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Jc.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Jc.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Hc,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Wc();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 Gc}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as Yc,prepareReplyContext as Xc,ReplyAuthorityRequestError as Zc}from"@roll-agent/reply-authority-client";import{z as Qc}from"zod";var el="·",tl=/[--—–]/,nl=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function rl(e){return(e??"").trim()}function al(e){return e.some(e=>"candidate_expected_location"!==e.source)}function ol(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 il(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function sl(e){const t=il(e)?e.signals:e,n=il(e)?e.analysisPath:void 0,r=il(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=ol(t);return!al(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function cl(e){const t=nl.exec(rl(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function ll(e){const t=rl(e);if(!t||nl.test(t))return;if(!tl.test(t))return;const[n=""]=t.split(tl);return rl(n)||void 0}function dl(e){const t=rl(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(el).map(e=>rl(e));return{expectedLocation:n,expectedPosition:r}}function ul(e){const t=rl(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=dl(e.expectedJobText),a=ll(t),o=cl(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var ml,pl=Qc.object({index:Qc.number(),sender:Qc.enum(["candidate","recruiter","system"]),messageType:Qc.enum(["text","system","resume","wechat-exchange"]),content:Qc.string(),time:Qc.string()}),gl=Qc.object({name:Qc.string(),age:Qc.string(),experience:Qc.string(),education:Qc.string(),communicationPosition:Qc.string(),expectedPosition:Qc.string(),expectedLocation:Qc.string(),expectedSalary:Qc.string(),tags:Qc.array(Qc.string())}),fl=Qc.object({success:Qc.boolean(),conversationId:Qc.string(),candidateId:Qc.string(),candidateInfo:gl,preferredBrand:Qc.string().optional(),preferredBrandId:Qc.number().int().positive().optional(),locationSignals:Qc.array(Yc).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:Qc.array(pl),formattedHistory:Qc.array(Qc.string()),stats:Qc.object({totalMessages:Qc.number(),candidateMessages:Qc.number(),recruiterMessages:Qc.number(),systemMessages:Qc.number()}),error:Qc.string().optional()});function hl(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function yl(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:hl(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function bl(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 vl(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),prepareReplyContext:Xc,...ml}}function wl(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function xl(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Sl(e){return{candidateMessage:xl(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 Rl=6e5,Il=0;function Cl(e){return e instanceof Zc?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function kl(e){Cl(e)&&(Il=Date.now()+Rl)}async function Al(e){if(Date.now()<Il)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===xl(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=qi(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=Sl({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=>{kl(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){kl(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var _l=Gc({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Qc.object({conversationId:Qc.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Qc.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Qc.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Qc.number().default(100).describe("最多返回的消息条数")}),output:fl,execute:async(e,t)=>{const n=e.maxMessages??100,r=vl(),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)),yl(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||!bl(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&&!bl(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=ul({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=wl(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`),Ic(g,p.conversationId,p.candidateId,t.logger),await Al({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)}`),yl(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as El}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Nl,GenerateSignedReplyResponseSchema as Ml,ReasoningConfigSchema as Pl,ReplyAuthorityRequestError as Tl,ReplyStreamFinalEventSchema as Bl,ReplyStreamLocationResolvedEventSchema as Ol,streamGenerateSignedReply as Dl}from"@roll-agent/reply-authority-client";import{z as $l}from"zod";import{ReplyGateAdvisoryCodeSchema as jl}from"@roll-agent/reply-authority-client";import{z as Ll}from"zod";var Fl=["option_1","option_2"],ql=["judge","orchestrator"],zl=[...ql,"service_recommended_fallback","explicit_no_judge"],Vl={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"},Jl=Ll.array(jl).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),Ul=Ll.object({chosenOption:Ll.enum(Fl).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:Ll.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:Jl.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:Ll.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),Hl=Ul.omit({judgeModel:!0}).extend({confirmedFindingCodes:Jl.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as Wl}from"node:crypto";var Kl=new Map;function Gl(e){for(const[t,n]of Kl)n.expiresAt<=e&&Kl.delete(t)}function Yl(e,t=Math.floor(Date.now()/1e3)){if(Gl(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${Wl()}`,r={preparedReplyId:n,...e};return Kl.set(n,{...r,consumed:!1}),r}function Xl(e,t=Math.floor(Date.now()/1e3)){const n=Kl.get(e);return void 0===n?(Gl(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Kl.delete(e),Gl(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Kl.set(e,{...n,consumed:!0}),{ok:!0,record:ed(n)})}function Zl(e,t=Math.floor(Date.now()/1e3)){const n=Kl.get(e);return void 0===n?(Gl(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Kl.delete(e),Gl(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:ed(n)}}function Ql(e,t,n=Math.floor(Date.now()/1e3)){const r=Zl(e,n);if(!r.ok)return r;const a=Kl.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(Kl.set(e,{...a,judgement:t}),{ok:!0,record:ed({...a,judgement:t})})}function ed(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 td}from"@roll-agent/reply-authority-client";import{z as nd}from"zod";var rd=1e3,ad=8,od=500,id=200,sd=12,cd=100,ld=td.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),dd=nd.object({candidateMessage:nd.string().min(1).max(rd),recentConversation:nd.array(nd.string().min(1).max(od)).max(ad),candidateInfo:ld.optional(),preferredBrand:nd.string().min(1).max(id).optional()});function ud(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 md(e,t){const n=ud(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function pd(e){if(void 0===e)return;const t={...void 0!==e.age?{age:md(e.age,id)}:{},...void 0!==e.experience?{experience:md(e.experience,id)}:{},...void 0!==e.education?{education:md(e.education,id)}:{},...void 0!==e.communicationPosition?{communicationPosition:md(e.communicationPosition,id)}:{},...void 0!==e.expectedPosition?{expectedPosition:md(e.expectedPosition,id)}:{},...void 0!==e.expectedLocation?{expectedLocation:md(e.expectedLocation,id)}:{},...void 0!==e.expectedSalary?{expectedSalary:md(e.expectedSalary,id)}:{},...void 0!==e.info?{info:e.info.map(e=>md(e,cd)).filter(e=>e.length>0).slice(0,sd)}:{}};return Object.keys(t).length>0?t:void 0}function gd(e){const t=md(e.candidateMessage,rd);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>md(e,od)).filter(e=>e.length>0).slice(-ad),r=pd(e.candidateInfo),a=void 0!==e.preferredBrand?md(e.preferredBrand,id):void 0;return dd.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 hd,yd=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(!Xe())return!1;try{return await this.target.evaluateJson(fd(e))}catch{return!1}}},bd=$l.object({conversationId:$l.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:$l.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:$l.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:$l.number().default(100).describe("最多读取的聊天消息条数"),reasoning:Pl.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),vd=["rejected","timeout","server_error"],wd=$l.object({success:$l.boolean(),preparedReplyId:$l.string().optional(),suggestedReply:$l.string().optional(),stage:$l.string().optional(),confidence:$l.number().optional(),expiresAt:$l.number().optional(),requestId:$l.string().optional(),timing:$l.object({totalLatencyMs:$l.number().int().min(0),replyLatencyMs:$l.number().int().min(0).optional(),turnPlanningLatencyMs:$l.number().int().min(0).optional(),contextBuildingLatencyMs:$l.number().int().min(0).optional(),preparedContextHit:$l.boolean()}).optional(),gateRewritten:$l.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:$l.object({groupId:$l.string().min(1),options:$l.array($l.object({option:$l.enum(Fl),suggestedReply:$l.string(),expiresAt:$l.number().int().min(0)})).min(2).max(2),findings:$l.array($l.object({code:$l.string().min(1),description:$l.string().min(1)})),rubricVersion:$l.string().min(1),rubricHash:$l.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:$l.string().optional(),errorKind:$l.enum(vd).optional()}),xd={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function Sd(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),createReplyPreviewVisualSession:e=>new yd(e),streamGenerateSignedReply:Dl,fetchReplyFeedbackRubric:Nl,savePreparedReply:Yl,random:Math.random,...hd}}function Rd(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 Id(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function Cd(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function kd(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function Ad(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function _d(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Ed(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function Nd(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function Md(e){const t=Nd(e.navigationUnreadCount);return t>0?t:"candidate"===Ed(e.data.messages)?.sender?1:0}function Pd(e){const t=ul({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:_d(e.data.messages),conversationHistory:Ad(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 Td(e){return"phase.started"===e.type?Id(e,"label")??xd[Id(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Id(e,"toolName")?`: ${Id(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Id(e,"toolName")?`: ${Id(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function Bd(e,t){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{}}}function Od(e){return e.replace(/\s+\(url=.*\)$/,"")}function Dd(e){const t=e instanceof Error?e.message:String(e);if(e instanceof Tl){const n=Od(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 $d(e){return e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(e<1e4?1:0)}s`}function jd(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function Ld(e){const t=["回复已生成",`总 ${$d(e.totalLatencyMs)}`];return void 0!==e.replyLatencyMs&&t.push(`生成 ${$d(e.replyLatencyMs)}`),e.preparedContextHit&&t.push("预热命中"),e.gateRewritten&&t.push("终稿经安全门调整"),t.join(" · ")}function Fd(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 qd(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 zd(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 Vd(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return zd({...e,reason:Vl.RUBRIC_MISMATCH});const t=qd(e.replyVariants,e.random);if(2!==t.length)return zd({...e,reason:Vl.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return zd({...e,reason:Vl.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:Fl[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 Jd=El({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:bd,output:wd,execute:async(e,t)=>{const n=Sd(),r=e.maxMessages??100,a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await wn(o),await i.begin(a?"正在打开目标聊天":"正在准备当前聊天");const c=a?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==c&&!c.found){const e=c.error??"打开聊天失败";return await i.fail(e),Bd(e)}if(void 0===c&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),Bd(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const l=c?.name??e.candidateName??"",d=await o.readActiveChatPanel();if(l.length>0&&(!d||!Rd(l,d.candidateName))){const e=`右侧聊天面板未切换到 ${l}`;return await i.fail("聊天面板未同步"),Bd(e)}const u=await o.readSelectedChatTarget();if(!u){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),Bd(e)}if(null!==d&&u.candidateName.length>0&&!Rd(u.candidateName,d.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${u.candidateName} / ${d.candidateName}`;return await i.fail("当前会话不一致"),Bd(e)}if(!(void 0===c||c.conversationId.length>0&&u.conversationId===c.conversationId||0===c.conversationId.length&&c.candidateId.length>0&&u.candidateId===c.candidateId)){const e=`当前选中会话与目标会话不一致: ${c?.name||c?.conversationId||""}`;return await i.fail("当前会话不一致"),Bd(e)}await o.waitForChatMessages();const m=await o.readCandidateChatDetails(r);if(null!==m.selectedTarget&&(m.selectedTarget.conversationId!==u.conversationId||m.selectedTarget.candidateId!==u.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${m.selectedTarget.conversationId}/${m.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),Bd(e)}if(0===_d(m.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),Bd(e)}const p=qi(await o.readUsernameEvidence());if(!p.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),Bd(e)}const g=Pd({data:m,conversationId:u.conversationId,candidateId:u.candidateId,recruiterUsername:p.username,reasoning:e.reasoning}),f=Md({navigationUnreadCount:c?.unreadCount,data:m});t.logger.info(`Generating reply preview for ${u.candidateName||u.candidateId}`);const h=Date.now();await s.begin("正在生成回复");let y,b,v,w,x="";const S=new Map;let R=!1;for await(const e of n.streamGenerateSignedReply(g)){if("stream.started"===e.type&&(y=Id(e,"requestId"),b=Id(e,"tenantId")),"phase.completed"===e.type){const t=Id(e,"phase"),n=Cd(e,"latencyMs");void 0!==t&&void 0!==n&&S.set(t,Math.round(n))}"gate.completed"===e.type&&!0===kd(e,"rewritten")&&(R=!0);const r=Td(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=Ol.safeParse(e);if(t.success){const e=sl({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+=Id(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=Bl.parse(e),a=Ml.parse(r),o=S.get("turn_planning"),i=S.get("context_building"),c=Math.max(0,Math.round(Date.now()-h)),l=void 0!==a.latencyMs?Math.round(a.latencyMs):void 0,d=a.replyVariants?.items.reduce((e,t)=>Math.min(e,t.envelopeExp),a.envelopeExp)??a.envelopeExp,m=jd({turnPlanningLatencyMs:o,contextBuildingLatencyMs:i});let p;if(w={totalLatencyMs:c,...void 0!==l?{replyLatencyMs:l}:{},...void 0!==o?{turnPlanningLatencyMs:o}:{},...void 0!==i?{contextBuildingLatencyMs:i}:{},preparedContextHit:m},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("双稿反馈身份缺失"),Bd(e)}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});p=Vd({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:u.conversationId,judgeContext:gd(g),random:n.random}),"not_learned"===p.state&&t.logger.warn(`Reply variants will use a non-learning terminal outcome: reason=${p.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=${Vl.RUBRIC_FETCH_FAILED} detail=${n}`),p=zd({replyVariants:a.replyVariants,tenantId:b,conversationId:u.conversationId,reason:Vl.RUBRIC_FETCH_FAILED})}}v=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:d,unreadCountBeforeReply:f,...void 0!==y?{requestId:y}:{},...void 0!==p?{variantGroup:p}:{}}),await s.complete(Ld({...w,gateRewritten:R}),a.suggestedReply,"judge_ready"===p?.state?{options:p.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})),findings:p.findings}:void 0)}}if(void 0===v){const e="Reply Authority stream 未返回 final";return await s.fail(e),Bd(e)}return Fd(v,w,R)}catch(e){const t=Dd(e);return await(s?.fail(t.message)),await(i?.fail(t.message)),Bd(t.message,t.errorKind)}finally{o?.close()}}});import{defineTool as Ud}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Hd}from"@roll-agent/reply-authority-client";import{z as Wd}from"zod";var Kd,Gd="zhipin_judge_prepared_reply",Yd=Wd.object({success:Wd.boolean(),variantDecision:Ul.optional(),decisionSource:Wd.enum(zl).optional(),fallback:Wd.boolean().optional(),recommendedOption:Wd.enum(Fl).optional(),error:Wd.string().optional()}),Xd=Wd.object({preparedReplyId:Wd.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:Wd.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),Zd=new Map;function Qd(){return{fetchReplyFeedbackRubric:Hd,...Kd}}function eu(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function tu(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 nu(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 ru(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 au(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 ou(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=Qd();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=${Vl.RUBRIC_FETCH_FAILED} detail=${r}`),tu(e,Vl.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return tu(e,Vl.RUBRIC_MISMATCH);try{i=await t.llm.generateText(au(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_SAMPLING_FAILED} detail=${r}`),tu(e,Vl.JUDGE_SAMPLING_FAILED)}try{s=Hl.parse(nu(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_OUTPUT_INVALID} detail=${r}`),tu(e,Vl.JUDGE_OUTPUT_INVALID)}return ru(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:ud(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),tu(e,Vl.JUDGE_OUTPUT_INVALID))}async function iu(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=Zd.get(e.preparedReplyId);if(void 0!==r)return r;const a=ou(e,t,n).then(n=>{const r=Ql(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{Zd.delete(e.preparedReplyId)});return Zd.set(e.preparedReplyId,a),a}function su(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 cu=Ud({name:Gd,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:Xd,output:Yd,execute:async(e,t)=>{const n=Zl(e.preparedReplyId);if(!n.ok)return{success:!1,error:eu(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return Yd.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return su(await iu(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as lu}from"node:crypto";import{BrowserActionApprovalSchema as du}from"@roll-agent/browser";import{postReplyFeedback as uu}from"@roll-agent/reply-authority-client";import{defineTool as mu}from"@roll-agent/sdk";import{z as pu}from"zod";import{createHash as gu}from"node:crypto";import{mkdirSync as fu}from"node:fs";import{homedir as hu}from"node:os";import{dirname as yu,resolve as bu}from"node:path";import{DatabaseSync as vu}from"node:sqlite";import{postReplyFeedback as wu,ReplyFeedbackBodySchema as xu}from"@roll-agent/reply-authority-client";import{createAgentLogger as Su}from"@roll-agent/sdk";var Ru,Iu,Cu=604800,ku=3e4,Au=1e3,_u=9e5,Eu=9e5,Nu=50,Mu=["pending","delivered","dead"],Pu=Mu.map(e=>`'${e}'`).join(", "),Tu=Su("browser-use-agent:reply-feedback-outbox");function Bu(e){return"~"===e?hu():e.startsWith("~/")?bu(hu(),e.slice(2)):bu(e)}function Ou(){return bu(hu(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function Du(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 $u(e){const t=e.retentionSeconds??Du("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",Cu),n=e.flushIntervalMs??Du("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",ku),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??Ou();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=Bu(r),o=e.retryBaseDelayMs??Au,i=e.authRetryDelayMs??_u;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 ju(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(ju).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${ju(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function Lu(e){return gu("sha256").update(e).digest("hex")}function Fu(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 qu(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 zu(e){return xu.parse(JSON.parse(e.body_json))}function Vu(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function Ju(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 Uu(e){return 401===e||403===e}function Hu(e){return!(void 0!==e&&!Uu(e))||(408===e||425===e||429===e||e>=500)}function Wu(e,t){if(Uu(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,Eu)}function Ku(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function Gu(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 Yu(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 Xu(e,t,n,r){const a=Ju(n),o=Vu(n);if(Hu(a)&&r<t.retry_expires_at){const n=r+Wu(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 Zu(e,t){const n=Date.now();if(n>=t.retry_expires_at)return Xu(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(zu(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 Yu(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return Xu(e,qu(e.db,t.group_id)??t,n,Date.now())}}async function Qu(e){const t=Date.now();Gu(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,Nu);for(const t of n)await Zu(e,t)}function em(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,Ku(e,()=>Qu(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function tm(e){fu(yu(e),{recursive:!0});const t=new vu(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 (${Pu})),\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 nm(e={}){if(Ru&&!Ru.closing)return Ru.deliver=e.deliver??Ru.deliver,void(Ru.logger=e.logger??Ru.logger);if(Ru?.closing||Iu)throw new Error("Reply feedback outbox is shutting down.");const t=$u(e),n=tm(t.dbPath),r=setInterval(()=>{Ru&&em(Ru)},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??wu,logger:e.logger??Tu,closing:!1,backgroundFlushQueued:!1};Ru=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),em(a)}async function rm(){if(Iu)return Iu;const e=Ru;return e?(e.closing=!0,clearInterval(e.timer),Iu=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),Ru===e&&(Ru=void 0)}})().finally(()=>{Iu=void 0})):void 0}async function am(e,t,n,r={}){Ru||nm({deliver:t,logger:n});const a=Ru;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=xu.parse(e),i=ju(o),s=Lu(i);return Ku(a,async()=>{const e=Date.now(),t=Fu(e,a.retentionMs,r.feedbackExpiresAt);Gu(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=qu(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=qu(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}:{}}:Zu(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}import{defineTool as om}from"@roll-agent/sdk";import{z as im}from"zod";function sm(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as cm}from"zod";var lm="reply-authority-service",dm="browser-use-agent/zhipin_send_reply",um="zhipin",mm=60,pm=cm.object({platform:cm.literal(um),username:cm.string().min(1),accountId:cm.string().min(1).optional()}),gm=cm.object({v:cm.literal(2),iss:cm.literal(lm),kid:cm.string().min(1),jti:cm.string().min(1),iat:cm.number().int(),exp:cm.number().int(),aud:cm.literal(dm),platform:cm.literal(um),tenantId:cm.string().min(1),conversationId:cm.string().min(1),candidateId:cm.string().min(1),reply:cm.string(),policyVersion:cm.string().min(1),recruiterBinding:pm}),fm=cm.object({kid:cm.string().min(1),algorithm:cm.literal("Ed25519"),publicKey:cm.string().min(1),validFrom:cm.string().min(1),validUntil:cm.string().optional()}),hm=cm.object({keys:cm.array(fm)}),ym=new Map;function bm(e){for(const[t,n]of ym)n<e-mm&&ym.delete(t)}function vm(e,t=Math.floor(Date.now()/1e3)){return bm(t),ym.has(e)}function wm(e,t,n=Math.floor(Date.now()/1e3)){bm(n),ym.set(e,t)}import{createPublicKey as xm,verify as Sm}from"node:crypto";var Rm=new Map,Im=null;function Cm(){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 km(e){return Rm=new Map(e.map(e=>[e.kid,e]))}async function Am(){if(Im)return Im;Im=(async()=>{const e=await fetch(Cm()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return km(hm.parse(t).keys)})();try{return await Im}finally{Im=null}}async function _m(){await Am()}async function Em(e){const t=Rm.get(e);if(t)return t;const n=(await Am()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function Nm(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Mm(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")}Nm(a);const i=gm.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function Pm(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-mm)throw new Error("Envelope expired");if(e.iat>t+mm)throw new Error("Envelope issued in the future")}async function Tm(e,t=Math.floor(Date.now()/1e3)){const n=Mm(e),r=await Em(n.payload.kid),a=xm({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!Sm(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return Pm(n.payload,t),n.payload}var Bm,Om=im.object({success:im.boolean(),sentMessage:im.string(),error:im.string().optional()});function Dm(){return{getReplyAuthorityKeysLoaded:pe,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Bm}}function $m(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 jm(e){return"function"==typeof e.readCandidateChatDetails}async function Lm(e){if(jm(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function Fm(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function qm(e,t){return null===t||0===e.candidateName.length||$m(e.candidateName,t.candidateName)}function zm(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function Vm(e,t){const n=Dm();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 Tm(e.signedEnvelope);if(o=i.reply,vm(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(r),await new yd(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=zm(e.unreadCountBeforeReply);if(!Fm(c,i)||!qm(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,zm(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&&!$m(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=qi(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(!sm(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 Lm(r);return xc({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),wm(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 Jm,Um=om({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:im.object({signedEnvelope:im.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:im.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:im.number().optional().describe("候选人在列表中的索引(可选)")}),output:Om,execute:async(e,t)=>await Vm(e,t)}),Hm="zhipin_send_prepared_reply",Wm=80,Km=pu.object({success:pu.boolean(),sentMessage:pu.string(),chosenOption:pu.enum(Fl).optional(),decisionSource:pu.enum(zl).optional(),decisionReason:pu.string().optional(),judgeModel:pu.string().optional(),feedbackExpected:pu.boolean().optional(),feedbackStatus:pu.enum(["accepted","duplicate","queued","skipped","failed"]).optional(),feedbackError:pu.string().optional(),error:pu.string().optional()}),Gm=pu.object({preparedReplyId:pu.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:nt.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:du.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:Ul.optional().describe("可选的显式双稿选择;缺省时发送工具会自动执行并缓存默认 Judge。"),skipVariantJudge:pu.boolean().optional().describe("仅用于明确的应急降级:跳过双稿 Judge、发送服务端推荐稿并回传 not_learned 终态,但不产生学习样本。")}).superRefine((e,t)=>{void 0!==e.variantDecision&&!0===e.skipVariantJudge&&t.addIssue({code:pu.ZodIssueCode.custom,message:"variantDecision 与 skipVariantJudge 不能同时传入",path:["skipVariantJudge"]})});function Ym(){return{sendSignedZhipinReply:Vm,postReplyFeedback:uu,submitReplyFeedback:am,...Jm}}function Xm(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function Zm(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 Qm(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 ep(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"}:Zm(e.judgement.decision,t.variantDecision)?Qm(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?Qm(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:Qm(await iu(e,n))}function tp(e,t){return"judge_ready"===e.variantGroup?.state?e.variantGroup.options.find(e=>e.option===t):void 0}function np(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 rp(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&&np(e,r))return{ok:!1,error:"variantDecision.confirmedFindingCodes 必须来自 replyVariantSelection.findings"};const a="decision"===t.kind?t.decision.chosenOption:t.recommendedOption,o=tp(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 ap(e){return`sha256:${lu("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 op(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>Wm?`${t.slice(0,Wm-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function ip(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 sp(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 cp=mu({name:Hm,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:Gm,output:Km,execute:async(e,t)=>{const n=Ym(),r=Zl(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:Xm(r.reason)};const a=await ep(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=rp(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=ap(o),s=`${o.record.preparedReplyId}:${i}`,c=It(t,{subject:{tool:Hm,target:o.record.preparedReplyId,summary:op(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});hn(t,de(),{action:Hm,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=Xl(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:Xm(l.reason)};const d=rp(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 ip(n,d,t);return{...u,...sp(d),...m}}});import{defineTool as lp}from"@roll-agent/sdk";import{z as dp}from"zod";var up,mp=dp.object({success:dp.boolean(),exchanged:dp.boolean(),wechatNumber:dp.string().optional(),error:dp.string().optional()}),pp=900,gp=160,fp=1100;function hp(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...up}}function yp(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 bp(e){return"function"==typeof e.readCandidateChatDetails}async function vp(e){if(bp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var wp=lp({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:mp,execute:async(e,t)=>{const n=hp();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(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&&!yp(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 vp(r),c=await r.exchangeWechat({preClickDelayMs:pp,pressDurationMs:gp,settleMs:fp,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&Rc({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 xp}from"@roll-agent/sdk";import{z as Sp}from"zod";var Rp,Ip=Sp.object({success:Sp.boolean(),username:Sp.string(),usedSelector:Sp.string().optional(),usedStrategy:Sp.string().optional(),source:Sp.string().optional(),error:Sp.string().optional()});function Cp(){return{openNativePagePort:Us,pickBestUsername:qi,createNativeVisualActivitySession:e=>new ha(e),...Rp}}var kp=xp({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Sp.object({}),output:Ip,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Cp();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 Ap}from"@roll-agent/sdk";import{z as _p}from"zod";var Ep,Np=["unknown","top","middle","bottom","only-page"],Mp=_p.object({containerFound:_p.boolean(),containerLabel:_p.string(),scrollTop:_p.number(),scrollHeight:_p.number(),clientHeight:_p.number(),itemCount:_p.number(),atStart:_p.boolean(),atEnd:_p.boolean()}),Pp=_p.object({success:_p.boolean(),surface:_p.enum(ki),direction:_p.enum(["up","down"]),stepsRequested:_p.number(),stepsCompleted:_p.number(),reachedBoundary:_p.boolean(),atTop:_p.boolean(),atBottom:_p.boolean(),canScrollUp:_p.boolean(),canScrollDown:_p.boolean(),position:_p.enum(Np),before:Mp,after:Mp,error:_p.string().optional()});function Tp(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Ep}}function Bp(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Op(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function Dp(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:Op(e)}}var $p=Ap({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:_p.object({surface:_p.enum(ki).describe("要滚动的页面区域"),direction:_p.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:_p.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:_p.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:_p.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:Pp,execute:async(e,t)=>{const n=Tp(),r=_i(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,...Dp(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Dp(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=Bp();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Dp(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as jp}from"@roll-agent/sdk";import{z as Lp}from"zod";var Fp,qp=Lp.object({applyMode:Lp.enum(Pi),ageMin:Lp.number().int().min(16).optional(),ageMax:Lp.number().int().min(16).optional(),location:Lp.object({city:Lp.string(),district:Lp.string().optional()}).optional(),optionSelections:Lp.array(Lp.object({fieldKey:Lp.string(),label:Lp.string(),values:Lp.array(Lp.string()),selection:Lp.enum(["single","multi"]),clearValue:Lp.string()}))}),zp=Lp.object({ageMin:Lp.number().optional(),ageMax:Lp.number().optional(),location:Lp.object({city:Lp.string(),district:Lp.string().optional()}).optional(),optionSelections:Lp.array(Lp.object({fieldKey:Lp.string(),label:Lp.string(),values:Lp.array(Lp.string())})),gender:Lp.string().optional(),activity:Lp.string().optional()}),Vp=Lp.object({success:Lp.boolean(),status:Lp.enum(Bi),requested:qp,applied:zp.optional(),filterButtonText:Lp.string().optional(),error:Lp.string().optional()}),Jp=Lp.string().min(1),Up=Lp.array(Jp).min(1),Hp=Lp.object({applyMode:Lp.enum(Pi).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:Lp.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:Lp.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:Jp.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:Jp.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:Lp.enum(Ni).optional().describe("性别筛选:不限、男、女"),activity:Lp.enum(Mi).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:Up.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:Jp.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:Jp.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:Up.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:Up.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:Jp.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:Up.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:Jp.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:Up.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:Up.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:Jp.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"}),Wp=350,Kp=130,Gp=600;function Yp(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Fp}}function Xp(e,t){const n=Oi(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function Zp(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 Qp(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function eg(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(Xp(t,r))}function tg(e){const t=[];if(void 0===e.applyMode&&!Zp(e)&&Qp(e)||!Zp(e)&&!Qp(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(Xp("gender",[n])),t.push(Xp("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}eg(t,"gender",e.gender),eg(t,"activity",e.activity),eg(t,"major",e.major),eg(t,"recentNotView",e.recentNotView),eg(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),eg(t,"candidateKeywords",e.candidateKeywords),eg(t,"school",e.school),eg(t,"switchJobFrequency",e.switchJobFrequency),eg(t,"intention",e.intention),eg(t,"salary",e.salary),eg(t,"degree",e.degree),eg(t,"experience",e.experience),eg(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 ng(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 rg(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 ag(e){const t=rg(e.applied);return{success:"applied"===e.status,status:e.status,requested:ng(e.requested),...void 0!==t?{applied:t}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var og=jp({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:Hp,output:Vp,execute:async(e,t)=>{const n=Yp(),r=tg(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 wn(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),ag({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Ei.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:Wp,pressDurationMs:Kp,settleMs:Gp,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),ag(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),ag({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as ig}from"@roll-agent/sdk";import{z as sg}from"zod";var cg=["target-count","boundary","no-new-items","max-steps"];import{AsyncLocalStorage as lg}from"node:async_hooks";var dg="@c",ug="@j",mg="legacy",pg=/^@?c([1-9]\d*)$/i,gg=/^@?j([1-9]\d*)$/i,fg=new Map,hg=new Map,yg=new lg;function bg(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${dg}${String(e+1)}`}function vg(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${ug}${String(e+1)}`}function wg(e){const t=pg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function xg(e){const t=gg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Sg(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 Cg(e.candidateRef).index}function Rg(e){const t=e.map((e,t)=>({...e,candidateRef:bg(t)}));return fg.set(Mg(),new Map(t.map(e=>[e.candidateRef,e]))),t}function Ig(e){const t=e.map((e,t)=>({...e,jobRef:vg(t)}));return hg.set(Mg(),new Map(t.map(e=>[e.jobRef,e]))),t}function Cg(e){const t=wg(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=bg(t);return fg.get(Mg())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function kg(e){const t=xg(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=vg(t),r=hg.get(Mg())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function Ag(e){const t=[];for(const n of e.indices??[])t.push({index:Sg({index:n}),candidateRef:bg(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Cg(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return Eg(t)}function _g(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)||Ng(e.name)===Ng(t.name))}function Eg(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function Ng(e){return e.trim().toLocaleLowerCase("zh-CN")}function Mg(){const e=yg.getStore();if(void 0!==e)return e;try{return be().id}catch{return mg}}var Pg,Tg=sg.object({index:sg.number(),candidateRef:sg.string(),candidateId:sg.string(),name:sg.string(),age:sg.string(),experience:sg.string(),education:sg.string(),workStatus:sg.string(),company:sg.string(),currentPosition:sg.string(),expectedLocation:sg.string(),expectedPosition:sg.string(),expectedSalary:sg.string(),tags:sg.array(sg.string()),buttonText:sg.string()}),Bg=sg.object({containerLabel:sg.string(),stepsRequested:sg.number(),stepsCompleted:sg.number(),reachedBoundary:sg.boolean(),stopReason:sg.enum(cg),uniqueCount:sg.number(),duplicateCount:sg.number(),noNewRounds:sg.number(),beforeItemCount:sg.number(),afterItemCount:sg.number(),beforeScrollHeight:sg.number(),afterScrollHeight:sg.number()}),Og=sg.object({success:sg.boolean(),candidates:sg.array(Tg),total:sg.number(),scrollStats:Bg.optional(),error:sg.string().optional()});function Dg(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Pg}}var $g=ig({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:sg.object({maxResults:sg.number().optional().describe("最多返回条数"),autoScroll:sg.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:sg.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Og,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=Dg();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=Rg(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 jg}from"@roll-agent/sdk";import{z as Lg}from"zod";var Fg,qg=["listed","recommend_not_ready","selector_not_found"],zg=Lg.object({index:Lg.number(),jobRef:Lg.string().optional(),value:Lg.string(),label:Lg.string(),isCurrent:Lg.boolean()}),Vg=Lg.object({success:Lg.boolean(),status:Lg.enum(qg),current:zg.optional(),jobs:Lg.array(zg),availableCount:Lg.number(),canSwitch:Lg.boolean(),error:Lg.string().optional()}),Jg=450,Ug=140,Hg=750;function Wg(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Fg}}function Kg(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function Gg(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Yg(e){const t=Ig(e.options),n=e.options.map((e,n)=>Kg(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>Gg(e,t))??Kg(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 Xg=jg({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:Lg.object({}),output:Vg,execute:async(e,t)=>{const n=Wg();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:Jg,pressDurationMs:Ug,settleMs:Hg,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),Yg(e)}catch(e){const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),Yg({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as Zg}from"@roll-agent/browser";import{defineTool as Qg}from"@roll-agent/sdk";import{z as ef}from"zod";var tf,nf=ef.object({success:ef.boolean(),alreadyOnRecommend:ef.boolean(),usedSidebarClick:ef.boolean(),recommendReady:ef.boolean(),page:Zg.optional(),error:ef.string().optional()});function rf(){return{getContextManager:ue,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...tf}}async function af(e,t){return Wt(e,await t.inspectPage())}var of=Qg({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:ef.object({}),output:nf,execute:async(e,t)=>{const n=rf(),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 wn(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(Ei.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await af(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 af(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 af(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await af(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 sf}from"@roll-agent/sdk";import{z as cf}from"zod";var lf,df=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],uf=cf.object({index:cf.number(),value:cf.string(),label:cf.string(),isCurrent:cf.boolean()}),mf=cf.object({jobRef:cf.string().optional(),jobValue:cf.string().optional(),jobName:cf.string().optional(),index:cf.number().optional(),searchKeyword:cf.string().optional(),useSearch:cf.boolean().optional(),forceClick:cf.boolean().optional()}),pf=cf.object({success:cf.boolean(),status:cf.enum(df),requested:mf,current:uf.optional(),selected:uf.optional(),options:cf.array(uf),matchedCount:cf.number(),error:cf.string().optional()}),gf=cf.object({jobRef:cf.string().regex(gg,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:cf.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:cf.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:cf.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:cf.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:cf.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:cf.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 至少需要提供一个"}),ff=900,hf=180,yf=1400;function bf(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...lf}}function vf(e){if(void 0!==e.jobRef){const t=kg(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 wf(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 xf(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function Sf(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:xf(e.current)}:{},...void 0!==e.selected?{selected:xf(e.selected)}:{},options:e.options.map(xf),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Rf=sf({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:gf,output:pf,execute:async(e,t)=>{const n=bf();let r,a;try{const o=vf(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 wn(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:ff,pressDurationMs:hf,settleMs:yf,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),Sf(i)}catch(t){const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),Sf({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:wf(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as If}from"@roll-agent/sdk";import{z as Cf}from"zod";var kf,Af=Cf.object({index:Cf.number(),candidateRef:Cf.string(),candidateName:Cf.string(),candidateId:Cf.string(),success:Cf.boolean(),error:Cf.string().optional()}),_f=Cf.object({success:Cf.boolean(),results:Cf.array(Af),summary:Cf.object({total:Cf.number(),succeeded:Cf.number(),failed:Cf.number()})}),Ef=[1400,1800,2200],Nf=450,Mf=140,Pf=750,Tf=Cf.object({indices:Cf.array(Cf.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Cf.array(Cf.string().regex(pg,"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 Bf(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...kf}}var Of=If({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Tf,output:_f,execute:async(e,t)=>{const n=Ag(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=Bf();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=Ef[(e-1)%Ef.length]??Ef[0];await r.sleep(t)}if(Df(s)){const e=await a.inspectRecommendCard(s.index);if(!_g(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:Nf,pressDurationMs:Mf,settleMs:Pf,...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}:{}}),Sc(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 Df(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as $f}from"@roll-agent/sdk";import{z as jf}from"zod";var Lf="recommendFrame",Ff="recommend",qf="#recommendFrame",zf=".candidate-card-wrap",Vf="[data-geek], .geek-item",Jf=`${zf}, ${Vf}`,Uf="[data-geek], .card-inner, .geek-item",Hf="[data-geek]",Wf=".name",Kf=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],Gf=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Yf=".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog",Xf=".boss-popup__wrapper",Zf='iframe[src*="c-resume"]',Qf="canvas#resume, div#resume canvas";function eh(e){return e.hasNamedRecommendFrame?"named-frame":e.hasRecommendUrlFrame?"recommend-url-frame":"main-page"}function th(e){return e>0?zf:Vf}function nh(e){return{candidateId:(e.ownDataGeek??e.childDataGeek??"").trim(),name:(e.nameText??"").trim()}}function rh(e){const t=(e.recommendFrameRect?.x??0)+(e.resumeFrameRect?.x??0),n=(e.recommendFrameRect?.y??0)+(e.resumeFrameRect?.y??0);return{x:Math.round(t+e.canvasRect.x),y:Math.round(n+e.canvasRect.y),width:Math.round(e.canvasRect.width),height:Math.round(e.canvasRect.height)}}function ah(e){return e.evaluate(e=>document.querySelectorAll(e).length,zf).then(th)}function oh(e){const t=e.frame(Lf),n=e.frames().find(e=>e.url().includes(Ff)),r=eh({hasNamedRecommendFrame:null!==t,hasRecommendUrlFrame:void 0!==n});return"named-frame"===r?t??e:"recommend-url-frame"===r?n??e:e}async function ih(e,t=1e4){try{return await e.waitForSelector(Jf,{timeout:t}),!0}catch{return!1}}async function sh(e,t){const n=await ah(e),r=e.locator(n);if(await r.count()<=t)return{found:!1,cardSelector:n,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const a=r.nth(t),o=await a.evaluate((e,t)=>{const n=e.getAttribute("data-geek")??e.querySelector(t.candidateIdSelector)?.getAttribute("data-geek")??"",r=e.querySelector(t.candidateNameSelector)?.textContent?.trim()??"",a=e.querySelector("button.btn.btn-greet");return{candidateId:n,name:r,hasGreetButton:null!==a&&a.offsetWidth>0}},{candidateIdSelector:Hf,candidateNameSelector:Wf}),i=nh({ownDataGeek:o.candidateId,nameText:o.name});return{found:!0,cardSelector:n,candidateId:i.candidateId,name:i.name,hasGreetButton:o.hasGreetButton}}var ch=jf.object({success:jf.boolean(),candidateRef:jf.string().optional(),candidateName:jf.string(),candidateId:jf.string(),error:jf.string().optional()}),lh=jf.object({index:jf.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:jf.string().regex(pg,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef"),dh=$f({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:lh,output:ch,execute:async(e,t)=>{const n=uh(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${r}`);const o=ue(),i=await o.getPage("zhipin"),s=oh(i);if(!await ih(s))return{success:!1,candidateRef:a,candidateName:"",candidateId:"",error:"推荐列表未加载"};const c=await sh(s,r);if(!c.found)return{success:!1,candidateRef:a,candidateName:"",candidateId:"",error:c.error??`候选人引用 ${a} 超出范围`};if(mh(n)&&!_g(n,c))return{success:!1,candidateRef:a,candidateName:c.name,candidateId:c.candidateId,error:`候选人引用 ${a} 已过期,请重新获取推荐列表`};const l=s.locator(c.cardSelector).nth(r),d=await l.locator(Uf).count()>0?l.locator(Uf).first():l;return await d.scrollIntoViewIfNeeded(),await Ke(i,d,{target:s}),await d.hover(),await Si(i,200,400),await Ge(i,d,{target:s}),await d.click(),await Si(i,1e3,2e3),t.logger.info(`Opened resume for ${c.name}`),{success:!0,candidateRef:a,candidateName:c.name,candidateId:c.candidateId}}});function uh(e){if(void 0!==e.candidateRef)return Cg(e.candidateRef);const t=Sg(e);return{index:t,candidateRef:bg(t),candidateId:""}}function mh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as ph}from"@roll-agent/sdk";import{z as gh}from"zod";var fh=gh.object({x:gh.number(),y:gh.number(),width:gh.number(),height:gh.number()}),hh=gh.object({success:gh.boolean(),screenshotArea:fh.optional(),canvasInfo:gh.object({width:gh.number(),height:gh.number()}).optional(),error:gh.string().optional()}),yh=ph({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:gh.object({}),output:hh,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=ue(),r=await n.getPage("zhipin");try{const e=r.frame(Lf)??r.frames().find(e=>e.url().includes(Ff));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$(Zf);if(!n)return{success:!1,error:"未找到简历 iframe"};const a=await n.contentFrame();if(!a)return{success:!1,error:"无法访问简历 iframe 内容"};try{await a.waitForSelector(Qf,{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await a.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{width:t.width,height:t.height,clientWidth:n.width,clientHeight:n.height,x:n.x,y:n.y}},Qf);if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await r.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},qf),s=rh({recommendFrameRect:i,resumeFrameRect:await e.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},Zf),canvasRect:{x:o.x,y:o.y,width:o.clientWidth,height:o.clientHeight}});return t.logger.info(`Canvas located at (${s.x}, ${s.y})`),{success:!0,screenshotArea:s,canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as bh}from"@roll-agent/sdk";import{z as vh}from"zod";var wh=vh.object({success:vh.boolean(),closed:vh.boolean(),error:vh.string().optional()}),xh=bh({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:vh.object({}),output:wh,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=ue(),r=await n.getPage("zhipin"),a=r.frame(Lf)??r.frames().find(e=>e.url().includes(Ff));if(!await(async()=>{if(a)for(const e of Kf){const t=a.locator(e).first();if(await t.isVisible())return await Ke(r,t,{target:a}),await Ge(r,t,{target:a}),await t.click(),!0}for(const e of Gf){const t=r.locator(e).first();if(await t.isVisible())return await Ke(r,t),await Ge(r,t),await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await r.waitForTimeout(300);const e=a?await a.$(Yf):await r.$(Xf);if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as Sh}from"@roll-agent/sdk";import{z as Rh}from"zod";import{waitForSelector as Ih}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 kh,waitForSelector as Ah}from"@roll-agent/browser";var _h="https://www.yupao.com",Eh=`${_h}/chat`,Nh=`${_h}/login`;async function Mh(e,t={}){e.url().includes("/chat")||await kh(e,Eh,t),await Ah(e,Ch.messageList.container,{timeout:15e3})}async function Ph(e,t,n={}){const r=`${_h}/chat?id=${encodeURIComponent(t)}`;await kh(e,r,n),await Ah(e,Ch.chat.input,{timeout:15e3})}async function Th(e,t){await Mh(e),await Ih(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 Bh=Rh.object({limit:Rh.number().optional().describe("最多返回的消息条数")}),Oh=Rh.object({conversationId:Rh.string(),candidateName:Rh.string(),lastMessage:Rh.string(),unreadCount:Rh.number(),timestamp:Rh.string()}),Dh=Rh.object({messages:Rh.array(Oh),total:Rh.number()}),$h=Sh({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Bh,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 Th(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});import{defineTool as jh}from"@roll-agent/sdk";import{z as Lh}from"zod";import{BrowserActionApprovalSchema as Fh}from"@roll-agent/browser";import{clickElement as qh,isBrowserActionPolicyError as zh,typeText as Vh,waitForSelector as Jh}from"@roll-agent/browser";async function Uh(e,t,n,r={}){try{return await Ph(e,t,r),await Vh(e,Ch.chat.input,n,r),await qh(e,Ch.chat.sendButton,r),await Jh(e,Ch.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(zh(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var Hh=Lh.object({conversationId:Lh.string().describe("对话 ID"),message:Lh.string().describe("要发送的回复消息"),browserActionApproval:Fh.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Wh=Lh.object({success:Lh.boolean(),conversationId:Lh.string(),sentMessage:Lh.string(),error:Lh.string().optional()}),Kh=jh({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:Hh,output:Wh,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=hn(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 Uh(s,n,r,fn(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=yn(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 Gh,BrowserRuntimeModeSchema as Yh,BrowserChannelSchema as Xh,BrowserProfileColorSchema as Zh,BrowserWindowBoundsSchema as Qh,PlatformSchema as ey}from"@roll-agent/browser";import{z as ty}from"zod";var ny=ty.object({platform:ey.optional(),mode:Yh.default("managed-cdp"),headless:ty.boolean().optional(),cdpUrl:ty.string().optional(),cdpHost:ty.string().default("127.0.0.1"),cdpPort:ty.number().int().min(1).max(65535).optional(),channel:Xh.default("chrome"),executablePath:ty.string().optional(),userDataDir:ty.string().trim().min(1),sessionsDir:ty.string().trim().min(1).optional(),args:ty.array(ty.string()).optional(),profileName:ty.string().trim().min(1).optional(),profileColor:Zh.optional(),windowBounds:Qh.optional(),trackingAgentId:ty.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:ty.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:ty.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),ry=ty.object({defaultInstance:ty.string().trim().min(1).optional(),instances:ty.record(ty.string(),ny).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:ty.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:ty.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:ty.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function ay(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 oy(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 iy(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 sy(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 cy(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 ry.parse(t)}function ly(e=process.env){return Gh.parse({mode:e.BROWSER_MODE,headless:ay(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:oy(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:iy(e.BROWSER_ARGS_JSON),profileColor:e.BROWSER_PROFILE_COLOR,sessionsDir:e.BROWSER_SESSIONS_DIR,security:sy(e.BROWSER_SECURITY_JSON)})}function dy(e=process.env){return cy(e.BROWSER_INSTANCES_JSON)}import{z as uy}from"zod";import{StructuredToolError as my}from"@roll-agent/sdk";var py=new Map;async function gy(e,t,n={}){const{signal:r,onWait:a}=n;yy(e,r,0);const o=py.get(e);let i;const s=new Promise(e=>{i=e});py.set(e,s);const c=performance.now();if(void 0!==o)try{await hy(o,r)}catch(t){throw o.then(()=>{i(),vy(e,s)}),t instanceof fy&&by(e,wy(c)),t}try{return void 0!==o&&a?.(wy(c)),yy(e,r,wy(c)),await t()}finally{i(),vy(e,s)}}var fy=class extends Error{constructor(){super("Aborted while queued"),this.name="QueueAbortError"}};async function hy(e,t){if(void 0!==t){if(t.aborted)throw new fy;await new Promise((n,r)=>{const a=()=>{r(new fy)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new fy):n()})})}else await e}function yy(e,t,n){!0===t?.aborted&&by(e,n)}function by(e,t){throw new my({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 vy(e,t){py.get(e)===t&&py.delete(e)}function wy(e){return performance.now()-e}import{StructuredToolError as xy}from"@roll-agent/sdk";function Sy(e){if(void 0===e)return;const t=ge().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new xy({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 Ry(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var Iy={browserInstance:uy.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 ky(e,t={}){const n=_y(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(),Sy(Ay(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 gy(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 Ay(e,t){const n=Ry(e);return t??n}function _y(e){if(e instanceof uy.ZodObject)return e.extend(Iy);if(e instanceof uy.ZodEffects){const t=_y(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?uy.ZodEffects.createWithPreprocess(n.transform,t,e._def):uy.ZodEffects.create(t,n,e._def)}}var Ey=t("browser-use-agent");async function Ny(){const e=(await Promise.allSettled([rm(),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 My(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e}var Py=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function Ty(e){return My(e.name)?e:ky(e,{startRuntime:"browser_status"!==e.name,serializePageOps:!Py.has(e.name)})}var By=e({name:"browser-use-agent",tools:[At,Xt,$n,nr,mr,vr,Ea,za,Ya,bi,Mc,zc,Kc,_l,Jd,cu,cp,wp,kp,$p,of,Xg,Rf,og,$g,Of,dh,yh,xh,$h,Kh,xe,Ot].map(Ty)},{onShutdown:Ny});async function Oy(){xt(wt()),nm({logger:Ey}),await le(ly(),dy());try{await _m(),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 By.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Oy().catch(async e=>{Ey.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await Ny().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 I(){return b??=A(),b}function C(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=C(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=_(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=D();if(void 0!==e)return e}if("win32"===process.platform){const e=O();if(void 0!==e)return e}return v}function _(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(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 E(y("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function O(){try{return M(y("powershell.exe",["-NoProfile","-NonInteractive","-Command",R],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function D(){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 $,L,j,F,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:I()})}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($||L)return;se=!1;const n=($=new H(e,t)).getDefaultInstanceId(),r=$.listBundles()[0],a=void 0!==n?$.getBundle(n):r;void 0!==a&&(L=a.runtime,j=a.contextManager,F=a.sessionStore)}function de(){if($)return $.getBundle().runtime;if(!L)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return L}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=L,r=[];if($=void 0,j=void 0,L=void 0,F=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 Ie,BrowserStatusSchema as Ce}from"@roll-agent/browser";import{createHash as ke}from"node:crypto";import{z as Ae}from"zod";var _e=["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"],Ee=/^[0-9a-f]{8}$/,Ne=Ae.object({present:Ae.boolean(),fingerprint:Ae.string().regex(Ee).optional()}),Me=Ae.record(Ne);function Pe(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:Te(n)}]:[e,{present:!1}]}))}function Te(e){return ke("sha256").update(e).digest("hex").slice(0,8)}var Be,Oe,De=180,$e=60,Le=280,je="roll-agent-visual-cursor-root",Fe="__rollVisualCursorState";function qe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function ze(){return void 0!==Be?Be:qe(process.env.BROWSER_VISUAL_CURSOR)??!0}async function Ve(e){return await e.evaluate(e=>{const t=e.getBoundingClientRect();return t.width<=0||t.height<=0?null:{x:Math.round(t.left+t.width/2),y:Math.round(t.top+t.height/2)}})}async function Je(e,t){await e.evaluate(e=>{const t="roll-agent-visual-cursor-root",n="roll-agent-visual-cursor-pointer",r="__rollVisualCursorState",a=(()=>{const e=document.getElementById(t);if(e)return e;const r=document.createElement("div");r.id=t,r.style.position="fixed",r.style.left="0",r.style.top="0",r.style.width="0",r.style.height="0",r.style.pointerEvents="none",r.style.zIndex="2147483647";const a=document.createElement("div");return a.id=n,a.setAttribute("aria-hidden","true"),a.style.position="fixed",a.style.left="0",a.style.top="0",a.style.width="24px",a.style.height="24px",a.style.opacity="0",a.style.transform="translate(-9999px, -9999px)",a.style.willChange="transform, opacity",a.style.filter="drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))",a.innerHTML='<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/></svg>',r.append(a),document.documentElement.append(r),r})(),o=a.querySelector(`#${n}`);if(!o)return;const i=window[r],s=e.point,c=e.durationMs,l=s.x-2,d=s.y-2;if(i?o.style.transition=`transform ${c}ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease`:(o.style.transition="opacity 120ms ease",o.style.transform=`translate(${l}px, ${d}px)`),o.style.opacity="1",o.style.transform=`translate(${l}px, ${d}px)`,window[r]={x:s.x,y:s.y},!e.clickPulse)return;const u=document.createElement("div");u.setAttribute("aria-hidden","true"),u.style.position="fixed",u.style.left="0",u.style.top="0",u.style.width="18px",u.style.height="18px",u.style.borderRadius="9999px",u.style.border="2px solid rgba(20, 184, 166, 0.95)",u.style.background="rgba(20, 184, 166, 0.18)",u.style.pointerEvents="none",u.style.opacity="0.9",u.style.transform=`translate(${s.x-9}px, ${s.y-9}px) scale(0.55)`,u.style.transition=`transform ${e.pulseDurationMs}ms ease, opacity ${e.pulseDurationMs}ms ease`,a.append(u),requestAnimationFrame(()=>{u.style.opacity="0",u.style.transform=`translate(${s.x-18}px, ${s.y-18}px) scale(2)`}),globalThis.setTimeout(()=>{u.remove()},e.pulseDurationMs+40)},{point:t.point,durationMs:t.durationMs,clickPulse:t.clickPulse,pulseDurationMs:t.pulseDurationMs})}function Ue(e){const t=[e],n="function"==typeof e.frames?e.frames():[];return t.push(...n),t}async function He(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:je,stateKey:Fe})}async function We(e,t={}){if(e.isClosed())return!1;let n=!1;for(const r of Ue(e))if(r!==t.preserveTarget)try{await He(r),n=!0}catch{}return n}async function Ke(e,t,n={}){if(!ze()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const r=await Ve(t);if(!r)return!1;const a=n.durationMs??De,o=n.settleMs??$e,i=n.target??e;return await We(e,{preserveTarget:i}),await Je(n.target??e,{point:r,durationMs:a,clickPulse:!1,pulseDurationMs:Le}),await e.waitForTimeout(Math.max(a+o,0)),!0}catch{return!1}}async function Ge(e,t,n={}){if(!ze()||e.isClosed())return!1;try{const r=await Ve(t);if(!r)return!1;const a=n.pulseDurationMs??Le,o=n.target??e;return await We(e,{preserveTarget:o}),await Je(o,{point:r,durationMs:0,clickPulse:!0,pulseDurationMs:a}),await e.waitForTimeout(a),!0}catch{return!1}}function Ye(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function Xe(){return void 0!==Oe?Oe:Ye(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Ze}from"@roll-agent/sdk";import{z as Qe}from"zod";import{randomUUID as et}from"node:crypto";import{z as tt}from"zod";var nt=tt.object({id:tt.string().trim().min(1)}),rt=new Map;function at(e=Date.now()){for(const[t,n]of rt)n.expiresAtMs<=e&&rt.delete(t)}function ot(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function it(e,t,n=Date.now()){at(n);const r=et(),a=n+t;return rt.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 st(e){return at(),!!ct(e)&&(rt.delete(e.approval.id),!0)}function ct(e){at();const t=rt.get(e.approval.id);return void 0!==t&&!!ot(e.subject,t)}var lt=["log","deny","confirm"],dt=Qe.enum(lt),ut=["zhipin_send_prepared_reply"],mt=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],pt=Qe.object({code:Qe.enum(mt),message:Qe.string()}),gt=Qe.object({policy:dt}),ft=Qe.object({approvalTtlMs:Qe.number().int().positive().max(36e5).default(3e5),tools:Qe.record(gt).default({})}),ht=ft.parse({}),yt=new Set(ut),bt=ht;function vt(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 wt(e=process.env){try{return ft.parse(vt(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 xt(e){bt=ft.parse(e)}function St(){return bt}function Rt(e){const t=e.toolPolicy??bt,n=[];for(const e of Object.keys(t.tools))yt.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 It(e,t){const n=bt.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(Ct(t.subject,"tool_policy_deny"))}`),new Ze({code:"action_denied",message:n,details:Ct(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?ct({approval:t.approval,subject:t.subject}):st({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&&st({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...Ct(t.subject,"tool_policy_confirm"),approvalRequest:it(t.subject,bt.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(Ct(t.subject,"tool_policy_confirm"))}`),new Ze({code:"needs_confirmation",message:a,details:o})}function Ct(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var kt=Ce.extend({replyAuthorityKeysLoaded:Re.boolean(),visualCursorEnabled:Re.boolean(),visualActivityEnabled:Re.boolean(),security:Ie,toolPolicy:ft,policyWarnings:Re.array(pt),effectiveEnvSources:Me}),At=Se({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:Re.object({}),output:kt,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=St(),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:ze(),visualActivityEnabled:Xe(),security:l,toolPolicy:d,policyWarnings:Rt({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:Pe(_e)}}});import{BrowserRuntimeModeSchema as _t}from"@roll-agent/browser";import{StructuredToolError as Et,defineTool as Nt}from"@roll-agent/sdk";import{z as Mt}from"zod";var Pt=Mt.object({browserInstance:Mt.string().trim().min(1).optional(),browserInstances:Mt.array(Mt.string().trim().min(1)).min(1).optional(),all:Mt.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:Mt.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),Tt=Mt.object({browserInstance:Mt.string(),status:Mt.enum(J),mode:_t.optional(),message:Mt.string().optional()}),Bt=Mt.object({ok:Mt.boolean(),stopped:Mt.number().int().nonnegative(),results:Mt.array(Tt)}),Ot=Nt({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:Pt,output:Bt,execute:async(e,t)=>{const n=ge(),r=Dt(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return $t(await n.closeInstances(r))}});function Dt(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 Et({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function $t(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 Lt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as jt,PlatformSchema as Ft}from"@roll-agent/browser";import{z as qt}from"zod";import{PLATFORMS as zt}from"@roll-agent/browser";var Vt={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Jt(e){return new URL(Vt[e]).host}function Ut(e,t){try{return new URL(e).host.includes(Jt(t))}catch{return!1}}function Ht(e){return zt.find(t=>Ut(e,t))}function Wt(e,t){const n=Ht(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 Kt(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:Ht(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var Gt=qt.object({platform:Ft.optional().describe("可选:仅返回指定平台相关的页面")}),Yt=qt.object({pages:qt.array(jt)}),Xt=Lt({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:Gt,output:Yt,execute:async(e,t)=>{const n=ue();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>Wt(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as Zt}from"node:timers/promises";import{defineTool as Qt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as en,BrowserPageInfoSchema as tn}from"@roll-agent/browser";import{z as nn}from"zod";import{StructuredToolError as rn}from"@roll-agent/sdk";import{isBrowserActionPolicyError as an,preflightBrowserAction as on}from"@roll-agent/browser";import{randomUUID as sn}from"node:crypto";var cn=6e4,ln=new Map;function dn(e=Date.now()){for(const[t,n]of ln)n.expiresAtMs<=e&&ln.delete(t)}function un(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function mn(e,t=Date.now()){dn(t);const n=sn(),r=t+cn,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return ln.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 pn(e){dn();const t=ln.get(e.approval.id);return void 0!==t&&(!!un(e.details,t)&&(ln.delete(e.approval.id),!0))}function gn(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function fn(e,t,n={}){return{security:gn(t,n),approveAction:pn,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function hn(e,t,n){const r=on({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&&pn({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 rn({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:mn(r.details)}:{}}})}function yn(e){if(an(e))return new rn({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:mn(e.payload.details)}:{}}})}function bn(){try{return de()}catch{return}}async function vn(e){await e.bringToFront().catch(()=>{})}async function wn(e,t={}){const n=t.runtime??bn();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await vn(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await vn(e)}var xn,Sn=15e3,Rn=250,In=["/web/chat"],Cn=nn.object({url:nn.string().url().describe("要导航到的目标 URL"),browserActionApproval:en.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),kn=nn.object({success:nn.boolean(),page:tn});function An(){return{getContextManager:ue,getRuntime:de,detectPlatformFromUrl:Ht,matchesPlatformHost:Ut,toNativePageInfo:Wt,delay:Zt,...xn}}function _n(e){return"object"==typeof e&&null!==e}function En(e){return!!_n(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function Nn(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==In.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function Mn(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 Pn(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!En(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function Tn(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<Sn;){try{const t=await Pn(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Bn(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(Rn)}throw new Error(`Native navigation did not reach document ready state within ${Sn}ms${o?`: ${o.message}`:""}`)}function Bn(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 On(e,t,n){t.url!==n&&await e.navigate(n)}async function Dn(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var $n=Qt({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:Cn,output:kn,execute:async(e,t)=>{const n=An(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);Nn(e.url,o);const i=hn(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 Mn(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...fn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await On(c,s.page,e.url);const o=await Tn(c,n,e.url,s.page.url),i=await Dn(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 Ln,BrowserPageInfoSchema as jn}from"@roll-agent/browser";import{defineTool as Fn}from"@roll-agent/sdk";import{z as qn}from"zod";import{setTimeout as zn}from"node:timers/promises";var Vn=15e3,Jn=250,Un="__rollReloadMark";function Hn(e){return"object"==typeof e&&null!==e}function Wn(e){if(!Hn(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function Kn(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function Gn(e){const t=await e.evaluateJson(`(() => ({ mark: window.${Un} ?? null, readyState: document.readyState }))()`);if(!Wn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function Yn(e,t){const n=(t.createToken??Kn)(),r=t.now??(()=>Date.now()),a=t.delay??zn,o=t.timeoutMs??Vn,i=t.pollMs??Jn;await e.evaluateJson(`(() => { window.${Un} = ${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 Gn(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 Xn,Zn=qn.object({ignoreCache:qn.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:Ln.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Qn=qn.object({success:qn.boolean(),reloaded:qn.boolean(),page:jn});function er(){return{getContextManager:ue,getRuntime:de,toNativePageInfo:Wt,reloadNativePageAndWaitForSwap:Yn,...Xn}}function tr(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 nr=Fn({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:Zn,output:Qn,execute:async(e,t)=>{const n=er(),r=n.getContextManager(),a=n.getRuntime(),o=tr(r,await a.listNativePages()),i=hn(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,{...fn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({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 rr}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as ar,BrowserPageInfoSchema as or,PlatformSchema as ir}from"@roll-agent/browser";import{z as sr}from"zod";async function cr(e,t){return(await e.listNativePages()).find(e=>Ut(e.url,t))}async function lr(e,t,n={}){const r=await cr(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(Vt[t],n),reusedExistingPage:!1}}var dr=sr.object({platform:ir.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:ar.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),ur=sr.object({success:sr.boolean(),page:or,reusedExistingTab:sr.boolean()}),mr=rr({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:dr,output:ur,execute:async(e,t)=>{const{platform:n}=e,r=de(),a=ue(),o=Vt[n],i=hn(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 lr(r,n,fn(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:Wt(a,s),reusedExistingTab:c}}});import{defineTool as pr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as gr,PlatformSchema as fr}from"@roll-agent/browser";import{z as hr}from"zod";var yr=hr.object({platform:fr.describe("要将该页面绑定为当前活跃页的平台"),pageId:hr.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),br=hr.object({success:hr.boolean(),page:gr}),vr=pr({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:yr,output:br,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:Wt(n,r)}}});import{defineTool as wr}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as xr,BrowserPageInfoSchema as Sr,createBrowserAxSnapshot as Rr}from"@roll-agent/browser";import{z as Ir}from"zod";import{BrowserElementRefStore as Cr}from"@roll-agent/browser";var kr=new Cr;async function Ar(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 _r}from"node:crypto";var Er="data-roll-browser-action-",Nr=120;function Mr(){return`${Er}${_r().replaceAll("-","")}`}function Pr(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),Nr)):Nr}function Tr(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Br(e){if(!Tr(e)||!Tr(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Or(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Dr(e){return Array.isArray(e)?e.flatMap(e=>{const t=$r(e);return void 0===t?[]:[t]}):[]}function $r(e){if(!Tr(e))return;const t=e.nodeId,n=e.backendNodeId,r=Or(e.attributes),a=$r(e.contentDocument),o=$r(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:Dr(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Dr(e.pseudoElements),shadowRoots:Dr(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function Lr(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 jr(e){if(!Tr(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 Fr(e){return Array.isArray(e)?e.flatMap(e=>{const t=jr(e);return void 0===t?[]:[t]}):[]}function qr(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function zr(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 Vr(e,t){const n=new Map;for(const r of t){const t=Lr(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Jr(e,t){const n=new Map,r=t=>{const a=Lr(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 Ur(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 Hr(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 Wr(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!Tr(n))return new Map;const r=$r(n.root);return void 0===r?new Map:Jr(t,r)}const r=Br(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Vr(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function Kr(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=Pr(n.maxCandidates??Nr);if(0===r)return[];const a=Mr(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(Er.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=Fr(await e.evaluateJson(Ur(a,r),i).catch(()=>[]));try{if(0===s.length)return[];const t=await Wr(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:qr(e),name:e.name,hints:zr(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Hr(a),i).catch(()=>{})}}var Gr=28,Yr=90,Xr=250,Zr=620,Qr=8,ea=32,ta=8;function na(e){return new Promise(t=>{setTimeout(t,e)})}function ra(e,t,n){return Math.min(Math.max(e,t),n)}function aa(e,t){return Math.max(0,Math.floor(e??t))}function oa(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function ia(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function sa(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function ca(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 la(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function da(e,t){const n=ia(e,t);if(n<=Qr)return[e,t];const r=ra(Math.round(n/30),ta,ea),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=ra(.08*n,4,28)*la(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=sa(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 ua(e,t){const n=oa(t);return da(void 0===e?ca(n):oa(e),n)}var ma=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??na,this.defaultStepDelayMs=aa(t.stepDelayMs,Gr)}reset(e){this.lastPoint=void 0===e?void 0:oa(e)}async moveTo(e,t={}){const n=aa(t.stepDelayMs,this.defaultStepDelayMs),r=ua(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=oa(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:oa(e),durationMs:Zr})),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??Yr),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??Xr),this.lastPoint=oa(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??Yr),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=oa(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},pa=14,ga=720;function fa(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 ha=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!Xe()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...Xe()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??pa,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){ze()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){ze()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=ga){return!!Xe()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=ga){return!!Xe()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!Xe()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(fa(e))}catch{return!1}}};function ya(e){return new ha(e)}async function ba(e,t,n){const r=new ma(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var va=Ir.object({pageId:Ir.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:Ir.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:Ir.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:Ir.boolean().default(!0).describe("默认 true:只返回可交互节点")}),wa=Ir.object({page:Sr,snapshot:xr});function xa(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 Sa(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 Ra(e,t){const n=xa(t);return(await Promise.all(n.map(async t=>{const n=await Sa(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function Ia(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function Ca(e){return e.refs.reduce((e,t)=>Math.max(e,Ia(t.ref)),0)}function ka(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ka(n.nodes,t)]})}function Aa(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=Aa(e.children??[],t),a=void 0===n?[]:Aa(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function _a(e){let t=Ca(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await Ra(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 Kr(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await Rr(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 Ra(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=Ca(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?ka(e.snapshot.nodes,s):Aa(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}var Ea=wr({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:va,output:wa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({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=ya(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await Kr(o,s),l=await Rr(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await _a({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return kr.saveSnapshot(a.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:Wt(r,a),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as Na,defineTool as Ma}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Pa,BrowserElementRefHandleSchema as Ta,clickElementRef as Ba}from"@roll-agent/browser";import{z as Oa}from"zod";import{BrowserElementRefHandleSchema as Da}from"@roll-agent/browser";import{z as $a}from"zod";var La=$a.enum(["backend_node_id","role_name_nth"]),ja=$a.object({ref:Da,role:$a.string(),name:$a.string(),x:$a.number(),y:$a.number(),resolvedBy:La,backendNodeId:$a.number().int().positive().optional(),frameId:$a.string().optional(),disabled:$a.boolean()}),Fa=$a.object({success:$a.literal(!0),ref:Da,resolvedBy:La,target:ja}),qa=Oa.object({ref:Ta.describe("browser_snapshot 返回的 @eN element ref"),pageId:Oa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Pa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),za=Ma({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:qa,output:Fa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=kr.getRef(a.targetId,e.ref);if(void 0===o)throw new Na({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=hn(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...fn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=ya(s);await t.begin(`正在点击 ${e.ref}`);const r=await Ba({controller:s,elementRef:o,options:{clickTarget:async e=>{await ba(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),r}catch(t){await ya(s).fail(`点击 ${e.ref} 失败`);const n=yn(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as Va,defineTool as Ja}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ua,BrowserElementRefHandleSchema as Ha,typeElementRef as Wa}from"@roll-agent/browser";import{z as Ka}from"zod";var Ga=Ka.object({ref:Ha.describe("browser_snapshot 返回的 @eN element ref"),text:Ka.string().describe("要输入的文本"),clear:Ka.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:Ka.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Ua.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Ya=Ja({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:Ga,output:Fa,execute:async(e,t)=>{const n=de(),r=ue(),a=await Ar({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=kr.getRef(a.targetId,e.ref);if(void 0===o)throw new Va({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=hn(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...fn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await wn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=ya(s);await t.begin(`正在输入到 ${e.ref}`);const r=await Wa({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await ba(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),r}catch(t){await ya(s).fail(`输入到 ${e.ref} 失败`);const n=yn(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as Xa}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Za}from"@roll-agent/browser";import{z as Qa}from"zod";var eo=["native","native-watch","native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],to=[...eo,"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"],no=Qa.enum(["localStorage","sessionStorage"]),ro=Qa.enum(["array","object","string","number","boolean","null"]),ao=Qa.enum(["empty","json","string"]),oo=Qa.enum(eo),io=Qa.enum(to),so=Qa.enum(["request","response"]),co=Qa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),lo=Qa.object({area:no,key:Qa.string(),valueLength:Qa.number().int().nonnegative(),valueKind:ao,jsonKind:ro.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()}),uo=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()}),mo=Qa.object({before:Qa.number().optional(),after:Qa.number().optional(),delta:Qa.number().optional()}),po=Qa.object({before:Qa.boolean().optional(),after:Qa.boolean().optional()}),go=Qa.object({before:Qa.number().int().nonnegative().optional(),after:Qa.number().int().nonnegative().optional(),delta:Qa.number().int().optional()}),fo=Qa.object({area:no,key:Qa.string(),beforePresent:Qa.boolean(),afterPresent:Qa.boolean(),numericDeltas:Qa.record(mo).optional(),booleanChanges:Qa.record(po).optional(),arrayLengthDeltas:Qa.record(go).optional()}),ho=Qa.object({url:Qa.string(),title:Qa.string(),visibilityState:Qa.string(),hasFocus:Qa.boolean()}),yo=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())}),bo=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()}),vo=Qa.object({type:Qa.literal("mouseMoved"),x:Qa.number(),y:Qa.number()}),wo=Qa.object({targetId:Qa.string(),websocketUrlAvailable:Qa.boolean(),connected:Qa.boolean(),pageBroughtToFront:Qa.boolean().optional(),runtimeEnabled:Qa.boolean().optional(),evaluate:ho.optional(),dom:bo.optional(),input:vo.optional()}),xo=Qa.object({phase:oo,success:Qa.boolean(),durationMs:Qa.number().int().nonnegative(),error:Qa.string().optional()}),So=Qa.object({phase:io,capturedAt:Qa.string(),targetFound:Qa.boolean(),page:Za.optional(),urlChangedFromPrevious:Qa.boolean(),titleChangedFromPrevious:Qa.boolean(),previousUrl:Qa.string().optional(),currentUrl:Qa.string().optional(),previousTitle:Qa.string().optional(),currentTitle:Qa.string().optional()}),Ro=Qa.object({kind:so,reason:co,capturedAt:Qa.string(),url:Qa.string(),method:Qa.string().optional(),resourceType:Qa.string().optional(),status:Qa.number().int().optional()}),Io=Qa.object({capturedAt:Qa.string(),url:Qa.string()}),Co=Qa.object({phase:oo.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;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 事件数。")}),ko=Qa.object({success:Qa.boolean(),requestedPhase:oo,mode:Qa.string(),nativePages:Qa.array(Za),targetPage:Za.optional(),browserAttached:Qa.boolean(),pageAttached:Qa.boolean(),nativeTimeline:Qa.array(So),networkEvents:Qa.array(Ro).optional(),navigationEvents:Qa.array(Io).optional(),nativeCdp:wo.optional(),evaluate:ho.optional(),detectorFingerprint:yo.optional(),storage:Qa.object({localStorage:Qa.array(lo),sessionStorage:Qa.array(lo),cookies:Qa.array(uo),counterDiffs:Qa.array(fo)}).optional(),phases:Qa.array(xo),warnings:Qa.array(Qa.string())}),Ao=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],_o=new Set(Ao),Eo=["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"],No=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],Mo={"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 Po(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 To(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Bo(e){return Eo.includes(e)}function Oo(e){return No.includes(e)}function Do(e){try{return JSON.parse(e)}catch{return}}function $o(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 Lo(e){return new Promise(t=>{setTimeout(t,e)})}function jo(e){return _o.has(e)}function Fo(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=Do(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=Po(n),a=To(n)&&jo(t.key)?$o(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,...To(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function qo(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 zo(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 Vo(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?Ut(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=>Ut(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 Jo(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:Wt(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 Uo(e,t,n,r){const a=await e.listNativePages();return Jo(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>Ut(e.url,"zhipin"))[0],r)}async function Ho(e,t,n,r,a){try{const o=t[t.length-1],i=await Uo(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 Wo(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await Ho(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await Lo(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Ko(e){if(!To(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 Go(e,t){if(!To(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!To(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=To(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 Yo(e){return Ko(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Xo(e){return Go(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 Zo(e){return Go(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Qo(e){return await e.bringToFront(),!0}async function ei(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 ti(e,t,n,r,a,o){await Ho(e,t,n,r,o);const i=await Wo(e,t,Mo[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 ni(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await zo(e,async()=>await Qo(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 zo(e,async()=>await Yo(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 zo(e,async()=>await Zo(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 zo(e,async()=>await ei(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function ri(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 zo("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 ti(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(Oo(e.requestedPhase))return await ni(e.requestedPhase,o,e.phases,t),i=await ti(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await zo("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await ti(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 zo("native-evaluate-url",async()=>await Yo(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await ti(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 zo("native-dom-read",async()=>await Xo(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await ti(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function ai(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 oi(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=ai(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=ai(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 Lo(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function ii(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Ut(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function si(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function ci(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 li(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},Ao)).map(e=>Fo(e.area,{key:e.key,value:e.value}))}async function di(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function ui(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 mi(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function pi(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function gi(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function fi(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function hi(e,t){const n=mi(e),r=mi(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 pi(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=gi(n,r))}const c={};for(const e of pi(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 pi(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=fi(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 yi(e,t){const[n,r,a]=await Promise.all([ci(e,"localStorage"),ci(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>jo(e.key)).map(e=>Fo("localStorage",e)),...r.filter(e=>jo(e.key)).map(e=>Fo("sessionStorage",e))];return{localStorage:n.map(e=>Fo("localStorage",e)),sessionStorage:r.map(e=>Fo("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>qo(e)),counterDiffs:hi(t,o)}}var bi=Xa({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:Co,output:ko,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 zo("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>Wt(o,e)),v=Vo(y,e.targetPageId,s);if(v&&(d=Wt(o,v),f=v.targetId,l.push(Jo(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 Wo(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(Bo(n)){const e=await ri({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}}const w=await zo("browser-attach",async()=>await i.getBrowser());c.push(w.phaseResult);const x=w.result;p=w.phaseResult.success,await Ho(o,l,"browser-attach",f,s);const S=await Wo(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 I;"network-watch"===n&&void 0!==x&&(I=ii(x,v),I?R.push(oi(I,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",v);const C=await zo("page-attach",async()=>await o.getPage("zhipin"));c.push(C.phaseResult);const k=C.result;if(g=C.phaseResult.success,k&&(d=await Kt(o,k)),await Ho(o,l,"page-attach",f,s),!C.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await zo("network-watch",async()=>si(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){I!==k&&R.push(oi(k,r,a));const e=await zo("network-watch",async()=>si(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await Ho(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 li(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await Lo(r)}const _=await zo("page-evaluate",async()=>await di(k));c.push(_.phaseResult);const E=_.result;if(await Ho(o,l,"page-evaluate",f,s),!_.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!==E?{evaluate:E}:{},phases:c,warnings:s};const N=await zo("detector-fingerprint",async()=>await ui(k));c.push(N.phaseResult);const M=N.result;if(await Ho(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!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await zo("storage-summary",async()=>await yi(k,A));c.push(P.phaseResult);const T=P.result;return await Ho(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!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as vi}from"@roll-agent/sdk";import{z as wi}from"zod";import{setTimeout as xi}from"node:timers/promises";async function Si(e,t=300,n=800){const r=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(r)}function Ri(e){return e.trim().toLocaleLowerCase("zh-CN")}function Ii(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function Ci(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=Ri(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>Ri(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=Ri(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=Ri(e.name);return Ii(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var ki=["chat-list","chat-history","recommend-list"],Ai={"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 _i(e){return Ai[e]}var Ei={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"}},Ni=["不限","男","女"],Mi=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],Pi=["patch","replace"],Ti=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:Ni},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:Mi},{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:"不限"}],Bi=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function Oi(e){const t=Ti.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function Di(e){return void 0!==e.ageMin||void 0!==e.ageMax}var $i=30,Li=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function ji(e){const t=e.trim();return!(0===t.length||t.length>$i)&&(!Li.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Fi(e){let t=e.priority;return Li.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 qi(e){const t=e.filter(e=>ji(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=Fi(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 zi='.user-list.b-scroll-stable [role="listitem"], .geek-item',Vi='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',Ji=".candidate-card-wrap",Ui="[data-geek], .geek-item",Hi=`${Ji}, ${Ui}`,Wi=["/web/chat/recommend"],Ki=250,Gi=300,Yi=12,Xi=900,Zi=1200,Qi=4,es=4,ts=250,ns=90,rs=520,as=16,os=50,is=650,ss=10,cs=.015,ls="button, a, label, li, span, div, [role='button'], [role='radio']",ds=["visible","hidden","prerender","unloaded"],us=["not_chat_page"];function ms(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 ps(e){return new Promise(t=>{setTimeout(t,e)})}function gs(e){return{x:e.x,y:e.y}}function fs(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function hs(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function ys(e){return e.trim().toLocaleLowerCase("zh-CN")}function bs(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function vs(e,t){const n=ys(e),r=ys(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function ws(e){return"string"==typeof e?e:""}function xs(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function Ss(e){return"boolean"==typeof e&&e}function Rs(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{};return{conversationId:ws(n.conversationId),candidateId:ws(n.candidateId),name:ws(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:ws(n.position),hasUnread:Ss(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:ws(n.lastMessageTime),messagePreview:ws(n.messagePreview)}}):[]}function Is(e){return Array.isArray(e)?e.flatMap(e=>{if(!fs(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 Cs(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:ws(n.candidateId),name:ws(n.name),age:ws(n.age),experience:ws(n.experience),education:ws(n.education),workStatus:ws(n.workStatus),company:ws(n.company),currentPosition:ws(n.currentPosition),expectedLocation:ws(n.expectedLocation),expectedPosition:ws(n.expectedPosition),expectedSalary:ws(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>ws(e)).filter(Boolean):[],buttonText:ws(n.buttonText)}}):[]}function ks(e){return Array.isArray(e)?e.map((e,t)=>{const n=fs(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:ws(n.value),label:ws(n.label),isCurrent:Ss(n.isCurrent)}}):[]}function As(e){return fs(e)?{found:Ss(e.found),isOpen:Ss(e.isOpen),currentLabel:ws(e.currentLabel),currentValue:ws(e.currentValue),options:ks(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function _s(e){if(!fs(e))return null;const t=ws(e.conversationId),n=ws(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:ws(e.candidateName)}}function Es(e){if(!fs(e))return null;const t=ws(e.candidateName);return t.length>0?{candidateName:t}:null}function Ns(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!fs(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:ws(e.content),time:ws(e.time)}]}):[]}function Ms(e){const t=fs(e)?e:{},n=t.tags;return{name:ws(t.name),age:ws(t.age),experience:ws(t.experience),education:ws(t.education),communicationPosition:ws(t.communicationPosition),expectedJobText:ws(t.expectedJobText),expectedSalary:ws(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>ws(e)).filter(Boolean):[]}}function Ps(e){const t=fs(e)?e:{};return{selectedTarget:_s(t.selectedTarget),activePanel:Es(t.activePanel),candidateInfo:Ms(t.candidateInfo),messages:Ns(t.messages)}}function Ts(e){return fs(e)?{found:Ss(e.found),x:xs(e.x),y:xs(e.y)}:{found:!1,x:0,y:0}}function Bs(e){return fs(e)?{found:Ss(e.found),left:xs(e.left),top:xs(e.top)}:{found:!1,left:0,top:0}}function Os(e){return fs(e)?{found:Ss(e.found),cardSelector:ws(e.cardSelector)||Ji,candidateId:ws(e.candidateId),name:ws(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:Ss(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:Ji,candidateId:"",name:"",hasGreetButton:!1}}function Ds(e){return fs(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 $s(e){return fs(e)&&!0===e.ok?{ok:!0,current:Ds(e.current),trackLeft:xs(e.trackLeft),trackTop:xs(e.trackTop),trackWidth:xs(e.trackWidth),trackHeight:xs(e.trackHeight),minHandleX:xs(e.minHandleX),minHandleY:xs(e.minHandleY),maxHandleX:xs(e.maxHandleX),maxHandleY:xs(e.maxHandleY)}:{ok:!1,error:fs(e)&&ws(e.error)||"未找到年龄滑块"}}function Ls(e){return fs(e)?{containerFound:Ss(e.containerFound),containerLabel:ws(e.containerLabel),scrollTop:xs(e.scrollTop),scrollHeight:xs(e.scrollHeight),clientHeight:xs(e.clientHeight),itemCount:xs(e.itemCount),atStart:Ss(e.atStart),atEnd:Ss(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function js(e){return[e,...(e.childFrames??[]).flatMap(e=>js(e))]}function Fs(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 qs(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 zs(e){return ds.some(t=>t===e)}function Vs(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 Js(e,t,n){const r=t.filter(e=>Ut(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&hs(a.url))return a;const e=r.filter(e=>hs(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 Us(e={},t={}){const n=t.ctxManager??ue(),r=t.runtime??de(),a=Js(n,await n.listNativePages(),e),o=await r.connectNativePage(a);return new Hs({target:a,controller:o})}var Hs=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new ma(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 ps(Ki)}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 hs(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await Yn(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(Vi)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await ps(Ki)}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(Vi,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 ps(Ki)}for(let e=0;e<Yi&&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 ps(Gi),await a())return!0;if(e.after<=1)break}for(let e=0;e<=Yi&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=Yi)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await ps(Gi)}return await a()}async isRecommendSurfaceOpen(){const e=[Ei.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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 ps(Ki)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=js(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 Bs(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Ei.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=Ts(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Ts(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(gs(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??ns,settleMs:t.settleMs??ts}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(Hi)}) !== 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 ps(Ki)}else await ps(Ki);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=As(await this.evaluateJson(e).catch(()=>{}));return t.found?t:As(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 ps(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 ps(260),await this.selectAllFocusedText(),await ps(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await ps(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await ps(500),!0;await ps(350);for(const t of Array.from(e))await this.controller.insertText(t),await ps(110);return await ps(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=bs(e.jobName),a=t.filter(e=>bs(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>bs(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=bs(e.jobName),r=bs(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 ps(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 ps(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 ps(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 ps(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?Ei.nav.chatLink:Ei.nav.recommendLink,r=Ts(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(Ei.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=_i(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 ps(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 ps(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 ps(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=Fs(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await ps(Gi)}return Fs(r)}async openChat(e){if(await wn(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?Yi: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 ps(ts);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await ps(ts),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:Vs(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Fs(o),c=!0!==e.preferUnread||t?Ci(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 ps(Gi)}},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 ps(Gi)}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 _s(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 Es(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 ps(Ki)}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 Ps(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 = (${ms.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=Os(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=Os(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 exchangeWechat(e={}){const t=Ts(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=Ts(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 ps(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=Ts(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 ps(250);const r=Ts(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 ps(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(!Di(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(Di(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=qs(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<Qi;t+=1){await ps(Zi);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>=es){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:Xi});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 Is(await this.evaluateJson(`(() => {\n const limit = ${$i};\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([Ei.username.primary,...Ei.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(Ji)};\n const fallbackSelector = ${JSON.stringify(Ui)};\n const iframe = document.querySelector(${JSON.stringify(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\n const parseCandidateProfileTokens = ${ms.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 })()`}buildRecommendGreetClickExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(Ji)};\n const fallbackSelector = ${JSON.stringify(Ui)};\n const iframe = document.querySelector(${JSON.stringify(Ei.recommend.iframe)});\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cards =\n primaryCards.length > 0\n ? primaryCards\n : Array.from(root.querySelectorAll(fallbackSelector));\n const item = cards[index];\n if (!item) return { found: false, x: 0, y: 0 };\n const button =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n if (!button) return { found: false, x: 0, y: 0 };\n button.scrollIntoView({ block: "center", inline: "center" });\n const rect = button.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { found: false, x: 0, y: 0 };\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`}async waitForWechatExchangeDialog(e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const tooltip = document.querySelector(".exchange-tooltip");\n if (tooltip && visible(tooltip)) return true;\n for (const element of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = element.textContent ?? "";\n if (\n text.includes("交换微信") &&\n element.querySelector(".boss-btn-primary, .boss-btn") &&\n visible(element)\n ) {\n return true;\n }\n }\n return false;\n })()').catch(()=>!1))return!0;await ps(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(`${Ei.recommend.filterButton}, ${Ji}, ${Ei.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 ps(Ki)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Ei.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(Ei.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 ps(Ki)}}await ps(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(Ei.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(ls)};\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 ps(Ki)}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 ps(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 ps(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(Ei.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(as)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(os)} -\n ${JSON.stringify(as)})\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=Ds(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:Ds(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Ei.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=$s(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=$s(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??ns})}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 ps(is),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-as)/(os-as)))}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+cs),s="min"===e?Math.max(0,o-cs):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<ss;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??as,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(Ei.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(ls)};\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(Ti.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(Ei.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 ps(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Ei.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 ps(600),!0;await ps(Ki)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Ei.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 Rs(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(zi)}));\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&&vs(e.name,n.candidateName);if(r&&a)return{ready:!0};await ps(Ki)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:zs(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=Ts(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(zi)}));\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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\n const parseCandidateProfileTokens = ${ms.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(Ji)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(Ui)}));\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=Cs(await this.evaluateJson(e));if(t.length>0)return t;return Cs(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=_i(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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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=Ls(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=Ls(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=_i(e);await wn(this);const a=Ts(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(Ei.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(gs(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??rs):n??rs}),await ps(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=_i(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(Ei.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Wi)};\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=Ls(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=Ls(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)}}};import{createHash as Ws}from"node:crypto";var Ks,Gs,Ys={fetch:fetch,env:process.env},Xs="https://huajune.duliday.com",Zs=new Set;function Qs(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function ec(e,t){const n=Qs(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 tc(e){const t=ec(e,"RECRUITMENT_EVENTS_ENABLED"),n=Qs(e,"RECRUITMENT_EVENTS_API_BASE_URL")??Xs,r=Qs(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=Qs(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function nc(e,t){const n=ye(e.defaultAgentId);if(void 0!==n)return n;he()&&rc(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 rc(e,t,n){Zs.has(t)||(Zs.add(t),e.warn(n))}function ac(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function oc(e){return"object"==typeof e&&null!==e}function ic(e,t){const n=e[t];return"string"==typeof n?n:void 0}function sc(e){if(!oc(e)||!oc(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>oc(e)&&"error"===ic(e,"status"));if(!oc(t))return;const n=ic(t,"idempotencyKey"),r=t.error,a=oc(r)?ic(r,"code"):void 0,o=oc(r)?ic(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function cc(e,t,n){const r=tc(n.env);if(!r.enabled)return;const a=nc(r,t);if(void 0===r.apiToken)return void rc(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(he()||rc(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(ac(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=sc(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function lc(e,t){return`${e}:${Ws("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function dc(e,t){(async()=>{const n=Gs??(async(e,t)=>{await cc(e,t,{...Ys,...Ks})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function uc(){return(new Date).toISOString().slice(0,10)}function mc(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function pc(e,t,n){const r=e?.candidateInfo,a=mc(r?.age),o=mc(r?.education),i=mc(r?.expectedSalary);return{name:mc(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function gc(e){return mc(e?.candidateInfo.communicationPosition)??""}function fc(e){const t=mc(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 hc(e){const t=mc(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?mc(n[0]):void 0}function yc(e){return e.length>0?{job:{jobName:e}}:{}}function bc(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 wc(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&bc(n.name,n.position)&&dc({idempotencyKey:lc("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,uc()]),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 xc(e,t){const n=gc(e.candidateDetails),r=n,a=pc(e.candidateDetails,e.candidateName,r);if(!bc(a.name,a.position))return;const o=hc(e.candidateDetails);dc({idempotencyKey:lc("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...yc(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Sc(e,t){if(!e.clicked||0===e.name.length)return;const n=mc(e.expectedPosition);if(void 0===n||!bc(e.name,n))return;const r=mc(e.age),a=mc(e.education),o=mc(e.expectedSalary),i=mc(e.expectedLocation);dc({idempotencyKey:lc("zhipin-candidate-contacted",[e.candidateId,e.name,uc()]),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 Rc(e,t){const n=gc(e.candidateDetails),r=pc(e.candidateDetails,e.candidateName,n);if(!bc(r.name,r.position))return;const a=hc(e.candidateDetails);dc({idempotencyKey:lc("zhipin-wechat-requested",[e.conversationId,e.candidateId,uc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...yc(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 Ic(e,t,n,r){const a=gc(e),o=fc(e),i=hc(e),s=pc(e,e.candidateInfo.name,o);if(!bc(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=vc(c);dc({idempotencyKey:lc("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...yc(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var Cc,kc=wi.object({name:wi.string(),conversationId:wi.string(),candidateId:wi.string(),position:wi.string(),time:wi.string(),preview:wi.string(),unreadCount:wi.number(),hasUnread:wi.boolean(),index:wi.number()}),Ac=wi.object({success:wi.boolean(),candidates:wi.array(kc),total:wi.number(),stats:wi.object({withName:wi.number(),withUnread:wi.number()})});function _c(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Cc}}function Ec(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 Nc(e){const t=new Set,n=[];for(const r of e){const e=Ec(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var Mc=vi({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:wi.object({limit:wi.number().optional().describe("最多返回条数"),onlyUnread:wi.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:wi.enum(["time","unreadCount","name"]).default("time"),autoScroll:wi.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:wi.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Ac,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=_c();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=Nc(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)`),wc(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 Pc,BrowserPageInfoSchema as Tc}from"@roll-agent/browser";import{defineTool as Bc,StructuredToolError as Oc}from"@roll-agent/sdk";import{z as Dc}from"zod";var $c,Lc=Dc.object({forceReload:Dc.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:Dc.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:Pc.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),jc=Dc.object({success:Dc.boolean(),alreadyOnChat:Dc.boolean(),usedSidebarClick:Dc.boolean(),usedReload:Dc.boolean(),chatReady:Dc.boolean(),reloadSkippedReason:Dc.enum(us).optional(),page:Tc.optional(),error:Dc.string().optional()});function Fc(){return{getContextManager:ue,getRuntime:de,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...$c}}async function qc(e,t){return Wt(e,await t.inspectPage())}var zc=Bc({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:Lc,output:jc,execute:async(e,t)=>{const n=Fc(),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 wn(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 qc(r,a),error:s.error};hn(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 qc(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await qc(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(Ei.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 qc(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 qc(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 qc(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await qc(r,a),error:"沟通页未就绪"})}catch(e){if(e instanceof Oc)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 Vc}from"@roll-agent/sdk";import{z as Jc}from"zod";var Uc,Hc=Jc.object({success:Jc.boolean(),conversationId:Jc.string(),candidateId:Jc.string(),candidateName:Jc.string(),index:Jc.number(),hasUnread:Jc.boolean(),unreadCount:Jc.number(),lastMessageTime:Jc.string(),messagePreview:Jc.string(),error:Jc.string().optional()});function Wc(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Uc}}var Kc=Vc({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Jc.object({conversationId:Jc.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Jc.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Jc.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Jc.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Hc,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=Wc();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 Gc}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as Yc,prepareReplyContext as Xc,ReplyAuthorityRequestError as Zc}from"@roll-agent/reply-authority-client";import{z as Qc}from"zod";var el="·",tl=/[--—–]/,nl=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function rl(e){return(e??"").trim()}function al(e){return e.some(e=>"candidate_expected_location"!==e.source)}function ol(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 il(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function sl(e){const t=il(e)?e.signals:e,n=il(e)?e.analysisPath:void 0,r=il(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=ol(t);return!al(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function cl(e){const t=nl.exec(rl(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function ll(e){const t=rl(e);if(!t||nl.test(t))return;if(!tl.test(t))return;const[n=""]=t.split(tl);return rl(n)||void 0}function dl(e){const t=rl(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(el).map(e=>rl(e));return{expectedLocation:n,expectedPosition:r}}function ul(e){const t=rl(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=dl(e.expectedJobText),a=ll(t),o=cl(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var ml,pl=Qc.object({index:Qc.number(),sender:Qc.enum(["candidate","recruiter","system"]),messageType:Qc.enum(["text","system","resume","wechat-exchange"]),content:Qc.string(),time:Qc.string()}),gl=Qc.object({name:Qc.string(),age:Qc.string(),experience:Qc.string(),education:Qc.string(),communicationPosition:Qc.string(),expectedPosition:Qc.string(),expectedLocation:Qc.string(),expectedSalary:Qc.string(),tags:Qc.array(Qc.string())}),fl=Qc.object({success:Qc.boolean(),conversationId:Qc.string(),candidateId:Qc.string(),candidateInfo:gl,preferredBrand:Qc.string().optional(),preferredBrandId:Qc.number().int().positive().optional(),locationSignals:Qc.array(Yc).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:Qc.array(pl),formattedHistory:Qc.array(Qc.string()),stats:Qc.object({totalMessages:Qc.number(),candidateMessages:Qc.number(),recruiterMessages:Qc.number(),systemMessages:Qc.number()}),error:Qc.string().optional()});function hl(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function yl(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:hl(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function bl(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 vl(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),prepareReplyContext:Xc,...ml}}function wl(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function xl(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Sl(e){return{candidateMessage:xl(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 Rl=6e5,Il=0;function Cl(e){return e instanceof Zc?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function kl(e){Cl(e)&&(Il=Date.now()+Rl)}async function Al(e){if(Date.now()<Il)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===xl(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=qi(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=Sl({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=>{kl(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){kl(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var _l=Gc({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Qc.object({conversationId:Qc.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Qc.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Qc.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Qc.number().default(100).describe("最多返回的消息条数")}),output:fl,execute:async(e,t)=>{const n=e.maxMessages??100,r=vl(),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)),yl(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||!bl(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&&!bl(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=ul({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=wl(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`),Ic(g,p.conversationId,p.candidateId,t.logger),await Al({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)}`),yl(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as El}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Nl,GenerateSignedReplyResponseSchema as Ml,ReasoningConfigSchema as Pl,ReplyAuthorityRequestError as Tl,ReplyStreamFinalEventSchema as Bl,ReplyStreamLocationResolvedEventSchema as Ol,streamGenerateSignedReply as Dl}from"@roll-agent/reply-authority-client";import{z as $l}from"zod";import{ReplyGateAdvisoryCodeSchema as Ll}from"@roll-agent/reply-authority-client";import{z as jl}from"zod";var Fl=["option_1","option_2"],ql=["judge","orchestrator"],zl=[...ql,"service_recommended_fallback","explicit_no_judge"],Vl={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"},Jl=jl.array(Ll).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),Ul=jl.object({chosenOption:jl.enum(Fl).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:jl.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:Jl.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:jl.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),Hl=Ul.omit({judgeModel:!0}).extend({confirmedFindingCodes:Jl.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as Wl}from"node:crypto";var Kl=new Map;function Gl(e){for(const[t,n]of Kl)n.expiresAt<=e&&Kl.delete(t)}function Yl(e,t=Math.floor(Date.now()/1e3)){if(Gl(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${Wl()}`,r={preparedReplyId:n,...e};return Kl.set(n,{...r,consumed:!1}),r}function Xl(e,t=Math.floor(Date.now()/1e3)){const n=Kl.get(e);return void 0===n?(Gl(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Kl.delete(e),Gl(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Kl.set(e,{...n,consumed:!0}),{ok:!0,record:ed(n)})}function Zl(e,t=Math.floor(Date.now()/1e3)){const n=Kl.get(e);return void 0===n?(Gl(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Kl.delete(e),Gl(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:ed(n)}}function Ql(e,t,n=Math.floor(Date.now()/1e3)){const r=Zl(e,n);if(!r.ok)return r;const a=Kl.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(Kl.set(e,{...a,judgement:t}),{ok:!0,record:ed({...a,judgement:t})})}function ed(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 td}from"@roll-agent/reply-authority-client";import{z as nd}from"zod";var rd=1e3,ad=8,od=500,id=200,sd=12,cd=100,ld=td.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),dd=nd.object({candidateMessage:nd.string().min(1).max(rd),recentConversation:nd.array(nd.string().min(1).max(od)).max(ad),candidateInfo:ld.optional(),preferredBrand:nd.string().min(1).max(id).optional()});function ud(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 md(e,t){const n=ud(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function pd(e){if(void 0===e)return;const t={...void 0!==e.age?{age:md(e.age,id)}:{},...void 0!==e.experience?{experience:md(e.experience,id)}:{},...void 0!==e.education?{education:md(e.education,id)}:{},...void 0!==e.communicationPosition?{communicationPosition:md(e.communicationPosition,id)}:{},...void 0!==e.expectedPosition?{expectedPosition:md(e.expectedPosition,id)}:{},...void 0!==e.expectedLocation?{expectedLocation:md(e.expectedLocation,id)}:{},...void 0!==e.expectedSalary?{expectedSalary:md(e.expectedSalary,id)}:{},...void 0!==e.info?{info:e.info.map(e=>md(e,cd)).filter(e=>e.length>0).slice(0,sd)}:{}};return Object.keys(t).length>0?t:void 0}function gd(e){const t=md(e.candidateMessage,rd);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>md(e,od)).filter(e=>e.length>0).slice(-ad),r=pd(e.candidateInfo),a=void 0!==e.preferredBrand?md(e.preferredBrand,id):void 0;return dd.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 hd,yd=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(!Xe())return!1;try{return await this.target.evaluateJson(fd(e))}catch{return!1}}},bd=$l.object({conversationId:$l.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:$l.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:$l.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:$l.number().default(100).describe("最多读取的聊天消息条数"),reasoning:Pl.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),vd=["rejected","timeout","server_error"],wd=$l.object({requestId:$l.string().optional(),elapsedMs:$l.number().int().min(0).optional(),clientTimeoutMs:$l.number().int().positive().optional(),lastStartedPhase:$l.string().min(1).optional(),activePhase:$l.string().min(1).optional(),phaseLatencies:$l.record($l.number().int().min(0)).optional()}),xd=$l.object({success:$l.boolean(),preparedReplyId:$l.string().optional(),suggestedReply:$l.string().optional(),stage:$l.string().optional(),confidence:$l.number().optional(),expiresAt:$l.number().optional(),timing:$l.object({totalLatencyMs:$l.number().int().min(0),replyLatencyMs:$l.number().int().min(0).optional(),turnPlanningLatencyMs:$l.number().int().min(0).optional(),contextBuildingLatencyMs:$l.number().int().min(0).optional(),preparedContextHit:$l.boolean()}).optional(),gateRewritten:$l.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:$l.object({groupId:$l.string().min(1),options:$l.array($l.object({option:$l.enum(Fl),suggestedReply:$l.string(),expiresAt:$l.number().int().min(0)})).min(2).max(2),findings:$l.array($l.object({code:$l.string().min(1),description:$l.string().min(1)})),rubricVersion:$l.string().min(1),rubricHash:$l.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:$l.string().optional(),errorKind:$l.enum(vd).optional()}).extend(wd.shape),Sd={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function Rd(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),createReplyPreviewVisualSession:e=>new yd(e),streamGenerateSignedReply:Dl,fetchReplyFeedbackRubric:Nl,savePreparedReply:Yl,random:Math.random,...hd}}function Id(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 Cd(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function kd(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function Ad(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function _d(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Ed(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Nd(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function Md(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function Pd(e){const t=Md(e.navigationUnreadCount);return t>0?t:"candidate"===Nd(e.data.messages)?.sender?1:0}function Td(e){const t=ul({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:Ed(e.data.messages),conversationHistory:_d(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 Bd(e){return"phase.started"===e.type?Cd(e,"label")??Sd[Cd(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Cd(e,"toolName")?`: ${Cd(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Cd(e,"toolName")?`: ${Cd(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function Od(e,t,n={}){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{},...n}}function Dd(e,t){const n=t instanceof Tl?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 $d(e){return e.replace(/\s+\(url=.*\)$/,"")}function Ld(e){const t=e instanceof Error?e.message:String(e);if(e instanceof Tl){const n=$d(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 Fd(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function qd(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 zd(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 Vd(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 Jd(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 Ud(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return Jd({...e,reason:Vl.RUBRIC_MISMATCH});const t=Vd(e.replyVariants,e.random);if(2!==t.length)return Jd({...e,reason:Vl.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return Jd({...e,reason:Vl.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:Fl[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 Hd=El({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:bd,output:xd,execute:async(e,t)=>{const n=Rd(),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 wn(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),Od(e)}if(void 0===l&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),Od(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||!Id(d,u.candidateName))){const e=`右侧聊天面板未切换到 ${d}`;return await i.fail("聊天面板未同步"),Od(e)}const m=await o.readSelectedChatTarget();if(!m){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),Od(e)}if(null!==u&&m.candidateName.length>0&&!Id(m.candidateName,u.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`;return await i.fail("当前会话不一致"),Od(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("当前会话不一致"),Od(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("聊天详情目标不一致"),Od(e)}if(0===Ed(p.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),Od(e)}const g=qi(await o.readUsernameEvidence());if(!g.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),Od(e)}const f=Td({data:p,conversationId:m.conversationId,candidateId:m.candidateId,recruiterUsername:g.username,reasoning:e.reasoning}),h=Pd({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=Cd(e,"requestId"),b=Cd(e,"tenantId")),"phase.started"===e.type){const t=Cd(e,"phase");void 0!==t&&(c.lastStartedPhase=t,c.activePhase=t)}if("phase.completed"===e.type){const t=Cd(e,"phase"),n=kd(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===Ad(e,"rewritten")&&(S=!0);const r=Bd(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=Ol.safeParse(e);if(t.success){const e=sl({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+=Cd(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=Bl.parse(e),a=Ml.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=Fd({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("双稿反馈身份缺失"),Od(e,void 0,Dd(c))}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});g=Ud({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:m.conversationId,judgeContext:gd(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=${Vl.RUBRIC_FETCH_FAILED} detail=${n}`),g=Jd({replyVariants:a.replyVariants,tenantId:b,conversationId:m.conversationId,reason:Vl.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(qd({...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),Od(e,void 0,Dd(c))}return zd(v,w,S)}catch(e){const t=Ld(e),n=Dd(c,e);return await(s?.fail(t.message)),await(i?.fail(t.message)),Od(t.message,t.errorKind,n)}finally{o?.close()}}});import{defineTool as Wd}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Kd}from"@roll-agent/reply-authority-client";import{z as Gd}from"zod";var Yd,Xd="zhipin_judge_prepared_reply",Zd=Gd.object({success:Gd.boolean(),variantDecision:Ul.optional(),decisionSource:Gd.enum(zl).optional(),fallback:Gd.boolean().optional(),recommendedOption:Gd.enum(Fl).optional(),error:Gd.string().optional()}),Qd=Gd.object({preparedReplyId:Gd.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:Gd.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),eu=new Map;function tu(){return{fetchReplyFeedbackRubric:Kd,...Yd}}function nu(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function ru(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 ou(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 iu(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 su(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=tu();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=${Vl.RUBRIC_FETCH_FAILED} detail=${r}`),ru(e,Vl.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return ru(e,Vl.RUBRIC_MISMATCH);try{i=await t.llm.generateText(iu(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_SAMPLING_FAILED} detail=${r}`),ru(e,Vl.JUDGE_SAMPLING_FAILED)}try{s=Hl.parse(au(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_OUTPUT_INVALID} detail=${r}`),ru(e,Vl.JUDGE_OUTPUT_INVALID)}return ou(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:ud(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${Vl.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),ru(e,Vl.JUDGE_OUTPUT_INVALID))}async function cu(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=eu.get(e.preparedReplyId);if(void 0!==r)return r;const a=su(e,t,n).then(n=>{const r=Ql(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{eu.delete(e.preparedReplyId)});return eu.set(e.preparedReplyId,a),a}function lu(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 du=Wd({name:Xd,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:Qd,output:Zd,execute:async(e,t)=>{const n=Zl(e.preparedReplyId);if(!n.ok)return{success:!1,error:nu(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return Zd.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return lu(await cu(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as uu}from"node:crypto";import{BrowserActionApprovalSchema as mu}from"@roll-agent/browser";import{postReplyFeedback as pu}from"@roll-agent/reply-authority-client";import{defineTool as gu}from"@roll-agent/sdk";import{z as fu}from"zod";import{createHash as hu}from"node:crypto";import{mkdirSync as yu}from"node:fs";import{homedir as bu}from"node:os";import{dirname as vu,resolve as wu}from"node:path";import{DatabaseSync as xu}from"node:sqlite";import{postReplyFeedback as Su,ReplyFeedbackBodySchema as Ru}from"@roll-agent/reply-authority-client";import{createAgentLogger as Iu}from"@roll-agent/sdk";var Cu,ku,Au=604800,_u=3e4,Eu=1e3,Nu=9e5,Mu=9e5,Pu=50,Tu=["pending","delivered","dead"],Bu=Tu.map(e=>`'${e}'`).join(", "),Ou=Iu("browser-use-agent:reply-feedback-outbox");function Du(e){return"~"===e?bu():e.startsWith("~/")?wu(bu(),e.slice(2)):wu(e)}function $u(){return wu(bu(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function Lu(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??Lu("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",Au),n=e.flushIntervalMs??Lu("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",_u),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??$u();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=Du(r),o=e.retryBaseDelayMs??Eu,i=e.authRetryDelayMs??Nu;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 Fu(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(Fu).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${Fu(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function qu(e){return hu("sha256").update(e).digest("hex")}function zu(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 Vu(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 Ju(e){return Ru.parse(JSON.parse(e.body_json))}function Uu(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function Hu(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 Wu(e){return 401===e||403===e}function Ku(e){return!(void 0!==e&&!Wu(e))||(408===e||425===e||429===e||e>=500)}function Gu(e,t){if(Wu(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,Mu)}function Yu(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function Xu(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 Zu(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 Qu(e,t,n,r){const a=Hu(n),o=Uu(n);if(Ku(a)&&r<t.retry_expires_at){const n=r+Gu(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 em(e,t){const n=Date.now();if(n>=t.retry_expires_at)return Qu(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(Ju(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 Zu(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return Qu(e,Vu(e.db,t.group_id)??t,n,Date.now())}}async function tm(e){const t=Date.now();Xu(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,Pu);for(const t of n)await em(e,t)}function nm(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,Yu(e,()=>tm(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function rm(e){yu(vu(e),{recursive:!0});const t=new xu(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 (${Bu})),\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(Cu&&!Cu.closing)return Cu.deliver=e.deliver??Cu.deliver,void(Cu.logger=e.logger??Cu.logger);if(Cu?.closing||ku)throw new Error("Reply feedback outbox is shutting down.");const t=ju(e),n=rm(t.dbPath),r=setInterval(()=>{Cu&&nm(Cu)},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??Su,logger:e.logger??Ou,closing:!1,backgroundFlushQueued:!1};Cu=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),nm(a)}async function om(){if(ku)return ku;const e=Cu;return e?(e.closing=!0,clearInterval(e.timer),ku=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),Cu===e&&(Cu=void 0)}})().finally(()=>{ku=void 0})):void 0}async function im(e,t,n,r={}){Cu||am({deliver:t,logger:n});const a=Cu;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=Ru.parse(e),i=Fu(o),s=qu(i);return Yu(a,async()=>{const e=Date.now(),t=zu(e,a.retentionMs,r.feedbackExpiresAt);Xu(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=Vu(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=Vu(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}:{}}:em(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}import{defineTool as sm}from"@roll-agent/sdk";import{z as cm}from"zod";function lm(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as dm}from"zod";var um="reply-authority-service",mm="browser-use-agent/zhipin_send_reply",pm="zhipin",gm=60,fm=dm.object({platform:dm.literal(pm),username:dm.string().min(1),accountId:dm.string().min(1).optional()}),hm=dm.object({v:dm.literal(2),iss:dm.literal(um),kid:dm.string().min(1),jti:dm.string().min(1),iat:dm.number().int(),exp:dm.number().int(),aud:dm.literal(mm),platform:dm.literal(pm),tenantId:dm.string().min(1),conversationId:dm.string().min(1),candidateId:dm.string().min(1),reply:dm.string(),policyVersion:dm.string().min(1),recruiterBinding:fm}),ym=dm.object({kid:dm.string().min(1),algorithm:dm.literal("Ed25519"),publicKey:dm.string().min(1),validFrom:dm.string().min(1),validUntil:dm.string().optional()}),bm=dm.object({keys:dm.array(ym)}),vm=new Map;function wm(e){for(const[t,n]of vm)n<e-gm&&vm.delete(t)}function xm(e,t=Math.floor(Date.now()/1e3)){return wm(t),vm.has(e)}function Sm(e,t,n=Math.floor(Date.now()/1e3)){wm(n),vm.set(e,t)}import{createPublicKey as Rm,verify as Im}from"node:crypto";var Cm=new Map,km=null;function Am(){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 _m(e){return Cm=new Map(e.map(e=>[e.kid,e]))}async function Em(){if(km)return km;km=(async()=>{const e=await fetch(Am()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return _m(bm.parse(t).keys)})();try{return await km}finally{km=null}}async function Nm(){await Em()}async function Mm(e){const t=Cm.get(e);if(t)return t;const n=(await Em()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function Pm(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function Tm(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")}Pm(a);const i=hm.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function Bm(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-gm)throw new Error("Envelope expired");if(e.iat>t+gm)throw new Error("Envelope issued in the future")}async function Om(e,t=Math.floor(Date.now()/1e3)){const n=Tm(e),r=await Mm(n.payload.kid),a=Rm({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!Im(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return Bm(n.payload,t),n.payload}var Dm,$m=cm.object({success:cm.boolean(),sentMessage:cm.string(),error:cm.string().optional()});function Lm(){return{getReplyAuthorityKeysLoaded:pe,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Dm}}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 Fm(e){return"function"==typeof e.readCandidateChatDetails}async function qm(e){if(Fm(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function zm(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function Vm(e,t){return null===t||0===e.candidateName.length||jm(e.candidateName,t.candidateName)}function Jm(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function Um(e,t){const n=Lm();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 Om(e.signedEnvelope);if(o=i.reply,xm(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(r),await new yd(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=Jm(e.unreadCountBeforeReply);if(!zm(c,i)||!Vm(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,Jm(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=qi(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(!lm(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 qm(r);return xc({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),Sm(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 Hm,Wm=sm({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:cm.object({signedEnvelope:cm.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:cm.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:cm.number().optional().describe("候选人在列表中的索引(可选)")}),output:$m,execute:async(e,t)=>await Um(e,t)}),Km="zhipin_send_prepared_reply",Gm=80,Ym=fu.object({success:fu.boolean(),sentMessage:fu.string(),chosenOption:fu.enum(Fl).optional(),decisionSource:fu.enum(zl).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()}),Xm=fu.object({preparedReplyId:fu.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:nt.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:mu.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:Ul.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 Zm(){return{sendSignedZhipinReply:Um,postReplyFeedback:pu,submitReplyFeedback:im,...Hm}}function Qm(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function ep(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 tp(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 np(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"}:ep(e.judgement.decision,t.variantDecision)?tp(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?tp(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:tp(await cu(e,n))}function rp(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 op(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=rp(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 ip(e){return`sha256:${uu("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 sp(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>Gm?`${t.slice(0,Gm-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function cp(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 lp(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 dp=gu({name:Km,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:Xm,output:Ym,execute:async(e,t)=>{const n=Zm(),r=Zl(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:Qm(r.reason)};const a=await np(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=op(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=ip(o),s=`${o.record.preparedReplyId}:${i}`,c=It(t,{subject:{tool:Km,target:o.record.preparedReplyId,summary:sp(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});hn(t,de(),{action:Km,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=Xl(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:Qm(l.reason)};const d=op(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 cp(n,d,t);return{...u,...lp(d),...m}}});import{defineTool as up}from"@roll-agent/sdk";import{z as mp}from"zod";var pp,gp=mp.object({success:mp.boolean(),exchanged:mp.boolean(),wechatNumber:mp.string().optional(),error:mp.string().optional()}),fp=900,hp=160,yp=1100;function bp(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...pp}}function vp(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 wp(e){return"function"==typeof e.readCandidateChatDetails}async function xp(e){if(wp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var Sp=up({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:mp.object({conversationId:mp.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:mp.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:mp.number().optional().describe("候选人在列表中的索引(可选)")}),output:gp,execute:async(e,t)=>{const n=bp();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(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&&!vp(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 xp(r),c=await r.exchangeWechat({preClickDelayMs:fp,pressDurationMs:hp,settleMs:yp,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&Rc({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 Rp}from"@roll-agent/sdk";import{z as Ip}from"zod";var Cp,kp=Ip.object({success:Ip.boolean(),username:Ip.string(),usedSelector:Ip.string().optional(),usedStrategy:Ip.string().optional(),source:Ip.string().optional(),error:Ip.string().optional()});function Ap(){return{openNativePagePort:Us,pickBestUsername:qi,createNativeVisualActivitySession:e=>new ha(e),...Cp}}var _p=Rp({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Ip.object({}),output:kp,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Ap();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 Ep}from"@roll-agent/sdk";import{z as Np}from"zod";var Mp,Pp=["unknown","top","middle","bottom","only-page"],Tp=Np.object({containerFound:Np.boolean(),containerLabel:Np.string(),scrollTop:Np.number(),scrollHeight:Np.number(),clientHeight:Np.number(),itemCount:Np.number(),atStart:Np.boolean(),atEnd:Np.boolean()}),Bp=Np.object({success:Np.boolean(),surface:Np.enum(ki),direction:Np.enum(["up","down"]),stepsRequested:Np.number(),stepsCompleted:Np.number(),reachedBoundary:Np.boolean(),atTop:Np.boolean(),atBottom:Np.boolean(),canScrollUp:Np.boolean(),canScrollDown:Np.boolean(),position:Np.enum(Pp),before:Tp,after:Tp,error:Np.string().optional()});function Op(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Mp}}function Dp(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function $p(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function Lp(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:$p(e)}}var jp=Ep({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:Np.object({surface:Np.enum(ki).describe("要滚动的页面区域"),direction:Np.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:Np.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:Np.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:Np.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:Bp,execute:async(e,t)=>{const n=Op(),r=_i(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,...Lp(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Lp(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){await(c?.fail("滚动失败"));const n=Dp();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...Lp(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as Fp}from"@roll-agent/sdk";import{z as qp}from"zod";var zp,Vp=qp.object({applyMode:qp.enum(Pi),ageMin:qp.number().int().min(16).optional(),ageMax:qp.number().int().min(16).optional(),location:qp.object({city:qp.string(),district:qp.string().optional()}).optional(),optionSelections:qp.array(qp.object({fieldKey:qp.string(),label:qp.string(),values:qp.array(qp.string()),selection:qp.enum(["single","multi"]),clearValue:qp.string()}))}),Jp=qp.object({ageMin:qp.number().optional(),ageMax:qp.number().optional(),location:qp.object({city:qp.string(),district:qp.string().optional()}).optional(),optionSelections:qp.array(qp.object({fieldKey:qp.string(),label:qp.string(),values:qp.array(qp.string())})),gender:qp.string().optional(),activity:qp.string().optional()}),Up=qp.object({success:qp.boolean(),status:qp.enum(Bi),requested:Vp,applied:Jp.optional(),filterButtonText:qp.string().optional(),error:qp.string().optional()}),Hp=qp.string().min(1),Wp=qp.array(Hp).min(1),Kp=qp.object({applyMode:qp.enum(Pi).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:qp.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:qp.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:Hp.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:Hp.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:qp.enum(Ni).optional().describe("性别筛选:不限、男、女"),activity:qp.enum(Mi).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:Wp.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:Hp.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:Hp.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:Wp.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:Wp.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:Hp.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:Wp.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:Hp.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:Wp.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:Wp.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:Hp.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"}),Gp=350,Yp=130,Xp=600;function Zp(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...zp}}function Qp(e,t){const n=Oi(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function eg(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 tg(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function ng(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(Qp(t,r))}function rg(e){const t=[];if(void 0===e.applyMode&&!eg(e)&&tg(e)||!eg(e)&&!tg(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(Qp("gender",[n])),t.push(Qp("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}ng(t,"gender",e.gender),ng(t,"activity",e.activity),ng(t,"major",e.major),ng(t,"recentNotView",e.recentNotView),ng(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),ng(t,"candidateKeywords",e.candidateKeywords),ng(t,"school",e.school),ng(t,"switchJobFrequency",e.switchJobFrequency),ng(t,"intention",e.intention),ng(t,"salary",e.salary),ng(t,"degree",e.degree),ng(t,"experience",e.experience),ng(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 og(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 ig(e){const t=og(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 sg=Fp({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:Kp,output:Up,execute:async(e,t)=>{const n=Zp(),r=rg(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 wn(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),ig({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Ei.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:Gp,pressDurationMs:Yp,settleMs:Xp,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),ig(e)}catch(e){const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),ig({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as cg}from"@roll-agent/sdk";import{z as lg}from"zod";var dg=["target-count","boundary","no-new-items","max-steps"];import{AsyncLocalStorage as ug}from"node:async_hooks";var mg="@c",pg="@j",gg="legacy",fg=/^@?c([1-9]\d*)$/i,hg=/^@?j([1-9]\d*)$/i,yg=new Map,bg=new Map,vg=new ug;function wg(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${mg}${String(e+1)}`}function xg(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${pg}${String(e+1)}`}function Sg(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 Rg(e){const t=hg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Ig(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 Ag(e.candidateRef).index}function Cg(e){const t=e.map((e,t)=>({...e,candidateRef:wg(t)}));return yg.set(Tg(),new Map(t.map(e=>[e.candidateRef,e]))),t}function kg(e){const t=e.map((e,t)=>({...e,jobRef:xg(t)}));return bg.set(Tg(),new Map(t.map(e=>[e.jobRef,e]))),t}function Ag(e){const t=Sg(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=wg(t);return yg.get(Tg())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function _g(e){const t=Rg(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=xg(t),r=bg.get(Tg())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function Eg(e){const t=[];for(const n of e.indices??[])t.push({index:Ig({index:n}),candidateRef:wg(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Ag(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return Mg(t)}function Ng(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)||Pg(e.name)===Pg(t.name))}function Mg(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function Pg(e){return e.trim().toLocaleLowerCase("zh-CN")}function Tg(){const e=vg.getStore();if(void 0!==e)return e;try{return be().id}catch{return gg}}var Bg,Og=lg.object({index:lg.number(),candidateRef:lg.string(),candidateId:lg.string(),name:lg.string(),age:lg.string(),experience:lg.string(),education:lg.string(),workStatus:lg.string(),company:lg.string(),currentPosition:lg.string(),expectedLocation:lg.string(),expectedPosition:lg.string(),expectedSalary:lg.string(),tags:lg.array(lg.string()),buttonText:lg.string()}),Dg=lg.object({containerLabel:lg.string(),stepsRequested:lg.number(),stepsCompleted:lg.number(),reachedBoundary:lg.boolean(),stopReason:lg.enum(dg),uniqueCount:lg.number(),duplicateCount:lg.number(),noNewRounds:lg.number(),beforeItemCount:lg.number(),afterItemCount:lg.number(),beforeScrollHeight:lg.number(),afterScrollHeight:lg.number()}),$g=lg.object({success:lg.boolean(),candidates:lg.array(Og),total:lg.number(),scrollStats:Dg.optional(),error:lg.string().optional()});function Lg(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...Bg}}var jg=cg({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:lg.object({maxResults:lg.number().optional().describe("最多返回条数"),autoScroll:lg.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:lg.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:$g,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=Lg();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=Cg(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 Fg}from"@roll-agent/sdk";import{z as qg}from"zod";var zg,Vg=["listed","recommend_not_ready","selector_not_found"],Jg=qg.object({index:qg.number(),jobRef:qg.string().optional(),value:qg.string(),label:qg.string(),isCurrent:qg.boolean()}),Ug=qg.object({success:qg.boolean(),status:qg.enum(Vg),current:Jg.optional(),jobs:qg.array(Jg),availableCount:qg.number(),canSwitch:qg.boolean(),error:qg.string().optional()}),Hg=450,Wg=140,Kg=750;function Gg(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...zg}}function Yg(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function Xg(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Zg(e){const t=kg(e.options),n=e.options.map((e,n)=>Yg(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>Xg(e,t))??Yg(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 Qg=Fg({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:qg.object({}),output:Ug,execute:async(e,t)=>{const n=Gg();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await wn(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:Hg,pressDurationMs:Wg,settleMs:Kg,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),Zg(e)}catch(e){const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),Zg({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as ef}from"@roll-agent/browser";import{defineTool as tf}from"@roll-agent/sdk";import{z as nf}from"zod";var rf,af=nf.object({success:nf.boolean(),alreadyOnRecommend:nf.boolean(),usedSidebarClick:nf.boolean(),recommendReady:nf.boolean(),page:ef.optional(),error:nf.string().optional()});function of(){return{getContextManager:ue,openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...rf}}async function sf(e,t){return Wt(e,await t.inspectPage())}var cf=tf({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:nf.object({}),output:af,execute:async(e,t)=>{const n=of(),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 wn(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(Ei.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await sf(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 sf(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 sf(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await sf(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 lf}from"@roll-agent/sdk";import{z as df}from"zod";var uf,mf=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],pf=df.object({index:df.number(),value:df.string(),label:df.string(),isCurrent:df.boolean()}),gf=df.object({jobRef:df.string().optional(),jobValue:df.string().optional(),jobName:df.string().optional(),index:df.number().optional(),searchKeyword:df.string().optional(),useSearch:df.boolean().optional(),forceClick:df.boolean().optional()}),ff=df.object({success:df.boolean(),status:df.enum(mf),requested:gf,current:pf.optional(),selected:pf.optional(),options:df.array(pf),matchedCount:df.number(),error:df.string().optional()}),hf=df.object({jobRef:df.string().regex(hg,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:df.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:df.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:df.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:df.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:df.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:df.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 至少需要提供一个"}),yf=900,bf=180,vf=1400;function wf(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),...uf}}function xf(e){if(void 0!==e.jobRef){const t=_g(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 Sf(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 Rf(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function If(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Rf(e.current)}:{},...void 0!==e.selected?{selected:Rf(e.selected)}:{},options:e.options.map(Rf),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Cf=lf({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:hf,output:ff,execute:async(e,t)=>{const n=wf();let r,a;try{const o=xf(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 wn(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:yf,pressDurationMs:bf,settleMs:vf,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),If(i)}catch(t){const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),If({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Sf(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as kf}from"@roll-agent/sdk";import{z as Af}from"zod";var _f,Ef=Af.object({index:Af.number(),candidateRef:Af.string(),candidateName:Af.string(),candidateId:Af.string(),success:Af.boolean(),error:Af.string().optional()}),Nf=Af.object({success:Af.boolean(),results:Af.array(Ef),summary:Af.object({total:Af.number(),succeeded:Af.number(),failed:Af.number()})}),Mf=[1400,1800,2200],Pf=450,Tf=140,Bf=750,Of=Af.object({indices:Af.array(Af.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Af.array(Af.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 Df(){return{openNativePagePort:Us,createNativeVisualActivitySession:e=>new ha(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),..._f}}var $f=kf({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:Of,output:Nf,execute:async(e,t)=>{const n=Eg(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=Df();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=Mf[(e-1)%Mf.length]??Mf[0];await r.sleep(t)}if(Lf(s)){const e=await a.inspectRecommendCard(s.index);if(!Ng(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:Pf,pressDurationMs:Tf,settleMs:Bf,...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}:{}}),Sc(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 Lf(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 Ff}from"zod";var qf="recommendFrame",zf="recommend",Vf="#recommendFrame",Jf=".candidate-card-wrap",Uf="[data-geek], .geek-item",Hf=`${Jf}, ${Uf}`,Wf="[data-geek], .card-inner, .geek-item",Kf="[data-geek]",Gf=".name",Yf=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],Xf=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Zf=".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog",Qf=".boss-popup__wrapper",eh='iframe[src*="c-resume"]',th="canvas#resume, div#resume canvas";function nh(e){return e.hasNamedRecommendFrame?"named-frame":e.hasRecommendUrlFrame?"recommend-url-frame":"main-page"}function rh(e){return e>0?Jf:Uf}function ah(e){return{candidateId:(e.ownDataGeek??e.childDataGeek??"").trim(),name:(e.nameText??"").trim()}}function oh(e){const t=(e.recommendFrameRect?.x??0)+(e.resumeFrameRect?.x??0),n=(e.recommendFrameRect?.y??0)+(e.resumeFrameRect?.y??0);return{x:Math.round(t+e.canvasRect.x),y:Math.round(n+e.canvasRect.y),width:Math.round(e.canvasRect.width),height:Math.round(e.canvasRect.height)}}function ih(e){return e.evaluate(e=>document.querySelectorAll(e).length,Jf).then(rh)}function sh(e){const t=e.frame(qf),n=e.frames().find(e=>e.url().includes(zf)),r=nh({hasNamedRecommendFrame:null!==t,hasRecommendUrlFrame:void 0!==n});return"named-frame"===r?t??e:"recommend-url-frame"===r?n??e:e}async function ch(e,t=1e4){try{return await e.waitForSelector(Hf,{timeout:t}),!0}catch{return!1}}async function lh(e,t){const n=await ih(e),r=e.locator(n);if(await r.count()<=t)return{found:!1,cardSelector:n,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const a=r.nth(t),o=await a.evaluate((e,t)=>{const n=e.getAttribute("data-geek")??e.querySelector(t.candidateIdSelector)?.getAttribute("data-geek")??"",r=e.querySelector(t.candidateNameSelector)?.textContent?.trim()??"",a=e.querySelector("button.btn.btn-greet");return{candidateId:n,name:r,hasGreetButton:null!==a&&a.offsetWidth>0}},{candidateIdSelector:Kf,candidateNameSelector:Gf}),i=ah({ownDataGeek:o.candidateId,nameText:o.name});return{found:!0,cardSelector:n,candidateId:i.candidateId,name:i.name,hasGreetButton:o.hasGreetButton}}var dh=Ff.object({success:Ff.boolean(),candidateRef:Ff.string().optional(),candidateName:Ff.string(),candidateId:Ff.string(),error:Ff.string().optional()}),uh=Ff.object({index:Ff.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:Ff.string().regex(fg,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef"),mh=jf({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:uh,output:dh,execute:async(e,t)=>{const n=ph(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${r}`);const o=ue(),i=await o.getPage("zhipin"),s=sh(i);if(!await ch(s))return{success:!1,candidateRef:a,candidateName:"",candidateId:"",error:"推荐列表未加载"};const c=await lh(s,r);if(!c.found)return{success:!1,candidateRef:a,candidateName:"",candidateId:"",error:c.error??`候选人引用 ${a} 超出范围`};if(gh(n)&&!Ng(n,c))return{success:!1,candidateRef:a,candidateName:c.name,candidateId:c.candidateId,error:`候选人引用 ${a} 已过期,请重新获取推荐列表`};const l=s.locator(c.cardSelector).nth(r),d=await l.locator(Wf).count()>0?l.locator(Wf).first():l;return await d.scrollIntoViewIfNeeded(),await Ke(i,d,{target:s}),await d.hover(),await Si(i,200,400),await Ge(i,d,{target:s}),await d.click(),await Si(i,1e3,2e3),t.logger.info(`Opened resume for ${c.name}`),{success:!0,candidateRef:a,candidateName:c.name,candidateId:c.candidateId}}});function ph(e){if(void 0!==e.candidateRef)return Ag(e.candidateRef);const t=Ig(e);return{index:t,candidateRef:wg(t),candidateId:""}}function gh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as fh}from"@roll-agent/sdk";import{z as hh}from"zod";var yh=hh.object({x:hh.number(),y:hh.number(),width:hh.number(),height:hh.number()}),bh=hh.object({success:hh.boolean(),screenshotArea:yh.optional(),canvasInfo:hh.object({width:hh.number(),height:hh.number()}).optional(),error:hh.string().optional()}),vh=fh({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:hh.object({}),output:bh,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=ue(),r=await n.getPage("zhipin");try{const e=r.frame(qf)??r.frames().find(e=>e.url().includes(zf));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$(eh);if(!n)return{success:!1,error:"未找到简历 iframe"};const a=await n.contentFrame();if(!a)return{success:!1,error:"无法访问简历 iframe 内容"};try{await a.waitForSelector(th,{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await a.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{width:t.width,height:t.height,clientWidth:n.width,clientHeight:n.height,x:n.x,y:n.y}},th);if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await r.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},Vf),s=oh({recommendFrameRect:i,resumeFrameRect:await e.evaluate(e=>{const t=document.querySelector(e);if(!t)return null;const n=t.getBoundingClientRect();return{x:n.x,y:n.y}},eh),canvasRect:{x:o.x,y:o.y,width:o.clientWidth,height:o.clientHeight}});return t.logger.info(`Canvas located at (${s.x}, ${s.y})`),{success:!0,screenshotArea:s,canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as wh}from"@roll-agent/sdk";import{z as xh}from"zod";var Sh=xh.object({success:xh.boolean(),closed:xh.boolean(),error:xh.string().optional()}),Rh=wh({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:xh.object({}),output:Sh,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=ue(),r=await n.getPage("zhipin"),a=r.frame(qf)??r.frames().find(e=>e.url().includes(zf));if(!await(async()=>{if(a)for(const e of Yf){const t=a.locator(e).first();if(await t.isVisible())return await Ke(r,t,{target:a}),await Ge(r,t,{target:a}),await t.click(),!0}for(const e of Xf){const t=r.locator(e).first();if(await t.isVisible())return await Ke(r,t),await Ge(r,t),await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await r.waitForTimeout(300);const e=a?await a.$(Zf):await r.$(Qf);if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as Ih}from"@roll-agent/sdk";import{z as Ch}from"zod";import{waitForSelector as kh}from"@roll-agent/browser";var Ah={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 _h,waitForSelector as Eh}from"@roll-agent/browser";var Nh="https://www.yupao.com",Mh=`${Nh}/chat`,Ph=`${Nh}/login`;async function Th(e,t={}){e.url().includes("/chat")||await _h(e,Mh,t),await Eh(e,Ah.messageList.container,{timeout:15e3})}async function Bh(e,t,n={}){const r=`${Nh}/chat?id=${encodeURIComponent(t)}`;await _h(e,r,n),await Eh(e,Ah.chat.input,{timeout:15e3})}async function Oh(e,t){await Th(e),await kh(e,Ah.messageList.item,{timeout:1e4});const n=Ah.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 Dh=Ch.object({limit:Ch.number().optional().describe("最多返回的消息条数")}),$h=Ch.object({conversationId:Ch.string(),candidateName:Ch.string(),lastMessage:Ch.string(),unreadCount:Ch.number(),timestamp:Ch.string()}),Lh=Ch.object({messages:Ch.array($h),total:Ch.number()}),jh=Ih({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Dh,output:Lh,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=ue(),r=await n.getPage("yupao"),a=await Oh(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});import{defineTool as Fh}from"@roll-agent/sdk";import{z as qh}from"zod";import{BrowserActionApprovalSchema as zh}from"@roll-agent/browser";import{clickElement as Vh,isBrowserActionPolicyError as Jh,typeText as Uh,waitForSelector as Hh}from"@roll-agent/browser";async function Wh(e,t,n,r={}){try{return await Bh(e,t,r),await Uh(e,Ah.chat.input,n,r),await Vh(e,Ah.chat.sendButton,r),await Hh(e,Ah.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(Jh(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var Kh=qh.object({conversationId:qh.string().describe("对话 ID"),message:qh.string().describe("要发送的回复消息"),browserActionApproval:zh.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Gh=qh.object({success:qh.boolean(),conversationId:qh.string(),sentMessage:qh.string(),error:qh.string().optional()}),Yh=Fh({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:Kh,output:Gh,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=hn(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 Wh(s,n,r,fn(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=yn(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 Xh,BrowserRuntimeModeSchema as Zh,BrowserChannelSchema as Qh,BrowserProfileColorSchema as ey,BrowserWindowBoundsSchema as ty,PlatformSchema as ny}from"@roll-agent/browser";import{z as ry}from"zod";var ay=ry.object({platform:ny.optional(),mode:Zh.default("managed-cdp"),headless:ry.boolean().optional(),cdpUrl:ry.string().optional(),cdpHost:ry.string().default("127.0.0.1"),cdpPort:ry.number().int().min(1).max(65535).optional(),channel:Qh.default("chrome"),executablePath:ry.string().optional(),userDataDir:ry.string().trim().min(1),sessionsDir:ry.string().trim().min(1).optional(),args:ry.array(ry.string()).optional(),profileName:ry.string().trim().min(1).optional(),profileColor:ey.optional(),windowBounds:ty.optional(),trackingAgentId:ry.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:ry.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:ry.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),oy=ry.object({defaultInstance:ry.string().trim().min(1).optional(),instances:ry.record(ry.string(),ay).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:ry.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:ry.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:ry.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function iy(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 sy(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 ly(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 dy(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 oy.parse(t)}function uy(e=process.env){return Xh.parse({mode:e.BROWSER_MODE,headless:iy(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:sy(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:ly(e.BROWSER_SECURITY_JSON)})}function my(e=process.env){return dy(e.BROWSER_INSTANCES_JSON)}import{z as py}from"zod";import{StructuredToolError as gy}from"@roll-agent/sdk";var fy=new Map;async function hy(e,t,n={}){const{signal:r,onWait:a}=n;vy(e,r,0);const o=fy.get(e);let i;const s=new Promise(e=>{i=e});fy.set(e,s);const c=performance.now();if(void 0!==o)try{await by(o,r)}catch(t){throw o.then(()=>{i(),xy(e,s)}),t instanceof yy&&wy(e,Sy(c)),t}try{return void 0!==o&&a?.(Sy(c)),vy(e,r,Sy(c)),await t()}finally{i(),xy(e,s)}}var yy=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 yy;await new Promise((n,r)=>{const a=()=>{r(new yy)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new yy):n()})})}else await e}function vy(e,t,n){!0===t?.aborted&&wy(e,n)}function wy(e,t){throw new gy({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 xy(e,t){fy.get(e)===t&&fy.delete(e)}function Sy(e){return performance.now()-e}import{StructuredToolError as Ry}from"@roll-agent/sdk";function Iy(e){if(void 0===e)return;const t=ge().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new Ry({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 Cy(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var ky={browserInstance:py.string().trim().min(1).optional().describe("可选 browser instance id;多浏览器实例时用于选择目标 profile/CDP runtime。")};function Ay(e){return e.startsWith("zhipin_")?"zhipin":e.startsWith("yupao_")?"yupao":void 0}function _y(e,t={}){const n=Ny(e.input);if(void 0===n)return e;const r=t.startRuntime??!0,a=t.serializePageOps??!0,o=t.expectedPlatform??Ay(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(),Iy(Ey(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 hy(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 Ey(e,t){const n=Cy(e);return t??n}function Ny(e){if(e instanceof py.ZodObject)return e.extend(ky);if(e instanceof py.ZodEffects){const t=Ny(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?py.ZodEffects.createWithPreprocess(n.transform,t,e._def):py.ZodEffects.create(t,n,e._def)}}var My=t("browser-use-agent");async function Py(){const e=(await Promise.allSettled([om(),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 Ty(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e}var By=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function Oy(e){return Ty(e.name)?e:_y(e,{startRuntime:"browser_status"!==e.name,serializePageOps:!By.has(e.name)})}var Dy=e({name:"browser-use-agent",tools:[At,Xt,$n,nr,mr,vr,Ea,za,Ya,bi,Mc,zc,Kc,_l,Hd,du,dp,Sp,_p,jp,cf,Qg,Cf,sg,jg,$f,mh,vh,Rh,jh,Yh,xe,Ot].map(Oy)},{onShutdown:Py});async function $y(){xt(wt()),am({logger:My}),await le(uy(),my());try{await Nm(),me(!0)}catch(e){me(!1),My.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Dy.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}$y().catch(async e=>{My.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await Py().catch(()=>{}),process.exit(1)});
|