@roll-agent/browser-use-agent 0.25.2 → 0.26.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/SKILL.md +45 -16
- package/dist/browser-execution.d.ts +19 -0
- package/dist/browser-observation.d.ts +14 -0
- package/dist/browser-script-approval.d.ts +18 -0
- package/dist/index.js +1 -1
- package/dist/reply-authority/schemas.d.ts +2 -2
- package/dist/tools/browser-dom-action-candidates.d.ts +1 -0
- package/dist/tools/browser-execute.d.ts +152 -0
- package/dist/tools/browser-ref-frame-guard.d.ts +8 -0
- package/dist/tools/browser-ref-schemas.d.ts +6 -6
- package/dist/tools/browser-snapshot.d.ts +22 -3
- package/dist/tools/browser-workflows.d.ts +670 -0
- package/dist/tools/click-ref.d.ts +2 -1
- package/dist/tools/type-ref.d.ts +2 -1
- package/dist/tools/zhipin-diagnose-browser-state.d.ts +4 -4
- package/dist/tools/zhipin-generate-reply-preview.d.ts +2 -2
- package/dist/tools/zhipin-list-recommend-jobs.d.ts +2 -2
- package/dist/tools/zhipin-select-recommend-job.d.ts +6 -6
- package/dist/tools/zhipin-send-prepared-reply.d.ts +3 -3
- package/dist/workflows/parameters.d.ts +8 -0
- package/dist/workflows/store.d.ts +319 -0
- package/package.json +4 -4
- package/references/browser-exploration.md +141 -0
- package/references/generic-browser-refs.md +77 -25
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{defineTool as n}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as a}from"@roll-agent/sdk";import"@roll-agent/browser";import{AsyncLocalStorage as o}from"node:async_hooks";import{access as i}from"node:fs/promises";import{constants as s}from"node:fs";import{homedir as c}from"node:os";import{join as l}from"node:path";import{createHash as d}from"node:crypto";import{StructuredToolError as u}from"@roll-agent/sdk";import{BrowserContextManager as m,BrowserRuntime as p,BrowserRuntimeConfigSchema as g,probeBrowserRuntimeCdpHealth as f,SessionStore as h}from"@roll-agent/browser";import{execFileSync as y}from"node:child_process";var b,v={x:0,y:0,width:1920,height:1080},w=25,x=80,S=4,R=["Add-Type -AssemblyName System.Windows.Forms;","$area = [System.Windows.Forms.Screen]::PrimaryScreen.WorkingArea;","[pscustomobject]@{ x = $area.X; y = $area.Y; width = $area.Width; height = $area.Height } | ConvertTo-Json -Compress"].join(" ");function C(){return b??=A(),b}function I(e){if(e<=1)return[1];if(2===e)return[2];if(3===e)return[3];if(4===e)return[2,2];const t=Math.ceil(e/S),n=Math.floor(e/t),r=e%t;return Array.from({length:t},(e,t)=>n+(t<r?1:0))}function k(e){if(!Number.isInteger(e.index)||e.index<0||!Number.isInteger(e.total)||e.total<=0||e.index>=e.total)throw new RangeError("Auto window layout index must be within the configured instance count.");const t=I(e.total),n=t.length;let r=0,a=e.index;for(const[e,n]of t.entries()){if(a<n){r=e;break}a-=n}const o=t[r]??1,i=e.workArea.x+Math.floor(e.workArea.width*a/o),s=e.workArea.x+Math.floor(e.workArea.width*(a+1)/o),c=e.workArea.y+Math.floor(e.workArea.height*r/n);return{x:i,y:c,width:s-i,height:e.workArea.y+Math.floor(e.workArea.height*(r+1)/n)-c}}function A(){const e=E(process.env.ROLL_BROWSER_WORK_AREA);if(void 0!==e)return e;if("darwin"===process.platform){const e=B();if(void 0!==e)return{x:e.x,y:e.y+w,width:e.width,height:Math.max(400,e.height-w-x)}}if("linux"===process.platform){const e=O();if(void 0!==e)return e}if("win32"===process.platform){const e=D();if(void 0!==e)return e}return v}function E(e){if(void 0===e||0===e.trim().length)return;const t=e.split(",").map(e=>Number.parseInt(e.trim(),10));if(4!==t.length||t.some(e=>!Number.isFinite(e)||e<0))return;const[n,r,a,o]=t;return a<=0||o<=0?void 0:{x:n,y:r,width:a,height:o}}function _(e){const t=N(e,/UI Looks like:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==t)return t;for(const t of e.split("\n")){if(!t.includes("Resolution:")||/\bRetina\b/i.test(t))continue;const e=N(t,/Resolution:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==e)return e}}function N(e,t){const n=e.match(t);if(void 0===n?.[1]||void 0===n[2])return;const r=Number.parseInt(n[1],10),a=Number.parseInt(n[2],10);return r<=0||a<=0?void 0:{x:0,y:0,width:r,height:a}}function M(e){const t=e.trim().replace(/^\uFEFF/,"");if(0===t.length)return;let n;try{n=JSON.parse(t)}catch{return}if(!P(n))return;const r=T(n.x),a=T(n.y),o=T(n.width),i=T(n.height);return void 0===r||void 0===a||void 0===o||void 0===i||o<=0||i<=0?void 0:{x:r,y:a,width:o,height:i}}function P(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function T(e){if("number"==typeof e&&Number.isInteger(e))return e;if("string"==typeof e&&e.trim().length>0){const t=Number(e.trim());return Number.isInteger(t)?t:void 0}}function B(){try{return _(y("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function D(){try{return M(y("powershell.exe",["-NoProfile","-NonInteractive","-Command",R],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function O(){try{const e=y("xrandr",["--current"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).match(/(\d+)x(\d+)\+/);if(void 0===e?.[1]||void 0===e[2])return;const t=Number.parseInt(e[1],10),n=Number.parseInt(e[2],10);if(t<=0||n<=0)return;return{x:0,y:0,width:t,height:n}}catch{return}}var $,F,j,L,q="default",z=l(c(),".roll-agent","browser","sessions"),V=["#2563EB","#DC2626","#16A34A","#D97706","#7C3AED","#0891B2","#DB2777","#65A30D"],J=["stopped","not_running","not_found","failed"],U=new o,H=class{bundles=new Map;defaultInstanceId;startPromises=new Map;constructor(e,t){if(void 0===t||0===Object.keys(t.instances).length){const t=ne({id:q,runtimeConfig:e});return this.bundles.set(t.id,t),void(this.defaultInstanceId=t.id)}this.defaultInstanceId=t.defaultInstance;const n=Object.entries(t.instances);for(const[t,[r,a]]of n.entries()){const o=ne({id:r,runtimeConfig:Y(e,r,a,{index:t,total:n.length}),...void 0!==a.platform?{platform:a.platform}:{},...void 0!==a.trackingAgentId?{trackingAgentId:a.trackingAgentId}:{}});this.bundles.set(r,o)}}getDefaultInstanceId(){return this.defaultInstanceId}listBundles(){return[...this.bundles.values()]}isLegacySingleInstancePool(){return 1===this.bundles.size&&this.bundles.keys().next().value===q}resolvePrimaryInstanceId(){return this.defaultInstanceId??this.getOnlyInstanceId()}async ensureBundleStarted(e){const t=this.getBundle(e);return await this.ensureStarted(t.id),t}async ensureStarted(e){const t=this.bundles.get(e);if(void 0===t)throw new Error(`Browser instance "${e}" was not found.`);if(t.runtime.isRunning())return;const n=this.startPromises.get(e);if(void 0!==n)return void await n;const r=t.runtime.start().catch(async t=>{throw this.startPromises.delete(e),t});this.startPromises.set(e,r);try{await r}finally{this.startPromises.delete(e)}}getBundle(e){const t=e??U.getStore()??this.defaultInstanceId??this.getOnlyInstanceId();if(void 0===t)throw new u({code:"needs_input",message:"browserInstance is required because multiple browser instances are configured.",details:{availableInstances:[...this.bundles.keys()]}});const n=this.bundles.get(t);if(void 0===n)throw new u({code:"browser_instance_not_found",message:`Browser instance "${t}" was not found.`,details:{availableInstances:[...this.bundles.keys()]}});return n}async getInstanceStatuses(){return await Promise.all(this.listBundles().map(async e=>{const[t,n]=await Promise.all([f(e.config),re(e.config.userDataDir)]);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},mode:e.config.mode,cdp:t,profile:n,tracking:oe(e.trackingAgentId)}}))}async closeAll(){const e=(await this.closeInstances([...this.bundles.keys()])).filter(e=>"failed"===e.status).map(e=>new Error(`Failed to stop browser runtime for ${e.browserInstance}: ${e.message??"unknown error"}`));if(e.length>0)throw new AggregateError(e,"Browser instance pool shutdown failed")}async closeInstances(e){const t=[...new Set(e)];return await Promise.all(t.map(async e=>await this.closeInstance(e)))}async closeInstance(e){const t=this.bundles.get(e);if(void 0===t)return{browserInstance:e,status:"not_found",message:`Browser instance "${e}" was not found.`};const n=[],r=this.startPromises.get(e);if(void 0!==r)try{await r}catch(t){n.push(new Error(`Failed to finish browser startup for ${e}`,{cause:t}))}if(!t.runtime.isRunning())return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"not_running",mode:t.config.mode};try{await t.contextManager.closeAll()}catch(t){n.push(new Error(`Failed to close browser contexts for ${e}`,{cause:t}))}try{"managed-cdp"===t.config.mode?await t.runtime.stop():await t.runtime.disconnect()}catch(t){n.push(new Error(`Failed to stop browser runtime for ${e}`,{cause:t}))}return n.length>0?{browserInstance:e,status:"failed",mode:t.config.mode,message:W(n)}:{browserInstance:e,status:"stopped",mode:t.config.mode}}getOnlyInstanceId(){if(1===this.bundles.size)return this.bundles.keys().next().value}};function W(e){return e.map(e=>void 0===e.cause?e.message:`${e.message}: ${K(e.cause)}`).join("; ")}function K(e){return e instanceof Error?e.message:String(e)}async function G(e,t){return await U.run(e,t)}function Y(e,t,n,r){const a=n.headless??e.headless,o=n.windowBounds??te({instance:n,headless:a,...r});return g.parse({...e,mode:n.mode,headless:a,instanceId:t,profileName:n.profileName??t,profileColor:n.profileColor??X(r.index),cdpUrl:n.cdpUrl,cdpHost:n.cdpHost,cdpPort:n.cdpPort,channel:n.channel,executablePath:n.executablePath,userDataDir:n.userDataDir,args:n.args,windowBounds:o,sessionsDir:n.sessionsDir??l(z,t)})}function X(e){return V[e]??Z(e)}function Z(e){return Q(137.508*e%360,.68,.45)}function Q(e,t,n){const r=(1-Math.abs(2*n-1))*t,a=e/60,o=r*(1-Math.abs(a%2-1));let i,s,c;[i,s,c]=a<1?[r,o,0]:a<2?[o,r,0]:a<3?[0,r,o]:a<4?[0,o,r]:a<5?[o,0,r]:[r,0,o];const l=n-r/2;return`#${ee(i+l)}${ee(s+l)}${ee(c+l)}`}function ee(e){return Math.round(255*e).toString(16).padStart(2,"0").toUpperCase()}function te(e){if(!(e.total<=1||e.headless||"managed-cdp"!==e.instance.mode))return k({index:e.index,total:e.total,workArea:C()})}function ne(e){const t=new h(e.runtimeConfig.sessionsDir),n=new p(e.runtimeConfig),r=new m(n,t);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},...void 0!==e.trackingAgentId?{trackingAgentId:e.trackingAgentId}:{},runtime:n,contextManager:r,sessionStore:t,config:e.runtimeConfig}}async function re(e){if(void 0===e)return{exists:!1,writable:!1};return{userDataDir:e,exists:await ae(e,s.F_OK),writable:await ae(e,s.W_OK)}}async function ae(e,t){try{return await i(e,t),!0}catch{return!1}}function oe(e){if(void 0!==e)return{source:"instance",agentIdFingerprint:ie(e)};const t=process.env.RECRUITMENT_EVENTS_DEFAULT_AGENT_ID?.trim();return void 0!==t&&t.length>0?{source:"default-env",agentIdFingerprint:ie(t)}:{source:"missing"}}function ie(e){return d("sha256").update(e).digest("hex").slice(0,8)}var se=!1,ce=a("browser-use-agent");async function le(e,t){if($||F)return;se=!1;const n=($=new H(e,t)).getDefaultInstanceId(),r=$.listBundles()[0],a=void 0!==n?$.getBundle(n):r;void 0!==a&&(F=a.runtime,j=a.contextManager,L=a.sessionStore)}function de(){if($)return $.getBundle().runtime;if(!F)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return F}function ue(){if($)return $.getBundle().contextManager;if(!j)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return j}function me(e){se=e}function pe(){return se}function ge(){if(!$)throw new Error("BrowserInstancePool not initialized. Call initRuntime() first.");return $}async function fe(){await ge().ensureBundleStarted()}function he(){return void 0!==$&&!$.isLegacySingleInstancePool()}function ye(e){if(!$)return e;let t;try{t=$.getBundle()}catch{return}return t.trackingAgentId??e}function be(){return ge().getBundle()}async function ve(){const e=$,t=j,n=F,r=[];if($=void 0,j=void 0,F=void 0,L=void 0,se=!1,e){ce.info("Closing browser instance pool...");try{await e.closeAll()}catch(e){r.push(new Error("Failed to close browser instance pool",{cause:e})),ce.error(`Failed to close browser instance pool: ${e instanceof Error?e.stack??e.message:String(e)}`)}}else if(t){ce.info("Closing browser contexts...");try{await t.closeAll()}catch(e){r.push(new Error("Failed to close browser contexts",{cause:e})),ce.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(!e&&n){ce.info("Stopping browser process...");try{await n.stop()}catch(e){r.push(new Error("Failed to stop browser runtime",{cause:e})),ce.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(ce.info("Browser runtime shutdown complete"),r.length>0)throw new AggregateError(r,"Browser runtime shutdown failed")}var we=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),xe=n({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:we,execute:async(e,t)=>{const n=de();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as Se}from"@roll-agent/sdk";import{z as Re}from"zod";import{BrowserSecurityConfigSchema as Ce,BrowserStatusSchema as Ie}from"@roll-agent/browser";import{createHash as ke}from"node:crypto";import{z as Ae}from"zod";var Ee,_e,Ne=["REPLY_AUTHORITY_URL","REPLY_AUTHORITY_BEARER_TOKEN","REPLY_AUTHORITY_TIMEOUT_MS","REPLY_AUTHORITY_KEYS_URL","RECRUITMENT_EVENTS_ENABLED","RECRUITMENT_EVENTS_API_BASE_URL","RECRUITMENT_EVENTS_API_TOKEN","RECRUITMENT_EVENTS_DEFAULT_AGENT_ID","BROWSER_SECURITY_JSON","BROWSER_INSTANCES_JSON","BROWSER_USE_POLICY_JSON","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],Me=/^[0-9a-f]{8}$/,Pe=Ae.object({present:Ae.boolean(),fingerprint:Ae.string().regex(Me).optional()}),Te=Ae.record(Pe);function Be(e,t=process.env){return Object.fromEntries(e.map(e=>{const n=t[e];return"string"==typeof n&&n.length>0?[e,{present:!0,fingerprint:De(n)}]:[e,{present:!1}]}))}function De(e){return ke("sha256").update(e).digest("hex").slice(0,8)}function Oe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function $e(){return void 0!==Ee?Ee:Oe(process.env.BROWSER_VISUAL_CURSOR)??!0}function Fe(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function je(){return void 0!==_e?_e:Fe(process.env.BROWSER_VISUAL_ACTIVITY)??!0}import{StructuredToolError as Le}from"@roll-agent/sdk";import{z as qe}from"zod";import{randomUUID as ze}from"node:crypto";import{z as Ve}from"zod";var Je=Ve.object({id:Ve.string().trim().min(1)}),Ue=new Map;function He(e=Date.now()){for(const[t,n]of Ue)n.expiresAtMs<=e&&Ue.delete(t)}function We(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function Ke(e,t,n=Date.now()){He(n);const r=ze(),a=n+t;return Ue.set(r,{id:r,tool:e.tool,target:e.target,digest:e.digest,...void 0!==e.summary?{summary:e.summary}:{},expiresAtMs:a}),{id:r,expiresAt:new Date(a).toISOString(),tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{},retryInput:{toolActionApproval:{id:r}}}}function Ge(e){return He(),!!Ye(e)&&(Ue.delete(e.approval.id),!0)}function Ye(e){He();const t=Ue.get(e.approval.id);return void 0!==t&&!!We(e.subject,t)}var Xe=["log","deny","confirm"],Ze=qe.enum(Xe),Qe=["zhipin_send_prepared_reply"],et=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],tt=qe.object({code:qe.enum(et),message:qe.string()}),nt=qe.object({policy:Ze}),rt=qe.object({approvalTtlMs:qe.number().int().positive().max(36e5).default(3e5),tools:qe.record(nt).default({})}),at=rt.parse({}),ot=new Set(Qe),it=at;function st(e){if(void 0===e)return{};try{return JSON.parse(e)}catch(e){throw new Error(`BROWSER_USE_POLICY_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}}function ct(e=process.env){try{return rt.parse(st(e.BROWSER_USE_POLICY_JSON))}catch(e){if(e instanceof Error&&e.message.startsWith("BROWSER_USE_POLICY_JSON must be valid JSON"))throw e;throw new Error(`BROWSER_USE_POLICY_JSON is invalid: ${e instanceof Error?e.message:String(e)}`)}}function lt(e){it=rt.parse(e)}function dt(){return it}function ut(e){const t=e.toolPolicy??it,n=[];for(const e of Object.keys(t.tools))ot.has(e)||n.push({code:"unknown_tool_policy",message:`BROWSER_USE_POLICY_JSON 配置了未接入 tool-level policy 的工具: ${e}`});return"confirm"===e.browserSecurity.actionPolicy&&Object.values(t.tools).some(e=>"confirm"===e.policy)&&n.push({code:"double_confirmation",message:"BROWSER_SECURITY_JSON.actionPolicy=confirm 与 tool policy confirm 同时启用,可能导致双重确认。"}),"confirm"!==e.browserSecurity.actionPolicy&&"deny"!==e.browserSecurity.actionPolicy||n.push({code:"browser_action_policy_not_recommended",message:"BROWSER_SECURITY_JSON.actionPolicy=confirm/deny 是高级调试模式,Boss 日常编排建议使用 actionPolicy=log。"}),n}function mt(e,t){const n=it.tools[t.subject.tool],r=n?.policy??"log";if("log"===r)return e.logger.info(`Browser-use tool allowed by tool policy: ${t.subject.tool}`),{consumeApproval:()=>{}};if("deny"===r){const n="Tool execution denied by browser-use tool policy.";throw e.logger.warn(`${n} ${JSON.stringify(pt(t.subject,"tool_policy_deny"))}`),new Le({code:"action_denied",message:n,details:pt(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?Ye({approval:t.approval,subject:t.subject}):Ge({approval:t.approval,subject:t.subject})))return e.logger.info(`Browser-use tool approved by toolActionApproval: ${t.subject.tool}`),{consumeApproval:()=>{!0===t.deferApprovalConsumption&&void 0!==t.approval&&Ge({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...pt(t.subject,"tool_policy_confirm"),approvalRequest:Ke(t.subject,it.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(pt(t.subject,"tool_policy_confirm"))}`),new Le({code:"needs_confirmation",message:a,details:o})}function pt(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var gt=Ie.extend({replyAuthorityKeysLoaded:Re.boolean(),visualCursorEnabled:Re.boolean(),visualActivityEnabled:Re.boolean(),security:Ce,toolPolicy:rt,policyWarnings:Re.array(tt),effectiveEnvSources:Te}),ft=Se({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:Re.object({}),output:gt,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=ge(),r=n.listBundles(),a=n.resolvePrimaryInstanceId(),o=r.find(e=>e.id===a)??r[0];if(void 0===o)throw new Error("BrowserInstancePool has no runtime bundles.");const i=r.some(e=>e.runtime.isRunning()),{headless:s,mode:c,security:l}=o.runtime.getConfig(),d=dt(),u=[];for(const e of r){const t=e.runtime,n=e.contextManager,r=e.sessionStore,a=n.getActivePlatforms();for(const o of a){const a=n.getPageCount(o),i=n.getCurrentUrl(o);let s=null,c="unknown";if(t.shouldRestoreSessionSnapshot()){const[e,t]=await Promise.all([r.loadCookies(o),r.loadLocalStorage(o)]);s=void 0!==e&&e.length>0||void 0!==t&&Object.keys(t).length>0,c=s?"snapshot":"none"}else t.usesPersistentProfile()&&(s=null,c="profile");u.push({browserInstance:e.id,platform:o,pagesOpen:a,currentUrl:i,hasLoginState:s,loginStateSource:c})}}return{running:i,headless:s,mode:c,activeSessions:u,defaultInstanceId:n.getDefaultInstanceId(),primaryInstanceId:a,instances:await n.getInstanceStatuses(),replyAuthorityKeysLoaded:pe(),visualCursorEnabled:$e(),visualActivityEnabled:je(),security:l,toolPolicy:d,policyWarnings:ut({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:Be(Ne)}}});import{BrowserRuntimeModeSchema as ht}from"@roll-agent/browser";import{StructuredToolError as yt,defineTool as bt}from"@roll-agent/sdk";import{z as vt}from"zod";var wt=vt.object({browserInstance:vt.string().trim().min(1).optional(),browserInstances:vt.array(vt.string().trim().min(1)).min(1).optional(),all:vt.boolean().optional()}).superRefine((e,t)=>{const n=void 0!==e.browserInstance||void 0!==e.browserInstances&&e.browserInstances.length>0;e.all&&n&&t.addIssue({code:vt.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),xt=vt.object({browserInstance:vt.string(),status:vt.enum(J),mode:ht.optional(),message:vt.string().optional()}),St=vt.object({ok:vt.boolean(),stopped:vt.number().int().nonnegative(),results:vt.array(xt)}),Rt=bt({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:wt,output:St,execute:async(e,t)=>{const n=ge(),r=Ct(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return It(await n.closeInstances(r))}});function Ct(e,t){if(!0===e.all)return t;const n=[...void 0!==e.browserInstance?[e.browserInstance]:[],...e.browserInstances??[]],r=[...new Set(n)];if(0===r.length)throw new yt({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function It(e){return{ok:e.every(e=>"not_found"!==e.status&&"failed"!==e.status),stopped:e.filter(e=>"stopped"===e.status).length,results:[...e]}}import{defineTool as kt}from"@roll-agent/sdk";import{BrowserPageInfoSchema as At,PlatformSchema as Et}from"@roll-agent/browser";import{z as _t}from"zod";import{PLATFORMS as Nt}from"@roll-agent/browser";var Mt={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function Pt(e){return new URL(Mt[e]).host}function Tt(e,t){try{return new URL(e).host.includes(Pt(t))}catch{return!1}}function Bt(e){return Nt.find(t=>Tt(e,t))}function Dt(e,t){const n=Bt(t.url)??null;return{pageId:t.targetId,url:t.url,title:t.title,boundPlatform:e.getBoundPlatformForNativePage(t.targetId)??null,detectedPlatform:n,isSelectedForPlatform:e.isNativePageSelected(t.targetId)}}async function Ot(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:Bt(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var $t=_t.object({platform:Et.optional().describe("可选:仅返回指定平台相关的页面")}),Ft=_t.object({pages:_t.array(At)}),jt=kt({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:$t,output:Ft,execute:async(e,t)=>{const n=ue();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>Dt(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{setTimeout as Lt}from"node:timers/promises";import{defineTool as qt}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as zt,BrowserPageInfoSchema as Vt}from"@roll-agent/browser";import{z as Jt}from"zod";import{StructuredToolError as Ut}from"@roll-agent/sdk";import{isBrowserActionPolicyError as Ht,preflightBrowserAction as Wt}from"@roll-agent/browser";import{randomUUID as Kt}from"node:crypto";var Gt=6e4,Yt=new Map;function Xt(e=Date.now()){for(const[t,n]of Yt)n.expiresAtMs<=e&&Yt.delete(t)}function Zt(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function Qt(e,t=Date.now()){Xt(t);const n=Kt(),r=t+Gt,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return Yt.set(n,a),{id:n,expiresAt:new Date(r).toISOString(),action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},retryInput:{browserActionApproval:{id:n}}}}function en(e){Xt();const t=Yt.get(e.approval.id);return void 0!==t&&(!!Zt(e.details,t)&&(Yt.delete(e.approval.id),!0))}function tn(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function nn(e,t,n={}){return{security:tn(t,n),approveAction:en,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function rn(e,t,n){const r=Wt({security:t.getConfig().security,action:n.action,target:n.target,...void 0!==n.url?{url:n.url}:{}});if(r.ok)return r.log&&e.logger.info(r.message),{approvedByConfirmation:!1};if("needs_confirmation"===r.code&&void 0!==n.approval&&en({approval:n.approval,details:r.details}))return e.logger.info(`Browser action approved by browserActionApproval: ${n.action} ${n.target}`),{approvedByConfirmation:!0};throw e.logger.warn(`${r.message} ${JSON.stringify(r.details)}`),new Ut({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:Qt(r.details)}:{}}})}function an(e){if(Ht(e))return new Ut({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:Qt(e.payload.details)}:{}}})}function on(){try{return de()}catch{return}}async function sn(e){await e.bringToFront().catch(()=>{})}async function cn(e,t={}){const n=t.runtime??on();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await sn(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await sn(e)}var ln,dn=15e3,un=250,mn=["/web/chat"],pn=Jt.object({url:Jt.string().url().describe("要导航到的目标 URL"),browserActionApproval:zt.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),gn=Jt.object({success:Jt.boolean(),page:Vt});function fn(){return{getContextManager:ue,getRuntime:de,detectPlatformFromUrl:Bt,matchesPlatformHost:Tt,toNativePageInfo:Dt,delay:Lt,...ln}}function hn(e){return"object"==typeof e&&null!==e}function yn(e){return!!hn(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function bn(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==mn.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function vn(e,t,n,r,a){const o=n.detectPlatformFromUrl(r),i=await t.listNativePages();if(o){const s=i.find(e=>n.matchesPlatformHost(e.url,o));if(s)return a.info(`Reusing native ${o} page for navigation`),await t.activateNativePage(s.targetId),e.rememberNativePageSelection(o,s),{page:s,platform:o};a.info(`Opening native ${o} page for navigation`);const c=await t.openNativePage(r);return e.rememberNativePageSelection(o,c),{page:c,platform:o}}return a.info("Opening a new native page for non-platform navigation"),{page:await t.openNativePage(r)}}async function wn(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!yn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function xn(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<dn;){try{const t=await wn(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Sn(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(un)}throw new Error(`Native navigation did not reach document ready state within ${dn}ms${o?`: ${o.message}`:""}`)}function Sn(e,t,n){if(e===t)return!0;try{const t=new URL(e);return("http:"===t.protocol||"https:"===t.protocol)&&e!==n}catch{return!1}}async function Rn(e,t,n){t.url!==n&&await e.navigate(n)}async function Cn(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var In=qt({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:pn,output:gn,execute:async(e,t)=>{const n=fn(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);bn(e.url,o);const i=rn(t,a,{action:"navigate",target:e.url,url:e.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Native navigating to ${e.url}`);const s=await vn(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await Rn(c,s.page,e.url);const o=await xn(c,n,e.url,s.page.url),i=await Cn(a,s.page,o),l=s.platform??n.detectPlatformFromUrl(i.url);return l&&(r.rememberNativePageSelection(l,i),t.logger.info(`Bound native navigated page to ${l}`)),{success:!0,page:n.toNativePageInfo(r,i)}}finally{c.close()}}});import{BrowserActionApprovalSchema as kn,BrowserPageInfoSchema as An}from"@roll-agent/browser";import{defineTool as En}from"@roll-agent/sdk";import{z as _n}from"zod";import{setTimeout as Nn}from"node:timers/promises";var Mn=15e3,Pn=250,Tn="__rollReloadMark";function Bn(e){return"object"==typeof e&&null!==e}function Dn(e){if(!Bn(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function On(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function $n(e){const t=await e.evaluateJson(`(() => ({ mark: window.${Tn} ?? null, readyState: document.readyState }))()`);if(!Dn(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function Fn(e,t){const n=(t.createToken??On)(),r=t.now??(()=>Date.now()),a=t.delay??Nn,o=t.timeoutMs??Mn,i=t.pollMs??Pn;await e.evaluateJson(`(() => { window.${Tn} = ${JSON.stringify(n)}; return true; })()`),await e.reload({url:t.url,...void 0!==t.ignoreCache?{ignoreCache:t.ignoreCache}:{}}),t.onReloadSent?.();const s=r();let c;for(;r()-s<o;){try{const t=await $n(e);if(t.mark!==n&&("interactive"===t.readyState||"complete"===t.readyState))return}catch(e){c=e instanceof Error?e:new Error(String(e))}await a(i)}throw new Error(`Native page reload did not swap document within ${String(o)}ms${c?`: ${c.message}`:""}`)}var jn,Ln=_n.object({ignoreCache:_n.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:kn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),qn=_n.object({success:_n.boolean(),reloaded:_n.boolean(),page:An});function zn(){return{getContextManager:ue,getRuntime:de,toNativePageInfo:Dt,reloadNativePageAndWaitForSwap:Fn,...jn}}function Vn(e,t){const n=t.filter(t=>e.isNativePageSelected(t.targetId));if(1===n.length){const e=n[0];if(e)return e}if(n.length>1)throw new Error("Multiple selected native tabs found; select a single tab before reloading.");if(1===t.length){const e=t[0];if(e)return e}if(0===t.length)throw new Error("No native page is open; navigate before reloading.");throw new Error("Multiple native pages are open and none is selected; select the target tab before reloading.")}var Jn=En({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:Ln,output:qn,execute:async(e,t)=>{const n=zn(),r=n.getContextManager(),a=n.getRuntime(),o=Vn(r,await a.listNativePages()),i=rn(t,a,{action:"navigate",target:o.url,url:o.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Reloading native tab ${o.targetId} (${o.url})`);const s=await a.connectNativePage(o,{...nn(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:o.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:a}),await n.reloadNativePageAndWaitForSwap(s,{url:o.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{}});const t=(await a.listNativePages()).find(e=>e.targetId===o.targetId)??o;return{success:!0,reloaded:!0,page:n.toNativePageInfo(r,t)}}finally{s.close()}}});import{defineTool as Un}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Hn,BrowserPageInfoSchema as Wn,PlatformSchema as Kn}from"@roll-agent/browser";import{z as Gn}from"zod";async function Yn(e,t){return(await e.listNativePages()).find(e=>Tt(e.url,t))}async function Xn(e,t,n={}){const r=await Yn(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(Mt[t],n),reusedExistingPage:!1}}var Zn=Gn.object({platform:Kn.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:Hn.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Qn=Gn.object({success:Gn.boolean(),page:Wn,reusedExistingTab:Gn.boolean()}),er=Un({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:Zn,output:Qn,execute:async(e,t)=>{const{platform:n}=e,r=de(),a=ue(),o=Mt[n],i=rn(t,r,{action:"navigate",target:o,url:o,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}});t.logger.info(`Opening platform page for ${n}`);const{page:s,reusedExistingPage:c}=await Xn(r,n,nn(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:Dt(a,s),reusedExistingTab:c}}});import{defineTool as tr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as nr,PlatformSchema as rr}from"@roll-agent/browser";import{z as ar}from"zod";var or=ar.object({platform:rr.describe("要将该页面绑定为当前活跃页的平台"),pageId:ar.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),ir=ar.object({success:ar.boolean(),page:nr}),sr=tr({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:or,output:ir,execute:async(e,t)=>{const n=ue();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const r=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:Dt(n,r)}}});import{defineTool as cr}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as lr,BrowserPageInfoSchema as dr,createBrowserAxSnapshot as ur}from"@roll-agent/browser";import{z as mr}from"zod";import{BrowserElementRefStore as pr}from"@roll-agent/browser";var gr=new pr;async function fr(e){const t=await e.runtime.listNativePages();if(void 0!==e.pageId){const n=t.find(t=>t.targetId===e.pageId);if(void 0===n)throw new Error(`Page "${e.pageId}" not found. Run list_pages to inspect pageId values.`);return n}const n=t.filter(t=>e.ctxManager.isNativePageSelected(t.targetId));if(1===n.length&&void 0!==n[0])return n[0];if(1===t.length&&void 0!==t[0])return t[0];throw new Error("browser_snapshot/click_ref/type_ref need pageId when multiple native pages are open. Run list_pages first.")}import{randomUUID as hr}from"node:crypto";var yr="data-roll-browser-action-",br=120;function vr(){return`${yr}${hr().replaceAll("-","")}`}function wr(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),br)):br}function xr(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Sr(e){if(!xr(e)||!xr(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function Rr(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Cr(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ir(e);return void 0===t?[]:[t]}):[]}function Ir(e){if(!xr(e))return;const t=e.nodeId,n=e.backendNodeId,r=Rr(e.attributes),a=Ir(e.contentDocument),o=Ir(e.templateContent);return{..."number"==typeof t&&Number.isInteger(t)?{nodeId:t}:{},..."number"==typeof n&&Number.isInteger(n)?{backendNodeId:n}:{},...void 0!==r?{attributes:r}:{},children:Cr(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Cr(e.pseudoElements),shadowRoots:Cr(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function kr(e,t){if(void 0!==t)for(let n=0;n<t.length-1;n+=2)if(t[n]===e)return t[n+1]}function Ar(e){if(!xr(e))return;const t=e.marker,n=e.name;return"string"==typeof t&&"string"==typeof n&&0!==n.trim().length?{marker:t,name:n.trim(),disabled:!0===e.disabled,hasClassHint:!0===e.hasClassHint,hasCursorPointer:!0===e.hasCursorPointer,hasOnClick:!0===e.hasOnClick,hasTabIndex:!0===e.hasTabIndex,isEditable:!0===e.isEditable}:void 0}function Er(e){return Array.isArray(e)?e.flatMap(e=>{const t=Ar(e);return void 0===t?[]:[t]}):[]}function _r(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function Nr(e){const t=[];return e.hasCursorPointer&&t.push("cursor:pointer"),e.hasOnClick&&t.push("onclick"),e.hasTabIndex&&t.push("tabindex"),e.isEditable&&t.push("contenteditable"),e.hasClassHint&&t.push("class:action"),t}function Mr(e,t){const n=new Map;for(const r of t){const t=kr(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Pr(e,t){const n=new Map,r=t=>{const a=kr(e,t.attributes);void 0!==a&&void 0!==t.backendNodeId&&n.set(a,t.backendNodeId);for(const e of t.children)r(e);void 0!==t.contentDocument&&r(t.contentDocument);for(const e of t.pseudoElements)r(e);for(const e of t.shadowRoots)r(e);void 0!==t.templateContent&&r(t.templateContent)};return r(t),n}function Tr(e,t){return`(() => {\n const markerAttribute = ${JSON.stringify(e)};\n const maxCandidates = ${JSON.stringify(t)};\n const normalize = (value) => String(value ?? "").replace(/\\s+/g, " ").trim();\n const classTextOf = (element) => String(element.getAttribute("class") ?? "");\n const directTextOf = (element) => normalize(Array.from(element.childNodes)\n .filter((node) => node.nodeType === Node.TEXT_NODE)\n .map((node) => node.textContent ?? "")\n .join(" "));\n const visibleTextOf = (element) => normalize(\n element instanceof HTMLElement ? element.innerText : element.textContent\n );\n const hasCompositeOptionHint = (element) => {\n const pattern = /dropdown|menu|option|select|item/i;\n let current = element;\n for (let depth = 0; current && depth < 4; depth += 1) {\n if (pattern.test(classTextOf(current))) return true;\n current = current.parentElement;\n }\n return false;\n };\n const domActionNameOf = (element) => {\n const direct = directTextOf(element);\n if (direct) return direct;\n if (element.childElementCount === 0) return visibleTextOf(element);\n if (hasCompositeOptionHint(element)) return visibleTextOf(element);\n return "";\n };\n const isVisible = (element) => {\n if (element.closest("[hidden], [aria-hidden=\\"true\\"]")) return false;\n const style = window.getComputedStyle(element);\n if (style.visibility === "hidden" || style.display === "none") return false;\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const isDisabled = (element) =>\n element.matches(":disabled") ||\n element.getAttribute("aria-disabled") === "true" ||\n element.getAttribute("disabled") !== null;\n const isNativeSemantic = (element) => {\n const tag = element.tagName.toLowerCase();\n return (\n (tag === "a" && element.hasAttribute("href")) ||\n tag === "button" ||\n tag === "input" ||\n tag === "textarea" ||\n tag === "select" ||\n tag === "option" ||\n tag === "summary" ||\n element.hasAttribute("role")\n );\n };\n const hasNearbyClassHint = (element) => {\n const pattern = /btn|button|click|dropdown|tab|tabs|filter|menu|nav|option|select|switch|toggle/i;\n let current = element;\n for (let depth = 0; current && depth < 4; depth += 1) {\n if (pattern.test(classTextOf(current))) return true;\n current = current.parentElement;\n }\n return false;\n };\n const isCandidate = (element) => {\n if (!isVisible(element) || isNativeSemantic(element)) return false;\n const tag = element.tagName.toLowerCase();\n if (!["span", "div", "li", "label", "em", "i", "b", "strong"].includes(tag)) return false;\n const name = domActionNameOf(element);\n if (name.length === 0 || name.length > 100) return false;\n const style = window.getComputedStyle(element);\n const hasCursorPointer = style.cursor === "pointer";\n const hasOnClick = element.hasAttribute("onclick") || element.onclick !== null;\n const hasTabIndex = element.getAttribute("tabindex") !== null && element.getAttribute("tabindex") !== "-1";\n const contentEditable = element.getAttribute("contenteditable");\n const isEditable = contentEditable === "" || contentEditable === "true";\n const hasClassHint = hasNearbyClassHint(element);\n\n if (!hasCursorPointer && !hasOnClick && !hasTabIndex && !isEditable && !hasClassHint) {\n return false;\n }\n\n if (hasCursorPointer && !hasOnClick && !hasTabIndex && !isEditable && !hasClassHint) {\n const parent = element.parentElement;\n if (parent && window.getComputedStyle(parent).cursor === "pointer") return false;\n }\n return true;\n };\n const output = [];\n for (const element of document.querySelectorAll("body *")) {\n if (!isCandidate(element)) continue;\n if (element.closest("[" + markerAttribute + "]")) continue;\n const marker = String(output.length);\n const style = window.getComputedStyle(element);\n const contentEditable = element.getAttribute("contenteditable");\n element.setAttribute(markerAttribute, marker);\n output.push({\n marker,\n name: domActionNameOf(element),\n disabled: isDisabled(element),\n hasClassHint: hasNearbyClassHint(element),\n hasCursorPointer: style.cursor === "pointer",\n hasOnClick: element.hasAttribute("onclick") || element.onclick !== null,\n hasTabIndex: element.getAttribute("tabindex") !== null && element.getAttribute("tabindex") !== "-1",\n isEditable: contentEditable === "" || contentEditable === "true"\n });\n if (output.length >= maxCandidates) break;\n }\n return output;\n })()`}function Br(e){return`(() => {\n const markerAttribute = ${JSON.stringify(e)};\n const elements = document.querySelectorAll("[" + markerAttribute + "]");\n for (const element of elements) {\n element.removeAttribute(markerAttribute);\n }\n return elements.length;\n })()`}async function Dr(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!xr(n))return new Map;const r=Ir(n.root);return void 0===r?new Map:Pr(t,r)}const r=Sr(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Mr(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function Or(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=wr(n.maxCandidates??br);if(0===r)return[];const a=vr(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(yr.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=Er(await e.evaluateJson(Tr(a,r),i).catch(()=>[]));try{if(0===s.length)return[];const t=await Dr(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:_r(e),name:e.name,hints:Nr(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Br(a),i).catch(()=>{})}}var $r=28,Fr=90,jr=250,Lr=620,qr=8,zr=32,Vr=8;function Jr(e){return new Promise(t=>{setTimeout(t,e)})}function Ur(e,t,n){return Math.min(Math.max(e,t),n)}function Hr(e,t){return Math.max(0,Math.floor(e??t))}function Wr(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Kr(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function Gr(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function Yr(e){const t=e.x>=96?-72:72,n=e.y>=72?-36:36;return{x:Math.max(1,e.x+t),y:Math.max(1,e.y+n)}}function Xr(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function Zr(e,t){const n=Kr(e,t);if(n<=qr)return[e,t];const r=Ur(Math.round(n/30),Vr,zr),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=Ur(.08*n,4,28)*Xr(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=Gr(u),p=Math.sin(Math.PI*u)*l,g=Math.sin(.13*(e.x+t.y+17*i))*Math.min(1.8,n/180);d.push({x:Math.round(e.x+a*m+s*(p+g)),y:Math.round(e.y+o*m+c*(p+g))})}return d.push(t),d}function Qr(e,t){const n=Wr(t);return Zr(void 0===e?Yr(n):Wr(e),n)}var ea=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??Jr,this.defaultStepDelayMs=Hr(t.stepDelayMs,$r)}reset(e){this.lastPoint=void 0===e?void 0:Wr(e)}async moveTo(e,t={}){const n=Hr(t.stepDelayMs,this.defaultStepDelayMs),r=Qr(this.lastPoint,e),a=Math.max(r.length-1,0)*n;await(t.motionObserver?.previewMouseMotion({points:r,durationMs:a}));for(let e=0;e<r.length;e+=1){const a=r[e];void 0!==a&&(await this.dispatcher.dispatchMouseEvent({type:"mouseMoved",x:a.x,y:a.y,...void 0!==t.button?{button:t.button}:{},buttons:t.buttons??0}),e<r.length-1&&n>0&&await this.sleep(n))}return this.lastPoint=Wr(e),{points:r,durationMs:a}}async click(e,t={}){const n=t.button??"left",r=t.clickCount??1;await this.moveTo(e,{button:"none",buttons:0,...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{}}),await this.delayIfPositive(t.preClickDelayMs),await(t.motionObserver?.previewMouseClick?.({point:Wr(e),durationMs:Lr})),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:"left"===n?1:0,clickCount:r}),await this.delayIfPositive(t.pressDurationMs??Fr),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(e.x),y:Math.round(e.y),button:n,buttons:0,clickCount:r}),await this.delayIfPositive(t.settleMs??jr),this.lastPoint=Wr(e)}async drag(e,t,n={}){await this.moveTo(e,{button:"none",buttons:0,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mousePressed",x:Math.round(e.x),y:Math.round(e.y),button:"left",buttons:1,clickCount:1}),await this.delayIfPositive(n.pressDurationMs??Fr),this.reset(e),await this.moveTo(t,{button:"left",buttons:1,...void 0!==n.motionObserver?{motionObserver:n.motionObserver}:{},...void 0!==n.stepDelayMs?{stepDelayMs:n.stepDelayMs}:{}}),await this.dispatcher.dispatchMouseEvent({type:"mouseReleased",x:Math.round(t.x),y:Math.round(t.y),button:"left",buttons:0,clickCount:1}),this.lastPoint=Wr(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},ta=14,na=720;function ra(e){return`(() => {\n const args = ${JSON.stringify(e)};\n const activityRootId = "roll-agent-visual-activity-root";\n const activityViewportId = "roll-agent-visual-activity-viewport";\n const activityRegionId = "roll-agent-visual-activity-region";\n const activityCapsuleId = "roll-agent-visual-activity-capsule";\n const activityDotId = "roll-agent-visual-activity-dot";\n const activityLabelId = "roll-agent-visual-activity-label";\n const cursorRootId = "roll-agent-visual-cursor-root";\n const cursorPointerId = "roll-agent-visual-cursor-pointer";\n const cursorStateKey = "__rollVisualCursorState";\n\n const themes = {\n info: {\n accent: "#14b8a6",\n accentSoft: "rgba(20, 184, 166, 0.42)",\n accentGlow: "rgba(20, 184, 166, 0.18)",\n capsuleBg: "rgba(15, 23, 42, 0.82)",\n capsuleBorder: "rgba(45, 212, 191, 0.38)",\n text: "#F8FAFC",\n dot: "#2DD4BF"\n },\n success: {\n accent: "#22c55e",\n accentSoft: "rgba(34, 197, 94, 0.42)",\n accentGlow: "rgba(34, 197, 94, 0.18)",\n capsuleBg: "rgba(10, 24, 16, 0.86)",\n capsuleBorder: "rgba(74, 222, 128, 0.38)",\n text: "#F0FDF4",\n dot: "#4ADE80"\n },\n error: {\n accent: "#f59e0b",\n accentSoft: "rgba(245, 158, 11, 0.42)",\n accentGlow: "rgba(245, 158, 11, 0.2)",\n capsuleBg: "rgba(41, 24, 10, 0.88)",\n capsuleBorder: "rgba(251, 191, 36, 0.4)",\n text: "#FFFBEB",\n dot: "#FBBF24"\n }\n };\n\n const readRect = (selector, padding) => {\n if (!selector) return null;\n const element = document.querySelector(selector);\n if (!element) return null;\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return null;\n const safePadding = Math.max(padding ?? 0, 0);\n const left = Math.max(rect.left - safePadding, 0);\n const top = Math.max(rect.top - safePadding, 0);\n const right = Math.min(rect.right + safePadding, window.innerWidth);\n const bottom = Math.min(rect.bottom + safePadding, window.innerHeight);\n return {\n x: Math.round(left),\n y: Math.round(top),\n width: Math.max(Math.round(right - left), 0),\n height: Math.max(Math.round(bottom - top), 0),\n centerX: Math.round(rect.left + rect.width / 2),\n centerY: Math.round(rect.top + rect.height / 2)\n };\n };\n\n const normalizeActivityViewport = (viewport) => {\n viewport.style.position = "fixed";\n viewport.style.inset = "0";\n viewport.style.opacity = "0";\n viewport.style.pointerEvents = "none";\n viewport.style.display = "none";\n viewport.style.border = "0";\n viewport.style.boxShadow = "none";\n viewport.style.borderRadius = "0";\n viewport.style.transform = "none";\n viewport.style.transition = "none";\n viewport.style.background = "transparent";\n };\n\n const showActivityViewportFrame = (viewport, theme, mode) => {\n normalizeActivityViewport(viewport);\n viewport.style.display = "block";\n viewport.style.opacity = mode === "complete" ? "0.28" : "0.22";\n viewport.style.background =\n "linear-gradient(180deg, " +\n theme.accentGlow +\n " 0%, transparent 16%, transparent 84%, " +\n theme.accentGlow +\n " 100%), linear-gradient(90deg, " +\n theme.accentGlow +\n " 0%, transparent 12%, transparent 88%, " +\n theme.accentGlow +\n " 100%)";\n viewport.style.boxShadow =\n "inset 0 0 0 2px " + theme.accentSoft + ", inset 0 0 28px " + theme.accentGlow;\n };\n\n const ensureActivityRoot = () => {\n let root = document.getElementById(activityRootId);\n if (root) {\n const viewport = document.getElementById(activityViewportId);\n if (viewport) normalizeActivityViewport(viewport);\n return root;\n }\n root = document.createElement("div");\n root.id = activityRootId;\n root.style.position = "fixed";\n root.style.inset = "0";\n root.style.pointerEvents = "none";\n root.style.zIndex = "2147483646";\n\n const viewport = document.createElement("div");\n viewport.id = activityViewportId;\n normalizeActivityViewport(viewport);\n\n const region = document.createElement("div");\n region.id = activityRegionId;\n region.style.position = "fixed";\n region.style.left = "0";\n region.style.top = "0";\n region.style.width = "0";\n region.style.height = "0";\n region.style.borderRadius = "18px";\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n region.style.transition =\n "transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease";\n\n const capsule = document.createElement("div");\n capsule.id = activityCapsuleId;\n capsule.style.position = "fixed";\n capsule.style.left = "50%";\n capsule.style.top = "20px";\n capsule.style.display = "inline-flex";\n capsule.style.alignItems = "center";\n capsule.style.gap = "10px";\n capsule.style.padding = "10px 14px";\n capsule.style.borderRadius = "999px";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n capsule.style.transition = "opacity 180ms ease, transform 220ms ease";\n capsule.style.backdropFilter = "blur(12px)";\n capsule.style.fontSize = "13px";\n capsule.style.fontWeight = "600";\n capsule.style.lineHeight = "18px";\n capsule.style.whiteSpace = "nowrap";\n\n const dot = document.createElement("div");\n dot.id = activityDotId;\n dot.style.width = "8px";\n dot.style.height = "8px";\n dot.style.borderRadius = "999px";\n\n const label = document.createElement("div");\n label.id = activityLabelId;\n label.setAttribute("aria-live", "polite");\n\n capsule.append(dot, label);\n root.append(viewport, region, capsule);\n document.documentElement.append(root);\n return root;\n };\n\n const applyTheme = (themeName, mode) => {\n const theme = themes[themeName] ?? themes.info;\n const viewport = document.getElementById(activityViewportId);\n const region = document.getElementById(activityRegionId);\n const capsule = document.getElementById(activityCapsuleId);\n const dot = document.getElementById(activityDotId);\n if (!viewport || !region || !capsule || !dot) return;\n showActivityViewportFrame(viewport, theme, mode);\n region.style.border = "1px solid " + theme.accentSoft;\n region.style.background = theme.accentGlow;\n region.style.boxShadow =\n "0 0 0 1px " + theme.accentSoft + ", 0 16px 42px " + theme.accentGlow;\n capsule.style.border = "1px solid " + theme.capsuleBorder;\n capsule.style.background = theme.capsuleBg;\n capsule.style.color = theme.text;\n capsule.style.boxShadow =\n "0 18px 46px rgba(15, 23, 42, 0.24), 0 0 0 1px " + theme.capsuleBorder;\n dot.style.background = theme.dot;\n dot.style.boxShadow = "0 0 0 5px " + theme.accentGlow;\n };\n\n const renderActivity = () => {\n if (!args.activity) return;\n ensureActivityRoot();\n const viewport = document.getElementById(activityViewportId);\n const region = document.getElementById(activityRegionId);\n const capsule = document.getElementById(activityCapsuleId);\n const label = document.getElementById(activityLabelId);\n if (!viewport || !region || !capsule || !label) return;\n\n if (args.activity.mode === "clear") {\n normalizeActivityViewport(viewport);\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n return;\n }\n\n applyTheme(args.activity.tone ?? "info", args.activity.mode);\n\n if (typeof args.activity.label === "string") {\n label.textContent = args.activity.label;\n }\n\n capsule.style.opacity = "1";\n capsule.style.transform = "translate(-50%, 0)";\n\n const rect = args.activity.selector\n ? readRect(args.activity.selector, args.activity.padding ?? 14)\n : null;\n if (rect) {\n region.style.width = rect.width + "px";\n region.style.height = rect.height + "px";\n region.style.transform = "translate(" + rect.x + "px, " + rect.y + "px)";\n region.style.opacity = "1";\n } else {\n region.style.opacity = "0";\n region.style.transform = "translate(-9999px, -9999px)";\n }\n\n if (args.activity.mode === "complete") {\n window.setTimeout(() => {\n normalizeActivityViewport(viewport);\n region.style.opacity = "0";\n capsule.style.opacity = "0";\n capsule.style.transform = "translate(-50%, -8px)";\n }, Math.max(args.activity.lingerMs ?? 720, 0));\n }\n };\n\n const ensureCursorRoot = () => {\n let root = document.getElementById(cursorRootId);\n if (root) return root;\n root = document.createElement("div");\n root.id = cursorRootId;\n root.style.position = "fixed";\n root.style.left = "0";\n root.style.top = "0";\n root.style.width = "0";\n root.style.height = "0";\n root.style.pointerEvents = "none";\n root.style.zIndex = "2147483647";\n\n const pointer = document.createElement("div");\n pointer.id = cursorPointerId;\n pointer.setAttribute("aria-hidden", "true");\n pointer.style.position = "fixed";\n pointer.style.left = "0";\n pointer.style.top = "0";\n pointer.style.width = "24px";\n pointer.style.height = "24px";\n pointer.style.opacity = "0";\n pointer.style.transform = "translate(-9999px, -9999px)";\n pointer.style.willChange = "transform, opacity";\n pointer.style.filter = "drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))";\n pointer.innerHTML =\n '<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">' +\n '<path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/>' +\n '</svg>';\n\n root.append(pointer);\n document.documentElement.append(root);\n return root;\n };\n\n const readCursorPath = () => {\n if (!Array.isArray(args.cursor?.path?.points)) return null;\n const points = args.cursor.path.points\n .map((point) => {\n const x = Number(point?.x);\n const y = Number(point?.y);\n return Number.isFinite(x) && Number.isFinite(y)\n ? { centerX: Math.round(x), centerY: Math.round(y) }\n : null;\n })\n .filter((point) => point !== null);\n return points.length > 0 ? points : null;\n };\n\n const readCursorClick = () => {\n const point = args.cursor?.click?.point;\n const x = Number(point?.x);\n const y = Number(point?.y);\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n return {\n point: { centerX: Math.round(x), centerY: Math.round(y) },\n durationMs: Math.max(Number(args.cursor?.click?.durationMs ?? 620), 0)\n };\n };\n\n const renderClickPulse = (root, point, durationMs) => {\n const createPulse = (size, border, background, shadow, startScale, endScale, delayMs) => {\n const radius = size / 2;\n const pulse = document.createElement("div");\n pulse.setAttribute("aria-hidden", "true");\n pulse.style.position = "fixed";\n pulse.style.left = "0";\n pulse.style.top = "0";\n pulse.style.width = size + "px";\n pulse.style.height = size + "px";\n pulse.style.borderRadius = "9999px";\n pulse.style.border = border;\n pulse.style.background = background;\n pulse.style.boxShadow = shadow;\n pulse.style.pointerEvents = "none";\n pulse.style.opacity = "0.96";\n pulse.style.transformOrigin = "center center";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + startScale + ")";\n pulse.style.transition =\n "transform " + durationMs + "ms cubic-bezier(0.16, 1, 0.3, 1), opacity " + durationMs + "ms ease-out";\n root.append(pulse);\n\n window.setTimeout(() => {\n window.requestAnimationFrame(() => {\n pulse.style.opacity = "0";\n pulse.style.transform =\n "translate(" + (point.centerX - radius) + "px, " + (point.centerY - radius) + "px) scale(" + endScale + ")";\n });\n }, delayMs);\n\n window.setTimeout(() => {\n pulse.remove();\n }, durationMs + delayMs + 80);\n };\n\n createPulse(\n 28,\n "3px solid rgba(45, 212, 191, 0.98)",\n "rgba(20, 184, 166, 0.24)",\n "0 0 0 5px rgba(20, 184, 166, 0.18), 0 0 26px rgba(20, 184, 166, 0.34)",\n 0.55,\n 2.35,\n 0\n );\n createPulse(\n 14,\n "2px solid rgba(255, 255, 255, 0.95)",\n "rgba(255, 255, 255, 0.34)",\n "0 0 18px rgba(45, 212, 191, 0.45)",\n 0.7,\n 1.55,\n 65\n );\n };\n\n const renderCursor = () => {\n const path = readCursorPath();\n const click = readCursorClick();\n const point = path ? path[path.length - 1] : click?.point ?? null;\n if (!point) return;\n const root = ensureCursorRoot();\n const pointer = document.getElementById(cursorPointerId);\n if (!pointer) return;\n const targetX = point.centerX - 2;\n const targetY = point.centerY - 2;\n if (path && path.length > 1 && typeof pointer.animate === "function") {\n for (const animation of pointer.getAnimations()) {\n animation.cancel();\n }\n pointer.style.opacity = "1";\n const keyframes = path.map((entry) => ({\n transform: "translate(" + (entry.centerX - 2) + "px, " + (entry.centerY - 2) + "px)"\n }));\n const duration = Math.max(args.cursor.path.durationMs ?? 180, 0);\n const animation = pointer.animate(keyframes, {\n duration,\n easing: "cubic-bezier(0.22, 1, 0.36, 1)",\n fill: "forwards"\n });\n animation.onfinish = () => {\n pointer.style.transform = "translate(" + targetX + "px, " + targetY + "px)";\n };\n window[cursorStateKey] = { x: point.centerX, y: point.centerY };\n if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n return;\n }\n pointer.style.transition = "opacity 120ms ease";\n pointer.style.opacity = "1";\n pointer.style.transform = "translate(" + targetX + "px, " + targetY + "px)";\n window[cursorStateKey] = { x: point.centerX, y: point.centerY };\n if (click) {\n renderClickPulse(root, click.point, click.durationMs);\n }\n };\n\n renderActivity();\n renderCursor();\n return true;\n })()`}var aa=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!je()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...je()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??ta,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){$e()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){$e()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=na){return!!je()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!je()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(ra(e))}catch{return!1}}};function oa(e){return new aa(e)}async function ia(e,t,n){const r=new ea(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var sa=mr.object({pageId:mr.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:mr.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:mr.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:mr.boolean().default(!0).describe("默认 true:只返回可交互节点")}),ca=mr.object({page:dr,snapshot:lr});function la(e){const t=[],n=e=>{"iframe"===e.role.toLowerCase()&&void 0!==e.backendNodeId&&t.push(e);for(const t of e.children??[])n(t)};for(const t of e)n(t);return t}async function da(e,t){if(void 0===t.backendNodeId)return;const n=await e.describeNode({backendNodeId:t.backendNodeId,depth:1}).catch(()=>{});return n?.contentDocumentFrameId??n?.frameId}async function ua(e,t){const n=la(t);return(await Promise.all(n.map(async t=>{const n=await da(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function ma(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function pa(e){return e.refs.reduce((e,t)=>Math.max(e,ma(t.ref)),0)}function ga(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ga(n.nodes,t)]})}function fa(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=fa(e.children??[],t),a=void 0===n?[]:fa(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function ha(e){let t=pa(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await ua(e.controller,e.snapshot.nodes)];for(let s=0;s<i.length;s+=1){const c=i[s];if(void 0===c)continue;if(n<=0){r=!0;break}if(void 0===c.node.ref||a.has(c.frameId))continue;a.add(c.frameId);const l=await Or(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await ur(e.controller,{depthOffset:c.node.depth+1,domActionHints:l,frameId:c.frameId,initialRefCount:t,interactiveOnly:e.snapshot.interactiveOnly,maxNodes:n,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}).catch(()=>{});if(void 0===d||0===d.nodeCount||0===d.refs.length)continue;const u=await ua(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=pa(d),n=Math.max(0,n-d.nodeCount),r=r||d.truncated}if(0===o.length)return{...e.snapshot,truncated:r};const s=new Map(o.map(e=>[e.iframeRef,e.snapshot])),c=o.flatMap(e=>e.snapshot.refs),l=o.reduce((e,t)=>e+t.snapshot.nodeCount,0);return{...e.snapshot,nodes:e.snapshot.interactiveOnly?ga(e.snapshot.nodes,s):fa(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}var ya=cr({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,并为可交互节点生成 @eN ref;默认只返回可交互节点。",input:sa,output:ca,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}});t.logger.info(`Creating browser AX snapshot for page ${a.targetId}`);const o=await n.connectNativePage(a),i=oa(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await Or(o,s),l=await ur(o,{domActionHints:c,interactiveOnly:e.interactiveOnly??!0,maxNodes:s,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),d=await ha({controller:o,snapshot:l,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}});return gr.saveSnapshot(a.targetId,d),await i.succeed(`已识别 ${d.refs.length} 个可操作元素`),{page:Dt(r,a),snapshot:d}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});import{StructuredToolError as ba,defineTool as va}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as wa,BrowserElementRefHandleSchema as xa,clickElementRef as Sa}from"@roll-agent/browser";import{z as Ra}from"zod";import{BrowserElementRefHandleSchema as Ca}from"@roll-agent/browser";import{z as Ia}from"zod";var ka=Ia.enum(["backend_node_id","role_name_nth"]),Aa=Ia.object({ref:Ca,role:Ia.string(),name:Ia.string(),x:Ia.number(),y:Ia.number(),resolvedBy:ka,backendNodeId:Ia.number().int().positive().optional(),frameId:Ia.string().optional(),disabled:Ia.boolean()}),Ea=Ia.object({success:Ia.literal(!0),ref:Ca,resolvedBy:ka,target:Aa}),_a=Ra.object({ref:xa.describe("browser_snapshot 返回的 @eN element ref"),pageId:Ra.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:wa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Na=va({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:_a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new ba({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在点击 ${e.ref}`);const r=await Sa({controller:s,elementRef:o,options:{clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已点击 ${e.ref}`),r}catch(t){await oa(s).fail(`点击 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{StructuredToolError as Ma,defineTool as Pa}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as Ta,BrowserElementRefHandleSchema as Ba,typeElementRef as Da}from"@roll-agent/browser";import{z as Oa}from"zod";var $a=Oa.object({ref:Ba.describe("browser_snapshot 返回的 @eN element ref"),text:Oa.string().describe("要输入的文本"),clear:Oa.boolean().default(!1).describe("输入前是否先清空当前控件内容"),pageId:Oa.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:Ta.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Fa=Pa({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:$a,output:Ea,execute:async(e,t)=>{const n=de(),r=ue(),a=await fr({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}}),o=gr.getRef(a.targetId,e.ref);if(void 0===o)throw new Ma({code:"not_found",message:`Element ref ${e.ref} is not available. Run browser_snapshot for this page first.`,details:{ref:e.ref,pageId:a.targetId}});const i=rn(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...nn(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await cn({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const t=oa(s);await t.begin(`正在输入到 ${e.ref}`);const r=await Da({controller:s,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await ia(s,t,e)}}});return await t.succeed(`已输入到 ${e.ref}`),r}catch(t){await oa(s).fail(`输入到 ${e.ref} 失败`);const n=an(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});import{defineTool as ja}from"@roll-agent/sdk";import{BrowserPageInfoSchema as La}from"@roll-agent/browser";import{z as qa}from"zod";import{setTimeout as za}from"node:timers/promises";function Va(e){return e.trim().toLocaleLowerCase("zh-CN")}function Ja(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function Ua(e,t){if(void 0!==t.conversationId){const n=e.find(e=>e.conversationId===t.conversationId);if(n)return n}const n=t.candidateName;if(n){const t=Va(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>Va(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=Va(e.name);return n.includes(t)||t.includes(n)}),a)return a;const o=t.length<=2?1:t.length<=4?.75:.6;if(a=r.find(e=>{const n=Va(e.name);return Ja(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var Ha=["chat-list","chat-history","recommend-list"],Wa={"chat-list":{surface:"chat-list",defaultDirection:"down",containerSelectors:[".user-list.b-scroll-stable",".chat-user .user-list",".chat-user-list",".chat-list-wrapper",".chat-list-wrap",".chat-user .b-scroll-stable",".b-scroll-stable",".chat-user .user-container",".chat-user"],itemSelector:'.user-list.b-scroll-stable [role="listitem"], .geek-item',highlightSelector:".user-list.b-scroll-stable, .chat-user .user-container, .chat-user"},"chat-history":{surface:"chat-history",defaultDirection:"up",containerSelectors:[".conversation-message",".chat-message-list",".conversation-main",".conversation-box"],itemSelector:".chat-message-list > .message-item, .conversation-message .message-item",highlightSelector:".conversation-message, .chat-message-list, .conversation-main"},"recommend-list":{surface:"recommend-list",defaultDirection:"down",containerSelectors:["#recommend-list",".list-wrap.card-list-wrap",".card-list-wrap",".recommend-list-wrap",".recommend-list",".card-list",".candidate-list",".candidate-body",".geek-list",".list-wrap",".recommendV2"],itemSelector:".candidate-card-wrap, li.card-item, .geek-item",highlightSelector:".candidate-card-wrap, li.card-item, .geek-item"}};function Ka(e){return Wa[e]}import{StructuredToolError as Ga,isStructuredToolError as Ya}from"@roll-agent/sdk";var Xa="zhipin_access_restricted",Za=["/web/passport/zp/403.html","/web/common/403.html","/web/passport/cm/403.html"],Qa=["/web/passport/zp/verify.html","/web/user/safe/verify-slider","/web/passport/cm/verify.html"],eo=["/web/passport/zp/security.html","/web/common/security-check.html"],to=["/web/user"],no="38",ro={31:"ip_block","-1000031":"ip_block",5002:"ip_block",32:"uid_block",5003:"uid_block",5004:"uid_block"},ao={"访问受限":"ip_block","安全验证":"verify"},oo={ip_block:"IP 级封禁:请等待页面标明的解封时间;刷新、换工具、换 browserInstance/profile 均无效。",uid_block:"账号级封禁:请等待账号解封;刷新、换工具、换 browserInstance/profile 均无效。",verify:"请在浏览器中手动完成验证并等待后再操作,不要立即重试。",security:"请在浏览器中手动完成安全检查并等待后再操作,不要立即重试。",anti_spider_login:"站点反爬拦截要求登录:请人工登录并等待后再操作,不要自动重试。"};function io(e){const t=e.url.trim(),n=(e.title??"").trim(),r=mo(t);if(uo(r,Za))return{kind:ro[po(t)??""]??"ip_block",url:t,title:n};if(uo(r,Qa))return{kind:"verify",url:t,title:n};if(uo(r,eo))return{kind:"security",url:t,title:n};if(uo(r,to)&&po(t)===no)return{kind:"anti_spider_login",url:t,title:n};for(const e of Object.keys(ao))if(n.includes(e))return{kind:ao[e],url:t,title:n};return null}function so(e){const t=io(e);if(null!==t)throw co(t)}function co(e){return new Ga({code:Xa,message:`BOSS 风控页已出现(${e.kind})。${oo[e.kind]}`,details:{kind:e.kind,url:e.url,title:e.title}})}function lo(e){if(Ya(e))throw e}function uo(e,t){return t.some(t=>e===t||e.startsWith(`${t}/`))}function mo(e){try{return new URL(e).pathname}catch{const t=e.split("#")[0]??e,n=t.split("?")[0]??t,r=n.indexOf("://"),a=r>=0?n.slice(r+3):n,o=a.indexOf("/");return o>=0?a.slice(o):a}}function po(e){try{return new URL(e).searchParams.get("code")??void 0}catch{const t=e.split("?")[1]?.split("#")[0];if(void 0===t||0===t.length)return;for(const e of t.split("&")){const t=e.indexOf("=");if("code"!==(t>=0?e.slice(0,t):e))continue;const n=t>=0?e.slice(t+1):"";try{return decodeURIComponent(n)}catch{return n}}return}}var go={login:{notLoggedIn:".header-login-btn"},unread:{container:".chat-list-wrap",listItem:'[role="listitem"]',geekItemWrap:".geek-item-wrap",item:".chat-item",unreadBadge:".badge-count",unreadBadgeNew:".badge-count.badge-count-common-less",unreadBadgeSpan:".badge-count span",unreadDot:".red-dot",figure:".figure",badge:".badge",candidateName:".candidate-name",candidateNameAlt:".chat-item-name",candidateNameSelectors:'[class*="name"], .nickname, .geek-name',candidateNameNew:".geek-name",jobTitle:".source-job",lastMessage:".push-text",lastMessageAlt:".chat-last-msg",messageTime:".time, .time-shadow",clickArea:".chat-item-content",unreadCandidates:".geek-item"},chat:{chatContainer:".chat-container",messageList:".message-list",messageItem:".message-item",messageContent:".message-content",messageText:".text-content",userMessage:".message-right",candidateMessage:".message-left",messageTime:".message-time",senderName:".sender-name",inputBox:".chat-input",inputTextarea:"textarea.chat-input",inputEditorId:"#boss-chat-editor-input",sendButton:".btn-send",conversationEditor:".conversation-editor",submitButton:".submit-content .submit",submitButtonActive:".submit-content .submit.active",submitContent:".submit-content",sendButtonAlt:".conversation-editor .submit-content",sendIcon:".submit-content .icon-send",systemMessage:".system-msg"},chatDetails:{candidateInfoContainer:".base-info-single-container, .base-info-content",candidateName:".name-box, .geek-name, .base-name",candidateInfoItem:".geek-info-item, .base-info-item, .base-info-single-detial > div",candidateTag:".geek-tag, .high-light-boss",communicationPosition:".position-name",communicationPositionAlt:".position-item:not(.expect) .value.high-light-boss",candidateExpectContainer:".position-item.expect",candidateExpectValue:".position-item.expect .value.job",candidateExpectSalary:".position-item.expect .high-light-orange",candidatePosition:".geek-position, .position-name",candidatePositionAlt:".position-content .value, .position-item .value",chatMessageContainer:".conversation-message, .message-list",messageItem:".message-item",messageTime:".message-time .time",messageTextSpan:".text span",systemMessage:".item-system",friendMessage:".item-friend",myMessage:".item-myself",resumeMessage:".item-resume",readStatus:".status-read"},exchangeWechat:{exchangeButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > span.operate-btn",exchangeButtonFallback:".operate-exchange-left .operate-btn",confirmDialog:".exchange-tooltip",confirmButton:".exchange-tooltip .btn-box .boss-btn-primary.boss-btn",confirmButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > div > div > span.boss-btn-primary.boss-btn",cancelButton:".exchange-tooltip .btn-box .boss-btn-outline.boss-btn",wechatCard:".message-card-top-wrap",wechatCardAlt:'[class*="d-top-text"]'},username:{primary:".nav-item.nav-logout .user-name",fallbacks:["#header > div > div > div.nav-item.nav-logout > div.top-profile-logout.ui-dropmenu.ui-dropmenu-drop-arrow > div.ui-dropmenu-label > div > span.user-name",".ui-dropmenu-label .user-name",".nav-logout .user-name","#header .user-name",".top-profile .user-name",".nav-user .user-name",".user-name",'[class*="user-name"]','[class*="username"]','[data-qa="user-name"]',".header-user-name","#header .label-name"]},nav:{sidebar:".side-wrap.side-wrap-v2",chatLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/index"]',recommendLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/recommend"]'},recommend:{iframe:"#recommendFrame",resumeIframe:'iframe[src*="c-resume"]',filterButton:".recommend-filter .filter-label, .filter-label-wrap .filter-label, .filter-label",filterPanel:".filter-panel",candidateItem:"[data-geek], .geek-item",candidateName:".name",candidateBaseInfo:".base-info",workExps:".timeline-wrap.work-exps",expectInfo:".row-flex, .timeline-wrap.expect",salaryWrap:".salary-wrap",tagsWrap:".tags-wrap",greetButton:".btn-greet, .op-btn",resumeCanvas:"div#resume > canvas#resume, canvas#resume",closeResumeBtn:".close-btn, .dialog-close",closeResumeBtnV2:".recommendV2 .close-btn"},candidateProfile:{panel:".candidate-info, .resume-info, .geek-info",name:".candidate-info .name, .geek-info .name",age:".candidate-info .age, .geek-info .age",gender:".candidate-info .gender, .geek-info .gender",experience:".candidate-info .experience, .geek-info .work-exp",education:".candidate-info .education, .geek-info .edu",expectedSalary:".candidate-info .salary, .geek-info .expect-salary",expectedPosition:".candidate-info .position, .geek-info .expect-position",activeTime:".candidate-info .active-time, .geek-info .active"}},fo=["不限","男","女"],ho=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],yo=["patch","replace"],bo=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:fo},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:ho},{key:"major",label:"专业",selection:"multi",clearValue:"不限"},{key:"recentNotView",label:"近期没有看过",selection:"single",clearValue:"不限"},{key:"exchangeResumeWithColleague",label:"是否与同事交换简历",selection:"single",clearValue:"不限"},{key:"candidateKeywords",label:"牛人关键词",selection:"multi",clearValue:"不限"},{key:"school",label:"院校",selection:"multi",clearValue:"不限"},{key:"switchJobFrequency",label:"跳槽频率",selection:"single",clearValue:"不限"},{key:"intention",label:"求职意向",selection:"multi",clearValue:"不限"},{key:"salary",label:"薪资待遇",selection:"single",clearValue:"不限"},{key:"degree",label:"学历要求",selection:"multi",clearValue:"不限"},{key:"experience",label:"经验要求",selection:"multi",clearValue:"不限"},{key:"callPhone",label:"是否可拨打电话",selection:"single",clearValue:"不限"}],vo=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function wo(e){const t=bo.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function xo(e){return void 0!==e.ageMin||void 0!==e.ageMax}var So=".candidate-card-wrap",Ro="[data-geek], .geek-item",Co=`${So}, ${Ro}`,Io="[data-geek], .card-inner, .geek-item",ko='.boss-popup__wrapper, .dialog-lib-resume, .boss-dialog, [data-type="boss-dialog"]',Ao=`${ko}, .recommendV2`,Eo=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],_o='iframe[src*="c-resume"]',No="canvas#resume, div#resume canvas";function Mo(e){const t=(e.recommendFrameRect?.x??0)+(e.resumeFrameRect?.x??0),n=(e.recommendFrameRect?.y??0)+(e.resumeFrameRect?.y??0);return{x:Math.round(t+e.canvasRect.x),y:Math.round(n+e.canvasRect.y),width:Math.round(e.canvasRect.width),height:Math.round(e.canvasRect.height)}}var Po=30,To=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Bo(e){const t=e.trim();return!(0===t.length||t.length>Po)&&(!To.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Do(e){let t=e.priority;return To.has(e.text.trim())&&(t+=10),e.text.trim().length>10&&(t+=5),/^[\u4e00-\u9fff]{2,4}$/.test(e.text.trim())&&(t-=.5),void 0!==e.xRatio&&(t-=4*(e.xRatio-.5)),t}function Oo(e){const t=e.filter(e=>Bo(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),r=n.get(t)??new Set;r.add(e.strategy),n.set(t,r)}const r=t.map(e=>{let t=Do(e);const r=n.get(e.text.trim())?.size??1;return r>1&&(t-=.5*r),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return r?{found:!0,username:r.evidence.text.trim(),strategy:r.evidence.strategy,source:r.evidence.source}:{found:!1}}var $o='.user-list.b-scroll-stable [role="listitem"], .geek-item',Fo='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',jo=".candidate-card-wrap",Lo="[data-geek], .geek-item",qo=`${jo}, ${Lo}`,zo=["/web/chat/recommend"],Vo=250,Jo=300,Uo=12,Ho=900,Wo=1200,Ko=4,Go=4,Yo=250,Xo=90,Zo=30,Qo=700,ei=800,ti=520,ni=16,ri=50,ai=650,oi=10,ii=.015,si="button, a, label, li, span, div, [role='button'], [role='radio']",ci=["visible","hidden","prerender","unloaded"],li=["not_chat_page"];function di(e){const t=/(初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/,n=/(\d{2,3}岁|(?:\d{2}年|\d{4}届|\d{4}年)?应届生|\d{2,4}年毕业|在校生|经验不限|无经验|\d+\s*年以上|[1一]年以内|\d+\s*-\s*\d+\s*年|\d+\s*年|初中及以下|中专\/中技|中专|中技|高中|大专|本科|硕士|博士)/g;function r(e){return e.replace(/\s+/g," ").trim()}function a(e){return e.replace(/\s+/g,"")}function o(e){return String(Number.parseInt(e,10))}function i(e){const n=a(e).match(/^(\d{2,3})岁$/);if(void 0!==n?.[1])return{kind:"age",value:`${n[1]}岁`};const r=e.match(t);if(void 0!==r?.[1])return{kind:"education",value:r[1]};const i=function(e){const t=a(e);if(/^(?:\d{2}年|\d{4}届|\d{4}年)?应届生$/.test(t))return"应届生";if(/^\d{2,4}年毕业$/.test(t))return;if("在校生"===t)return"在校生";if("经验不限"===t||"无经验"===t)return t;if("一年以内"===t)return"1年以内";const n=t.match(/^(\d+)年以上$/);if(void 0!==n?.[1])return`${o(n[1])}年以上`;const r=t.match(/^(\d+)-(\d+)年$/);if(void 0!==r?.[1]&&void 0!==r[2])return`${o(r[1])}-${o(r[2])}年`;const i=t.match(/^(\d+)年$/);return void 0!==i?.[1]?`${o(i[1])}年`:"1年以内"===t?"1年以内":void 0}(e);return void 0!==i?{kind:"experience",value:i}:{kind:"ignored"}}const s=e.map(e=>r(e)).filter(e=>e.length>0).flatMap(e=>function(e){const t=[...r(e).matchAll(n)].map(e=>r(e[1]??"")).filter(e=>e.length>0);return t.length>0?t:e.split(/[丨|·\n\r\t]+/).map(e=>r(e)).filter(e=>e.length>0)}(e)).map((e,t)=>({index:t,token:i(e)})),c=s.find(e=>"age"===e.token.kind)?.token,l=s.find(e=>"education"===e.token.kind)?.token,d="age"===c?.kind?c.value:"",u="education"===l?.kind?l.value:"",m=s.find(e=>"age"===e.token.kind)?.index??-1,p=s.find(e=>"education"===e.token.kind)?.index??-1,g=s.flatMap(e=>"experience"===e.token.kind?[{index:e.index,experience:e.token.value}]:[]),f=g.find(e=>(m<0||e.index>m)&&(p<0||e.index<p))?.experience??g[0]?.experience??"";return{age:d,experience:function(e,t){const n=function(e){const t=e.match(/^(\d{2,3})岁$/);if(void 0===t?.[1])return;const n=Number.parseInt(t[1],10);return Number.isFinite(n)?n:void 0}(e),r=function(e){const t=a(e),n=t.match(/^(\d+)-(\d+)年$/);if(void 0!==n?.[2])return Number.parseInt(n[2],10);const r=t.match(/^(\d+)年以上$/)??t.match(/^(\d+)年$/);return void 0!==r?.[1]?Number.parseInt(r[1],10):"1年以内"===t?1:void 0}(t);return void 0===n||void 0===r||r<=Math.max(0,n-12)}(d,f)?f:"",education:u}}function ui(e){return new Promise(t=>{setTimeout(t,e)})}function mi(e){return{x:e.x,y:e.y}}function pi(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function gi(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function fi(e){return e.trim().toLocaleLowerCase("zh-CN")}function hi(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function yi(e,t){const n=fi(e),r=fi(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function bi(e){return"string"==typeof e?e:""}function vi(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function wi(e){return"boolean"==typeof e&&e}function xi(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{};return{conversationId:bi(n.conversationId),candidateId:bi(n.candidateId),name:bi(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:bi(n.position),hasUnread:wi(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:bi(n.lastMessageTime),messagePreview:bi(n.messagePreview)}}):[]}function Si(e){return Array.isArray(e)?e.flatMap(e=>{if(!pi(e))return[];const t=e.text,n=e.strategy,r=e.priority,a=e.source,o=e.xRatio;return"string"!=typeof t||"role-link"!==n&&"role-button"!==n&&"leaf-text"!==n&&"css-fallback"!==n||"number"!=typeof r||"string"!=typeof a?[]:[{text:t,strategy:n,priority:r,source:a,..."number"==typeof o?{xRatio:o}:{}}]}):[]}function Ri(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:bi(n.candidateId),name:bi(n.name),age:bi(n.age),experience:bi(n.experience),education:bi(n.education),workStatus:bi(n.workStatus),company:bi(n.company),currentPosition:bi(n.currentPosition),expectedLocation:bi(n.expectedLocation),expectedPosition:bi(n.expectedPosition),expectedSalary:bi(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>bi(e)).filter(Boolean):[],buttonText:bi(n.buttonText)}}):[]}function Ci(e){return Array.isArray(e)?e.map((e,t)=>{const n=pi(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:bi(n.value),label:bi(n.label),isCurrent:wi(n.isCurrent)}}):[]}function Ii(e){return pi(e)?{found:wi(e.found),isOpen:wi(e.isOpen),currentLabel:bi(e.currentLabel),currentValue:bi(e.currentValue),options:Ci(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function ki(e){if(!pi(e))return null;const t=bi(e.conversationId),n=bi(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:bi(e.candidateName)}}function Ai(e){if(!pi(e))return null;const t=bi(e.candidateName);return t.length>0?{candidateName:t}:null}function Ei(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!pi(e))return[];const n=e.sender,r=e.messageType;return"candidate"!==n&&"recruiter"!==n&&"system"!==n||"text"!==r&&"system"!==r&&"resume"!==r&&"wechat-exchange"!==r?[]:[{index:"number"==typeof e.index&&Number.isInteger(e.index)?e.index:t,sender:n,messageType:r,content:bi(e.content),time:bi(e.time)}]}):[]}function _i(e){const t=pi(e)?e:{},n=t.tags;return{name:bi(t.name),age:bi(t.age),experience:bi(t.experience),education:bi(t.education),communicationPosition:bi(t.communicationPosition),expectedJobText:bi(t.expectedJobText),expectedSalary:bi(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>bi(e)).filter(Boolean):[]}}function Ni(e){const t=pi(e)?e:{};return{selectedTarget:ki(t.selectedTarget),activePanel:Ai(t.activePanel),candidateInfo:_i(t.candidateInfo),messages:Ei(t.messages)}}function Mi(e){return pi(e)?{found:wi(e.found),x:vi(e.x),y:vi(e.y)}:{found:!1,x:0,y:0}}function Pi(e){return pi(e)&&"number"==typeof e.x&&"number"==typeof e.y?{x:e.x,y:e.y}:null}function Ti(e){if(pi(e)&&"number"==typeof e.width&&"number"==typeof e.height)return{width:e.width,height:e.height}}function Bi(e){if(pi(e)&&"number"==typeof e.x&&"number"==typeof e.y&&"number"==typeof e.width&&"number"==typeof e.height)return{x:e.x,y:e.y,width:e.width,height:e.height}}function Di(e){return pi(e)?{dialogVisible:!0===e.dialogVisible,iframeFound:!0===e.iframeFound,iframeVisible:!0===e.iframeVisible,canvasReady:!0===e.canvasReady}:{dialogVisible:!1,iframeFound:!1,iframeVisible:!1,canvasReady:!1}}function Oi(e,t,n){if(!pi(e)||!0!==e.found)return{found:!1,error:"无法读取简历 canvas 状态"};const r=Bi(e.canvasRect);if(void 0===r)return{found:!1,error:"简历 canvas 坐标不完整"};const a=Mo({recommendFrameRect:n,resumeFrameRect:Pi(e.resumeFrameRect),canvasRect:r}),o=pi(t)?t:{},i=o.canvasSize,s=pi(i)&&"number"==typeof i.width&&"number"==typeof i.height?{width:i.width,height:i.height}:{width:Math.round(r.width),height:Math.round(r.height)},c=o.dataUrl,l=o.dataUrlError;return{found:!0,screenshotArea:a,canvasSize:s,..."string"==typeof c&&c.startsWith("data:image/")?{dataUrl:c}:{},..."string"==typeof l?{dataUrlError:l}:{},...!0===o.blank?{blank:!0}:{}}}function $i(e){return pi(e)?{found:wi(e.found),left:vi(e.left),top:vi(e.top)}:{found:!1,left:0,top:0}}function Fi(e){return pi(e)?{found:wi(e.found),cardSelector:bi(e.cardSelector)||jo,candidateId:bi(e.candidateId),name:bi(e.name),..."string"==typeof e.age?{age:e.age}:{},..."string"==typeof e.experience?{experience:e.experience}:{},..."string"==typeof e.education?{education:e.education}:{},..."string"==typeof e.workStatus?{workStatus:e.workStatus}:{},..."string"==typeof e.company?{company:e.company}:{},..."string"==typeof e.currentPosition?{currentPosition:e.currentPosition}:{},..."string"==typeof e.expectedLocation?{expectedLocation:e.expectedLocation}:{},..."string"==typeof e.expectedPosition?{expectedPosition:e.expectedPosition}:{},..."string"==typeof e.expectedSalary?{expectedSalary:e.expectedSalary}:{},hasGreetButton:wi(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:jo,candidateId:"",name:"",hasGreetButton:!1}}function ji(e){return pi(e)?{..."number"==typeof e.ageMin?{ageMin:e.ageMin}:{},..."number"==typeof e.ageMax?{ageMax:e.ageMax}:{},..."number"==typeof e.minRatio?{minRatio:e.minRatio}:{},..."number"==typeof e.maxRatio?{maxRatio:e.maxRatio}:{}}:{}}function Li(e){return pi(e)&&!0===e.ok?{ok:!0,current:ji(e.current),trackLeft:vi(e.trackLeft),trackTop:vi(e.trackTop),trackWidth:vi(e.trackWidth),trackHeight:vi(e.trackHeight),minHandleX:vi(e.minHandleX),minHandleY:vi(e.minHandleY),maxHandleX:vi(e.maxHandleX),maxHandleY:vi(e.maxHandleY)}:{ok:!1,error:pi(e)&&bi(e.error)||"未找到年龄滑块"}}function qi(e){return pi(e)?{containerFound:wi(e.containerFound),containerLabel:bi(e.containerLabel),scrollTop:vi(e.scrollTop),scrollHeight:vi(e.scrollHeight),clientHeight:vi(e.clientHeight),itemCount:vi(e.itemCount),atStart:wi(e.atStart),atEnd:wi(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function zi(e){return[e,...(e.childFrames??[]).flatMap(e=>zi(e))]}function Vi(e){const t=new Set,n=[];for(const r of e){const e=r.conversationId.length>0?`conversation:${r.conversationId}`:`fallback:${r.name}:${r.messagePreview}:${r.index}`;t.has(e)||(t.add(e),n.push(r))}return n}function Ji(e){return e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.age,e.experience,e.expectedLocation,e.expectedPosition,e.expectedSalary].join("|"):void 0}function Ui(e){return ci.some(t=>t===e)}function Hi(e,t){return`打开候选人聊天后,右侧会话未同步切换到 ${e.name||e.conversationId};${[`document.visibilityState=${t.visibilityState}`,`expectedConversationId=${e.conversationId||"<empty>"}`,`selectedConversationId=${t.selectedConversationId||"<none>"}`,`activePanelCandidateName=${t.activePanelCandidateName||"<none>"}`].join(", ")}${"hidden"===t.visibilityState?";页面当前不可见,可能已最小化、被完全遮挡或位于其他 macOS Space":""}`}function Wi(e,t,n){const r=t.filter(e=>Tt(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&gi(a.url))return a;const e=r.filter(e=>gi(e.url));if(1===e.length){const t=e[0];if(t)return t}if(a)throw new Error("Selected BOSS page is not a chat page; switch to chat page first.");if(e.length>1)throw new Error("Multiple BOSS chat pages found; select the target tab first.");throw new Error("No BOSS chat page found; switch to chat page first.")}if(a)return a;if(1===r.length){const e=r[0];if(e)return e}if(r.length>1)throw new Error("Multiple BOSS pages found; select the target tab first.");throw new Error("No BOSS page found.")}function Ki(e,t){const n=t.filter(e=>Tt(e.url,"zhipin")),r=n.find(t=>e.isNativePageSelected(t.targetId));return r||(1===n.length?n[0]:void 0)}async function Gi(e={},t={}){const n=t.ctxManager??ue(),r=t.runtime??de(),a=await n.listNativePages();if(!0!==e.skipRiskGate){const e=Ki(n,a);e&&so(e)}const o=Wi(n,a,e),i=await r.connectNativePage(o),s=new Yi({target:o,controller:i});if(!0!==e.skipRiskGate)try{await s.assertNotRestricted()}catch(e){throw s.close(),e}return s}var Yi=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new ea(e.controller)}get targetId(){return this.target.targetId}async inspectPage(){return{...this.target,url:await this.url().catch(()=>this.target.url),title:await this.title().catch(()=>this.target.title)}}async assertNotRestricted(){let e;try{e=await this.url()}catch{throw new Error("无法读取当前页面地址,已中止后续操作(页面可能已跳转或失效)")}so({url:e,title:await this.title().catch(()=>"")})}async url(){return await this.evaluateJson("location.href")}async title(){return await this.evaluateJson("document.title")}async waitForSelector(e,t=5e3){const n=Date.now();for(;Date.now()-n<t;){if(await this.evaluateJson(`document.querySelector(${JSON.stringify(e)}) !== null`).catch(()=>!1))return!0;await ui(Vo)}return!1}async evaluateJson(e){return await this.controller.evaluateJson(e)}async bringToFront(){await this.controller.bringToFront()}async inspectChatReloadTarget(){const e=await this.url().catch(()=>this.target.url);return gi(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await Fn(this.controller,{url:e.url,...void 0!==e.ignoreCache?{ignoreCache:e.ignoreCache}:{},...void 0!==e.onReloadSent?{onReloadSent:e.onReloadSent}:{}})}async isChatSurfaceOpen(){return await this.evaluateJson(`location.href.includes("/web/chat/index") ||\n document.querySelector("#container.chat-container-private") !== null ||\n document.querySelector(${JSON.stringify(Fo)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await ui(Vo)}return!1}async waitForChatListReady(e={},t=1e4){const n=Date.now(),r=e.expectedConversationId,a=async()=>{if(void 0===r)return!1;return(await this.readVisibleChatCandidates().catch(()=>[])).some(e=>e.conversationId===r)};if(!await this.waitForSelector(Fo,t))return await a();if(void 0===r)return!0;for(;Date.now()-n<t;){const e=await this.readVisibleChatCandidates().catch(()=>[]);if(e.some(e=>e.conversationId===r))return!0;if(e.length>0)break;await ui(Vo)}for(let e=0;e<Uo&&Date.now()-n<t;e+=1){const e=await this.scrollChatList("up").catch(()=>{});if(void 0===e||!e.ok||e.after>=e.before)break;if(await ui(Jo),await a())return!0;if(e.after<=1)break}for(let e=0;e<=Uo&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=Uo)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await ui(Jo)}return await a()}async isRecommendSurfaceOpen(){const e=[go.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (hasRecommendUrl) {\n return true;\n }\n\n if (href.includes("/web/chat/index")) {\n return false;\n }\n\n const recommendFrame = document.querySelector(${JSON.stringify(e)});\n return recommendFrame !== null;\n })()`)}async waitForRecommendSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isRecommendSurfaceOpen().catch(()=>!1))return!0;await ui(Vo)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=zi(e).map(e=>e.frame),n=t.find(e=>"recommendFrame"===e.name),r=t.find(t=>t.id!==e.frame.id&&t.url.includes("recommend")),a=n??r;if(void 0===a)return;if(void 0!==this.recommendFrameContextId&&this.recommendFrameContextFrameId===a.id)return this.recommendFrameContextId;const o=await this.controller.createIsolatedWorld(a.id).catch(()=>{});return void 0!==o?(this.recommendFrameContextId=o,this.recommendFrameContextFrameId=a.id,o):void 0}async evaluateRecommendFrameJson(e){const t=await this.resolveRecommendFrameContextId();if(void 0!==t)return await this.controller.evaluateJson(e,{contextId:t}).catch(()=>{})}async readRecommendFrameOffset(){return $i(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(go.recommend.iframe)}) ??\n document.querySelector('iframe[name="recommendFrame"]') ??\n document.querySelector('iframe[src*="recommend"]');\n if (!iframe) {\n return { found: false, left: 0, top: 0 };\n }\n const rect = iframe.getBoundingClientRect();\n return { found: rect.width > 0 && rect.height > 0, left: rect.left, top: rect.top };\n })()`).catch(()=>{}))}async resolveRecommendClickTarget(e){const t=Mi(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=Mi(await this.evaluateRecommendFrameJson(e));if(!n.found)return n;const r=await this.readRecommendFrameOffset();return r.found?{found:!0,x:Math.round(n.x+r.left),y:Math.round(n.y+r.top)}:n}async dispatchNativeClick(e,t={}){return!!e.found&&(await this.mouse.click(mi(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??Xo,settleMs:t.settleMs??Yo}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(qo)}) !== null`;return!!await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async waitForRecommendList(e=1e4){const t=Date.now();for(;Date.now()-t<e;)if(await this.isRecommendSurfaceOpen().catch(()=>!1)){if(await this.hasRecommendList().catch(()=>!1))return!0;await ui(Vo)}else await ui(Vo);return await this.hasRecommendList().catch(()=>!1)}async readRecommendJobSelectorState(){const e='(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const text = (element) => (element.textContent ?? "").replace(/\\s+/g, " ").trim();\n const wrap = document.querySelector(".job-selecter-wrap");\n if (!wrap) {\n return {\n found: false,\n isOpen: false,\n currentLabel: "",\n currentValue: "",\n options: []\n };\n }\n\n const list = wrap.querySelector(".ui-dropmenu-list");\n const options = Array.from(wrap.querySelectorAll(".job-list .job-item")).map(\n (item, index) => {\n const labelElement = item.querySelector(".label") ?? item;\n return {\n index,\n value: item.getAttribute("value") ?? item.getAttribute("data-value") ?? "",\n label: text(labelElement),\n isCurrent: item.classList.contains("curr")\n };\n },\n );\n const current = options.find((option) => option.isCurrent);\n const label = wrap.querySelector(".ui-dropmenu-label");\n return {\n found: true,\n isOpen: Boolean(list && visible(list)),\n currentLabel: current?.label ?? (label ? text(label) : ""),\n currentValue: current?.value ?? "",\n options\n };\n })()',t=Ii(await this.evaluateJson(e).catch(()=>{}));return t.found?t:Ii(await this.evaluateRecommendFrameJson(e))}async openRecommendJobSelector(e={}){if((await this.readRecommendJobSelectorState()).isOpen)return!0;const t=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const wrap = document.querySelector(".job-selecter-wrap");\n const label = wrap?.querySelector(".ui-dropmenu-label") ?? wrap;\n if (!label || !visible(label)) {\n return { found: false, x: 0, y: 0 };\n }\n label.scrollIntoView({ block: "center", inline: "center" });\n const rect = label.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()');return!!await this.dispatchNativeClick(t,e)&&(await ui(900),(await this.readRecommendJobSelectorState()).isOpen)}async setRecommendJobSearch(e,t={}){const n=await this.resolveRecommendClickTarget('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const input = document.querySelector(".job-selecter-wrap .top-chat-search input.ipt.chat-job-search");\n if (!(input instanceof HTMLInputElement) || !visible(input)) {\n return { found: false, x: 0, y: 0 };\n }\n input.scrollIntoView({ block: "center", inline: "center" });\n const rect = input.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()');if(!await this.dispatchNativeClick(n,t))return!1;if(await ui(260),await this.selectAllFocusedText(),await ui(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await ui(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await ui(500),!0;await ui(350);for(const t of Array.from(e))await this.controller.insertText(t),await ui(110);return await ui(650),!0}selectRecommendJobMatch(e,t){const n=e=>{const t=e[0];return{...void 0!==t?{selected:t}:{},matchedCount:e.length}};if(void 0!==e.jobValue){return n(t.filter(t=>t.value===e.jobValue))}if(void 0!==e.jobName){const r=hi(e.jobName),a=t.filter(e=>hi(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>hi(e.label).includes(r)))}if(void 0!==e.index){return n(t.filter(t=>t.index===e.index))}return{matchedCount:0}}getCurrentRecommendJobOption(e){const t=e.options.find(e=>e.isCurrent);return void 0!==t?t:0!==e.currentLabel.length||0!==e.currentValue.length?{index:-1,value:e.currentValue,label:e.currentLabel,isCurrent:!0}:void 0}currentRecommendJobMatchesRequest(e,t){if(void 0===t)return!1;if(void 0!==e.jobValue&&t.value.length>0)return t.value===e.jobValue;if(void 0!==e.jobName&&t.label.length>0){const n=hi(e.jobName),r=hi(t.label);return r===n||r.includes(n)}return void 0!==e.index&&t.index===e.index}hasRecommendJobAlternative(e,t){return t.some(n=>!n.isCurrent&&(void 0===e?t.length>1:n.value.length>0&&e.value.length>0?n.value!==e.value:n.label!==e.label||n.index!==e.index))}async clickRecommendJobOption(e,t={}){const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedValue = ${JSON.stringify(e.value)};\n const expectedIndex = ${JSON.stringify(e.index)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const items = Array.from(document.querySelectorAll(".job-selecter-wrap .job-list .job-item"));\n const item = items.find((candidate, index) => {\n const value = candidate.getAttribute("value") ?? candidate.getAttribute("data-value") ?? "";\n return expectedValue.length > 0 ? value === expectedValue : index === expectedIndex;\n });\n if (!item || !visible(item)) {\n return { found: false, x: 0, y: 0 };\n }\n item.scrollIntoView({ block: "center", inline: "center" });\n const rect = item.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);return await this.dispatchNativeClick(n,t)}async listRecommendJobs(e={}){if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",options:[],availableCount:0,canSwitch:!1,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(e)){const e=await this.readRecommendJobSelectorState(),t=this.getCurrentRecommendJobOption(e);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",...void 0!==t?{current:t}:{},options:e.options,availableCount:e.options.length,canSwitch:!1,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",e),await ui(700);const t=await this.readRecommendJobSelectorState(),n=this.getCurrentRecommendJobOption(t);return{success:!0,status:"listed",...void 0!==n?{current:n}:{},options:t.options,availableCount:t.options.length,canSwitch:this.hasRecommendJobAlternative(n,t.options)}}async selectRecommendJob(e,t={}){const n={...e};if(!await this.waitForRecommendList(3e3))return{success:!1,status:"recommend_not_ready",requested:n,options:[],matchedCount:0,error:"推荐牛人页未就绪"};if(!await this.openRecommendJobSelector(t)){const e=await this.readRecommendJobSelectorState(),t=e.options.find(e=>e.isCurrent);return{success:!1,status:e.found?"selector_not_found":"recommend_not_ready",requested:n,...void 0!==t?{current:t}:{},options:e.options,matchedCount:0,error:e.found?"未找到或无法打开岗位下拉":"未找到岗位下拉"}}await this.setRecommendJobSearch("",t),await ui(700);let r=await this.readRecommendJobSelectorState();const a=this.getCurrentRecommendJobOption(r);if(void 0!==a&&this.currentRecommendJobMatchesRequest(n,a)&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:a,selected:a,options:r.options,matchedCount:1};let o=this.selectRecommendJobMatch(n,r.options);const i=n.searchKeyword??n.jobName;void 0===o.selected&&!1!==n.useSearch&&void 0!==i&&i.trim().length>0&&await this.setRecommendJobSearch(i,t)&&(await ui(900),r=await this.readRecommendJobSelectorState(),o=this.selectRecommendJobMatch(n,r.options));const s=this.getCurrentRecommendJobOption(r);if(void 0===o.selected)return{success:!1,status:"not_found",requested:n,...void 0!==s?{current:s}:{},options:r.options,matchedCount:o.matchedCount,error:"未找到匹配的招聘岗位"};if(o.selected.isCurrent&&!0!==n.forceClick)return{success:!0,status:"already_selected",requested:n,current:o.selected,selected:o.selected,options:r.options,matchedCount:o.matchedCount};if(!await this.clickRecommendJobOption(o.selected,t))return{success:!1,status:"selector_not_found",requested:n,...void 0!==s?{current:s}:{},selected:o.selected,options:r.options,matchedCount:o.matchedCount,error:"未能点击匹配的招聘岗位"};let c=await this.readRecommendJobSelectorState();for(let e=0;e<6;e+=1){const e=this.getCurrentRecommendJobOption(c);if(e?.value===o.selected.value||e?.label===o.selected.label)break;await ui(600),c=await this.readRecommendJobSelectorState()}const l=c.options.find(e=>e.value===o.selected?.value)??o.selected;return{success:!0,status:"selected",requested:n,current:l,selected:l,options:c.options.length>0?c.options:r.options,matchedCount:o.matchedCount}}async clickSidebarSection(e,t={}){const n="chat"===e?go.nav.chatLink:go.nav.recommendLink,r=Mi(await this.evaluateJson(`(() => {\n const selector = ${JSON.stringify(n)};\n const labels = ${JSON.stringify("chat"===e?["沟通","消息"]:["推荐牛人"])};\n const normalizedLabels = labels.map((label) => label.replace(/\\s+/g, ""));\n const interactiveSelector = 'a, button, [role="link"], [role="button"]';\n const textSelector = 'span, div, li, a, button, [role="link"], [role="button"]';\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const normalizeText = (element) =>\n (element.textContent ?? "").replace(/\\s+/g, "").trim();\n const matchesExactLabel = (element) => {\n const text = normalizeText(element);\n return normalizedLabels.some((label) => text === label);\n };\n const matchesInteractiveLabel = (element) => {\n const text = normalizeText(element);\n return normalizedLabels.some(\n (label) =>\n text === label ||\n text.startsWith(label) ||\n (text.includes(label) && text.length <= label.length + 8),\n );\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const readCenter = (element) => {\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n };\n\n const sidebar = document.querySelector(${JSON.stringify(go.nav.sidebar)}) ?? document;\n const selectorTargets = Array.from(document.querySelectorAll(selector))\n .filter((element) => visible(element))\n .sort((left, right) => area(left) - area(right));\n if (selectorTargets[0]) {\n return readCenter(selectorTargets[0]);\n }\n\n const interactiveTargets = Array.from(sidebar.querySelectorAll(interactiveSelector))\n .filter((element) => visible(element) && matchesInteractiveLabel(element))\n .sort((left, right) => area(left) - area(right));\n if (interactiveTargets[0]) {\n return readCenter(interactiveTargets[0]);\n }\n\n const exactTextTargets = Array.from(sidebar.querySelectorAll(textSelector))\n .filter((element) => visible(element) && matchesExactLabel(element))\n .sort((left, right) => area(left) - area(right));\n if (exactTextTargets[0]) {\n return readCenter(exactTextTargets[0]);\n }\n\n return { found: false, x: 0, y: 0 };\n })()`));return!!r.found&&await this.dispatchNativeClick(r,t)}async scrollSurface(e,t={}){const n=Ka(e),r=t.direction??n.defaultDirection,a=Math.max(0,Math.floor(t.steps??1)),o=Math.max(0,Math.floor(t.settleMs??700)),i=await this.inspectSurface(e);let s=i,c=0;for(let n=0;n<a;n+=1){if("chat-list"===e&&s.itemCount>0){const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if("up"===r&&s.atStart||"down"===r&&s.atEnd){if(s.itemCount<=0)break;const n=await this.scrollSurfaceWithWheel(e,r,t.distance);if(void 0===n)break;if(s=n,c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}continue}if(s=await this.scrollSurfaceOnce(e,r,t.distance),c+=1,o>0){await ui(o);const t=await this.inspectSurface(e);s=t.containerFound||!s.containerFound?t:s}}return{success:i.containerFound||s.containerFound||c>0,direction:r,stepsRequested:a,stepsCompleted:c,reachedBoundary:"up"===r?s.atStart:s.atEnd,before:i,after:s}}async readChatCandidates(e={}){const t=e.targetCount??20,n=e.autoScroll?e.maxScrolls??3:0,r=[];for(let e=0;e<=n;e+=1){r.push(...await this.readVisibleChatCandidates());const a=Vi(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await ui(Jo)}return Vi(r)}async openChat(e){if(await cn(this),!await this.isChatSurfaceOpen().catch(()=>!1)){if(!await this.clickSidebarSection("chat",{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})||!await this.waitForChatSurface())return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"}}const t=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index,n=Math.max(0,Math.floor(e.maxScrolls??(t?Uo:4))),r=async t=>{if(!await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}}))return{...t,found:!1,error:`未能点击候选人: ${t.name||t.conversationId}`};await ui(Yo);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await ui(Yo),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:Hi(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=Vi(o),c=!0!==e.preferUnread||t?Ua(s,e):s.find(e=>e.hasUnread);if(void 0!==c)return await r(c);if(i>=a)break;if(!(await this.scrollChatList(n)).ok)break;await ui(Jo)}},o=await a("down",t?0:n);if(void 0!==o)return o;if(t&&n>0){for(let e=0;e<n;e+=1){const e=await this.scrollChatList("up");if(!e.ok||e.after<=1||e.after>=e.before)break;await ui(Jo)}const e=await a("down",n);if(void 0!==e)return e}return{found:!1,conversationId:"",candidateId:"",name:e.candidateName??"",index:e.index??-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${e.candidateName??(void 0!==e.conversationId?`conversationId ${e.conversationId}`:`index ${e.index??0}`)}`}}async readSelectedChatTarget(){return ki(await this.evaluateJson('(() => {\n const selected = document.querySelector(".geek-item.selected");\n if (!selected) {\n return null;\n }\n\n const conversationId =\n selected.getAttribute("data-id") ??\n selected.closest(\'[role="listitem"]\')?.getAttribute("key") ??\n "";\n const candidateId =\n selected.getAttribute("data-geek") ??\n selected.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n const candidateName =\n selected\n .querySelector(\'[class*="name"], .nickname, .geek-name, .candidate-name\')\n ?.textContent?.trim() ?? "";\n\n return { conversationId, candidateId, candidateName };\n })()'))}async readActiveChatPanel(){return Ai(await this.evaluateJson('(() => {\n const rootSelectors = [".chat-conversation", ".conversation-box", ".conversation-message"];\n const nameSelectors = [\n ".base-info-single-detial .name-box",\n ".base-info-content .name-box",\n ".base-info-single-container .name-box",\n ".base-info-content .base-name",\n ".chat-user-name",\n ".name-box",\n ".base-name",\n ];\n\n for (const rootSelector of rootSelectors) {\n const root = document.querySelector(rootSelector);\n if (!root) continue;\n\n for (const nameSelector of nameSelectors) {\n const candidateName = root.querySelector(nameSelector)?.textContent?.trim() ?? "";\n if (candidateName.length > 0) {\n return { candidateName };\n }\n }\n }\n\n return null;\n })()'))}async waitForChatMessages(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('document.querySelector(".chat-message-list .message-item, .conversation-message .message-item") !== null').catch(()=>!1))return!0;await ui(Vo)}return await this.evaluateJson('document.querySelector(".chat-message-list .message-item, .conversation-message .message-item") !== null').catch(()=>!1)}async readCandidateChatDetails(e){const t=Math.max(0,Math.floor(e));return Ni(await this.evaluateJson(`(() => {\n const maxMsgs = ${JSON.stringify(t)};\n const selected = document.querySelector(".geek-item.selected");\n const selectedTarget = selected\n ? {\n conversationId:\n selected.getAttribute("data-id") ??\n selected.closest('[role="listitem"]')?.getAttribute("key") ??\n "",\n candidateId:\n selected.getAttribute("data-geek") ??\n selected.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n selected.getAttribute("data-id") ??\n selected.closest('[role="listitem"]')?.getAttribute("key") ??\n "",\n candidateName:\n selected\n .querySelector('[class*="name"], .nickname, .geek-name, .candidate-name')\n ?.textContent?.trim() ?? ""\n }\n : null;\n\n const conversationRoot =\n document.querySelector(".chat-conversation") ??\n document.querySelector(".conversation-box") ??\n document;\n\n const nameSelectors = [\n ".base-info-single-detial .name-box",\n ".base-info-content .name-box",\n ".base-info-single-container .name-box",\n ".base-info-content .base-name",\n ".chat-user-name",\n ".name-box",\n ".base-name"\n ];\n let activePanel = null;\n for (const nameSelector of nameSelectors) {\n const candidateName =\n conversationRoot.querySelector(nameSelector)?.textContent?.trim() ?? "";\n if (candidateName.length > 0) {\n activePanel = { candidateName };\n break;\n }\n }\n\n const detailArea = conversationRoot.querySelector(\n ".base-info-single-detial, .base-info-content, .base-info-single-container"\n );\n const name =\n detailArea\n ?.querySelector(".name-box, .base-name, .chat-user-name, .geek-name")\n ?.textContent?.trim() ?? "";\n\n const infoItems = detailArea\n ? detailArea.querySelectorAll(":scope > div")\n : conversationRoot.querySelectorAll(".geek-info-item, .base-info-item");\n const infoTexts = [];\n infoItems.forEach((el) => {\n const text = el.textContent?.trim();\n if (text) infoTexts.push(text);\n });\n\n const profile = (${di.toString()})(infoTexts);\n const age = profile.age;\n const experience = profile.experience;\n const education = profile.education;\n\n let communicationPosition = "";\n const posNameEl = conversationRoot.querySelector(".position-name");\n if (posNameEl) {\n const cloned = posNameEl.cloneNode(true);\n cloned.querySelectorAll(".popover-wrap, .tooltip-job").forEach((element) => element.remove());\n communicationPosition = cloned.textContent?.trim() ?? "";\n }\n\n let expectedJobText = "";\n const expectValue = conversationRoot.querySelector(".position-item.expect .value.job");\n if (expectValue) {\n expectedJobText = expectValue.textContent?.trim() ?? "";\n }\n const expectedSalary =\n conversationRoot\n .querySelector(".position-item.expect .high-light-orange")\n ?.textContent?.trim() ?? "";\n\n const tags = [];\n if (detailArea) {\n detailArea\n .querySelectorAll(".geek-tag, .base-info-item .high-light-boss")\n .forEach((element) => {\n const text = element.textContent?.trim();\n if (text && !text.includes("更换职位") && text.length < 20) tags.push(text);\n });\n }\n\n const msgItems = conversationRoot.querySelectorAll(\n ".chat-message-list > .message-item, .conversation-message .message-item"\n );\n const timeRegex = /\\d{1,2}:\\d{2}(?::\\d{2})?|\\d{4}-\\d{2}-\\d{2}/;\n const messages = [];\n let msgIdx = 0;\n\n msgItems.forEach((item) => {\n if (msgIdx >= maxMsgs) return;\n\n const hasFriend = item.querySelector(".item-friend") !== null;\n const hasMyself = item.querySelector(".item-myself") !== null;\n const hasSystem = item.querySelector(".item-system") !== null;\n const hasResume = item.querySelector(".item-resume") !== null;\n const hasDialog = item.querySelector(".message-dialog-center") !== null;\n\n let sender = "system";\n let messageType = "text";\n if (hasFriend) {\n sender = "candidate";\n } else if (hasMyself) {\n sender = "recruiter";\n } else if (hasSystem || hasDialog) {\n sender = "system";\n messageType = "system";\n }\n if (hasResume) messageType = "resume";\n\n const cardEl = item.querySelector(".message-card-top-wrap, [class*='d-top-text']");\n if (cardEl) {\n const cardText = cardEl.textContent ?? "";\n if (cardText.includes("微信") || cardText.includes("WeChat")) {\n messageType = "wechat-exchange";\n }\n }\n\n const timeEl = item.querySelector(".message-time .time, .message-time");\n const timeMatch = (timeEl?.textContent ?? "").match(timeRegex);\n const time = timeMatch ? timeMatch[0] : "";\n\n let content = "";\n if (messageType === "wechat-exchange" && cardEl) {\n const cardText = cardEl.textContent ?? "";\n const digitMatch = cardText.match(/\\b(\\d{8,15})\\b/);\n const wxMatch = cardText.match(/微信[::号]*\\s*([a-zA-Z0-9_-]{5,20})/);\n if (digitMatch) content = "[微信号: " + digitMatch[1] + "]";\n else if (wxMatch) content = "[微信号: " + wxMatch[1] + "]";\n else content = "[交换微信]";\n } else if (cardEl) {\n const titleEl = item.querySelector(".message-card-top-title");\n const descEl = item.querySelector(".dialog-content, .message-card-top-text");\n content = (titleEl?.textContent?.trim() ?? descEl?.textContent?.trim() ?? "").trim();\n } else {\n const textEl = item.querySelector(".text span, .text-content, .text");\n if (textEl) {\n content = (textEl.textContent?.trim() ?? "")\n .replace(timeRegex, "")\n .replace("已读", "")\n .trim();\n }\n }\n\n if (content || messageType !== "text") {\n messages.push({ index: msgIdx, sender, messageType, content, time });\n msgIdx += 1;\n }\n });\n\n return {\n selectedTarget,\n activePanel,\n candidateInfo: {\n name,\n age,\n experience,\n education,\n communicationPosition,\n expectedJobText,\n expectedSalary,\n tags\n },\n messages\n };\n })()`))}async inspectRecommendCard(e){const t=Math.max(0,Math.floor(e)),n=this.buildRecommendCardInspectionExpression(t),r=Fi(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=Fi(await this.evaluateRecommendFrameJson(n));return a.found?a:r}async clickRecommendGreet(e,t={}){const n=await this.inspectRecommendCard(e);if(!n.found||!n.hasGreetButton)return{...n,clicked:!1,...n.found||void 0!==n.error?{}:{error:"索引超出范围"},...n.found&&!n.hasGreetButton?{error:"未找到打招呼按钮"}:{}};const r=await this.resolveRecommendClickTarget(this.buildRecommendGreetClickExpression(Math.max(0,Math.floor(e)))),a=await this.dispatchNativeClick(r,t);return{...n,clicked:a,...a?{}:{error:"未能点击打招呼按钮"}}}async clickRecommendCardSurface(e,t={}){const n=await this.resolveRecommendClickTarget(this.buildRecommendCardOpenClickExpression(Math.max(0,Math.floor(e))));return await this.dispatchNativeClick(n,t)}async waitForResumeDialog(e=1e4){const t=Date.now();let n=await this.readResumeDialogState();for(;!n.canvasReady&&Date.now()-t<e;)await ui(Vo),n=await this.readResumeDialogState();return n}async readResumeCanvasGeometry(){const e=this.buildResumeCanvasGeometryExpression();let t=!1,n=await this.evaluateJson(e).catch(()=>{});if(!pi(n)||!0!==n.found){const r=await this.evaluateRecommendFrameJson(e);void 0!==r&&(n=r,t=!0)}if(!pi(n)||!0!==n.found){const e=pi(n)?n.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const r=Bi(n.canvasRect),a=Ti(n.canvasSize);if(void 0===r||void 0===a)return{found:!1,error:"简历 canvas 坐标读取失败"};const o=Pi(n.resumeFrameRect),i=t?await this.readRecommendFrameOffset():void 0;return{found:!0,screenshotArea:Mo({recommendFrameRect:void 0!==i&&i.found?{x:i.left,y:i.top}:null,resumeFrameRect:o??null,canvasRect:r}),canvasSize:a}}buildResumeCanvasGeometryExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const canvasSelector = ${JSON.stringify(No)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n return {\n found: true,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n },\n canvasSize: { width: canvas.width, height: canvas.height }\n };\n })()`}async captureResumeCanvas(e){await ui(ei);const t=this.buildResumeStitchInitExpression();let n=!1,r=await this.evaluateJson(t).catch(()=>{});if(!pi(r)||!0!==r.found){const e=await this.evaluateRecommendFrameJson(t);void 0!==e&&(r=e,n=!0)}if(!pi(r)||!0!==r.found){const e=pi(r)?r.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const a=async e=>n?await this.evaluateRecommendFrameJson(e):await this.evaluateJson(e).catch(()=>{}),o="number"==typeof r.maxScrollTotal&&r.maxScrollTotal>0?r.maxScrollTotal:0,i=this.buildResumeStitchStepExpression();for(let t=0;t<Zo;t++){await ui(Qo);const n=await a(i);if(!pi(n)||!0!==n.ok||!0===n.done)break;if(void 0!==e){const r="number"==typeof n.scrollTop?n.scrollTop:0;await Promise.resolve(e({round:t,scrolledPx:r,totalPx:o})).catch(()=>{})}}const s=await a(this.buildResumeStitchExportExpression()),c=n?await this.readRecommendFrameOffset():void 0;return Oi(r,s,void 0!==c&&c.found?{x:c.left,y:c.top}:null)}async captureViewportClip(e,t=1){return await this.controller.captureScreenshot({format:"png",clip:{x:e.x,y:e.y,width:e.width,height:e.height,scale:t},timeoutMs:15e3})}async readResumeDialogClipArea(){const e=this.buildResumeDialogRectExpression(),t=Bi(await this.evaluateJson(e).catch(()=>{}));if(void 0!==t)return t;const n=Bi(await this.evaluateRecommendFrameJson(e));if(void 0===n)return;const r=await this.readRecommendFrameOffset();return r.found?{x:Math.round(n.x+r.left),y:Math.round(n.y+r.top),width:Math.round(n.width),height:Math.round(n.height)}:n}async closeResumeDialog(e={}){const t=await this.resolveRecommendClickTarget(this.buildResumeCloseTargetExpression());return t.found&&await this.dispatchNativeClick(t,e)&&await this.waitForResumeDialogGone()?{closed:!0,method:"close-button"}:(await this.controller.dispatchKeyEvent({type:"keyDown",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.waitForResumeDialogGone()?{closed:!0,method:"escape"}:{closed:!1,error:"简历弹窗未关闭"})}async readResumeDialogState(){const e=this.buildResumeDialogStateExpression(),t=Di(await this.evaluateJson(e).catch(()=>{}));if(t.iframeFound)return t;const n=Di(await this.evaluateRecommendFrameJson(e));return{dialogVisible:t.dialogVisible||n.dialogVisible,iframeFound:n.iframeFound,iframeVisible:n.iframeVisible,canvasReady:n.canvasReady}}async waitForResumeDialogGone(e=4e3){const t=Date.now();for(;Date.now()-t<e;){const e=await this.readResumeDialogState();if(!(e.iframeFound&&e.iframeVisible||e.dialogVisible))return!0;await ui(Vo)}return!1}async exchangeWechat(e={}){const t=Mi(await this.evaluateJson('(() => {\n const selectors = [\n ".chat-conversation .conversation-operate .operate-exchange-left span.operate-btn",\n ".chat-conversation .conversation-operate span.operate-btn",\n ".conversation-box .conversation-operate .operate-exchange-left span.operate-btn",\n ".conversation-box .conversation-operate span.operate-btn",\n ".conversation-operate .operate-exchange-left span.operate-btn",\n ".conversation-operate .operate-exchange-left span"\n ];\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n for (const element of Array.from(document.querySelectorAll(selector))) {\n if (normalize(element.textContent) !== "换微信" || !visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(t,e))return{success:!1,exchanged:!1,error:"未找到当前聊天输入区的「换微信」按钮"};if(!await this.waitForWechatExchangeDialog())return{success:!1,exchanged:!1,error:"确认对话框未弹出"};const n=Mi(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const tooltip = document.querySelector(".exchange-tooltip");\n const selectorGroups = tooltip\n ? [\n [tooltip, ".btn-box .boss-btn-primary.boss-btn"],\n [tooltip, ".btn-box span.boss-btn-primary"],\n [tooltip, "span.boss-btn-primary"],\n [tooltip, ".boss-btn-primary"]\n ]\n : [];\n for (const [root, selector] of selectorGroups) {\n const button = root.querySelector(selector);\n if (button && visible(button)) {\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n\n for (const container of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = container.textContent ?? "";\n if (!text.includes("交换微信") || !visible(container)) continue;\n const buttons = container.querySelectorAll(\n "span.boss-btn-primary, button.boss-btn-primary, span.boss-btn, button.boss-btn"\n );\n for (const button of Array.from(buttons)) {\n if (normalize(button.textContent) !== "确定" || !visible(button)) continue;\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(n,e))return{success:!1,exchanged:!1,error:"未找到确认按钮"};await ui(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=Mi(await this.evaluateJson('(() => {\n const selectors = ["#boss-chat-editor-input", "textarea.chat-input", ".chat-input"];\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n const element = document.querySelector(selector);\n if (!element || !visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));if(!await this.dispatchNativeClick(n,t))return{success:!1,error:"未找到聊天输入框"};await this.selectAllFocusedText(),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await this.controller.insertText(e),await ui(250);const r=Mi(await this.evaluateJson('(() => {\n const selectors = [\n ".submit-content .submit.active",\n ".submit-content .submit",\n ".submit-content",\n ".btn-send"\n ];\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n for (const selector of selectors) {\n const button = document.querySelector(selector);\n if (!button || !visible(button)) continue;\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n for (const span of Array.from(document.querySelectorAll("span, button"))) {\n if ((span.textContent ?? "").replace(/\\s+/g, "").trim() !== "发送" || !visible(span)) {\n continue;\n }\n const rect = span.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n return { found: false, x: 0, y: 0 };\n })()').catch(()=>{}));return await this.dispatchNativeClick(r,t)?(await ui(800),{success:!0}):{success:!1,error:"未找到发送按钮"}}async applyRecommendFilter(e,t={}){if(!await this.waitForRecommendFilterSurface(3e3))return{status:"recommend_not_ready",requested:e,error:"推荐牛人页未就绪"};let n=e.location;if(void 0!==e.location){if(!await this.applyRecommendLocationFilter(e.location,t))return{status:"filter_not_found",requested:e,error:`未能设置地区筛选:${e.location.city}${void 0!==e.location.district?`-${e.location.district}`:""}`};n=e.location}if(!xo(e)&&0===e.optionSelections.length){const t=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:{...void 0!==n?{location:n}:{},optionSelections:[]},...void 0!==t?{filterButtonText:t}:{}}}if(!await this.openRecommendFilterPanel(t))return{status:"filter_not_found",requested:e,error:"未找到或无法打开筛选按钮"};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"筛选条件触发 VIP 弹窗"};if("replace"===e.applyMode&&!await this.clickRecommendFilterClear(t))return{status:"clear_failed",requested:e,error:"筛选清除失败"};for(const n of e.optionSelections){if(!await this.clickRecommendFilterValues(n,t))return await this.detectVipModal()?{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}:{status:"filter_not_found",requested:e,error:`未找到${n.label}筛选项:${n.values.join("、")}`};if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:`${n.label}筛选触发 VIP 弹窗`}}let r={};if(xo(e)){const n=await this.setRecommendAgeRange(e,t);if(!n.success)return{status:"age_not_applied",requested:e,error:n.error};r=n.state}if(await this.detectVipModal())return{status:"requires_vip",requested:e,error:"年龄筛选触发 VIP 弹窗"};const a=await this.readNativeAppliedFilterState(e,r,n);if(!await this.clickRecommendFilterSubmit(t))return{status:"submit_failed",requested:e,applied:a,error:"筛选确认失败"};const o=await this.readRecommendFilterButtonText();return{status:"applied",requested:e,applied:a,...void 0!==o?{filterButtonText:o}:{}}}async readRecommendCandidates(e={}){const t=e.targetCount,n=e.autoScroll?e.maxScrolls??4:0,r=await this.inspectSurface("recommend-list");let a=r;const o=new Map;let i=0,s=0,c=0,l="max-steps";const d=async()=>{let e=0;const t=await this.readVisibleRecommendCandidates();for(const n of t){const t=Ji(n);void 0!==t&&0!==t.length&&(o.has(t)?s+=1:(o.set(t,n),e+=1))}return e};await d();for(let e=0;e<n;e+=1){if(void 0!==t&&o.size>=t){l="target-count";break}if(a.atEnd){let e=!1;for(let t=0;t<Ko;t+=1){await ui(Wo);const t=await d(),n=await this.inspectSurface("recommend-list"),r=t>0||n.scrollHeight>a.scrollHeight||n.itemCount>a.itemCount||!n.atEnd;if(a=n,r){t>0&&(c=0),e=!0;break}}if(!e){l="boundary";break}if(void 0!==t&&o.size>=t){l="target-count";break}}if(c>=Go){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:Ho});a=e.after,i+=e.stepsCompleted;c=await d()>0?0:c+1}void 0!==t&&o.size>=t?l="target-count":i>=n&&(l="max-steps");const u=[...o.values()];return{success:r.containerFound,direction:"down",stepsRequested:n,stepsCompleted:i,reachedBoundary:a.atEnd,before:r,after:a,items:u,uniqueCount:u.length,duplicateCount:s,noNewRounds:c,stopReason:l}}async readUsernameEvidence(){return Si(await this.evaluateJson(`(() => {\n const limit = ${Po};\n const evidence = [];\n const viewportWidth = window.innerWidth || 1280;\n const push = (entry) => {\n const text = String(entry.text ?? "").trim();\n if (text.length > 0 && text.length <= limit) {\n evidence.push({ ...entry, text });\n }\n };\n const scope =\n document.querySelector("header") ??\n document.querySelector("#header") ??\n document.querySelector('[role="banner"]') ??\n document.querySelector('[role="navigation"]') ??\n document.body;\n\n if (scope) {\n for (const element of Array.from(scope.querySelectorAll('a, [role="link"]'))) {\n const text = element.textContent?.trim() ?? "";\n const rect = element.getBoundingClientRect();\n push({\n text,\n strategy: "role-link",\n priority: 1,\n source: "role:link",\n xRatio: (rect.x + rect.width / 2) / viewportWidth\n });\n }\n for (const element of Array.from(scope.querySelectorAll('button, [role="button"]'))) {\n const text = element.textContent?.trim() ?? "";\n const rect = element.getBoundingClientRect();\n push({\n text,\n strategy: "role-button",\n priority: 1,\n source: "role:button",\n xRatio: (rect.x + rect.width / 2) / viewportWidth\n });\n }\n\n const walker = document.createTreeWalker(scope, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n const text = walker.currentNode.textContent?.trim() ?? "";\n push({\n text,\n strategy: "leaf-text",\n priority: 3,\n source: "leaf-text"\n });\n }\n }\n\n for (const selector of ${JSON.stringify([go.username.primary,...go.username.fallbacks])}) {\n try {\n push({\n text: document.querySelector(selector)?.textContent?.trim() ?? "",\n strategy: "css-fallback",\n priority: 4,\n source: selector\n });\n } catch {\n // Ignore invalid selectors from site-specific fallback list.\n }\n }\n\n return evidence;\n })()`))}close(){this.controller.close()}buildRecommendCardInspectionExpression(e){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(jo)};\n const fallbackSelector = ${JSON.stringify(Lo)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const parseCandidateProfileTokens = ${di.toString()};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (!hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n found: false,\n cardSelector: primarySelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "推荐列表未加载"\n };\n }\n\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cardSelector = primaryCards.length > 0 ? primarySelector : fallbackSelector;\n const cards = primaryCards.length > 0 ? primaryCards : Array.from(root.querySelectorAll(cardSelector));\n if (cards.length <= index) {\n return {\n found: false,\n cardSelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "索引超出范围"\n };\n }\n\n const item = cards[index];\n const splitParts = (text) => (text ?? "")\n .split(/[丨·|]/)\n .map((part) => part.trim())\n .filter(Boolean);\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n "";\n const name = item.querySelector(".name")?.textContent?.trim() ?? "";\n let age = "";\n let experience = "";\n let education = "";\n let workStatus = "";\n const baseInfoEl = item.querySelector(".base-info.join-text-wrap, .base-info");\n if (baseInfoEl) {\n let textParts = Array.from(baseInfoEl.querySelectorAll(":scope > *"))\n .map((child) => child.textContent?.trim() ?? "")\n .filter(Boolean);\n\n if (textParts.length <= 1) {\n textParts = splitParts(baseInfoEl.textContent?.trim() ?? "");\n }\n\n const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n if (!workStatus && /(在职|离职|在校)/.test(part)) {\n workStatus = part;\n }\n }\n }\n const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = splitParts(workExpEl?.textContent?.trim() ?? "");\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\n let expectedLocation = "";\n let expectedPosition = "";\n const expectRow = item.querySelector(".row-flex:not(.geek-desc)");\n if (expectRow) {\n const labelText = expectRow.querySelector(".label")?.textContent ?? "";\n const contentEl = expectRow.querySelector(".content");\n if ((labelText.includes("期望") || labelText.includes("最近关注")) && contentEl) {\n const parts = splitParts(contentEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n if (!expectedLocation) {\n const expectEl =\n item.querySelector(".timeline-wrap.expect .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.expect .content");\n if (expectEl) {\n const parts = splitParts(expectEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n const greetButton =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n const rect = greetButton?.getBoundingClientRect();\n const hasGreetButton = Boolean(rect && rect.width > 0 && rect.height > 0);\n\n return {\n found: true,\n cardSelector,\n candidateId,\n name,\n age,\n experience,\n education,\n workStatus,\n company,\n currentPosition,\n expectedLocation,\n expectedPosition,\n expectedSalary: item.querySelector(".salary-wrap")?.textContent?.trim() ?? "",\n hasGreetButton\n };\n })()`}buildRecommendCardTargetClickExpression(e,t){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(jo)};\n const fallbackSelector = ${JSON.stringify(Lo)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cards =\n primaryCards.length > 0\n ? primaryCards\n : Array.from(root.querySelectorAll(fallbackSelector));\n const item = cards[index];\n if (!item) return { found: false, x: 0, y: 0 };\n const target = ${t};\n if (!target) return { found: false, x: 0, y: 0 };\n target.scrollIntoView({ block: "center", inline: "center" });\n const rect = target.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { found: false, x: 0, y: 0 };\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`}buildRecommendGreetClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,'item.querySelector("button.btn.btn-greet") ?? item.querySelector("button.btn-greet") ?? item.querySelector(".btn-greet") ?? item.querySelector(".op-btn")')}buildRecommendCardOpenClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,`item.querySelector(${JSON.stringify(Io)}) ?? item`)}buildResumeDialogStateExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const dialogSelector = ${JSON.stringify(ko)};\n const canvasSelector = ${JSON.stringify(No)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n let dialogVisible = false;\n let iframeFound = false;\n let iframeVisible = false;\n let canvasReady = false;\n const dialog = document.querySelector(dialogSelector);\n if (dialog && visible(dialog)) dialogVisible = true;\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (resumeIframe) {\n iframeFound = true;\n iframeVisible = visible(resumeIframe);\n const resumeDoc = resumeIframe.contentDocument;\n const canvas = resumeDoc ? resumeDoc.querySelector(canvasSelector) : null;\n if (canvas && canvas.width > 0 && canvas.height > 0) canvasReady = true;\n }\n return { dialogVisible, iframeFound, iframeVisible, canvasReady };\n })()`}buildResumeStitchInitExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(_o)};\n const canvasSelector = ${JSON.stringify(No)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n\n const scrollers = [];\n let node = resumeIframe.parentElement;\n while (node) {\n if (node.scrollHeight > node.clientHeight + 20) scrollers.push(node);\n node = node.parentElement;\n }\n const rootScroller = document.scrollingElement;\n if (\n rootScroller &&\n rootScroller.scrollHeight > rootScroller.clientHeight + 20 &&\n !scrollers.includes(rootScroller)\n ) {\n scrollers.push(rootScroller);\n }\n\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n const dpr = canvasRect.width > 0 ? canvas.width / canvasRect.width : 1;\n const maxScrollTotal = scrollers.reduce(\n (sum, el) => sum + Math.max(0, el.scrollHeight - el.clientHeight),\n 0\n );\n const totalHeightPx = Math.max(\n Math.round((maxScrollTotal + canvasRect.height) * dpr),\n canvas.height\n );\n\n const stitched = resumeDoc.createElement("canvas");\n stitched.width = canvas.width;\n stitched.height = totalHeightPx;\n const context = stitched.getContext("2d");\n if (!context) return { found: false, error: "无法创建拼接 canvas 上下文" };\n context.fillStyle = "#ffffff";\n context.fillRect(0, 0, stitched.width, stitched.height);\n\n for (const el of scrollers) el.scrollTop = 0;\n globalThis.__rollResumeStitch = {\n stitched,\n context,\n canvas,\n scrollers,\n dpr,\n maxDrawnY: 0,\n scrollerIndex: 0,\n effectiveOffset: 0,\n pending: null,\n pendingFpBase: "",\n readFp: function () {\n try {\n if (!this.fpContext) {\n const fpCanvas = this.canvas.ownerDocument.createElement("canvas");\n fpCanvas.width = 32;\n fpCanvas.height = 32;\n this.fpContext = fpCanvas.getContext("2d", { willReadFrequently: true });\n }\n const fpContext = this.fpContext;\n if (!fpContext) return "noctx";\n fpContext.fillStyle = "#ffffff";\n fpContext.fillRect(0, 0, 32, 32);\n fpContext.drawImage(this.canvas, 0, 0, 32, 32);\n const data = fpContext.getImageData(0, 0, 32, 32).data;\n let hash = 0;\n for (let index = 0; index < data.length; index += 4) {\n hash = ((hash * 31 + data[index] + data[index + 1] + data[index + 2]) >>> 0);\n }\n return String(hash);\n } catch (error) {\n return "err";\n }\n }\n };\n return {\n found: true,\n needScroll: maxScrollTotal > 4,\n maxScrollTotal,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n }\n };\n })()`}buildResumeStitchStepExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n const fingerprint = state.readFp();\n\n if (state.pending) {\n const pendingScroller = state.scrollers[state.pending.index];\n const realDelta = pendingScroller\n ? pendingScroller.scrollTop - state.pending.before\n : 0;\n if (realDelta <= 2) {\n state.scrollerIndex = state.pending.index + 1;\n } else if (fingerprint !== state.pendingFpBase) {\n state.effectiveOffset += realDelta;\n } else {\n if (pendingScroller) pendingScroller.scrollTop = state.pending.before;\n state.scrollerIndex = state.pending.index + 1;\n }\n state.pending = null;\n }\n\n const drawY = Math.round(state.effectiveOffset * state.dpr);\n const drawnBottom = drawY + state.canvas.height;\n if (drawnBottom > state.stitched.height) {\n const grown = state.stitched.ownerDocument.createElement("canvas");\n grown.width = state.stitched.width;\n grown.height = drawnBottom;\n const grownContext = grown.getContext("2d");\n if (!grownContext) return { ok: false, error: "无法扩容拼接 canvas" };\n grownContext.fillStyle = "#ffffff";\n grownContext.fillRect(0, 0, grown.width, grown.height);\n grownContext.drawImage(state.stitched, 0, 0);\n state.stitched = grown;\n state.context = grownContext;\n }\n state.context.drawImage(state.canvas, 0, drawY);\n if (drawnBottom > state.maxDrawnY) state.maxDrawnY = drawnBottom;\n\n const scroller = state.scrollers[state.scrollerIndex];\n if (scroller) {\n const before = scroller.scrollTop;\n scroller.scrollTop = before + scroller.clientHeight;\n state.pending = { index: state.scrollerIndex, before };\n state.pendingFpBase = fingerprint;\n }\n return { ok: true, done: !scroller, scrollTop: state.effectiveOffset };\n })()'}buildResumeStitchExportExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n delete globalThis.__rollResumeStitch;\n let output = state.stitched;\n const finalHeight = Math.min(\n state.stitched.height,\n Math.max(state.maxDrawnY, state.canvas.height)\n );\n if (finalHeight < state.stitched.height) {\n const trimmed = state.stitched.ownerDocument.createElement("canvas");\n trimmed.width = state.stitched.width;\n trimmed.height = finalHeight;\n const trimContext = trimmed.getContext("2d");\n if (trimContext) {\n trimContext.drawImage(state.stitched, 0, 0);\n output = trimmed;\n }\n }\n let blank = true;\n const outputContext = output.getContext("2d");\n if (outputContext) {\n try {\n const stepX = Math.max(1, Math.floor(output.width / 16));\n const stepY = Math.max(1, Math.floor(output.height / 16));\n outer: for (let y = 0; y < output.height; y += stepY) {\n for (let x = 0; x < output.width; x += stepX) {\n const pixel = outputContext.getImageData(x, y, 1, 1).data;\n if (pixel[3] > 8 && (pixel[0] < 246 || pixel[1] < 246 || pixel[2] < 246)) {\n blank = false;\n break outer;\n }\n }\n }\n } catch (error) {\n blank = false;\n }\n }\n let dataUrl = null;\n let dataUrlError = null;\n try {\n dataUrl = output.toDataURL("image/png");\n } catch (error) {\n dataUrlError = error instanceof Error ? error.message : String(error);\n }\n for (const el of state.scrollers) el.scrollTop = 0;\n return {\n ok: true,\n dataUrl,\n dataUrlError,\n blank,\n canvasSize: { width: output.width, height: output.height }\n };\n })()'}buildResumeDialogRectExpression(){return`(() => {\n const dialogSelector = ${JSON.stringify(ko)};\n let best = null;\n let bestArea = 0;\n for (const element of Array.from(document.querySelectorAll(dialogSelector))) {\n const rect = element.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (rect.width > 0 && rect.height > 0 && area > bestArea) {\n best = rect;\n bestArea = area;\n }\n }\n if (!best) return { found: false };\n return {\n found: true,\n x: Math.max(0, best.x),\n y: Math.max(0, best.y),\n width: best.width,\n height: best.height\n };\n })()`}buildResumeCloseTargetExpression(){return`(() => {\n const scopeSelector = ${JSON.stringify(Ao)};\n const closeSelectors = ${JSON.stringify([...Eo])};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const scopes = Array.from(document.querySelectorAll(scopeSelector)).filter(visible);\n for (const scope of scopes) {\n for (const selector of closeSelectors) {\n for (const element of Array.from(scope.querySelectorAll(selector))) {\n if (!visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n }\n return { found: false, x: 0, y: 0 };\n })()`}async waitForWechatExchangeDialog(e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const tooltip = document.querySelector(".exchange-tooltip");\n if (tooltip && visible(tooltip)) return true;\n for (const element of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = element.textContent ?? "";\n if (\n text.includes("交换微信") &&\n element.querySelector(".boss-btn-primary, .boss-btn") &&\n visible(element)\n ) {\n return true;\n }\n }\n return false;\n })()').catch(()=>!1))return!0;await ui(450)}return!1}async readWechatNumber(){const e=await this.evaluateJson('(() => {\n const cardSelectors = [\n ".message-card-top-wrap",\n \'[class*="d-top-text"]\',\n ".message-card-top-title"\n ];\n const parse = (text) => {\n const digitMatch = text.match(/\\b(\\d{8,15})\\b/);\n if (digitMatch) return digitMatch[1];\n const wxMatch = text.match(/微信[::号]*\\s*([a-zA-Z0-9_-]{5,20})/);\n if (wxMatch) return wxMatch[1];\n const letterMatch = text.match(/\\b([a-zA-Z][a-zA-Z0-9_-]{5,19})\\b/);\n if (letterMatch && !["微信", "WeChat"].includes(letterMatch[1])) {\n return letterMatch[1];\n }\n return null;\n };\n\n for (const selector of cardSelectors) {\n const cards = Array.from(document.querySelectorAll(selector));\n for (let index = cards.length - 1; index >= 0; index -= 1) {\n const parsed = parse(cards[index]?.textContent ?? "");\n if (parsed) return parsed;\n }\n }\n\n const msgItems = Array.from(document.querySelectorAll(".message-item"));\n for (let index = msgItems.length - 1; index >= 0; index -= 1) {\n const card = msgItems[index]?.querySelector(\'.message-card-top-wrap, [class*="d-top-text"]\');\n if (!card) continue;\n const parsed = parse(card.textContent ?? "");\n if (parsed) return parsed;\n }\n return null;\n })()').catch(()=>null);return"string"==typeof e&&e.length>0?e:void 0}async selectAllFocusedText(){await this.evaluateJson('(() => {\n const el = document.activeElement;\n if (!el) return false;\n if (el.tagName === "INPUT" || el.tagName === "TEXTAREA") {\n try {\n el.select();\n return true;\n } catch {\n return false;\n }\n }\n if (el.isContentEditable) {\n const range = document.createRange();\n range.selectNodeContents(el);\n const selection = window.getSelection();\n if (!selection) return false;\n selection.removeAllRanges();\n selection.addRange(range);\n return true;\n }\n return false;\n })()').catch(()=>{})}async waitForRecommendFilterSurface(e=1e4){const t=`(() => {\n return document.querySelector(${JSON.stringify(`${go.recommend.filterButton}, ${jo}, ${go.recommend.candidateItem}`)}) !== null;\n })()`,n=Date.now();for(;Date.now()-n<e;){if(await this.evaluateJson(t).catch(()=>!1)||await this.evaluateRecommendFrameJson(t))return!0;await ui(Vo)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(go.recommend.filterPanel)});\n if (!panel) return false;\n const rect = panel.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n })()`;return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async openRecommendFilterPanel(e){if(await this.isRecommendFilterPanelVisible())return!0;for(let t=0;t<3;t+=1){await this.dismissPreviousFilterPrompt(e);const t=await this.resolveRecommendClickTarget(`(() => {\n const filterButtonSelector = ${JSON.stringify(go.recommend.filterButton)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const scoreCandidate = (element) => {\n const classes = classText(element);\n const parentClasses = element.parentElement ? classText(element.parentElement) : "";\n const ancestorClasses =\n element.closest(".recommend-filter, .filter-label-wrap, .filter-wrap") !== null\n ? "recommend-filter"\n : "";\n let score = 0;\n for (const value of [classes, parentClasses, ancestorClasses]) {\n if (/recommend-filter/.test(value)) score += 3;\n else if (/filter-label/.test(value)) score += 2;\n else if (/filter/.test(value)) score += 1;\n }\n return score;\n };\n const selectorCandidates = Array.from(document.querySelectorAll(filterButtonSelector));\n const textCandidates = Array.from(\n document.querySelectorAll("button, a, span, div, [role='button']")\n ).filter((element) => /^筛选(?:·\\d+)?$/.test(normalize(element.textContent)));\n const candidate = [...selectorCandidates, ...textCandidates]\n .filter(visible)\n .sort((a, b) => {\n const scoreDelta = scoreCandidate(b) - scoreCandidate(a);\n if (scoreDelta !== 0) return scoreDelta;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return aRect.width * aRect.height - bRect.width * bRect.height;\n })[0];\n if (!candidate) return { found: false, x: 0, y: 0 };\n candidate.scrollIntoView({ block: "center", inline: "center" });\n const rect = candidate.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);if(await this.dispatchNativeClick(t,e)){const t=Date.now();for(;Date.now()-t<4e3;){if(await this.isRecommendFilterPanelVisible())return await this.dismissPreviousFilterPrompt(e),!0;await ui(Vo)}}await ui(300)}return!1}async dismissPreviousFilterPrompt(e){const t=await this.resolveRecommendClickTarget('(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const roots = Array.from(document.body.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return aRect.width * aRect.height - bRect.width * bRect.height;\n });\n for (const root of roots) {\n const text = normalize(root.textContent);\n if (!text.includes("是否应用上次") && !text.includes("上次的筛选条件")) continue;\n const candidates = Array.from(\n root.querySelectorAll("button, a, span, div, [role=\'button\']")\n ).filter(visible);\n for (const candidate of candidates) {\n const buttonText = normalize(candidate.textContent);\n if (/^(取消|不应用|否|关闭|稍后)$/.test(buttonText)) {\n const rect = candidate.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n }\n return { found: false, x: 0, y: 0 };\n })()');return await this.dispatchNativeClick(t,e)}async clickRecommendFilterOption(e,t,n){const r=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(si)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const resolveClickable = (element, row) => {\n let current = element;\n while (current && current !== row.parentElement) {\n const tag = current.tagName.toLowerCase();\n const role = current.getAttribute("role") ?? "";\n if (\n tag === "button" ||\n tag === "a" ||\n tag === "label" ||\n tag === "li" ||\n role === "button" ||\n role === "radio"\n ) {\n return current;\n }\n current = current.parentElement;\n }\n return element;\n };\n\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return { found: false, x: 0, y: 0 };\n\n const rows = Array.from(panel.querySelectorAll("div, li, dl, dd, section, ul"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return visible(element) && text.includes(rowLabel) && text.includes(optionLabel);\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n });\n\n for (const row of rows) {\n const option = Array.from(row.querySelectorAll(clickableSelector))\n .filter((element) => visible(element) && normalize(element.textContent) === optionLabel)\n .sort((a, b) => area(a) - area(b))[0];\n if (!option) continue;\n const clickable = resolveClickable(option, row);\n clickable.scrollIntoView({ block: "center", inline: "center" });\n const rect = clickable.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n\n return { found: false, x: 0, y: 0 };\n })()`);return await this.dispatchNativeClick(r,n)}async clickRecommendFilterValues(e,t){const n=Array.from(new Set(e.values.map(e=>e.trim()))).filter(e=>e.length>0);if(0===n.length)return!0;if("single"===e.selection){const r=n[0];return void 0===r||await this.clickRecommendFilterOption(e.label,r,t)}if(n.includes(e.clearValue))return await this.clickRecommendFilterOption(e.label,e.clearValue,t);if(!await this.clickRecommendFilterOption(e.label,e.clearValue,t))return!1;for(const r of n)if(!await this.clickRecommendFilterOption(e.label,r,t))return!1;return!0}async isRecommendLocationPanelVisible(){const e='(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return aRect.width * aRect.height - bRect.width * bRect.height;\n });\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const explicitPanel = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .some((element) => visible(element) && isLocationPanel(element));\n if (explicitPanel) return true;\n return roots.some((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 120 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n })()';return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async openRecommendLocationPanel(e,t){if(await this.isRecommendLocationPanelVisible())return!0;const n=await this.resolveRecommendClickTarget(`(() => {\n const expectedCity = ${JSON.stringify(e.city)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = normalizeCity(expectedCity);\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const centerY = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.top + rect.height / 2;\n };\n const readCenter = (element) => {\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n };\n\n const controls = Array.from(document.querySelectorAll("button, a, span, div, [role='button']"))\n .filter((element) => visible(element));\n const filterButton = controls\n .filter((element) => /^筛选(?:·\\d+)?$/.test(normalize(element.textContent)))\n .sort((a, b) => area(a) - area(b))[0];\n if (filterButton) {\n const filterRect = filterButton.getBoundingClientRect();\n const sameRowCandidates = controls\n .filter((element) => {\n if (element === filterButton) return false;\n const text = normalize(element.textContent);\n if (text.length === 0 || text.length > 12) return false;\n if (text.includes("_") || text.includes("筛选") || text.includes("推荐")) return false;\n const rect = element.getBoundingClientRect();\n return (\n rect.right <= filterRect.left + 8 &&\n Math.abs(centerY(element) - centerY(filterButton)) <= 36\n );\n })\n .sort((a, b) => {\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n return Math.abs(filterRect.left - aRect.right) - Math.abs(filterRect.left - bRect.right);\n });\n if (sameRowCandidates[0]) return readCenter(sameRowCandidates[0]);\n }\n\n const cityCandidates = controls\n .filter((element) => {\n const text = normalizeCity(element.textContent);\n const rect = element.getBoundingClientRect();\n return text === expected && rect.top < 160;\n })\n .sort((a, b) => area(a) - area(b));\n if (cityCandidates[0]) return readCenter(cityCandidates[0]);\n\n return { found: false, x: 0, y: 0 };\n })()`);if(!await this.dispatchNativeClick(n,t))return!1;const r=Date.now();for(;Date.now()-r<4e3;){if(await this.isRecommendLocationPanelVisible())return!0;await ui(Vo)}return!1}async clickRecommendLocationValue(e,t,n){const r=await this.resolveRecommendClickTarget(`(() => {\n const kind = ${JSON.stringify(e)};\n const rawValue = ${JSON.stringify(t)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const normalizeCity = (text) => normalize(text).replace(/市$/, "");\n const expected = kind === "city" ? normalizeCity(rawValue) : normalize(rawValue);\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认") &&\n text.includes(expected)\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n if (!panel) return { found: false, x: 0, y: 0 };\n\n const panelRect = panel.getBoundingClientRect();\n const leftLimit = panelRect.left + panelRect.width * (kind === "city" ? 0 : 0.22);\n const rightLimit = panelRect.left + panelRect.width * (kind === "city" ? 0.34 : 0.72);\n const candidates = Array.from(\n panel.querySelectorAll("button, a, label, li, span, div, [role='button'], [role='checkbox']")\n )\n .filter((element) => {\n if (!visible(element)) return false;\n const text = kind === "city" ? normalizeCity(element.textContent) : normalize(element.textContent);\n if (text !== expected) return false;\n const rect = element.getBoundingClientRect();\n const centerX = rect.left + rect.width / 2;\n return centerX >= leftLimit && centerX <= rightLimit;\n })\n .sort((a, b) => area(a) - area(b));\n const candidate = candidates[0];\n if (!candidate) return { found: false, x: 0, y: 0 };\n candidate.scrollIntoView({ block: "center", inline: "center" });\n const rect = candidate.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);return!!await this.dispatchNativeClick(r,n)&&(await ui(250),!0)}async clickRecommendLocationSubmit(e){const t=await this.resolveRecommendClickTarget('(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const roots = Array.from(document.querySelectorAll("div, section, aside"))\n .filter((element) => visible(element))\n .sort((a, b) => area(a) - area(b));\n const isLocationPanel = (root) => {\n const text = normalize(root.textContent);\n return (\n text.includes("仅推荐期望城市为本城市的牛人") &&\n text.includes("清除") &&\n text.includes("确认")\n );\n };\n const panels = Array.from(document.querySelectorAll(".check-area-warp, .check-area-top"))\n .filter((element) => visible(element) && isLocationPanel(element))\n .sort((a, b) => area(a) - area(b));\n const panel = panels[0] ?? roots.find((root) => {\n const rect = root.getBoundingClientRect();\n return (\n rect.width >= 240 &&\n rect.width <= 900 &&\n rect.height >= 140 &&\n rect.height <= 520 &&\n isLocationPanel(root)\n );\n });\n if (!panel) return { found: false, x: 0, y: 0 };\n const button = Array.from(\n panel.querySelectorAll("button, a, span, div, [role=\'button\']")\n )\n .filter((element) => visible(element) && normalize(element.textContent) === "确认")\n .sort((a, b) => area(a) - area(b))[0];\n if (!button) return { found: false, x: 0, y: 0 };\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()');return!!await this.dispatchNativeClick(t,e)&&(await ui(500),!0)}async applyRecommendLocationFilter(e,t){return!!await this.openRecommendLocationPanel(e,t)&&(!!await this.clickRecommendLocationValue("city",e.city,t)&&(!(void 0!==e.district&&!await this.clickRecommendLocationValue("district",e.district,t))&&await this.clickRecommendLocationSubmit(t)))}async detectVipModal(){const e='(() => {\n const normalize = (text) => (text ?? "").replace(/\\s+/g, " ").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const modalPattern =\n /(购买VIP|VIP账号|开通VIP|开启VIP|专享筛选特权|扫码支付|立即开通|支付金额)/;\n return Array.from(document.body.querySelectorAll("div, section, aside"))\n .some((element) => visible(element) && modalPattern.test(normalize(element.textContent)));\n })()';return await this.evaluateJson(e).catch(()=>!1)||(await this.evaluateRecommendFrameJson(e)??!1)}async readNativeAgeState(){const e=`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const parseAgeValue = (text) => {\n if (text.includes("不限")) return undefined;\n const match = text.match(/\\d+/);\n return match ? Number.parseInt(match[0], 10) : undefined;\n };\n const parseAgeState = (text) => {\n const normalized = normalize(text);\n const ageText = normalized.includes("年龄")\n ? normalized.slice(normalized.indexOf("年龄") + "年龄".length)\n : normalized;\n const numbers = Array.from(ageText.matchAll(/\\d+/g), (match) =>\n Number.parseInt(match[0], 10)\n ).filter((value) => Number.isInteger(value));\n const ageMin = numbers[0];\n const ageMax = ageText.includes("不限") ? undefined : numbers[1];\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {})\n };\n };\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const preferRailTrack = (elements) =>\n [...elements].sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const looksLikeSlider = (element) => {\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return /slider|range|track|bar/i.test(classes) || role === "slider";\n };\n const looksLikeHandle = (element) => {\n const rect = element.getBoundingClientRect();\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return (\n role === "slider" ||\n (/handle|handler|button|thumb|slider-btn|dot|point|circle|knob/i.test(classes) &&\n rect.width <= 80 &&\n rect.height <= 80)\n );\n };\n const readRatio = (dot, track) => {\n const styleLeft = dot.style.left;\n if (styleLeft.endsWith("%")) {\n const parsed = Number.parseFloat(styleLeft);\n if (Number.isFinite(parsed)) return Math.max(0, Math.min(1, parsed / 100));\n }\n const dotRect = dot.getBoundingClientRect();\n const trackRect = track.getBoundingClientRect();\n if (trackRect.width <= 0) return undefined;\n return Math.max(\n 0,\n Math.min(1, (dotRect.left + dotRect.width / 2 - trackRect.left) / trackRect.width)\n );\n };\n const ratioToAge = (ratio) =>\n Math.round(\n ${JSON.stringify(ni)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(ri)} -\n ${JSON.stringify(ni)})\n );\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return {};\n const row = Array.from(panel.querySelectorAll("div, li, section, dl, dd"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return (\n visible(element) &&\n text.includes("年龄") &&\n (/\\d+|不限/.test(text) ||\n Array.from(element.querySelectorAll("*")).some(\n (child) => looksLikeSlider(child) || looksLikeHandle(child),\n ))\n );\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return {};\n const dots = Array.from(row.querySelectorAll(".vue-slider-dot"))\n .filter(visible)\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const track = preferRailTrack(\n Array.from(row.querySelectorAll(".vue-slider-rail, .vue-slider")).filter(visible)\n )[0];\n if (dots.length >= 2) {\n const minDot = dots[0];\n const maxDot = dots[dots.length - 1];\n const minText = normalize(minDot?.querySelector(".vue-slider-dot-tooltip-text")?.textContent);\n const maxText = normalize(maxDot?.querySelector(".vue-slider-dot-tooltip-text")?.textContent);\n const rowText = normalize(row.textContent);\n const rowState = parseAgeState(rowText);\n const minRatio = track && minDot ? readRatio(minDot, track) : undefined;\n const maxRatio = track && maxDot ? readRatio(maxDot, track) : undefined;\n const ageMin =\n parseAgeValue(minText) ??\n rowState.ageMin ??\n (minRatio !== undefined ? ratioToAge(minRatio) : undefined);\n let ageMax = parseAgeValue(maxText) ?? rowState.ageMax;\n if (\n ageMax === undefined &&\n !maxText.includes("不限") &&\n !rowText.includes("不限") &&\n maxRatio !== undefined\n ) {\n ageMax = ratioToAge(maxRatio);\n }\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {}),\n ...(minRatio !== undefined ? { minRatio } : {}),\n ...(maxRatio !== undefined ? { maxRatio } : {})\n };\n }\n return parseAgeState(normalize(row.textContent));\n })()`,t=ji(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:ji(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const parseAgeState = (text) => {\n const normalized = normalize(text);\n const ageText = normalized.includes("年龄")\n ? normalized.slice(normalized.indexOf("年龄") + "年龄".length)\n : normalized;\n const numbers = Array.from(ageText.matchAll(/\\d+/g), (match) =>\n Number.parseInt(match[0], 10)\n ).filter((value) => Number.isInteger(value));\n const ageMin = numbers[0];\n const ageMax = ageText.includes("不限") ? undefined : numbers[1];\n return {\n ...(ageMin !== undefined ? { ageMin } : {}),\n ...(ageMax !== undefined ? { ageMax } : {})\n };\n };\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const classText = (element) =>\n typeof element.className === "string" ? element.className : "";\n const preferRailTrack = (elements) =>\n [...elements].sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const looksLikeSlider = (element) => {\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return /slider|range|track|bar/i.test(classes) || role === "slider";\n };\n const looksLikeHandle = (element) => {\n const rect = element.getBoundingClientRect();\n const classes = classText(element);\n const role = element.getAttribute("role") ?? "";\n return (\n role === "slider" ||\n (/handle|handler|button|thumb|slider-btn|dot|point|circle|knob/i.test(classes) &&\n rect.width <= 80 &&\n rect.height <= 80)\n );\n };\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return { ok: false, error: "未找到筛选面板" };\n const row = Array.from(panel.querySelectorAll("div, li, section, dl, dd"))\n .filter((element) => {\n const text = normalize(element.textContent);\n return (\n visible(element) &&\n text.includes("年龄") &&\n Array.from(element.querySelectorAll("*")).some(\n (child) => looksLikeSlider(child) || looksLikeHandle(child)\n )\n );\n })\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return { ok: false, error: "未找到年龄滑块" };\n\n const vueSliderDots = Array.from(row.querySelectorAll(".vue-slider-dot"))\n .filter(visible)\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const fallbackHandles = Array.from(row.querySelectorAll("*"))\n .filter((element) => visible(element) && looksLikeHandle(element))\n .sort((a, b) => a.getBoundingClientRect().left - b.getBoundingClientRect().left);\n const handles = vueSliderDots.length >= 2 ? vueSliderDots : fallbackHandles;\n if (handles.length < 2) {\n return { ok: false, error: "未找到年龄滑块双手柄" };\n }\n\n const minHandle = handles[0];\n const maxHandle = handles[handles.length - 1];\n const minRect = minHandle.getBoundingClientRect();\n const maxRect = maxHandle.getBoundingClientRect();\n const minDistance = Math.max(40, maxRect.left - minRect.left);\n const tracks = [\n ...preferRailTrack(Array.from(row.querySelectorAll(".vue-slider-rail, .vue-slider"))),\n ...Array.from(row.querySelectorAll("*")).filter(looksLikeSlider)\n ]\n .filter((element, index, array) => array.indexOf(element) === index)\n .filter((element) => {\n if (!visible(element)) return false;\n const rect = element.getBoundingClientRect();\n return (\n rect.width >= Math.max(80, minDistance) &&\n rect.height <= 100 &&\n rect.left <= minRect.left + minRect.width &&\n rect.right >= maxRect.right - maxRect.width\n );\n })\n .sort((a, b) => {\n const aClasses = classText(a);\n const bClasses = classText(b);\n if (/vue-slider-rail/.test(aClasses) && !/vue-slider-rail/.test(bClasses)) return -1;\n if (!/vue-slider-rail/.test(aClasses) && /vue-slider-rail/.test(bClasses)) return 1;\n const aRect = a.getBoundingClientRect();\n const bRect = b.getBoundingClientRect();\n const heightDelta = aRect.height - bRect.height;\n if (heightDelta !== 0) return heightDelta;\n return bRect.width - aRect.width;\n });\n const track = tracks[0];\n if (!track) return { ok: false, error: "未找到年龄滑块轨道" };\n const trackRect = track.getBoundingClientRect();\n return {\n ok: true,\n current: parseAgeState(normalize(row.textContent)),\n trackLeft: trackRect.left,\n trackTop: trackRect.top,\n trackWidth: trackRect.width,\n trackHeight: trackRect.height,\n minHandleX: Math.round(minRect.left + minRect.width / 2),\n minHandleY: Math.round(minRect.top + minRect.height / 2),\n maxHandleX: Math.round(maxRect.left + maxRect.width / 2),\n maxHandleY: Math.round(maxRect.top + maxRect.height / 2)\n };\n })()`,t=Li(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Li(await this.evaluateRecommendFrameJson(e));if(!n.ok)return n;const r=await this.readRecommendFrameOffset();return r.found?{...n,trackLeft:n.trackLeft+r.left,trackTop:n.trackTop+r.top,minHandleX:n.minHandleX+r.left,minHandleY:n.minHandleY+r.top,maxHandleX:n.maxHandleX+r.left,maxHandleY:n.maxHandleY+r.top}:n}async dispatchNativeDrag(e,t,n,r,a={}){await this.mouse.drag({x:e,y:t},{x:n,y:r},{...void 0!==a.motionObserver?{motionObserver:a.motionObserver}:{},pressDurationMs:a.pressDurationMs??Xo})}async dragAgeHandleToRatio(e,t,n){const r=await this.resolveNativeAgeSlider();if(!r.ok||r.trackWidth<=0)return!1;const a="min"===e?r.minHandleX:r.maxHandleX,o="min"===e?r.minHandleY:r.maxHandleY,i=r.trackLeft+Math.max(0,Math.min(1,t))*r.trackWidth,s=r.trackTop+Math.max(1,r.trackHeight/2);return await this.dispatchNativeDrag(a,o,i,s,n),await ui(ai),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-ni)/(ri-ni)))}clampRatio(e,t,n){return Math.max(t,Math.min(n,e))}async setAgeHandleToNumber(e,t,n){const r=await this.readNativeAgeState(),a=r.minRatio??0,o=r.maxRatio??1;let i="min"===e?0:Math.min(1,a+ii),s="min"===e?Math.max(0,o-ii):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<oi;r+=1){if(!await this.dragAgeHandleToRatio(e,c,n))return!1;const r=await this.readNativeAgeState(),a="min"===e?r.ageMin:r.ageMax;if(a===t)return!0;void 0===a?"max"===e?s=c:i=c:a<t?i=c:s=c;const o=(i+s)/2;if(Math.abs(o-c)<.001)break;c=this.clampRatio(o,i,s)}const l=await this.readNativeAgeState();return"min"===e?l.ageMin===t:l.ageMax===t}isDesiredAgeState(e,t,n){return e.ageMin===t&&e.ageMax===n}async setRecommendAgeRange(e,t){const n=e.ageMin??ni,r=e.ageMax,a=await this.resolveNativeAgeSlider();if(!a.ok)return{success:!1,error:a.error};if(!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法重置为不限"};if(!await this.setAgeHandleToNumber("min",n,t))return{success:!1,error:`年龄下限无法设置为 ${n}`};if(void 0===r){if(void 0!==(await this.readNativeAgeState()).ageMax&&!await this.dragAgeHandleToRatio("max",1,t))return{success:!1,error:"年龄上限无法设置为不限"}}else if(!await this.setAgeHandleToNumber("max",r,t))return{success:!1,error:`年龄上限无法设置为 ${r}`};const o=await this.readNativeAgeState();if(!this.isDesiredAgeState(o,n,r)){const e=void 0===o.ageMax?"不限":String(o.ageMax);return{success:!1,error:`年龄筛选未精确生效,当前为 ${o.ageMin??"未知"}-${e}`}}return{success:!0,state:o}}async readSelectedRecommendOptionTexts(e,t){const n=`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(si)};\n const selectedClassPattern = /active|selected|checked|current|choose|chosen/i;\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const area = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width * rect.height;\n };\n const isSelected = (element) => {\n const classes = typeof element.className === "string" ? element.className : "";\n return (\n selectedClassPattern.test(classes) ||\n element.getAttribute("aria-checked") === "true" ||\n element.getAttribute("aria-selected") === "true"\n );\n };\n const panel = Array.from(document.querySelectorAll(panelSelector))\n .filter(visible)\n .sort((a, b) => area(a) - area(b))[0];\n if (!panel) return fallback;\n const row = Array.from(panel.querySelectorAll("div, li, dl, dd, section, ul"))\n .filter((element) => visible(element) && normalize(element.textContent).includes(rowLabel))\n .sort((a, b) => {\n const areaDelta = area(a) - area(b);\n if (areaDelta !== 0) return areaDelta;\n return normalize(a.textContent).length - normalize(b.textContent).length;\n })[0];\n if (!row) return fallback;\n const selected = Array.from(row.querySelectorAll(clickableSelector))\n .filter((element) => visible(element) && isSelected(element))\n .map((element) => normalize(element.textContent))\n .filter((text) => text !== "" && text !== rowLabel);\n return selected.length > 0 ? Array.from(new Set(selected)) : fallback;\n })()`,r=await this.evaluateJson(n).catch(()=>[]);if(r.length>0)return r;const a=await this.evaluateRecommendFrameJson(n);return Array.isArray(a)&&a.length>0?a:t}async readNativeAppliedFilterState(e,t,n){const r=await Promise.all(bo.map(async t=>{const n=e.optionSelections.find(e=>e.fieldKey===t.key),r=n?.values??[];return{fieldKey:t.key,label:t.label,values:await this.readSelectedRecommendOptionTexts(t.label,r)}})),a=r.find(e=>"gender"===e.fieldKey)?.values[0],o=r.find(e=>"activity"===e.fieldKey)?.values[0];return{...void 0!==t.ageMin?{ageMin:t.ageMin}:{},...void 0!==t.ageMax?{ageMax:t.ageMax}:{},...void 0!==n?{location:n}:{},optionSelections:r,...void 0!==a?{gender:a}:{},...void 0!==o?{activity:o}:{}}}async clickRecommendFilterClear(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const panel = Array.from(document.querySelectorAll(panelSelector)).filter(visible)[0];\n if (!panel) return { found: false, x: 0, y: 0 };\n const button = Array.from(\n panel.querySelectorAll("button, a, span, div, [role='button']")\n )\n .filter(visible)\n .find((element) => normalize(element.textContent) === "清除");\n if (!button) return { found: false, x: 0, y: 0 };\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);return!!await this.dispatchNativeClick(t,e)&&(await ui(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(go.recommend.filterPanel)};\n const normalize = (text) => (text ?? "").replace(/\\s+/g, "").trim();\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const panel = Array.from(document.querySelectorAll(panelSelector)).filter(visible)[0];\n if (!panel) return { found: false, x: 0, y: 0 };\n const button = Array.from(\n panel.querySelectorAll("button, a, span, div, [role='button']")\n )\n .filter(visible)\n .find((element) => normalize(element.textContent) === "确定");\n if (!button) return { found: false, x: 0, y: 0 };\n const rect = button.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`);if(!await this.dispatchNativeClick(t,e))return!1;const n=Date.now();for(;Date.now()-n<4e3;){if(!await this.isRecommendFilterPanelVisible())return await ui(600),!0;await ui(Vo)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(go.recommend.filterButton)});\n const text = element?.textContent?.replace(/\\s+/g, " ").trim() ?? "";\n return text.length > 0 ? text : null;\n })()`,t=await this.evaluateJson(e).catch(()=>null);if("string"==typeof t&&t.length>0)return t;const n=await this.evaluateRecommendFrameJson(e);return"string"==typeof n&&n.length>0?n:void 0}async readVisibleChatCandidates(){return xi(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify($o)}));\n return items.map((item, idx) => {\n const conversationId =\n item.getAttribute("data-id") ??\n item.closest('[role="listitem"]')?.getAttribute("key") ??\n "";\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n const nameEl = item.querySelector(\n '[class*="name"], .nickname, .geek-name, .candidate-name'\n );\n const name = nameEl?.textContent?.trim() ?? "";\n const position = item.querySelector(".source-job")?.textContent?.trim() ?? "";\n const badgeEl = item.querySelector(".badge-count");\n const unreadCount = parseInt(badgeEl?.textContent?.trim() ?? "0", 10) || 0;\n const hasUnread = unreadCount > 0 || item.querySelector(".red-dot") !== null;\n const lastMessageTime =\n item.querySelector(".time, .time-shadow")?.textContent?.trim() ?? "";\n const messagePreview = (\n item.querySelector(".push-text, .chat-last-msg")?.textContent?.trim() ?? ""\n ).slice(0, 100);\n\n return {\n conversationId,\n candidateId,\n name,\n index: idx,\n position,\n hasUnread,\n unreadCount,\n lastMessageTime,\n messagePreview,\n };\n });\n })()`))}async waitForNativeChatReady(e,t=6e3){const n=Date.now();for(;Date.now()-n<t;){const t=await this.readSelectedChatTarget().catch(()=>null),n=await this.readActiveChatPanel().catch(()=>null),r=null!==t&&(e.conversationId.length>0&&t.conversationId===e.conversationId||0===e.conversationId.length&&e.candidateId.length>0&&t.candidateId===e.candidateId),a=0===e.name.length||null!==n&&yi(e.name,n.candidateName);if(r&&a)return{ready:!0};await ui(Vo)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:Ui(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=Mi(await this.evaluateJson(`(() => {\n const expected = ${JSON.stringify({conversationId:e.conversationId,candidateId:e.candidateId,index:e.index})};\n const items = Array.from(document.querySelectorAll(${JSON.stringify($o)}));\n const readCandidate = (item, idx) => {\n const conversationId =\n item.getAttribute("data-id") ??\n item.closest('[role="listitem"]')?.getAttribute("key") ??\n "";\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n conversationId;\n return { item, idx, conversationId, candidateId };\n };\n\n const candidates = items.map(readCandidate);\n const matched =\n candidates.find((entry) =>\n expected.conversationId.length > 0 &&\n entry.conversationId === expected.conversationId\n ) ??\n candidates.find((entry) =>\n expected.candidateId.length > 0 &&\n entry.candidateId === expected.candidateId\n ) ??\n candidates.find((entry) => entry.idx === expected.index);\n\n if (!matched) {\n return { found: false, x: 0, y: 0 };\n }\n\n matched.item.scrollIntoView({ block: "center", inline: "center" });\n const rect = matched.item.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) {\n return { found: false, x: 0, y: 0 };\n }\n\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`));return!!n.found&&await this.dispatchNativeClick(n,t)}async readVisibleRecommendCandidates(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return[];const e=`(() => {\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const parseCandidateProfileTokens = ${di.toString()};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (!hasRecommendUrl && href.includes("/web/chat/index")) {\n return [];\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (!hasRecommendUrl && !iframe && !hasRecommendShell) {\n return [];\n }\n\n const root = iframe?.contentDocument ?? document;\n let items = Array.from(root.querySelectorAll(${JSON.stringify(jo)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(Lo)}));\n }\n\n return items.map((item, idx) => {\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n "";\n const name = item.querySelector(".name")?.textContent?.trim() ?? "";\n\n let age = "";\n let experience = "";\n let education = "";\n let workStatus = "";\n const baseInfoEl = item.querySelector(".base-info.join-text-wrap, .base-info");\n if (baseInfoEl) {\n let textParts = Array.from(baseInfoEl.querySelectorAll(":scope > *"))\n .map((child) => child.textContent?.trim() ?? "")\n .filter(Boolean);\n\n if (textParts.length <= 1) {\n textParts = [];\n baseInfoEl.childNodes.forEach((node) => {\n if (node.nodeType === Node.TEXT_NODE) {\n const text = node.textContent?.trim() ?? "";\n if (text) textParts.push(text);\n }\n });\n }\n\n if (textParts.length <= 1) {\n textParts = (baseInfoEl.textContent?.trim() ?? "")\n .split(/[丨·|]/)\n .map((text) => text.trim())\n .filter(Boolean);\n }\n\n const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n if (!workStatus && /(在职|离职|在校)/.test(part)) {\n workStatus = part;\n }\n }\n }\n\n const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = (workExpEl?.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\n\n let expectedLocation = "";\n let expectedPosition = "";\n const expectRow = item.querySelector(".row-flex:not(.geek-desc)");\n if (expectRow) {\n const labelText = expectRow.querySelector(".label")?.textContent ?? "";\n const contentEl = expectRow.querySelector(".content");\n if ((labelText.includes("期望") || labelText.includes("最近关注")) && contentEl) {\n const parts = (contentEl.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n if (!expectedLocation) {\n const expectEl =\n item.querySelector(".timeline-wrap.expect .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.expect .content");\n if (expectEl) {\n const parts = (expectEl.textContent?.trim() ?? "")\n .split("·")\n .map((text) => text.trim());\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n\n const tags = Array.from(\n item.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span")\n )\n .map((tag) => tag.textContent?.trim() ?? "")\n .filter(Boolean);\n\n return {\n index: idx,\n candidateId,\n name,\n age,\n experience,\n education,\n workStatus,\n company,\n currentPosition,\n expectedLocation,\n expectedPosition,\n expectedSalary: item.querySelector(".salary-wrap")?.textContent?.trim() ?? "",\n tags,\n buttonText: item.querySelector("button.btn.btn-greet")?.textContent?.trim() ?? "",\n };\n });\n })()`,t=Ri(await this.evaluateJson(e));if(t.length>0)return t;return Ri(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=Ka(e),n=`(() => {\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(t.containerSelectors)};\n const itemSelector = ${JSON.stringify(t.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (surface === "recommend-list" && !hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (surface === "recommend-list" && !hasRecommendUrl && !iframe && !hasRecommendShell) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const readItemCount = () => {\n if (surface === "chat-list") {\n const chatList = root.querySelector(".user-list.b-scroll-stable");\n const roleItems = chatList?.querySelectorAll('[role="listitem"]');\n if (roleItems && roleItems.length > 0) return roleItems.length;\n }\n return root.querySelectorAll(itemSelector).length;\n };\n const itemCount = readItemCount();\n const isVisible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n const isScrollable = (element) => {\n if (!isVisible(element)) return false;\n const view = element.ownerDocument?.defaultView ?? window;\n const style = view.getComputedStyle(element);\n if (style.overflowY === "hidden" || style.overflowY === "clip") return false;\n return element.scrollHeight > element.clientHeight + 2;\n };\n const labelFor = (element, fallback) => {\n if (element === document.scrollingElement) return "document";\n if (element === root.scrollingElement) return root === document ? "document" : "frame-document";\n return (\n element.id ||\n Array.from(element.classList ?? []).join(".") ||\n fallback ||\n element.tagName.toLowerCase()\n );\n };\n const targets = [];\n const pushTarget = (element, fallback) => {\n if (element && !isVisible(element)) return;\n if (!element || targets.some((target) => target.element === element)) return;\n targets.push({ element, label: labelFor(element, fallback) });\n };\n const readCssPixel = (value) => {\n const parsed = Number.parseFloat(value);\n return Number.isFinite(parsed) ? parsed : 0;\n };\n const findChatListElement = (element) => {\n if (surface !== "chat-list") return null;\n if (element.matches?.(".user-list.b-scroll-stable")) return element;\n return (\n element.querySelector?.(".user-list.b-scroll-stable") ??\n element.closest?.(".user-list.b-scroll-stable") ??\n null\n );\n };\n const readChatListSnapshot = (target) => {\n const list = findChatListElement(target.element);\n if (!list || itemCount <= 0 || !isVisible(list)) return null;\n\n const nativeScrollTop = Math.max(0, list.scrollTop);\n const nativeScrollHeight = Math.max(0, list.scrollHeight);\n const nativeClientHeight = Math.max(0, list.clientHeight);\n const nativeMaxScrollTop = Math.max(0, nativeScrollHeight - nativeClientHeight);\n if (nativeScrollHeight > nativeClientHeight + 2) {\n return {\n containerFound: true,\n containerLabel: labelFor(list, ".user-list.b-scroll-stable"),\n scrollTop: Math.round(nativeScrollTop),\n scrollHeight: Math.round(nativeScrollHeight),\n clientHeight: Math.round(nativeClientHeight),\n itemCount,\n atStart: nativeScrollTop <= 1,\n atEnd: nativeScrollTop >= nativeMaxScrollTop - 1\n };\n }\n\n const listRect = list.getBoundingClientRect();\n if (listRect.width <= 0 || listRect.height <= 0) return null;\n const group = list.querySelector('[role="group"]');\n if (!group) return null;\n\n const view = list.ownerDocument?.defaultView ?? window;\n const groupStyle = view.getComputedStyle(group);\n const paddingTop = Math.max(0, readCssPixel(groupStyle.paddingTop));\n const paddingBottom = Math.max(0, readCssPixel(groupStyle.paddingBottom));\n const roleItems = Array.from(list.querySelectorAll('[role="listitem"]'));\n const renderedItems =\n roleItems.length > 0 ? roleItems : Array.from(list.querySelectorAll(".geek-item"));\n const renderedHeight = renderedItems.reduce((sum, item) => {\n const rect = item.getBoundingClientRect();\n return sum + Math.max(0, rect.height);\n }, 0);\n const virtualHeight = Math.max(\n Math.round(paddingTop + renderedHeight + paddingBottom),\n Math.round(listRect.height)\n );\n return {\n containerFound: true,\n containerLabel: labelFor(list, ".user-list.b-scroll-stable") + ":virtual",\n scrollTop: Math.round(paddingTop),\n scrollHeight: virtualHeight,\n clientHeight: Math.round(listRect.height),\n itemCount,\n atStart: paddingTop <= 1,\n atEnd: paddingBottom <= 1\n };\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element, selector);\n }\n } catch {}\n }\n\n const firstVisibleItem = Array.from(root.querySelectorAll(itemSelector)).find(isVisible);\n let current = firstVisibleItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current, "item-ancestor");\n current = current.parentElement;\n }\n\n if (surface === "recommend-list" && root !== document) {\n const outerFrame = iframe ?? window.frameElement;\n let outer = outerFrame?.parentElement ?? null;\n while (outer && outer !== document.body && outer !== document.documentElement) {\n pushTarget(outer, "iframe-ancestor");\n outer = outer.parentElement;\n }\n pushTarget(document.scrollingElement, "document");\n pushTarget(document.documentElement, "documentElement");\n pushTarget(document.body, "body");\n }\n pushTarget(root.scrollingElement, root === document ? "document" : "frame-document");\n\n for (const target of targets) {\n const chatListSnapshot = readChatListSnapshot(target);\n if (chatListSnapshot) return chatListSnapshot;\n }\n\n const scrollTarget = targets.find((target) => isScrollable(target.element));\n if (!scrollTarget) {\n return {\n containerFound: false,\n containerLabel: targets[0]?.label ?? "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount,\n atStart: true,\n atEnd: true\n };\n }\n\n const scrollable = scrollTarget.element;\n const max = Math.max(0, scrollable.scrollHeight - scrollable.clientHeight);\n return {\n containerFound: true,\n containerLabel: scrollTarget.label,\n scrollTop: scrollable.scrollTop,\n scrollHeight: scrollable.scrollHeight,\n clientHeight: scrollable.clientHeight,\n itemCount,\n atStart: scrollable.scrollTop <= 1,\n atEnd: scrollable.scrollTop >= max - 1\n };\n })()`,r=qi(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=qi(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=Ka(e);await cn(this);const a=Mi(await this.evaluateJson(`(() => {\n const nativeWheelTarget = true;\n const surface = ${JSON.stringify(e)};\n const containerSelectors = ${JSON.stringify(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const viewportWidth = window.innerWidth || document.documentElement.clientWidth || 0;\n const viewportHeight = window.innerHeight || document.documentElement.clientHeight || 0;\n const targets = [];\n const pushTarget = (element) => {\n if (!element || targets.includes(element)) return;\n targets.push(element);\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element);\n }\n } catch {}\n }\n\n const firstItem = Array.from(root.querySelectorAll(itemSelector)).find((element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n });\n pushTarget(firstItem);\n let current = firstItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current);\n current = current.parentElement;\n }\n\n const readPoint = (element) => {\n const rect = element.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return null;\n let left = rect.left;\n let top = rect.top;\n if (root !== document && iframe) {\n const frameRect = iframe.getBoundingClientRect();\n left += frameRect.left;\n top += frameRect.top;\n }\n const x = Math.round(Math.min(Math.max(left + rect.width / 2, 4), Math.max(4, viewportWidth - 4)));\n const y = Math.round(Math.min(Math.max(top + rect.height / 2, 4), Math.max(4, viewportHeight - 4)));\n return { found: true, x, y };\n };\n\n for (const element of targets) {\n const point = readPoint(element);\n if (point) return point;\n }\n\n return { found: false, x: 0, y: 0 };\n })()`));if(!a.found)return;await this.mouse.moveTo(mi(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??ti):n??ti}),await ui(120);const o=await this.inspectSurface(e);return o.containerFound?o:{...o,containerFound:!0,containerLabel:o.containerLabel.length>0?o.containerLabel:"native-wheel",atStart:"up"!==t&&o.atStart,atEnd:"down"!==t&&o.atEnd}}async scrollSurfaceOnce(e,t,n){const r=Ka(e),a=`(() => {\n const surface = ${JSON.stringify(e)};\n const direction = ${JSON.stringify(t)};\n const explicitDistance = ${void 0!==n?JSON.stringify(n):"undefined"};\n const containerSelectors = ${JSON.stringify(r.containerSelectors)};\n const itemSelector = ${JSON.stringify(r.itemSelector)};\n const iframe = document.querySelector(${JSON.stringify(go.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(zo)};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (surface === "recommend-list" && !hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const hasRecommendShell =\n document.querySelector(".recommendV2, .recommend-list-wrap, .recommend-list, .candidate-list, .recommend-filter, .candidate-card-wrap") !== null;\n if (surface === "recommend-list" && !hasRecommendUrl && !iframe && !hasRecommendShell) {\n return {\n containerFound: false,\n containerLabel: "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount: 0,\n atStart: true,\n atEnd: true\n };\n }\n\n const root = surface === "recommend-list" ? (iframe?.contentDocument ?? document) : document;\n const itemCount = root.querySelectorAll(itemSelector).length;\n const isVisible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const isScrollable = (element) => {\n if (!isVisible(element)) return false;\n const view = element.ownerDocument?.defaultView ?? window;\n const style = view.getComputedStyle(element);\n if (style.overflowY === "hidden" || style.overflowY === "clip") return false;\n return element.scrollHeight > element.clientHeight + 2;\n };\n const labelFor = (element, fallback) => {\n if (element === document.scrollingElement) return "document";\n if (element === root.scrollingElement) return root === document ? "document" : "frame-document";\n return (\n element.id ||\n Array.from(element.classList ?? []).join(".") ||\n fallback ||\n element.tagName.toLowerCase()\n );\n };\n const targets = [];\n const pushTarget = (element, fallback) => {\n if (element && !isVisible(element)) return;\n if (!element || targets.some((target) => target.element === element)) return;\n targets.push({ element, label: labelFor(element, fallback) });\n };\n\n for (const selector of containerSelectors) {\n try {\n for (const element of Array.from(root.querySelectorAll(selector))) {\n pushTarget(element, selector);\n }\n } catch {}\n }\n\n const firstVisibleItem = Array.from(root.querySelectorAll(itemSelector)).find(isVisible);\n let current = firstVisibleItem?.parentElement ?? null;\n while (current && current !== root.body && current !== root.documentElement) {\n pushTarget(current, "item-ancestor");\n current = current.parentElement;\n }\n\n if (surface === "recommend-list" && root !== document) {\n const outerFrame = iframe ?? window.frameElement;\n let outer = outerFrame?.parentElement ?? null;\n while (outer && outer !== document.body && outer !== document.documentElement) {\n pushTarget(outer, "iframe-ancestor");\n outer = outer.parentElement;\n }\n pushTarget(document.scrollingElement, "document");\n pushTarget(document.documentElement, "documentElement");\n pushTarget(document.body, "body");\n }\n pushTarget(root.scrollingElement, root === document ? "document" : "frame-document");\n\n const scrollTarget = targets.find((target) => isScrollable(target.element));\n if (!scrollTarget) {\n return {\n containerFound: false,\n containerLabel: targets[0]?.label ?? "",\n scrollTop: 0,\n scrollHeight: 0,\n clientHeight: 0,\n itemCount,\n atStart: true,\n atEnd: true\n };\n }\n\n const scrollable = scrollTarget.element;\n const max = Math.max(0, scrollable.scrollHeight - scrollable.clientHeight);\n const amount = explicitDistance ?? Math.max(320, Math.floor(scrollable.clientHeight * 0.85));\n scrollable.scrollTop =\n direction === "up"\n ? Math.max(0, scrollable.scrollTop - amount)\n : Math.min(max, scrollable.scrollTop + amount);\n\n return {\n containerFound: true,\n containerLabel: scrollTarget.label,\n scrollTop: scrollable.scrollTop,\n scrollHeight: scrollable.scrollHeight,\n clientHeight: scrollable.clientHeight,\n itemCount,\n atStart: scrollable.scrollTop <= 1,\n atEnd: scrollable.scrollTop >= max - 1\n };\n })()`,o=qi(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=qi(await this.evaluateRecommendFrameJson(a));return i.itemCount>0?i:o}async scrollChatList(e="down"){const t=await this.scrollSurface("chat-list",{direction:e,steps:1,settleMs:0});return{ok:t.success&&t.stepsCompleted>0,before:t.before.scrollTop,after:t.after.scrollTop,max:Math.max(0,t.after.scrollHeight-t.after.clientHeight)}}},Xi=["native","native-watch","native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read","resume-canvas","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],Zi=[...Xi,"native-ws-connect-watch","native-page-bring-front-watch","native-evaluate-url-no-runtime-enable-watch","native-dom-read-no-runtime-enable-watch","native-input-move-no-runtime-enable-watch","native-runtime-enable-watch","native-evaluate-url-watch","native-dom-read-watch","browser-attach-watch"],Qi=qa.enum(["localStorage","sessionStorage"]),es=qa.enum(["array","object","string","number","boolean","null"]),ts=qa.enum(["empty","json","string"]),ns=qa.enum(Xi),rs=qa.enum(Zi),as=qa.enum(["request","response"]),os=qa.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),is=qa.object({area:Qi,key:qa.string(),valueLength:qa.number().int().nonnegative(),valueKind:ts,jsonKind:es.optional(),jsonTopLevelKeys:qa.array(qa.string()).optional(),jsonArrayLength:qa.number().int().nonnegative().optional(),numericFields:qa.record(qa.number()).optional(),booleanFields:qa.record(qa.boolean()).optional(),arrayLengths:qa.record(qa.number().int().nonnegative()).optional()}),ss=qa.object({name:qa.string(),domain:qa.string(),path:qa.string(),expires:qa.string(),valueLength:qa.number().int().nonnegative(),httpOnly:qa.boolean(),secure:qa.boolean(),sameSite:qa.string().optional()}),cs=qa.object({before:qa.number().optional(),after:qa.number().optional(),delta:qa.number().optional()}),ls=qa.object({before:qa.boolean().optional(),after:qa.boolean().optional()}),ds=qa.object({before:qa.number().int().nonnegative().optional(),after:qa.number().int().nonnegative().optional(),delta:qa.number().int().optional()}),us=qa.object({area:Qi,key:qa.string(),beforePresent:qa.boolean(),afterPresent:qa.boolean(),numericDeltas:qa.record(cs).optional(),booleanChanges:qa.record(ls).optional(),arrayLengthDeltas:qa.record(ds).optional()}),ms=qa.object({url:qa.string(),title:qa.string(),visibilityState:qa.string(),hasFocus:qa.boolean()}),ps=qa.object({navigatorWebdriver:qa.boolean().optional(),userAgentContainsHeadless:qa.boolean(),languagesLength:qa.number().int().nonnegative(),pluginsLength:qa.number().int().nonnegative(),hasChromeRuntime:qa.boolean(),permissionQueryIsNative:qa.boolean().optional(),hasPlaywrightBinding:qa.boolean(),hasPwInitScripts:qa.boolean(),cdcKeys:qa.array(qa.string()),webdriverKeys:qa.array(qa.string()),automationLikeWindowKeys:qa.array(qa.string())}),gs=qa.object({rootNodeId:qa.number().int(),rootNodeName:qa.string(),childNodeCount:qa.number().int().nonnegative().optional(),bodyTextLength:qa.number().int().nonnegative().optional(),elementCount:qa.number().int().nonnegative().optional()}),fs=qa.object({type:qa.literal("mouseMoved"),x:qa.number(),y:qa.number()}),hs=qa.object({targetId:qa.string(),websocketUrlAvailable:qa.boolean(),connected:qa.boolean(),pageBroughtToFront:qa.boolean().optional(),runtimeEnabled:qa.boolean().optional(),evaluate:ms.optional(),dom:gs.optional(),input:fs.optional()}),ys=qa.object({phase:ns,success:qa.boolean(),durationMs:qa.number().int().nonnegative(),error:qa.string().optional()}),bs=qa.object({phase:rs,capturedAt:qa.string(),targetFound:qa.boolean(),page:La.optional(),urlChangedFromPrevious:qa.boolean(),titleChangedFromPrevious:qa.boolean(),previousUrl:qa.string().optional(),currentUrl:qa.string().optional(),previousTitle:qa.string().optional(),currentTitle:qa.string().optional()}),vs=qa.object({kind:as,reason:os,capturedAt:qa.string(),url:qa.string(),method:qa.string().optional(),resourceType:qa.string().optional(),status:qa.number().int().optional()}),ws=qa.object({capturedAt:qa.string(),url:qa.string()}),xs=qa.object({phase:ns.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;resume-canvas 只读探测简历弹窗与 canvas 就绪状态和坐标;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:qa.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:qa.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:qa.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),Ss=qa.object({success:qa.boolean(),requestedPhase:ns,mode:qa.string(),nativePages:qa.array(La),targetPage:La.optional(),browserAttached:qa.boolean(),pageAttached:qa.boolean(),nativeTimeline:qa.array(bs),networkEvents:qa.array(vs).optional(),navigationEvents:qa.array(ws).optional(),nativeCdp:hs.optional(),resumeCanvas:qa.object({dialogVisible:qa.boolean(),iframeFound:qa.boolean(),iframeVisible:qa.boolean(),canvasReady:qa.boolean(),screenshotArea:qa.object({x:qa.number(),y:qa.number(),width:qa.number(),height:qa.number()}).optional(),canvasSize:qa.object({width:qa.number(),height:qa.number()}).optional(),error:qa.string().optional()}).optional(),evaluate:ms.optional(),detectorFingerprint:ps.optional(),storage:qa.object({localStorage:qa.array(is),sessionStorage:qa.array(is),cookies:qa.array(ss),counterDiffs:qa.array(us)}).optional(),phases:qa.array(ys),warnings:qa.array(qa.string())}),Rs=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Cs=new Set(Rs),Is=["native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read"],ks=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],As={"native-ws-connect":"native-ws-connect-watch","native-page-bring-front":"native-page-bring-front-watch","native-evaluate-url-no-runtime-enable":"native-evaluate-url-no-runtime-enable-watch","native-dom-read-no-runtime-enable":"native-dom-read-no-runtime-enable-watch","native-input-move-no-runtime-enable":"native-input-move-no-runtime-enable-watch","native-runtime-enable":"native-runtime-enable-watch","native-evaluate-url":"native-evaluate-url-watch","native-dom-read":"native-dom-read-watch"};function Es(e){if(null===e)return"null";if(Array.isArray(e))return"array";switch(typeof e){case"boolean":return"boolean";case"number":return"number";case"string":default:return"string";case"object":return"object"}}function _s(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Ns(e){return Is.includes(e)}function Ms(e){return ks.includes(e)}function Ps(e){try{return JSON.parse(e)}catch{return}}function Ts(e){const t=Object.fromEntries(Object.entries(e).filter(e=>"number"==typeof e[1])),n=Object.fromEntries(Object.entries(e).filter(e=>"boolean"==typeof e[1])),r=Object.fromEntries(Object.entries(e).filter(e=>Array.isArray(e[1])).map(([e,t])=>[e,t.length]));return{...Object.keys(t).length>0?{numericFields:t}:{},...Object.keys(n).length>0?{booleanFields:n}:{},...Object.keys(r).length>0?{arrayLengths:r}:{}}}function Bs(e){return new Promise(t=>{setTimeout(t,e)})}function Ds(e){return Cs.has(e)}function Os(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=Ps(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=Es(n),a=_s(n)&&Ds(t.key)?Ts(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,..._s(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function $s(e){return{name:e.name,domain:e.domain,path:e.path,expires:e.expires>0?new Date(1e3*e.expires).toISOString():"Session",valueLength:e.value.length,httpOnly:e.httpOnly,secure:e.secure,...void 0!==e.sameSite?{sameSite:e.sameSite}:{}}}async function Fs(e,t){const n=Date.now();try{return{result:await t(),phaseResult:{phase:e,success:!0,durationMs:Date.now()-n}}}catch(t){return{phaseResult:{phase:e,success:!1,durationMs:Date.now()-n,error:t instanceof Error?t.message:String(t)}}}}function js(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?Tt(r.url,"zhipin")?r:void n.push(`targetPageId "${t}" is not a zhipin page.`):void n.push(`targetPageId "${t}" not found in native pages.`)}const r=e.filter(e=>Tt(e.url,"zhipin"));if(1===r.length)return r[0];0===r.length?n.push("No zhipin native page found. Open Boss first or pass targetPageId."):n.push("Multiple zhipin native pages found. Pass targetPageId to avoid ambiguity.")}function Ls(e,t,n,r){const a=n?.url,o=n?.title,i=r?.currentUrl,s=r?.currentTitle;return{phase:t,capturedAt:(new Date).toISOString(),targetFound:void 0!==n,...void 0!==n?{page:Dt(e,n)}:{},urlChangedFromPrevious:void 0!==i&&void 0!==a&&i!==a,titleChangedFromPrevious:void 0!==s&&void 0!==o&&s!==o,...void 0!==i?{previousUrl:i}:{},...void 0!==a?{currentUrl:a}:{},...void 0!==s?{previousTitle:s}:{},...void 0!==o?{currentTitle:o}:{}}}async function qs(e,t,n,r){const a=await e.listNativePages();return Ls(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>Tt(e.url,"zhipin"))[0],r)}async function zs(e,t,n,r,a){try{const o=t[t.length-1],i=await qs(e,n,r,o);t.push(i),i.urlChangedFromPrevious&&a.push(`Target URL changed after ${n}: ${i.previousUrl??"(unknown)"} -> ${i.currentUrl??"(missing)"}.`)}catch(e){a.push(`Failed to capture native snapshot after ${n}: ${e instanceof Error?e.message:String(e)}.`)}}async function Vs(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await zs(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await Bs(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function Js(e){if(!_s(e))throw new Error("Native CDP evaluate did not return an object value.");const t=e.url,n=e.title,r=e.visibilityState,a=e.hasFocus;if("string"!=typeof t||"string"!=typeof n||"string"!=typeof r||"boolean"!=typeof a)throw new Error("Native CDP evaluate returned an unexpected page summary shape.");return{url:t,title:n,visibilityState:r,hasFocus:a}}function Us(e,t){if(!_s(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!_s(n))throw new Error("Native CDP DOM.getDocument did not return a root node.");const r=n.nodeId,a=n.nodeName,o=n.childNodeCount;if("number"!=typeof r||"string"!=typeof a)throw new Error("Native CDP DOM root node has an unexpected shape.");const i=_s(t)?t:{},s=i.bodyTextLength,c=i.elementCount;return{rootNodeId:r,rootNodeName:a,..."number"==typeof o?{childNodeCount:o}:{},..."number"==typeof s?{bodyTextLength:s}:{},..."number"==typeof c?{elementCount:c}:{}}}async function Hs(e){return Js(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function Ws(e){return Us(await e.getDocument({depth:1,pierce:!1}),await e.evaluateJson('(() => ({\n bodyTextLength: document.body?.innerText?.length ?? 0,\n elementCount: document.querySelectorAll("*").length\n }))()'))}async function Ks(e){return Us(await e.getDocument({depth:1,pierce:!1}),void 0)}async function Gs(e){return await e.bringToFront(),!0}async function Ys(e){const t={type:"mouseMoved",x:0,y:0};return await e.dispatchMouseEvent({type:t.type,x:t.x,y:t.y}),t}async function Xs(e,t,n,r,a,o){await zs(e,t,n,r,o);const i=await Vs(e,t,As[n],r,a,o);return i&&o.push(`Native CDP phase ${n} was followed by a native URL change; stop before deeper native CDP operations.`),i}async function Zs(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await Fs(e,async()=>await Gs(t));return n.push(a.phaseResult),void 0!==a.result&&(r.pageBroughtToFront=a.result),a.phaseResult}case"native-evaluate-url-no-runtime-enable":{const a=await Fs(e,async()=>await Hs(t));return n.push(a.phaseResult),void 0!==a.result&&(r.evaluate=a.result),a.phaseResult}case"native-dom-read-no-runtime-enable":{const a=await Fs(e,async()=>await Ks(t));return n.push(a.phaseResult),void 0!==a.result&&(r.dom=a.result),a.phaseResult}case"native-input-move-no-runtime-enable":{const a=await Fs(e,async()=>await Ys(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function Qs(e){const t={targetId:e.target.targetId,websocketUrlAvailable:"string"==typeof e.target.webSocketDebuggerUrl&&e.target.webSocketDebuggerUrl.length>0,connected:!1},n=e.target.webSocketDebuggerUrl;let r;try{const a=await Fs("native-ws-connect",async()=>{if(void 0===n||0===n.length)throw new Error("Native CDP target does not expose webSocketDebuggerUrl.");return await e.runtime.connectNativePage(e.target,{allowUnsafeRuntimeEnableForDiagnostics:!0})});if(e.phases.push(a.phaseResult),r=a.result,t.connected=a.phaseResult.success,!a.phaseResult.success||void 0===r)return{summary:t,triggeredNavigation:!1};const o=r;let i=await Xs(e.ctxManager,e.nativeTimeline,"native-ws-connect",e.targetPageId,e.watchMs,e.warnings);if("native-ws-connect"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};if(Ms(e.requestedPhase))return await Zs(e.requestedPhase,o,e.phases,t),i=await Xs(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await Fs("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await Xs(e.ctxManager,e.nativeTimeline,"native-runtime-enable",e.targetPageId,e.watchMs,e.warnings),!s.phaseResult.success||"native-runtime-enable"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};const c=await Fs("native-evaluate-url",async()=>await Hs(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await Xs(e.ctxManager,e.nativeTimeline,"native-evaluate-url",e.targetPageId,e.watchMs,e.warnings),!c.phaseResult.success||"native-evaluate-url"===e.requestedPhase||i)return{summary:t,triggeredNavigation:i};const l=await Fs("native-dom-read",async()=>await Ws(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await Xs(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function ec(e){const t=e.toLowerCase();return t.includes("device-action-report")?"device-action-report":t.includes("boss_risk_report")?"boss-risk-report":t.includes("apm-fe.zhipin.com")||t.includes("/wapi/zpapm/actionlog/")?"apm-action-log":t.includes("zhipin-security")||t.includes("/security/")?"zhipin-security":void 0}async function tc(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=ec(t);void 0!==n&&o({kind:"request",reason:n,capturedAt:(new Date).toISOString(),url:t,method:e.method(),resourceType:e.resourceType()})},s=e=>{const t=e.url(),n=ec(t);if(void 0===n)return;const r=e.request();o({kind:"response",reason:n,capturedAt:(new Date).toISOString(),url:t,method:r.method(),resourceType:r.resourceType(),status:e.status()})},c=e=>{a.push({capturedAt:(new Date).toISOString(),url:e.url()})};e.on("request",i),e.on("response",s),e.on("framenavigated",c);try{await Bs(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function nc(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&Tt(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function rc(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function ac(e,t){return await e.evaluate(e=>{const t="localStorage"===e?window.localStorage:window.sessionStorage;return Array.from({length:t.length},(e,n)=>{const r=t.key(n)??"";return{key:r,value:r.length>0?t.getItem(r)??"":""}}).filter(e=>e.key.length>0)},t)}async function oc(e){return(await e.evaluate(e=>{const t=["localStorage","sessionStorage"],n=[];for(const r of t){const t="localStorage"===r?window.localStorage:window.sessionStorage;for(const a of e){const e=t.getItem(a);null!==e&&n.push({area:r,key:a,value:e})}}return n},Rs)).map(e=>Os(e.area,{key:e.key,value:e.value}))}async function ic(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function sc(e){return await e.evaluate(()=>{const e=window,t=Object.keys(e),n=t.filter(e=>e.startsWith("cdc_")||e.includes("_cdc_")).slice(0,20),r=t.filter(e=>e.toLowerCase().includes("webdriver")).slice(0,20),a=t.filter(e=>{const t=e.toLowerCase();return t.includes("playwright")||t.includes("puppeteer")||t.includes("selenium")||t.includes("chromedriver")}).slice(0,20),o=navigator.permissions?.query,i="function"==typeof o?o.toString().includes("[native code]"):void 0;return{..."boolean"==typeof navigator.webdriver?{navigatorWebdriver:navigator.webdriver}:{},userAgentContainsHeadless:navigator.userAgent.toLowerCase().includes("headless"),languagesLength:navigator.languages?.length??0,pluginsLength:navigator.plugins?.length??0,hasChromeRuntime:"object"==typeof e.chrome&&null!==e.chrome&&"runtime"in e.chrome,...void 0!==i?{permissionQueryIsNative:i}:{},hasPlaywrightBinding:"__playwright__binding__"in e,hasPwInitScripts:"__pwInitScripts"in e,cdcKeys:n,webdriverKeys:r,automationLikeWindowKeys:a}})}function cc(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function lc(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function dc(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function uc(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function mc(e,t){const n=cc(e),r=cc(t),a=[...new Set([...n.keys(),...r.keys()])],o=[];for(const e of a){const t=n.get(e),a=r.get(e),i=a??t;if(!i)continue;const s={};for(const e of lc(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=dc(n,r))}const c={};for(const e of lc(t?.booleanFields,a?.booleanFields)){const n=t?.booleanFields?.[e],r=a?.booleanFields?.[e];n!==r&&(c[e]={...void 0!==n?{before:n}:{},...void 0!==r?{after:r}:{}})}const l={};for(const e of lc(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=uc(n,r))}void 0!==t&&void 0!==a&&0===Object.keys(s).length&&0===Object.keys(c).length&&0===Object.keys(l).length||o.push({area:i.area,key:i.key,beforePresent:void 0!==t,afterPresent:void 0!==a,...Object.keys(s).length>0?{numericDeltas:s}:{},...Object.keys(c).length>0?{booleanChanges:c}:{},...Object.keys(l).length>0?{arrayLengthDeltas:l}:{}})}return o}async function pc(e,t){const[n,r,a]=await Promise.all([ac(e,"localStorage"),ac(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>Ds(e.key)).map(e=>Os("localStorage",e)),...r.filter(e=>Ds(e.key)).map(e=>Os("sessionStorage",e))];return{localStorage:n.map(e=>Os("localStorage",e)),sessionStorage:r.map(e=>Os("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>$s(e)),counterDiffs:mc(t,o)}}var gc=ja({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:xs,output:Ss,execute:async(e,t)=>{const n=e.phase??"native",r=e.watchMs??3e3,a=e.networkEventLimit??30,o=ue(),i=de(),s=[],c=[],l=[];let d,u,m,p=!1,g=!1,f=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const h=await Fs("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>Dt(o,e)),v=js(y,e.targetPageId,s);if(v&&(d=Dt(o,v),f=v.targetId,l.push(Ls(o,"native",v,void 0))),!h.phaseResult.success||"native"===n)return{success:h.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await Vs(o,l,"native-watch",f,r,s),{success:h.phaseResult.success&&void 0!==f,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(!v)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(Ns(n)){const e=await Qs({requestedPhase:n,target:v,runtime:i,ctxManager:o,targetPageId:f,watchMs:r,phases:c,nativeTimeline:l,warnings:s});return{success:c.every(e=>e.success)&&!e.triggeredNavigation,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}if("resume-canvas"===n){const e=await Fs("resume-canvas",async()=>{const e=await Gi({skipRiskGate:!0});try{const t=await e.waitForResumeDialog(1e3);return{dialogState:t,geometry:t.canvasReady?await e.readResumeCanvasGeometry():void 0}}finally{e.close()}});c.push(e.phaseResult);const t=e.result;return{success:e.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==t?{resumeCanvas:{dialogVisible:t.dialogState.dialogVisible,iframeFound:t.dialogState.iframeFound,iframeVisible:t.dialogState.iframeVisible,canvasReady:t.dialogState.canvasReady,...void 0!==t.geometry?.screenshotArea?{screenshotArea:t.geometry.screenshotArea}:{},...void 0!==t.geometry?.canvasSize?{canvasSize:t.geometry.canvasSize}:{},...void 0!==t.geometry?.error?{error:t.geometry.error}:{}}}:{},phases:c,warnings:s}}const w=await Fs("browser-attach",async()=>await i.getBrowser());c.push(w.phaseResult);const x=w.result;p=w.phaseResult.success,await zs(o,l,"browser-attach",f,s);const S=await Vs(o,l,"browser-attach-watch",f,r,s);if(S&&s.push("Browser attach was followed by a native URL change; treat this account/browser profile as unsafe for Playwright-backed zhipin tools."),!w.phaseResult.success||"browser-attach"===n)return{success:c.every(e=>e.success)&&!S,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if(S)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};const R=[];let C;"network-watch"===n&&void 0!==x&&(C=nc(x,v),C?R.push(tc(C,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",v);const I=await Fs("page-attach",async()=>await o.getPage("zhipin"));c.push(I.phaseResult);const k=I.result;if(g=I.phaseResult.success,k&&(d=await Ot(o,k)),await zs(o,l,"page-attach",f,s),!I.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await Fs("network-watch",async()=>rc(await Promise.all(R),a));c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents)}return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}if("page-attach"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){C!==k&&R.push(tc(k,r,a));const e=await Fs("network-watch",async()=>rc(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await zs(o,l,"network-watch",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let A=[];if("storage-summary"===n){try{A=await oc(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await Bs(r)}const E=await Fs("page-evaluate",async()=>await ic(k));c.push(E.phaseResult);const _=E.result;if(await zs(o,l,"page-evaluate",f,s),!E.phaseResult.success||"page-evaluate"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},phases:c,warnings:s};const N=await Fs("detector-fingerprint",async()=>await sc(k));c.push(N.phaseResult);const M=N.result;if(await zs(o,l,"detector-fingerprint",f,s),!N.phaseResult.success||"detector-fingerprint"===n)return{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await Fs("storage-summary",async()=>await pc(k,A));c.push(P.phaseResult);const T=P.result;return await zs(o,l,"storage-summary",f,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:g,nativeTimeline:l,...void 0!==_?{evaluate:_}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as fc}from"@roll-agent/sdk";import{z as hc}from"zod";import{createHash as yc}from"node:crypto";var bc,vc,wc={fetch:fetch,env:process.env},xc="https://huajune.duliday.com",Sc=new Set;function Rc(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function Cc(e,t){const n=Rc(e,t);if(void 0!==n){if("true"===n)return!0;if("false"===n)return!1;throw new Error(`Expected boolean env value "true" or "false" for ${t}, received "${n}".`)}}function Ic(e){const t=Cc(e,"RECRUITMENT_EVENTS_ENABLED"),n=Rc(e,"RECRUITMENT_EVENTS_API_BASE_URL")??xc,r=Rc(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=Rc(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function kc(e,t){const n=ye(e.defaultAgentId);if(void 0!==n)return n;he()&&Ac(t,"missing-instance-tracking","Recruitment event skipped: select a browserInstance, configure tracking-agent-id on the active browser instance, or set RECRUITMENT_EVENTS_DEFAULT_AGENT_ID.")}function Ac(e,t,n){Sc.has(t)||(Sc.add(t),e.warn(n))}function Ec(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function _c(e){return"object"==typeof e&&null!==e}function Nc(e,t){const n=e[t];return"string"==typeof n?n:void 0}function Mc(e){if(!_c(e)||!_c(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>_c(e)&&"error"===Nc(e,"status"));if(!_c(t))return;const n=Nc(t,"idempotencyKey"),r=t.error,a=_c(r)?Nc(r,"code"):void 0,o=_c(r)?Nc(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function Pc(e,t,n){const r=Ic(n.env);if(!r.enabled)return;const a=kc(r,t);if(void 0===r.apiToken)return void Ac(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(he()||Ac(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_DEFAULT_AGENT_ID."));const o={events:[{...e,agentId:a,eventTime:e.eventTime??(new Date).toISOString()}]},i=await n.fetch(Ec(r.apiBaseUrl),{method:"POST",headers:{Authorization:`Bearer ${r.apiToken}`,"Content-Type":"application/json"},body:JSON.stringify(o)});if(!i.ok){const e=await i.text().catch(()=>"");throw new Error(`Recruitment events API request failed (${i.status})${e.length>0?`: ${e}`:""}`)}const s=Mc(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function Tc(e,t){return`${e}:${yc("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function Bc(e,t){(async()=>{const n=vc??(async(e,t)=>{await Pc(e,t,{...wc,...bc})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function Dc(){return(new Date).toISOString().slice(0,10)}function Oc(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function $c(e,t,n){const r=e?.candidateInfo,a=Oc(r?.age),o=Oc(r?.education),i=Oc(r?.expectedSalary);return{name:Oc(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function Fc(e){return Oc(e?.candidateInfo.communicationPosition)??""}function jc(e){const t=Oc(e?.candidateInfo.expectedJobText);if(void 0===t)return"";const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?n[1]??"":t}function Lc(e){const t=Oc(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?Oc(n[0]):void 0}function qc(e){return e.length>0?{job:{jobName:e}}:{}}function zc(e,t){return e.trim().length>0&&t.trim().length>0}function Vc(e){const t=e.content.trim(),n=/微信(?:号)?\s*[::]?\s*([A-Za-z0-9_-]{5,})/.exec(t);return void 0!==n?.[1]?n[1]:/^[A-Za-z][A-Za-z0-9_-]{5,}$/.test(t)?t:void 0}function Jc(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&zc(n.name,n.position)&&Bc({idempotencyKey:Tc("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,Dc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_received",candidate:{name:n.name,position:n.position},job:{jobId:0,jobName:n.position},details:{unreadCount:n.unreadCount>0?n.unreadCount:1,lastMessagePreview:n.preview,conversationId:n.conversationId,candidateId:n.candidateId}},t)}function Uc(e,t){const n=Fc(e.candidateDetails),r=n,a=$c(e.candidateDetails,e.candidateName,r);if(!zc(a.name,a.position))return;const o=Lc(e.candidateDetails);Bc({idempotencyKey:Tc("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...qc(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function Hc(e,t){if(!e.clicked||0===e.name.length)return;const n=Oc(e.expectedPosition);if(void 0===n||!zc(e.name,n))return;const r=Oc(e.age),a=Oc(e.education),o=Oc(e.expectedSalary),i=Oc(e.expectedLocation);Bc({idempotencyKey:Tc("zhipin-candidate-contacted",[e.candidateId,e.name,Dc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"candidate_contacted",candidate:{name:e.name,position:n,...void 0!==r?{age:r}:{},...void 0!==a?{education:a}:{},...void 0!==o?{expectedSalary:o}:{},...void 0!==i?{expectedLocation:i}:{}},details:{candidateId:e.candidateId}},t)}function Wc(e,t){const n=Fc(e.candidateDetails),r=$c(e.candidateDetails,e.candidateName,n);if(!zc(r.name,r.position))return;const a=Lc(e.candidateDetails);Bc({idempotencyKey:Tc("zhipin-wechat-requested",[e.conversationId,e.candidateId,Dc()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...qc(n),details:{exchangeType:e.exchangeType??(void 0!==e.wechatNumber?"accepted":"requested"),conversationId:e.conversationId,candidateId:e.candidateId,...void 0!==e.wechatNumber?{wechatNumber:e.wechatNumber}:{}}},t)}function Kc(e,t,n,r){const a=Fc(e),o=jc(e),i=Lc(e),s=$c(e,e.candidateInfo.name,o);if(!zc(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=Vc(c);Bc({idempotencyKey:Tc("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...qc(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var Gc,Yc=hc.object({name:hc.string(),conversationId:hc.string(),candidateId:hc.string(),position:hc.string(),time:hc.string(),preview:hc.string(),unreadCount:hc.number(),hasUnread:hc.boolean(),index:hc.number()}),Xc=hc.object({success:hc.boolean(),candidates:hc.array(Yc),total:hc.number(),stats:hc.object({withName:hc.number(),withUnread:hc.number()})});function Zc(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Gc}}function Qc(e){return e.conversationId.length>0?e.conversationId:e.candidateId.length>0?e.candidateId:0!==e.name.length?[e.name,e.position,e.lastMessageTime].join("|"):void 0}function el(e){const t=new Set,n=[];for(const r of e){const e=Qc(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var tl=fc({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:hc.object({limit:hc.number().optional().describe("最多返回条数"),onlyUnread:hc.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:hc.enum(["time","unreadCount","name"]).default("time"),autoScroll:hc.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:hc.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:Xc,execute:async(e,t)=>{const n=e.onlyUnread??!1;let r,a;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const o=Zc();try{r=await o.openNativePagePort({requireChatPage:!0}),a=o.createNativeVisualActivitySession(r),await a.begin("正在读取消息列表");if(!await r.waitForSelector('.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',5e3))return await a.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};const i=n?"正在读取未读消息列表":"正在读取消息列表";await a.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:i,padding:8});const s=e.autoScroll??!0,c=e.maxScrolls??4,l=el(await r.readChatCandidates({autoScroll:s,maxScrolls:c,...void 0===e.limit||n?{}:{targetCount:e.limit}})).map(e=>({name:e.name,conversationId:e.conversationId,candidateId:e.candidateId,position:e.position,time:e.lastMessageTime,preview:e.messagePreview,unreadCount:e.unreadCount,hasUnread:e.hasUnread,index:e.index}));let d=n?l.filter(e=>e.hasUnread):l;const u=e.sortBy??"time";"time"===u||("unreadCount"===u?d.sort((e,t)=>t.unreadCount-e.unreadCount):"name"===u&&d.sort((e,t)=>e.name.localeCompare(t.name))),void 0!==e.limit&&(d=d.slice(0,e.limit));const m={withName:l.filter(e=>e.name.length>0).length,withUnread:l.filter(e=>e.hasUnread).length};return await a.succeed(n?`已读取 ${d.length} 条未读消息`:`已读取 ${d.length} 条消息`),t.logger.info(`Found ${d.length} candidates (${m.withUnread} with unread)`),Jc(d,t.logger),{success:!0,candidates:d,total:l.length,stats:m}}catch(e){return lo(e),await(a?.fail("读取消息列表失败")),t.logger.warn(`Native zhipin message read failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}}}finally{r?.close()}}});import{BrowserActionApprovalSchema as nl,BrowserPageInfoSchema as rl}from"@roll-agent/browser";import{defineTool as al}from"@roll-agent/sdk";import{z as ol}from"zod";var il,sl=ol.object({forceReload:ol.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:ol.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:nl.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),cl=ol.object({success:ol.boolean(),alreadyOnChat:ol.boolean(),usedSidebarClick:ol.boolean(),usedReload:ol.boolean(),chatReady:ol.boolean(),reloadSkippedReason:ol.enum(li).optional(),page:rl.optional(),error:ol.string().optional()});function ll(){return{getContextManager:ue,getRuntime:de,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...il}}async function dl(e,t){return Dt(e,await t.inspectPage())}var ul=al({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:sl,output:cl,execute:async(e,t)=>{const n=ll(),r=n.getContextManager();let a,o,i=!1;t.logger.info("Opening Boss chat page via native sidebar navigation");try{if(a=await n.openNativePagePort(!0===e.forceReload?{requireChatPage:!0}:{}),o=n.createNativeVisualActivitySession(a),await cn(a),!0===e.forceReload){const s=await a.inspectChatReloadTarget();if(!s.ok)return await o.fail("当前不是沟通页"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:!1,chatReady:!1,reloadSkippedReason:s.skippedReason,page:await dl(r,a),error:s.error};rn(t,n.getRuntime(),{action:"navigate",target:s.url,url:s.url,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),await o.begin("正在刷新沟通页"),t.logger.info("Reloading Boss chat page via native CDP Page.reload"),await a.reload({url:s.url,onReloadSent:()=>{i=!0}});return await a.waitForChatListReady({...void 0!==e.expectedConversationId?{expectedConversationId:e.expectedConversationId}:{}})?(await o.succeed("已刷新沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await dl(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await dl(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(go.nav.sidebar,{label:e,padding:10})}if(await a.isChatSurfaceOpen())return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,usedReload:i,chatReady:!0,page:await dl(r,a)};if(!await a.clickSidebarSection("chat",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到沟通导航"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await dl(r,a),error:"未找到沟通导航"};t.logger.info("Clicked Boss sidebar nav: 沟通");return await a.waitForChatSurface()?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!0,page:await dl(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await dl(r,a),error:"沟通页未就绪"})}catch(e){return lo(e),await(o?.fail("切换沟通页失败")),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,error:e instanceof Error?e.message:"切换沟通页失败"}}finally{a?.close()}}});import{defineTool as ml}from"@roll-agent/sdk";import{z as pl}from"zod";var gl,fl=pl.object({success:pl.boolean(),conversationId:pl.string(),candidateId:pl.string(),candidateName:pl.string(),index:pl.number(),hasUnread:pl.boolean(),unreadCount:pl.number(),lastMessageTime:pl.string(),messagePreview:pl.string(),error:pl.string().optional()});function hl(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...gl}}var yl=ml({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:pl.object({conversationId:pl.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:pl.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:pl.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:pl.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:fl,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=hl();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开目标聊天"),await a.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-user",{label:"正在定位候选人",padding:8});const o=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,preferUnread:e.preferUnread??!1,...void 0!==a?{motionObserver:a}:{}});return o.found?(await a.succeed(`已打开 ${o.name||"目标"} 的聊天`),t.logger.info(`Opened chat with ${o.name} (index: ${o.index})`),{success:!0,conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.name,index:o.index,hasUnread:o.hasUnread,unreadCount:o.unreadCount,lastMessageTime:o.lastMessageTime,messagePreview:o.messagePreview}):(await a.fail("打开聊天失败"),{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:o.error??`未找到候选人: ${e.candidateName??`index ${e.index}`}`})}catch(n){return lo(n),await(a?.fail("打开聊天失败")),t.logger.warn(`Native zhipin open chat failed: ${n instanceof Error?n.message:String(n)}`),{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:n instanceof Error?n.message:"打开聊天失败"}}finally{r?.close()}}});import{defineTool as bl}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as vl,prepareReplyContext as wl,ReplyAuthorityRequestError as xl}from"@roll-agent/reply-authority-client";import{z as Sl}from"zod";var Rl="·",Cl=/[--—–]/,Il=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function kl(e){return(e??"").trim()}function Al(e){return e.some(e=>"candidate_expected_location"!==e.source)}function El(e){const t=Al(e)?e.filter(e=>"candidate_expected_location"!==e.source):e,n=t.slice(0,3).map(e=>{const t="candidate_expected_location"===e.source?"(弱)":"";return`${e.text}${t}`}),r=t.length>3?` 等${String(t.length)}项`:"";return`${n.join("、")}${r}`}function _l(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function Nl(e){const t=_l(e)?e.signals:e,n=_l(e)?e.analysisPath:void 0,r=_l(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=El(t);return!Al(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function Ml(e){const t=Il.exec(kl(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function Pl(e){const t=kl(e);if(!t||Il.test(t))return;if(!Cl.test(t))return;const[n=""]=t.split(Cl);return kl(n)||void 0}function Tl(e){const t=kl(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(Rl).map(e=>kl(e));return{expectedLocation:n,expectedPosition:r}}function Bl(e){const t=kl(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=Tl(e.expectedJobText),a=Pl(t),o=Ml(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var Dl,Ol=Sl.object({index:Sl.number(),sender:Sl.enum(["candidate","recruiter","system"]),messageType:Sl.enum(["text","system","resume","wechat-exchange"]),content:Sl.string(),time:Sl.string()}),$l=Sl.object({name:Sl.string(),age:Sl.string(),experience:Sl.string(),education:Sl.string(),communicationPosition:Sl.string(),expectedPosition:Sl.string(),expectedLocation:Sl.string(),expectedSalary:Sl.string(),tags:Sl.array(Sl.string())}),Fl=Sl.object({success:Sl.boolean(),conversationId:Sl.string(),candidateId:Sl.string(),candidateInfo:$l,preferredBrand:Sl.string().optional(),preferredBrandId:Sl.number().int().positive().optional(),locationSignals:Sl.array(vl).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:Sl.array(Ol),formattedHistory:Sl.array(Sl.string()),stats:Sl.object({totalMessages:Sl.number(),candidateMessages:Sl.number(),recruiterMessages:Sl.number(),systemMessages:Sl.number()}),error:Sl.string().optional()});function jl(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function Ll(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:jl(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function ql(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function zl(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),prepareReplyContext:wl,...Dl}}function Vl(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Jl(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Ul(e){return{candidateMessage:Jl(e.data.messages),conversationHistory:[...e.formattedHistory],candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==e.preferredBrand?{preferredBrand:e.preferredBrand}:{},...void 0!==e.preferredBrandId?{preferredBrandId:e.preferredBrandId}:{},target:{platform:"zhipin",conversationId:e.selectedTarget.conversationId,candidateId:e.selectedTarget.candidateId,recruiterUsername:e.recruiterUsername}}}var Hl=6e5,Wl=0;function Kl(e){return e instanceof xl?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function Gl(e){Kl(e)&&(Wl=Date.now()+Hl)}async function Yl(e){if(Date.now()<Wl)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===Jl(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=Oo(await e.nativePage.readUsernameEvidence())}catch(t){return void e.logger.debug(`Skip Reply Authority prepare: recruiter username read failed: ${t instanceof Error?t.message:String(t)}`)}if(!t.found)return void e.logger.debug("Skip Reply Authority prepare: recruiter username was not detected.");const n=Ul({data:e.data,selectedTarget:e.selectedTarget,recruiterUsername:t.username,formattedHistory:e.formattedHistory,communicationPosition:e.communicationPosition,expectedPosition:e.expectedPosition,expectedLocation:e.expectedLocation,preferredBrand:e.preferredBrand,preferredBrandId:e.preferredBrandId}),r=Date.now();try{e.deps.prepareReplyContext(n).then(t=>{e.logger.info(`Reply Authority prepare ${t.status} for ${e.selectedTarget.conversationId} in ${String(Date.now()-r)}ms`)}).catch(t=>{Gl(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){Gl(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var Xl=bl({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Sl.object({conversationId:Sl.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Sl.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Sl.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Sl.number().default(100).describe("最多返回的消息条数")}),output:Fl,execute:async(e,t)=>{const n=e.maxMessages??100,r=zl(),a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i;const s=a?"正在打开目标聊天":"正在准备当前聊天",c="正在提取聊天记录",l=async(e,t)=>(await(i?.fail(e)),Ll(t));try{o=await r.openNativePagePort(),i=r.createNativeVisualActivitySession(o),await i.begin(s);const d=a?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==d&&!d.found)return await l("打开聊天失败",d.error??"打开聊天失败");if(void 0===d&&!await o.isChatSurfaceOpen().catch(()=>!1))return await l("当前不是沟通页","当前页面不是 BOSS 沟通页,无法读取当前聊天详情");t.logger.info("Extracting candidate info"+(void 0!==d?` for ${d.name}`:" (current window)")),await i.begin(c),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:c,padding:12});const u=d?.name??e.candidateName??"",m=await o.readActiveChatPanel();if(u.length>0&&(!m||!ql(u,m.candidateName)))return await l("聊天面板未同步",`右侧聊天面板未切换到 ${u}`);const p=await o.readSelectedChatTarget();if(!p)return await l("未识别当前会话","未能提取当前选中聊天的 conversationId/candidateId");if(!(void 0===d||d.conversationId.length>0&&p.conversationId===d.conversationId||0===d.conversationId.length&&d.candidateId.length>0&&p.candidateId===d.candidateId))return await l("当前会话不一致",`当前选中会话与目标会话不一致: ${d?.name||d?.conversationId||""}`);if(m&&p.candidateName.length>0&&!ql(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const g=await o.readCandidateChatDetails(n),f=Bl({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),h=Vl(g.messages),y={totalMessages:g.messages.length,candidateMessages:g.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:g.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:g.messages.filter(e=>"system"===e.sender).length};return await i.succeed(`已提取 ${y.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${y.totalMessages} messages`),Kc(g,p.conversationId,p.candidateId,t.logger),await Yl({nativePage:o,deps:r,data:g,selectedTarget:p,formattedHistory:h,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,preferredBrand:f.preferredBrand,preferredBrandId:f.preferredBrandId,logger:t.logger}),{success:!0,conversationId:p.conversationId,candidateId:p.candidateId,candidateInfo:{name:g.candidateInfo.name,age:g.candidateInfo.age,experience:g.candidateInfo.experience,education:g.candidateInfo.education,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,expectedSalary:g.candidateInfo.expectedSalary,tags:[...g.candidateInfo.tags]},...void 0!==f.preferredBrand?{preferredBrand:f.preferredBrand}:{},...void 0!==f.preferredBrandId?{preferredBrandId:f.preferredBrandId}:{},locationSignals:[],chatMessages:[...g.messages],formattedHistory:h,stats:y}}catch(e){return lo(e),await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),Ll(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as Zl}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Ql,GenerateSignedReplyResponseSchema as ed,ReasoningConfigSchema as td,ReplyAuthorityRequestError as nd,ReplyStreamFinalEventSchema as rd,ReplyStreamLocationResolvedEventSchema as ad,streamGenerateSignedReply as od}from"@roll-agent/reply-authority-client";import{z as id}from"zod";import{ReplyGateAdvisoryCodeSchema as sd}from"@roll-agent/reply-authority-client";import{z as cd}from"zod";var ld=["option_1","option_2"],dd=["judge","orchestrator"],ud=[...dd,"service_recommended_fallback","explicit_no_judge"],md={RUBRIC_FETCH_FAILED:"rubric_fetch_failed",RUBRIC_MISMATCH:"rubric_mismatch",INVALID_VARIANT_SHAPE:"invalid_variant_shape",JUDGE_SAMPLING_FAILED:"judge_sampling_failed",JUDGE_OUTPUT_INVALID:"judge_output_invalid"},pd=cd.array(sd).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),gd=cd.object({chosenOption:cd.enum(ld).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:cd.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:pd.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:cd.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),fd=gd.omit({judgeModel:!0}).extend({confirmedFindingCodes:pd.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as hd}from"node:crypto";var yd=new Map;function bd(e){for(const[t,n]of yd)n.expiresAt<=e&&yd.delete(t)}function vd(e,t=Math.floor(Date.now()/1e3)){if(bd(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${hd()}`,r={preparedReplyId:n,...e};return yd.set(n,{...r,consumed:!1}),r}function wd(e,t=Math.floor(Date.now()/1e3)){const n=yd.get(e);return void 0===n?(bd(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(yd.delete(e),bd(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(yd.set(e,{...n,consumed:!0}),{ok:!0,record:Rd(n)})}function xd(e,t=Math.floor(Date.now()/1e3)){const n=yd.get(e);return void 0===n?(bd(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(yd.delete(e),bd(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:Rd(n)}}function Sd(e,t,n=Math.floor(Date.now()/1e3)){const r=xd(e,n);if(!r.ok)return r;const a=yd.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(yd.set(e,{...a,judgement:t}),{ok:!0,record:Rd({...a,judgement:t})})}function Rd(e){return{preparedReplyId:e.preparedReplyId,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,stage:e.stage,confidence:e.confidence,expiresAt:e.expiresAt,...void 0!==e.requestId?{requestId:e.requestId}:{},...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},...void 0!==e.variantGroup?{variantGroup:e.variantGroup}:{},...void 0!==e.judgement?{judgement:e.judgement}:{}}}import{CandidateInfoSchema as Cd}from"@roll-agent/reply-authority-client";import{z as Id}from"zod";var kd=1e3,Ad=8,Ed=500,_d=200,Nd=12,Md=100,Pd=Cd.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),Td=Id.object({candidateMessage:Id.string().min(1).max(kd),recentConversation:Id.array(Id.string().min(1).max(Ed)).max(Ad),candidateInfo:Pd.optional(),preferredBrand:Id.string().min(1).max(_d).optional()});function Bd(e){return e.replace(/(?<!\d)1[3-9]\d{9}(?!\d)/g,"[手机号已隐藏]").replace(/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}/g,"[邮箱已隐藏]").replace(/(微信|wechat|vx|v信)(?:号|是|[::]|\s)*[A-Za-z0-9][-_A-Za-z0-9]{5,19}/gi,"$1 [账号已隐藏]")}function Dd(e,t){const n=Bd(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function Od(e){if(void 0===e)return;const t={...void 0!==e.age?{age:Dd(e.age,_d)}:{},...void 0!==e.experience?{experience:Dd(e.experience,_d)}:{},...void 0!==e.education?{education:Dd(e.education,_d)}:{},...void 0!==e.communicationPosition?{communicationPosition:Dd(e.communicationPosition,_d)}:{},...void 0!==e.expectedPosition?{expectedPosition:Dd(e.expectedPosition,_d)}:{},...void 0!==e.expectedLocation?{expectedLocation:Dd(e.expectedLocation,_d)}:{},...void 0!==e.expectedSalary?{expectedSalary:Dd(e.expectedSalary,_d)}:{},...void 0!==e.info?{info:e.info.map(e=>Dd(e,Md)).filter(e=>e.length>0).slice(0,Nd)}:{}};return Object.keys(t).length>0?t:void 0}function $d(e){const t=Dd(e.candidateMessage,kd);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>Dd(e,Ed)).filter(e=>e.length>0).slice(-Ad),r=Od(e.candidateInfo),a=void 0!==e.preferredBrand?Dd(e.preferredBrand,_d):void 0;return Td.parse({candidateMessage:t,recentConversation:n,...void 0!==r?{candidateInfo:r}:{},...void 0!==a&&a.length>0?{preferredBrand:a}:{}})}function Fd(e){return`(() => {\n const input = ${JSON.stringify(e)};\n const rootId = "roll-agent-reply-preview-root";\n const statusId = "roll-agent-reply-preview-status";\n const draftId = "roll-agent-reply-preview-draft";\n const variantsId = "roll-agent-reply-preview-variants";\n const findingsId = "roll-agent-reply-preview-findings";\n const badgeId = "roll-agent-reply-preview-badge";\n const locationId = "roll-agent-reply-preview-location";\n const spinnerId = "roll-agent-reply-preview-spinner";\n const styleId = "roll-agent-reply-preview-style";\n const stateKey = "__rollAgentReplyPreviewState";\n const state = window[stateKey] || {};\n window[stateKey] = state;\n\n const cancelPendingRemove = () => {\n if (state.removeTimer) {\n window.clearTimeout(state.removeTimer);\n state.removeTimer = undefined;\n }\n };\n\n if (input.mode === "clear") {\n const existingRoot = document.getElementById(rootId);\n if (!existingRoot) return true;\n\n cancelPendingRemove();\n existingRoot.style.opacity = "0";\n existingRoot.style.transform = "translateY(8px) scale(0.98)";\n state.removeTimer = window.setTimeout(() => {\n if (document.getElementById(rootId) === existingRoot) {\n existingRoot.remove();\n }\n state.removeTimer = undefined;\n }, 240);\n return true;\n }\n\n const ensureRoot = () => {\n let root = document.getElementById(rootId);\n if (root) {\n cancelPendingRemove();\n return root;\n }\n\n if (!document.getElementById(styleId)) {\n const style = document.createElement("style");\n style.id = styleId;\n style.textContent = "@keyframes rollAgentReplyPreviewSpin { to { transform: rotate(360deg); } }";\n document.head.append(style);\n }\n\n root = document.createElement("div");\n root.id = rootId;\n root.style.position = "fixed";\n root.style.right = "20px";\n root.style.bottom = "20px";\n root.style.width = "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = "42vh";\n root.style.zIndex = "2147483646";\n root.style.pointerEvents = "none";\n root.style.display = "flex";\n root.style.flexDirection = "column";\n root.style.gap = "10px";\n root.style.padding = "14px";\n root.style.borderRadius = "16px";\n root.style.border = "1px solid rgba(45, 212, 191, 0.32)";\n root.style.background = "rgba(15, 23, 42, 0.88)";\n root.style.color = "#F8FAFC";\n root.style.boxShadow = "0 22px 58px rgba(15, 23, 42, 0.34)";\n root.style.backdropFilter = "blur(14px)";\n root.style.fontFamily =\n "-apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";\n root.style.opacity = "0";\n root.style.transform = "translateY(8px)";\n root.style.transition = "opacity 180ms ease, transform 220ms ease";\n\n const header = document.createElement("div");\n header.style.display = "flex";\n header.style.alignItems = "center";\n header.style.gap = "8px";\n header.style.minWidth = "0";\n\n const spinner = document.createElement("div");\n spinner.id = spinnerId;\n spinner.setAttribute("aria-hidden", "true");\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.flex = "0 0 auto";\n spinner.style.borderRadius = "999px";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.transition =\n "border-color 160ms ease, background 160ms ease, box-shadow 160ms ease, transform 160ms ease";\n\n const status = document.createElement("div");\n status.id = statusId;\n status.style.minWidth = "0";\n status.style.fontSize = "13px";\n status.style.fontWeight = "700";\n status.style.lineHeight = "18px";\n status.style.overflow = "hidden";\n status.style.textOverflow = "ellipsis";\n status.style.whiteSpace = "nowrap";\n\n const badge = document.createElement("div");\n badge.id = badgeId;\n badge.style.alignSelf = "flex-start";\n badge.style.fontSize = "11px";\n badge.style.lineHeight = "16px";\n badge.style.padding = "3px 8px";\n badge.style.borderRadius = "999px";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n badge.textContent = "临时草稿";\n\n const location = document.createElement("div");\n location.id = locationId;\n location.style.display = "none";\n location.style.flexWrap = "wrap";\n location.style.gap = "6px";\n location.style.fontSize = "11px";\n location.style.lineHeight = "16px";\n location.style.color = "#99F6E4";\n\n const draft = document.createElement("div");\n draft.id = draftId;\n draft.style.maxHeight = "30vh";\n draft.style.overflow = "hidden auto";\n draft.style.whiteSpace = "pre-wrap";\n draft.style.fontSize = "14px";\n draft.style.lineHeight = "21px";\n draft.style.color = "#E2E8F0";\n\n const variants = document.createElement("div");\n variants.id = variantsId;\n variants.style.display = "none";\n variants.style.gridTemplateColumns = "repeat(2, minmax(0, 1fr))";\n variants.style.gap = "10px";\n variants.style.minHeight = "0";\n\n const findings = document.createElement("div");\n findings.id = findingsId;\n findings.style.display = "none";\n findings.style.flexDirection = "column";\n findings.style.gap = "4px";\n findings.style.paddingTop = "2px";\n findings.style.fontSize = "11px";\n findings.style.lineHeight = "16px";\n findings.style.color = "#CBD5E1";\n\n header.append(spinner, status);\n root.append(header, location, badge, draft, variants, findings);\n document.documentElement.append(root);\n return root;\n };\n\n const root = ensureRoot();\n const status = document.getElementById(statusId);\n const location = document.getElementById(locationId);\n const draft = document.getElementById(draftId);\n const variants = document.getElementById(variantsId);\n const findings = document.getElementById(findingsId);\n const badge = document.getElementById(badgeId);\n const spinner = document.getElementById(spinnerId);\n if (!status || !location || !draft || !variants || !findings || !badge || !spinner) return false;\n\n root.style.opacity = "1";\n root.style.transform = "translateY(0)";\n root.style.width = input.variantSelection\n ? "min(760px, calc(100vw - 40px))"\n : "min(420px, calc(100vw - 40px))";\n root.style.maxHeight = input.variantSelection ? "58vh" : "42vh";\n\n if (typeof input.label === "string") {\n status.textContent = input.label;\n }\n\n if (typeof input.locationSummary === "string" && input.locationSummary.length > 0) {\n location.textContent = input.locationSummary;\n location.style.display = "flex";\n } else if (input.mode === "begin") {\n location.textContent = "";\n location.style.display = "none";\n }\n\n if (typeof input.draftText === "string") {\n draft.textContent = input.draftText;\n }\n\n const resetVariantSelection = () => {\n variants.replaceChildren();\n variants.style.display = "none";\n findings.replaceChildren();\n findings.style.display = "none";\n draft.style.display = "block";\n };\n\n const renderVariantSelection = (selection) => {\n variants.replaceChildren();\n findings.replaceChildren();\n draft.style.display = "none";\n variants.style.display = "grid";\n variants.style.gridTemplateColumns =\n window.innerWidth < 640 ? "minmax(0, 1fr)" : "repeat(2, minmax(0, 1fr))";\n\n for (const option of selection.options) {\n const card = document.createElement("div");\n card.style.minWidth = "0";\n card.style.maxHeight = "32vh";\n card.style.overflow = "hidden auto";\n card.style.padding = "10px";\n card.style.borderRadius = "8px";\n card.style.border = "1px solid rgba(148, 163, 184, 0.28)";\n card.style.background = "rgba(15, 23, 42, 0.66)";\n\n const title = document.createElement("div");\n title.textContent = option.option;\n title.style.marginBottom = "6px";\n title.style.fontSize = "12px";\n title.style.fontWeight = "700";\n title.style.lineHeight = "16px";\n title.style.color = "#A7F3D0";\n\n const body = document.createElement("div");\n body.textContent = option.suggestedReply;\n body.style.whiteSpace = "pre-wrap";\n body.style.fontSize = "13px";\n body.style.lineHeight = "20px";\n body.style.color = "#E2E8F0";\n\n card.append(title, body);\n variants.append(card);\n }\n\n if (selection.findings.length > 0) {\n findings.style.display = "flex";\n const title = document.createElement("div");\n title.textContent = "检查项";\n title.style.fontWeight = "700";\n title.style.color = "#FDE68A";\n findings.append(title);\n\n for (const finding of selection.findings) {\n const item = document.createElement("div");\n item.textContent = finding.description || finding.code;\n item.style.color = "#CBD5E1";\n findings.append(item);\n }\n } else {\n findings.style.display = "none";\n }\n };\n\n if (input.variantSelection && input.variantSelection.options.length > 0) {\n renderVariantSelection(input.variantSelection);\n } else if (\n input.mode === "begin" ||\n input.mode === "draft" ||\n input.mode === "final" ||\n input.mode === "complete" ||\n input.mode === "fail"\n ) {\n resetVariantSelection();\n }\n\n if (input.mode === "final" || input.mode === "complete") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.borderTopColor = "rgba(34, 197, 94, 0.24)";\n spinner.style.background = "#22C55E";\n spinner.style.boxShadow = "0 0 0 3px rgba(34, 197, 94, 0.14)";\n badge.textContent = "最终回复";\n badge.style.background = "rgba(34, 197, 94, 0.16)";\n badge.style.color = "#BBF7D0";\n root.style.borderColor = "rgba(74, 222, 128, 0.34)";\n } else if (input.mode === "fail") {\n spinner.style.animation = "none";\n spinner.style.borderColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.borderTopColor = "rgba(245, 158, 11, 0.28)";\n spinner.style.background = "#F59E0B";\n spinner.style.boxShadow = "0 0 0 3px rgba(245, 158, 11, 0.14)";\n badge.textContent = "生成失败";\n badge.style.background = "rgba(245, 158, 11, 0.18)";\n badge.style.color = "#FDE68A";\n root.style.borderColor = "rgba(251, 191, 36, 0.4)";\n } else {\n spinner.style.width = "14px";\n spinner.style.height = "14px";\n spinner.style.animation = "rollAgentReplyPreviewSpin 820ms linear infinite";\n spinner.style.border = "2px solid rgba(153, 246, 228, 0.24)";\n spinner.style.borderTopColor = "#99F6E4";\n spinner.style.background = "transparent";\n spinner.style.boxShadow = "none";\n badge.textContent = input.provisional === false ? "最终回复" : "临时草稿";\n badge.style.background = "rgba(20, 184, 166, 0.18)";\n badge.style.color = "#99F6E4";\n root.style.borderColor = "rgba(45, 212, 191, 0.32)";\n }\n\n return true;\n })()`}var jd,Ld=class{target;constructor(e){this.target=e}async begin(e,t){return await this.render({mode:"begin",label:e,...void 0!==t&&t.length>0?{locationSummary:t}:{},draftText:"",provisional:!0})}async updateStatus(e){return await this.render({mode:"status",label:e})}async updateDraft(e,t){return await this.render({mode:t?"draft":"final",draftText:e,provisional:t})}async complete(e,t,n){return await this.render({mode:"complete",label:e,draftText:t,provisional:!1,...void 0!==n?{variantSelection:n}:{}})}async fail(e){return await this.render({mode:"fail",label:e})}async clear(){return await this.render({mode:"clear"})}async render(e){if(!je())return!1;try{return await this.target.evaluateJson(Fd(e))}catch{return!1}}},qd=id.object({conversationId:id.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:id.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:id.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:id.number().default(100).describe("最多读取的聊天消息条数"),reasoning:td.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),zd=["rejected","timeout","server_error"],Vd=id.object({requestId:id.string().optional(),elapsedMs:id.number().int().min(0).optional(),clientTimeoutMs:id.number().int().positive().optional(),lastStartedPhase:id.string().min(1).optional(),activePhase:id.string().min(1).optional(),phaseLatencies:id.record(id.number().int().min(0)).optional()}),Jd=id.object({success:id.boolean(),preparedReplyId:id.string().optional(),suggestedReply:id.string().optional(),stage:id.string().optional(),confidence:id.number().optional(),expiresAt:id.number().optional(),timing:id.object({totalLatencyMs:id.number().int().min(0),replyLatencyMs:id.number().int().min(0).optional(),turnPlanningLatencyMs:id.number().int().min(0).optional(),contextBuildingLatencyMs:id.number().int().min(0).optional(),preparedContextHit:id.boolean()}).optional(),gateRewritten:id.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:id.object({groupId:id.string().min(1),options:id.array(id.object({option:id.enum(ld),suggestedReply:id.string(),expiresAt:id.number().int().min(0)})).min(2).max(2),findings:id.array(id.object({code:id.string().min(1),description:id.string().min(1)})),rubricVersion:id.string().min(1),rubricHash:id.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:id.string().optional(),errorKind:id.enum(zd).optional()}).extend(Vd.shape),Ud={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function Hd(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),createReplyPreviewVisualSession:e=>new Ld(e),streamGenerateSignedReply:od,fetchReplyFeedbackRubric:Ql,savePreparedReply:vd,random:Math.random,...jd}}function Wd(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Kd(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function Gd(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function Yd(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function Xd(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function Zd(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function Qd(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function eu(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function tu(e){const t=eu(e.navigationUnreadCount);return t>0?t:"candidate"===Qd(e.data.messages)?.sender?1:0}function nu(e){const t=Bl({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:Zd(e.data.messages),conversationHistory:Xd(e.data.messages),candidateInfo:{name:e.data.candidateInfo.name,age:e.data.candidateInfo.age,experience:e.data.candidateInfo.experience,education:e.data.candidateInfo.education,communicationPosition:t.communicationPosition,expectedPosition:t.expectedPosition,expectedLocation:t.expectedLocation,expectedSalary:e.data.candidateInfo.expectedSalary,info:[...e.data.candidateInfo.tags]},...void 0!==t.preferredBrand?{preferredBrand:t.preferredBrand}:{},...void 0!==t.preferredBrandId?{preferredBrandId:t.preferredBrandId}:{},...void 0!==e.reasoning?{modelConfig:{reasoning:e.reasoning}}:{},target:{platform:"zhipin",conversationId:e.conversationId,candidateId:e.candidateId,recruiterUsername:e.recruiterUsername}}}function ru(e){return"phase.started"===e.type?Kd(e,"label")??Ud[Kd(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(Kd(e,"toolName")?`: ${Kd(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(Kd(e,"toolName")?`: ${Kd(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function au(e,t,n={}){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{},...n}}function ou(e,t){const n=t instanceof nd?t:void 0,r=e.requestId??n?.meta.requestId,a=Object.fromEntries(e.phaseLatencies);return{...void 0!==r?{requestId:r}:{},...void 0!==e.startedAtMs?{elapsedMs:Math.max(0,Math.round(Date.now()-e.startedAtMs))}:{},...void 0!==n?{clientTimeoutMs:n.meta.timeoutMs}:{},...void 0!==e.lastStartedPhase?{lastStartedPhase:e.lastStartedPhase}:{},...void 0!==e.activePhase?{activePhase:e.activePhase}:{},...Object.keys(a).length>0?{phaseLatencies:a}:{}}}function iu(e){return e.replace(/\s+\(url=.*\)$/,"")}function su(e){const t=e instanceof Error?e.message:String(e);if(e instanceof nd){const n=iu(t);if(422===e.statusCode)return{message:`回复未通过事实核验:${n}`,errorKind:"rejected"};if(504===e.statusCode||n.includes("请求超时"))return{message:`AI 响应超时:${n}`,errorKind:"timeout"};if(void 0!==e.statusCode&&e.statusCode>=500)return{message:`Reply Authority 服务端异常:${n}`,errorKind:"server_error"}}return{message:t}}function cu(e){return e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(e<1e4?1:0)}s`}function lu(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function du(e){const t=["回复已生成",`总 ${cu(e.totalLatencyMs)}`];return void 0!==e.replyLatencyMs&&t.push(`生成 ${cu(e.replyLatencyMs)}`),e.preparedContextHit&&t.push("预热命中"),e.gateRewritten&&t.push("终稿经安全门调整"),t.join(" · ")}function uu(e,t=void 0,n=!1){return{success:!0,preparedReplyId:e.preparedReplyId,suggestedReply:e.suggestedReply,stage:e.stage,confidence:e.confidence,expiresAt:e.expiresAt,...void 0!==e.requestId?{requestId:e.requestId}:{},...void 0!==t?{timing:t}:{},...n?{gateRewritten:n}:{},..."judge_ready"===e.variantGroup?.state?{replyVariantSelection:{groupId:e.variantGroup.groupId,options:e.variantGroup.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply,expiresAt:e.envelopeExp})),findings:e.variantGroup.findings.map(e=>({code:e.code,description:e.description})),rubricVersion:e.variantGroup.rubricVersion,rubricHash:e.variantGroup.rubricHash}}:{}}}function mu(e,t){const n=e.items[0],r=e.items[1];return void 0===n||void 0===r?e.items:t()<.5?[r,n]:[n,r]}function pu(e){return{state:"not_learned",groupId:e.replyVariants.groupId,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},chosenVariant:"draft",reason:e.reason}}function gu(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return pu({...e,reason:md.RUBRIC_MISMATCH});const t=mu(e.replyVariants,e.random);if(2!==t.length)return pu({...e,reason:md.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return pu({...e,reason:md.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:ld[t]??"option_1",variant:e.variant,suggestedReply:e.suggestedReply,signedEnvelope:e.signedEnvelope,envelopeExp:e.envelopeExp})),a=r.find(t=>t.variant===e.replyVariants.recommended)?.option??"option_1";return{state:"judge_ready",groupId:e.replyVariants.groupId,options:r,findings:e.replyVariants.findings,rubricVersion:e.replyVariants.rubricVersion,rubricHash:e.replyVariants.rubricHash,...void 0!==e.replyVariants.feedbackExpiresAt?{feedbackExpiresAt:e.replyVariants.feedbackExpiresAt}:{},target:{platform:"zhipin",tenantId:e.tenantId,conversationId:e.conversationId},recommendedOption:a,judgeContext:e.judgeContext}}var fu=Zl({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:qd,output:Jd,execute:async(e,t)=>{const n=Hd(),r=e.maxMessages??100,a=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index;let o,i,s;const c={startedAtMs:void 0,requestId:void 0,lastStartedPhase:void 0,activePhase:void 0,phaseLatencies:new Map};try{o=await n.openNativePagePort(),i=n.createNativeVisualActivitySession(o),s=n.createReplyPreviewVisualSession(o),await cn(o),await i.begin(a?"正在打开目标聊天":"正在准备当前聊天");const l=a?await o.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,maxScrolls:4}):void 0;if(void 0!==l&&!l.found){const e=l.error??"打开聊天失败";return await i.fail(e),au(e)}if(void 0===l&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),au(e)}await i.begin("正在读取聊天上下文"),await i.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:"正在读取聊天上下文",padding:12});const d=l?.name??e.candidateName??"",u=await o.readActiveChatPanel();if(d.length>0&&(!u||!Wd(d,u.candidateName))){const e=`右侧聊天面板未切换到 ${d}`;return await i.fail("聊天面板未同步"),au(e)}const m=await o.readSelectedChatTarget();if(!m){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),au(e)}if(null!==u&&m.candidateName.length>0&&!Wd(m.candidateName,u.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`;return await i.fail("当前会话不一致"),au(e)}if(!(void 0===l||l.conversationId.length>0&&m.conversationId===l.conversationId||0===l.conversationId.length&&l.candidateId.length>0&&m.candidateId===l.candidateId)){const e=`当前选中会话与目标会话不一致: ${l?.name||l?.conversationId||""}`;return await i.fail("当前会话不一致"),au(e)}await o.waitForChatMessages();const p=await o.readCandidateChatDetails(r);if(null!==p.selectedTarget&&(p.selectedTarget.conversationId!==m.conversationId||p.selectedTarget.candidateId!==m.candidateId)){const e=`聊天详情目标与当前选中会话不一致: ${p.selectedTarget.conversationId}/${p.selectedTarget.candidateId}`;return await i.fail("聊天详情目标不一致"),au(e)}if(0===Zd(p.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),au(e)}const g=Oo(await o.readUsernameEvidence());if(!g.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),au(e)}const f=nu({data:p,conversationId:m.conversationId,candidateId:m.candidateId,recruiterUsername:g.username,reasoning:e.reasoning}),h=tu({navigationUnreadCount:l?.unreadCount,data:p});t.logger.info(`Generating reply preview for ${m.candidateName||m.candidateId}`);const y=Date.now();c.startedAtMs=y,await s.begin("正在生成回复");let b,v,w,x="",S=!1;for await(const e of n.streamGenerateSignedReply(f)){if("stream.started"===e.type&&(c.requestId=Kd(e,"requestId"),b=Kd(e,"tenantId")),"phase.started"===e.type){const t=Kd(e,"phase");void 0!==t&&(c.lastStartedPhase=t,c.activePhase=t)}if("phase.completed"===e.type){const t=Kd(e,"phase"),n=Gd(e,"latencyMs");void 0!==t&&void 0!==n&&(c.phaseLatencies.set(t,Math.round(n)),c.activePhase===t&&(c.activePhase=void 0))}"gate.completed"===e.type&&!0===Yd(e,"rewritten")&&(S=!0);const r=ru(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=ad.safeParse(e);if(t.success){const e=Nl({signals:t.data.signals,analysisPath:t.data.analysisPath,inquiryType:t.data.inquiryType});e.length>0&&await s.updateStatus(e)}}if("draft.started"===e.type&&(x="",await s.updateDraft(x,!0)),"draft.delta"===e.type){x+=Kd(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=rd.parse(e),a=ed.parse(r),o=c.phaseLatencies.get("turn_planning"),i=c.phaseLatencies.get("context_building"),l=Math.max(0,Math.round(Date.now()-y)),d=void 0!==a.latencyMs?Math.round(a.latencyMs):void 0,u=a.replyVariants?.items.reduce((e,t)=>Math.min(e,t.envelopeExp),a.envelopeExp)??a.envelopeExp,p=lu({turnPlanningLatencyMs:o,contextBuildingLatencyMs:i});let g;if(w={totalLatencyMs:l,...void 0!==d?{replyLatencyMs:d}:{},...void 0!==o?{turnPlanningLatencyMs:o}:{},...void 0!==i?{contextBuildingLatencyMs:i}:{},preparedContextHit:p},void 0!==a.replyVariants){if(void 0===b){const e="Reply Authority returned replyVariants without stream.started.tenantId; refusing to create a sendable prepared reply without feedback identity.";return t.logger.error(e),await s.fail("双稿反馈身份缺失"),au(e,void 0,ou(c))}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});g=gu({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:m.conversationId,judgeContext:$d(f),random:n.random}),"not_learned"===g.state&&t.logger.warn(`Reply variants will use a non-learning terminal outcome: reason=${g.reason}`)}catch(e){const n=e instanceof Error?e.message:String(e);t.logger.warn(`Failed to fetch reply feedback rubric; using a non-learning terminal outcome. reason=${md.RUBRIC_FETCH_FAILED} detail=${n}`),g=pu({replyVariants:a.replyVariants,tenantId:b,conversationId:m.conversationId,reason:md.RUBRIC_FETCH_FAILED})}}v=n.savePreparedReply({signedEnvelope:a.signedEnvelope,suggestedReply:a.suggestedReply,stage:a.stage,confidence:a.confidence,expiresAt:u,unreadCountBeforeReply:h,...void 0!==c.requestId?{requestId:c.requestId}:{},...void 0!==g?{variantGroup:g}:{}}),await s.complete(du({...w,gateRewritten:S}),a.suggestedReply,"judge_ready"===g?.state?{options:g.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})),findings:g.findings}:void 0)}}if(void 0===v){const e="Reply Authority stream 未返回 final";return await s.fail(e),au(e,void 0,ou(c))}return uu(v,w,S)}catch(e){lo(e);const t=su(e),n=ou(c,e);return await(s?.fail(t.message)),await(i?.fail(t.message)),au(t.message,t.errorKind,n)}finally{o?.close()}}});import{defineTool as hu}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as yu}from"@roll-agent/reply-authority-client";import{z as bu}from"zod";var vu,wu="zhipin_judge_prepared_reply",xu=bu.object({success:bu.boolean(),variantDecision:gd.optional(),decisionSource:bu.enum(ud).optional(),fallback:bu.boolean().optional(),recommendedOption:bu.enum(ld).optional(),error:bu.string().optional()}),Su=bu.object({preparedReplyId:bu.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:bu.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),Ru=new Map;function Cu(){return{fetchReplyFeedbackRubric:yu,...vu}}function Iu(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function ku(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return{kind:"fallback",source:"service_recommended_fallback",recommendedOption:n.recommendedOption,reason:t}}function Au(e){const t=e.trim(),n=/^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(t),r=n?.[1]?.trim()??t,a=r.indexOf("{"),o=r.lastIndexOf("}");if(a<0||o<a)throw new Error("judge 输出不是 JSON 对象");return JSON.parse(r.slice(a,o+1))}function Eu(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)return!1;const r=new Set(n.findings.map(e=>e.code));return t.every(e=>r.has(e))}function _u(e,t){const n=e.variantGroup;if("judge_ready"!==n?.state)throw new Error("preparedReplyId 未包含双稿选项");return["你是招聘回复双稿 judge。请只比较中性选项 option_1 / option_2,不要推断内部稿件身份。","两份 option 均已通过 Reply Authority 的事实硬门并完成签名;不得自行补充、改写或推断岗位事实。","请结合 candidateContext、当前回复阶段和冻结 rubric,比较目标贴合、合规、事实安全、语气、转化意图、候选人体验、简洁度与回归风险。","reason 必须指出候选人当前目标、胜出选项的具体优势,以及另一选项的具体不足;禁止只写“更好”“更自然”等空泛结论。","reason 不得复述姓名、电话、微信号、candidateId、conversationId 或大段候选人原话。","输出必须是 JSON 对象,且只包含:chosenOption、reason、confirmedFindingCodes。","confirmedFindingCodes 必须显式返回且只能来自 findings.code;只有逐项核实全部为误报时才返回空数组,禁止因不确定而省略。","confirmedFindingCodes 与 chosenOption 相互独立:只要某个 finding 指出的问题在任一 option 文本中真实存在,就必须勾选该 code,哪怕最终选择了包含该问题的 option。","若两稿差异很小,仍选择整体风险更低的一稿,并在 reason 中说明关键取舍。","",`stage: ${e.stage}`,`candidateContext: ${JSON.stringify(n.judgeContext??null)}`,`rubricVersion: ${n.rubricVersion}`,`rubricHash: ${n.rubricHash}`,`rubric: ${JSON.stringify(t.rubric)}`,`advisoryFindings: ${JSON.stringify(t.advisoryFindings)}`,`findings: ${JSON.stringify(n.findings)}`,`options: ${JSON.stringify(n.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})))}`].join("\n")}async function Nu(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=Cu();let o,i,s;try{o=await a.fetchReplyFeedbackRubric({tenantId:r.target.tenantId,rubricVersion:r.rubricVersion})}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.RUBRIC_FETCH_FAILED} detail=${r}`),ku(e,md.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return ku(e,md.RUBRIC_MISMATCH);try{i=await t.llm.generateText(_u(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_SAMPLING_FAILED} detail=${r}`),ku(e,md.JUDGE_SAMPLING_FAILED)}try{s=fd.parse(Au(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_OUTPUT_INVALID} detail=${r}`),ku(e,md.JUDGE_OUTPUT_INVALID)}return Eu(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:Bd(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${md.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),ku(e,md.JUDGE_OUTPUT_INVALID))}async function Mu(e,t,n="mcp-sampling"){if("judge_ready"!==e.variantGroup?.state)throw new Error("preparedReplyId 未包含双稿选项");if(void 0!==e.judgement)return e.judgement;const r=Ru.get(e.preparedReplyId);if(void 0!==r)return r;const a=Nu(e,t,n).then(n=>{const r=Sd(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{Ru.delete(e.preparedReplyId)});return Ru.set(e.preparedReplyId,a),a}function Pu(e){return"decision"===e.kind?{success:!0,variantDecision:e.decision,decisionSource:e.source}:{success:!0,fallback:!0,recommendedOption:e.recommendedOption,decisionSource:e.source,error:e.reason}}var Tu=hu({name:wu,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:Su,output:xu,execute:async(e,t)=>{const n=xd(e.preparedReplyId);if(!n.ok)return{success:!1,error:Iu(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return xu.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return Pu(await Mu(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as Bu}from"node:crypto";import{BrowserActionApprovalSchema as Du}from"@roll-agent/browser";import{postReplyFeedback as Ou}from"@roll-agent/reply-authority-client";import{defineTool as $u}from"@roll-agent/sdk";import{z as Fu}from"zod";import{createHash as ju}from"node:crypto";import{mkdirSync as Lu}from"node:fs";import{homedir as qu}from"node:os";import{dirname as zu,resolve as Vu}from"node:path";import{DatabaseSync as Ju}from"node:sqlite";import{postReplyFeedback as Uu,ReplyFeedbackBodySchema as Hu}from"@roll-agent/reply-authority-client";import{createAgentLogger as Wu}from"@roll-agent/sdk";var Ku,Gu,Yu=604800,Xu=3e4,Zu=1e3,Qu=9e5,em=9e5,tm=50,nm=["pending","delivered","dead"],rm=nm.map(e=>`'${e}'`).join(", "),am=Wu("browser-use-agent:reply-feedback-outbox");function om(e){return"~"===e?qu():e.startsWith("~/")?Vu(qu(),e.slice(2)):Vu(e)}function im(){return Vu(qu(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function sm(e,t){const n=process.env[e]?.trim();if(!n)return t;const r=Number(n);if(!Number.isSafeInteger(r)||r<=0)throw new Error(`${e} must be a positive integer.`);return r}function cm(e){const t=e.retentionSeconds??sm("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",Yu),n=e.flushIntervalMs??sm("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",Xu),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??im();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=om(r),o=e.retryBaseDelayMs??Zu,i=e.authRetryDelayMs??Qu;if(!Number.isSafeInteger(t)||t<=0)throw new Error("reply feedback outbox retentionSeconds must be a positive integer.");if(!Number.isSafeInteger(n)||n<=0)throw new Error("reply feedback outbox flushIntervalMs must be a positive integer.");if(!Number.isSafeInteger(o)||o<=0)throw new Error("reply feedback outbox retryBaseDelayMs must be a positive integer.");if(!Number.isSafeInteger(i)||i<=0)throw new Error("reply feedback outbox authRetryDelayMs must be a positive integer.");return{dbPath:a,retentionMs:1e3*t,flushIntervalMs:n,retryBaseDelayMs:o,authRetryDelayMs:i}}function lm(e){if(null===e||"boolean"==typeof e||"number"==typeof e)return JSON.stringify(e);if("string"==typeof e)return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(lm).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${lm(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function dm(e){return ju("sha256").update(e).digest("hex")}function um(e,t,n){if(void 0===n)return e+t;if(!Number.isSafeInteger(n)||n<0||n>Math.floor(Number.MAX_SAFE_INTEGER/1e3))throw new Error("feedbackExpiresAt must be a non-negative Unix timestamp in seconds.");return Math.min(e+t,1e3*n)}function mm(e,t){return e.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE group_id = ?").get(t)}function pm(e){return Hu.parse(JSON.parse(e.body_json))}function gm(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function fm(e){if("object"!=typeof e||null===e)return;const t=Reflect.get(e,"statusCode");return"number"==typeof t&&Number.isInteger(t)?t:void 0}function hm(e){return 401===e||403===e}function ym(e){return!(void 0!==e&&!hm(e))||(408===e||425===e||429===e||e>=500)}function bm(e,t){if(hm(t.last_status_code??void 0))return e.authRetryDelayMs;const n=Math.min(Math.max(t.attempt_count-1,0),10);return Math.min(e.retryBaseDelayMs*2**n,em)}function vm(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function wm(e,t){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead',\n last_error = 'Reply feedback outbox retention expired before delivery.',\n updated_at = ?,\n delete_after = ?\n WHERE state = 'pending' AND retry_expires_at <= ?").run(t,t+e.retentionMs,t),e.db.prepare("DELETE FROM reply_feedback_outbox WHERE state <> 'pending' AND delete_after <= ?").run(t)}function xm(e,t,n,r){e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'delivered', delivered_status = ?, last_status_code = NULL,\n last_error = NULL, updated_at = ?, delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(n,r,r+e.retentionMs,t.group_id,t.body_hash)}function Sm(e,t,n,r){const a=fm(n),o=gm(n);if(ym(a)&&r<t.retry_expires_at){const n=r+bm(e,{...t,last_status_code:a??null});return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'pending', next_attempt_at = ?, last_status_code = ?, last_error = ?,\n updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,a??null,o,r,t.group_id,t.body_hash),e.logger.warn(`Reply feedback queued for retry: groupId=${t.group_id} statusCode=${a??"network"} nextAttemptAt=${new Date(n).toISOString()} error=${o}`),{status:"queued",error:o}}return e.db.prepare("UPDATE reply_feedback_outbox\n SET state = 'dead', last_status_code = ?, last_error = ?, updated_at = ?,\n delete_after = ?\n WHERE group_id = ? AND body_hash = ?").run(a??null,o,r,r+e.retentionMs,t.group_id,t.body_hash),e.logger.error(`Reply feedback moved to dead state: groupId=${t.group_id} statusCode=${a??"network"} error=${o}`),{status:"failed",error:o}}async function Rm(e,t){const n=Date.now();if(n>=t.retry_expires_at)return Sm(e,t,new Error("Reply feedback outbox retention expired."),n);e.db.prepare("UPDATE reply_feedback_outbox\n SET attempt_count = attempt_count + 1, updated_at = ?\n WHERE group_id = ? AND body_hash = ?").run(n,t.group_id,t.body_hash);try{const n=await e.deliver(pm(t));if("accepted"!==n.status&&"duplicate"!==n.status)throw new Error(`Unexpected reply feedback response status: ${String(n.status)}`);if(n.groupId!==t.group_id)throw new Error(`Reply feedback response groupId mismatch: expected ${t.group_id}, got ${n.groupId}.`);const r=Date.now();return xm(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return Sm(e,mm(e.db,t.group_id)??t,n,Date.now())}}async function Cm(e){const t=Date.now();wm(e,t);const n=e.db.prepare("SELECT group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error\n FROM reply_feedback_outbox\n WHERE state = 'pending' AND next_attempt_at <= ?\n ORDER BY next_attempt_at ASC, rowid ASC\n LIMIT ?").all(t,tm);for(const t of n)await Rm(e,t)}function Im(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,vm(e,()=>Cm(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function km(e){Lu(zu(e),{recursive:!0});const t=new Ju(e);try{return t.exec(`\n PRAGMA journal_mode = WAL;\n PRAGMA synchronous = FULL;\n PRAGMA busy_timeout = 5000;\n CREATE TABLE IF NOT EXISTS reply_feedback_outbox (\n group_id TEXT PRIMARY KEY,\n body_json TEXT NOT NULL,\n body_hash TEXT NOT NULL,\n state TEXT NOT NULL CHECK (state IN (${rm})),\n attempt_count INTEGER NOT NULL DEFAULT 0,\n next_attempt_at INTEGER NOT NULL,\n retry_expires_at INTEGER NOT NULL,\n delete_after INTEGER NOT NULL,\n delivered_status TEXT CHECK (delivered_status IN ('accepted', 'duplicate')),\n last_status_code INTEGER,\n last_error TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n );\n CREATE INDEX IF NOT EXISTS reply_feedback_outbox_due_idx\n ON reply_feedback_outbox (state, next_attempt_at);\n `),t}catch(e){throw t.close(),e}}function Am(e={}){if(Ku&&!Ku.closing)return Ku.deliver=e.deliver??Ku.deliver,void(Ku.logger=e.logger??Ku.logger);if(Ku?.closing||Gu)throw new Error("Reply feedback outbox is shutting down.");const t=cm(e),n=km(t.dbPath),r=setInterval(()=>{Ku&&Im(Ku)},t.flushIntervalMs);r.unref();const a={db:n,dbPath:t.dbPath,retentionMs:t.retentionMs,flushIntervalMs:t.flushIntervalMs,retryBaseDelayMs:t.retryBaseDelayMs,authRetryDelayMs:t.authRetryDelayMs,timer:r,operationQueue:Promise.resolve(),deliver:e.deliver??Uu,logger:e.logger??am,closing:!1,backgroundFlushQueued:!1};Ku=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),Im(a)}async function Em(){if(Gu)return Gu;const e=Ku;return e?(e.closing=!0,clearInterval(e.timer),Gu=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),Ku===e&&(Ku=void 0)}})().finally(()=>{Gu=void 0})):void 0}async function _m(e,t,n,r={}){Ku||Am({deliver:t,logger:n});const a=Ku;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=Hu.parse(e),i=lm(o),s=dm(i);return vm(a,async()=>{const e=Date.now(),t=um(e,a.retentionMs,r.feedbackExpiresAt);wm(a,e);const n=a.db.prepare("INSERT INTO reply_feedback_outbox (\n group_id, body_json, body_hash, state, attempt_count, next_attempt_at,\n retry_expires_at, delete_after, delivered_status, last_status_code, last_error,\n created_at, updated_at\n ) VALUES (?, ?, ?, 'pending', 0, ?, ?, ?, NULL, NULL, NULL, ?, ?)\n ON CONFLICT(group_id) DO NOTHING").run(o.groupId,i,s,e,t,t+a.retentionMs,e,e);let c=mm(a.db,o.groupId);return c?c.body_hash!==s?{status:"failed",error:`Reply feedback group ${o.groupId} already has a different payload.`}:("pending"===c.state&&c.retry_expires_at>t&&(a.db.prepare("UPDATE reply_feedback_outbox\n SET retry_expires_at = ?, delete_after = MIN(delete_after, ?), updated_at = ?\n WHERE group_id = ? AND body_hash = ? AND state = 'pending'").run(t,t+a.retentionMs,e,c.group_id,c.body_hash),c=mm(a.db,o.groupId)??c),"delivered"===c.state?{status:"duplicate"}:"dead"===c.state?{status:"failed",error:c.last_error??"Reply feedback is permanently dead."}:0===n.changes&&c.next_attempt_at>e?{status:"queued",...c.last_error?{error:c.last_error}:{}}:Rm(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}import{defineTool as Nm}from"@roll-agent/sdk";import{z as Mm}from"zod";function Pm(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as Tm}from"zod";var Bm="reply-authority-service",Dm="browser-use-agent/zhipin_send_reply",Om="zhipin",$m=60,Fm=Tm.object({platform:Tm.literal(Om),username:Tm.string().min(1),accountId:Tm.string().min(1).optional()}),jm=Tm.object({v:Tm.literal(2),iss:Tm.literal(Bm),kid:Tm.string().min(1),jti:Tm.string().min(1),iat:Tm.number().int(),exp:Tm.number().int(),aud:Tm.literal(Dm),platform:Tm.literal(Om),tenantId:Tm.string().min(1),conversationId:Tm.string().min(1),candidateId:Tm.string().min(1),reply:Tm.string(),policyVersion:Tm.string().min(1),recruiterBinding:Fm}),Lm=Tm.object({kid:Tm.string().min(1),algorithm:Tm.literal("Ed25519"),publicKey:Tm.string().min(1),validFrom:Tm.string().min(1),validUntil:Tm.string().optional()}),qm=Tm.object({keys:Tm.array(Lm)}),zm=new Map;function Vm(e){for(const[t,n]of zm)n<e-$m&&zm.delete(t)}function Jm(e,t=Math.floor(Date.now()/1e3)){return Vm(t),zm.has(e)}function Um(e,t,n=Math.floor(Date.now()/1e3)){Vm(n),zm.set(e,t)}import{createPublicKey as Hm,verify as Wm}from"node:crypto";var Km=new Map,Gm=null;function Ym(){const e=process.env.REPLY_AUTHORITY_KEYS_URL?.trim();if(!e)throw new Error("REPLY_AUTHORITY_KEYS_URL 未配置,browser-use-agent 无法拉取 Reply Authority 公钥。");return e}function Xm(e){return Km=new Map(e.map(e=>[e.kid,e]))}async function Zm(){if(Gm)return Gm;Gm=(async()=>{const e=await fetch(Ym()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Xm(qm.parse(t).keys)})();try{return await Gm}finally{Gm=null}}async function Qm(){await Zm()}async function ep(e){const t=Km.get(e);if(t)return t;const n=(await Zm()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function tp(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function np(e){const t=e.split("."),n=t[0],r=t[1];if(2!==t.length||void 0===n||void 0===r||0===n.length||0===r.length)throw new Error("Invalid signed envelope format");let a,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{a=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}tp(a);const i=jm.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function rp(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-$m)throw new Error("Envelope expired");if(e.iat>t+$m)throw new Error("Envelope issued in the future")}async function ap(e,t=Math.floor(Date.now()/1e3)){const n=np(e),r=await ep(n.payload.kid),a=Hm({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!Wm(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return rp(n.payload,t),n.payload}var op,ip=Mm.object({success:Mm.boolean(),sentMessage:Mm.string(),error:Mm.string().optional()});function sp(){return{getReplyAuthorityKeysLoaded:pe,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...op}}function cp(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function lp(e){return"function"==typeof e.readCandidateChatDetails}async function dp(e){if(lp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function up(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function mp(e,t){return null===t||0===e.candidateName.length||cp(e.candidateName,t.candidateName)}function pp(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function gp(e,t){const n=sp();let r,a,o="";if(!n.getReplyAuthorityKeysLoaded()){const e="Reply Authority 公钥尚未成功预加载,当前无法发送签名回复。请检查启动日志、`REPLY_AUTHORITY_KEYS_URL` 配置,以及 `browser_status.replyAuthorityKeysLoaded`。";return t.logger.error(e),{success:!1,sentMessage:o,error:e}}try{const i=await ap(e.signedEnvelope);if(o=i.reply,Jm(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await new Ld(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=pp(e.unreadCountBeforeReply);if(!up(c,i)||!mp(c,s)){const t=await r.openChat({conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,sentMessage:o,error:t.error??"未找到目标聊天"};l=Math.max(l,pp(t.unreadCount)),s=await r.readActiveChatPanel(),c=await r.readSelectedChatTarget()}if(null===c)return await a.fail("未能提取当前聊天的 conversationId/candidateId"),{success:!1,sentMessage:o,error:"未能提取当前聊天的 conversationId/candidateId"};if(null!==s&&c.candidateName.length>0&&!cp(c.candidateName,s.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${c.candidateName} / ${s.candidateName}`;return await a.fail(e),{success:!1,sentMessage:o,error:e}}if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return await a.fail("发送目标与签名不匹配"),{success:!1,sentMessage:o,error:"发送目标与签名不匹配"};const d=Oo(await r.readUsernameEvidence());if(!d.found)return await a.fail("未找到用户名"),{success:!1,sentMessage:o,error:"未找到用户名,请确认当前页面已登录招聘者账号。"};const u={platform:"zhipin",username:d.username,strategy:d.strategy,source:d.source};if(!Pm(u,i.recruiterBinding)){const e=`recruiter 绑定不匹配:当前账号 ${u.username} 与签发时 ${i.recruiterBinding.username} 不一致`;return await a.fail(e),{success:!1,sentMessage:o,error:e}}t.logger.info(`Sending native message (${o.length} chars) to ${c.candidateName||c.candidateId}`);const m=await r.sendChatReply(o,{...void 0!==a?{motionObserver:a}:{}});if(!m.success)return await a.fail(m.error??"发送失败"),{success:!1,sentMessage:o,error:m.error??"发送失败"};const p=await dp(r);return Uc({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),Um(i.jti,i.exp),await a.succeed("已发送回复"),{success:!0,sentMessage:o}}catch(e){lo(e);const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,sentMessage:o,error:t}}finally{r?.close()}}var fp,hp=Nm({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Mm.object({signedEnvelope:Mm.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Mm.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Mm.number().optional().describe("候选人在列表中的索引(可选)")}),output:ip,execute:async(e,t)=>await gp(e,t)}),yp="zhipin_send_prepared_reply",bp=80,vp=Fu.object({success:Fu.boolean(),sentMessage:Fu.string(),chosenOption:Fu.enum(ld).optional(),decisionSource:Fu.enum(ud).optional(),decisionReason:Fu.string().optional(),judgeModel:Fu.string().optional(),feedbackExpected:Fu.boolean().optional(),feedbackStatus:Fu.enum(["accepted","duplicate","queued","skipped","failed"]).optional(),feedbackError:Fu.string().optional(),error:Fu.string().optional()}),wp=Fu.object({preparedReplyId:Fu.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:Je.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:Du.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:gd.optional().describe("可选的显式双稿选择;缺省时发送工具会自动执行并缓存默认 Judge。"),skipVariantJudge:Fu.boolean().optional().describe("仅用于明确的应急降级:跳过双稿 Judge、发送服务端推荐稿并回传 not_learned 终态,但不产生学习样本。")}).superRefine((e,t)=>{void 0!==e.variantDecision&&!0===e.skipVariantJudge&&t.addIssue({code:Fu.ZodIssueCode.custom,message:"variantDecision 与 skipVariantJudge 不能同时传入",path:["skipVariantJudge"]})});function xp(){return{sendSignedZhipinReply:gp,postReplyFeedback:Ou,submitReplyFeedback:_m,...fp}}function Sp(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function Rp(e,t){const n=e=>void 0===e?void 0:[...e].sort();return e.chosenOption===t.chosenOption&&e.reason===t.reason&&JSON.stringify(n(e.confirmedFindingCodes))===JSON.stringify(n(t.confirmedFindingCodes))}function Cp(e){return"decision"===e.kind?{kind:"decision",source:e.source,decision:e.decision}:{kind:"fallback",source:e.source,recommendedOption:e.recommendedOption,reason:e.reason}}async function Ip(e,t,n){const r=e.variantGroup;return void 0===r?void 0!==t.variantDecision||!0===t.skipVariantJudge?{kind:"error",error:"当前 preparedReplyId 没有双稿选项,不能提交双稿选择或跳过 Judge"}:{kind:"single"}:"not_learned"===r.state?void 0!==t.variantDecision?{kind:"error",error:"当前 preparedReplyId 已降级为非学习终态,禁止提交学习 decision"}:{kind:"terminal_fallback",source:"service_recommended_fallback",chosenVariant:r.chosenVariant,reason:r.reason}:void 0!==t.variantDecision?void 0!==e.judgement?"fallback"===e.judgement.kind?{kind:"error",error:"当前 preparedReplyId 的默认 Judge 已降级,禁止事后伪造学习 decision"}:Rp(e.judgement.decision,t.variantDecision)?Cp(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?Cp(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:Cp(await Mu(e,n))}function kp(e,t){return"judge_ready"===e.variantGroup?.state?e.variantGroup.options.find(e=>e.option===t):void 0}function Ap(e,t){if(void 0===t.confirmedFindingCodes)return!1;const n=e.variantGroup;if("judge_ready"!==n?.state)return!0;const r=new Set(n.findings.map(e=>e.code));return t.confirmedFindingCodes.some(e=>!r.has(e))}function Ep(e,t){const n=e.variantGroup;if(void 0===n)return"single"!==t.kind?{ok:!1,error:"当前 preparedReplyId 没有双稿选项"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},record:e};if("not_learned"===n.state)return"terminal_fallback"!==t.kind?{ok:!1,error:"非学习双稿 preparedReplyId 缺少降级终态"}:{ok:!0,signedEnvelope:e.signedEnvelope,suggestedReply:e.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenVariant:t.chosenVariant,decisionSource:t.source,decisionReason:t.reason,record:e};if("single"===t.kind||"terminal_fallback"===t.kind)return{ok:!1,error:"双稿 preparedReplyId 缺少选择状态"};const r="decision"===t.kind?t.decision:void 0;if(void 0!==r&&Ap(e,r))return{ok:!1,error:"variantDecision.confirmedFindingCodes 必须来自 replyVariantSelection.findings"};const a="decision"===t.kind?t.decision.chosenOption:t.recommendedOption,o=kp(e,a);if(void 0===o)return{ok:!1,error:`未找到双稿选项 ${a}`};const i="decision"===t.kind?t.decision.reason:t.reason;return{ok:!0,signedEnvelope:o.signedEnvelope,suggestedReply:o.suggestedReply,...void 0!==e.unreadCountBeforeReply?{unreadCountBeforeReply:e.unreadCountBeforeReply}:{},chosenOption:o.option,chosenVariant:o.variant,...void 0!==r?{variantDecision:r}:{},decisionSource:t.source,decisionReason:i,record:e}}function _p(e){return`sha256:${Bu("sha256").update(JSON.stringify([e.record.preparedReplyId,e.signedEnvelope,e.suggestedReply,e.record.expiresAt,e.unreadCountBeforeReply,e.record.variantGroup?.groupId,e.chosenOption,e.decisionSource,e.decisionReason,e.variantDecision?.confirmedFindingCodes,e.variantDecision?.judgeModel])).digest("hex")}`}function Np(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>bp?`${t.slice(0,bp-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function Mp(e,t,n){const r=t.record.variantGroup;if(void 0===r)return{};if(void 0===t.chosenVariant||void 0===t.decisionSource||void 0===t.decisionReason)return{feedbackStatus:"failed",feedbackError:"Dual-draft send completed without terminal feedback metadata."};try{let a;if(void 0!==t.variantDecision){if("judge"!==t.decisionSource&&"orchestrator"!==t.decisionSource)throw new Error("Learning feedback has an invalid decision source.");a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"selected",decisionSource:t.decisionSource,...void 0!==t.variantDecision.confirmedFindingCodes?{confirmedFindingCodes:t.variantDecision.confirmedFindingCodes}:{},reason:t.variantDecision.reason,rubricVersion:r.rubricVersion,rubricHash:r.rubricHash,...void 0!==t.variantDecision.judgeModel?{judgeModel:t.variantDecision.judgeModel}:{}}}else{if("service_recommended_fallback"!==t.decisionSource&&"explicit_no_judge"!==t.decisionSource)throw new Error("Non-learning feedback has an invalid decision source.");const e=t.decisionReason.trim().slice(0,500);a={groupId:r.groupId,target:r.target,chosenVariant:t.chosenVariant,feedbackOutcome:"not_learned",decisionSource:t.decisionSource,reason:e||"Dual-draft learning was intentionally skipped.",rubricVersion:r.rubricVersion,rubricHash:r.rubricHash}}const o=await e.submitReplyFeedback(a,e.postReplyFeedback,n.logger,void 0!==r.feedbackExpiresAt?{feedbackExpiresAt:r.feedbackExpiresAt}:{});return{feedbackStatus:o.status,...void 0!==o.error?{feedbackError:o.error}:{}}}catch(e){const t=e instanceof Error?e.message:String(e);return n.logger.error(`Reply feedback outbox failed after sending prepared reply: ${t}`),{feedbackStatus:"failed",feedbackError:t}}}function Pp(e){return void 0===e.decisionSource?{}:{...void 0!==e.chosenOption?{chosenOption:e.chosenOption}:{},decisionSource:e.decisionSource,...void 0!==e.decisionReason?{decisionReason:e.decisionReason}:{},...void 0!==e.variantDecision?.judgeModel?{judgeModel:e.variantDecision.judgeModel}:{},feedbackExpected:void 0!==e.variantDecision}}var Tp=$u({name:yp,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:wp,output:vp,execute:async(e,t)=>{const n=xp(),r=xd(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:Sp(r.reason)};const a=await Ip(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=Ep(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=_p(o),s=`${o.record.preparedReplyId}:${i}`,c=mt(t,{subject:{tool:yp,target:o.record.preparedReplyId,summary:Np(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});rn(t,de(),{action:yp,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=wd(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:Sp(l.reason)};const d=Ep(l.record,a);if(!d.ok)return{success:!1,sentMessage:"",error:d.error};const u=await n.sendSignedZhipinReply({signedEnvelope:d.signedEnvelope,...void 0!==d.unreadCountBeforeReply?{unreadCountBeforeReply:d.unreadCountBeforeReply}:{}},t);if(!u.success)return u;const m=await Mp(n,d,t);return{...u,...Pp(d),...m}}});import{defineTool as Bp}from"@roll-agent/sdk";import{z as Dp}from"zod";var Op,$p=Dp.object({success:Dp.boolean(),exchanged:Dp.boolean(),wechatNumber:Dp.string().optional(),error:Dp.string().optional()}),Fp=900,jp=160,Lp=1100;function qp(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Op}}function zp(e,t){const n=e.trim().toLocaleLowerCase("zh-CN"),r=t.trim().toLocaleLowerCase("zh-CN");return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Vp(e){return"function"==typeof e.readCandidateChatDetails}async function Jp(e){if(Vp(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var Up=Bp({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Dp.object({conversationId:Dp.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Dp.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Dp.number().optional().describe("候选人在列表中的索引(可选)")}),output:$p,execute:async(e,t)=>{const n=qp();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在换微信"),void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index){const t=await r.openChat({conversationId:e.conversationId,candidateName:e.candidateName,index:e.index,...void 0!==a?{motionObserver:a}:{}});if(!t.found)return await a.fail(t.error??"未找到目标聊天"),{success:!1,exchanged:!1,error:t.error??"未找到目标聊天"}}else if(!await r.isChatSurfaceOpen())return await a.fail("消息列表未加载"),{success:!1,exchanged:!1,error:"消息列表未加载"};const o=await r.readSelectedChatTarget();if(null===o)return await a.fail("未选中聊天联系人"),{success:!1,exchanged:!1,error:"未选中聊天联系人,无法点击当前聊天输入区的「换微信」按钮"};const i=await r.readActiveChatPanel();if(null!==i&&o.candidateName.length>0&&!zp(o.candidateName,i.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${o.candidateName} / ${i.candidateName}`;return await a.fail(e),{success:!1,exchanged:!1,error:e}}t.logger.info(`Starting native WeChat exchange with ${o.candidateName}`);const s=await Jp(r),c=await r.exchangeWechat({preClickDelayMs:Fp,pressDurationMs:jp,settleMs:Lp,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&Wc({conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.candidateName,...void 0!==c.wechatNumber?{wechatNumber:c.wechatNumber}:{},...void 0!==s?{candidateDetails:s}:{}},t.logger),await a.succeed("已完成换微信"),c):(await a.fail(c.error??"换微信失败"),c)}catch(e){lo(e);const t=e instanceof Error?e.message:String(e);return await(a?.fail(t)),{success:!1,exchanged:!1,error:t}}finally{r?.close()}}});import{defineTool as Hp}from"@roll-agent/sdk";import{z as Wp}from"zod";var Kp,Gp=Wp.object({success:Wp.boolean(),username:Wp.string(),usedSelector:Wp.string().optional(),usedStrategy:Wp.string().optional(),source:Wp.string().optional(),error:Wp.string().optional()});function Yp(){return{openNativePagePort:Gi,pickBestUsername:Oo,createNativeVisualActivitySession:e=>new aa(e),...Kp}}var Xp=Hp({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:Wp.object({}),output:Gp,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=Yp();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在识别登录账号"),await a.highlightSelector('header, #header, [role="banner"], [role="navigation"]',{label:"正在识别登录账号",padding:10});const e=n.pickBestUsername(await r.readUsernameEvidence());if(!e.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return await a.succeed(`已识别账号:${e.username}`),t.logger.info(`Username: ${e.username} (strategy: ${e.strategy}, source: ${e.source})`),{success:!0,username:e.username,usedSelector:"css-fallback"===e.strategy?e.source:void 0,usedStrategy:e.strategy,source:e.source}}catch(e){return lo(e),await(a?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}finally{r?.close()}}});import{defineTool as Zp}from"@roll-agent/sdk";import{z as Qp}from"zod";var eg,tg=["unknown","top","middle","bottom","only-page"],ng=Qp.object({containerFound:Qp.boolean(),containerLabel:Qp.string(),scrollTop:Qp.number(),scrollHeight:Qp.number(),clientHeight:Qp.number(),itemCount:Qp.number(),atStart:Qp.boolean(),atEnd:Qp.boolean()}),rg=Qp.object({success:Qp.boolean(),surface:Qp.enum(Ha),direction:Qp.enum(["up","down"]),stepsRequested:Qp.number(),stepsCompleted:Qp.number(),reachedBoundary:Qp.boolean(),atTop:Qp.boolean(),atBottom:Qp.boolean(),canScrollUp:Qp.boolean(),canScrollDown:Qp.boolean(),position:Qp.enum(tg),before:ng,after:ng,error:Qp.string().optional()});function ag(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...eg}}function og(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function ig(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function sg(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:ig(e)}}var cg=Zp({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:Qp.object({surface:Qp.enum(Ha).describe("要滚动的页面区域"),direction:Qp.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:Qp.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:Qp.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:Qp.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:rg,execute:async(e,t)=>{const n=ag(),r=Ka(e.surface),a=e.direction??r.defaultDirection,o=e.steps??1,i=e.settleMs??700;let s,c;try{s=await n.openNativePagePort({requireChatPage:"chat-list"===e.surface||"chat-history"===e.surface}),c=n.createNativeVisualActivitySession(s);const l=`正在滚动 ${e.surface}`;await c.begin(l),await c.highlightSelector(r.highlightSelector,{label:l,padding:8});const d=await s.scrollSurface(e.surface,{direction:a,steps:o,settleMs:i,...void 0!==e.distance?{distance:e.distance}:{}});return d.success?(await c.succeed(`已滚动 ${d.stepsCompleted}/${d.stepsRequested} 步`),t.logger.info(`Scrolled ${e.surface}: ${d.stepsCompleted}/${d.stepsRequested}, items ${d.before.itemCount} -> ${d.after.itemCount}`),{success:d.success,surface:e.surface,direction:d.direction,stepsRequested:d.stepsRequested,stepsCompleted:d.stepsCompleted,reachedBoundary:d.reachedBoundary,...sg(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...sg(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){lo(t),await(c?.fail("滚动失败"));const n=og();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...sg(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as lg}from"@roll-agent/sdk";import{z as dg}from"zod";var ug,mg=dg.object({applyMode:dg.enum(yo),ageMin:dg.number().int().min(16).optional(),ageMax:dg.number().int().min(16).optional(),location:dg.object({city:dg.string(),district:dg.string().optional()}).optional(),optionSelections:dg.array(dg.object({fieldKey:dg.string(),label:dg.string(),values:dg.array(dg.string()),selection:dg.enum(["single","multi"]),clearValue:dg.string()}))}),pg=dg.object({ageMin:dg.number().optional(),ageMax:dg.number().optional(),location:dg.object({city:dg.string(),district:dg.string().optional()}).optional(),optionSelections:dg.array(dg.object({fieldKey:dg.string(),label:dg.string(),values:dg.array(dg.string())})),gender:dg.string().optional(),activity:dg.string().optional()}),gg=dg.object({success:dg.boolean(),status:dg.enum(vo),requested:mg,applied:pg.optional(),filterButtonText:dg.string().optional(),error:dg.string().optional()}),fg=dg.string().min(1),hg=dg.array(fg).min(1),yg=dg.object({applyMode:dg.enum(yo).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:dg.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:dg.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:fg.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:fg.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:dg.enum(fo).optional().describe("性别筛选:不限、男、女"),activity:dg.enum(ho).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:hg.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:fg.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:fg.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:hg.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:hg.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:fg.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:hg.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:fg.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:hg.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:hg.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:fg.optional().describe("是否可拨打电话筛选,如:不限、可拨打")}).refine(e=>void 0===e.ageMin||void 0===e.ageMax||e.ageMin<=e.ageMax,{path:["ageMax"],message:"ageMax must be greater than or equal to ageMin"}).refine(e=>void 0===e.locationDistrict||void 0!==e.locationCity,{path:["locationCity"],message:"locationCity is required when locationDistrict is provided"}),bg=350,vg=130,wg=600;function xg(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...ug}}function Sg(e,t){const n=wo(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function Rg(e){return void 0!==e.locationCity||void 0!==e.locationDistrict||void 0!==e.major||void 0!==e.recentNotView||void 0!==e.exchangeResumeWithColleague||void 0!==e.candidateKeywords||void 0!==e.school||void 0!==e.switchJobFrequency||void 0!==e.intention||void 0!==e.salary||void 0!==e.degree||void 0!==e.experience||void 0!==e.callPhone}function Cg(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function Ig(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(Sg(t,r))}function kg(e){const t=[];if(void 0===e.applyMode&&!Rg(e)&&Cg(e)||!Rg(e)&&!Cg(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(Sg("gender",[n])),t.push(Sg("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}Ig(t,"gender",e.gender),Ig(t,"activity",e.activity),Ig(t,"major",e.major),Ig(t,"recentNotView",e.recentNotView),Ig(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),Ig(t,"candidateKeywords",e.candidateKeywords),Ig(t,"school",e.school),Ig(t,"switchJobFrequency",e.switchJobFrequency),Ig(t,"intention",e.intention),Ig(t,"salary",e.salary),Ig(t,"degree",e.degree),Ig(t,"experience",e.experience),Ig(t,"callPhone",e.callPhone);const n=e.applyMode??"patch";return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.locationCity?{location:{city:e.locationCity,...void 0!==e.locationDistrict?{district:e.locationDistrict}:{}}}:{},applyMode:n,optionSelections:t}}function Ag(e){return{applyMode:e.applyMode,...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values],selection:e.selection,clearValue:e.clearValue}))}}function Eg(e){if(void 0!==e)return{...void 0!==e.ageMin?{ageMin:e.ageMin}:{},...void 0!==e.ageMax?{ageMax:e.ageMax}:{},...void 0!==e.location?{location:{city:e.location.city,...void 0!==e.location.district?{district:e.location.district}:{}}}:{},optionSelections:e.optionSelections.map(e=>({fieldKey:e.fieldKey,label:e.label,values:[...e.values]})),...void 0!==e.gender?{gender:e.gender}:{},...void 0!==e.activity?{activity:e.activity}:{}}}function _g(e){const t=Eg(e.applied);return{success:"applied"===e.status,status:e.status,requested:Ag(e.requested),...void 0!==t?{applied:t}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var Ng=lg({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:yg,output:gg,execute:async(e,t)=>{const n=xg(),r=kg(e);let a,o;t.logger.info(`Filtering Boss recommend candidates through native CDP: applyMode=${r.applyMode}, ageMin=${r.ageMin??"unchanged"}, ageMax=${r.ageMax??"unchanged"}, location=${void 0!==r.location?`${r.location.city}${void 0!==r.location.district?`-${r.location.district}`:""}`:"unchanged"}, fields=${r.optionSelections.map(e=>e.fieldKey).join(",")}`);try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),_g({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(go.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:bg,pressDurationMs:vg,settleMs:wg,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),_g(e)}catch(e){lo(e);const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),_g({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as Mg}from"@roll-agent/sdk";import{z as Pg}from"zod";var Tg=["target-count","boundary","no-new-items","max-steps"];import{AsyncLocalStorage as Bg}from"node:async_hooks";var Dg="@c",Og="@j",$g="legacy",Fg=/^@?c([1-9]\d*)$/i,jg=/^@?j([1-9]\d*)$/i,Lg=new Map,qg=new Map,zg=new Bg;function Vg(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${Dg}${String(e+1)}`}function Jg(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${Og}${String(e+1)}`}function Ug(e){const t=Fg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Hg(e){const t=jg.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function Wg(e){if(void 0!==e.index){if(!Number.isInteger(e.index)||e.index<0)throw new Error("index 必须是从 0 开始的非负整数");return e.index}if(void 0===e.candidateRef)throw new Error("必须提供 index 或 candidateRef");return Yg(e.candidateRef).index}function Kg(e){const t=e.map((e,t)=>({...e,candidateRef:Vg(t)}));return Lg.set(nf(),new Map(t.map(e=>[e.candidateRef,e]))),t}function Gg(e){const t=e.map((e,t)=>({...e,jobRef:Jg(t)}));return qg.set(nf(),new Map(t.map(e=>[e.jobRef,e]))),t}function Yg(e){const t=Ug(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=Vg(t);return Lg.get(nf())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function Xg(e){const t=Hg(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=Jg(t),r=qg.get(nf())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function Zg(e){const t=[];for(const n of e.indices??[])t.push({index:Wg({index:n}),candidateRef:Vg(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(Yg(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return ef(t)}function Qg(e,t){return!1!==t.found&&(e.candidateId.length>0&&t.candidateId.length>0?e.candidateId===t.candidateId:!(e.name&&t.name&&t.name.length>0)||tf(e.name)===tf(t.name))}function ef(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function tf(e){return e.trim().toLocaleLowerCase("zh-CN")}function nf(){const e=zg.getStore();if(void 0!==e)return e;try{return be().id}catch{return $g}}var rf,af=Pg.object({index:Pg.number(),candidateRef:Pg.string(),candidateId:Pg.string(),name:Pg.string(),age:Pg.string(),experience:Pg.string(),education:Pg.string(),workStatus:Pg.string(),company:Pg.string(),currentPosition:Pg.string(),expectedLocation:Pg.string(),expectedPosition:Pg.string(),expectedSalary:Pg.string(),tags:Pg.array(Pg.string()),buttonText:Pg.string()}),of=Pg.object({containerLabel:Pg.string(),stepsRequested:Pg.number(),stepsCompleted:Pg.number(),reachedBoundary:Pg.boolean(),stopReason:Pg.enum(Tg),uniqueCount:Pg.number(),duplicateCount:Pg.number(),noNewRounds:Pg.number(),beforeItemCount:Pg.number(),afterItemCount:Pg.number(),beforeScrollHeight:Pg.number(),afterScrollHeight:Pg.number()}),sf=Pg.object({success:Pg.boolean(),candidates:Pg.array(af),total:Pg.number(),scrollStats:of.optional(),error:Pg.string().optional()});function cf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...rf}}var lf=Mg({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:Pg.object({maxResults:Pg.number().optional().describe("最多返回条数"),autoScroll:Pg.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:Pg.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:sf,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=cf();let r,a;try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await a.begin("正在打开推荐列表");if(!await r.waitForRecommendList())return await a.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};const o="正在读取推荐列表";await a.begin(o),await a.highlightSelector(".candidate-card-wrap, li.card-item, [data-geek], .geek-item",{label:o,padding:8});const i=await r.readRecommendCandidates({autoScroll:e.autoScroll??!0,maxScrolls:e.maxScrolls??4,...void 0!==e.maxResults?{targetCount:e.maxResults}:{}}),s=void 0!==e.maxResults?i.items.slice(0,e.maxResults):[...i.items],c=Kg(s),l=s.map((e,t)=>{const n=c[t];if(!n)throw new Error(`候选人引用生成失败:position ${String(t)}`);return{...e,candidateRef:n.candidateRef}}),d=!1===e.autoScroll?void 0:{containerLabel:i.after.containerLabel,stepsRequested:i.stepsRequested,stepsCompleted:i.stepsCompleted,reachedBoundary:i.reachedBoundary,stopReason:i.stopReason,uniqueCount:i.uniqueCount,duplicateCount:i.duplicateCount,noNewRounds:i.noNewRounds,beforeItemCount:i.before.itemCount,afterItemCount:i.after.itemCount,beforeScrollHeight:i.before.scrollHeight,afterScrollHeight:i.after.scrollHeight};return await a.succeed(`已读取 ${l.length} 位候选人`),t.logger.info(`Found ${l.length} candidates in recommend list`+(d?`, scroll stop: ${d.stopReason}`:"")),{success:!0,candidates:l,total:l.length,...void 0!==d?{scrollStats:d}:{}}}catch(e){return lo(e),await(a?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{r?.close()}}});import{defineTool as df}from"@roll-agent/sdk";import{z as uf}from"zod";var mf,pf=["listed","recommend_not_ready","selector_not_found"],gf=uf.object({index:uf.number(),jobRef:uf.string().optional(),value:uf.string(),label:uf.string(),isCurrent:uf.boolean()}),ff=uf.object({success:uf.boolean(),status:uf.enum(pf),current:gf.optional(),jobs:uf.array(gf),availableCount:uf.number(),canSwitch:uf.boolean(),error:uf.string().optional()}),hf=450,yf=140,bf=750;function vf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...mf}}function wf(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function xf(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Sf(e){const t=Gg(e.options),n=e.options.map((e,n)=>wf(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>xf(e,t))??wf(t)}return{success:e.success,status:e.status,...void 0!==r?{current:r}:{},jobs:n,availableCount:e.availableCount,canSwitch:e.canSwitch,...void 0!==e.error?{error:e.error}:{}}}var Rf=df({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:uf.object({}),output:ff,execute:async(e,t)=>{const n=vf();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:hf,pressDurationMs:yf,settleMs:bf,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),Sf(e)}catch(e){lo(e);const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),Sf({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as Cf}from"@roll-agent/browser";import{defineTool as If}from"@roll-agent/sdk";import{z as kf}from"zod";var Af,Ef=kf.object({success:kf.boolean(),alreadyOnRecommend:kf.boolean(),usedSidebarClick:kf.boolean(),recommendReady:kf.boolean(),page:Cf.optional(),error:kf.string().optional()});function _f(){return{getContextManager:ue,openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Af}}async function Nf(e,t){return Dt(e,await t.inspectPage())}var Mf=If({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:kf.object({}),output:Ef,execute:async(e,t)=>{const n=_f(),r=n.getContextManager();let a,o;t.logger.info("Opening Boss recommend page via native sidebar navigation");try{a=await n.openNativePagePort(),o=n.createNativeVisualActivitySession(a),await cn(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(go.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await Nf(r,a)};if(!await a.clickSidebarSection("recommend",{...void 0!==o?{motionObserver:o}:{}}))return await o.fail("未找到推荐牛人导航"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,page:await Nf(r,a),error:"未找到推荐牛人导航"};t.logger.info("Clicked Boss sidebar nav: 推荐牛人");return await a.waitForRecommendSurface()?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await Nf(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await Nf(r,a),error:"推荐牛人页未就绪"})}catch(e){return lo(e),await(o?.fail("切换推荐牛人页失败")),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,error:e instanceof Error?e.message:"切换推荐牛人页失败"}}finally{a?.close()}}});import{defineTool as Pf}from"@roll-agent/sdk";import{z as Tf}from"zod";var Bf,Df=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],Of=Tf.object({index:Tf.number(),value:Tf.string(),label:Tf.string(),isCurrent:Tf.boolean()}),$f=Tf.object({jobRef:Tf.string().optional(),jobValue:Tf.string().optional(),jobName:Tf.string().optional(),index:Tf.number().optional(),searchKeyword:Tf.string().optional(),useSearch:Tf.boolean().optional(),forceClick:Tf.boolean().optional()}),Ff=Tf.object({success:Tf.boolean(),status:Tf.enum(Df),requested:$f,current:Of.optional(),selected:Of.optional(),options:Tf.array(Of),matchedCount:Tf.number(),error:Tf.string().optional()}),jf=Tf.object({jobRef:Tf.string().regex(jg,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:Tf.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:Tf.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:Tf.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:Tf.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:Tf.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:Tf.boolean().default(!1).describe("目标岗位已选中时是否仍点击一次岗位项;默认 false,避免无意义重复点击")}).refine(e=>void 0!==e.jobRef||void 0!==e.jobValue||void 0!==e.jobName||void 0!==e.index,{path:["jobRef"],message:"jobRef、jobValue、jobName、index 至少需要提供一个"}),Lf=900,qf=180,zf=1400;function Vf(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),...Bf}}function Jf(e){if(void 0!==e.jobRef){const t=Xg(e.jobRef);return{jobRef:t.jobRef,...t.value.length>0?{jobValue:t.value}:{},...0===t.value.length&&t.label.length>0?{jobName:t.label}:{},index:t.index,...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}return{...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Uf(e){return{...void 0!==e.jobRef?{jobRef:e.jobRef}:{},...void 0!==e.jobValue?{jobValue:e.jobValue}:{},...void 0!==e.jobName?{jobName:e.jobName}:{},...void 0!==e.index?{index:e.index}:{},...void 0!==e.searchKeyword?{searchKeyword:e.searchKeyword}:{},useSearch:e.useSearch??!0,forceClick:e.forceClick??!1}}function Hf(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function Wf(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:Hf(e.current)}:{},...void 0!==e.selected?{selected:Hf(e.selected)}:{},options:e.options.map(Hf),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var Kf=Pf({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:jf,output:Ff,execute:async(e,t)=>{const n=Vf();let r,a;try{const o=Jf(e);t.logger.info(`Selecting Boss recommend job: ref=${o.jobRef??"N/A"}, value=${o.jobValue??"N/A"}, name=${o.jobName??"N/A"}, index=${o.index??"N/A"}, forceClick=${!0===o.forceClick}`),r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await cn(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:Lf,pressDurationMs:qf,settleMs:zf,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),Wf(i)}catch(t){lo(t);const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),Wf({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:Uf(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as Gf}from"@roll-agent/sdk";import{z as Yf}from"zod";var Xf,Zf=Yf.object({index:Yf.number(),candidateRef:Yf.string(),candidateName:Yf.string(),candidateId:Yf.string(),success:Yf.boolean(),error:Yf.string().optional()}),Qf=Yf.object({success:Yf.boolean(),results:Yf.array(Zf),summary:Yf.object({total:Yf.number(),succeeded:Yf.number(),failed:Yf.number()})}),eh=[1400,1800,2200],th=450,nh=140,rh=750,ah=Yf.object({indices:Yf.array(Yf.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:Yf.array(Yf.string().regex(Fg,"candidateRef 应类似 @c1")).min(1).optional().describe("要打招呼的候选人语义引用列表,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>(e.indices?.length??0)>0||(e.candidateRefs?.length??0)>0,"必须提供 indices 或 candidateRefs");function oh(){return{openNativePagePort:Gi,createNativeVisualActivitySession:e=>new aa(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...Xf}}var ih=Gf({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:ah,output:Qf,execute:async(e,t)=>{const n=Zg(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=oh();let a,o;try{a=await r.openNativePagePort(),o=r.createNativeVisualActivitySession(a),await o.begin("正在打开推荐列表");if(!await a.waitForRecommendList()){await o.fail("推荐列表未加载");const e=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:e,summary:{total:e.length,succeeded:0,failed:e.length}}}const e=n.length>1?"正在批量打招呼":"正在打招呼";await o.begin(e),await o.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:e,padding:8});const i=[];for(const[e,s]of n.entries()){if(e>0){const t=eh[(e-1)%eh.length]??eh[0];await r.sleep(t)}if(sh(s)){const e=await a.inspectRecommendCard(s.index);if(!Qg(s,e)){await a.assertNotRestricted(),i.push({index:s.index,candidateRef:s.candidateRef,candidateName:e.name,candidateId:e.candidateId,success:!1,error:`候选人引用 ${s.candidateRef} 已过期,请重新获取推荐列表`});continue}}const n=await a.clickRecommendGreet(s.index,{preClickDelayMs:th,pressDurationMs:nh,settleMs:rh,...void 0!==o?{motionObserver:o}:{}});i.push({index:s.index,candidateRef:s.candidateRef,candidateName:n.name,candidateId:n.candidateId,success:n.clicked,...void 0!==n.error?{error:n.error}:{}}),Hc(n,t.logger),n.clicked||await a.assertNotRestricted()}const s={total:i.length,succeeded:i.filter(e=>e.success).length,failed:i.filter(e=>!e.success).length};return 0===s.failed?await o.succeed(`已完成 ${s.succeeded}/${s.total} 位候选人`):await o.fail(`已完成 ${s.succeeded}/${s.total} 位候选人`),{success:0===s.failed,results:i,summary:s}}catch(e){lo(e);const t=e instanceof Error?e.message:String(e);await(o?.fail(t));const r=n.map(e=>({index:e.index,candidateRef:e.candidateRef,candidateName:"",candidateId:"",success:!1,error:t}));return{success:!1,results:r,summary:{total:r.length,succeeded:0,failed:r.length}}}finally{a?.close()}}});function sh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as ch}from"@roll-agent/sdk";import{z as lh}from"zod";var dh,uh=lh.object({success:lh.boolean(),candidateRef:lh.string().optional(),candidateName:lh.string(),candidateId:lh.string(),resumeReady:lh.boolean().optional(),error:lh.string().optional()}),mh=lh.object({index:lh.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:lh.string().regex(Fg,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef");function ph(){return{openNativePagePort:Gi,...dh}}var gh=ch({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:mh,output:uh,execute:async(e,t)=>{const n=hh(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${String(r)} through native backend`);const o=ph();let i,s;try{i=await o.openNativePagePort(),s=new aa(i),await s.begin("正在打开简历详情");if(!await i.waitForRecommendList())return await s.fail("推荐列表未加载"),fh(a,"推荐列表未加载");const e=await i.inspectRecommendCard(r);if(!e.found){const t=e.error??`候选人引用 ${a} 超出范围`;return await s.fail(t),fh(a,t)}if(yh(n)&&!Qg(n,e)){const t=`候选人引用 ${a} 已过期,请重新获取推荐列表`;return await s.fail(t),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:t}}if(!await i.clickRecommendCardSurface(r))return await s.fail("未能点击候选人卡片"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:"未能点击候选人卡片"};const c=await i.waitForResumeDialog(12e3),l=c.canvasReady;return c.iframeFound?(await s.succeed(`已打开 ${e.name} 的简历`),t.logger.info(`Opened resume for ${e.name}`),{success:!0,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:l,...l?{}:{error:"简历弹窗已出现,但 canvas 尚未就绪"}}):(await s.fail("简历弹窗未出现"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:!1,error:"点击后简历弹窗未出现"})}catch(e){return lo(e),await(s?.fail("打开简历失败")),t.logger.warn(`Native zhipin open resume failed: ${e instanceof Error?e.message:String(e)}`),fh(a,"打开简历失败,请重试")}finally{i?.close()}}});function fh(e,t){return{success:!1,...void 0!==e?{candidateRef:e}:{},candidateName:"",candidateId:"",error:t}}function hh(e){if(void 0!==e.candidateRef)return Yg(e.candidateRef);const t=Wg(e);return{index:t,candidateRef:Vg(t),candidateId:""}}function yh(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{mkdir as bh,writeFile as vh}from"node:fs/promises";import{tmpdir as wh}from"node:os";import{dirname as xh,isAbsolute as Sh,join as Rh}from"node:path";import{defineTool as Ch}from"@roll-agent/sdk";import{z as Ih}from"zod";var kh,Ah="data:image/png;base64,",Eh=Ih.object({success:Ih.boolean(),imagePath:Ih.string().optional(),captureMode:Ih.enum(["canvas-data-url","viewport-clip","dom-screenshot"]).optional(),canvasSize:Ih.object({width:Ih.number(),height:Ih.number()}).optional(),note:Ih.string().optional(),error:Ih.string().optional(),mcpImages:Ih.array(Ih.object({data:Ih.string(),mimeType:Ih.string()})).optional().describe("截图的 MCP image content(base64);由 SDK 转为 image block 返回,调用方无需解析")});function _h(){return{openNativePagePort:Gi,writePngFile:async(e,t)=>{await bh(xh(e),{recursive:!0}),await vh(e,Buffer.from(t,"base64"))},now:()=>Date.now(),...kh}}var Nh=Ch({name:"zhipin_capture_resume",description:"截取当前打开的简历详情弹窗中的 canvas 简历为 PNG 图片文件并返回 imagePath。简历内容是 canvas 图像渲染,无法从 DOM/文本读取;编排器应在调用后用自身的多模态图像能力读取该文件来理解简历内容。需先用 zhipin_open_resume 打开简历弹窗",input:Ih.object({outputPath:Ih.string().min(1).refine(e=>Sh(e),{message:"outputPath 必须是绝对路径"}).optional().describe("PNG 输出文件的绝对路径;缺省时写入系统临时目录")}),output:Eh,execute:async(e,t)=>{t.logger.info("Capturing resume canvas through native backend");const n=_h();let r,a;try{r=await n.openNativePagePort(),a=new aa(r),await a.begin("正在读取简历");const o=await r.waitForResumeDialog(12e3);if(!o.iframeFound)return await a.fail("简历弹窗未打开"),{success:!1,error:"简历弹窗未打开,请先调用 zhipin_open_resume"};if(!o.canvasReady)return await a.fail("简历内容未加载完成"),{success:!1,error:"简历 canvas 未加载完成,请稍后重试"};const i=await r.captureResumeCanvas(async e=>{await(a?.begin(Ph(e)))});if(!i.found||void 0===i.canvasSize)return await a.fail("未找到简历内容"),{success:!1,error:i.error??"未找到简历 canvas"};let s,c,l;if(await r.assertNotRestricted(),!0!==i.blank&&void 0!==i.dataUrl&&i.dataUrl.startsWith(Ah))s=i.dataUrl.slice(Ah.length),c="canvas-data-url";else if(!0===i.blank){const e=await r.readResumeDialogClipArea();if(void 0===e)return await a.fail("未找到简历弹窗区域"),{success:!1,error:"简历 canvas 为空白且未找到弹窗区域,无法截图"};await Th(a),s=await r.captureViewportClip(e,2),c="dom-screenshot",l="此简历为 DOM 文本渲染(canvas 为空),已改为截取弹窗可见区域;若弹窗内有滚动内容可能未完整覆盖"}else{if(void 0===i.screenshotArea)return await a.fail("简历坐标不完整"),{success:!1,error:"简历 canvas 坐标不完整,无法截图"};await Th(a),s=await r.captureViewportClip(i.screenshotArea),c="viewport-clip",l=`canvas.toDataURL 不可用${void 0!==i.dataUrlError?`(${i.dataUrlError})`:""},已回退为视口区域截图,超出可视区的简历内容可能被截断`}const d=e.outputPath??Rh(wh(),"roll-browser-use",`resume-${String(n.now())}.png`);return await n.writePngFile(d,s),await a.succeed("简历读取完成"),t.logger.info(`Resume captured to ${d} (${c})`),{success:!0,imagePath:d,captureMode:c,canvasSize:i.canvasSize,...void 0!==l?{note:l}:{},mcpImages:[{data:s,mimeType:"image/png"}]}}catch(e){return lo(e),await(a?.fail("简历读取失败")),t.logger.warn(`Native zhipin capture resume failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,error:"简历读取失败,请重试"}}finally{r?.close()}}}),Mh=240;function Ph(e){if(e.totalPx>0){const t=Math.min(99,Math.round(e.scrolledPx/e.totalPx*100));return t>0?`正在读取简历 ${String(t)}%`:"正在读取简历"}return`正在读取简历 · 第 ${String(e.round+1)} 屏`}async function Th(e){await e.clear()&&await new Promise(e=>setTimeout(e,Mh))}import{defineTool as Bh}from"@roll-agent/sdk";import{z as Dh}from"zod";var Oh,$h=Dh.object({success:Dh.boolean(),closed:Dh.boolean(),method:Dh.enum(["close-button","escape"]).optional(),error:Dh.string().optional()});function Fh(){return{openNativePagePort:Gi,...Oh}}var jh=Bh({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:Dh.object({}),output:$h,execute:async(e,t)=>{t.logger.info("Closing resume detail modal through native backend");const n=Fh();let r;try{r=await n.openNativePagePort();const e=await r.closeResumeDialog();return e.closed?(t.logger.info(`Resume modal closed via ${e.method??"unknown"}`),{success:!0,closed:!0,...void 0!==e.method?{method:e.method}:{}}):{success:!1,closed:!1,error:e.error??"简历弹窗未关闭"}}finally{r?.close()}}});import{defineTool as Lh}from"@roll-agent/sdk";import{z as qh}from"zod";import{waitForSelector as zh}from"@roll-agent/browser";var Vh={login:{qrCode:".login-qr img, .qr-code img",loginSuccess:".user-info, .header-user"},messageList:{container:".chat-list, .msg-list",item:".chat-item, .msg-item",candidateName:".chat-item .name, .msg-item .name",lastMessage:".chat-item .msg, .msg-item .content",unreadBadge:".chat-item .unread, .msg-item .badge",timestamp:".chat-item .time, .msg-item .time"},chat:{input:".chat-input textarea, .msg-input textarea",sendButton:".btn-send, .send-btn",messageItem:".chat-msg, .msg-bubble",messageText:".chat-msg .text, .msg-bubble .text"}};import{navigateTo as Jh,waitForSelector as Uh}from"@roll-agent/browser";var Hh="https://www.yupao.com",Wh=`${Hh}/chat`,Kh=`${Hh}/login`;async function Gh(e,t={}){e.url().includes("/chat")||await Jh(e,Wh,t),await Uh(e,Vh.messageList.container,{timeout:15e3})}async function Yh(e,t,n={}){const r=`${Hh}/chat?id=${encodeURIComponent(t)}`;await Jh(e,r,n),await Uh(e,Vh.chat.input,{timeout:15e3})}async function Xh(e,t){await Gh(e),await zh(e,Vh.messageList.item,{timeout:1e4});const n=Vh.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],r=t.maxItems?e.slice(0,t.maxItems):e;for(const e of r){const r=e.querySelector(t.sel.candidateName),a=e.querySelector(t.sel.lastMessage),o=e.querySelector(t.sel.unreadBadge),i=e.querySelector(t.sel.timestamp),s=e.getAttribute("data-id")??e.getAttribute("data-conversation-id")??e.querySelector("a")?.getAttribute("href")?.match(/id=([^&]+)/)?.[1]??"";n.push({conversationId:s,candidateName:r?.textContent?.trim()??"",lastMessage:a?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var Zh=qh.object({limit:qh.number().optional().describe("最多返回的消息条数")}),Qh=qh.object({conversationId:qh.string(),candidateName:qh.string(),lastMessage:qh.string(),unreadCount:qh.number(),timestamp:qh.string()}),ey=qh.object({messages:qh.array(Qh),total:qh.number()}),ty=Lh({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:Zh,output:ey,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=ue(),r=await n.getPage("yupao"),a=await Xh(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});import{defineTool as ny}from"@roll-agent/sdk";import{z as ry}from"zod";import{BrowserActionApprovalSchema as ay}from"@roll-agent/browser";import{clickElement as oy,isBrowserActionPolicyError as iy,typeText as sy,waitForSelector as cy}from"@roll-agent/browser";async function ly(e,t,n,r={}){try{return await Yh(e,t,r),await sy(e,Vh.chat.input,n,r),await oy(e,Vh.chat.sendButton,r),await cy(e,Vh.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(iy(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var dy=ry.object({conversationId:ry.string().describe("对话 ID"),message:ry.string().describe("要发送的回复消息"),browserActionApproval:ay.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),uy=ry.object({success:ry.boolean(),conversationId:ry.string(),sentMessage:ry.string(),error:ry.string().optional()}),my=ny({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:dy,output:uy,execute:async(e,t)=>{const{conversationId:n,message:r}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const a=ue(),o=de(),i=rn(t,o,{action:"yupao_send_reply",target:n,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await a.getPage("yupao");let c;try{c=await ly(s,n,r,nn(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=an(e);if(void 0!==t)throw t;throw e}return c.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${c.error}`),{success:c.success,conversationId:n,sentMessage:r,error:c.error}}});import{BrowserRuntimeConfigSchema as py,BrowserRuntimeModeSchema as gy,BrowserChannelSchema as fy,BrowserProfileColorSchema as hy,BrowserWindowBoundsSchema as yy,PlatformSchema as by}from"@roll-agent/browser";import{z as vy}from"zod";var wy=vy.object({platform:by.optional(),mode:gy.default("managed-cdp"),headless:vy.boolean().optional(),cdpUrl:vy.string().optional(),cdpHost:vy.string().default("127.0.0.1"),cdpPort:vy.number().int().min(1).max(65535).optional(),channel:fy.default("chrome"),executablePath:vy.string().optional(),userDataDir:vy.string().trim().min(1),sessionsDir:vy.string().trim().min(1).optional(),args:vy.array(vy.string()).optional(),profileName:vy.string().trim().min(1).optional(),profileColor:hy.optional(),windowBounds:yy.optional(),trackingAgentId:vy.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:vy.ZodIssueCode.custom,path:["cdpPort"],message:"managed-cdp browser instance requires cdpPort"}),"remote-cdp"!==e.mode&&"existing-session"!==e.mode||void 0!==e.cdpUrl||t.addIssue({code:vy.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),xy=vy.object({defaultInstance:vy.string().trim().min(1).optional(),instances:vy.record(vy.string(),wy).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:vy.ZodIssueCode.custom,path:["defaultInstance"],message:`defaultInstance "${e.defaultInstance}" is not declared in browser.instances`});const n=new Map,r=new Map;for(const[a,o]of Object.entries(e.instances)){if(void 0!==o.cdpPort){const e=n.get(o.cdpPort);void 0!==e?t.addIssue({code:vy.ZodIssueCode.custom,path:["instances",a,"cdpPort"],message:`cdpPort ${String(o.cdpPort)} is already used by browser instance "${e}"`}):n.set(o.cdpPort,a)}const e=r.get(o.userDataDir);void 0!==e?t.addIssue({code:vy.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function Sy(e){if(void 0!==e){if("true"===e)return!0;if("false"===e)return!1;throw new Error(`Expected boolean env value "true" or "false", received "${e}".`)}}function Ry(e,t){if(void 0===e)return;const n=Number.parseInt(e,10);if(!Number.isInteger(n))throw new Error(`${t} must be an integer, received "${e}".`);return n}function Cy(e){if(void 0===e)return;const t=JSON.parse(e);if(!Array.isArray(t)||!t.every(e=>"string"==typeof e))throw new Error("BROWSER_ARGS_JSON must be a JSON string array.");return t}function Iy(e){if(void 0!==e)try{return JSON.parse(e)}catch(e){throw new Error(`BROWSER_SECURITY_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}}function ky(e){if(void 0===e)return;let t;try{t=JSON.parse(e)}catch(e){throw new Error(`BROWSER_INSTANCES_JSON must be valid JSON: ${e instanceof Error?e.message:String(e)}`)}return xy.parse(t)}function Ay(e=process.env){return py.parse({mode:e.BROWSER_MODE,headless:Sy(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:Ry(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:Cy(e.BROWSER_ARGS_JSON),profileColor:e.BROWSER_PROFILE_COLOR,sessionsDir:e.BROWSER_SESSIONS_DIR,security:Iy(e.BROWSER_SECURITY_JSON)})}function Ey(e=process.env){return ky(e.BROWSER_INSTANCES_JSON)}import{z as _y}from"zod";import{StructuredToolError as Ny}from"@roll-agent/sdk";var My=new Map;async function Py(e,t,n={}){const{signal:r,onWait:a}=n;Dy(e,r,0);const o=My.get(e);let i;const s=new Promise(e=>{i=e});My.set(e,s);const c=performance.now();if(void 0!==o)try{await By(o,r)}catch(t){throw o.then(()=>{i(),$y(e,s)}),t instanceof Ty&&Oy(e,Fy(c)),t}try{return void 0!==o&&a?.(Fy(c)),Dy(e,r,Fy(c)),await t()}finally{i(),$y(e,s)}}var Ty=class extends Error{constructor(){super("Aborted while queued"),this.name="QueueAbortError"}};async function By(e,t){if(void 0!==t){if(t.aborted)throw new Ty;await new Promise((n,r)=>{const a=()=>{r(new Ty)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new Ty):n()})})}else await e}function Dy(e,t,n){!0===t?.aborted&&Oy(e,n)}function Oy(e,t){throw new Ny({code:"cancelled_while_queued",message:`Request was cancelled while waiting for another in-flight operation on browser instance "${e}". The tool did not execute.`,details:{browserInstance:e,queuedMs:Math.round(t)}})}function $y(e,t){My.get(e)===t&&My.delete(e)}function Fy(e){return performance.now()-e}import{StructuredToolError as jy}from"@roll-agent/sdk";function Ly(e){if(void 0===e)return;const t=ge().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new jy({code:"platform_mismatch",message:`Tool platform "${e}" does not match browser instance "${t.id}" platform "${t.platform}".`,details:{browserInstance:t.id,expectedPlatform:t.platform,requestedPlatform:e}})}function qy(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var zy={browserInstance:_y.string().trim().min(1).optional().describe("可选 browser instance id;多浏览器实例时用于选择目标 profile/CDP runtime。")};function Vy(e){return e.startsWith("zhipin_")?"zhipin":e.startsWith("yupao_")?"yupao":void 0}function Jy(e,t={}){const n=Hy(e.input);if(void 0===n)return e;const r=t.startRuntime??!0,a=t.serializePageOps??!0,o=t.expectedPlatform??Vy(e.name);return{...e,input:n,execute:async(t,n)=>{const i=t;return await G(i.browserInstance,async()=>{void 0!==i.browserInstance&&ge().getBundle(),Ly(Uy(t,o));const s=async()=>(r&&await fe(),await e.execute(t,n));if(!a)return await s();const c=ge().getBundle().id;return await Py(c,s,{...void 0!==n.signal?{signal:n.signal}:{},onWait:t=>{n.logger.info(`${e.name} waited ${Math.round(t)}ms for an in-flight operation on browser instance "${c}" (same-instance page operations are serialized)`)}})})}}}function Uy(e,t){const n=qy(e);return t??n}function Hy(e){if(e instanceof _y.ZodObject)return e.extend(zy);if(e instanceof _y.ZodEffects){const t=Hy(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?_y.ZodEffects.createWithPreprocess(n.transform,t,e._def):_y.ZodEffects.create(t,n,e._def)}}var Wy=t("browser-use-agent");async function Ky(){const e=(await Promise.allSettled([Em(),ve()])).filter(e=>"rejected"===e.status).map(e=>e.reason);if(e.length>0)throw new AggregateError(e,"Failed to fully shut down browser-use-agent")}function Gy(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e}var Yy=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function Xy(e){return Gy(e.name)?e:Jy(e,{startRuntime:"browser_status"!==e.name,serializePageOps:!Yy.has(e.name)})}var Zy=e({name:"browser-use-agent",tools:[ft,jt,In,Jn,er,sr,ya,Na,Fa,gc,tl,ul,yl,Xl,fu,Tu,Tp,Up,Xp,cg,Mf,Rf,Kf,Ng,lf,ih,gh,Nh,jh,ty,my,xe,Rt].map(Xy)},{onShutdown:Ky});async function Qy(){lt(ct()),Am({logger:Wy}),await le(Ay(),Ey());try{await Qm(),me(!0)}catch(e){me(!1),Wy.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Zy.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Qy().catch(async e=>{Wy.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await Ky().catch(()=>{}),process.exit(1)});
|
|
1
|
+
var e,t,n,r,a,o,i=Object.defineProperty,s=Object.getOwnPropertyNames,c=(e,t,n)=>function(){if(n)throw n[0];try{return e&&(t=(0,e[s(e)[0]])(e=0)),t}catch(e){throw n=[e],e}},l=(e,t)=>{for(var n in t)i(e,n,{get:t[n],enumerable:!0})};import{execFileSync as d}from"node:child_process";function u(){return o??=f(),o}function m(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/r),n=Math.floor(e/t),a=e%t;return Array.from({length:t},(e,t)=>n+(t<a?1:0))}function p(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=m(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 f(){const r=g(process.env.ROLL_BROWSER_WORK_AREA);if(void 0!==r)return r;if("darwin"===process.platform){const e=x();if(void 0!==e)return{x:e.x,y:e.y+t,width:e.width,height:Math.max(400,e.height-t-n)}}if("linux"===process.platform){const e=R();if(void 0!==e)return e}if("win32"===process.platform){const e=S();if(void 0!==e)return e}return e}function g(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 h(e){const t=y(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=y(t,/Resolution:\s*(\d+)\s*x\s*(\d+)/i);if(void 0!==e)return e}}function y(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 b(e){const t=e.trim().replace(/^\uFEFF/,"");if(0===t.length)return;let n;try{n=JSON.parse(t)}catch{return}if(!w(n))return;const r=v(n.x),a=v(n.y),o=v(n.width),i=v(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 w(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function v(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 x(){try{return h(d("system_profiler",["SPDisplaysDataType"],{encoding:"utf8",timeout:2e3,stdio:["ignore","pipe","ignore"]}).trim())}catch{return}}function S(){try{return b(d("powershell.exe",["-NoProfile","-NonInteractive","-Command",a],{encoding:"utf8",timeout:2e3,windowsHide:!0,stdio:["ignore","pipe","ignore"]}))}catch{return}}function R(){try{const e=d("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 I,C,k,_,A,E,N=c({"src/auto-window-layout.ts"(){"use strict";e={x:0,y:0,width:1920,height:1080},t=25,n=80,r=4,a=["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(" ")}});import{AsyncLocalStorage as M}from"node:async_hooks";import{access as P}from"node:fs/promises";import{constants as T}from"node:fs";import{homedir as O}from"node:os";import{join as B}from"node:path";import{createHash as D}from"node:crypto";import{StructuredToolError as $}from"@roll-agent/sdk";import{BrowserContextManager as j,BrowserRuntime as F,BrowserRuntimeConfigSchema as L,probeBrowserRuntimeCdpHealth as q,SessionStore as z}from"@roll-agent/browser";function V(e){return e.map(e=>void 0===e.cause?e.message:`${e.message}: ${J(e.cause)}`).join("; ")}function J(e){return e instanceof Error?e.message:String(e)}async function U(e,t){return await A.run(e,t)}function H(e,t,n,r){const a=n.headless??e.headless,o=n.windowBounds??X({instance:n,headless:a,...r});return L.parse({...e,mode:n.mode,headless:a,instanceId:t,profileName:n.profileName??t,profileColor:n.profileColor??W(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??B(C,t)})}function W(e){return k[e]??K(e)}function K(e){return G(137.508*e%360,.68,.45)}function G(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`#${Y(i+l)}${Y(s+l)}${Y(c+l)}`}function Y(e){return Math.round(255*e).toString(16).padStart(2,"0").toUpperCase()}function X(e){if(!(e.total<=1||e.headless||"managed-cdp"!==e.instance.mode))return p({index:e.index,total:e.total,workArea:u()})}function Z(e){const t=new z(e.runtimeConfig.sessionsDir),n=new F(e.runtimeConfig),r=new j(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 Q(e){if(void 0===e)return{exists:!1,writable:!1};return{userDataDir:e,exists:await ee(e,T.F_OK),writable:await ee(e,T.W_OK)}}async function ee(e,t){try{return await P(e,t),!0}catch{return!1}}function te(e){if(void 0!==e)return{source:"instance",agentIdFingerprint:ne(e)};const t=process.env.RECRUITMENT_EVENTS_DEFAULT_AGENT_ID?.trim();return void 0!==t&&t.length>0?{source:"default-env",agentIdFingerprint:ne(t)}:{source:"missing"}}function ne(e){return D("sha256").update(e).digest("hex").slice(0,8)}var re,ae,oe,ie,se,ce,le=c({"src/browser-instance-pool.ts"(){"use strict";N(),I="default",C=B(O(),".roll-agent","browser","sessions"),k=["#2563EB","#DC2626","#16A34A","#D97706","#7C3AED","#0891B2","#DB2777","#65A30D"],_=["stopped","not_running","not_found","failed"],A=new M,E=class{bundles=new Map;defaultInstanceId;startPromises=new Map;constructor(e,t){if(void 0===t||0===Object.keys(t.instances).length){const t=Z({id:I,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=Z({id:r,runtimeConfig:H(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===I}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??A.getStore()??this.defaultInstanceId??this.getOnlyInstanceId();if(void 0===t)throw new $({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 $({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([q(e.config),Q(e.config.userDataDir)]);return{id:e.id,...void 0!==e.platform?{platform:e.platform}:{},mode:e.config.mode,cdp:t,profile:n,tracking:te(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:V(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:V(n)}:{browserInstance:e,status:"stopped",mode:t.config.mode}}getOnlyInstanceId(){if(1===this.bundles.size)return this.bundles.keys().next().value}}}});import{createAgentLogger as de}from"@roll-agent/sdk";import"@roll-agent/browser";async function ue(e,t){if(re||ae)return;se=!1;const n=(re=new E(e,t)).getDefaultInstanceId(),r=re.listBundles()[0],a=void 0!==n?re.getBundle(n):r;void 0!==a&&(ae=a.runtime,oe=a.contextManager,ie=a.sessionStore)}function me(){if(re)return re.getBundle().runtime;if(!ae)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return ae}function pe(){if(re)return re.getBundle().contextManager;if(!oe)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return oe}function fe(e){se=e}function ge(){return se}function he(){return re}function ye(){if(!re)throw new Error("BrowserInstancePool not initialized. Call initRuntime() first.");return re}async function be(){await ye().ensureBundleStarted()}function we(){return void 0!==re&&!re.isLegacySingleInstancePool()}function ve(e){if(!re)return e;let t;try{t=re.getBundle()}catch{return}return t.trackingAgentId??e}function xe(){return ye().getBundle()}async function Se(){const e=re,t=oe,n=ae,r=[];if(re=void 0,oe=void 0,ae=void 0,ie=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 Re,Ie,Ce=c({"src/runtime-holder.ts"(){"use strict";le(),se=!1,ce=de("browser-use-agent")}});import{randomUUID as ke}from"node:crypto";import{z as _e}from"zod";function Ae(e=Date.now()){for(const[t,n]of Ie)n.expiresAtMs<=e&&Ie.delete(t)}function Ee(e,t){return e.tool===t.tool&&e.target===t.target&&e.digest===t.digest}function Ne(e,t,n=Date.now()){Ae(n);const r=ke(),a=n+t;return Ie.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 Me(e){return Ae(),!!Pe(e)&&(Ie.delete(e.approval.id),!0)}function Pe(e){Ae();const t=Ie.get(e.approval.id);return void 0!==t&&!!Ee(e.subject,t)}var Te,Oe,Be=c({"src/tool-action-approval.ts"(){"use strict";Re=_e.object({id:_e.string().trim().min(1)}),Ie=new Map}});import{randomUUID as De}from"node:crypto";import{DOM_CHOICE_UTILS as $e}from"@roll-agent/browser";function je(){return`${Te}${De().replaceAll("-","")}`}function Fe(e){return Number.isFinite(e)?Math.max(0,Math.min(Math.trunc(e),Oe)):Oe}function Le(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function qe(e){if(!Le(e)||!Le(e.root))return;const t=e.root.nodeId;return"number"==typeof t&&Number.isInteger(t)?t:void 0}function ze(e){if(Array.isArray(e)&&!e.some(e=>"string"!=typeof e))return e}function Ve(e){return Array.isArray(e)?e.flatMap(e=>{const t=Je(e);return void 0===t?[]:[t]}):[]}function Je(e){if(!Le(e))return;const t=e.nodeId,n=e.backendNodeId,r=ze(e.attributes),a=Je(e.contentDocument),o=Je(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:Ve(e.children),...void 0!==a?{contentDocument:a}:{},pseudoElements:Ve(e.pseudoElements),shadowRoots:Ve(e.shadowRoots),...void 0!==o?{templateContent:o}:{}}}function Ue(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 He(e){if(!Le(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 We(e){return Array.isArray(e)?e.flatMap(e=>{const t=He(e);return void 0===t?[]:[t]}):[]}function Ke(e){return e.isEditable?"editable":e.hasCursorPointer||e.hasOnClick||e.hasClassHint?"clickable":"focusable"}function Ge(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 Ye(e,t){const n=new Map;for(const r of t){const t=Ue(e,r.attributes);void 0!==t&&void 0!==r.backendNodeId&&n.set(t,r.backendNodeId)}return n}function Xe(e,t){const n=new Map,r=t=>{const a=Ue(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 Ze(e,t,n){return`(() => {\n const markerAttribute = ${JSON.stringify(e)};\n const maxCandidates = ${JSON.stringify(t)};\n const choices = (${$e})(document);\n const choiceRows = new Set();\n const choiceContainers = new Set();\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 domActionNameOf = (element) => {\n if (choiceRows.has(element)) return choices.text(element);\n const direct = directTextOf(element);\n if (direct) return direct;\n if (element.childElementCount === 0) return visibleTextOf(element);\n if (element.hasAttribute("onclick") || element.onclick !== null || window.getComputedStyle(element).cursor === "pointer") return visibleTextOf(element);\n return "";\n };\n const isVisible = choices.visible;\n const isDisabled = (element) =>\n element.matches(":disabled") ||\n element.closest('[aria-disabled="true"], [disabled], [inert]') !== 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) || choiceContainers.has(element)) return false;\n if (choiceRows.has(element)) return true;\n if (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 const scopeSelector = ${JSON.stringify(n??null)};\n const roots = scopeSelector === null ? [document.body] : Array.from(document.querySelectorAll(scopeSelector));\n if (roots.length !== 1 || !roots[0]) return [];\n const elements = scopeSelector === null ? roots[0].querySelectorAll("*") : [roots[0], ...roots[0].querySelectorAll("*")];\n // Resolve atomic rows before assigning markers: a short panel must not swallow its children.\n let inspected = 0;\n for (const element of [roots[0], ...elements]) {\n if (++inspected > 10000) break;\n if (element.childElementCount < 1) continue;\n const rows = choices.rows(element);\n if (!rows.length) continue;\n const semantic = element.matches('select,[role="listbox"],[role="menu"],[role="tree"]');\n const actionable = semantic || rows.some((row) =>\n window.getComputedStyle(row).cursor === "pointer" || row.hasAttribute("onclick") ||\n row.onclick !== null || row.hasAttribute("tabindex") || row.hasAttribute("aria-selected"));\n if (!actionable) continue;\n for (const row of rows) {\n // Native SELECT options are exposed by AX; their DOM popup boxes are not in-page hit targets.\n if (row.tagName === "OPTION") continue;\n choiceRows.add(row);\n for (let parent = row.parentElement; parent && roots[0].contains(parent); parent = parent.parentElement) {\n choiceContainers.add(parent);\n }\n }\n }\n for (const element of elements) {\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: choiceRows.has(element) || 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 Qe(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 et(e,t,n){if(void 0!==n.frameId){const n=await e.getDocument({depth:-1,pierce:!0});if(!Le(n))return new Map;const r=Je(n.root);return void 0===r?new Map:Xe(t,r)}const r=qe(await e.getDocument({depth:0}));if(void 0===r)return new Map;const a=await e.querySelectorAllByNodeId({nodeId:r,selector:`[${t}]`});return Ye(t,await Promise.all(a.map(async t=>await e.describeNode({nodeId:t}))))}async function tt(e,t={}){const n="number"==typeof t?{maxCandidates:t}:t,r=Fe(n.maxCandidates??Oe);if(0===r)return[];const a=je(),o=void 0===n.frameId?void 0:await e.createIsolatedWorld(n.frameId,{worldName:`roll-dom-action-${a.slice(Te.length)}`}).catch(()=>{});if(void 0!==n.frameId&&void 0===o)return[];const i=void 0===o?{}:{contextId:o},s=We(await e.evaluateJson(Ze(a,r,n.scope),i).catch(()=>[]));try{if(0===s.length)return[];const t=await et(e,a,n);return s.flatMap(e=>{const n=t.get(e.marker);return void 0===n?[]:[{backendNodeId:n,kind:Ke(e),name:e.name,hints:Ge(e),disabled:e.disabled}]})}finally{await e.evaluateJson(Qe(a),i).catch(()=>{})}}var nt,rt=c({"src/tools/browser-dom-action-candidates.ts"(){"use strict";Te="data-roll-browser-action-",Oe=120}});import{BrowserElementRefStore as at}from"@roll-agent/browser";var ot=c({"src/element-ref-store.ts"(){"use strict";nt=new at}});import{createBrowserAxSnapshot as it,enrichBrowserSnapshot as st,readBrowserDocumentIdentity as ct,resolveBrowserSnapshotScope as lt}from"@roll-agent/browser";import{readBrowserDocumentIdentity as dt}from"@roll-agent/browser";function ut(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 mt(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 pt(e,t){const n=ut(t);return(await Promise.all(n.map(async t=>{const n=await mt(e,t);return void 0===n?void 0:{node:t,frameId:n}}))).filter(e=>void 0!==e)}function ft(e){const t=Number.parseInt(e.slice(2),10);return Number.isFinite(t)?t:0}function gt(e){return e.refs.reduce((e,t)=>Math.max(e,ft(t.ref)),0)}function ht(e,t){return e.flatMap(e=>{const n=void 0===e.ref?void 0:t.get(e.ref);return void 0===n?[e]:[e,...ht(n.nodes,t)]})}function yt(e,t){return e.map(e=>{const n=void 0===e.ref?void 0:t.get(e.ref),r=yt(e.children??[],t),a=void 0===n?[]:yt(n.nodes,t),o=void 0===n?r:[...r,...a];return 0===o.length?e:{...e,children:o}})}async function bt(e){let t=gt(e.snapshot),n=Math.max(0,e.snapshot.maxNodes-e.snapshot.nodeCount),r=e.snapshot.truncated;const a=new Set,o=[],i=[...await pt(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 tt(e.controller,{frameId:c.frameId,maxCandidates:n}).catch(()=>[]),d=await it(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 pt(e.controller,d.nodes);o.push({iframeRef:c.node.ref,snapshot:d}),i.push(...u),t=gt(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?ht(e.snapshot.nodes,s):yt(e.snapshot.nodes,s),refs:[...e.snapshot.refs,...c],nodeCount:e.snapshot.nodeCount+l,truncated:r}}async function wt(e){const t=await ct(e.controller),n=void 0===e.scope?void 0:await lt({controller:e.controller,scope:e.scope,includeFrameDocuments:void 0===e.allowedOrigins}),r=await tt(e.controller,{maxCandidates:e.maxNodes??500,...void 0===e.scope?{}:{scope:e.scope}}),a=await it(e.controller,{domActionHints:r,...void 0===n?{}:{backendNodeIds:n.backendNodeIds},interactiveOnly:e.interactiveOnly??!0,maxNodes:e.maxNodes??500,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),o=void 0!==e.allowedOrigins?{...a,coverageWarnings:["iframe_expansion_disabled_for_origin_bounded_execution"]}:await bt({controller:e.controller,snapshot:a,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{}}),i=await st({controller:e.controller,snapshot:o,browserInstance:e.browserInstance,pageId:e.page.targetId,includeFrameDocuments:void 0===e.allowedOrigins,...void 0!==e.scope?{scope:e.scope}:{}});if(i.documentId!==t||await ct(e.controller)!==t)throw new Error("Document changed while observing. Take a new snapshot.");return nt.saveSnapshot(e.page.targetId,i),i}var vt,xt=c({"src/browser-observation.ts"(){"use strict";rt(),ot()}});function St(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Rt(e,t=0){if(t>24)throw new Error("JSON exceeds maximum depth");if(null!==e&&"string"!=typeof e&&"boolean"!=typeof e&&("number"!=typeof e||!Number.isFinite(e)))if(Array.isArray(e))for(const n of e)Rt(n,t+1);else{if(!St(e)||Object.getPrototypeOf(e)!==Object.prototype&&null!==Object.getPrototypeOf(e))throw new Error("Expected finite JSON value");for(const[n,r]of Object.entries(e)){if(["__proto__","constructor","prototype"].includes(n))throw new Error("Unsafe JSON key");Rt(r,t+1)}}}function It(e){Rt(e);const t=e=>Array.isArray(e)?e.map(t):St(e)?Object.fromEntries(Object.keys(e).sort().map(n=>[n,t(e[n])])):e;return JSON.stringify(t(e))}function Ct(e,t=0){if(0===t&&(Rt(e),Buffer.byteLength(JSON.stringify(e))>32768))throw new Error("Parameter schema too large");if(t>12||!St(e)||"string"!=typeof e.type||!Object.hasOwn(vt,e.type))throw new Error("Unsupported parameter schema type/depth");const n=new Set(["type","description","enum",...vt[e.type]]);for(const t of Object.keys(e))if(!n.has(t))throw new Error(`Unsupported schema keyword: ${t}`);if(void 0!==e.description&&"string"!=typeof e.description)throw new Error("Invalid schema description");for(const[t,n]of[["minimum","maximum"],["minLength","maxLength"],["minItems","maxItems"]])if(t&&n){for(const r of[t,n]){const t=e[r];if(void 0!==t&&("number"!=typeof t||!Number.isFinite(t)||"minimum"!==r&&"maximum"!==r&&(!Number.isInteger(t)||t<0)))throw new Error(`Invalid schema ${r}`)}if("number"==typeof e[t]&&"number"==typeof e[n]&&e[t]>e[n])throw new Error("Inverted schema bounds")}if("object"===e.type){if(void 0!==e.properties&&!St(e.properties))throw new Error("Invalid properties");const n=St(e.properties)?e.properties:{};for(const e of Object.values(n))Ct(e,t+1);if(void 0!==e.required&&(!Array.isArray(e.required)||e.required.some(e=>"string"!=typeof e||!Object.hasOwn(n,e))||new Set(e.required).size!==e.required.length))throw new Error("Invalid required properties");if(void 0!==e.additionalProperties&&"boolean"!=typeof e.additionalProperties)throw new Error("additionalProperties must be boolean")}if("array"===e.type&&Ct(e.items,t+1),void 0!==e.enum){if(!Array.isArray(e.enum)||0===e.enum.length||e.enum.length>100)throw new Error("Invalid enum");const t={...e};delete t.enum;for(const n of e.enum)kt(t,n,"$enum")}}function kt(e,t,n){if(Array.isArray(e.enum)&&!e.enum.some(e=>It(e)===It(t)))throw new Error(`${n}: value not in enum`);const r=()=>{throw new Error(`${n}: parameter does not match ${String(e.type)}`)},a=(t,n,a)=>{("number"==typeof e[n]&&t<e[n]||"number"==typeof e[a]&&t>e[a])&&r()};if("null"===e.type)null!==t&&r();else if("boolean"===e.type)"boolean"!=typeof t&&r();else if("string"===e.type)"string"!=typeof t?r():a([...t].length,"minLength","maxLength");else if("number"===e.type||"integer"===e.type)"number"!=typeof t||!Number.isFinite(t)||"integer"===e.type&&!Number.isInteger(t)?r():a(t,"minimum","maximum");else if("array"===e.type)Array.isArray(t)&&St(e.items)?(a(t.length,"minItems","maxItems"),t.forEach((t,r)=>kt(e.items,t,`${n}[${r}]`))):r();else if("object"===e.type)if(St(t)){const r=St(e.properties)?e.properties:{};for(const r of Array.isArray(e.required)?e.required:[])if("string"==typeof r&&!Object.hasOwn(t,r))throw new Error(`${n}: missing ${r}`);for(const[a,o]of Object.entries(t)){const t=r[a];if(Object.hasOwn(r,a)&&St(t))kt(t,o,`${n}.${a}`);else if(!1===e.additionalProperties)throw new Error(`${n}: unexpected ${a}`)}}else r()}function _t(e,t){if(Ct(e),Rt(t),Buffer.byteLength(JSON.stringify(t))>65536)throw new Error("Parameters too large");return kt(e,t,"$"),t}var At=c({"src/workflows/parameters.ts"(){"use strict";vt={object:["properties","required","additionalProperties"],string:["minLength","maxLength"],number:["minimum","maximum"],integer:["minimum","maximum"],boolean:[],null:[],array:["items","minItems","maxItems"]}}});import{createHash as Et}from"node:crypto";import{StructuredToolError as Nt}from"@roll-agent/sdk";import{isUrlAllowedByDomainAllowlist as Mt}from"@roll-agent/browser";function Pt(e,t){return Et("sha256").update(It({source:e.source,args:e.args,pageId:e.pageId,capabilities:[...new Set(e.capabilities)].sort(),allowedOrigins:[...new Set(e.allowedOrigins)].sort(),preconditions:e.preconditions,postconditions:e.postconditions,timeoutMs:e.timeoutMs,maxCalls:e.maxCalls,browserInstance:t.browserInstance,documentId:t.documentId,workflowKey:t.workflowKey??null,toolName:t.toolName??"browser_execute"})).digest("hex")}function Tt(e,t){if(e.allowedOrigins.some(e=>!Mt(e,t.domainAllowlist)))throw new Nt({code:"action_denied",message:"A declared script origin is outside domainAllowlist"})}function Ot(e,t,n){Tt(e,n);const r=Pt(e,t);if(!e.capabilities.some(e=>"interact"===e||"navigate"===e)||"log"===n.actionPolicy)return{approved:!1,executionDigest:r};if("deny"===n.actionPolicy)throw new Nt({code:"action_denied",message:"Browser script interactions are denied by actionPolicy"});const a={tool:t.toolName??"browser_execute",target:`${t.browserInstance}:${e.pageId}`,digest:r,summary:`Execute script ${r.slice(0,12)} with ${e.capabilities.join(", ")} on ${e.allowedOrigins.join(", ")}`};if(e.scriptApproval&&Me({approval:e.scriptApproval,subject:a}))return{approved:!0,executionDigest:r};const o=Ne(a,3e5);throw new Nt({code:"needs_confirmation",message:"Confirm this exact browser script, arguments, page and capabilities before execution",details:{executionState:"not_executed",approvalRequest:{...o,retryInput:{scriptApproval:o.retryInput.toolActionApproval}},executionDigest:r,pageId:e.pageId,browserInstance:t.browserInstance,capabilities:e.capabilities,allowedOrigins:e.allowedOrigins}})}var Bt,Dt=c({"src/browser-script-approval.ts"(){"use strict";At(),Be()}}),$t={};l($t,{executeBrowserTool:()=>Zt,setBrowserExecutionDependenciesForTests:()=>Xt});import{randomUUID as jt}from"node:crypto";import{mkdtemp as Ft,writeFile as Lt}from"node:fs/promises";import{join as qt}from"node:path";import{tmpdir as zt}from"node:os";import{StructuredToolError as Vt}from"@roll-agent/sdk";import{BrowserExecuteInputSchema as Jt,BrowserScriptError as Ut,BrowserScriptPageDriver as Ht,compileBrowserScript as Wt,executeBrowserProgram as Kt,normalizeBrowserOrigin as Gt,readBrowserDocumentIdentity as Yt}from"@roll-agent/browser";function Xt(e){Bt=e}async function Zt(e,t,n={}){const r=Jt.parse(e),a=It(r.args);if(Buffer.byteLength(a)>65536||Buffer.byteLength(r.source)>65536)throw new Vt({code:"invalid_input",message:"Script source and JSON parameters each have a 64KiB limit"});if(!r.capabilities.includes("read"))throw new Vt({code:"invalid_input",message:"Browser scripts require read capability for target and origin checks"});const o=Bt??{runtime:me(),browserInstance:he()?.getBundle().id??"default",compile:Wt,execute:Kt};if(Tt(r,o.runtime.getConfig().security),t.signal?.aborted)throw new Vt({code:"cancelled",message:"Browser script cancelled before execution"});const i=await o.compile(r.source);if(!i.valid)throw new Vt({code:"invalid_script",message:i.error??"Script compilation failed"});if(t.signal?.aborted)throw new Vt({code:"cancelled",message:"Browser script cancelled during compilation"});const s=(await o.runtime.listNativePages()).find(e=>e.targetId===r.pageId);if(!s)throw new Vt({code:"not_found",message:"The specified browser page no longer exists"});if(!r.allowedOrigins.includes(Gt(s.url)))throw new Vt({code:"action_denied",message:"The current page origin is not declared by this script"});const c=await o.runtime.connectNativePage(s),l=[];try{const e=await Yt(c),a=Ot(r,{browserInstance:o.browserInstance,documentId:e,...void 0===n.workflowKey?{}:{workflowKey:n.workflowKey},...void 0===n.toolName?{}:{toolName:n.toolName}},o.runtime.getConfig().security);let i;const d=new Ht({controller:c,pageId:s.targetId,browserInstance:o.browserInstance,allowedOrigins:r.allowedOrigins,capabilities:r.capabilities,...void 0===t.signal?{}:{signal:t.signal},guard:async e=>{if(t.signal?.aborted)throw new Ut("cancelled","Browser execution cancelled");if(!r.capabilities.includes(e))throw new Ut("capability_denied","Helper capability was not declared");const i=o.runtime.getConfig().security;try{Tt(r,i)}catch{throw new Ut("domain_denied","Allowed browser origins changed")}if(("interact"===e||"navigate"===e)&&("deny"===i.actionPolicy||"confirm"===i.actionPolicy&&!a.approved))throw new Ut("action_denied","Browser policy changed; execution stopped without replay");try{await(n.beforeAction?.())}catch(e){if(e instanceof Vt)throw new Ut(e.payload.code,"Workflow state changed; execution stopped");throw e}},observe:async e=>await wt({controller:c,page:s,browserInstance:o.browserInstance,allowedOrigins:r.allowedOrigins,maxNodes:Math.min(60,o.runtime.getConfig().security.maxSnapshotNodes),interactiveOnly:!1,...void 0===e?.scope?{}:{scope:e.scope}}),resolveRef:async(e,t)=>nt.getScopedRef({ref:e,snapshotId:t,browserInstance:o.browserInstance,pageId:s.targetId,documentId:await Yt(c)}),capture:async e=>{if(l.length>=10||e.length>33554432)throw new Ut("artifact_limit","Screenshot budget exceeded");i??=await Ft(qt(zt(),"roll-browser-execution-"));const t=jt(),n=qt(i,`${t}.png`);await Lt(n,Buffer.from(e,"base64"),{mode:384,flag:"wx"});const r={id:t,path:n,mimeType:"image/png"};return l.push(r),r}}),u=await o.execute(r,{driver:d,artifacts:l,...void 0===t.signal?{}:{signal:t.signal}});return t.logger.info(`browser_execute ${u.executionId}: ${u.status}, ${u.metrics.helperCalls} helpers, ${Math.round(u.metrics.elapsedMs)}ms`),{result:u,executionDigest:a.executionDigest}}finally{c.close()}}var Qt=c({"src/browser-execution.ts"(){"use strict";Ce(),xt(),ot(),At(),Dt()}});import{defineAgent as en,createAgentLogger as tn}from"@roll-agent/sdk";Ce();import{defineTool as nn}from"@roll-agent/sdk";import{z as rn}from"zod";var an=rn.object({success:rn.boolean(),mode:rn.string(),connected:rn.boolean()}),on=nn({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:rn.object({}),output:an,execute:async(e,t)=>{const n=me();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as sn}from"@roll-agent/sdk";import{z as cn}from"zod";import{BrowserSecurityConfigSchema as ln,BrowserStatusSchema as dn}from"@roll-agent/browser";import{createHash as un}from"node:crypto";import{z as mn}from"zod";var pn,fn,gn=["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"],hn=/^[0-9a-f]{8}$/,yn=mn.object({present:mn.boolean(),fingerprint:mn.string().regex(hn).optional()}),bn=mn.record(yn);function wn(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:vn(n)}]:[e,{present:!1}]}))}function vn(e){return un("sha256").update(e).digest("hex").slice(0,8)}function xn(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function Sn(){return void 0!==pn?pn:xn(process.env.BROWSER_VISUAL_CURSOR)??!0}function Rn(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function In(){return void 0!==fn?fn:Rn(process.env.BROWSER_VISUAL_ACTIVITY)??!0}Ce(),Be();import{StructuredToolError as Cn}from"@roll-agent/sdk";import{z as kn}from"zod";var _n=["log","deny","confirm"],An=kn.enum(_n),En=["zhipin_send_prepared_reply"],Nn=["unknown_tool_policy","double_confirmation","browser_action_policy_not_recommended"],Mn=kn.object({code:kn.enum(Nn),message:kn.string()}),Pn=kn.object({policy:An}),Tn=kn.object({approvalTtlMs:kn.number().int().positive().max(36e5).default(3e5),tools:kn.record(Pn).default({})}),On=Tn.parse({}),Bn=new Set(En),Dn=On;function $n(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 jn(e=process.env){try{return Tn.parse($n(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 Fn(e){Dn=Tn.parse(e)}function Ln(){return Dn}function qn(e){const t=e.toolPolicy??Dn,n=[];for(const e of Object.keys(t.tools))Bn.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 zn(e,t){const n=Dn.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(Vn(t.subject,"tool_policy_deny"))}`),new Cn({code:"action_denied",message:n,details:Vn(t.subject,"tool_policy_deny")})}if(void 0!==t.approval&&(!0===t.deferApprovalConsumption?Pe({approval:t.approval,subject:t.subject}):Me({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&&Me({approval:t.approval,subject:t.subject})}};const a="Tool execution requires confirmation by browser-use tool policy.",o={...Vn(t.subject,"tool_policy_confirm"),approvalRequest:Ne(t.subject,Dn.approvalTtlMs)};throw e.logger.warn(`${a} ${JSON.stringify(Vn(t.subject,"tool_policy_confirm"))}`),new Cn({code:"needs_confirmation",message:a,details:o})}function Vn(e,t){return{reason:t,tool:e.tool,target:e.target,...void 0!==e.summary?{summary:e.summary}:{}}}var Jn=dn.extend({replyAuthorityKeysLoaded:cn.boolean(),visualCursorEnabled:cn.boolean(),visualActivityEnabled:cn.boolean(),security:ln,toolPolicy:Tn,policyWarnings:cn.array(Mn),effectiveEnvSources:bn}),Un=sn({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:cn.object({}),output:Jn,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=ye(),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=Ln(),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:ge(),visualCursorEnabled:Sn(),visualActivityEnabled:In(),security:l,toolPolicy:d,policyWarnings:qn({browserSecurity:l,toolPolicy:d}),effectiveEnvSources:wn(gn)}}});le(),Ce();import{BrowserRuntimeModeSchema as Hn}from"@roll-agent/browser";import{StructuredToolError as Wn,defineTool as Kn}from"@roll-agent/sdk";import{z as Gn}from"zod";var Yn=Gn.object({browserInstance:Gn.string().trim().min(1).optional(),browserInstances:Gn.array(Gn.string().trim().min(1)).min(1).optional(),all:Gn.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:Gn.ZodIssueCode.custom,path:["all"],message:"all cannot be combined with browserInstance or browserInstances"})}),Xn=Gn.object({browserInstance:Gn.string(),status:Gn.enum(_),mode:Hn.optional(),message:Gn.string().optional()}),Zn=Gn.object({ok:Gn.boolean(),stopped:Gn.number().int().nonnegative(),results:Gn.array(Xn)}),Qn=Kn({name:"browser_stop",description:"关闭 browser-use-agent 当前托管的一个、多个或全部浏览器实例;不停止 browser-use-agent 服务进程。",input:Yn,output:Zn,execute:async(e,t)=>{const n=ye(),r=er(e,n.listBundles().map(e=>e.id));t.logger.info(`Stopping browser instances: ${r.join(", ")}`);return tr(await n.closeInstances(r))}});function er(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 Wn({code:"needs_input",message:"browserInstance, browserInstances, or all is required.",details:{availableInstances:t}});return r}function tr(e){return{ok:e.every(e=>"not_found"!==e.status&&"failed"!==e.status),stopped:e.filter(e=>"stopped"===e.status).length,results:[...e]}}Ce();import{defineTool as nr}from"@roll-agent/sdk";import{BrowserPageInfoSchema as rr,PlatformSchema as ar}from"@roll-agent/browser";import{z as or}from"zod";import{PLATFORMS as ir}from"@roll-agent/browser";var sr={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function cr(e){return new URL(sr[e]).host}function lr(e,t){try{return new URL(e).host.includes(cr(t))}catch{return!1}}function dr(e){return ir.find(t=>lr(e,t))}function ur(e,t){const n=dr(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 mr(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:dr(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var pr=or.object({platform:ar.optional().describe("可选:仅返回指定平台相关的页面")}),fr=or.object({pages:or.array(rr)}),gr=nr({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:pr,output:fr,execute:async(e,t)=>{const n=pe();t.logger.info("Listing browser pages");const r=(await n.listNativePages()).map(e=>ur(n,e));return{pages:void 0===e.platform?r:r.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});Ce();import{setTimeout as hr}from"node:timers/promises";import{defineTool as yr}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as br,BrowserPageInfoSchema as wr}from"@roll-agent/browser";import{z as vr}from"zod";import{StructuredToolError as xr}from"@roll-agent/sdk";import{isBrowserActionPolicyError as Sr,preflightBrowserAction as Rr}from"@roll-agent/browser";import{randomUUID as Ir}from"node:crypto";var Cr=6e4,kr=new Map;function _r(e=Date.now()){for(const[t,n]of kr)n.expiresAtMs<=e&&kr.delete(t)}function Ar(e,t){return e.action===t.action&&e.target===t.target&&(e.url??void 0)===(t.url??void 0)}function Er(e,t=Date.now()){_r(t);const n=Ir(),r=t+Cr,a={id:n,action:e.action,target:e.target,...void 0!==e.url?{url:e.url}:{},expiresAtMs:r};return kr.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 Nr(e){_r();const t=kr.get(e.approval.id);return void 0!==t&&(!!Ar(e.details,t)&&(kr.delete(e.approval.id),!0))}function Mr(e,t){const n=e.getConfig().security;return!0===t.approvedByConfirmation&&"confirm"===n.actionPolicy?{...n,actionPolicy:"log"}:n}function Pr(e,t,n={}){return{security:Mr(t,n),approveAction:Nr,...void 0!==n.approval?{approval:n.approval}:{},...!1===n.logActions?{}:{onActionLog:t=>{e.logger.info(t)}}}}function Tr(e,t,n){const r=Rr({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&&Nr({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 xr({code:r.code,message:r.message,details:{...r.details,..."needs_confirmation"===r.code?{approvalRequest:Er(r.details)}:{}}})}function Or(e){if(Sr(e))return new xr({code:e.payload.code,message:e.payload.message,details:{...e.payload.details,..."needs_confirmation"===e.payload.code?{approvalRequest:Er(e.payload.details)}:{}}})}function Br(){try{return me()}catch{return}}async function Dr(e){await e.bringToFront().catch(()=>{})}async function $r(e,t={}){const n=t.runtime??Br();if(void 0===n)return;const r=n.getConfig().security.foregroundPolicy;if("never"===r)return;if("always"===r)return void await Dr(e);const a=n.getNativePageWindowState;if("function"!=typeof a)return;"minimized"===await a.call(n,e.targetId).catch(()=>"unknown")&&await Dr(e)}Ce();var jr,Fr=15e3,Lr=250,qr=["/web/chat"],zr=vr.object({url:vr.string().url().describe("要导航到的目标 URL"),browserActionApproval:br.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Vr=vr.object({success:vr.boolean(),page:wr});function Jr(){return{getContextManager:pe,getRuntime:me,detectPlatformFromUrl:dr,matchesPlatformHost:lr,toNativePageInfo:ur,delay:hr,...jr}}function Ur(e){return"object"==typeof e&&null!==e}function Hr(e){return!!Ur(e)&&("string"==typeof e.url&&"string"==typeof e.title&&"string"==typeof e.readyState)}function Wr(e,t){if("zhipin"!==t)return;const n=new URL(e).pathname;if(void 0!==qr.find(e=>n===e||n.startsWith(`${e}/`)))throw new Error("navigate_active_tab 不支持直接导航 BOSS 后台聊天/推荐路径;请使用 zhipin_open_chat_page 或 zhipin_open_recommend_page。")}async function Kr(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 Gr(e){const t=await e.evaluateJson("(() => ({ url: location.href, title: document.title, readyState: document.readyState }))()");if(!Hr(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected page load state.");return t}async function Yr(e,t,n,r){const a=Date.now();let o;for(;Date.now()-a<Fr;){try{const t=await Gr(e);if(("interactive"===t.readyState||"complete"===t.readyState)&&Xr(t.url,n,r))return t}catch(e){o=e instanceof Error?e:new Error(String(e))}await t.delay(Lr)}throw new Error(`Native navigation did not reach document ready state within ${Fr}ms${o?`: ${o.message}`:""}`)}function Xr(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 Zr(e,t,n){t.url!==n&&await e.navigate(n)}async function Qr(e,t,n){return{...(await e.listNativePages()).find(e=>e.targetId===t.targetId)??t,url:n.url,title:n.title}}var ea=yr({name:"navigate_active_tab",description:"通过 native CDP 打开或导航页面;已知平台优先复用平台 tab,不触发 Playwright attach,不直接跳转 BOSS 后台聊天/推荐路径。",input:zr,output:Vr,execute:async(e,t)=>{const n=Jr(),r=n.getContextManager(),a=n.getRuntime(),o=n.detectPlatformFromUrl(e.url);Wr(e.url,o);const i=Tr(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 Kr(r,a,n,e.url,t.logger),c=await a.connectNativePage(s.page,{...Pr(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await $r({targetId:s.page.targetId,bringToFront:async()=>{await c.bringToFront()}},{runtime:a}),await Zr(c,s.page,e.url);const o=await Yr(c,n,e.url,s.page.url),i=await Qr(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()}}});Ce();import{BrowserActionApprovalSchema as ta,BrowserPageInfoSchema as na}from"@roll-agent/browser";import{defineTool as ra}from"@roll-agent/sdk";import{z as aa}from"zod";import{setTimeout as oa}from"node:timers/promises";var ia=15e3,sa=250,ca="__rollReloadMark";function la(e){return"object"==typeof e&&null!==e}function da(e){if(!la(e))return!1;const t=e.mark;return(null===t||"string"==typeof t)&&"string"==typeof e.readyState}function ua(){return`roll-reload-${String(Date.now())}-${Math.random().toString(36).slice(2)}`}async function ma(e){const t=await e.evaluateJson(`(() => ({ mark: window.${ca} ?? null, readyState: document.readyState }))()`);if(!da(t))throw new Error("Native CDP Runtime.evaluate returned an unexpected reload state.");return t}async function pa(e,t){const n=(t.createToken??ua)(),r=t.now??(()=>Date.now()),a=t.delay??oa,o=t.timeoutMs??ia,i=t.pollMs??sa;await e.evaluateJson(`(() => { window.${ca} = ${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 ma(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 fa,ga=aa.object({ignoreCache:aa.boolean().optional().describe("是否绕过缓存强制重新拉取资源(等价 Ctrl+Shift+R),默认 false。"),browserActionApproval:ta.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),ha=aa.object({success:aa.boolean(),reloaded:aa.boolean(),page:na});function ya(){return{getContextManager:pe,getRuntime:me,toNativePageInfo:ur,reloadNativePageAndWaitForSwap:pa,...fa}}function ba(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 wa=ra({name:"browser_reload_active_tab",description:"对当前 tracked native page 执行 CDP Page.reload,清空页面内 DOM 与 SPA 状态后等待文档完成换页;不触发 Playwright attach,走现有 actionPolicy / domainAllowlist 边界。",input:ga,output:ha,execute:async(e,t)=>{const n=ya(),r=n.getContextManager(),a=n.getRuntime(),o=ba(r,await a.listNativePages()),i=Tr(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,{...Pr(t,a,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{await $r({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()}}});Ce();import{defineTool as va}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as xa,BrowserPageInfoSchema as Sa,PlatformSchema as Ra}from"@roll-agent/browser";import{z as Ia}from"zod";async function Ca(e,t){return(await e.listNativePages()).find(e=>lr(e.url,t))}async function ka(e,t,n={}){const r=await Ca(e,t);if(r)return await e.activateNativePage(r.targetId),{page:r,reusedExistingPage:!0};return{page:await e.openNativePage(sr[t],n),reusedExistingPage:!1}}var _a=Ia.object({platform:Ra.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡"),browserActionApproval:xa.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Aa=Ia.object({success:Ia.boolean(),page:Sa,reusedExistingTab:Ia.boolean()}),Ea=va({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:_a,output:Aa,execute:async(e,t)=>{const{platform:n}=e,r=me(),a=pe(),o=sr[n],i=Tr(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 ka(r,n,Pr(t,r,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1}));return a.rememberNativePageSelection(n,s),{success:!0,page:ur(a,s),reusedExistingTab:c}}});Ce();import{defineTool as Na}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ma,PlatformSchema as Pa}from"@roll-agent/browser";import{z as Ta}from"zod";var Oa=Ta.object({platform:Pa.describe("要将该页面绑定为当前活跃页的平台"),pageId:Ta.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),Ba=Ta.object({success:Ta.boolean(),page:Ma}),Da=Na({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:Oa,output:Ba,execute:async(e,t)=>{const n=pe();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const r=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:ur(n,r)}}});xt(),Ce();import{defineTool as $a}from"@roll-agent/sdk";import{BrowserAxSnapshotSchema as ja,BrowserPageInfoSchema as Fa}from"@roll-agent/browser";import{z as La}from"zod";async function qa(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.")}var za=28,Va=90,Ja=250,Ua=620,Ha=8,Wa=32,Ka=8;function Ga(e){return new Promise(t=>{setTimeout(t,e)})}function Ya(e,t,n){return Math.min(Math.max(e,t),n)}function Xa(e,t){return Math.max(0,Math.floor(e??t))}function Za(e){return{x:Math.round(Number.isFinite(e.x)?e.x:0),y:Math.round(Number.isFinite(e.y)?e.y:0)}}function Qa(e,t){return Math.hypot(t.x-e.x,t.y-e.y)}function eo(e){return e<.5?4*e*e*e:1-Math.pow(-2*e+2,3)/2}function to(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 no(e,t){return Math.round(e.x+e.y+t.x+t.y)%2==0?1:-1}function ro(e,t){const n=Qa(e,t);if(n<=Ha)return[e,t];const r=Ya(Math.round(n/30),Ka,Wa),a=t.x-e.x,o=t.y-e.y,i=Math.max(n,1),s=-o/i,c=a/i,l=Ya(.08*n,4,28)*no(e,t),d=[e];for(let i=1;i<r;i+=1){const u=i/r,m=eo(u),p=Math.sin(Math.PI*u)*l,f=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+f)),y:Math.round(e.y+o*m+c*(p+f))})}return d.push(t),d}function ao(e,t){const n=Za(t);return ro(void 0===e?to(n):Za(e),n)}var oo=class{dispatcher;sleep;defaultStepDelayMs;lastPoint;constructor(e,t={}){this.dispatcher=e,this.sleep=t.sleep??Ga,this.defaultStepDelayMs=Xa(t.stepDelayMs,za)}reset(e){this.lastPoint=void 0===e?void 0:Za(e)}async moveTo(e,t={}){const n=Xa(t.stepDelayMs,this.defaultStepDelayMs),r=ao(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=Za(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:Za(e),durationMs:Ua})),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??Va),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??Ja),this.lastPoint=Za(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??Va),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=Za(t)}async delayIfPositive(e){const t=Math.max(0,Math.floor(e??0));t>0&&await this.sleep(t)}},io=14,so=720;function co(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 lo=class{target;constructor(e){this.target=e}async begin(e,t="info"){return!!In()&&await this.render({activity:{mode:"begin",label:e,tone:t}})}async highlightSelector(e,t={}){const n={...In()?{activity:{mode:"highlight",selector:e,label:t.label,padding:t.padding??io,tone:t.tone??"info"}}:{}};return"activity"in n&&await this.render(n)}async previewMouseMotion(e){Sn()&&0!==e.points.length&&await this.render({cursor:{path:{points:e.points,durationMs:e.durationMs}}})}async previewMouseClick(e){Sn()&&await this.render({cursor:{click:{point:e.point,durationMs:e.durationMs}}})}async succeed(e,t=so){return!!In()&&await this.render({activity:{mode:"complete",label:e,tone:"success",lingerMs:t}})}async fail(e,t=so){return!!In()&&await this.render({activity:{mode:"complete",label:e,tone:"error",lingerMs:t}})}async clear(){return!!In()&&await this.render({activity:{mode:"clear"}})}async render(e){try{return await this.target.evaluateJson(co(e))}catch{return!1}}};function uo(e){return new lo(e)}async function mo(e,t,n){const r=new oo(e);await r.click({x:n.x,y:n.y},{motionObserver:t})}var po=La.object({scope:La.string().max(1e3).optional().describe("可选:唯一匹配的 CSS 区域选择器"),pageId:La.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),maxDepth:La.number().int().nonnegative().optional().describe("可选:限制 AX Tree 深度"),maxNodes:La.number().int().positive().optional().describe("可选:返回节点上限;实际值不会超过 Browser security maxSnapshotNodes"),interactiveOnly:La.boolean().default(!0).describe("默认 true:只返回可交互节点")}),fo=La.object({page:Fa,snapshot:ja}),go=$a({name:"browser_snapshot",description:"读取当前或指定页面的 Accessibility Tree,补充独立选项行、控件上下文和覆盖缺口,并生成 @eN / snapshotId;支持 scope 区域观察,iframe ref 保留 frameId。默认只返回可交互节点。",input:po,output:fo,execute:async(e,t)=>{const n=me(),r=pe(),a=await qa({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=uo(o);try{await i.begin("正在读取页面快照");const t=n.getConfig().security,s=Math.min(e.maxNodes??t.maxSnapshotNodes,t.maxSnapshotNodes),c=await wt({controller:o,page:a,browserInstance:he()?.getBundle().id??"default",maxNodes:s,interactiveOnly:e.interactiveOnly??!0,...void 0!==e.maxDepth?{maxDepth:e.maxDepth}:{},...void 0!==e.scope?{scope:e.scope}:{}});return await i.succeed(`已识别 ${c.refs.length} 个可操作元素`),{page:ur(r,a),snapshot:c}}catch(e){throw await i.fail("读取页面快照失败"),e}finally{o.close()}}});ot(),Ce();import{StructuredToolError as ho,defineTool as yo}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as bo,BrowserElementRefHandleSchema as wo,clickElementRef as vo}from"@roll-agent/browser";import{z as xo}from"zod";import{BrowserElementRefHandleSchema as So}from"@roll-agent/browser";import{z as Ro}from"zod";var Io=Ro.enum(["backend_node_id","role_name_nth"]),Co=Ro.object({ref:So,role:Ro.string(),name:Ro.string(),x:Ro.number(),y:Ro.number(),resolvedBy:Io,backendNodeId:Ro.number().int().positive().optional(),frameId:Ro.string().optional(),disabled:Ro.boolean()}),ko=Ro.object({success:Ro.literal(!0),ref:So,resolvedBy:Io,target:Co});xt();import{assertFramePointUnoccluded as _o,BrowserScriptError as Ao,isUrlAllowedByDomainAllowlist as Eo,normalizeBrowserOrigin as No}from"@roll-agent/browser";import{StructuredToolError as Mo}from"@roll-agent/sdk";function Po(e,t){if(!t.frameId)return e;const n=t.frameId;let r;const a=async()=>{if(t.signal?.aborted)throw new Mo({code:"cancelled",message:"Browser action cancelled."});const e=t.runtime.getConfig().security;if("deny"===e.actionPolicy||"confirm"===e.actionPolicy&&!t.approvedByConfirmation)throw new Mo({code:"action_denied",message:"Browser action policy changed; take no further action."})},o=async(r,o)=>{await a();const i=await e.getFrameTree();if(await a(),n===i.frame.id)return;let s;try{s=No(i.frame.url)}catch{throw new Mo({code:"coverage_gap",message:"Cannot determine the ancestor document origin."})}const c=async()=>{if(await a(),!Eo(s,t.runtime.getConfig().security.domainAllowlist))throw new Mo({code:"domain_denied",message:"The frame ancestor is outside the current domain allowlist."})};try{await _o(e,{frameId:n,tree:i,point:r,allowedOrigins:[s],requireFocus:o,guard:c})}catch(e){if(e instanceof Ao)throw new Mo({code:e.code,message:e.message});throw e}},i=async t=>{"mousePressed"!==t.type&&"mouseReleased"!==t.type||(await o(t,!1),r={x:t.x,y:t.y}),await e.dispatchMouseEvent(t)},s=async()=>{if(!r)throw new Mo({code:"focus_changed",message:"No verified frame focus target is available."});await o(r,!0)},c=async t=>{await s(),await e.dispatchKeyEvent(t)},l=async t=>{await s(),await e.insertText(t)};return new Proxy(e,{get(e,t){if("dispatchMouseEvent"===t)return i;if("dispatchKeyEvent"===t)return c;if("insertText"===t)return l;const n=Reflect.get(e,t,e);return"function"==typeof n?n.bind(e):n}})}var To=xo.object({ref:wo.describe("browser_snapshot 返回的 @eN element ref"),snapshotId:xo.string().optional().describe("严格绑定 browser_snapshot;过期快照或文档变更时停止"),pageId:xo.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:bo.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Oo=yo({name:"click_ref",description:"点击 browser_snapshot 返回的 @eN ref;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:To,output:ko,execute:async(e,t)=>{const n=me(),r=pe(),a=await qa({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}});let o=nt.getRef(a.targetId,e.ref);if(void 0===o)throw new ho({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=Tr(t,n,{action:"click",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...Pr(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{if(void 0!==e.snapshotId&&(o=nt.getScopedRef({browserInstance:he()?.getBundle().id??"default",pageId:a.targetId,snapshotId:e.snapshotId,documentId:await dt(s),ref:e.ref}),void 0===o))throw new ho({code:"not_found",message:"Snapshot ref is stale or belongs to another page/instance. Run browser_snapshot again."});await $r({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const r=uo(s),c=Po(s,{...void 0===o.frameId?{}:{frameId:o.frameId},runtime:n,approvedByConfirmation:i.approvedByConfirmation,...void 0===t.signal?{}:{signal:t.signal}});await r.begin(`正在点击 ${e.ref}`);const l=await vo({controller:c,elementRef:o,options:{clickTarget:async e=>{await mo(c,r,e)}}});return await r.succeed(`已点击 ${e.ref}`),l}catch(t){await uo(s).fail(`点击 ${e.ref} 失败`);const n=Or(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});ot(),Ce();import{StructuredToolError as Bo,defineTool as Do}from"@roll-agent/sdk";import{BrowserActionApprovalSchema as $o,BrowserElementRefHandleSchema as jo,typeElementRef as Fo}from"@roll-agent/browser";import{z as Lo}from"zod";xt();var qo=Lo.object({ref:jo.describe("browser_snapshot 返回的 @eN element ref"),text:Lo.string().describe("要输入的文本"),clear:Lo.boolean().default(!1).describe("输入前是否先清空当前控件内容"),snapshotId:Lo.string().optional().describe("严格绑定 browser_snapshot;过期快照或文档变更时停止"),pageId:Lo.string().optional().describe("可选:通过 list_pages 返回的 pageId/native targetId"),browserActionApproval:$o.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),zo=Do({name:"type_ref",description:"向 browser_snapshot 返回的 @eN ref 输入文本;优先使用 backendNodeId,失效时使用 role/name/nth fallback。",input:qo,output:ko,execute:async(e,t)=>{const n=me(),r=pe(),a=await qa({runtime:n,ctxManager:r,...void 0!==e.pageId?{pageId:e.pageId}:{}});let o=nt.getRef(a.targetId,e.ref);if(void 0===o)throw new Bo({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=Tr(t,n,{action:"type",target:e.ref,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),s=await n.connectNativePage(a,{...Pr(t,n,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation,logActions:!1})});try{if(void 0!==e.snapshotId&&(o=nt.getScopedRef({browserInstance:he()?.getBundle().id??"default",pageId:a.targetId,snapshotId:e.snapshotId,documentId:await dt(s),ref:e.ref}),void 0===o))throw new Bo({code:"not_found",message:"Snapshot ref is stale or belongs to another page/instance. Run browser_snapshot again."});await $r({targetId:a.targetId,bringToFront:async()=>{await s.bringToFront()}},{runtime:n});const r=uo(s),c=Po(s,{...void 0===o.frameId?{}:{frameId:o.frameId},runtime:n,approvedByConfirmation:i.approvedByConfirmation,...void 0===t.signal?{}:{signal:t.signal}});await r.begin(`正在输入到 ${e.ref}`);const l=await Fo({controller:c,elementRef:o,text:e.text,options:{...void 0!==e.clear?{clear:e.clear}:{},clickTarget:async e=>{await mo(c,r,e)}}});return await r.succeed(`已输入到 ${e.ref}`),l}catch(t){await uo(s).fail(`输入到 ${e.ref} 失败`);const n=Or(t);if(void 0!==n)throw n;throw t}finally{s.close()}}});Qt();import{defineTool as Vo}from"@roll-agent/sdk";import{BrowserExecuteInputSchema as Jo,BrowserExecuteResultSchema as Uo}from"@roll-agent/browser";var Ho=Vo({name:"browser_execute",description:"在单个明确页面执行受控 JavaScript:只使用 page helpers,支持组合操作、条件、循环与结果断言。陌生单选控件可先 page.inspectControl(field),再 page.choose(field,{label});iframe 定位保留 frameId 或使用严格 ref。优先使用已有 zhipin_* 等专用工具。失败返回已执行步骤,不自动重放。",input:Jo,output:Uo,execute:async(e,t)=>(await Zt(Jo.parse(e),t)).result});import{createHash as Wo}from"node:crypto";import{defineTool as Ko,StructuredToolError as Go}from"@roll-agent/sdk";import{BrowserExecuteInputSchema as Yo,BrowserExecuteResultSchema as Xo,BrowserScriptConditionSchema as Zo,compileBrowserScript as Qo}from"@roll-agent/browser";import{z as ei}from"zod";At();import{createHash as ti,randomUUID as ni}from"node:crypto";import{constants as ri}from"node:fs";import{lstat as ai,mkdir as oi,open as ii,readdir as si,rename as ci,rm as li,rmdir as di}from"node:fs/promises";import{homedir as ui}from"node:os";import{dirname as mi,join as pi,resolve as fi}from"node:path";import{setTimeout as gi}from"node:timers/promises";import{z as hi}from"zod";var yi=hi.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/),bi=hi.string().regex(/^[a-f0-9]{64}$/),wi=hi.string().max(2048).refine(e=>{try{const t=new URL(e);return["https:","http:"].includes(t.protocol)&&t.origin===e}catch{return!1}},"Expected canonical HTTP(S) origin"),vi=hi.record(hi.unknown()).superRefine((e,t)=>{try{Ct(e)}catch(e){t.addIssue({code:"custom",message:e instanceof Error?e.message:"Invalid parameter schema"})}}),xi=hi.object({id:yi,name:hi.string().min(1).max(120),description:hi.string().max(2e3),appliesTo:hi.object({origins:hi.array(wi).min(1).max(20),pathPrefix:hi.string().max(2048).startsWith("/").optional()}).strict(),source:hi.string().min(1).max(65536),parameterSchema:vi,capabilities:hi.array(hi.enum(["read","interact","navigate","capture"])).min(1).max(4),allowedOrigins:hi.array(wi).min(1).max(20),preconditions:hi.array(hi.record(hi.unknown())).max(30).default([]),postconditions:hi.array(hi.record(hi.unknown())).min(1).max(30),notes:hi.string().max(8e3).default(""),locatorExplanations:hi.array(hi.string().max(1e3)).max(30).default([])}).strict().superRefine((e,t)=>{e.appliesTo.origins.some(t=>!e.allowedOrigins.includes(t))&&t.addIssue({code:"custom",message:"Applicable origins must be allowed"})}),Si=hi.enum(["draft","active","disabled","suspended"]),Ri=hi.object({compiled:hi.literal(!0),success:hi.literal(!0),verifiedAssertions:hi.number().int().min(1).max(100),executionDigest:bi}).strict(),Ii=Ri.extend({version:bi,at:hi.string().datetime()}),Ci=hi.object({status:Si,validations:hi.array(Ii).max(10),updatedAt:hi.string().datetime()}).strict(),ki=hi.object({version:bi,createdAt:hi.string().datetime(),draft:xi}).strict();function _i(e){return ti("sha256").update(It(e)).digest("hex")}function Ai(e){return e instanceof Error&&"code"in e&&"ENOENT"===e.code}function Ei(e){return e instanceof Error&&"code"in e&&"EEXIST"===e.code}var Ni=class{root;managedBase;constructor(e={}){this.managedBase=void 0===e.root?pi(ui(),".roll-agent"):fi(e.root),this.root=fi(e.root??pi(ui(),".roll-agent","browser","workflows"))}async directory(e){e!==this.managedBase&&await this.directory(mi(e));try{const t=await ai(e);if(t.isSymbolicLink()||!t.isDirectory())throw new Error("Workflow directory must not be a symlink or file")}catch(t){if(!Ai(t))throw t;e===this.managedBase&&await oi(mi(e),{recursive:!0,mode:448});try{await oi(e,{mode:448})}catch(e){if(!Ei(e))throw e}const n=await ai(e);if(n.isSymbolicLink()||!n.isDirectory())throw new Error("Unsafe workflow directory")}}async read(e){const t=await ii(e,ri.O_RDONLY|ri.O_NOFOLLOW);try{const e=await t.stat();if(!e.isFile()||e.size>16e4)throw new Error("Invalid workflow file");return JSON.parse(await t.readFile("utf8"))}finally{await t.close()}}async write(e,t){const n=`${e}.${ni()}.tmp`,r=await ii(n,ri.O_CREAT|ri.O_EXCL|ri.O_WRONLY|ri.O_NOFOLLOW,384);try{await r.writeFile(It(t)),await r.sync()}finally{await r.close()}try{await ci(n,e)}finally{await li(n,{force:!0})}}async locked(e,t){yi.parse(e),await this.directory(this.root);const n=pi(this.root,e);await this.directory(n);const r=pi(n,".lock"),a=Date.now()+5e3;for(;;)try{await oi(r,{mode:448});break}catch(e){if(!Ei(e))throw e;if(Date.now()>=a)throw new Error("Workflow store busy; stale locks require explicit recovery");await gi(20)}try{return await t(n)}finally{await di(r)}}async load(e,t,n){bi.parse(n);const r=ki.parse(await this.read(pi(e,`${n}.json`)));if(r.version!==n||r.draft.id!==t||_i(r.draft)!==n)throw new Error("Workflow content hash mismatch");let a;try{a=Ci.parse(await this.read(pi(e,`${n}.state.json`)))}catch(e){if(!Ai(e))throw e;a={status:"draft",validations:[],updatedAt:r.createdAt}}if(a.validations.some(e=>e.version!==n)||"active"===a.status&&0===a.validations.length)throw new Error("Invalid workflow validation state");return{...r,state:a}}async saveDraft(e){const t=xi.parse(e);if(Buffer.byteLength(It(t))>128e3)throw new Error("Workflow draft too large");const n=_i(t);return await this.locked(t.id,async e=>{try{return await this.load(e,t.id,n)}catch(e){if(!Ai(e))throw e}const r=(new Date).toISOString();return await this.write(pi(e,`${n}.json`),{version:n,createdAt:r,draft:t}),await this.load(e,t.id,n)})}async getVersion(e,t){return await this.locked(e,async n=>await this.load(n,e,t))}async recordValidation(e,t,n){const r=Ri.parse(n);return await this.locked(e,async n=>{const a=await this.load(n,e,t),o=(new Date).toISOString(),i={...a.state,updatedAt:o,validations:[...a.state.validations,{...r,version:t,at:o}].slice(-10)};return await this.write(pi(n,`${t}.state.json`),i),i})}async setStatus(e,t,n){if(!hi.enum(["active","disabled","suspended"]).safeParse(n).success)throw new Error("Invalid workflow status");return await this.locked(e,async r=>{const a=await this.load(r,e,t);if("active"===n&&0===a.state.validations.length)throw new Error("Activation requires compilation and verified successful execution for this version");const o={...a.state,status:n,updatedAt:(new Date).toISOString()};return await this.write(pi(r,`${t}.state.json`),o),o})}async listApplicableActive(e){const t=new URL(e);if(!["http:","https:"].includes(t.protocol))return[];await this.directory(this.root);const n=[];for(const e of(await si(this.root)).filter(e=>yi.safeParse(e).success).sort())await this.locked(e,async r=>{for(const a of(await si(r)).filter(e=>/^[a-f0-9]{64}\.json$/.test(e)).sort()){const o=await this.load(r,e,a.slice(0,-5)),{draft:i,version:s,state:c}=o;"active"!==c.status||!i.appliesTo.origins.includes(t.origin)||i.appliesTo.pathPrefix&&!t.pathname.startsWith(i.appliesTo.pathPrefix)||n.push({id:e,version:s,name:i.name,description:i.description,appliesTo:i.appliesTo,parameterSchema:i.parameterSchema})}});return n}};At(),Ce(),Be();var Mi=ei.object({id:ei.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/),version:ei.string().regex(/^[a-f0-9]{64}$/)}),Pi=ei.object({url:ei.string().url().max(8e3)}).strict(),Ti=ei.object({draft:xi}).strict(),Oi=Mi.extend({pageId:ei.string().min(1),args:ei.record(ei.unknown()).default({}),scriptApproval:ei.object({id:ei.string().min(1)}).strict().optional()}).strict(),Bi=Mi.extend({status:ei.enum(["active","disabled"]),toolActionApproval:Re.optional()}).strict(),Di=Mi.extend({name:ei.string(),status:Si,validationCount:ei.number().int().nonnegative()}),$i=ei.object({workflow:Mi.extend({status:Si}),result:Xo,needsReexploration:ei.boolean(),validationRecorded:ei.boolean().optional()}),ji={store:new Ni,execute:async(e,t,n)=>await(await Promise.resolve().then(()=>(Qt(),$t))).executeBrowserTool(e,t,n),readPageUrl:async e=>{const t=(await me().listNativePages()).find(t=>t.targetId===e);if(!t)throw new Go({code:"not_found",message:"Workflow page was not found; select an existing page"});return t.url},compile:Qo},Fi=ji;function Li(e){return{id:e.draft.id,version:e.version,name:e.draft.name,status:e.state.status,validationCount:e.state.validations.length}}function qi(e,t){const n=new URL(t);if(!e.draft.appliesTo.origins.includes(n.origin)||e.draft.appliesTo.pathPrefix&&!n.pathname.startsWith(e.draft.appliesTo.pathPrefix))throw new Go({code:"workflow_not_applicable",message:"Workflow does not apply to the selected page"})}async function zi(e){if(!(await Fi.compile(e.draft.source)).valid)throw new Go({code:"invalid_workflow",message:"Workflow source failed compilation"})}function Vi(e,t){return _t(e.draft.parameterSchema,t.args),Yo.parse({pageId:t.pageId,source:e.draft.source,args:t.args,capabilities:e.draft.capabilities,allowedOrigins:e.draft.allowedOrigins,preconditions:e.draft.preconditions,postconditions:e.draft.postconditions,...t.scriptApproval?{scriptApproval:t.scriptApproval}:{}})}var Ji=new Set(["stale_ref","stale_target","ambiguous_target","not_found","target_not_found","verification_failed","coverage_gap","target_disabled","target_occluded","target_obscured","control_unassociated","unsupported_control","focus_changed","target_moved"]);async function Ui(e,t,n){const r=Fi,a=await r.store.getVersion(e.id,e.version);if(!n&&"active"!==a.state.status)throw new Go({code:"workflow_inactive",message:"Only explicitly enabled workflow versions may run"});qi(a,await r.readPageUrl(e.pageId));const o=Vi(a,e),{result:i,executionDigest:s}=await r.execute(o,t,{workflowKey:`${e.id}:${e.version}`,toolName:n?"browser_workflow_validate":"browser_workflow_run",beforeAction:async()=>{const t=await r.store.getVersion(e.id,e.version),o=n&&t.state.status!==a.state.status&&["disabled","suspended"].includes(t.state.status);if(!n&&"active"!==t.state.status||o)throw new Go({code:"workflow_inactive",message:"Workflow was disabled or suspended while executing"})}}),c="failed"===i.status&&void 0!==i.error&&Ji.has(i.error.code);c&&await r.store.setStatus(e.id,e.version,"suspended");let l=!1;n&&"completed"===i.status&&"passed"===i.verification&&i.metrics.verifiedAssertions>0&&(await r.store.recordValidation(e.id,e.version,{compiled:!0,success:!0,verifiedAssertions:i.metrics.verifiedAssertions,executionDigest:s}),l=!0);const d=await r.store.getVersion(e.id,e.version);return{workflow:{id:e.id,version:e.version,status:d.state.status},result:i,needsReexploration:c,...n?{validationRecorded:l}:{}}}var Hi=Ko({name:"browser_workflow_list",description:"按明确 URL 发现已启用的站点经验;不会启动或连接浏览器。优先使用已有平台专用方法。",input:Pi,output:ei.object({workflows:ei.array(ei.object({id:ei.string(),version:ei.string(),name:ei.string(),description:ei.string(),appliesTo:ei.object({origins:ei.array(ei.string()),pathPrefix:ei.string().optional()}),parameterSchema:ei.record(ei.unknown())}))}),annotations:{readOnlyHint:!0},execute:async e=>({workflows:await Fi.store.listApplicableActive(e.url)})}),Wi=Ko({name:"browser_workflow_save_draft",description:"将已探索的方法保存为不可变草稿;只编译、不执行,也不自动启用。不保存运行参数或页面内容。",input:Ti,output:Di,execute:async e=>{const t=Ti.parse(e);if("object"!==t.draft.parameterSchema.type)throw new Go({code:"invalid_workflow",message:"Workflow parameters must use an object schema"});const n=ei.array(Zo).max(20),r={...t.draft,preconditions:n.parse(t.draft.preconditions),postconditions:n.min(1).parse(t.draft.postconditions)};if(!(await Fi.compile(r.source)).valid)throw new Go({code:"invalid_workflow",message:"Workflow source failed compilation"});return Li(await Fi.store.saveDraft(r))}}),Ki=Ko({name:"browser_workflow_validate",description:"在当前明确页面上实际执行指定版本,验证参数与结果断言;可能产生浏览器操作,遵守整段审批,不自动重复或启用。",input:Oi,output:$i,execute:async(e,t)=>await Ui(Oi.parse(e),t,!0)}),Gi=Ko({name:"browser_workflow_set_status",description:"启用精确版本需成功验证记录和一次显式批准;停用立即生效。管理操作不需要浏览器。",input:Bi,output:Di,execute:async e=>{const t=Bi.parse(e),n=await Fi.store.getVersion(t.id,t.version);if("active"===t.status){if(await zi(n),0===n.state.validations.length)throw new Go({code:"workflow_unvalidated",message:"Validate this exact version successfully before enabling it"});const e={tool:"browser_workflow_set_status",target:`${t.id}:${t.version}`,digest:Wo("sha256").update(`active:${t.id}:${t.version}`).digest("hex"),summary:`Enable ${n.draft.name} version ${t.version}`};if(!t.toolActionApproval||!Me({approval:t.toolActionApproval,subject:e}))throw new Go({code:"needs_confirmation",message:"Enabling a reusable workflow requires explicit approval for this exact version",details:{executionState:"not_executed",reason:"workflow_activation",approvalRequest:Ne(e,3e5)}})}return await Fi.store.setStatus(t.id,t.version,t.status),Li(await Fi.store.getVersion(t.id,t.version))}}),Yi=Ko({name:"browser_workflow_run",description:"执行已显式启用且适用于当前页面的精确站点经验版本;失败返回部分进度,需要重新探索时暂停推荐。",input:Oi,output:$i,execute:async(e,t)=>await Ui(Oi.parse(e),t,!1)});Ce();import{defineTool as Xi}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Zi}from"@roll-agent/browser";import{z as Qi}from"zod";Ce();import{setTimeout as es}from"node:timers/promises";function ts(e){return e.trim().toLocaleLowerCase("zh-CN")}function ns(e,t){let n=0;for(const r of e)t.includes(r)&&(n+=1);return n}function rs(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=ts(n),r=e.filter(e=>e.name.length>0);let a=r.find(e=>ts(e.name)===t);if(a)return a;if(a=r.find(e=>{const n=ts(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=ts(e.name);return ns(t,n)>=Math.ceil(Math.min(t.length,n.length)*o)}),a)return a}if(void 0!==t.index)return e[t.index]}var as=["chat-list","chat-history","recommend-list"],os={"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 is(e){return os[e]}import{StructuredToolError as ss,isStructuredToolError as cs}from"@roll-agent/sdk";var ls="zhipin_access_restricted",ds=["/web/passport/zp/403.html","/web/common/403.html","/web/passport/cm/403.html"],us=["/web/passport/zp/verify.html","/web/user/safe/verify-slider","/web/passport/cm/verify.html"],ms=["/web/passport/zp/security.html","/web/common/security-check.html"],ps=["/web/user"],fs="38",gs={31:"ip_block","-1000031":"ip_block",5002:"ip_block",32:"uid_block",5003:"uid_block",5004:"uid_block"},hs={"访问受限":"ip_block","安全验证":"verify"},ys={ip_block:"IP 级封禁:请等待页面标明的解封时间;刷新、换工具、换 browserInstance/profile 均无效。",uid_block:"账号级封禁:请等待账号解封;刷新、换工具、换 browserInstance/profile 均无效。",verify:"请在浏览器中手动完成验证并等待后再操作,不要立即重试。",security:"请在浏览器中手动完成安全检查并等待后再操作,不要立即重试。",anti_spider_login:"站点反爬拦截要求登录:请人工登录并等待后再操作,不要自动重试。"};function bs(e){const t=e.url.trim(),n=(e.title??"").trim(),r=Rs(t);if(Ss(r,ds))return{kind:gs[Is(t)??""]??"ip_block",url:t,title:n};if(Ss(r,us))return{kind:"verify",url:t,title:n};if(Ss(r,ms))return{kind:"security",url:t,title:n};if(Ss(r,ps)&&Is(t)===fs)return{kind:"anti_spider_login",url:t,title:n};for(const e of Object.keys(hs))if(n.includes(e))return{kind:hs[e],url:t,title:n};return null}function ws(e){const t=bs(e);if(null!==t)throw vs(t)}function vs(e){return new ss({code:ls,message:`BOSS 风控页已出现(${e.kind})。${ys[e.kind]}`,details:{kind:e.kind,url:e.url,title:e.title}})}function xs(e){if(cs(e))throw e}function Ss(e,t){return t.some(t=>e===t||e.startsWith(`${t}/`))}function Rs(e){try{return new URL(e).pathname}catch{const t=e.split("#")[0]??e,n=t.split("?")[0]??t,r=n.indexOf("://"),a=r>=0?n.slice(r+3):n,o=a.indexOf("/");return o>=0?a.slice(o):a}}function Is(e){try{return new URL(e).searchParams.get("code")??void 0}catch{const t=e.split("?")[1]?.split("#")[0];if(void 0===t||0===t.length)return;for(const e of t.split("&")){const t=e.indexOf("=");if("code"!==(t>=0?e.slice(0,t):e))continue;const n=t>=0?e.slice(t+1):"";try{return decodeURIComponent(n)}catch{return n}}return}}var Cs={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"}},ks=["不限","男","女"],_s=["不限","刚刚活跃","今日活跃","3日内活跃","本周活跃","本月活跃"],As=["patch","replace"],Es=[{key:"gender",label:"性别",selection:"single",clearValue:"不限",values:ks},{key:"activity",label:"活跃度",selection:"single",clearValue:"不限",values:_s},{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:"不限"}],Ns=["applied","recommend_not_ready","filter_not_found","requires_vip","age_not_applied","clear_failed","submit_failed","error"];function Ms(e){const t=Es.find(t=>t.key===e);if(void 0===t)throw new Error(`Unknown recommend filter field: ${e}`);return t}function Ps(e){return void 0!==e.ageMin||void 0!==e.ageMax}var Ts=".candidate-card-wrap",Os="[data-geek], .geek-item",Bs=`${Ts}, ${Os}`,Ds="[data-geek], .card-inner, .geek-item",$s='.boss-popup__wrapper, .dialog-lib-resume, .boss-dialog, [data-type="boss-dialog"]',js=`${$s}, .recommendV2`,Fs=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Ls='iframe[src*="c-resume"]',qs="canvas#resume, div#resume canvas";function zs(e){const t=(e.recommendFrameRect?.x??0)+(e.resumeFrameRect?.x??0),n=(e.recommendFrameRect?.y??0)+(e.resumeFrameRect?.y??0);return{x:Math.round(t+e.canvasRect.x),y:Math.round(n+e.canvasRect.y),width:Math.round(e.canvasRect.width),height:Math.round(e.canvasRect.height)}}var Vs=30,Js=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function Us(e){const t=e.trim();return!(0===t.length||t.length>Vs)&&(!Js.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Hs(e){let t=e.priority;return Js.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 Ws(e){const t=e.filter(e=>Us(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=Hs(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 Ks='.user-list.b-scroll-stable [role="listitem"], .geek-item',Gs='.user-list.b-scroll-stable, .user-list.b-scroll-stable [role="listitem"], .geek-item',Ys=".candidate-card-wrap",Xs="[data-geek], .geek-item",Zs=`${Ys}, ${Xs}`,Qs=["/web/chat/recommend"],ec=250,tc=300,nc=12,rc=900,ac=1200,oc=4,ic=4,sc=250,cc=90,lc=30,dc=700,uc=800,mc=520,pc=16,fc=50,gc=650,hc=10,yc=.015,bc="button, a, label, li, span, div, [role='button'], [role='radio']",wc=["visible","hidden","prerender","unloaded"],vc=["not_chat_page"];function xc(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,f=s.flatMap(e=>"experience"===e.token.kind?[{index:e.index,experience:e.token.value}]:[]),g=f.find(e=>(m<0||e.index>m)&&(p<0||e.index<p))?.experience??f[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,g)?g:"",education:u}}function Sc(e){return new Promise(t=>{setTimeout(t,e)})}function Rc(e){return{x:e.x,y:e.y}}function Ic(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function Cc(e){try{return"/web/chat/index"===new URL(e).pathname}catch{return e.includes("/web/chat/index")}}function kc(e){return e.trim().toLocaleLowerCase("zh-CN")}function _c(e){return e.replace(/\s+/g,"").trim().toLocaleLowerCase("zh-CN")}function Ac(e,t){const n=kc(e),r=kc(t);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))}function Ec(e){return"string"==typeof e?e:""}function Nc(e){return"number"==typeof e&&Number.isFinite(e)?e:0}function Mc(e){return"boolean"==typeof e&&e}function Pc(e){return Array.isArray(e)?e.map((e,t)=>{const n=Ic(e)?e:{};return{conversationId:Ec(n.conversationId),candidateId:Ec(n.candidateId),name:Ec(n.name),index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,position:Ec(n.position),hasUnread:Mc(n.hasUnread),unreadCount:"number"==typeof n.unreadCount&&Number.isInteger(n.unreadCount)?n.unreadCount:0,lastMessageTime:Ec(n.lastMessageTime),messagePreview:Ec(n.messagePreview)}}):[]}function Tc(e){return Array.isArray(e)?e.flatMap(e=>{if(!Ic(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 Oc(e){return Array.isArray(e)?e.map((e,t)=>{const n=Ic(e)?e:{},r=n.tags;return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,candidateId:Ec(n.candidateId),name:Ec(n.name),age:Ec(n.age),experience:Ec(n.experience),education:Ec(n.education),workStatus:Ec(n.workStatus),company:Ec(n.company),currentPosition:Ec(n.currentPosition),expectedLocation:Ec(n.expectedLocation),expectedPosition:Ec(n.expectedPosition),expectedSalary:Ec(n.expectedSalary),tags:Array.isArray(r)?r.map(e=>Ec(e)).filter(Boolean):[],buttonText:Ec(n.buttonText)}}):[]}function Bc(e){return Array.isArray(e)?e.map((e,t)=>{const n=Ic(e)?e:{};return{index:"number"==typeof n.index&&Number.isInteger(n.index)?n.index:t,value:Ec(n.value),label:Ec(n.label),isCurrent:Mc(n.isCurrent)}}):[]}function Dc(e){return Ic(e)?{found:Mc(e.found),isOpen:Mc(e.isOpen),currentLabel:Ec(e.currentLabel),currentValue:Ec(e.currentValue),options:Bc(e.options)}:{found:!1,isOpen:!1,currentLabel:"",currentValue:"",options:[]}}function $c(e){if(!Ic(e))return null;const t=Ec(e.conversationId),n=Ec(e.candidateId);return 0===t.length||0===n.length?null:{conversationId:t,candidateId:n,candidateName:Ec(e.candidateName)}}function jc(e){if(!Ic(e))return null;const t=Ec(e.candidateName);return t.length>0?{candidateName:t}:null}function Fc(e){return Array.isArray(e)?e.flatMap((e,t)=>{if(!Ic(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:Ec(e.content),time:Ec(e.time)}]}):[]}function Lc(e){const t=Ic(e)?e:{},n=t.tags;return{name:Ec(t.name),age:Ec(t.age),experience:Ec(t.experience),education:Ec(t.education),communicationPosition:Ec(t.communicationPosition),expectedJobText:Ec(t.expectedJobText),expectedSalary:Ec(t.expectedSalary),tags:Array.isArray(n)?n.map(e=>Ec(e)).filter(Boolean):[]}}function qc(e){const t=Ic(e)?e:{};return{selectedTarget:$c(t.selectedTarget),activePanel:jc(t.activePanel),candidateInfo:Lc(t.candidateInfo),messages:Fc(t.messages)}}function zc(e){return Ic(e)?{found:Mc(e.found),x:Nc(e.x),y:Nc(e.y)}:{found:!1,x:0,y:0}}function Vc(e){return Ic(e)&&"number"==typeof e.x&&"number"==typeof e.y?{x:e.x,y:e.y}:null}function Jc(e){if(Ic(e)&&"number"==typeof e.width&&"number"==typeof e.height)return{width:e.width,height:e.height}}function Uc(e){if(Ic(e)&&"number"==typeof e.x&&"number"==typeof e.y&&"number"==typeof e.width&&"number"==typeof e.height)return{x:e.x,y:e.y,width:e.width,height:e.height}}function Hc(e){return Ic(e)?{dialogVisible:!0===e.dialogVisible,iframeFound:!0===e.iframeFound,iframeVisible:!0===e.iframeVisible,canvasReady:!0===e.canvasReady}:{dialogVisible:!1,iframeFound:!1,iframeVisible:!1,canvasReady:!1}}function Wc(e,t,n){if(!Ic(e)||!0!==e.found)return{found:!1,error:"无法读取简历 canvas 状态"};const r=Uc(e.canvasRect);if(void 0===r)return{found:!1,error:"简历 canvas 坐标不完整"};const a=zs({recommendFrameRect:n,resumeFrameRect:Vc(e.resumeFrameRect),canvasRect:r}),o=Ic(t)?t:{},i=o.canvasSize,s=Ic(i)&&"number"==typeof i.width&&"number"==typeof i.height?{width:i.width,height:i.height}:{width:Math.round(r.width),height:Math.round(r.height)},c=o.dataUrl,l=o.dataUrlError;return{found:!0,screenshotArea:a,canvasSize:s,..."string"==typeof c&&c.startsWith("data:image/")?{dataUrl:c}:{},..."string"==typeof l?{dataUrlError:l}:{},...!0===o.blank?{blank:!0}:{}}}function Kc(e){return Ic(e)?{found:Mc(e.found),left:Nc(e.left),top:Nc(e.top)}:{found:!1,left:0,top:0}}function Gc(e){return Ic(e)?{found:Mc(e.found),cardSelector:Ec(e.cardSelector)||Ys,candidateId:Ec(e.candidateId),name:Ec(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:Mc(e.hasGreetButton),..."string"==typeof e.error?{error:e.error}:{}}:{found:!1,cardSelector:Ys,candidateId:"",name:"",hasGreetButton:!1}}function Yc(e){return Ic(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 Xc(e){return Ic(e)&&!0===e.ok?{ok:!0,current:Yc(e.current),trackLeft:Nc(e.trackLeft),trackTop:Nc(e.trackTop),trackWidth:Nc(e.trackWidth),trackHeight:Nc(e.trackHeight),minHandleX:Nc(e.minHandleX),minHandleY:Nc(e.minHandleY),maxHandleX:Nc(e.maxHandleX),maxHandleY:Nc(e.maxHandleY)}:{ok:!1,error:Ic(e)&&Ec(e.error)||"未找到年龄滑块"}}function Zc(e){return Ic(e)?{containerFound:Mc(e.containerFound),containerLabel:Ec(e.containerLabel),scrollTop:Nc(e.scrollTop),scrollHeight:Nc(e.scrollHeight),clientHeight:Nc(e.clientHeight),itemCount:Nc(e.itemCount),atStart:Mc(e.atStart),atEnd:Mc(e.atEnd)}:{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function Qc(e){return[e,...(e.childFrames??[]).flatMap(e=>Qc(e))]}function el(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 tl(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 nl(e){return wc.some(t=>t===e)}function rl(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 al(e,t,n){const r=t.filter(e=>lr(e.url,"zhipin")),a=r.find(t=>e.isNativePageSelected(t.targetId));if(n.requireChatPage){if(a&&Cc(a.url))return a;const e=r.filter(e=>Cc(e.url));if(1===e.length){const t=e[0];if(t)return t}if(a)throw new Error("Selected BOSS page is not a chat page; switch to chat page first.");if(e.length>1)throw new Error("Multiple BOSS chat pages found; select the target tab first.");throw new Error("No BOSS chat page found; switch to chat page first.")}if(a)return a;if(1===r.length){const e=r[0];if(e)return e}if(r.length>1)throw new Error("Multiple BOSS pages found; select the target tab first.");throw new Error("No BOSS page found.")}function ol(e,t){const n=t.filter(e=>lr(e.url,"zhipin")),r=n.find(t=>e.isNativePageSelected(t.targetId));return r||(1===n.length?n[0]:void 0)}async function il(e={},t={}){const n=t.ctxManager??pe(),r=t.runtime??me(),a=await n.listNativePages();if(!0!==e.skipRiskGate){const e=ol(n,a);e&&ws(e)}const o=al(n,a,e),i=await r.connectNativePage(o),s=new sl({target:o,controller:i});if(!0!==e.skipRiskGate)try{await s.assertNotRestricted()}catch(e){throw s.close(),e}return s}var sl=class{target;controller;mouse;recommendFrameContextId;recommendFrameContextFrameId;constructor(e){this.target=e.target,this.controller=e.controller,this.mouse=new oo(e.controller)}get targetId(){return this.target.targetId}async inspectPage(){return{...this.target,url:await this.url().catch(()=>this.target.url),title:await this.title().catch(()=>this.target.title)}}async assertNotRestricted(){let e;try{e=await this.url()}catch{throw new Error("无法读取当前页面地址,已中止后续操作(页面可能已跳转或失效)")}ws({url:e,title:await this.title().catch(()=>"")})}async url(){return await this.evaluateJson("location.href")}async title(){return await this.evaluateJson("document.title")}async waitForSelector(e,t=5e3){const n=Date.now();for(;Date.now()-n<t;){if(await this.evaluateJson(`document.querySelector(${JSON.stringify(e)}) !== null`).catch(()=>!1))return!0;await Sc(ec)}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 Cc(e)?{ok:!0,url:e}:{ok:!1,url:e,skippedReason:"not_chat_page",error:"当前 BOSS 页面不是沟通页,已跳过 reload;请先切换到沟通页。"}}async reload(e){await pa(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(Gs)}) !== null`)}async waitForChatSurface(e=8e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.isChatSurfaceOpen().catch(()=>!1))return!0;await Sc(ec)}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(Gs,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 Sc(ec)}for(let e=0;e<nc&&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 Sc(tc),await a())return!0;if(e.after<=1)break}for(let e=0;e<=nc&&Date.now()-n<t;e+=1){if(await a())return!0;if(e>=nc)break;const t=await this.scrollChatList("down").catch(()=>{});if(void 0===t||!t.ok||t.after<=t.before)break;await Sc(tc)}return await a()}async isRecommendSurfaceOpen(){const e=[Cs.recommend.iframe,'iframe[name="recommendFrame"]','iframe[src*="recommend"]'].join(", ");return await this.evaluateJson(`(() => {\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Qs)};\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 Sc(ec)}return!1}async resolveRecommendFrameContextId(){const e=await this.controller.getFrameTree().catch(()=>{});if(void 0===e)return;const t=Qc(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 Kc(await this.evaluateJson(`(() => {\n const iframe =\n document.querySelector(${JSON.stringify(Cs.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=zc(await this.evaluateJson(e).catch(()=>{}));if(t.found)return t;const n=zc(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(Rc(e),{...void 0!==t.motionObserver?{motionObserver:t.motionObserver}:{},...void 0!==t.preClickDelayMs?{preClickDelayMs:t.preClickDelayMs}:{},pressDurationMs:t.pressDurationMs??cc,settleMs:t.settleMs??sc}),!0)}async hasRecommendList(){if(!await this.isRecommendSurfaceOpen().catch(()=>!1))return!1;const e=`document.querySelector(${JSON.stringify(Zs)}) !== 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 Sc(ec)}else await Sc(ec);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=Dc(await this.evaluateJson(e).catch(()=>{}));return t.found?t:Dc(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 Sc(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 Sc(260),await this.selectAllFocusedText(),await Sc(160),await this.controller.dispatchKeyEvent({type:"rawKeyDown",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),await Sc(90),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Backspace",code:"Backspace",windowsVirtualKeyCode:8,nativeVirtualKeyCode:8}),0===e.length)return await Sc(500),!0;await Sc(350);for(const t of Array.from(e))await this.controller.insertText(t),await Sc(110);return await Sc(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=_c(e.jobName),a=t.filter(e=>_c(e.label)===r);if(a.length>0)return n(a);return n(t.filter(e=>_c(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=_c(e.jobName),r=_c(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 Sc(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 Sc(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 Sc(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 Sc(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?Cs.nav.chatLink:Cs.nav.recommendLink,r=zc(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(Cs.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=is(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 Sc(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 Sc(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 Sc(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=el(r);if(a.length>=t||e===n)return a;if(!(await this.scrollChatList()).ok)return a;await Sc(tc)}return el(r)}async openChat(e){if(await $r(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?nc: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 Sc(sc);let n=await this.waitForNativeChatReady(t);if(!n.ready){await this.clickChatCandidate(t,{...void 0!==e.motionObserver?{motionObserver:e.motionObserver}:{}})&&(await Sc(sc),n=await this.waitForNativeChatReady(t))}return n.ready?{...t,found:!0}:{...t,found:!1,error:rl(t,n)}},a=async(n,a)=>{const o=[];for(let i=0;i<=a;i+=1){o.push(...await this.readVisibleChatCandidates());const s=el(o),c=!0!==e.preferUnread||t?rs(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 Sc(tc)}},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 Sc(tc)}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 $c(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 jc(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 Sc(ec)}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 qc(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 = (${xc.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=Gc(await this.evaluateJson(n).catch(()=>{}));if(r.found)return r;const a=Gc(await this.evaluateRecommendFrameJson(n));return a.found?a:r}async clickRecommendGreet(e,t={}){const n=await this.inspectRecommendCard(e);if(!n.found||!n.hasGreetButton)return{...n,clicked:!1,...n.found||void 0!==n.error?{}:{error:"索引超出范围"},...n.found&&!n.hasGreetButton?{error:"未找到打招呼按钮"}:{}};const r=await this.resolveRecommendClickTarget(this.buildRecommendGreetClickExpression(Math.max(0,Math.floor(e)))),a=await this.dispatchNativeClick(r,t);return{...n,clicked:a,...a?{}:{error:"未能点击打招呼按钮"}}}async clickRecommendCardSurface(e,t={}){const n=await this.resolveRecommendClickTarget(this.buildRecommendCardOpenClickExpression(Math.max(0,Math.floor(e))));return await this.dispatchNativeClick(n,t)}async waitForResumeDialog(e=1e4){const t=Date.now();let n=await this.readResumeDialogState();for(;!n.canvasReady&&Date.now()-t<e;)await Sc(ec),n=await this.readResumeDialogState();return n}async readResumeCanvasGeometry(){const e=this.buildResumeCanvasGeometryExpression();let t=!1,n=await this.evaluateJson(e).catch(()=>{});if(!Ic(n)||!0!==n.found){const r=await this.evaluateRecommendFrameJson(e);void 0!==r&&(n=r,t=!0)}if(!Ic(n)||!0!==n.found){const e=Ic(n)?n.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const r=Uc(n.canvasRect),a=Jc(n.canvasSize);if(void 0===r||void 0===a)return{found:!1,error:"简历 canvas 坐标读取失败"};const o=Vc(n.resumeFrameRect),i=t?await this.readRecommendFrameOffset():void 0;return{found:!0,screenshotArea:zs({recommendFrameRect:void 0!==i&&i.found?{x:i.left,y:i.top}:null,resumeFrameRect:o??null,canvasRect:r}),canvasSize:a}}buildResumeCanvasGeometryExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(Ls)};\n const canvasSelector = ${JSON.stringify(qs)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n return {\n found: true,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n },\n canvasSize: { width: canvas.width, height: canvas.height }\n };\n })()`}async captureResumeCanvas(e){await Sc(uc);const t=this.buildResumeStitchInitExpression();let n=!1,r=await this.evaluateJson(t).catch(()=>{});if(!Ic(r)||!0!==r.found){const e=await this.evaluateRecommendFrameJson(t);void 0!==e&&(r=e,n=!0)}if(!Ic(r)||!0!==r.found){const e=Ic(r)?r.error:void 0;return{found:!1,error:"string"==typeof e?e:"未找到简历 canvas"}}const a=async e=>n?await this.evaluateRecommendFrameJson(e):await this.evaluateJson(e).catch(()=>{}),o="number"==typeof r.maxScrollTotal&&r.maxScrollTotal>0?r.maxScrollTotal:0,i=this.buildResumeStitchStepExpression();for(let t=0;t<lc;t++){await Sc(dc);const n=await a(i);if(!Ic(n)||!0!==n.ok||!0===n.done)break;if(void 0!==e){const r="number"==typeof n.scrollTop?n.scrollTop:0;await Promise.resolve(e({round:t,scrolledPx:r,totalPx:o})).catch(()=>{})}}const s=await a(this.buildResumeStitchExportExpression()),c=n?await this.readRecommendFrameOffset():void 0;return Wc(r,s,void 0!==c&&c.found?{x:c.left,y:c.top}:null)}async captureViewportClip(e,t=1){return await this.controller.captureScreenshot({format:"png",clip:{x:e.x,y:e.y,width:e.width,height:e.height,scale:t},timeoutMs:15e3})}async readResumeDialogClipArea(){const e=this.buildResumeDialogRectExpression(),t=Uc(await this.evaluateJson(e).catch(()=>{}));if(void 0!==t)return t;const n=Uc(await this.evaluateRecommendFrameJson(e));if(void 0===n)return;const r=await this.readRecommendFrameOffset();return r.found?{x:Math.round(n.x+r.left),y:Math.round(n.y+r.top),width:Math.round(n.width),height:Math.round(n.height)}:n}async closeResumeDialog(e={}){const t=await this.resolveRecommendClickTarget(this.buildResumeCloseTargetExpression());return t.found&&await this.dispatchNativeClick(t,e)&&await this.waitForResumeDialogGone()?{closed:!0,method:"close-button"}:(await this.controller.dispatchKeyEvent({type:"keyDown",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.controller.dispatchKeyEvent({type:"keyUp",key:"Escape",code:"Escape",windowsVirtualKeyCode:27}),await this.waitForResumeDialogGone()?{closed:!0,method:"escape"}:{closed:!1,error:"简历弹窗未关闭"})}async readResumeDialogState(){const e=this.buildResumeDialogStateExpression(),t=Hc(await this.evaluateJson(e).catch(()=>{}));if(t.iframeFound)return t;const n=Hc(await this.evaluateRecommendFrameJson(e));return{dialogVisible:t.dialogVisible||n.dialogVisible,iframeFound:n.iframeFound,iframeVisible:n.iframeVisible,canvasReady:n.canvasReady}}async waitForResumeDialogGone(e=4e3){const t=Date.now();for(;Date.now()-t<e;){const e=await this.readResumeDialogState();if(!(e.iframeFound&&e.iframeVisible||e.dialogVisible))return!0;await Sc(ec)}return!1}async exchangeWechat(e={}){const t=zc(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=zc(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 Sc(1800);const r=await this.readWechatNumber();return{success:!0,exchanged:!0,...void 0!==r?{wechatNumber:r}:{}}}async sendChatReply(e,t={}){const n=zc(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 Sc(250);const r=zc(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 Sc(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(!Ps(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(Ps(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=tl(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<oc;t+=1){await Sc(ac);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>=ic){l="no-new-items";break}const e=await this.scrollSurface("recommend-list",{direction:"down",steps:1,settleMs:rc});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 Tc(await this.evaluateJson(`(() => {\n const limit = ${Vs};\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([Cs.username.primary,...Cs.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(Ys)};\n const fallbackSelector = ${JSON.stringify(Xs)};\n const iframe = document.querySelector(${JSON.stringify(Cs.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Qs)};\n const parseCandidateProfileTokens = ${xc.toString()};\n const hasRecommendUrl = recommendUrlMarkers.some((marker) => href.includes(marker));\n if (!hasRecommendUrl && href.includes("/web/chat/index")) {\n return {\n found: false,\n cardSelector: primarySelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "推荐列表未加载"\n };\n }\n\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cardSelector = primaryCards.length > 0 ? primarySelector : fallbackSelector;\n const cards = primaryCards.length > 0 ? primaryCards : Array.from(root.querySelectorAll(cardSelector));\n if (cards.length <= index) {\n return {\n found: false,\n cardSelector,\n candidateId: "",\n name: "",\n hasGreetButton: false,\n error: "索引超出范围"\n };\n }\n\n const item = cards[index];\n const splitParts = (text) => (text ?? "")\n .split(/[丨·|]/)\n .map((part) => part.trim())\n .filter(Boolean);\n const candidateId =\n item.getAttribute("data-geek") ??\n item.querySelector("[data-geek]")?.getAttribute("data-geek") ??\n "";\n const name = item.querySelector(".name")?.textContent?.trim() ?? "";\n let age = "";\n let experience = "";\n let education = "";\n let workStatus = "";\n const baseInfoEl = item.querySelector(".base-info.join-text-wrap, .base-info");\n if (baseInfoEl) {\n let textParts = Array.from(baseInfoEl.querySelectorAll(":scope > *"))\n .map((child) => child.textContent?.trim() ?? "")\n .filter(Boolean);\n\n if (textParts.length <= 1) {\n textParts = splitParts(baseInfoEl.textContent?.trim() ?? "");\n }\n\n const profile = parseCandidateProfileTokens(textParts);\n age = profile.age;\n experience = profile.experience;\n education = profile.education;\n for (const part of textParts) {\n if (!workStatus && /(在职|离职|在校)/.test(part)) {\n workStatus = part;\n }\n }\n }\n const workExpEl =\n item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.work-exps .content");\n const workParts = splitParts(workExpEl?.textContent?.trim() ?? "");\n const company = workParts[0] ?? "";\n const currentPosition = workParts[1] ?? "";\n let expectedLocation = "";\n let expectedPosition = "";\n const expectRow = item.querySelector(".row-flex:not(.geek-desc)");\n if (expectRow) {\n const labelText = expectRow.querySelector(".label")?.textContent ?? "";\n const contentEl = expectRow.querySelector(".content");\n if ((labelText.includes("期望") || labelText.includes("最近关注")) && contentEl) {\n const parts = splitParts(contentEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n if (!expectedLocation) {\n const expectEl =\n item.querySelector(".timeline-wrap.expect .content.join-text-wrap") ??\n item.querySelector(".timeline-wrap.expect .content");\n if (expectEl) {\n const parts = splitParts(expectEl.textContent?.trim() ?? "");\n expectedLocation = parts[0] ?? "";\n expectedPosition = parts[1] ?? "";\n }\n }\n const greetButton =\n item.querySelector("button.btn.btn-greet") ??\n item.querySelector("button.btn-greet") ??\n item.querySelector(".btn-greet") ??\n item.querySelector(".op-btn");\n const rect = greetButton?.getBoundingClientRect();\n const hasGreetButton = Boolean(rect && rect.width > 0 && rect.height > 0);\n\n return {\n found: true,\n cardSelector,\n candidateId,\n name,\n age,\n experience,\n education,\n workStatus,\n company,\n currentPosition,\n expectedLocation,\n expectedPosition,\n expectedSalary: item.querySelector(".salary-wrap")?.textContent?.trim() ?? "",\n hasGreetButton\n };\n })()`}buildRecommendCardTargetClickExpression(e,t){return`(() => {\n const index = ${JSON.stringify(e)};\n const primarySelector = ${JSON.stringify(Ys)};\n const fallbackSelector = ${JSON.stringify(Xs)};\n const iframe = document.querySelector(${JSON.stringify(Cs.recommend.iframe)});\n const root = iframe?.contentDocument ?? document;\n const primaryCards = Array.from(root.querySelectorAll(primarySelector));\n const cards =\n primaryCards.length > 0\n ? primaryCards\n : Array.from(root.querySelectorAll(fallbackSelector));\n const item = cards[index];\n if (!item) return { found: false, x: 0, y: 0 };\n const target = ${t};\n if (!target) return { found: false, x: 0, y: 0 };\n target.scrollIntoView({ block: "center", inline: "center" });\n const rect = target.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { found: false, x: 0, y: 0 };\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n })()`}buildRecommendGreetClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,'item.querySelector("button.btn.btn-greet") ?? item.querySelector("button.btn-greet") ?? item.querySelector(".btn-greet") ?? item.querySelector(".op-btn")')}buildRecommendCardOpenClickExpression(e){return this.buildRecommendCardTargetClickExpression(e,`item.querySelector(${JSON.stringify(Ds)}) ?? item`)}buildResumeDialogStateExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(Ls)};\n const dialogSelector = ${JSON.stringify($s)};\n const canvasSelector = ${JSON.stringify(qs)};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n\n let dialogVisible = false;\n let iframeFound = false;\n let iframeVisible = false;\n let canvasReady = false;\n const dialog = document.querySelector(dialogSelector);\n if (dialog && visible(dialog)) dialogVisible = true;\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (resumeIframe) {\n iframeFound = true;\n iframeVisible = visible(resumeIframe);\n const resumeDoc = resumeIframe.contentDocument;\n const canvas = resumeDoc ? resumeDoc.querySelector(canvasSelector) : null;\n if (canvas && canvas.width > 0 && canvas.height > 0) canvasReady = true;\n }\n return { dialogVisible, iframeFound, iframeVisible, canvasReady };\n })()`}buildResumeStitchInitExpression(){return`(() => {\n const resumeIframeSelector = ${JSON.stringify(Ls)};\n const canvasSelector = ${JSON.stringify(qs)};\n const resumeIframe = document.querySelector(resumeIframeSelector);\n if (!resumeIframe) return { found: false, error: "未找到简历 iframe" };\n const resumeDoc = resumeIframe.contentDocument;\n if (!resumeDoc) return { found: false, error: "无法访问简历 iframe 内容" };\n const canvas = resumeDoc.querySelector(canvasSelector);\n if (!canvas) return { found: false, error: "简历 canvas 未加载" };\n\n const scrollers = [];\n let node = resumeIframe.parentElement;\n while (node) {\n if (node.scrollHeight > node.clientHeight + 20) scrollers.push(node);\n node = node.parentElement;\n }\n const rootScroller = document.scrollingElement;\n if (\n rootScroller &&\n rootScroller.scrollHeight > rootScroller.clientHeight + 20 &&\n !scrollers.includes(rootScroller)\n ) {\n scrollers.push(rootScroller);\n }\n\n const iframeRect = resumeIframe.getBoundingClientRect();\n const canvasRect = canvas.getBoundingClientRect();\n const dpr = canvasRect.width > 0 ? canvas.width / canvasRect.width : 1;\n const maxScrollTotal = scrollers.reduce(\n (sum, el) => sum + Math.max(0, el.scrollHeight - el.clientHeight),\n 0\n );\n const totalHeightPx = Math.max(\n Math.round((maxScrollTotal + canvasRect.height) * dpr),\n canvas.height\n );\n\n const stitched = resumeDoc.createElement("canvas");\n stitched.width = canvas.width;\n stitched.height = totalHeightPx;\n const context = stitched.getContext("2d");\n if (!context) return { found: false, error: "无法创建拼接 canvas 上下文" };\n context.fillStyle = "#ffffff";\n context.fillRect(0, 0, stitched.width, stitched.height);\n\n for (const el of scrollers) el.scrollTop = 0;\n globalThis.__rollResumeStitch = {\n stitched,\n context,\n canvas,\n scrollers,\n dpr,\n maxDrawnY: 0,\n scrollerIndex: 0,\n effectiveOffset: 0,\n pending: null,\n pendingFpBase: "",\n readFp: function () {\n try {\n if (!this.fpContext) {\n const fpCanvas = this.canvas.ownerDocument.createElement("canvas");\n fpCanvas.width = 32;\n fpCanvas.height = 32;\n this.fpContext = fpCanvas.getContext("2d", { willReadFrequently: true });\n }\n const fpContext = this.fpContext;\n if (!fpContext) return "noctx";\n fpContext.fillStyle = "#ffffff";\n fpContext.fillRect(0, 0, 32, 32);\n fpContext.drawImage(this.canvas, 0, 0, 32, 32);\n const data = fpContext.getImageData(0, 0, 32, 32).data;\n let hash = 0;\n for (let index = 0; index < data.length; index += 4) {\n hash = ((hash * 31 + data[index] + data[index + 1] + data[index + 2]) >>> 0);\n }\n return String(hash);\n } catch (error) {\n return "err";\n }\n }\n };\n return {\n found: true,\n needScroll: maxScrollTotal > 4,\n maxScrollTotal,\n resumeFrameRect: { x: iframeRect.x, y: iframeRect.y },\n canvasRect: {\n x: canvasRect.x,\n y: canvasRect.y,\n width: canvasRect.width,\n height: canvasRect.height\n }\n };\n })()`}buildResumeStitchStepExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n const fingerprint = state.readFp();\n\n if (state.pending) {\n const pendingScroller = state.scrollers[state.pending.index];\n const realDelta = pendingScroller\n ? pendingScroller.scrollTop - state.pending.before\n : 0;\n if (realDelta <= 2) {\n state.scrollerIndex = state.pending.index + 1;\n } else if (fingerprint !== state.pendingFpBase) {\n state.effectiveOffset += realDelta;\n } else {\n if (pendingScroller) pendingScroller.scrollTop = state.pending.before;\n state.scrollerIndex = state.pending.index + 1;\n }\n state.pending = null;\n }\n\n const drawY = Math.round(state.effectiveOffset * state.dpr);\n const drawnBottom = drawY + state.canvas.height;\n if (drawnBottom > state.stitched.height) {\n const grown = state.stitched.ownerDocument.createElement("canvas");\n grown.width = state.stitched.width;\n grown.height = drawnBottom;\n const grownContext = grown.getContext("2d");\n if (!grownContext) return { ok: false, error: "无法扩容拼接 canvas" };\n grownContext.fillStyle = "#ffffff";\n grownContext.fillRect(0, 0, grown.width, grown.height);\n grownContext.drawImage(state.stitched, 0, 0);\n state.stitched = grown;\n state.context = grownContext;\n }\n state.context.drawImage(state.canvas, 0, drawY);\n if (drawnBottom > state.maxDrawnY) state.maxDrawnY = drawnBottom;\n\n const scroller = state.scrollers[state.scrollerIndex];\n if (scroller) {\n const before = scroller.scrollTop;\n scroller.scrollTop = before + scroller.clientHeight;\n state.pending = { index: state.scrollerIndex, before };\n state.pendingFpBase = fingerprint;\n }\n return { ok: true, done: !scroller, scrollTop: state.effectiveOffset };\n })()'}buildResumeStitchExportExpression(){return'(() => {\n const state = globalThis.__rollResumeStitch;\n if (!state) return { ok: false, error: "stitch 未初始化" };\n delete globalThis.__rollResumeStitch;\n let output = state.stitched;\n const finalHeight = Math.min(\n state.stitched.height,\n Math.max(state.maxDrawnY, state.canvas.height)\n );\n if (finalHeight < state.stitched.height) {\n const trimmed = state.stitched.ownerDocument.createElement("canvas");\n trimmed.width = state.stitched.width;\n trimmed.height = finalHeight;\n const trimContext = trimmed.getContext("2d");\n if (trimContext) {\n trimContext.drawImage(state.stitched, 0, 0);\n output = trimmed;\n }\n }\n let blank = true;\n const outputContext = output.getContext("2d");\n if (outputContext) {\n try {\n const stepX = Math.max(1, Math.floor(output.width / 16));\n const stepY = Math.max(1, Math.floor(output.height / 16));\n outer: for (let y = 0; y < output.height; y += stepY) {\n for (let x = 0; x < output.width; x += stepX) {\n const pixel = outputContext.getImageData(x, y, 1, 1).data;\n if (pixel[3] > 8 && (pixel[0] < 246 || pixel[1] < 246 || pixel[2] < 246)) {\n blank = false;\n break outer;\n }\n }\n }\n } catch (error) {\n blank = false;\n }\n }\n let dataUrl = null;\n let dataUrlError = null;\n try {\n dataUrl = output.toDataURL("image/png");\n } catch (error) {\n dataUrlError = error instanceof Error ? error.message : String(error);\n }\n for (const el of state.scrollers) el.scrollTop = 0;\n return {\n ok: true,\n dataUrl,\n dataUrlError,\n blank,\n canvasSize: { width: output.width, height: output.height }\n };\n })()'}buildResumeDialogRectExpression(){return`(() => {\n const dialogSelector = ${JSON.stringify($s)};\n let best = null;\n let bestArea = 0;\n for (const element of Array.from(document.querySelectorAll(dialogSelector))) {\n const rect = element.getBoundingClientRect();\n const area = rect.width * rect.height;\n if (rect.width > 0 && rect.height > 0 && area > bestArea) {\n best = rect;\n bestArea = area;\n }\n }\n if (!best) return { found: false };\n return {\n found: true,\n x: Math.max(0, best.x),\n y: Math.max(0, best.y),\n width: best.width,\n height: best.height\n };\n })()`}buildResumeCloseTargetExpression(){return`(() => {\n const scopeSelector = ${JSON.stringify(js)};\n const closeSelectors = ${JSON.stringify([...Fs])};\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const scopes = Array.from(document.querySelectorAll(scopeSelector)).filter(visible);\n for (const scope of scopes) {\n for (const selector of closeSelectors) {\n for (const element of Array.from(scope.querySelectorAll(selector))) {\n if (!visible(element)) continue;\n element.scrollIntoView({ block: "center", inline: "center" });\n const rect = element.getBoundingClientRect();\n return {\n found: true,\n x: Math.round(rect.left + rect.width / 2),\n y: Math.round(rect.top + rect.height / 2)\n };\n }\n }\n }\n return { found: false, x: 0, y: 0 };\n })()`}async waitForWechatExchangeDialog(e=5e3){const t=Date.now();for(;Date.now()-t<e;){if(await this.evaluateJson('(() => {\n const visible = (element) => {\n const rect = element.getBoundingClientRect();\n return rect.width > 0 && rect.height > 0;\n };\n const tooltip = document.querySelector(".exchange-tooltip");\n if (tooltip && visible(tooltip)) return true;\n for (const element of Array.from(document.querySelectorAll("div, section, aside"))) {\n const text = element.textContent ?? "";\n if (\n text.includes("交换微信") &&\n element.querySelector(".boss-btn-primary, .boss-btn") &&\n visible(element)\n ) {\n return true;\n }\n }\n return false;\n })()').catch(()=>!1))return!0;await Sc(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(`${Cs.recommend.filterButton}, ${Ys}, ${Cs.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 Sc(ec)}return!1}async isRecommendFilterPanelVisible(){const e=`(() => {\n const panel = document.querySelector(${JSON.stringify(Cs.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(Cs.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 Sc(ec)}}await Sc(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(Cs.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const optionLabel = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(bc)};\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 Sc(ec)}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 Sc(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 Sc(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(Cs.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(pc)} +\n Math.max(0, Math.min(1, ratio)) *\n (${JSON.stringify(fc)} -\n ${JSON.stringify(pc)})\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=Yc(await this.evaluateJson(e).catch(()=>{}));return void 0!==t.ageMin||void 0!==t.ageMax?t:Yc(await this.evaluateRecommendFrameJson(e))}async resolveNativeAgeSlider(){const e=`(() => {\n const panelSelector = ${JSON.stringify(Cs.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=Xc(await this.evaluateJson(e).catch(()=>{}));if(t.ok)return t;const n=Xc(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??cc})}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 Sc(gc),!0}estimateAgeRatio(e){return Math.max(0,Math.min(1,(e-pc)/(fc-pc)))}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+yc),s="min"===e?Math.max(0,o-yc):1,c=this.clampRatio(this.estimateAgeRatio(t),i,s);for(let r=0;r<hc;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??pc,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(Cs.recommend.filterPanel)};\n const rowLabel = ${JSON.stringify(e)};\n const fallback = ${JSON.stringify(t)};\n const clickableSelector = ${JSON.stringify(bc)};\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(Es.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(Cs.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 Sc(300),!0)}async clickRecommendFilterSubmit(e){const t=await this.resolveRecommendClickTarget(`(() => {\n const panelSelector = ${JSON.stringify(Cs.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 Sc(600),!0;await Sc(ec)}return!1}async readRecommendFilterButtonText(){const e=`(() => {\n const element = document.querySelector(${JSON.stringify(Cs.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 Pc(await this.evaluateJson(`(() => {\n const items = Array.from(document.querySelectorAll(${JSON.stringify(Ks)}));\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&&Ac(e.name,n.candidateName);if(r&&a)return{ready:!0};await Sc(ec)}const[r,a,o]=await Promise.all([this.evaluateJson("document.visibilityState").catch(()=>{}),this.readSelectedChatTarget().catch(()=>null),this.readActiveChatPanel().catch(()=>null)]);return{ready:!1,visibilityState:nl(r)?r:"unknown",selectedConversationId:a?.conversationId??"",activePanelCandidateName:o?.candidateName??""}}async clickChatCandidate(e,t={}){const n=zc(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(Ks)}));\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(Cs.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Qs)};\n const parseCandidateProfileTokens = ${xc.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(Ys)}));\n if (items.length === 0 && (hasRecommendUrl || iframe || hasRecommendShell)) {\n items = Array.from(root.querySelectorAll(${JSON.stringify(Xs)}));\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=Oc(await this.evaluateJson(e));if(t.length>0)return t;return Oc(await this.evaluateRecommendFrameJson(e))}async inspectSurface(e){const t=is(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(Cs.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Qs)};\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=Zc(await this.evaluateJson(n));if("recommend-list"!==e||r.itemCount>0)return r;const a=Zc(await this.evaluateRecommendFrameJson(n));return a.itemCount>0?a:r}async scrollSurfaceWithWheel(e,t,n){const r=is(e);await $r(this);const a=zc(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(Cs.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(Rc(a)),await this.controller.dispatchMouseEvent({type:"mouseWheel",x:a.x,y:a.y,buttons:0,deltaX:0,deltaY:"up"===t?-(n??mc):n??mc}),await Sc(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=is(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(Cs.recommend.iframe)});\n const href = location.href;\n const recommendUrlMarkers = ${JSON.stringify(Qs)};\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=Zc(await this.evaluateJson(a));if("recommend-list"!==e||o.itemCount>0)return o;const i=Zc(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)}}},cl=["native","native-watch","native-ws-connect","native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable","native-runtime-enable","native-evaluate-url","native-dom-read","resume-canvas","browser-attach","page-attach","network-watch","page-evaluate","detector-fingerprint","storage-summary"],ll=[...cl,"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"],dl=Qi.enum(["localStorage","sessionStorage"]),ul=Qi.enum(["array","object","string","number","boolean","null"]),ml=Qi.enum(["empty","json","string"]),pl=Qi.enum(cl),fl=Qi.enum(ll),gl=Qi.enum(["request","response"]),hl=Qi.enum(["apm-action-log","device-action-report","boss-risk-report","zhipin-security"]),yl=Qi.object({area:dl,key:Qi.string(),valueLength:Qi.number().int().nonnegative(),valueKind:ml,jsonKind:ul.optional(),jsonTopLevelKeys:Qi.array(Qi.string()).optional(),jsonArrayLength:Qi.number().int().nonnegative().optional(),numericFields:Qi.record(Qi.number()).optional(),booleanFields:Qi.record(Qi.boolean()).optional(),arrayLengths:Qi.record(Qi.number().int().nonnegative()).optional()}),bl=Qi.object({name:Qi.string(),domain:Qi.string(),path:Qi.string(),expires:Qi.string(),valueLength:Qi.number().int().nonnegative(),httpOnly:Qi.boolean(),secure:Qi.boolean(),sameSite:Qi.string().optional()}),wl=Qi.object({before:Qi.number().optional(),after:Qi.number().optional(),delta:Qi.number().optional()}),vl=Qi.object({before:Qi.boolean().optional(),after:Qi.boolean().optional()}),xl=Qi.object({before:Qi.number().int().nonnegative().optional(),after:Qi.number().int().nonnegative().optional(),delta:Qi.number().int().optional()}),Sl=Qi.object({area:dl,key:Qi.string(),beforePresent:Qi.boolean(),afterPresent:Qi.boolean(),numericDeltas:Qi.record(wl).optional(),booleanChanges:Qi.record(vl).optional(),arrayLengthDeltas:Qi.record(xl).optional()}),Rl=Qi.object({url:Qi.string(),title:Qi.string(),visibilityState:Qi.string(),hasFocus:Qi.boolean()}),Il=Qi.object({navigatorWebdriver:Qi.boolean().optional(),userAgentContainsHeadless:Qi.boolean(),languagesLength:Qi.number().int().nonnegative(),pluginsLength:Qi.number().int().nonnegative(),hasChromeRuntime:Qi.boolean(),permissionQueryIsNative:Qi.boolean().optional(),hasPlaywrightBinding:Qi.boolean(),hasPwInitScripts:Qi.boolean(),cdcKeys:Qi.array(Qi.string()),webdriverKeys:Qi.array(Qi.string()),automationLikeWindowKeys:Qi.array(Qi.string())}),Cl=Qi.object({rootNodeId:Qi.number().int(),rootNodeName:Qi.string(),childNodeCount:Qi.number().int().nonnegative().optional(),bodyTextLength:Qi.number().int().nonnegative().optional(),elementCount:Qi.number().int().nonnegative().optional()}),kl=Qi.object({type:Qi.literal("mouseMoved"),x:Qi.number(),y:Qi.number()}),_l=Qi.object({targetId:Qi.string(),websocketUrlAvailable:Qi.boolean(),connected:Qi.boolean(),pageBroughtToFront:Qi.boolean().optional(),runtimeEnabled:Qi.boolean().optional(),evaluate:Rl.optional(),dom:Cl.optional(),input:kl.optional()}),Al=Qi.object({phase:pl,success:Qi.boolean(),durationMs:Qi.number().int().nonnegative(),error:Qi.string().optional()}),El=Qi.object({phase:fl,capturedAt:Qi.string(),targetFound:Qi.boolean(),page:Zi.optional(),urlChangedFromPrevious:Qi.boolean(),titleChangedFromPrevious:Qi.boolean(),previousUrl:Qi.string().optional(),currentUrl:Qi.string().optional(),previousTitle:Qi.string().optional(),currentTitle:Qi.string().optional()}),Nl=Qi.object({kind:gl,reason:hl,capturedAt:Qi.string(),url:Qi.string(),method:Qi.string().optional(),resourceType:Qi.string().optional(),status:Qi.number().int().optional()}),Ml=Qi.object({capturedAt:Qi.string(),url:Qi.string()}),Pl=Qi.object({phase:pl.default("native").describe("诊断阶段。默认 native 只枚举原生 CDP target;native-* 阶段使用原生 CDP page WebSocket;resume-canvas 只读探测简历弹窗与 canvas 就绪状态和坐标;browser-attach 及更深阶段才会使用 Playwright attach。"),targetPageId:Qi.string().optional().describe("可选:通过 list_pages 或本工具 native 阶段看到的 BOSS 页面 pageId/targetId。"),watchMs:Qi.number().int().min(500).max(1e4).default(3e3).describe("native-watch / browser-attach 后置观察 / network-watch / storage-summary 内部等待窗口,单位毫秒。"),networkEventLimit:Qi.number().int().min(1).max(100).default(30).describe("network-watch 最多返回的相关 request/response 事件数。")}),Tl=Qi.object({success:Qi.boolean(),requestedPhase:pl,mode:Qi.string(),nativePages:Qi.array(Zi),targetPage:Zi.optional(),browserAttached:Qi.boolean(),pageAttached:Qi.boolean(),nativeTimeline:Qi.array(El),networkEvents:Qi.array(Nl).optional(),navigationEvents:Qi.array(Ml).optional(),nativeCdp:_l.optional(),resumeCanvas:Qi.object({dialogVisible:Qi.boolean(),iframeFound:Qi.boolean(),iframeVisible:Qi.boolean(),canvasReady:Qi.boolean(),screenshotArea:Qi.object({x:Qi.number(),y:Qi.number(),width:Qi.number(),height:Qi.number()}).optional(),canvasSize:Qi.object({width:Qi.number(),height:Qi.number()}).optional(),error:Qi.string().optional()}).optional(),evaluate:Rl.optional(),detectorFingerprint:Il.optional(),storage:Qi.object({localStorage:Qi.array(yl),sessionStorage:Qi.array(yl),cookies:Qi.array(bl),counterDiffs:Qi.array(Sl)}).optional(),phases:Qi.array(Al),warnings:Qi.array(Qi.string())}),Ol=["_AEG_CNT","_ZP_CNT_","__local__sec__store___"],Bl=new Set(Ol),Dl=["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"],$l=["native-page-bring-front","native-evaluate-url-no-runtime-enable","native-dom-read-no-runtime-enable","native-input-move-no-runtime-enable"],jl={"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 Fl(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 Ll(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function ql(e){return Dl.includes(e)}function zl(e){return $l.includes(e)}function Vl(e){try{return JSON.parse(e)}catch{return}}function Jl(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 Ul(e){return new Promise(t=>{setTimeout(t,e)})}function Hl(e){return Bl.has(e)}function Wl(e,t){if(0===t.value.length)return{area:e,key:t.key,valueLength:0,valueKind:"empty"};const n=Vl(t.value);if(void 0===n)return{area:e,key:t.key,valueLength:t.value.length,valueKind:"string"};const r=Fl(n),a=Ll(n)&&Hl(t.key)?Jl(n):{};return{area:e,key:t.key,valueLength:t.value.length,valueKind:"json",jsonKind:r,...Ll(n)?{jsonTopLevelKeys:Object.keys(n)}:{},...Array.isArray(n)?{jsonArrayLength:n.length}:{},...a}}function Kl(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 Gl(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 Yl(e,t,n){if(void 0!==t){const r=e.find(e=>e.targetId===t);return r?lr(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=>lr(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 Xl(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:ur(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 Zl(e,t,n,r){const a=await e.listNativePages();return Xl(e,t,void 0!==n?a.find(e=>e.targetId===n):a.filter(e=>lr(e.url,"zhipin"))[0],r)}async function Ql(e,t,n,r,a){try{const o=t[t.length-1],i=await Zl(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 ed(e,t,n,r,a,o){const i=t.length,s=Date.now();for(;;){await Ql(e,t,n,r,o);const i=a-(Date.now()-s);if(i<=0)break;await Ul(Math.min(500,i))}return t.slice(i).some(e=>e.urlChangedFromPrevious)}function td(e){if(!Ll(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 nd(e,t){if(!Ll(e))throw new Error("Native CDP DOM.getDocument did not return an object.");const n=e.root;if(!Ll(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=Ll(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 rd(e){return td(await e.evaluateJson("(() => ({\n url: location.href,\n title: document.title,\n visibilityState: document.visibilityState,\n hasFocus: document.hasFocus()\n }))()"))}async function ad(e){return nd(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 od(e){return nd(await e.getDocument({depth:1,pierce:!1}),void 0)}async function id(e){return await e.bringToFront(),!0}async function sd(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 cd(e,t,n,r,a,o){await Ql(e,t,n,r,o);const i=await ed(e,t,jl[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 ld(e,t,n,r){switch(e){case"native-page-bring-front":{const a=await Gl(e,async()=>await id(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 Gl(e,async()=>await rd(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 Gl(e,async()=>await od(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 Gl(e,async()=>await sd(t));return n.push(a.phaseResult),void 0!==a.result&&(r.input=a.result),a.phaseResult}}}async function dd(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 Gl("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 cd(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(zl(e.requestedPhase))return await ld(e.requestedPhase,o,e.phases,t),i=await cd(e.ctxManager,e.nativeTimeline,e.requestedPhase,e.targetPageId,e.watchMs,e.warnings),{summary:t,triggeredNavigation:i};const s=await Gl("native-runtime-enable",async()=>await o.unsafeEnableRuntimeForDiagnostics());if(e.phases.push(s.phaseResult),t.runtimeEnabled=s.phaseResult.success,i=await cd(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 Gl("native-evaluate-url",async()=>await rd(o));if(e.phases.push(c.phaseResult),void 0!==c.result&&(t.evaluate=c.result),i=await cd(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 Gl("native-dom-read",async()=>await ad(o));return e.phases.push(l.phaseResult),void 0!==l.result&&(t.dom=l.result),i=await cd(e.ctxManager,e.nativeTimeline,"native-dom-read",e.targetPageId,e.watchMs,e.warnings),l.phaseResult.success,{summary:t,triggeredNavigation:i}}finally{r?.close()}}function ud(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 md(e,t,n){const r=[],a=[],o=e=>{r.length<n&&r.push(e)},i=e=>{const t=e.url(),n=ud(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=ud(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 Ul(t)}finally{e.off("request",i),e.off("response",s),e.off("framenavigated",c)}return{networkEvents:r,navigationEvents:a}}function pd(e,t){const n=e.contexts().flatMap(e=>e.pages()).filter(e=>!e.isClosed()&&lr(e.url(),"zhipin"));return n.find(e=>e.url()===t.url)??n[0]}function fd(e,t){return{networkEvents:e.flatMap(e=>e.networkEvents).slice(0,t),navigationEvents:e.flatMap(e=>e.navigationEvents)}}async function gd(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 hd(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},Ol)).map(e=>Wl(e.area,{key:e.key,value:e.value}))}async function yd(e){return await e.evaluate(()=>({url:location.href,title:document.title,visibilityState:document.visibilityState,hasFocus:document.hasFocus()}))}async function bd(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 wd(e){return new Map(e.map(e=>[`${e.area}:${e.key}`,e]))}function vd(e,t){return[...new Set([...Object.keys(e??{}),...Object.keys(t??{})])]}function xd(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function Sd(e,t){return{...void 0!==e?{before:e}:{},...void 0!==t?{after:t}:{},...void 0!==e&&void 0!==t?{delta:t-e}:{}}}function Rd(e,t){const n=wd(e),r=wd(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 vd(t?.numericFields,a?.numericFields)){const n=t?.numericFields?.[e],r=a?.numericFields?.[e];n!==r&&(s[e]=xd(n,r))}const c={};for(const e of vd(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 vd(t?.arrayLengths,a?.arrayLengths)){const n=t?.arrayLengths?.[e],r=a?.arrayLengths?.[e];n!==r&&(l[e]=Sd(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 Id(e,t){const[n,r,a]=await Promise.all([gd(e,"localStorage"),gd(e,"sessionStorage"),e.context().cookies()]),o=[...n.filter(e=>Hl(e.key)).map(e=>Wl("localStorage",e)),...r.filter(e=>Hl(e.key)).map(e=>Wl("sessionStorage",e))];return{localStorage:n.map(e=>Wl("localStorage",e)),sessionStorage:r.map(e=>Wl("sessionStorage",e)),cookies:a.filter(e=>e.domain.includes("zhipin.com")||e.domain.includes("bosszhipin.com")).map(e=>Kl(e)),counterDiffs:Rd(t,o)}}var Cd=Xi({name:"zhipin_diagnose_browser_state",description:"分阶段诊断 Boss 页面在原生 CDP page WebSocket、Playwright CDP attach、页面绑定、网络上报、evaluate、检测指纹、storage/cookie 读取时的状态;默认只做 native target 枚举,所有 storage/cookie 值均脱敏。",input:Pl,output:Tl,execute:async(e,t)=>{const n=e.phase??"native",r=e.watchMs??3e3,a=e.networkEventLimit??30,o=pe(),i=me(),s=[],c=[],l=[];let d,u,m,p=!1,f=!1,g=e.targetPageId;t.logger.info(`Diagnosing zhipin browser state (phase: ${n})`);const h=await Gl("native",async()=>await o.listNativePages());c.push(h.phaseResult);const y=h.result??[],b=y.map(e=>ur(o,e)),w=Yl(y,e.targetPageId,s);if(w&&(d=ur(o,w),g=w.targetId,l.push(Xl(o,"native",w,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:f,nativeTimeline:l,phases:c,warnings:s};if("native-watch"===n)return await ed(o,l,"native-watch",g,r,s),{success:h.phaseResult.success&&void 0!==g,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if(!w)return{success:!1,requestedPhase:n,mode:i.mode,nativePages:b,browserAttached:p,pageAttached:f,nativeTimeline:l,phases:c,warnings:s};if(ql(n)){const e=await dd({requestedPhase:n,target:w,runtime:i,ctxManager:o,targetPageId:g,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:f,nativeTimeline:l,nativeCdp:e.summary,phases:c,warnings:s}}if("resume-canvas"===n){const e=await Gl("resume-canvas",async()=>{const e=await il({skipRiskGate:!0});try{const t=await e.waitForResumeDialog(1e3);return{dialogState:t,geometry:t.canvasReady?await e.readResumeCanvasGeometry():void 0}}finally{e.close()}});c.push(e.phaseResult);const t=e.result;return{success:e.phaseResult.success,requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:f,nativeTimeline:l,...void 0!==t?{resumeCanvas:{dialogVisible:t.dialogState.dialogVisible,iframeFound:t.dialogState.iframeFound,iframeVisible:t.dialogState.iframeVisible,canvasReady:t.dialogState.canvasReady,...void 0!==t.geometry?.screenshotArea?{screenshotArea:t.geometry.screenshotArea}:{},...void 0!==t.geometry?.canvasSize?{canvasSize:t.geometry.canvasSize}:{},...void 0!==t.geometry?.error?{error:t.geometry.error}:{}}}:{},phases:c,warnings:s}}const v=await Gl("browser-attach",async()=>await i.getBrowser());c.push(v.phaseResult);const x=v.result;p=v.phaseResult.success,await Ql(o,l,"browser-attach",g,s);const S=await ed(o,l,"browser-attach-watch",g,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."),!v.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:f,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:f,nativeTimeline:l,phases:c,warnings:s};const R=[];let I;"network-watch"===n&&void 0!==x&&(I=pd(x,w),I?R.push(md(I,r,a)):s.push("No attached zhipin page found before page-attach; network-watch starts after page-attach.")),o.rememberNativePageSelection("zhipin",w);const C=await Gl("page-attach",async()=>await o.getPage("zhipin"));c.push(C.phaseResult);const k=C.result;if(f=C.phaseResult.success,k&&(d=await mr(o,k)),await Ql(o,l,"page-attach",g,s),!C.phaseResult.success||!k){if("network-watch"===n&&R.length>0){const e=await Gl("network-watch",async()=>fd(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:f,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:f,nativeTimeline:l,phases:c,warnings:s};if("network-watch"===n){I!==k&&R.push(md(k,r,a));const e=await Gl("network-watch",async()=>fd(await Promise.all(R),a));return c.push(e.phaseResult),e.result&&(u=e.result.networkEvents,m=e.result.navigationEvents),await Ql(o,l,"network-watch",g,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:f,nativeTimeline:l,...void 0!==u?{networkEvents:u}:{},...void 0!==m?{navigationEvents:m}:{},phases:c,warnings:s}}let _=[];if("storage-summary"===n){try{_=await hd(k)}catch(e){s.push(`Failed to read storage counter baseline: ${e instanceof Error?e.message:String(e)}.`)}r>0&&await Ul(r)}const A=await Gl("page-evaluate",async()=>await yd(k));c.push(A.phaseResult);const E=A.result;if(await Ql(o,l,"page-evaluate",g,s),!A.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:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},phases:c,warnings:s};const N=await Gl("detector-fingerprint",async()=>await bd(k));c.push(N.phaseResult);const M=N.result;if(await Ql(o,l,"detector-fingerprint",g,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:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},phases:c,warnings:s};const P=await Gl("storage-summary",async()=>await Id(k,_));c.push(P.phaseResult);const T=P.result;return await Ql(o,l,"storage-summary",g,s),{success:c.every(e=>e.success),requestedPhase:n,mode:i.mode,nativePages:b,...void 0!==d?{targetPage:d}:{},browserAttached:p,pageAttached:f,nativeTimeline:l,...void 0!==E?{evaluate:E}:{},...void 0!==M?{detectorFingerprint:M}:{},...void 0!==T?{storage:T}:{},phases:c,warnings:s}}});import{defineTool as kd}from"@roll-agent/sdk";import{z as _d}from"zod";Ce();import{createHash as Ad}from"node:crypto";var Ed,Nd,Md={fetch:fetch,env:process.env},Pd="https://huajune.duliday.com",Td=new Set;function Od(e,t){const n=e[t]?.trim();return void 0===n||0===n.length?void 0:n}function Bd(e,t){const n=Od(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 Dd(e){const t=Bd(e,"RECRUITMENT_EVENTS_ENABLED"),n=Od(e,"RECRUITMENT_EVENTS_API_BASE_URL")??Pd,r=Od(e,"RECRUITMENT_EVENTS_API_TOKEN"),a=Od(e,"RECRUITMENT_EVENTS_DEFAULT_AGENT_ID");return{enabled:t??!0,apiBaseUrl:n,...void 0!==r?{apiToken:r}:{},...void 0!==a?{defaultAgentId:a}:{}}}function $d(e,t){const n=ve(e.defaultAgentId);if(void 0!==n)return n;we()&&jd(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 jd(e,t,n){Td.has(t)||(Td.add(t),e.warn(n))}function Fd(e){return`${e.replace(/\/+$/,"")}/api/v1/recruitment-events`}function Ld(e){return"object"==typeof e&&null!==e}function qd(e,t){const n=e[t];return"string"==typeof n?n:void 0}function zd(e){if(!Ld(e)||!Ld(e.data)||!Array.isArray(e.data.results))return;const t=e.data.results.find(e=>Ld(e)&&"error"===qd(e,"status"));if(!Ld(t))return;const n=qd(t,"idempotencyKey"),r=t.error,a=Ld(r)?qd(r,"code"):void 0,o=Ld(r)?qd(r,"message"):void 0;return`Recruitment events API rejected event${void 0!==n?` ${n}`:""}${void 0!==a?` ${a}`:""}${void 0!==o?`: ${o}`:""}`}async function Vd(e,t,n){const r=Dd(n.env);if(!r.enabled)return;const a=$d(r,t);if(void 0===r.apiToken)return void jd(t,"missing-required-config","Recruitment events tracking is enabled by default; require RECRUITMENT_EVENTS_API_TOKEN.");if(void 0===a)return void(we()||jd(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(Fd(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=zd(await i.json().catch(()=>{}));if(void 0!==s)throw new Error(s)}function Jd(e,t){return`${e}:${Ad("sha256").update(JSON.stringify(t.map(e=>e??""))).digest("hex").slice(0,24)}`}function Ud(e,t){(async()=>{const n=Nd??(async(e,t)=>{await Vd(e,t,{...Md,...Ed})});await n(e,t)})().catch(e=>{t.warn(`Recruitment event tracking failed: ${e instanceof Error?e.message:String(e)}`)})}function Hd(){return(new Date).toISOString().slice(0,10)}function Wd(e){const t=e?.trim();return void 0!==t&&t.length>0?t:void 0}function Kd(e,t,n){const r=e?.candidateInfo,a=Wd(r?.age),o=Wd(r?.education),i=Wd(r?.expectedSalary);return{name:Wd(r?.name)??t,position:n,...void 0!==a?{age:a}:{},...void 0!==o?{education:o}:{},...void 0!==i?{expectedSalary:i}:{}}}function Gd(e){return Wd(e?.candidateInfo.communicationPosition)??""}function Yd(e){const t=Wd(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 Xd(e){const t=Wd(e?.candidateInfo.expectedJobText);if(void 0===t)return;const n=t.split(/[·||]/).map(e=>e.trim()).filter(Boolean);return n.length>1?Wd(n[0]):void 0}function Zd(e){return e.length>0?{job:{jobName:e}}:{}}function Qd(e,t){return e.trim().length>0&&t.trim().length>0}function eu(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 tu(e,t){for(const n of e)n.hasUnread&&0!==n.name.length&&Qd(n.name,n.position)&&Ud({idempotencyKey:Jd("zhipin-message-received",[n.conversationId,n.candidateId,n.name,n.position,Hd()]),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 nu(e,t){const n=Gd(e.candidateDetails),r=n,a=Kd(e.candidateDetails,e.candidateName,r);if(!Qd(a.name,a.position))return;const o=Xd(e.candidateDetails);Ud({idempotencyKey:Jd("zhipin-message-sent",[e.conversationId,e.candidateId,e.replyId]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"message_sent",candidate:{...a,...void 0!==o?{expectedLocation:o}:{}},...Zd(n),details:{content:e.message,isAutoReply:!0,unreadCountBeforeReply:e.unreadCountBeforeReply,wasUnreadBeforeReply:e.unreadCountBeforeReply>0,conversationId:e.conversationId,candidateId:e.candidateId}},t)}function ru(e,t){if(!e.clicked||0===e.name.length)return;const n=Wd(e.expectedPosition);if(void 0===n||!Qd(e.name,n))return;const r=Wd(e.age),a=Wd(e.education),o=Wd(e.expectedSalary),i=Wd(e.expectedLocation);Ud({idempotencyKey:Jd("zhipin-candidate-contacted",[e.candidateId,e.name,Hd()]),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 au(e,t){const n=Gd(e.candidateDetails),r=Kd(e.candidateDetails,e.candidateName,n);if(!Qd(r.name,r.position))return;const a=Xd(e.candidateDetails);Ud({idempotencyKey:Jd("zhipin-wechat-requested",[e.conversationId,e.candidateId,Hd()]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...r,...void 0!==a?{expectedLocation:a}:{}},...Zd(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 ou(e,t,n,r){const a=Gd(e),o=Yd(e),i=Xd(e),s=Kd(e,e.candidateInfo.name,o);if(!Qd(s.name,s.position))return;const c=e.messages.find(e=>"wechat-exchange"===e.messageType);if(void 0===c)return;const l=eu(c);Ud({idempotencyKey:Jd("zhipin-wechat-completed",[t,n]),sourcePlatform:"zhipin",dataSource:"api_callback",eventType:"wechat_exchanged",candidate:{...s,...void 0!==i?{expectedLocation:i}:{}},...Zd(a),details:{exchangeType:"completed",conversationId:t,candidateId:n,messageIndex:c.index,...void 0!==l?{wechatNumber:l}:{}}},r)}var iu,su=_d.object({name:_d.string(),conversationId:_d.string(),candidateId:_d.string(),position:_d.string(),time:_d.string(),preview:_d.string(),unreadCount:_d.number(),hasUnread:_d.boolean(),index:_d.number()}),cu=_d.object({success:_d.boolean(),candidates:_d.array(su),total:_d.number(),stats:_d.object({withName:_d.number(),withUnread:_d.number()})});function lu(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...iu}}function du(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 uu(e){const t=new Set,n=[];for(const r of e){const e=du(r);void 0!==e&&t.has(e)||(void 0!==e&&t.add(e),n.push(r))}return n}var mu=kd({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:_d.object({limit:_d.number().optional().describe("最多返回条数"),onlyUnread:_d.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:_d.enum(["time","unreadCount","name"]).default("time"),autoScroll:_d.boolean().default(!0).describe("是否自动向下滚动消息列表并合并采集结果"),maxScrolls:_d.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:cu,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=lu();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=uu(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)`),tu(d,t.logger),{success:!0,candidates:d,total:l.length,stats:m}}catch(e){return xs(e),await(a?.fail("读取消息列表失败")),t.logger.warn(`Native zhipin message read failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}}}finally{r?.close()}}});import{BrowserActionApprovalSchema as pu,BrowserPageInfoSchema as fu}from"@roll-agent/browser";import{defineTool as gu}from"@roll-agent/sdk";import{z as hu}from"zod";Ce();var yu,bu=hu.object({forceReload:hu.boolean().optional().describe("为 true 时对当前沟通页执行 native CDP Page.reload,清空长跑累积的 DOM/SPA 状态,并在 document swap 后继续等待实际聊天列表 DOM;用于长跑 tab 的周期性恢复。"),expectedConversationId:hu.string().min(1).optional().describe("forceReload=true 时可传入;必要时滚动列表,等待该会话重新出现后才返回成功。"),browserActionApproval:pu.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID(仅 forceReload 时需要)。")}),wu=hu.object({success:hu.boolean(),alreadyOnChat:hu.boolean(),usedSidebarClick:hu.boolean(),usedReload:hu.boolean(),chatReady:hu.boolean(),reloadSkippedReason:hu.enum(vc).optional(),page:fu.optional(),error:hu.string().optional()});function vu(){return{getContextManager:pe,getRuntime:me,openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...yu}}async function xu(e,t){return ur(e,await t.inspectPage())}var Su=gu({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测;forceReload=true 时对当前沟通页执行 native reload,并等待实际聊天列表 DOM 就绪。",input:bu,output:wu,execute:async(e,t)=>{const n=vu(),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 $r(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 xu(r,a),error:s.error};Tr(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 xu(r,a)}):(await o.fail("刷新后沟通列表未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,page:await xu(r,a),error:"刷新后沟通列表未就绪"})}{const e="正在切换到沟通页";await o.begin(e),await o.highlightSelector(Cs.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 xu(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 xu(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 xu(r,a)}):(await o.fail("沟通页未就绪"),{success:!1,alreadyOnChat:!1,usedSidebarClick:!0,usedReload:i,chatReady:!1,page:await xu(r,a),error:"沟通页未就绪"})}catch(e){return xs(e),await(o?.fail("切换沟通页失败")),{success:!1,alreadyOnChat:!1,usedSidebarClick:!1,usedReload:i,chatReady:!1,error:e instanceof Error?e.message:"切换沟通页失败"}}finally{a?.close()}}});import{defineTool as Ru}from"@roll-agent/sdk";import{z as Iu}from"zod";var Cu,ku=Iu.object({success:Iu.boolean(),conversationId:Iu.string(),candidateId:Iu.string(),candidateName:Iu.string(),index:Iu.number(),hasUnread:Iu.boolean(),unreadCount:Iu.number(),lastMessageTime:Iu.string(),messagePreview:Iu.string(),error:Iu.string().optional()});function _u(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...Cu}}var Au=Ru({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:Iu.object({conversationId:Iu.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Iu.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Iu.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:Iu.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:ku,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=_u();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 xs(n),await(a?.fail("打开聊天失败")),t.logger.warn(`Native zhipin open chat failed: ${n instanceof Error?n.message:String(n)}`),{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:n instanceof Error?n.message:"打开聊天失败"}}finally{r?.close()}}});import{defineTool as Eu}from"@roll-agent/sdk";import{CandidateLocationSignalSchema as Nu,prepareReplyContext as Mu,ReplyAuthorityRequestError as Pu}from"@roll-agent/reply-authority-client";import{z as Tu}from"zod";var Ou="·",Bu=/[--—–]/,Du=/[[【[]\s*(\d+)\s*[\]】]]\s*$/;function $u(e){return(e??"").trim()}function ju(e){return e.some(e=>"candidate_expected_location"!==e.source)}function Fu(e){const t=ju(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 Lu(e){return!Array.isArray(e)&&"analysisPath"in e&&"signals"in e}function qu(e){const t=Lu(e)?e.signals:e,n=Lu(e)?e.analysisPath:void 0,r=Lu(e)?e.inquiryType:void 0;if(0===t.length)return"non_location_inquiry"===r?"非地点咨询":"profile_only"===n?"":"未识别到地点线索";const a=Fu(t);return!ju(t)?"profile_only"===n?`资料城市提示:${a}`:"未识别到地点线索":"fallback"===n?`已识别地点(兜底):${a}`:`已识别地点:${a}`}function zu(e){const t=Du.exec($u(e));if(!t)return;const n=Number(t[1]);return Number.isSafeInteger(n)&&n>0?n:void 0}function Vu(e){const t=$u(e);if(!t||Du.test(t))return;if(!Bu.test(t))return;const[n=""]=t.split(Bu);return $u(n)||void 0}function Ju(e){const t=$u(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",r=""]=t.split(Ou).map(e=>$u(e));return{expectedLocation:n,expectedPosition:r}}function Uu(e){const t=$u(e.communicationPosition),{expectedLocation:n,expectedPosition:r}=Ju(e.expectedJobText),a=Vu(t),o=zu(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:r,...void 0!==a?{preferredBrand:a}:{},...void 0!==o?{preferredBrandId:o}:{}}}var Hu,Wu=Tu.object({index:Tu.number(),sender:Tu.enum(["candidate","recruiter","system"]),messageType:Tu.enum(["text","system","resume","wechat-exchange"]),content:Tu.string(),time:Tu.string()}),Ku=Tu.object({name:Tu.string(),age:Tu.string(),experience:Tu.string(),education:Tu.string(),communicationPosition:Tu.string(),expectedPosition:Tu.string(),expectedLocation:Tu.string(),expectedSalary:Tu.string(),tags:Tu.array(Tu.string())}),Gu=Tu.object({success:Tu.boolean(),conversationId:Tu.string(),candidateId:Tu.string(),candidateInfo:Ku,preferredBrand:Tu.string().optional(),preferredBrandId:Tu.number().int().positive().optional(),locationSignals:Tu.array(Nu).describe("已废弃:地点证据改由 Reply Authority 服务端提取,此字段恒为空数组,仅为兼容保留"),chatMessages:Tu.array(Wu),formattedHistory:Tu.array(Tu.string()),stats:Tu.object({totalMessages:Tu.number(),candidateMessages:Tu.number(),recruiterMessages:Tu.number(),systemMessages:Tu.number()}),error:Tu.string().optional()});function Yu(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function Xu(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:Yu(),locationSignals:[],chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function Zu(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 Qu(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),prepareReplyContext:Mu,...Hu}}function em(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function tm(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function nm(e){return{candidateMessage:tm(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 rm=6e5,am=0;function om(e){return e instanceof Pu?403===e.statusCode:e instanceof Error&&e.message.includes("未配置")}function im(e){om(e)&&(am=Date.now()+rm)}async function sm(e){if(Date.now()<am)return void e.logger.debug("Skip Reply Authority prepare: cooling down after a persistent failure.");if(0===tm(e.data.messages).length)return void e.logger.debug("Skip Reply Authority prepare: latest candidate message is empty.");let t;try{t=Ws(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=nm({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=>{im(t),e.logger.debug(`Reply Authority prepare skipped or failed: ${t instanceof Error?t.message:String(t)}`)})}catch(t){im(t),e.logger.debug(`Reply Authority prepare failed to start: ${t instanceof Error?t.message:String(t)}`)}}var cm=Eu({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:Tu.object({conversationId:Tu.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:Tu.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:Tu.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:Tu.number().default(100).describe("最多返回的消息条数")}),output:Gu,execute:async(e,t)=>{const n=e.maxMessages??100,r=Qu(),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)),Xu(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||!Zu(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&&!Zu(p.candidateName,m.candidateName))return await l("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${p.candidateName} / ${m.candidateName}`);await o.waitForChatMessages();const f=await o.readCandidateChatDetails(n),g=Uu({communicationPosition:f.candidateInfo.communicationPosition,expectedJobText:f.candidateInfo.expectedJobText}),h=em(f.messages),y={totalMessages:f.messages.length,candidateMessages:f.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:f.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:f.messages.filter(e=>"system"===e.sender).length};return await i.succeed(`已提取 ${y.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${f.candidateInfo.name}: ${y.totalMessages} messages`),ou(f,p.conversationId,p.candidateId,t.logger),await sm({nativePage:o,deps:r,data:f,selectedTarget:p,formattedHistory:h,communicationPosition:g.communicationPosition,expectedPosition:g.expectedPosition,expectedLocation:g.expectedLocation,preferredBrand:g.preferredBrand,preferredBrandId:g.preferredBrandId,logger:t.logger}),{success:!0,conversationId:p.conversationId,candidateId:p.candidateId,candidateInfo:{name:f.candidateInfo.name,age:f.candidateInfo.age,experience:f.candidateInfo.experience,education:f.candidateInfo.education,communicationPosition:g.communicationPosition,expectedPosition:g.expectedPosition,expectedLocation:g.expectedLocation,expectedSalary:f.candidateInfo.expectedSalary,tags:[...f.candidateInfo.tags]},...void 0!==g.preferredBrand?{preferredBrand:g.preferredBrand}:{},...void 0!==g.preferredBrandId?{preferredBrandId:g.preferredBrandId}:{},locationSignals:[],chatMessages:[...f.messages],formattedHistory:h,stats:y}}catch(e){return xs(e),await(i?.fail("提取聊天记录失败")),t.logger.warn(`Native zhipin candidate info read failed: ${e instanceof Error?e.message:String(e)}`),Xu(e instanceof Error?e.message:"提取聊天记录失败")}finally{o?.close()}}});import{defineTool as lm}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as dm,GenerateSignedReplyResponseSchema as um,ReasoningConfigSchema as mm,ReplyAuthorityRequestError as pm,ReplyStreamFinalEventSchema as fm,ReplyStreamLocationResolvedEventSchema as gm,streamGenerateSignedReply as hm}from"@roll-agent/reply-authority-client";import{z as ym}from"zod";import{ReplyGateAdvisoryCodeSchema as bm}from"@roll-agent/reply-authority-client";import{z as wm}from"zod";var vm=["option_1","option_2"],xm=["judge","orchestrator"],Sm=[...xm,"service_recommended_fallback","explicit_no_judge"],Rm={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"},Im=wm.array(bm).max(4).refine(e=>new Set(e).size===e.length,{message:"confirmedFindingCodes 不能包含重复 code"}),Cm=wm.object({chosenOption:wm.enum(vm).describe("从 replyVariantSelection.options 中选择的中性选项"),reason:wm.string().trim().min(1).max(500).describe("结合候选人当前问题和 rubric,说明选择该选项的具体理由"),confirmedFindingCodes:Im.optional().describe("确认属实的 replyVariantSelection.findings code;显式空数组表示 findings 均为误报"),judgeModel:wm.string().trim().min(1).optional().describe("执行 judge 的模型审计标签")}),km=Cm.omit({judgeModel:!0}).extend({confirmedFindingCodes:Im.describe("必须显式返回;空数组仅表示已核实当前 findings 均为误报")});import{randomUUID as _m}from"node:crypto";var Am=new Map;function Em(e){for(const[t,n]of Am)n.expiresAt<=e&&Am.delete(t)}function Nm(e,t=Math.floor(Date.now()/1e3)){if(Em(t),e.expiresAt<=t)throw new Error("Prepared reply 已过期,禁止保存。");const n=`prep_${_m()}`,r={preparedReplyId:n,...e};return Am.set(n,{...r,consumed:!1}),r}function Mm(e,t=Math.floor(Date.now()/1e3)){const n=Am.get(e);return void 0===n?(Em(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Am.delete(e),Em(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:(Am.set(e,{...n,consumed:!0}),{ok:!0,record:Om(n)})}function Pm(e,t=Math.floor(Date.now()/1e3)){const n=Am.get(e);return void 0===n?(Em(t),{ok:!1,reason:"not_found"}):n.expiresAt<=t?(Am.delete(e),Em(t),{ok:!1,reason:"expired"}):n.consumed?{ok:!1,reason:"consumed"}:{ok:!0,record:Om(n)}}function Tm(e,t,n=Math.floor(Date.now()/1e3)){const r=Pm(e,n);if(!r.ok)return r;const a=Am.get(e);return void 0===a?{ok:!1,reason:"not_found"}:(Am.set(e,{...a,judgement:t}),{ok:!0,record:Om({...a,judgement:t})})}function Om(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 Bm}from"@roll-agent/reply-authority-client";import{z as Dm}from"zod";var $m=1e3,jm=8,Fm=500,Lm=200,qm=12,zm=100,Vm=Bm.pick({age:!0,experience:!0,education:!0,communicationPosition:!0,expectedPosition:!0,expectedLocation:!0,expectedSalary:!0,info:!0}),Jm=Dm.object({candidateMessage:Dm.string().min(1).max($m),recentConversation:Dm.array(Dm.string().min(1).max(Fm)).max(jm),candidateInfo:Vm.optional(),preferredBrand:Dm.string().min(1).max(Lm).optional()});function Um(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 Hm(e,t){const n=Um(e).replace(/\s+/g," ").trim();return n.length<=t?n:n.slice(0,t)}function Wm(e){if(void 0===e)return;const t={...void 0!==e.age?{age:Hm(e.age,Lm)}:{},...void 0!==e.experience?{experience:Hm(e.experience,Lm)}:{},...void 0!==e.education?{education:Hm(e.education,Lm)}:{},...void 0!==e.communicationPosition?{communicationPosition:Hm(e.communicationPosition,Lm)}:{},...void 0!==e.expectedPosition?{expectedPosition:Hm(e.expectedPosition,Lm)}:{},...void 0!==e.expectedLocation?{expectedLocation:Hm(e.expectedLocation,Lm)}:{},...void 0!==e.expectedSalary?{expectedSalary:Hm(e.expectedSalary,Lm)}:{},...void 0!==e.info?{info:e.info.map(e=>Hm(e,zm)).filter(e=>e.length>0).slice(0,qm)}:{}};return Object.keys(t).length>0?t:void 0}function Km(e){const t=Hm(e.candidateMessage,$m);if(0===t.length)throw new Error("Judge context 缺少候选人当前消息");const n=(e.conversationHistory??[]).map(e=>Hm(e,Fm)).filter(e=>e.length>0).slice(-jm),r=Wm(e.candidateInfo),a=void 0!==e.preferredBrand?Hm(e.preferredBrand,Lm):void 0;return Jm.parse({candidateMessage:t,recentConversation:n,...void 0!==r?{candidateInfo:r}:{},...void 0!==a&&a.length>0?{preferredBrand:a}:{}})}function Gm(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 Ym,Xm=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(!In())return!1;try{return await this.target.evaluateJson(Gm(e))}catch{return!1}}},Zm=ym.object({conversationId:ym.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:ym.string().optional().describe("候选人姓名。若用户说“给鲁倩生成回复”,这里应提取为“鲁倩”"),index:ym.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:ym.number().default(100).describe("最多读取的聊天消息条数"),reasoning:mm.optional().describe("可选 reasoning/thinking 控制。enabled=true 会请求 Reply Authority 使用模型推理模式;effort 可选 low/medium/high;scope 可选 reply/all")}),Qm=["rejected","timeout","server_error"],ep=ym.object({requestId:ym.string().optional(),elapsedMs:ym.number().int().min(0).optional(),clientTimeoutMs:ym.number().int().positive().optional(),lastStartedPhase:ym.string().min(1).optional(),activePhase:ym.string().min(1).optional(),phaseLatencies:ym.record(ym.number().int().min(0)).optional()}),tp=ym.object({success:ym.boolean(),preparedReplyId:ym.string().optional(),suggestedReply:ym.string().optional(),stage:ym.string().optional(),confidence:ym.number().optional(),expiresAt:ym.number().optional(),timing:ym.object({totalLatencyMs:ym.number().int().min(0),replyLatencyMs:ym.number().int().min(0).optional(),turnPlanningLatencyMs:ym.number().int().min(0).optional(),contextBuildingLatencyMs:ym.number().int().min(0).optional(),preparedContextHit:ym.boolean()}).optional(),gateRewritten:ym.boolean().optional().describe("服务端事实/质量门调整过终稿时为 true,此时最终回复可能与流式草稿不一致"),replyVariantSelection:ym.object({groupId:ym.string().min(1),options:ym.array(ym.object({option:ym.enum(vm),suggestedReply:ym.string(),expiresAt:ym.number().int().min(0)})).min(2).max(2),findings:ym.array(ym.object({code:ym.string().min(1),description:ym.string().min(1)})),rubricVersion:ym.string().min(1),rubricHash:ym.string().min(1)}).optional().describe("双稿选择信息;仅暴露中性 option_1/option_2、文本与 rubric 元数据,不暴露 draft/revised 或 signedEnvelope。"),error:ym.string().optional(),errorKind:ym.enum(Qm).optional()}).extend(ep.shape),np={tenant_context:"加载租户上下文",binding_check:"校验招聘账号绑定",turn_planning:"分析候选人意图",context_building:"准备业务上下文",qualification_check:"检查候选人资格",reply_generation:"生成回复草稿",dual_draft:"生成可选改写稿",fact_gate:"检查事实安全",reply_gate:"检查回复策略",signing:"签发安全信封"};function rp(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),createReplyPreviewVisualSession:e=>new Xm(e),streamGenerateSignedReply:hm,fetchReplyFeedbackRubric:dm,savePreparedReply:Nm,random:Math.random,...Ym}}function ap(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 op(e,t){const n=e[t];return"string"==typeof n&&n.trim().length>0?n:void 0}function ip(e,t){const n=e[t];return"number"==typeof n&&Number.isFinite(n)?n:void 0}function sp(e,t){const n=e[t];return"boolean"==typeof n?n:void 0}function cp(e){return e.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`)}function lp(e){const t=[...e].reverse().find(e=>"candidate"===e.sender&&e.content.trim().length>0);return t?.content.trim()??""}function dp(e){return[...e].reverse().find(e=>("candidate"===e.sender||"recruiter"===e.sender)&&e.content.trim().length>0)}function up(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}function mp(e){const t=up(e.navigationUnreadCount);return t>0?t:"candidate"===dp(e.data.messages)?.sender?1:0}function pp(e){const t=Uu({communicationPosition:e.data.candidateInfo.communicationPosition,expectedJobText:e.data.candidateInfo.expectedJobText});return{candidateMessage:lp(e.data.messages),conversationHistory:cp(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 fp(e){return"phase.started"===e.type?op(e,"label")??np[op(e,"phase")??""]??"正在处理回复生成阶段":"tool.started"===e.type?"正在执行工具"+(op(e,"toolName")?`: ${op(e,"toolName")}`:""):"tool.completed"===e.type?"工具执行完成"+(op(e,"toolName")?`: ${op(e,"toolName")}`:""):"draft.started"===e.type?"正在生成回复草稿":"reasoning.started"===e.type?"正在推理回复策略":"reasoning.completed"===e.type?"回复策略推理完成":void 0}function gp(e,t,n={}){return{success:!1,error:e,...void 0!==t?{errorKind:t}:{},...n}}function hp(e,t){const n=t instanceof pm?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 yp(e){return e.replace(/\s+\(url=.*\)$/,"")}function bp(e){const t=e instanceof Error?e.message:String(e);if(e instanceof pm){const n=yp(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 wp(e){return e<1e3?`${String(e)}ms`:`${(e/1e3).toFixed(e<1e4?1:0)}s`}function vp(e){return void 0!==e.turnPlanningLatencyMs&&void 0!==e.contextBuildingLatencyMs&&e.turnPlanningLatencyMs<=50&&e.contextBuildingLatencyMs<=50}function xp(e){const t=["回复已生成",`总 ${wp(e.totalLatencyMs)}`];return void 0!==e.replyLatencyMs&&t.push(`生成 ${wp(e.replyLatencyMs)}`),e.preparedContextHit&&t.push("预热命中"),e.gateRewritten&&t.push("终稿经安全门调整"),t.join(" · ")}function Sp(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 Rp(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 Ip(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 Cp(e){if(e.rubric.rubricVersion!==e.replyVariants.rubricVersion||e.rubric.rubricHash!==e.replyVariants.rubricHash)return Ip({...e,reason:Rm.RUBRIC_MISMATCH});const t=Rp(e.replyVariants,e.random);if(2!==t.length)return Ip({...e,reason:Rm.INVALID_VARIANT_SHAPE});const n=new Set(t.map(e=>e.variant));if(!n.has("draft")||!n.has("revised"))return Ip({...e,reason:Rm.INVALID_VARIANT_SHAPE});const r=t.map((e,t)=>({option:vm[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 kp=lm({name:"zhipin_generate_reply_preview",description:"读取当前或指定 BOSS 聊天,流式生成智能回复并在浏览器中展示阶段和临时草稿;返回 preparedReplyId 供发送。",input:Zm,output:tp,execute:async(e,t)=>{const n=rp(),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 $r(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),gp(e)}if(void 0===l&&!await o.isChatSurfaceOpen().catch(()=>!1)){const e="当前页面不是 BOSS 沟通页,无法生成回复";return await i.fail("当前不是沟通页"),gp(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||!ap(d,u.candidateName))){const e=`右侧聊天面板未切换到 ${d}`;return await i.fail("聊天面板未同步"),gp(e)}const m=await o.readSelectedChatTarget();if(!m){const e="未能提取当前选中聊天的 conversationId/candidateId";return await i.fail("未识别当前会话"),gp(e)}if(null!==u&&m.candidateName.length>0&&!ap(m.candidateName,u.candidateName)){const e=`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`;return await i.fail("当前会话不一致"),gp(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("当前会话不一致"),gp(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("聊天详情目标不一致"),gp(e)}if(0===lp(p.messages).length){const e="未找到候选人最新消息,无法生成回复";return await i.fail("候选人消息为空"),gp(e)}const f=Ws(await o.readUsernameEvidence());if(!f.found){const e="未找到用户名,请确认当前页面已登录招聘者账号。";return await i.fail("未找到用户名"),gp(e)}const g=pp({data:p,conversationId:m.conversationId,candidateId:m.candidateId,recruiterUsername:f.username,reasoning:e.reasoning}),h=mp({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,w,v,x="",S=!1;for await(const e of n.streamGenerateSignedReply(g)){if("stream.started"===e.type&&(c.requestId=op(e,"requestId"),b=op(e,"tenantId")),"phase.started"===e.type){const t=op(e,"phase");void 0!==t&&(c.lastStartedPhase=t,c.activePhase=t)}if("phase.completed"===e.type){const t=op(e,"phase"),n=ip(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===sp(e,"rewritten")&&(S=!0);const r=fp(e);if(void 0!==r&&await s.updateStatus(r),"location.resolved"===e.type){const t=gm.safeParse(e);if(t.success){const e=qu({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+=op(e,"delta")??"",await s.updateDraft(x,!0)}if("final"===e.type){const r=fm.parse(e),a=um.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=vp({turnPlanningLatencyMs:o,contextBuildingLatencyMs:i});let f;if(v={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("双稿反馈身份缺失"),gp(e,void 0,hp(c))}try{const e=await n.fetchReplyFeedbackRubric({tenantId:b,rubricVersion:a.replyVariants.rubricVersion});f=Cp({replyVariants:a.replyVariants,rubric:e,tenantId:b,conversationId:m.conversationId,judgeContext:Km(g),random:n.random}),"not_learned"===f.state&&t.logger.warn(`Reply variants will use a non-learning terminal outcome: reason=${f.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=${Rm.RUBRIC_FETCH_FAILED} detail=${n}`),f=Ip({replyVariants:a.replyVariants,tenantId:b,conversationId:m.conversationId,reason:Rm.RUBRIC_FETCH_FAILED})}}w=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!==f?{variantGroup:f}:{}}),await s.complete(xp({...v,gateRewritten:S}),a.suggestedReply,"judge_ready"===f?.state?{options:f.options.map(e=>({option:e.option,suggestedReply:e.suggestedReply})),findings:f.findings}:void 0)}}if(void 0===w){const e="Reply Authority stream 未返回 final";return await s.fail(e),gp(e,void 0,hp(c))}return Sp(w,v,S)}catch(e){xs(e);const t=bp(e),n=hp(c,e);return await(s?.fail(t.message)),await(i?.fail(t.message)),gp(t.message,t.errorKind,n)}finally{o?.close()}}});import{defineTool as _p}from"@roll-agent/sdk";import{fetchReplyFeedbackRubric as Ap}from"@roll-agent/reply-authority-client";import{z as Ep}from"zod";var Np,Mp="zhipin_judge_prepared_reply",Pp=Ep.object({success:Ep.boolean(),variantDecision:Cm.optional(),decisionSource:Ep.enum(Sm).optional(),fallback:Ep.boolean().optional(),recommendedOption:Ep.enum(vm).optional(),error:Ep.string().optional()}),Tp=Ep.object({preparedReplyId:Ep.string().min(1).describe("zhipin_generate_reply_preview 返回的 preparedReplyId"),judgeModel:Ep.string().min(1).optional().describe("写入 feedback 审计的模型标签;不会改变实际 MCP Sampling 模型")}),Op=new Map;function Bp(){return{fetchReplyFeedbackRubric:Ap,...Np}}function Dp(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复 judge":"preparedReplyId 不存在,请重新生成回复"}function $p(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 jp(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 Fp(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 Lp(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 qp(e,t,n){const r=e.variantGroup;if("judge_ready"!==r?.state)throw new Error("preparedReplyId 未包含双稿选项");const a=Bp();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=${Rm.RUBRIC_FETCH_FAILED} detail=${r}`),$p(e,Rm.RUBRIC_FETCH_FAILED)}if(o.rubricVersion!==r.rubricVersion||o.rubricHash!==r.rubricHash)return $p(e,Rm.RUBRIC_MISMATCH);try{i=await t.llm.generateText(Lp(e,o))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Rm.JUDGE_SAMPLING_FAILED} detail=${r}`),$p(e,Rm.JUDGE_SAMPLING_FAILED)}try{s=km.parse(jp(i))}catch(n){const r=n instanceof Error?n.message:String(n);return t.logger.warn(`Default dual-draft judge failed: reason=${Rm.JUDGE_OUTPUT_INVALID} detail=${r}`),$p(e,Rm.JUDGE_OUTPUT_INVALID)}return Fp(e,s.confirmedFindingCodes)?{kind:"decision",source:"judge",decision:{chosenOption:s.chosenOption,reason:Um(s.reason),confirmedFindingCodes:s.confirmedFindingCodes,judgeModel:n}}:(t.logger.warn(`Default dual-draft judge failed: reason=${Rm.JUDGE_OUTPUT_INVALID} detail=unknown finding code`),$p(e,Rm.JUDGE_OUTPUT_INVALID))}async function zp(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=Op.get(e.preparedReplyId);if(void 0!==r)return r;const a=qp(e,t,n).then(n=>{const r=Tm(e.preparedReplyId,n);return r.ok||t.logger.warn(`Failed to cache dual-draft judgement for ${e.preparedReplyId}: ${r.reason}`),n}).finally(()=>{Op.delete(e.preparedReplyId)});return Op.set(e.preparedReplyId,a),a}function Vp(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 Jp=_p({name:Mp,description:"对 zhipin_generate_reply_preview 返回的双稿 preparedReplyId 执行默认 judge;结果会缓存供发送复用。Judge 失败时返回稳定安全码并降级为推荐稿,发送阶段回传 not_learned 终态但不产生学习样本。",input:Tp,output:Pp,execute:async(e,t)=>{const n=Pm(e.preparedReplyId);if(!n.ok)return{success:!1,error:Dp(n.reason)};if(void 0===n.record.variantGroup)return{success:!1,error:"preparedReplyId 未包含双稿选项,无需 judge"};if("not_learned"===n.record.variantGroup.state)return Pp.parse({success:!0,fallback:!0,decisionSource:"service_recommended_fallback",error:n.record.variantGroup.reason});return Vp(await zp(n.record,t,e.judgeModel??"mcp-sampling"))}});import{createHash as Up}from"node:crypto";import{BrowserActionApprovalSchema as Hp}from"@roll-agent/browser";import{postReplyFeedback as Wp}from"@roll-agent/reply-authority-client";import{defineTool as Kp}from"@roll-agent/sdk";import{z as Gp}from"zod";import{createHash as Yp}from"node:crypto";import{mkdirSync as Xp}from"node:fs";import{homedir as Zp}from"node:os";import{dirname as Qp,resolve as ef}from"node:path";import{DatabaseSync as tf}from"node:sqlite";import{postReplyFeedback as nf,ReplyFeedbackBodySchema as rf}from"@roll-agent/reply-authority-client";import{createAgentLogger as af}from"@roll-agent/sdk";var of,sf,cf=604800,lf=3e4,df=1e3,uf=9e5,mf=9e5,pf=50,ff=["pending","delivered","dead"],gf=ff.map(e=>`'${e}'`).join(", "),hf=af("browser-use-agent:reply-feedback-outbox");function yf(e){return"~"===e?Zp():e.startsWith("~/")?ef(Zp(),e.slice(2)):ef(e)}function bf(){return ef(Zp(),".roll-agent","browser","reply-feedback-outbox.sqlite")}function wf(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 vf(e){const t=e.retentionSeconds??wf("REPLY_FEEDBACK_OUTBOX_RETENTION_SECONDS",cf),n=e.flushIntervalMs??wf("REPLY_FEEDBACK_OUTBOX_FLUSH_INTERVAL_MS",lf),r=e.dbPath??process.env.REPLY_FEEDBACK_OUTBOX_DB_PATH?.trim()??bf();if(!r)throw new Error("reply feedback outbox dbPath must not be empty.");const a=yf(r),o=e.retryBaseDelayMs??df,i=e.authRetryDelayMs??uf;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 xf(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(xf).join(",")}]`;if("object"==typeof e){return`{${Object.keys(e).filter(t=>void 0!==Reflect.get(e,t)).sort().map(t=>`${JSON.stringify(t)}:${xf(Reflect.get(e,t))}`).join(",")}}`}throw new Error("Unsupported reply feedback value type: "+typeof e)}function Sf(e){return Yp("sha256").update(e).digest("hex")}function Rf(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 If(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 Cf(e){return rf.parse(JSON.parse(e.body_json))}function kf(e){return(e instanceof Error?e.message:String(e)).slice(0,1e3)}function _f(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 Af(e){return 401===e||403===e}function Ef(e){return!(void 0!==e&&!Af(e))||(408===e||425===e||429===e||e>=500)}function Nf(e,t){if(Af(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,mf)}function Mf(e,t){const n=e.operationQueue.then(t);return e.operationQueue=n.then(()=>{},()=>{}),n}function Pf(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 Tf(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 Of(e,t,n,r){const a=_f(n),o=kf(n);if(Ef(a)&&r<t.retry_expires_at){const n=r+Nf(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 Bf(e,t){const n=Date.now();if(n>=t.retry_expires_at)return Of(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(Cf(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 Tf(e,t,n.status,r),e.logger.debug(`Reply feedback delivered: groupId=${t.group_id} status=${n.status}`),{status:n.status}}catch(n){return Of(e,If(e.db,t.group_id)??t,n,Date.now())}}async function Df(e){const t=Date.now();Pf(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,pf);for(const t of n)await Bf(e,t)}function $f(e){e.closing||e.backgroundFlushQueued||(e.backgroundFlushQueued=!0,Mf(e,()=>Df(e)).catch(t=>{e.logger.error(`Reply feedback outbox flush failed: ${t instanceof Error?t.message:String(t)}`)}).finally(()=>{e.backgroundFlushQueued=!1}))}function jf(e){Xp(Qp(e),{recursive:!0});const t=new tf(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 (${gf})),\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 Ff(e={}){if(of&&!of.closing)return of.deliver=e.deliver??of.deliver,void(of.logger=e.logger??of.logger);if(of?.closing||sf)throw new Error("Reply feedback outbox is shutting down.");const t=vf(e),n=jf(t.dbPath),r=setInterval(()=>{of&&$f(of)},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??nf,logger:e.logger??hf,closing:!1,backgroundFlushQueued:!1};of=a,a.logger.info(`Reply feedback outbox initialized: dbPath=${a.dbPath}`),$f(a)}async function Lf(){if(sf)return sf;const e=of;return e?(e.closing=!0,clearInterval(e.timer),sf=(async()=>{await e.operationQueue;try{e.db.exec("PRAGMA wal_checkpoint(TRUNCATE);")}finally{e.db.close(),of===e&&(of=void 0)}})().finally(()=>{sf=void 0})):void 0}async function qf(e,t,n,r={}){of||Ff({deliver:t,logger:n});const a=of;if(!a||a.closing)return{status:"failed",error:"Reply feedback outbox is not available."};a.deliver=t,a.logger=n;const o=rf.parse(e),i=xf(o),s=Sf(i);return Mf(a,async()=>{const e=Date.now(),t=Rf(e,a.retentionMs,r.feedbackExpiresAt);Pf(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=If(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=If(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}:{}}:Bf(a,c)):{status:"failed",error:"Reply feedback outbox insert failed."}})}Ce(),Be();import{defineTool as zf}from"@roll-agent/sdk";import{z as Vf}from"zod";function Jf(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}Ce();import{z as Uf}from"zod";var Hf="reply-authority-service",Wf="browser-use-agent/zhipin_send_reply",Kf="zhipin",Gf=60,Yf=Uf.object({platform:Uf.literal(Kf),username:Uf.string().min(1),accountId:Uf.string().min(1).optional()}),Xf=Uf.object({v:Uf.literal(2),iss:Uf.literal(Hf),kid:Uf.string().min(1),jti:Uf.string().min(1),iat:Uf.number().int(),exp:Uf.number().int(),aud:Uf.literal(Wf),platform:Uf.literal(Kf),tenantId:Uf.string().min(1),conversationId:Uf.string().min(1),candidateId:Uf.string().min(1),reply:Uf.string(),policyVersion:Uf.string().min(1),recruiterBinding:Yf}),Zf=Uf.object({kid:Uf.string().min(1),algorithm:Uf.literal("Ed25519"),publicKey:Uf.string().min(1),validFrom:Uf.string().min(1),validUntil:Uf.string().optional()}),Qf=Uf.object({keys:Uf.array(Zf)}),eg=new Map;function tg(e){for(const[t,n]of eg)n<e-Gf&&eg.delete(t)}function ng(e,t=Math.floor(Date.now()/1e3)){return tg(t),eg.has(e)}function rg(e,t,n=Math.floor(Date.now()/1e3)){tg(n),eg.set(e,t)}import{createPublicKey as ag,verify as og}from"node:crypto";var ig=new Map,sg=null;function cg(){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 lg(e){return ig=new Map(e.map(e=>[e.kid,e]))}async function dg(){if(sg)return sg;sg=(async()=>{const e=await fetch(cg()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return lg(Qf.parse(t).keys)})();try{return await sg}finally{sg=null}}async function ug(){await dg()}async function mg(e){const t=ig.get(e);if(t)return t;const n=(await dg()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function pg(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function fg(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")}pg(a);const i=Xf.safeParse(a);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:r}}function gg(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-Gf)throw new Error("Envelope expired");if(e.iat>t+Gf)throw new Error("Envelope issued in the future")}async function hg(e,t=Math.floor(Date.now()/1e3)){const n=fg(e),r=await mg(n.payload.kid),a=ag({key:Buffer.from(r.publicKey,"base64url"),format:"der",type:"spki"});if(!og(null,Buffer.from(n.payloadJson,"utf-8"),a,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return gg(n.payload,t),n.payload}var yg,bg=Vf.object({success:Vf.boolean(),sentMessage:Vf.string(),error:Vf.string().optional()});function wg(){return{getReplyAuthorityKeysLoaded:ge,openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...yg}}function vg(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 xg(e){return"function"==typeof e.readCandidateChatDetails}async function Sg(e){if(xg(e))return await e.readCandidateChatDetails(50).catch(()=>{})}function Rg(e,t){return null!==e&&e.conversationId===t.conversationId&&e.candidateId===t.candidateId}function Ig(e,t){return null===t||0===e.candidateName.length||vg(e.candidateName,t.candidateName)}function Cg(e){return void 0===e||!Number.isFinite(e)||e<=0?0:Math.floor(e)}async function kg(e,t){const n=wg();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 hg(e.signedEnvelope);if(o=i.reply,ng(i.jti))return{success:!1,sentMessage:o,error:"token 已消费,禁止重放"};r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await $r(r),await new Xm(r).clear(),await a.begin("正在发送回复");let s=await r.readActiveChatPanel().catch(()=>null),c=await r.readSelectedChatTarget().catch(()=>null),l=Cg(e.unreadCountBeforeReply);if(!Rg(c,i)||!Ig(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,Cg(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&&!vg(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=Ws(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(!Jf(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 Sg(r);return nu({conversationId:c.conversationId,candidateId:c.candidateId,replyId:i.jti,candidateName:c.candidateName,message:o,unreadCountBeforeReply:l,...void 0!==p?{candidateDetails:p}:{}},t.logger),rg(i.jti,i.exp),await a.succeed("已发送回复"),{success:!0,sentMessage:o}}catch(e){xs(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 _g,Ag=zf({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Vf.object({signedEnvelope:Vf.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Vf.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Vf.number().optional().describe("候选人在列表中的索引(可选)")}),output:bg,execute:async(e,t)=>await kg(e,t)}),Eg="zhipin_send_prepared_reply",Ng=80,Mg=Gp.object({success:Gp.boolean(),sentMessage:Gp.string(),chosenOption:Gp.enum(vm).optional(),decisionSource:Gp.enum(Sm).optional(),decisionReason:Gp.string().optional(),judgeModel:Gp.string().optional(),feedbackExpected:Gp.boolean().optional(),feedbackStatus:Gp.enum(["accepted","duplicate","queued","skipped","failed"]).optional(),feedbackError:Gp.string().optional(),error:Gp.string().optional()}),Pg=Gp.object({preparedReplyId:Gp.string().min(1).describe("预备回复 ID,由 zhipin_generate_reply_preview 返回"),toolActionApproval:Re.optional().describe("当 browser-use tool policy 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),browserActionApproval:Hp.optional().describe("当 BROWSER_SECURITY_JSON.actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。"),variantDecision:Cm.optional().describe("可选的显式双稿选择;缺省时发送工具会自动执行并缓存默认 Judge。"),skipVariantJudge:Gp.boolean().optional().describe("仅用于明确的应急降级:跳过双稿 Judge、发送服务端推荐稿并回传 not_learned 终态,但不产生学习样本。")}).superRefine((e,t)=>{void 0!==e.variantDecision&&!0===e.skipVariantJudge&&t.addIssue({code:Gp.ZodIssueCode.custom,message:"variantDecision 与 skipVariantJudge 不能同时传入",path:["skipVariantJudge"]})});function Tg(){return{sendSignedZhipinReply:kg,postReplyFeedback:Wp,submitReplyFeedback:qf,..._g}}function Og(e){return"expired"===e?"preparedReplyId 已过期,请重新生成回复":"consumed"===e?"preparedReplyId 已消费,禁止重复发送":"preparedReplyId 不存在,请重新生成回复"}function Bg(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 Dg(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 $g(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"}:Bg(e.judgement.decision,t.variantDecision)?Dg(e.judgement):{kind:"error",error:"variantDecision 与已缓存的 Judge 结果不一致,请重新生成回复"}:{kind:"decision",source:"orchestrator",decision:t.variantDecision}:!0===t.skipVariantJudge?void 0!==e.judgement?Dg(e.judgement):{kind:"explicit_no_judge",source:"explicit_no_judge",recommendedOption:r.recommendedOption,reason:"调用方显式跳过双稿 Judge"}:Dg(await zp(e,n))}function jg(e,t){return"judge_ready"===e.variantGroup?.state?e.variantGroup.options.find(e=>e.option===t):void 0}function Fg(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 Lg(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&&Fg(e,r))return{ok:!1,error:"variantDecision.confirmedFindingCodes 必须来自 replyVariantSelection.findings"};const a="decision"===t.kind?t.decision.chosenOption:t.recommendedOption,o=jg(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 qg(e){return`sha256:${Up("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 zg(e){const t=e.suggestedReply.replace(/\s+/g," ").trim(),n=t.length>Ng?`${t.slice(0,Ng-1)}…`:t,r=void 0!==e.chosenOption?` ${e.chosenOption}`:"";return n.length>0?`发送预备回复${r}: ${n}`:`发送预备回复${r}`}async function Vg(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 Jg(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 Ug=Kp({name:Eg,description:"发送由 zhipin_generate_reply_preview 生成的预备回复;双稿缺少 variantDecision 时会在工具内部自动 Judge,并在发送成功后持久化回传 feedback。",input:Pg,output:Mg,execute:async(e,t)=>{const n=Tg(),r=Pm(e.preparedReplyId);if(!r.ok)return{success:!1,sentMessage:"",error:Og(r.reason)};const a=await $g(r.record,e,t);if("error"===a.kind)return{success:!1,sentMessage:"",error:a.error};const o=Lg(r.record,a);if(!o.ok)return{success:!1,sentMessage:"",error:o.error};const i=qg(o),s=`${o.record.preparedReplyId}:${i}`,c=zn(t,{subject:{tool:Eg,target:o.record.preparedReplyId,summary:zg(o),digest:i},...void 0!==e.toolActionApproval?{approval:e.toolActionApproval}:{},deferApprovalConsumption:!0});Tr(t,me(),{action:Eg,target:s,...void 0!==e.browserActionApproval?{approval:e.browserActionApproval}:{}}),c.consumeApproval();const l=Mm(e.preparedReplyId);if(!l.ok)return{success:!1,sentMessage:"",error:Og(l.reason)};const d=Lg(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 Vg(n,d,t);return{...u,...Jg(d),...m}}});import{defineTool as Hg}from"@roll-agent/sdk";import{z as Wg}from"zod";var Kg,Gg=Wg.object({success:Wg.boolean(),exchanged:Wg.boolean(),wechatNumber:Wg.string().optional(),error:Wg.string().optional()}),Yg=900,Xg=160,Zg=1100;function Qg(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...Kg}}function eh(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 th(e){return"function"==typeof e.readCandidateChatDetails}async function nh(e){if(th(e))return await e.readCandidateChatDetails(50).catch(()=>{})}var rh=Hg({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:Wg.object({conversationId:Wg.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:Wg.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:Wg.number().optional().describe("候选人在列表中的索引(可选)")}),output:Gg,execute:async(e,t)=>{const n=Qg();let r,a;try{if(r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await $r(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&&!eh(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 nh(r),c=await r.exchangeWechat({preClickDelayMs:Yg,pressDurationMs:Xg,settleMs:Zg,...void 0!==a?{motionObserver:a}:{}});return c.success?(c.exchanged&&au({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){xs(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 ah}from"@roll-agent/sdk";import{z as oh}from"zod";var ih,sh=oh.object({success:oh.boolean(),username:oh.string(),usedSelector:oh.string().optional(),usedStrategy:oh.string().optional(),source:oh.string().optional(),error:oh.string().optional()});function ch(){return{openNativePagePort:il,pickBestUsername:Ws,createNativeVisualActivitySession:e=>new lo(e),...ih}}var lh=ah({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:oh.object({}),output:sh,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const n=ch();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 xs(e),await(a?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}finally{r?.close()}}});import{defineTool as dh}from"@roll-agent/sdk";import{z as uh}from"zod";var mh,ph=["unknown","top","middle","bottom","only-page"],fh=uh.object({containerFound:uh.boolean(),containerLabel:uh.string(),scrollTop:uh.number(),scrollHeight:uh.number(),clientHeight:uh.number(),itemCount:uh.number(),atStart:uh.boolean(),atEnd:uh.boolean()}),gh=uh.object({success:uh.boolean(),surface:uh.enum(as),direction:uh.enum(["up","down"]),stepsRequested:uh.number(),stepsCompleted:uh.number(),reachedBoundary:uh.boolean(),atTop:uh.boolean(),atBottom:uh.boolean(),canScrollUp:uh.boolean(),canScrollDown:uh.boolean(),position:uh.enum(ph),before:fh,after:fh,error:uh.string().optional()});function hh(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...mh}}function yh(){return{containerFound:!1,containerLabel:"",scrollTop:0,scrollHeight:0,clientHeight:0,itemCount:0,atStart:!0,atEnd:!0}}function bh(e){return e.containerFound?e.atStart&&e.atEnd?"only-page":e.atStart?"top":e.atEnd?"bottom":"middle":"unknown"}function wh(e){return{atTop:e.containerFound&&e.atStart,atBottom:e.containerFound&&e.atEnd,canScrollUp:e.containerFound&&!e.atStart,canScrollDown:e.containerFound&&!e.atEnd,position:bh(e)}}var vh=dh({name:"zhipin_scroll_view",description:"滚动或检查 BOSS直聘页面内部动态列表容器。用于调试或显式翻页,支持 chat-list、chat-history、recommend-list;steps=0 时只返回当前位置和顶部/底部边界。",input:uh.object({surface:uh.enum(as).describe("要滚动的页面区域"),direction:uh.enum(["up","down"]).optional().describe("滚动方向;不传则使用该区域默认方向"),steps:uh.number().int().min(0).max(20).default(1).describe("滚动步数;传 0 时不滚动,只检查当前列表是否在顶部/底部"),distance:uh.number().int().positive().optional().describe("每步滚动像素;不传则按容器高度估算"),settleMs:uh.number().int().min(0).max(5e3).default(700).describe("每步后等待 DOM 更新的毫秒数")}),output:gh,execute:async(e,t)=>{const n=hh(),r=is(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,...wh(d.after),before:d.before,after:d.after}):(await c.fail("列表未加载"),{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...wh(d.after),before:d.before,after:d.after,error:"列表未加载"})}catch(t){xs(t),await(c?.fail("滚动失败"));const n=yh();return{success:!1,surface:e.surface,direction:a,stepsRequested:o,stepsCompleted:0,reachedBoundary:!0,...wh(n),before:n,after:n,error:t instanceof Error?t.message:"滚动失败"}}finally{s?.close()}}});import{defineTool as xh}from"@roll-agent/sdk";import{z as Sh}from"zod";var Rh,Ih=Sh.object({applyMode:Sh.enum(As),ageMin:Sh.number().int().min(16).optional(),ageMax:Sh.number().int().min(16).optional(),location:Sh.object({city:Sh.string(),district:Sh.string().optional()}).optional(),optionSelections:Sh.array(Sh.object({fieldKey:Sh.string(),label:Sh.string(),values:Sh.array(Sh.string()),selection:Sh.enum(["single","multi"]),clearValue:Sh.string()}))}),Ch=Sh.object({ageMin:Sh.number().optional(),ageMax:Sh.number().optional(),location:Sh.object({city:Sh.string(),district:Sh.string().optional()}).optional(),optionSelections:Sh.array(Sh.object({fieldKey:Sh.string(),label:Sh.string(),values:Sh.array(Sh.string())})),gender:Sh.string().optional(),activity:Sh.string().optional()}),kh=Sh.object({success:Sh.boolean(),status:Sh.enum(Ns),requested:Ih,applied:Ch.optional(),filterButtonText:Sh.string().optional(),error:Sh.string().optional()}),_h=Sh.string().min(1),Ah=Sh.array(_h).min(1),Eh=Sh.object({applyMode:Sh.enum(As).optional().describe("筛选应用模式:patch 只修改显式传入字段;replace 先点击清除再设置显式传入字段"),ageMin:Sh.number().int().min(16).optional().describe("年龄下限;标准模式未传则不修改,空输入/旧字段兼容模式默认重置为 16"),ageMax:Sh.number().int().min(16).optional().describe("年龄上限;标准模式未传则不修改,旧字段兼容模式未传表示不限"),locationCity:_h.optional().describe("地区筛选城市,如:上海或上海市"),locationDistrict:_h.optional().describe('地区筛选区级选项,如:浦东新区;传 "不限" 表示城市不限区'),gender:Sh.enum(ks).optional().describe("性别筛选:不限、男、女"),activity:Sh.enum(_s).optional().describe("活跃度[单选],只支持:不限、刚刚活跃、今日活跃、3日内活跃、本周活跃、本月活跃"),major:Ah.optional().describe('专业筛选,可传多个可见专业选项;传 ["不限"] 表示清空'),recentNotView:_h.optional().describe("近期没有看过筛选,如:不限、近14天没有"),exchangeResumeWithColleague:_h.optional().describe("是否与同事交换简历筛选,如:不限、近一个月没有"),candidateKeywords:Ah.optional().describe('牛人关键词筛选,可传多个可见关键词;传 ["不限"] 表示清空'),school:Ah.optional().describe('院校筛选,可传多个可见选项;传 ["不限"] 表示清空'),switchJobFrequency:_h.optional().describe("跳槽频率[单选],如:不限、5年少于3份、平均每份工作大于1年"),intention:Ah.optional().describe('求职意向筛选,可传多个选项;传 ["不限"] 表示清空'),salary:_h.optional().describe("薪资待遇[单选],如:不限、3-5K、5-10K"),degree:Ah.optional().describe('学历要求筛选,可传多个选项;传 ["不限"] 表示清空'),experience:Ah.optional().describe('经验要求筛选,可传多个选项;传 ["不限"] 表示清空'),callPhone:_h.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"}),Nh=350,Mh=130,Ph=600;function Th(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...Rh}}function Oh(e,t){const n=Ms(e);return{fieldKey:e,label:n.label,values:t,selection:n.selection,clearValue:n.clearValue}}function Bh(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 Dh(e){return void 0!==e.ageMin||void 0!==e.ageMax||void 0!==e.gender||void 0!==e.activity}function $h(e,t,n){if(void 0===n)return;const r=Array.isArray(n)?n:[n];e.push(Oh(t,r))}function jh(e){const t=[];if(void 0===e.applyMode&&!Bh(e)&&Dh(e)||!Bh(e)&&!Dh(e)){const n=e.gender??"不限",r=e.activity??"不限";return t.push(Oh("gender",[n])),t.push(Oh("activity",[r])),{applyMode:"patch",ageMin:e.ageMin??16,...void 0!==e.ageMax?{ageMax:e.ageMax}:{},optionSelections:t}}$h(t,"gender",e.gender),$h(t,"activity",e.activity),$h(t,"major",e.major),$h(t,"recentNotView",e.recentNotView),$h(t,"exchangeResumeWithColleague",e.exchangeResumeWithColleague),$h(t,"candidateKeywords",e.candidateKeywords),$h(t,"school",e.school),$h(t,"switchJobFrequency",e.switchJobFrequency),$h(t,"intention",e.intention),$h(t,"salary",e.salary),$h(t,"degree",e.degree),$h(t,"experience",e.experience),$h(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 Fh(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 Lh(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 qh(e){const t=Lh(e.applied);return{success:"applied"===e.status,status:e.status,requested:Fh(e.requested),...void 0!==t?{applied:t}:{},...void 0!==e.filterButtonText?{filterButtonText:e.filterButtonText}:{},...void 0!==e.error?{error:e.error}:{}}}var zh=xh({name:"zhipin_filter_recommend_candidates",description:"在 BOSS「推荐牛人」页用 native CDP 设置地区、年龄、性别、活跃度、专业、关键词、薪资、学历、经验等筛选条件并提交。",input:Eh,output:kh,execute:async(e,t)=>{const n=Th(),r=jh(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 $r(a),await o.begin("正在打开推荐筛选");if(!await a.waitForRecommendList(3e3))return await o.fail("推荐牛人页未就绪"),qh({status:"recommend_not_ready",requested:r,error:"推荐牛人页未就绪"});await o.begin("正在设置推荐筛选"),await o.highlightSelector(Cs.recommend.filterButton,{label:"正在设置推荐筛选",padding:8});const e=await a.applyRecommendFilter(r,{preClickDelayMs:Nh,pressDurationMs:Mh,settleMs:Ph,...void 0!==o?{motionObserver:o}:{}});return"applied"===e.status?await o.succeed("已应用推荐筛选"):await o.fail(e.error??e.status),qh(e)}catch(e){xs(e);const t=e instanceof Error?e.message:"推荐筛选失败";return await(o?.fail(t)),qh({status:"error",requested:r,error:t})}finally{a?.close()}}});import{defineTool as Vh}from"@roll-agent/sdk";import{z as Jh}from"zod";var Uh=["target-count","boundary","no-new-items","max-steps"];Ce();import{AsyncLocalStorage as Hh}from"node:async_hooks";var Wh="@c",Kh="@j",Gh="legacy",Yh=/^@?c([1-9]\d*)$/i,Xh=/^@?j([1-9]\d*)$/i,Zh=new Map,Qh=new Map,ey=new Hh;function ty(e){if(!Number.isInteger(e)||e<0)throw new Error("候选人索引必须是从 0 开始的非负整数");return`${Wh}${String(e+1)}`}function ny(e){if(!Number.isInteger(e)||e<0)throw new Error("岗位索引必须是从 0 开始的非负整数");return`${Kh}${String(e+1)}`}function ry(e){const t=Yh.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function ay(e){const t=Xh.exec(e.trim()),n=t?.[1];if(!n)return;const r=Number(n);return Number.isInteger(r)?r-1:void 0}function oy(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 cy(e.candidateRef).index}function iy(e){const t=e.map((e,t)=>({...e,candidateRef:ty(t)}));return Zh.set(fy(),new Map(t.map(e=>[e.candidateRef,e]))),t}function sy(e){const t=e.map((e,t)=>({...e,jobRef:ny(t)}));return Qh.set(fy(),new Map(t.map(e=>[e.jobRef,e]))),t}function cy(e){const t=ry(e);if(void 0===t)throw new Error(`candidateRef "${e}" 格式无效,应类似 @c1`);const n=ty(t);return Zh.get(fy())?.get(n)??{index:t,candidateRef:n,candidateId:""}}function ly(e){const t=ay(e);if(void 0===t)throw new Error(`jobRef "${e}" 格式无效,应类似 @j1`);const n=ny(t),r=Qh.get(fy())?.get(n);if(void 0===r)throw new Error(`岗位引用 ${n} 已过期,请先调用 zhipin_list_recommend_jobs`);return r}function dy(e){const t=[];for(const n of e.indices??[])t.push({index:oy({index:n}),candidateRef:ty(n),candidateId:""});for(const n of e.candidateRefs??[])t.push(cy(n));if(0===t.length)throw new Error("必须提供 indices 或 candidateRefs");return my(t)}function uy(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)||py(e.name)===py(t.name))}function my(e){const t=new Map;for(const n of e)t.has(n.candidateRef)||t.set(n.candidateRef,n);return[...t.values()]}function py(e){return e.trim().toLocaleLowerCase("zh-CN")}function fy(){const e=ey.getStore();if(void 0!==e)return e;try{return xe().id}catch{return Gh}}var gy,hy=Jh.object({index:Jh.number(),candidateRef:Jh.string(),candidateId:Jh.string(),name:Jh.string(),age:Jh.string(),experience:Jh.string(),education:Jh.string(),workStatus:Jh.string(),company:Jh.string(),currentPosition:Jh.string(),expectedLocation:Jh.string(),expectedPosition:Jh.string(),expectedSalary:Jh.string(),tags:Jh.array(Jh.string()),buttonText:Jh.string()}),yy=Jh.object({containerLabel:Jh.string(),stepsRequested:Jh.number(),stepsCompleted:Jh.number(),reachedBoundary:Jh.boolean(),stopReason:Jh.enum(Uh),uniqueCount:Jh.number(),duplicateCount:Jh.number(),noNewRounds:Jh.number(),beforeItemCount:Jh.number(),afterItemCount:Jh.number(),beforeScrollHeight:Jh.number(),afterScrollHeight:Jh.number()}),by=Jh.object({success:Jh.boolean(),candidates:Jh.array(hy),total:Jh.number(),scrollStats:yy.optional(),error:Jh.string().optional()});function wy(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...gy}}var vy=Vh({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:Jh.object({maxResults:Jh.number().optional().describe("最多返回条数"),autoScroll:Jh.boolean().default(!0).describe("是否自动向下滚动动态列表并合并采集结果"),maxScrolls:Jh.number().int().min(0).max(50).default(4).describe("自动滚动的最大步数")}),output:by,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page through native backend");const n=wy();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=iy(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 xs(e),await(a?.fail("读取推荐列表失败")),{success:!1,candidates:[],total:0,error:e instanceof Error?e.message:"读取推荐列表失败"}}finally{r?.close()}}});import{defineTool as xy}from"@roll-agent/sdk";import{z as Sy}from"zod";var Ry,Iy=["listed","recommend_not_ready","selector_not_found"],Cy=Sy.object({index:Sy.number(),jobRef:Sy.string().optional(),value:Sy.string(),label:Sy.string(),isCurrent:Sy.boolean()}),ky=Sy.object({success:Sy.boolean(),status:Sy.enum(Iy),current:Cy.optional(),jobs:Sy.array(Cy),availableCount:Sy.number(),canSwitch:Sy.boolean(),error:Sy.string().optional()}),_y=450,Ay=140,Ey=750;function Ny(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...Ry}}function My(e,t){return{index:e.index,...void 0!==t?{jobRef:t}:{},value:e.value,label:e.label,isCurrent:e.isCurrent}}function Py(e,t){return e.value.length>0&&t.value.length>0?e.value===t.value:e.index===t.index&&e.label===t.label}function Ty(e){const t=sy(e.options),n=e.options.map((e,n)=>My(e,t[n]?.jobRef));let r;if(void 0!==e.current){const t=e.current;r=n.find(e=>Py(e,t))??My(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 Oy=xy({name:"zhipin_list_recommend_jobs",description:"读取 BOSS「推荐牛人」页顶部招聘岗位下拉选项,只读不切换;返回 jobRef/jobValue/current/canSwitch。",input:Sy.object({}),output:ky,execute:async(e,t)=>{const n=Ny();let r,a;t.logger.info("Listing Boss recommend jobs through native CDP");try{r=await n.openNativePagePort(),a=n.createNativeVisualActivitySession(r),await $r(r),await a.begin("正在读取推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在读取推荐岗位",padding:8});const e=await r.listRecommendJobs({preClickDelayMs:_y,pressDurationMs:Ay,settleMs:Ey,...void 0!==a?{motionObserver:a}:{}});return e.success?await a.succeed(`已读取 ${e.availableCount} 个推荐岗位`):await a.fail(e.error??"读取推荐岗位失败"),Ty(e)}catch(e){xs(e);const t=e instanceof Error?e.message:"读取推荐岗位失败";return await(a?.fail(t)),Ty({success:!1,status:"selector_not_found",options:[],availableCount:0,canSwitch:!1,error:t})}finally{r?.close()}}});import{BrowserPageInfoSchema as By}from"@roll-agent/browser";import{defineTool as Dy}from"@roll-agent/sdk";import{z as $y}from"zod";Ce();var jy,Fy=$y.object({success:$y.boolean(),alreadyOnRecommend:$y.boolean(),usedSidebarClick:$y.boolean(),recommendReady:$y.boolean(),page:By.optional(),error:$y.string().optional()});function Ly(){return{getContextManager:pe,openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...jy}}async function qy(e,t){return ur(e,await t.inspectPage())}var zy=Dy({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:$y.object({}),output:Fy,execute:async(e,t)=>{const n=Ly(),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 $r(a);const e="正在切换到推荐牛人页";if(await o.begin(e),await o.highlightSelector(Cs.nav.sidebar,{label:e,padding:10}),await a.isRecommendSurfaceOpen())return await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await qy(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 qy(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 qy(r,a)}):(await o.fail("推荐牛人页未就绪"),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1,page:await qy(r,a),error:"推荐牛人页未就绪"})}catch(e){return xs(e),await(o?.fail("切换推荐牛人页失败")),{success:!1,alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1,error:e instanceof Error?e.message:"切换推荐牛人页失败"}}finally{a?.close()}}});import{defineTool as Vy}from"@roll-agent/sdk";import{z as Jy}from"zod";var Uy,Hy=["selected","already_selected","not_found","recommend_not_ready","selector_not_found"],Wy=Jy.object({index:Jy.number(),value:Jy.string(),label:Jy.string(),isCurrent:Jy.boolean()}),Ky=Jy.object({jobRef:Jy.string().optional(),jobValue:Jy.string().optional(),jobName:Jy.string().optional(),index:Jy.number().optional(),searchKeyword:Jy.string().optional(),useSearch:Jy.boolean().optional(),forceClick:Jy.boolean().optional()}),Gy=Jy.object({success:Jy.boolean(),status:Jy.enum(Hy),requested:Ky,current:Wy.optional(),selected:Wy.optional(),options:Jy.array(Wy),matchedCount:Jy.number(),error:Jy.string().optional()}),Yy=Jy.object({jobRef:Jy.string().regex(Xh,"jobRef 应类似 @j1").optional().describe("岗位语义引用,如 @j1;来自 zhipin_list_recommend_jobs 输出,优先级最高"),jobValue:Jy.string().min(1).optional().describe("岗位下拉 li.job-item 的 value,来自本工具或页面 DOM;最稳定,优先匹配"),jobName:Jy.string().min(1).optional().describe("岗位标题/名称,缺少 jobValue 时用于文本匹配"),index:Jy.number().int().min(0).optional().describe("当前岗位下拉快照里的 index,仅在缺少 jobValue/jobName 时兜底"),searchKeyword:Jy.string().min(1).optional().describe("下拉搜索框关键词;不传时用 jobName 作为搜索关键词"),useSearch:Jy.boolean().default(!0).describe("初始可见项未命中时是否使用下拉搜索框收敛候选项"),forceClick:Jy.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 至少需要提供一个"}),Xy=900,Zy=180,Qy=1400;function eb(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),...Uy}}function tb(e){if(void 0!==e.jobRef){const t=ly(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 nb(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 rb(e){return{index:e.index,value:e.value,label:e.label,isCurrent:e.isCurrent}}function ab(e){return{success:e.success,status:e.status,requested:e.requested,...void 0!==e.current?{current:rb(e.current)}:{},...void 0!==e.selected?{selected:rb(e.selected)}:{},options:e.options.map(rb),matchedCount:e.matchedCount,...void 0!==e.error?{error:e.error}:{}}}var ob=Vy({name:"zhipin_select_recommend_job",description:"在 BOSS「推荐牛人」页切换顶部招聘岗位筛选。优先传 jobRef,其次 jobValue,再次 jobName,index 仅作当前快照兜底。",input:Yy,output:Gy,execute:async(e,t)=>{const n=eb();let r,a;try{const o=tb(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 $r(r),await a.begin("正在选择推荐岗位"),await a.highlightSelector(".job-selecter-wrap",{label:"正在选择推荐岗位",padding:8});const i=await r.selectRecommendJob(o,{preClickDelayMs:Xy,pressDurationMs:Zy,settleMs:Qy,...void 0!==a?{motionObserver:a}:{}});return i.success?await a.succeed("already_selected"===i.status?"推荐岗位已是目标岗位":"已选择推荐岗位"):await a.fail(i.error??"选择推荐岗位失败"),ab(i)}catch(t){xs(t);const n=t instanceof Error?t.message:"选择推荐岗位失败";return await(a?.fail(n)),ab({success:!1,status:void 0!==e.jobRef?"not_found":"selector_not_found",requested:nb(e),options:[],matchedCount:0,error:n})}finally{r?.close()}}});import{defineTool as ib}from"@roll-agent/sdk";import{z as sb}from"zod";var cb,lb=sb.object({index:sb.number(),candidateRef:sb.string(),candidateName:sb.string(),candidateId:sb.string(),success:sb.boolean(),error:sb.string().optional()}),db=sb.object({success:sb.boolean(),results:sb.array(lb),summary:sb.object({total:sb.number(),succeeded:sb.number(),failed:sb.number()})}),ub=[1400,1800,2200],mb=450,pb=140,fb=750,gb=sb.object({indices:sb.array(sb.number().int().min(0)).min(1).optional().describe("要打招呼的候选人索引列表"),candidateRefs:sb.array(sb.string().regex(Yh,"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 hb(){return{openNativePagePort:il,createNativeVisualActivitySession:e=>new lo(e),sleep:async e=>await new Promise(t=>{setTimeout(t,e)}),...cb}}var yb=ib({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:gb,output:db,execute:async(e,t)=>{const n=dy(e);t.logger.info(`Saying hello to ${n.length} candidates through native CDP`);const r=hb();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=ub[(e-1)%ub.length]??ub[0];await r.sleep(t)}if(bb(s)){const e=await a.inspectRecommendCard(s.index);if(!uy(s,e)){await a.assertNotRestricted(),i.push({index:s.index,candidateRef:s.candidateRef,candidateName:e.name,candidateId:e.candidateId,success:!1,error:`候选人引用 ${s.candidateRef} 已过期,请重新获取推荐列表`});continue}}const n=await a.clickRecommendGreet(s.index,{preClickDelayMs:mb,pressDurationMs:pb,settleMs:fb,...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}:{}}),ru(n,t.logger),n.clicked||await a.assertNotRestricted()}const s={total:i.length,succeeded:i.filter(e=>e.success).length,failed:i.filter(e=>!e.success).length};return 0===s.failed?await o.succeed(`已完成 ${s.succeeded}/${s.total} 位候选人`):await o.fail(`已完成 ${s.succeeded}/${s.total} 位候选人`),{success:0===s.failed,results:i,summary:s}}catch(e){xs(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 bb(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{defineTool as wb}from"@roll-agent/sdk";import{z as vb}from"zod";var xb,Sb=vb.object({success:vb.boolean(),candidateRef:vb.string().optional(),candidateName:vb.string(),candidateId:vb.string(),resumeReady:vb.boolean().optional(),error:vb.string().optional()}),Rb=vb.object({index:vb.number().int().min(0).optional().describe("候选人在列表中的 0-based 索引"),candidateRef:vb.string().regex(Yh,"candidateRef 应类似 @c1").optional().describe("候选人语义引用,如 @c1;来自 zhipin_get_candidate_list 输出")}).refine(e=>void 0!==e.index||void 0!==e.candidateRef,"必须提供 index 或 candidateRef");function Ib(){return{openNativePagePort:il,...xb}}var Cb=wb({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:Rb,output:Sb,execute:async(e,t)=>{const n=_b(e),r=n.index,a=n.candidateRef;t.logger.info(`Opening resume for candidate ${a} at index ${String(r)} through native backend`);const o=Ib();let i,s;try{i=await o.openNativePagePort(),s=new lo(i),await s.begin("正在打开简历详情");if(!await i.waitForRecommendList())return await s.fail("推荐列表未加载"),kb(a,"推荐列表未加载");const e=await i.inspectRecommendCard(r);if(!e.found){const t=e.error??`候选人引用 ${a} 超出范围`;return await s.fail(t),kb(a,t)}if(Ab(n)&&!uy(n,e)){const t=`候选人引用 ${a} 已过期,请重新获取推荐列表`;return await s.fail(t),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:t}}if(!await i.clickRecommendCardSurface(r))return await s.fail("未能点击候选人卡片"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,error:"未能点击候选人卡片"};const c=await i.waitForResumeDialog(12e3),l=c.canvasReady;return c.iframeFound?(await s.succeed(`已打开 ${e.name} 的简历`),t.logger.info(`Opened resume for ${e.name}`),{success:!0,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:l,...l?{}:{error:"简历弹窗已出现,但 canvas 尚未就绪"}}):(await s.fail("简历弹窗未出现"),{success:!1,candidateRef:a,candidateName:e.name,candidateId:e.candidateId,resumeReady:!1,error:"点击后简历弹窗未出现"})}catch(e){return xs(e),await(s?.fail("打开简历失败")),t.logger.warn(`Native zhipin open resume failed: ${e instanceof Error?e.message:String(e)}`),kb(a,"打开简历失败,请重试")}finally{i?.close()}}});function kb(e,t){return{success:!1,...void 0!==e?{candidateRef:e}:{},candidateName:"",candidateId:"",error:t}}function _b(e){if(void 0!==e.candidateRef)return cy(e.candidateRef);const t=oy(e);return{index:t,candidateRef:ty(t),candidateId:""}}function Ab(e){return e.candidateId.length>0||void 0!==e.name&&e.name.length>0}import{mkdir as Eb,writeFile as Nb}from"node:fs/promises";import{tmpdir as Mb}from"node:os";import{dirname as Pb,isAbsolute as Tb,join as Ob}from"node:path";import{defineTool as Bb}from"@roll-agent/sdk";import{z as Db}from"zod";var $b,jb="data:image/png;base64,",Fb=Db.object({success:Db.boolean(),imagePath:Db.string().optional(),captureMode:Db.enum(["canvas-data-url","viewport-clip","dom-screenshot"]).optional(),canvasSize:Db.object({width:Db.number(),height:Db.number()}).optional(),note:Db.string().optional(),error:Db.string().optional(),mcpImages:Db.array(Db.object({data:Db.string(),mimeType:Db.string()})).optional().describe("截图的 MCP image content(base64);由 SDK 转为 image block 返回,调用方无需解析")});function Lb(){return{openNativePagePort:il,writePngFile:async(e,t)=>{await Eb(Pb(e),{recursive:!0}),await Nb(e,Buffer.from(t,"base64"))},now:()=>Date.now(),...$b}}var qb=Bb({name:"zhipin_capture_resume",description:"截取当前打开的简历详情弹窗中的 canvas 简历为 PNG 图片文件并返回 imagePath。简历内容是 canvas 图像渲染,无法从 DOM/文本读取;编排器应在调用后用自身的多模态图像能力读取该文件来理解简历内容。需先用 zhipin_open_resume 打开简历弹窗",input:Db.object({outputPath:Db.string().min(1).refine(e=>Tb(e),{message:"outputPath 必须是绝对路径"}).optional().describe("PNG 输出文件的绝对路径;缺省时写入系统临时目录")}),output:Fb,execute:async(e,t)=>{t.logger.info("Capturing resume canvas through native backend");const n=Lb();let r,a;try{r=await n.openNativePagePort(),a=new lo(r),await a.begin("正在读取简历");const o=await r.waitForResumeDialog(12e3);if(!o.iframeFound)return await a.fail("简历弹窗未打开"),{success:!1,error:"简历弹窗未打开,请先调用 zhipin_open_resume"};if(!o.canvasReady)return await a.fail("简历内容未加载完成"),{success:!1,error:"简历 canvas 未加载完成,请稍后重试"};const i=await r.captureResumeCanvas(async e=>{await(a?.begin(Vb(e)))});if(!i.found||void 0===i.canvasSize)return await a.fail("未找到简历内容"),{success:!1,error:i.error??"未找到简历 canvas"};let s,c,l;if(await r.assertNotRestricted(),!0!==i.blank&&void 0!==i.dataUrl&&i.dataUrl.startsWith(jb))s=i.dataUrl.slice(jb.length),c="canvas-data-url";else if(!0===i.blank){const e=await r.readResumeDialogClipArea();if(void 0===e)return await a.fail("未找到简历弹窗区域"),{success:!1,error:"简历 canvas 为空白且未找到弹窗区域,无法截图"};await Jb(a),s=await r.captureViewportClip(e,2),c="dom-screenshot",l="此简历为 DOM 文本渲染(canvas 为空),已改为截取弹窗可见区域;若弹窗内有滚动内容可能未完整覆盖"}else{if(void 0===i.screenshotArea)return await a.fail("简历坐标不完整"),{success:!1,error:"简历 canvas 坐标不完整,无法截图"};await Jb(a),s=await r.captureViewportClip(i.screenshotArea),c="viewport-clip",l=`canvas.toDataURL 不可用${void 0!==i.dataUrlError?`(${i.dataUrlError})`:""},已回退为视口区域截图,超出可视区的简历内容可能被截断`}const d=e.outputPath??Ob(Mb(),"roll-browser-use",`resume-${String(n.now())}.png`);return await n.writePngFile(d,s),await a.succeed("简历读取完成"),t.logger.info(`Resume captured to ${d} (${c})`),{success:!0,imagePath:d,captureMode:c,canvasSize:i.canvasSize,...void 0!==l?{note:l}:{},mcpImages:[{data:s,mimeType:"image/png"}]}}catch(e){return xs(e),await(a?.fail("简历读取失败")),t.logger.warn(`Native zhipin capture resume failed: ${e instanceof Error?e.message:String(e)}`),{success:!1,error:"简历读取失败,请重试"}}finally{r?.close()}}}),zb=240;function Vb(e){if(e.totalPx>0){const t=Math.min(99,Math.round(e.scrolledPx/e.totalPx*100));return t>0?`正在读取简历 ${String(t)}%`:"正在读取简历"}return`正在读取简历 · 第 ${String(e.round+1)} 屏`}async function Jb(e){await e.clear()&&await new Promise(e=>setTimeout(e,zb))}import{defineTool as Ub}from"@roll-agent/sdk";import{z as Hb}from"zod";var Wb,Kb=Hb.object({success:Hb.boolean(),closed:Hb.boolean(),method:Hb.enum(["close-button","escape"]).optional(),error:Hb.string().optional()});function Gb(){return{openNativePagePort:il,...Wb}}var Yb=Ub({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:Hb.object({}),output:Kb,execute:async(e,t)=>{t.logger.info("Closing resume detail modal through native backend");const n=Gb();let r;try{r=await n.openNativePagePort();const e=await r.closeResumeDialog();return e.closed?(t.logger.info(`Resume modal closed via ${e.method??"unknown"}`),{success:!0,closed:!0,...void 0!==e.method?{method:e.method}:{}}):{success:!1,closed:!1,error:e.error??"简历弹窗未关闭"}}finally{r?.close()}}});Ce();import{defineTool as Xb}from"@roll-agent/sdk";import{z as Zb}from"zod";import{waitForSelector as Qb}from"@roll-agent/browser";var ew={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 tw,waitForSelector as nw}from"@roll-agent/browser";var rw="https://www.yupao.com",aw=`${rw}/chat`,ow=`${rw}/login`;async function iw(e,t={}){e.url().includes("/chat")||await tw(e,aw,t),await nw(e,ew.messageList.container,{timeout:15e3})}async function sw(e,t,n={}){const r=`${rw}/chat?id=${encodeURIComponent(t)}`;await tw(e,r,n),await nw(e,ew.chat.input,{timeout:15e3})}async function cw(e,t){await iw(e),await Qb(e,ew.messageList.item,{timeout:1e4});const n=ew.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 lw=Zb.object({limit:Zb.number().optional().describe("最多返回的消息条数")}),dw=Zb.object({conversationId:Zb.string(),candidateName:Zb.string(),lastMessage:Zb.string(),unreadCount:Zb.number(),timestamp:Zb.string()}),uw=Zb.object({messages:Zb.array(dw),total:Zb.number()}),mw=Xb({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:lw,output:uw,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=pe(),r=await n.getPage("yupao"),a=await cw(r,e.limit);return t.logger.info(`Found ${a.length} messages`),{messages:a.map(e=>({...e})),total:a.length}}});Ce();import{defineTool as pw}from"@roll-agent/sdk";import{z as fw}from"zod";import{BrowserActionApprovalSchema as gw}from"@roll-agent/browser";import{clickElement as hw,isBrowserActionPolicyError as yw,typeText as bw,waitForSelector as ww}from"@roll-agent/browser";async function vw(e,t,n,r={}){try{return await sw(e,t,r),await bw(e,ew.chat.input,n,r),await hw(e,ew.chat.sendButton,r),await ww(e,ew.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){if(yw(e))throw e;return{success:!1,error:e instanceof Error?e.message:String(e)}}}var xw=fw.object({conversationId:fw.string().describe("对话 ID"),message:fw.string().describe("要发送的回复消息"),browserActionApproval:gw.optional().describe("当 actionPolicy=confirm 返回 needs_confirmation 后,由 orchestrator 原样带回的批准 ID。")}),Sw=fw.object({success:fw.boolean(),conversationId:fw.string(),sentMessage:fw.string(),error:fw.string().optional()}),Rw=pw({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:xw,output:Sw,execute:async(e,t)=>{const{conversationId:n,message:r}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const a=pe(),o=me(),i=Tr(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 vw(s,n,r,Pr(t,o,{approval:e.browserActionApproval,approvedByConfirmation:i.approvedByConfirmation}))}catch(e){const t=Or(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}}});Ce();import{BrowserRuntimeConfigSchema as Iw,BrowserRuntimeModeSchema as Cw,BrowserChannelSchema as kw,BrowserProfileColorSchema as _w,BrowserWindowBoundsSchema as Aw,PlatformSchema as Ew}from"@roll-agent/browser";import{z as Nw}from"zod";var Mw=Nw.object({platform:Ew.optional(),mode:Cw.default("managed-cdp"),headless:Nw.boolean().optional(),cdpUrl:Nw.string().optional(),cdpHost:Nw.string().default("127.0.0.1"),cdpPort:Nw.number().int().min(1).max(65535).optional(),channel:kw.default("chrome"),executablePath:Nw.string().optional(),userDataDir:Nw.string().trim().min(1),sessionsDir:Nw.string().trim().min(1).optional(),args:Nw.array(Nw.string()).optional(),profileName:Nw.string().trim().min(1).optional(),profileColor:_w.optional(),windowBounds:Aw.optional(),trackingAgentId:Nw.string().trim().min(1).optional()}).superRefine((e,t)=>{"managed-cdp"===e.mode&&void 0===e.cdpPort&&t.addIssue({code:Nw.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:Nw.ZodIssueCode.custom,path:["cdpUrl"],message:`${e.mode} browser instance requires cdpUrl`})}),Pw=Nw.object({defaultInstance:Nw.string().trim().min(1).optional(),instances:Nw.record(Nw.string(),Mw).default({})}).superRefine((e,t)=>{void 0!==e.defaultInstance&&void 0===e.instances[e.defaultInstance]&&t.addIssue({code:Nw.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:Nw.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:Nw.ZodIssueCode.custom,path:["instances",a,"userDataDir"],message:`userDataDir is already used by browser instance "${e}"`}):r.set(o.userDataDir,a)}});function Tw(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 Ow(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 Bw(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 Dw(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 $w(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 Pw.parse(t)}function jw(e=process.env){return Iw.parse({mode:e.BROWSER_MODE,headless:Tw(e.BROWSER_HEADLESS),cdpUrl:e.BROWSER_CDP_URL,cdpHost:e.BROWSER_CDP_HOST,cdpPort:Ow(e.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:e.BROWSER_CHANNEL,executablePath:e.BROWSER_EXECUTABLE_PATH,userDataDir:e.BROWSER_USER_DATA_DIR,args:Bw(e.BROWSER_ARGS_JSON),profileColor:e.BROWSER_PROFILE_COLOR,sessionsDir:e.BROWSER_SESSIONS_DIR,security:Dw(e.BROWSER_SECURITY_JSON)})}function Fw(e=process.env){return $w(e.BROWSER_INSTANCES_JSON)}le();import{z as Lw}from"zod";import{StructuredToolError as qw}from"@roll-agent/sdk";var zw=new Map;async function Vw(e,t,n={}){const{signal:r,onWait:a}=n;Hw(e,r,0);const o=zw.get(e);let i;const s=new Promise(e=>{i=e});zw.set(e,s);const c=performance.now();if(void 0!==o)try{await Uw(o,r)}catch(t){throw o.then(()=>{i(),Kw(e,s)}),t instanceof Jw&&Ww(e,Gw(c)),t}try{return void 0!==o&&a?.(Gw(c)),Hw(e,r,Gw(c)),await t()}finally{i(),Kw(e,s)}}var Jw=class extends Error{constructor(){super("Aborted while queued"),this.name="QueueAbortError"}};async function Uw(e,t){if(void 0!==t){if(t.aborted)throw new Jw;await new Promise((n,r)=>{const a=()=>{r(new Jw)};t.addEventListener("abort",a,{once:!0}),e.then(()=>{t.removeEventListener("abort",a),t.aborted?r(new Jw):n()})})}else await e}function Hw(e,t,n){!0===t?.aborted&&Ww(e,n)}function Ww(e,t){throw new qw({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 Kw(e,t){zw.get(e)===t&&zw.delete(e)}function Gw(e){return performance.now()-e}Ce(),Ce();import{StructuredToolError as Yw}from"@roll-agent/sdk";function Xw(e){if(void 0===e)return;const t=ye().getBundle();if(void 0!==t.platform&&t.platform!==e)throw new Yw({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 Zw(e){if("object"!=typeof e||null===e||!("platform"in e))return;const t=e.platform;return"zhipin"===t||"yupao"===t?t:void 0}var Qw={browserInstance:Lw.string().trim().min(1).optional().describe("可选 browser instance id;多浏览器实例时用于选择目标 profile/CDP runtime。")};function ev(e){return e.startsWith("zhipin_")?"zhipin":e.startsWith("yupao_")?"yupao":void 0}function tv(e,t={}){const n=rv(e.input);if(void 0===n)return e;const r=t.startRuntime??!0,a=t.serializePageOps??!0,o=t.expectedPlatform??ev(e.name);return{...e,input:n,execute:async(t,n)=>{const i=t;return await U(i.browserInstance,async()=>{void 0!==i.browserInstance&&ye().getBundle(),Xw(nv(t,o));const s=async()=>(r&&await be(),await e.execute(t,n));if(!a)return await s();const c=ye().getBundle().id;return await Vw(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 nv(e,t){const n=Zw(e);return t??n}function rv(e){if(e instanceof Lw.ZodObject)return e.extend(Qw);if(e instanceof Lw.ZodEffects){const t=rv(e.innerType());if(void 0===t)return;const n=e._def.effect;return"preprocess"===n.type?Lw.ZodEffects.createWithPreprocess(n.transform,t,e._def):Lw.ZodEffects.create(t,n,e._def)}}var av=tn("browser-use-agent");async function ov(){const e=(await Promise.allSettled([Lf(),Se()])).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 iv(e){return"browser_stop"===e||"zhipin_judge_prepared_reply"===e||"browser_workflow_list"===e||"browser_workflow_save_draft"===e||"browser_workflow_set_status"===e}var sv=new Set(["browser_status","list_pages","attach_browser_session","zhipin_diagnose_browser_state"]);function cv(e){return iv(e.name)?e:tv(e,{startRuntime:"browser_status"!==e.name&&!["browser_execute","browser_workflow_validate","browser_workflow_run"].includes(e.name),serializePageOps:!sv.has(e.name)})}var lv=en({name:"browser-use-agent",tools:[Un,gr,ea,wa,Ea,Da,go,Oo,zo,Ho,Hi,Wi,Ki,Gi,Yi,Cd,mu,Su,Au,cm,kp,Jp,Ug,rh,lh,vh,zy,Oy,ob,zh,vy,yb,Cb,qb,Yb,mw,Rw,on,Qn].map(cv)},{onShutdown:ov});async function dv(){Fn(jn()),Ff({logger:av}),await ue(jw(),Fw());try{await ug(),fe(!0)}catch(e){fe(!1),av.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await lv.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}dv().catch(async e=>{av.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await ov().catch(()=>{}),process.exit(1)});
|