@roll-agent/browser-use-agent 0.7.1 → 0.7.2
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 +63 -13
- package/dist/diagnostics/effective-env.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/pages/zhipin/chat-navigation.d.ts +4 -0
- package/dist/pages/zhipin/chat-target.d.ts +4 -0
- package/dist/pages/zhipin/selectors.d.ts +5 -0
- package/dist/pages/zhipin/sidebar-navigation.d.ts +14 -0
- package/dist/tools/browser-status.d.ts +2 -0
- package/dist/tools/navigate-active-tab.d.ts +13 -0
- package/dist/tools/zhipin-exchange-wechat.d.ts +2 -1
- package/dist/tools/zhipin-get-candidate-info.d.ts +2 -1
- package/dist/tools/zhipin-get-candidate-list.d.ts +14 -1
- package/dist/tools/zhipin-get-username.d.ts +13 -0
- package/dist/tools/zhipin-open-chat-page.d.ts +36 -0
- package/dist/tools/zhipin-open-chat.d.ts +6 -3
- package/dist/tools/zhipin-open-recommend-page.d.ts +38 -0
- package/dist/tools/zhipin-say-hello.d.ts +22 -1
- package/dist/tools/zhipin-send-reply.d.ts +1 -1
- package/dist/visual-activity-session.d.ts +21 -0
- package/dist/visual-activity.d.ts +60 -0
- package/dist/visual-cursor.d.ts +22 -0
- package/package.json +1 -1
- package/references/env.yaml +9 -0
package/SKILL.md
CHANGED
|
@@ -11,15 +11,22 @@ metadata:
|
|
|
11
11
|
|
|
12
12
|
需要先启动 Agent 服务进程(HTTP 常驻),浏览器 session 跨调用持久。
|
|
13
13
|
|
|
14
|
+
默认情况下,agent 会在可见浏览器页面内同时启用两类页内反馈:
|
|
15
|
+
|
|
16
|
+
- `BROWSER_VISUAL_CURSOR`:点击/输入前显示虚拟指针和点击波纹
|
|
17
|
+
- `BROWSER_VISUAL_ACTIVITY`:读取消息列表、识别账号、提取聊天详情等非点击型操作期间,显示状态胶囊、区域柔光和完成态反馈
|
|
18
|
+
|
|
19
|
+
若不需要,可分别设为 `false` 关闭。
|
|
20
|
+
|
|
14
21
|
完整 inputSchema 可通过 `roll agent tools browser-use-agent`(或 `--json`)查询。
|
|
15
22
|
|
|
16
23
|
## 通用 Tools
|
|
17
24
|
|
|
18
|
-
- `browser_status()` — 查询浏览器运行状态和活跃 session;输出含 `replyAuthorityKeysLoaded`(启动期 Reply Authority
|
|
25
|
+
- `browser_status()` — 查询浏览器运行状态和活跃 session;输出含 `replyAuthorityKeysLoaded`(启动期 Reply Authority 公钥是否预加载成功)、`visualCursorEnabled`(页内虚拟指针是否启用)、`visualActivityEnabled`(页内读操作反馈是否启用)和 `effectiveEnvSources`(声明过的 env key 的 `{present, fingerprint}`,SHA256 前 8 位,不泄漏 value)。后者被 `roll doctor` / `roll agent info` 消费,用于检测 env 声明与运行态的 drift
|
|
19
26
|
- `open_platform(platform)` — 通过原生 CDP 打开并聚焦招聘平台主页;登录前不会触发 Playwright attach
|
|
20
27
|
- `list_pages(platform?)` — 通过原生 CDP 列出当前浏览器中可见的页面和 pageId(登录前 `pageId` 即原生 targetId)
|
|
21
28
|
- `select_page(platform, pageId)` — 将指定页面绑定为平台当前活跃页;登录前优先走原生 CDP target 激活
|
|
22
|
-
- `navigate_active_tab(url)` —
|
|
29
|
+
- `navigate_active_tab(url)` — 导航到指定 URL;若 URL 属于 `zhipin/yupao`,优先复用已打开的平台页,避免把无关 tab 导航成第二个平台页
|
|
23
30
|
|
|
24
31
|
## 调试 Tools
|
|
25
32
|
|
|
@@ -27,15 +34,51 @@ metadata:
|
|
|
27
34
|
|
|
28
35
|
## BOSS直聘 — 聊天 Tools
|
|
29
36
|
|
|
30
|
-
- `zhipin_read_messages(limit?, onlyUnread?, sortBy?)` —
|
|
31
|
-
- `
|
|
32
|
-
- `
|
|
37
|
+
- `zhipin_read_messages(limit?, onlyUnread?, sortBy?)` — 读取消息列表中的候选人,默认返回全部消息;若只看未读,显式传 `onlyUnread=true`
|
|
38
|
+
- `zhipin_open_chat_page()` — 通过点击 Boss 左侧导航切换回「沟通」页;优先复用当前已登录的 Boss 页面,不让编排器去猜站内 URL
|
|
39
|
+
- `zhipin_open_chat(conversationId?, candidateName?, index?, preferUnread?)` — 打开指定候选人的聊天窗口;匹配优先级是 `conversationId` > `candidateName` > `index`
|
|
40
|
+
- `zhipin_get_candidate_info(conversationId?, candidateName?, index?, maxMessages?)` — 提取候选人资料、聊天记录,以及当前选中聊天的 `conversationId` / `candidateId`。输出里的 `candidateInfo.communicationPosition`、`candidateInfo.expectedLocation`、`candidateInfo.expectedPosition` 已按“沟通职位 + 最近关注”结构化解析;若 `communicationPosition` 含连字符类分隔符(`-` / `-` / `—` / `–`),则取第一段作为可选 `preferredBrand`,否则不输出该字段
|
|
33
41
|
- `zhipin_send_reply(signedEnvelope, candidateName?, index?)` — 发送消息。只接受 Reply Authority Service 签发的 `signedEnvelope`;本地会先做 Ed25519 验签、过期检查、重放检查、目标绑定校验和 recruiter 绑定校验。启动期公钥预加载失败时直接前置拒绝,错误指向 `browser_status.replyAuthorityKeysLoaded`
|
|
34
|
-
- `zhipin_exchange_wechat(candidateName?, index?)` —
|
|
42
|
+
- `zhipin_exchange_wechat(conversationId?, candidateName?, index?)` — 换微信。若已知 `conversationId`,优先传它;`candidateName/index` 只作兜底
|
|
35
43
|
- `zhipin_get_username()` — 获取当前登录的招聘者用户名,返回 `username`(依赖当前 runtime 已跟踪页面;首次使用请先 `open_platform`,已打开但未跟踪页面可先 `list_pages + select_page`,确认登录后如需单独验证 attach,可先调用 `attach_browser_session`)。常用于 recruiter binding 解析和外部通知消息中的账号标识
|
|
36
44
|
|
|
45
|
+
## BOSS直聘 — 聊天编排硬规则
|
|
46
|
+
|
|
47
|
+
聊天工具链必须把 `conversationId` / `candidateId` 当作稳定主键,而不是把左侧列表的瞬时 `index` 当主键。
|
|
48
|
+
|
|
49
|
+
原因:
|
|
50
|
+
|
|
51
|
+
- BOSS 左侧消息列表是虚拟列表,DOM 只保留当前窗口内的若干条记录
|
|
52
|
+
- 点击会话、发送消息、收到新消息后,列表会实时重排
|
|
53
|
+
- 同一个人上一轮是 `index=3`,下一轮可能已经变成 `index=0`
|
|
54
|
+
- 因此 `index` 只适合“当前这一轮、当前这个 DOM 快照内”的临时兜底,不适合跨 tool / 跨 agent 透传
|
|
55
|
+
|
|
56
|
+
编排要求:
|
|
57
|
+
|
|
58
|
+
1. 先调用 `zhipin_read_messages`
|
|
59
|
+
2. 一旦返回了 `conversationId` / `candidateId`,后续所有 related tool 都复用这两个值
|
|
60
|
+
3. 调 `zhipin_open_chat` / `zhipin_get_candidate_info` / `zhipin_exchange_wechat` 时,优先传 `conversationId`
|
|
61
|
+
4. 调 `smart-reply-agent.generate_reply(..., target)` 时,`target.conversationId` / `target.candidateId` 必须直接来自 `browser-use-agent` 的真实输出
|
|
62
|
+
5. 禁止把 `zhipin_read_messages` 返回数组里的 `index` 缓存到下一轮,再把它当作会话主键使用
|
|
63
|
+
6. 只有在当前轮次拿不到 `conversationId` 时,才允许临时退回 `candidateName` 或 `index`
|
|
64
|
+
|
|
65
|
+
错误做法:
|
|
66
|
+
|
|
67
|
+
- `zhipin_read_messages` 拿到 `index=2`,几轮之后再调用 `zhipin_open_chat(index=2)`
|
|
68
|
+
- 用 `candidateName` 重新模糊匹配一个会话,再把历史 `candidateId` 假定为同一个人
|
|
69
|
+
- `smart-reply-agent` 的 `target` 不用 `browser-use-agent` 返回的 `conversationId/candidateId`,而是由 orch 自己重建
|
|
70
|
+
|
|
71
|
+
推荐做法:
|
|
72
|
+
|
|
73
|
+
1. `zhipin_read_messages` → 记录 `conversationId + candidateId + candidateName`
|
|
74
|
+
2. `zhipin_open_chat(conversationId)`
|
|
75
|
+
3. `zhipin_get_candidate_info(conversationId)`
|
|
76
|
+
4. `smart-reply-agent.generate_reply(..., target={ platform, conversationId, candidateId, recruiterUsername|recruiterBinding })`
|
|
77
|
+
5. `zhipin_send_reply(signedEnvelope)`
|
|
78
|
+
|
|
37
79
|
## BOSS直聘 — 推荐列表 Tools
|
|
38
80
|
|
|
81
|
+
- `zhipin_open_recommend_page()` — 通过点击 Boss 左侧导航切换到「推荐牛人」页;优先复用当前已登录的 Boss 页面,不让编排器去猜站内 URL
|
|
39
82
|
- `zhipin_get_candidate_list(maxResults?)` — 获取推荐列表页的候选人卡片信息(姓名、年龄、学历、期望薪资等)
|
|
40
83
|
- `zhipin_say_hello(indices)` — 对推荐列表中的候选人批量点击「打招呼」
|
|
41
84
|
- `zhipin_open_resume(index)` — 点击候选人卡片打开简历详情弹窗
|
|
@@ -49,17 +92,24 @@ metadata:
|
|
|
49
92
|
|
|
50
93
|
## 典型工作流
|
|
51
94
|
|
|
52
|
-
1. `zhipin_read_messages` →
|
|
53
|
-
2. `
|
|
54
|
-
3. `
|
|
55
|
-
4.
|
|
95
|
+
1. `zhipin_read_messages` → 获取消息列表,并记录 `conversationId` / `candidateId`
|
|
96
|
+
2. `zhipin_open_chat_page()` → 通过左侧导航切回 `沟通`(需要时)
|
|
97
|
+
3. `zhipin_open_chat(conversationId)` → 按稳定会话 ID 打开聊天
|
|
98
|
+
4. `zhipin_get_candidate_info(conversationId)` → 查看候选人资料、聊天记录
|
|
99
|
+
5. 调 `smart-reply-agent.generate_reply` 前,先尝试透传以下信号:
|
|
56
100
|
- 能读到就传:`candidateInfo.communicationPosition`、`candidateInfo.expectedLocation`、`candidateInfo.expectedPosition`
|
|
57
101
|
- 读不到就如实不传
|
|
58
102
|
- `preferredBrand`:仅当 `communicationPosition` 含连字符类分隔符(`-` / `-` / `—` / `–`)时,取第一段透传;没有分隔符就不传
|
|
59
103
|
- 严禁把通用岗位名(如“餐饮兼职服务员”“门店服务员”)或 `zhipin_get_candidate_list.company`(候选人现/前雇主)伪装成 `preferredBrand`
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
104
|
+
6. `smart-reply-agent.generate_reply(..., target)` → 获取 `suggestedReply + signedEnvelope`
|
|
105
|
+
7. `zhipin_send_reply(signedEnvelope)` → 验签、校验 recruiterBinding 后发送回复
|
|
106
|
+
8. `zhipin_exchange_wechat` → 交换微信(可选)
|
|
107
|
+
|
|
108
|
+
推荐列表链路建议:
|
|
109
|
+
|
|
110
|
+
1. `zhipin_open_recommend_page()` → 通过左侧导航切到 `推荐牛人`
|
|
111
|
+
2. `zhipin_get_candidate_list(maxResults?)` → 读取候选人卡片
|
|
112
|
+
3. `zhipin_say_hello(indices)` → 批量打招呼
|
|
63
113
|
|
|
64
114
|
## 支持平台
|
|
65
115
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export declare const BROWSER_USE_DECLARED_ENV_KEYS: readonly ["REPLY_AUTHORITY_KEYS_URL"];
|
|
2
|
+
export declare const BROWSER_USE_DECLARED_ENV_KEYS: readonly ["REPLY_AUTHORITY_KEYS_URL", "BROWSER_VISUAL_CURSOR", "BROWSER_VISUAL_ACTIVITY"];
|
|
3
3
|
export declare const EffectiveEnvSourceSchema: z.ZodObject<{
|
|
4
4
|
present: z.ZodBoolean;
|
|
5
5
|
fingerprint: z.ZodOptional<z.ZodString>;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{BrowserRuntimeConfigSchema as n}from"@roll-agent/browser";import{defineTool as a}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as o}from"@roll-agent/sdk";import{BrowserRuntime as i,BrowserContextManager as s,SessionStore as c}from"@roll-agent/browser";var d,u,l,m=!1,g=o("browser-use-agent");async function f(e){d||(m=!1,l=new c(e.sessionsDir),d=new i(e),await d.start(),u=new s(d,l))}function p(){if(!d)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return d}function h(){if(!u)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return u}function b(){if(!l)throw new Error("SessionStore not initialized. Call initRuntime() first.");return l}function w(e){m=e}function y(){return m}async function x(){const e=u,t=d,n=[];if(u=void 0,d=void 0,l=void 0,m=!1,e){g.info("Closing browser contexts...");try{await e.closeAll()}catch(e){n.push(new Error("Failed to close browser contexts",{cause:e})),g.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(t){g.info("Stopping browser process...");try{await t.stop()}catch(e){n.push(new Error("Failed to stop browser runtime",{cause:e})),g.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(g.info("Browser runtime shutdown complete"),n.length>0)throw new AggregateError(n,"Browser runtime shutdown failed")}var v=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),S=a({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:v,execute:async(e,t)=>{const n=p();return t.logger.info("Attaching Playwright browser session over CDP"),await n.getBrowser(),{success:!0,mode:n.mode,connected:!0}}});import{defineTool as k}from"@roll-agent/sdk";import{z as I}from"zod";import{BrowserStatusSchema as A}from"@roll-agent/browser";import{createHash as P}from"node:crypto";import{z as C}from"zod";var E=["REPLY_AUTHORITY_KEYS_URL"],_=/^[0-9a-f]{8}$/,q=C.object({present:C.boolean(),fingerprint:C.string().regex(_).optional()}),N=C.record(q);function R(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:z(n)}]:[e,{present:!1}]}))}function z(e){return P("sha256").update(e).digest("hex").slice(0,8)}var M=A.extend({replyAuthorityKeysLoaded:I.boolean(),effectiveEnvSources:N}),B=k({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:I.object({}),output:M,execute:async(e,t)=>{t.logger.info("Querying browser status");const n=p(),a=h(),r=b(),o=n.isRunning(),{headless:i,mode:s}=n.getConfig(),c=a.getActivePlatforms(),d=[];for(const e of c){const t=a.getPageCount(e),o=a.getCurrentUrl(e);let i=null,s="unknown";if(n.shouldRestoreSessionSnapshot()){const[t,n]=await Promise.all([r.loadCookies(e),r.loadLocalStorage(e)]);i=void 0!==t&&t.length>0||void 0!==n&&Object.keys(n).length>0,s=i?"snapshot":"none"}else n.usesPersistentProfile()&&(i=null,s="profile");d.push({platform:e,pagesOpen:t,currentUrl:o,hasLoginState:i,loginStateSource:s})}return{running:o,headless:i,mode:s,activeSessions:d,replyAuthorityKeysLoaded:y(),effectiveEnvSources:R(E)}}});import{defineTool as $}from"@roll-agent/sdk";import{BrowserPageInfoSchema as T,PlatformSchema as j}from"@roll-agent/browser";import{z as L}from"zod";import{PLATFORMS as U}from"@roll-agent/browser";var O={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function F(e){return new URL(O[e]).host}function D(e,t){try{return new URL(e).host.includes(F(t))}catch{return!1}}function W(e){return U.find(t=>D(e,t))}function H(e,t){const n=W(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 V(e,t){const n=t.url();return{pageId:e.getPageId(t),url:n,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:W(n)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var Y=L.object({platform:j.optional().describe("可选:仅返回指定平台相关的页面")}),G=L.object({pages:L.array(T)}),J=$({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:Y,output:G,execute:async(e,t)=>{const n=h();t.logger.info("Listing browser pages");const a=(await n.listNativePages()).map(e=>H(n,e));return{pages:void 0===e.platform?a:a.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{defineTool as K}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Z}from"@roll-agent/browser";import{z as X}from"zod";var Q=X.object({url:X.string().url().describe("要导航到的目标 URL")}),ee=X.object({success:X.boolean(),page:Z}),te=K({name:"navigate_active_tab",description:"将当前激活的浏览器 tab 导航到指定 URL;若 URL 属于已知平台,会自动绑定该平台当前活跃页。",input:Q,output:ee,execute:async(e,t)=>{const n=h();t.logger.info(`Navigating active tab to ${e.url}`);const a=await n.getActivePage();if(!a)throw new Error("No active browser tab detected. Use open_platform or select_page first.");await a.bringToFront().catch(()=>{}),await a.goto(e.url,{waitUntil:"domcontentloaded"});const r=W(a.url());return r?await n.selectAttachedPage(r,n.getPageId(a)):n.clearBindingForPage(a),{success:!0,page:await V(n,a)}}});import{defineTool as ne}from"@roll-agent/sdk";import{BrowserPageInfoSchema as ae,PlatformSchema as re}from"@roll-agent/browser";import{z as oe}from"zod";async function ie(e,t){return(await e.listNativePages()).find(e=>D(e.url,t))}async function se(e,t){const n=await ie(e,t);if(n)return await e.activateNativePage(n.targetId),{page:n,reusedExistingPage:!0};return{page:await e.openNativePage(O[t]),reusedExistingPage:!1}}var ce=oe.object({platform:re.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡")}),de=oe.object({success:oe.boolean(),page:ae,reusedExistingTab:oe.boolean()}),ue=ne({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:ce,output:de,execute:async(e,t)=>{const{platform:n}=e,a=p(),r=h();t.logger.info(`Opening platform page for ${n}`);const{page:o,reusedExistingPage:i}=await se(a,n);return r.rememberNativePageSelection(n,o),{success:!0,page:H(r,o),reusedExistingTab:i}}});import{defineTool as le}from"@roll-agent/sdk";import{BrowserPageInfoSchema as me,PlatformSchema as ge}from"@roll-agent/browser";import{z as fe}from"zod";var pe=fe.object({platform:ge.describe("要将该页面绑定为当前活跃页的平台"),pageId:fe.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),he=fe.object({success:fe.boolean(),page:me}),be=le({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:pe,output:he,execute:async(e,t)=>{const n=h();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const a=await n.selectNativePage(e.platform,e.pageId);return{success:!0,page:H(n,a)}}});import{defineTool as we}from"@roll-agent/sdk";import{z as ye}from"zod";async function xe(e,t=300,n=800){const a=Math.floor(Math.random()*(n-t))+t;await e.waitForTimeout(a)}async function ve(e){const t=Math.random();let n;n=t<.5?800+1200*Math.random():t<.8?500+300*Math.random():t<.95?2e3+2e3*Math.random():4e3+2e3*Math.random(),await e.waitForTimeout(Math.floor(n))}async function Se(e,t){const n=t?.minDistance??50,a=t?.maxDistance??200,r=t?.direction??"both",o=Math.floor(Math.random()*(a-n))+n,i="up"===r?-1:"down"===r||Math.random()>.5?1:-1;await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},o*i),await xe(e,200,500)}async function ke(e){if(Math.random()<.8){const t=100+Math.floor(100*Math.random());await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},t),await ve(e)}if(Math.random()<.5){const t=(50+Math.floor(100*Math.random()))*(Math.random()>.5?1:-1);await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},t)}}function Ie(e=.3){return Math.random()<e}import{setTimeout as Ae}from"node:timers/promises";var Pe="https://www.zhipin.com/web/geek/chat",Ce=".chat-list-wrap, .geek-item",Ee=new Set(["消息"]),_e="data-roll-chat-entry-target",qe="data-roll-chat-item-target";function Ne(e){return e.trim().toLocaleLowerCase("zh-CN")}function Re(e,t){let n=0;for(const a of e)t.includes(a)&&(n+=1);return n}function ze(e,t){if(void 0!==t.index)return e[t.index];const n=t.candidateName;if(!n)return;const a=Ne(n),r=e.filter(e=>e.name.length>0);let o=r.find(e=>Ne(e.name)===a);if(o)return o;if(o=r.find(e=>{const t=Ne(e.name);return t.includes(a)||a.includes(t)}),o)return o;const i=a.length<=2?1:a.length<=4?.75:.6;return r.find(e=>{const t=Ne(e.name);return Re(a,t)>=Math.ceil(Math.min(a.length,t.length)*i)})}function Me(e){return e.includes("/web/geek/chat")||e.includes("/web/chat")}async function Be(e,t=1e4){try{return await e.waitForSelector(Ce,{timeout:t}),!0}catch{return!1}}async function $e(e,t){const n=(await e.listAttachedPages()).find(e=>e!==t&&Me(e.url()));if(n)return await e.selectAttachedPage("zhipin",e.getPageId(n))}async function Te(e,t){await e.evaluate(e=>{document.querySelectorAll(`[${e}]`).forEach(t=>{t.removeAttribute(e)})},t).catch(()=>{})}async function je(e,t){const n=e.locator(t).first();await n.scrollIntoViewIfNeeded(),await n.hover(),await xe(e,200,400),await n.click()}async function Le(e){const t=await e.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},n=t=>e.messageLabels.some(e=>t===e||t.includes(e));document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const a=Array.from(document.querySelectorAll('a[href*="/web/geek/chat"], a[href*="/web/chat"]'));for(const n of a)if(t(n))return n.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`};const r=Array.from(document.querySelectorAll('a, button, [role="link"], [role="button"], span, div'));for(const a of r){if(n(a.textContent?.trim()??"")&&t(a))return a.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}}return{found:!1}},{markerAttr:_e,messageLabels:[...Ee]});if(!t.found)return!1;try{return await je(e,t.selector),!0}finally{await Te(e,_e)}}function Ue(e){return e instanceof Error&&/ERR_ABORTED/i.test(e.message)}async function Oe(e){try{return await e.goto(Pe,{waitUntil:"domcontentloaded"}),!0}catch(t){return!!Ue(t)&&(!!Me(e.url())||await Be(e,2e3))}}async function Fe(e,t){if(!await Le(t))return!1;if(await Be(t,5e3))return!0;const n=await $e(e,t);return!!n&&await Be(n,5e3)}async function De(e,t){if(Me(t.url())&&await Be(t))return!0;const n=await $e(e,t);if(n&&await Be(n))return!0;if(await Fe(e,t))return!0;if(!await Oe(t))return!1;if(await Be(t))return!0;await Ae(300);const a=await $e(e,t);return!!a&&await Be(a,5e3)}async function We(e){return e.evaluate(()=>Array.from(document.querySelectorAll(".geek-item")).map((e,t)=>{const n=e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name'),a=n?.textContent?.trim()??"",r=e.querySelector(".badge-count"),o=parseInt(r?.textContent?.trim()??"0",10)||0;return{name:a,index:t,hasUnread:o>0||null!==e.querySelector(".red-dot"),unreadCount:o,lastMessageTime:e.querySelector(".time, .time-shadow")?.textContent?.trim()??"",messagePreview:(e.querySelector(".push-text, .chat-last-msg")?.textContent?.trim()??"").slice(0,100)}}))}async function He(e,t){const n=await e.evaluate(e=>{document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const t=Array.from(document.querySelectorAll(".geek-item"))[e.targetIndex];if(!t)return{found:!1};return(t.querySelector(".chat-item-content")??t).setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}},{markerAttr:qe,targetIndex:t});if(!n.found)return!1;try{return await je(e,n.selector),!0}finally{await Te(e,qe)}}async function Ve(e,t){if(0===t.length)return void await xe(e,500,900);const n=Ne(t);try{await e.waitForFunction(e=>{const t=[".name-box",".geek-name",".base-name",".chat-user-name"];for(const n of t){const t=document.querySelector(n)?.textContent?.trim();if(!t)continue;const a=t.trim().toLocaleLowerCase("zh-CN");if(a.includes(e)||e.includes(a))return!0}return!1},n,{timeout:5e3})}catch{await xe(e,800,1200)}}async function Ye(e,t,n){if(void 0===n.candidateName&&void 0===n.index)return;if(!await De(e,t))return{found:!1,name:"",index:-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const a=await e.getPage("zhipin"),r=ze(await We(a),n);if(!r){return{found:!1,name:"",index:-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${n.candidateName??`index ${n.index}`}`}}return await He(a,r.index)?(await Ve(a,r.name),{...r,found:!0}):{...r,found:!1,error:`打开候选人聊天失败: ${r.name||`index ${r.index}`}`}}var Ge=ye.object({name:ye.string(),conversationId:ye.string(),candidateId:ye.string(),position:ye.string(),time:ye.string(),preview:ye.string(),unreadCount:ye.number(),hasUnread:ye.boolean(),index:ye.number()}),Je=ye.object({success:ye.boolean(),candidates:ye.array(Ge),total:ye.number(),stats:ye.object({withName:ye.number(),withUnread:ye.number()})}),Ke=we({name:"zhipin_read_messages",description:"读取 BOSS直聘未读候选人列表,支持过滤和排序",input:ye.object({limit:ye.number().optional().describe("最多返回条数"),onlyUnread:ye.boolean().default(!0).describe("是否只返回有未读消息的候选人"),sortBy:ye.enum(["time","unreadCount","name"]).default("time")}),output:Je,execute:async(e,t)=>{const n=e.onlyUnread??!0;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${n})`);const a=h(),r=await a.getPage("zhipin");if(!await De(a,r))return{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};const o=await a.getPage("zhipin");await ke(o);const i=await o.evaluate(()=>{const e=document.querySelectorAll(".geek-item"),t=[];return e.forEach((e,n)=>{const a=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"",r=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??a,o=e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name');let i=o?.textContent?.trim()??"";if(i.length>10){const e=i.match(/[\u4e00-\u9fa5]{2,4}/);e&&(i=e[0])}const s=e.querySelector(".source-job")?.textContent?.trim()??"",c=e.querySelector(".time, .time-shadow")?.textContent?.trim()??"",d=(e.querySelector(".push-text, .chat-last-msg")?.textContent?.trim()??"").slice(0,100);let u=0;const l=e.querySelector(".badge-count");l&&(u=parseInt(l.textContent?.trim()??"0",10)||0);const m=u>0||null!==e.querySelector(".red-dot");t.push({name:i,conversationId:a,candidateId:r,position:s,time:c,preview:d,unreadCount:u,hasUnread:m,index:n})}),t});let s=n?i.filter(e=>e.hasUnread):i;const c=e.sortBy??"time";"time"===c?s.sort((e,t)=>t.time.localeCompare(e.time)):"unreadCount"===c?s.sort((e,t)=>t.unreadCount-e.unreadCount):"name"===c&&s.sort((e,t)=>e.name.localeCompare(t.name)),void 0!==e.limit&&(s=s.slice(0,e.limit));const d={withName:i.filter(e=>e.name.length>0).length,withUnread:i.filter(e=>e.hasUnread).length};return await Se(o),t.logger.info(`Found ${s.length} candidates (${d.withUnread} with unread)`),{success:!0,candidates:s,total:i.length,stats:d}}});import{defineTool as Ze}from"@roll-agent/sdk";import{z as Xe}from"zod";var Qe=Xe.object({success:Xe.boolean(),candidateName:Xe.string(),index:Xe.number(),hasUnread:Xe.boolean(),unreadCount:Xe.number(),lastMessageTime:Xe.string(),messagePreview:Xe.string(),error:Xe.string().optional()}),et=Ze({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(按姓名模糊匹配或索引)",input:Xe.object({candidateName:Xe.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:Xe.number().optional().describe("候选人在列表中的索引"),preferUnread:Xe.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:Qe,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const n=h(),a=await n.getPage("zhipin");let r={candidateName:e.candidateName,index:e.index};if(e.preferUnread&&void 0===e.candidateName&&void 0===e.index){if(!await De(n,a))return{success:!1,candidateName:"",index:-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const e=await n.getPage("zhipin"),t=(await We(e)).find(e=>e.hasUnread);t&&(r={candidateName:t.name,index:t.index})}const o=await Ye(n,a,r);return o&&o.found?(t.logger.info(`Opened chat with ${o.name} (index: ${o.index})`),{success:!0,candidateName:o.name,index:o.index,hasUnread:o.hasUnread,unreadCount:o.unreadCount,lastMessageTime:o.lastMessageTime,messagePreview:o.messagePreview}):{success:!1,candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:o?.error??`未找到候选人: ${e.candidateName??`index ${e.index}`}`}}});import{defineTool as tt}from"@roll-agent/sdk";import{z as nt}from"zod";var at=".geek-item.selected";async function rt(e){try{await e.waitForSelector(at,{timeout:5e3})}catch{return null}const t=await e.evaluate(()=>{const e=document.querySelector(".geek-item.selected");if(!e)return null;const t=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"";return{conversationId:t,candidateId:e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??t,candidateName:e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name')?.textContent?.trim()??""}});return t&&"string"==typeof t.conversationId&&"string"==typeof t.candidateId&&0!==t.conversationId.length&&0!==t.candidateId.length?t:null}var ot="·",it=/[--—–]/;function st(e){return(e??"").trim()}function ct(e){const t=st(e);if(!t||!it.test(t))return;const[n=""]=t.split(it);return st(n)||void 0}function dt(e){const t=st(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[n="",a=""]=t.split(ot).map(e=>st(e));return{expectedLocation:n,expectedPosition:a}}function ut(e){const t=st(e.communicationPosition),{expectedLocation:n,expectedPosition:a}=dt(e.expectedJobText),r=ct(t);return{communicationPosition:t,expectedLocation:n,expectedPosition:a,...void 0!==r?{preferredBrand:r}:{}}}var lt=nt.object({index:nt.number(),sender:nt.enum(["candidate","recruiter","system"]),messageType:nt.enum(["text","system","resume","wechat-exchange"]),content:nt.string(),time:nt.string()}),mt=nt.object({name:nt.string(),age:nt.string(),experience:nt.string(),education:nt.string(),communicationPosition:nt.string(),expectedPosition:nt.string(),expectedLocation:nt.string(),expectedSalary:nt.string(),tags:nt.array(nt.string())}),gt=nt.object({success:nt.boolean(),conversationId:nt.string(),candidateId:nt.string(),candidateInfo:mt,preferredBrand:nt.string().optional(),chatMessages:nt.array(lt),formattedHistory:nt.array(nt.string()),stats:nt.object({totalMessages:nt.number(),candidateMessages:nt.number(),recruiterMessages:nt.number(),systemMessages:nt.number()}),error:nt.string().optional()}),ft=tt({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 candidateName 自动打开对应聊天,或不传则读取当前窗口;例如“查看鲁倩的聊天详情”应提取 candidateName=鲁倩。",input:nt.object({candidateName:nt.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:nt.number().optional().describe("候选人在列表中的索引(可选)"),maxMessages:nt.number().default(100).describe("最多返回的消息条数")}),output:gt,execute:async(e,t)=>{const n=e.maxMessages??100,a=h(),r=await a.getPage("zhipin"),o=await Ye(a,r,{candidateName:e.candidateName,index:e.index});if(o&&!o.found){return{success:!1,conversationId:"",candidateId:"",candidateInfo:{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]},chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:o.error}}t.logger.info("Extracting candidate info"+(o?` for ${o.name}`:" (current window)"));const i=await a.getPage("zhipin"),s=await rt(i);try{await i.waitForSelector(".chat-message-list .message-item, .conversation-message .message-item",{timeout:8e3})}catch{}const c=await i.evaluate(e=>{const t=document.querySelector(".base-info-single-detial, .base-info-content"),n=t?.querySelector(".name-box")?.textContent?.trim()??"",a=t?t.querySelectorAll(":scope > div"):document.querySelectorAll(".geek-info-item, .base-info-item"),r=[];a.forEach(e=>{const t=e.textContent?.trim();t&&r.push(t)});const o=r.join(" "),i=o.match(/(\d{2,3})岁/),s=i?i[1]+"岁":"",c=o.match(/(\d+年(?:以上)?|应届生|在校生)/),d=c?.[1]??"",u=o.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1]??"";let l="";const m=document.querySelector(".position-name");if(m){const e=m.cloneNode(!0);e.querySelectorAll(".popover-wrap, .tooltip-job").forEach(e=>e.remove()),l=e.textContent?.trim()??""}let g="";const f=document.querySelector(".position-item.expect .value.job");f&&(g=f.textContent?.trim()??"");const p=document.querySelector(".position-item.expect .high-light-orange")?.textContent?.trim()??"",h=[];t&&t.querySelectorAll(".geek-tag, .base-info-item .high-light-boss").forEach(e=>{const t=e.textContent?.trim();t&&!t.includes("更换职位")&&t.length<20&&h.push(t)});const b=document.querySelectorAll(".chat-message-list > .message-item"),w=/\d{1,2}:\d{2}(?::\d{2})?|\d{4}-\d{2}-\d{2}/,y=[];let x=0;return b.forEach(t=>{if(x>=e)return;const n=null!==t.querySelector(".item-friend"),a=null!==t.querySelector(".item-myself"),r=null!==t.querySelector(".item-system"),o=null!==t.querySelector(".item-resume"),i=null!==t.querySelector(".message-dialog-center");let s="system",c="text";n?s="candidate":a?s="recruiter":(r||i)&&(s="system",c="system"),o&&(c="resume");const d=t.querySelector(".message-card-top-wrap, [class*='d-top-text']");if(d){const e=d.textContent??"";(e.includes("微信")||e.includes("WeChat"))&&(c="wechat-exchange")}const u=t.querySelector(".message-time .time, .message-time"),l=(u?.textContent??"").match(w),m=l?l[0]:"";let g="";if("wechat-exchange"===c&&d){const e=d.textContent??"",t=e.match(/\b(\d{8,15})\b/),n=e.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);g=t?`[微信号: ${t[1]}]`:n?`[微信号: ${n[1]}]`:"[交换微信]"}else if(d){const e=t.querySelector(".message-card-top-title"),n=t.querySelector(".dialog-content, .message-card-top-text");g=(e?.textContent?.trim()??n?.textContent?.trim()??"").trim()}else{const e=t.querySelector(".text span, .text-content, .text");e&&(g=(e.textContent?.trim()??"").replace(w,"").replace("已读","").trim())}(g||"text"!==c)&&(y.push({index:x,sender:s,messageType:c,content:g,time:m}),x++)}),{candidateInfo:{name:n,age:s,experience:d,education:u,communicationPosition:l,expectedJobText:g,expectedSalary:p,tags:h},messages:y}},n),d=ut({communicationPosition:c.candidateInfo.communicationPosition,expectedJobText:c.candidateInfo.expectedJobText}),u=c.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),l={totalMessages:c.messages.length,candidateMessages:c.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:c.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:c.messages.filter(e=>"system"===e.sender).length};return t.logger.info(`Extracted info for ${c.candidateInfo.name}: ${l.totalMessages} messages`),{success:!0,conversationId:s?.conversationId??"",candidateId:s?.candidateId??"",candidateInfo:{name:c.candidateInfo.name,age:c.candidateInfo.age,experience:c.candidateInfo.experience,education:c.candidateInfo.education,communicationPosition:d.communicationPosition,expectedPosition:d.expectedPosition,expectedLocation:d.expectedLocation,expectedSalary:c.candidateInfo.expectedSalary,tags:c.candidateInfo.tags},...void 0!==d.preferredBrand?{preferredBrand:d.preferredBrand}:{},chatMessages:c.messages,formattedHistory:u,stats:l}}});import{defineTool as pt}from"@roll-agent/sdk";import{z as ht}from"zod";var bt={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"]},recommend:{iframe:"#recommendFrame",resumeIframe:'iframe[src*="c-resume"]',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"}},wt=30,yt=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function xt(e){const t=e.trim();return!(0===t.length||t.length>wt)&&(!yt.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function vt(e){const t=[],n=/(link|button|menuitem|img|heading)\s+"([^"]+)"/g;let a;for(;null!==(a=n.exec(e));){const e=a[2]?.trim()??"";e.length>0&&t.push({role:a[1]??"",name:e})}return t}function St(e){let t=e.priority;return yt.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 kt(e){const t=e.filter(e=>xt(e.text));if(0===t.length)return{found:!1};const n=new Map;for(const e of t){const t=e.text.trim(),a=n.get(t)??new Set;a.add(e.strategy),n.set(t,a)}const a=t.map(e=>{let t=St(e);const a=n.get(e.text.trim())?.size??1;return a>1&&(t-=.5*a),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return a?{found:!0,username:a.evidence.text.trim(),strategy:a.evidence.strategy,source:a.evidence.source}:{found:!1}}async function It(e){const t=[e.getByRole("banner"),e.locator("header").first(),e.getByRole("navigation").first(),e.locator("#header")];for(const e of t)try{if(await e.count()>0&&await e.first().isVisible())return e.first()}catch{}return null}async function At(e,t,n,a){const r=[],o=t.viewportSize(),i=o?.width??1280;try{const t=await e.getByRole(n).all();for(const e of t)try{if(!await e.isVisible())continue;const t=(await e.textContent()??"").trim();if(t.length>0&&t.length<=wt){let o;try{const t=await e.boundingBox();t&&(o=(t.x+t.width/2)/i)}catch{}r.push({text:t,strategy:a,priority:1,source:`role:${n}`,xRatio:o})}}catch{}}catch{}return r}async function Pt(e){const t=[];try{const n=vt(await e.ariaSnapshot({timeout:3e3}));for(const{role:e,name:a}of n)a.length>0&&a.length<=wt&&t.push({text:a,strategy:"aria-snapshot",priority:2,source:`aria:${e}:${a}`})}catch{}return t}async function Ct(e){const t=[];try{const n=await e.evaluate((e,t)=>{const n=[],a=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;a.nextNode();){const e=a.currentNode.textContent?.trim();e&&e.length>0&&e.length<=t&&n.push(e)}return n},wt);for(const e of n)t.push({text:e,strategy:"leaf-text",priority:3,source:"leaf-text"})}catch{}return t}async function Et(e){const t=[];try{const n=[bt.username.primary,...bt.username.fallbacks],a=await e.evaluate(e=>e.map(e=>{try{return{selector:e,text:document.querySelector(e)?.textContent?.trim()??""}}catch{return{selector:e,text:""}}}),n);for(const{selector:e,text:n}of a)n.length>0&&n.length<=wt&&t.push({text:n,strategy:"css-fallback",priority:4,source:e})}catch{}return t}async function _t(e){const t=await It(e),n=t?(await Promise.all([At(t,e,"link","role-link"),At(t,e,"button","role-button"),Pt(t)])).flat():[],a=kt(n);if(a.found){const e=a.username;if(new Set(n.filter(t=>t.text.trim()===e).map(e=>e.strategy)).size>=2)return n}const r=(await Promise.all([t?Ct(t):Promise.resolve([]),Et(e)])).flat();return[...n,...r]}async function qt(e,t=_t){const n=kt(await t(e));if(!n.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return{platform:"zhipin",username:n.username,strategy:n.strategy,source:n.source}}function Nt(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as Rt}from"zod";var zt="reply-authority-service",Mt="browser-use-agent/zhipin_send_reply",Bt="zhipin",$t=60,Tt=Rt.object({platform:Rt.literal(Bt),username:Rt.string().min(1),accountId:Rt.string().min(1).optional()}),jt=Rt.object({v:Rt.literal(2),iss:Rt.literal(zt),kid:Rt.string().min(1),jti:Rt.string().min(1),iat:Rt.number().int(),exp:Rt.number().int(),aud:Rt.literal(Mt),platform:Rt.literal(Bt),tenantId:Rt.string().min(1),conversationId:Rt.string().min(1),candidateId:Rt.string().min(1),reply:Rt.string(),policyVersion:Rt.string().min(1),recruiterBinding:Tt}),Lt=Rt.object({kid:Rt.string().min(1),algorithm:Rt.literal("Ed25519"),publicKey:Rt.string().min(1),validFrom:Rt.string().min(1),validUntil:Rt.string().optional()}),Ut=Rt.object({keys:Rt.array(Lt)}),Ot=new Map;function Ft(e){for(const[t,n]of Ot)n<e-$t&&Ot.delete(t)}function Dt(e,t=Math.floor(Date.now()/1e3)){return Ft(t),Ot.has(e)}function Wt(e,t,n=Math.floor(Date.now()/1e3)){Ft(n),Ot.set(e,t)}import{createPublicKey as Ht,verify as Vt}from"node:crypto";var Yt=new Map,Gt=null;function Jt(){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 Kt(e){return Yt=new Map(e.map(e=>[e.kid,e]))}async function Zt(){if(Gt)return Gt;Gt=(async()=>{const e=await fetch(Jt()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return Kt(Ut.parse(t).keys)})();try{return await Gt}finally{Gt=null}}async function Xt(){await Zt()}async function Qt(e){const t=Yt.get(e);if(t)return t;const n=(await Zt()).get(e);if(!n)throw new Error(`Unknown key ID: ${e}`);return n}function en(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function tn(e){const t=e.split("."),n=t[0],a=t[1];if(2!==t.length||void 0===n||void 0===a||0===n.length||0===a.length)throw new Error("Invalid signed envelope format");let r,o="";try{o=Buffer.from(n,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{r=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}en(r);const i=jt.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:a}}function nn(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-$t)throw new Error("Envelope expired");if(e.iat>t+$t)throw new Error("Envelope issued in the future")}async function an(e,t=Math.floor(Date.now()/1e3)){const n=tn(e),a=await Qt(n.payload.kid),r=Ht({key:Buffer.from(a.publicKey,"base64url"),format:"der",type:"spki"});if(!Vt(null,Buffer.from(n.payloadJson,"utf-8"),r,Buffer.from(n.signatureBase64,"base64url")))throw new Error("Signature verification failed");return nn(n.payload,t),n.payload}var rn=ht.object({success:ht.boolean(),sentMessage:ht.string(),error:ht.string().optional()}),on=pt({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:ht.object({signedEnvelope:ht.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:ht.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:ht.number().optional().describe("候选人在列表中的索引(可选)")}),output:rn,execute:async(e,t)=>{let n="";if(!y()){const e="Reply Authority 公钥尚未成功预加载,当前无法发送签名回复。请检查启动日志、`REPLY_AUTHORITY_KEYS_URL` 配置,以及 `browser_status.replyAuthorityKeysLoaded`。";return t.logger.error(e),{success:!1,sentMessage:n,error:e}}const a=h(),r=await a.getPage("zhipin");let o=r;try{const i=await an(e.signedEnvelope);if(n=i.reply,Dt(i.jti))return{success:!1,sentMessage:n,error:"token 已消费,禁止重放"};const s=await Ye(a,r,{candidateName:e.candidateName,index:e.index});if(s&&!s.found)return{success:!1,sentMessage:n,error:s.error};if(!s){if(!await De(a,r))return{success:!1,sentMessage:n,error:"消息列表未加载"}}o=await a.getPage("zhipin");const c=await rt(o);if(!c)return{success:!1,sentMessage:n,error:"未能提取当前聊天的 conversationId/candidateId"};if(c.conversationId!==i.conversationId||c.candidateId!==i.candidateId)return{success:!1,sentMessage:n,error:"发送目标与签名不匹配"};const d=await qt(o);if(!Nt(d,i.recruiterBinding))return{success:!1,sentMessage:n,error:`recruiter 绑定不匹配:当前账号 ${d.username} 与签发时 ${i.recruiterBinding.username} 不一致`};t.logger.info(`Sending message (${n.length} chars) to ${c.candidateName||c.candidateId}`);const u="#boss-chat-editor-input, textarea.chat-input, .chat-input";await o.waitForSelector(u,{timeout:5e3});if(await o.evaluate(e=>{const t=document.querySelector(e);return"true"===t?.getAttribute("contenteditable")},u)){const e=o.locator(u).first();await e.focus(),await o.evaluate(e=>{const t=document.querySelector(e.sel);t&&(t.innerHTML=e.msg.split("\n").map(e=>`<p>${e}</p>`).join(""))},{sel:u,msg:n}),await e.dispatchEvent("input",{bubbles:!0})}else await o.fill(u,n);await xe(o,200,500);const l=await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")});const e=[".submit-content .submit.active",".submit-content .submit",".submit-content",".btn-send"];for(const t of e){const e=document.querySelector(t);if(e&&e.offsetWidth>0)return{found:!0,selector:t}}const t=Array.from(document.querySelectorAll("span"));for(const e of t)if("发送"===e.textContent?.trim()&&e.offsetWidth>0)return e.setAttribute("data-roll-send-btn","true"),{found:!0,selector:'[data-roll-send-btn="true"]'};return{found:!1}});if(!l.found)return{success:!1,sentMessage:n,error:"未找到发送按钮"};const m=o.locator(l.selector).first();return await m.scrollIntoViewIfNeeded(),await m.hover(),await ve(o),await m.click(),await xe(o,500,1200),Wt(i.jti,i.exp),t.logger.info("Message sent successfully"),{success:!0,sentMessage:n}}catch(e){return{success:!1,sentMessage:n,error:e instanceof Error?e.message:String(e)}}finally{await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")})}).catch(()=>{})}}});import{defineTool as sn}from"@roll-agent/sdk";import{z as cn}from"zod";var dn=cn.object({success:cn.boolean(),exchanged:cn.boolean(),wechatNumber:cn.string().optional(),error:cn.string().optional()}),un=sn({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:cn.object({candidateName:cn.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:cn.number().optional().describe("候选人在列表中的索引(可选)")}),output:dn,execute:async(e,t)=>{const n=h(),a=await n.getPage("zhipin"),r=await Ye(n,a,{candidateName:e.candidateName,index:e.index});if(r&&!r.found)return{success:!1,exchanged:!1,error:r.error};t.logger.info("Starting WeChat exchange"+(r?` with ${r.name}`:""));const o=await n.getPage("zhipin");try{if(!(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=[".operate-exchange-left .operate-btn","span.operate-btn"];for(const n of t){const t=Array.from(document.querySelectorAll(n));for(const n of t){const t=n.textContent?.trim()??"";if(t.includes("换微信")&&e(n))return n.setAttribute("data-roll-wechat-btn","true"),{found:!0,text:t}}}const n=Array.from(document.querySelectorAll("span"));for(const t of n){const n=t.textContent?.trim()??"";if(n.includes("换微信")&&e(t))return t.setAttribute("data-roll-wechat-btn","true"),{found:!0,text:n}}return{found:!1}})).found)return{success:!1,exchanged:!1,error:"未找到「换微信」按钮"};await xe(o,200,400),await o.click('[data-roll-wechat-btn="true"]'),await o.evaluate(()=>{document.querySelector("[data-roll-wechat-btn]")?.removeAttribute("data-roll-wechat-btn")}),await xe(o,400,800);let e=!1;for(let t=0;t<8;t++){if(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=document.querySelector(".exchange-tooltip");if(t&&e(t))return!0;const n=document.querySelectorAll("div, section, aside");for(const t of Array.from(n)){if((t.textContent??"").includes("交换微信")&&t.querySelector(".boss-btn-primary, .boss-btn")&&e(t))return!0}return!1})){e=!0;break}await xe(o,400,800)}if(!e)return{success:!1,exchanged:!1,error:"确认对话框未弹出"};await ve(o);if(!(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=document.querySelector(".exchange-tooltip");if(t){const n=[".btn-box .boss-btn-primary.boss-btn",".btn-box span.boss-btn-primary","span.boss-btn-primary",".boss-btn-primary"];for(const a of n){const n=t.querySelector(a);if(n&&e(n))return n.setAttribute("data-roll-confirm-btn","true"),{found:!0,text:n.textContent?.trim()??""}}}const n=document.querySelectorAll("div, section, aside");for(const t of Array.from(n)){if(!(t.textContent??"").includes("交换微信"))continue;const n=t.querySelectorAll("span.boss-btn-primary, button.boss-btn-primary, span.boss-btn, button.boss-btn");for(const t of Array.from(n)){const n=t.textContent?.trim()??"";if("确定"===n&&e(t))return t.setAttribute("data-roll-confirm-btn","true"),{found:!0,text:n}}}return{found:!1}})).found)return{success:!1,exchanged:!1,error:"未找到确认按钮"};await xe(o,200,400),await o.click('[data-roll-confirm-btn="true"]'),await o.evaluate(()=>{document.querySelector("[data-roll-confirm-btn]")?.removeAttribute("data-roll-confirm-btn")}),await xe(o,1500,2500);const n=await o.evaluate(()=>{const e=[".message-card-top-wrap",'[class*="d-top-text"]',".message-card-top-title"];for(const t of e){const e=Array.from(document.querySelectorAll(t));for(let t=e.length-1;t>=0;t--){const n=e[t]?.textContent??"",a=n.match(/\b(\d{8,15})\b/);if(a)return a[1];const r=n.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);if(r)return r[1];const o=n.match(/\b([a-zA-Z][a-zA-Z0-9_-]{5,19})\b/);if(o&&!["微信","WeChat"].includes(o[1]))return o[1]}}const t=Array.from(document.querySelectorAll(".message-item"));for(let e=t.length-1;e>=0;e--){const n=t[e]?.querySelector('.message-card-top-wrap, [class*="d-top-text"]');if(n){const e=(n.textContent??"").match(/\b(\d{8,15})\b/);if(e)return e[1]}}return null});return t.logger.info("WeChat exchanged"+(n?`, number: ${n}`:"")),{success:!0,exchanged:!0,...null!==n?{wechatNumber:n}:{}}}catch(e){return{success:!1,exchanged:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as ln}from"@roll-agent/sdk";import{z as mn}from"zod";var gn=mn.object({success:mn.boolean(),username:mn.string(),usedSelector:mn.string().optional(),usedStrategy:mn.string().optional(),source:mn.string().optional(),error:mn.string().optional()}),fn=ln({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:mn.object({}),output:gn,execute:async(e,t)=>{t.logger.info("Getting zhipin username");try{const e=h(),n=await e.getPage("zhipin");await n.bringToFront().catch(()=>{});const a=await qt(n);return t.logger.info(`Username: ${a.username} (strategy: ${a.strategy}, source: ${a.source})`),{success:!0,username:a.username,usedSelector:"css-fallback"===a.strategy?a.source:void 0,usedStrategy:a.strategy,source:a.source}}catch(e){return{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}}});import{defineTool as pn}from"@roll-agent/sdk";import{z as hn}from"zod";var bn=hn.object({index:hn.number(),candidateId:hn.string(),name:hn.string(),age:hn.string(),experience:hn.string(),education:hn.string(),workStatus:hn.string(),company:hn.string(),currentPosition:hn.string(),expectedLocation:hn.string(),expectedPosition:hn.string(),expectedSalary:hn.string(),tags:hn.array(hn.string()),buttonText:hn.string()}),wn=hn.object({success:hn.boolean(),candidates:hn.array(bn),total:hn.number(),error:hn.string().optional()}),yn=pn({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:hn.object({maxResults:hn.number().optional().describe("最多返回条数")}),output:wn,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page");const n=h(),a=await n.getPage("zhipin"),r=a.frame("recommendFrame")??a.frames().find(e=>e.url().includes("recommend"))??a;try{await r.waitForSelector("[data-geek], .geek-item",{timeout:1e4})}catch{return{success:!1,candidates:[],total:0,error:"推荐列表未加载"}}const o=await r.evaluate(e=>{let t=Array.from(document.querySelectorAll(".candidate-card-wrap"));0===t.length&&(t=Array.from(document.querySelectorAll("[data-geek], .geek-item")));const n=e??t.length,a=[];return t.forEach((e,t)=>{if(t>=n)return;const r=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",o=e.querySelector(".name")?.textContent?.trim()??"";let i="",s="",c="",d="";const u=e.querySelector(".base-info.join-text-wrap, .base-info");if(u){const e=[];if(u.querySelectorAll(":scope > *").forEach(t=>{const n=t.textContent?.trim();n&&e.push(n)}),e.length<=1&&(e.length=0,u.childNodes.forEach(t=>{if(t.nodeType===Node.TEXT_NODE){const n=t.textContent?.trim();n&&e.push(n)}})),e.length<=1){const t=u.textContent?.trim()??"";e.length=0,t.split(/[丨·|]/).forEach(t=>{const n=t.trim();n&&e.push(n)})}for(const t of e)!i&&t.includes("岁")?i=t:!s&&(t.includes("年")||t.includes("应届")||t.includes("在校"))?s=t:!c&&/(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(t)?c=t:!d&&/(在职|离职|在校)/.test(t)&&(d=t)}const l=e.querySelector(".timeline-wrap.work-exps .content.join-text-wrap")??e.querySelector(".timeline-wrap.work-exps .content"),m=(l?.textContent?.trim()??"").split("·").map(e=>e.trim()),g=m[0]??"",f=m[1]??"";let p="",h="";const b=e.querySelector(".row-flex:not(.geek-desc)");if(b){const e=b.querySelector(".label"),t=b.querySelector(".content"),n=e?.textContent??"";if((n.includes("期望")||n.includes("最近关注"))&&t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());p=e[0]??"",h=e[1]??""}}if(!p){const t=e.querySelector(".timeline-wrap.expect .content.join-text-wrap")??e.querySelector(".timeline-wrap.expect .content");if(t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());p=e[0]??"",h=e[1]??""}}const w=e.querySelector(".salary-wrap")?.textContent?.trim()??"",y=[];e.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span").forEach(e=>{const t=e.textContent?.trim();t&&y.push(t)});const x=e.querySelector("button.btn.btn-greet")?.textContent?.trim()??"";a.push({index:t,candidateId:r,name:o,age:i,experience:s,education:c,workStatus:d,company:g,currentPosition:f,expectedLocation:p,expectedPosition:h,expectedSalary:w,tags:y,buttonText:x})}),a},e.maxResults);return t.logger.info(`Found ${o.length} candidates in recommend list`),{success:!0,candidates:o,total:o.length}}});import{defineTool as xn}from"@roll-agent/sdk";import{z as vn}from"zod";var Sn=".candidate-card-wrap",kn="[data-geek], .geek-item",In=`${Sn}, ${kn}`;function An(e){return e.evaluate(e=>document.querySelectorAll(e.primarySelector).length>0?e.primarySelector:e.fallbackSelector,{primarySelector:Sn,fallbackSelector:kn})}function Pn(e){return e.frame("recommendFrame")??e.frames().find(e=>e.url().includes("recommend"))??e}async function Cn(e,t=1e4){try{return await e.waitForSelector(In,{timeout:t}),!0}catch{return!1}}async function En(e,t){const n=await An(e),a=e.locator(n);if(await a.count()<=t)return{found:!1,cardSelector:n,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const r=a.nth(t),o=await r.evaluate(e=>{const t=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",n=e.querySelector(".name")?.textContent?.trim()??"",a=e.querySelector("button.btn.btn-greet");return{candidateId:t,name:n,hasGreetButton:null!==a&&a.offsetWidth>0}});return{found:!0,cardSelector:n,candidateId:o.candidateId,name:o.name,hasGreetButton:o.hasGreetButton}}var _n=vn.object({index:vn.number(),candidateName:vn.string(),candidateId:vn.string(),success:vn.boolean(),error:vn.string().optional()}),qn=vn.object({success:vn.boolean(),results:vn.array(_n),summary:vn.object({total:vn.number(),succeeded:vn.number(),failed:vn.number()})}),Nn=xn({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:vn.object({indices:vn.array(vn.number()).describe("要打招呼的候选人索引列表")}),output:qn,execute:async(e,t)=>{t.logger.info(`Saying hello to ${e.indices.length} candidates`);const n=h(),a=await n.getPage("zhipin"),r=Pn(a);if(!await Cn(r)){const t=e.indices.map(e=>({index:e,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:t,summary:{total:t.length,succeeded:0,failed:t.length}}}const o=[];for(const t of e.indices)try{const e=await En(r,t);if(e.found)if(e.hasGreetButton){const n=r.locator(e.cardSelector).nth(t).locator("button.btn.btn-greet").first();await n.scrollIntoViewIfNeeded(),await n.hover(),await ve(a),await n.click(),o.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!0})}else o.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!1,error:"未找到打招呼按钮"});else o.push({index:t,candidateName:"",candidateId:"",success:!1,...void 0!==e.error?{error:e.error}:{}});await ve(a),Ie(.3)&&await Se(a)}catch(e){o.push({index:t,candidateName:"",candidateId:"",success:!1,error:e instanceof Error?e.message:String(e)})}const i={total:o.length,succeeded:o.filter(e=>e.success).length,failed:o.filter(e=>!e.success).length};return t.logger.info(`Say hello: ${i.succeeded}/${i.total} succeeded`),{success:0===i.failed,results:o,summary:i}}});import{defineTool as Rn}from"@roll-agent/sdk";import{z as zn}from"zod";var Mn=zn.object({success:zn.boolean(),candidateName:zn.string(),candidateId:zn.string(),error:zn.string().optional()}),Bn=Rn({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:zn.object({index:zn.number().describe("候选人在列表中的索引")}),output:Mn,execute:async(e,t)=>{t.logger.info(`Opening resume for candidate at index ${e.index}`);const n=h(),a=await n.getPage("zhipin"),r=Pn(a);if(!await Cn(r))return{success:!1,candidateName:"",candidateId:"",error:"推荐列表未加载"};const o=await En(r,e.index);if(!o.found)return{success:!1,candidateName:"",candidateId:"",error:o.error??`索引 ${e.index} 超出范围`};const i=r.locator(o.cardSelector).nth(e.index),s=await i.locator("[data-geek], .card-inner, .geek-item").count()>0?i.locator("[data-geek], .card-inner, .geek-item").first():i;return await s.scrollIntoViewIfNeeded(),await s.hover(),await xe(a,200,400),await s.click(),await xe(a,1e3,2e3),t.logger.info(`Opened resume for ${o.name}`),{success:!0,candidateName:o.name,candidateId:o.candidateId}}});import{defineTool as $n}from"@roll-agent/sdk";import{z as Tn}from"zod";var jn=Tn.object({x:Tn.number(),y:Tn.number(),width:Tn.number(),height:Tn.number()}),Ln=Tn.object({success:Tn.boolean(),screenshotArea:jn.optional(),canvasInfo:Tn.object({width:Tn.number(),height:Tn.number()}).optional(),error:Tn.string().optional()}),Un=$n({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:Tn.object({}),output:Ln,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=h(),a=await n.getPage("zhipin");try{const e=a.frame("recommendFrame")??a.frames().find(e=>e.url().includes("recommend"));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const n=await e.$('iframe[src*="c-resume"]');if(!n)return{success:!1,error:"未找到简历 iframe"};const r=await n.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector("canvas#resume, div#resume canvas",{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await r.evaluate(()=>{const e=document.querySelector("canvas#resume, div#resume canvas");if(!e)return null;const t=e.getBoundingClientRect();return{width:e.width,height:e.height,clientWidth:t.width,clientHeight:t.height,x:t.x,y:t.y}});if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await a.evaluate(()=>{const e=document.querySelector("#recommendFrame");if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),s=await e.evaluate(()=>{const e=document.querySelector('iframe[src*="c-resume"]');if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),c=(i?.x??0)+(s?.x??0),d=(i?.y??0)+(s?.y??0);return t.logger.info(`Canvas located at (${c+o.x}, ${d+o.y})`),{success:!0,screenshotArea:{x:Math.round(c+o.x),y:Math.round(d+o.y),width:Math.round(o.clientWidth),height:Math.round(o.clientHeight)},canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as On}from"@roll-agent/sdk";import{z as Fn}from"zod";var Dn=Fn.object({success:Fn.boolean(),closed:Fn.boolean(),error:Fn.string().optional()}),Wn=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],Hn=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],Vn=On({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:Fn.object({}),output:Dn,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const n=h(),a=await n.getPage("zhipin"),r=a.frame("recommendFrame")??a.frames().find(e=>e.url().includes("recommend"));if(!await(async()=>{if(r)for(const e of Wn){const t=await r.$(e);if(t&&await t.isVisible())return await t.click(),!0}for(const e of Hn){const t=await a.$(e);if(t&&await t.isVisible())return await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await a.waitForTimeout(300);const e=r?await r.$(".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog"):await a.$(".boss-popup__wrapper");if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as Yn}from"@roll-agent/sdk";import{z as Gn}from"zod";import{waitForSelector as Jn}from"@roll-agent/browser";var Kn={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 Zn,waitForSelector as Xn}from"@roll-agent/browser";var Qn="https://www.yupao.com",ea=`${Qn}/chat`,ta=`${Qn}/login`;async function na(e){e.url().includes("/chat")||await Zn(e,ea),await Xn(e,Kn.messageList.container,{timeout:15e3})}async function aa(e,t){const n=`${Qn}/chat?id=${encodeURIComponent(t)}`;await Zn(e,n),await Xn(e,Kn.chat.input,{timeout:15e3})}async function ra(e,t){await na(e),await Jn(e,Kn.messageList.item,{timeout:1e4});const n=Kn.messageList;return await e.$$eval(n.item,(e,t)=>{const n=[],a=t.maxItems?e.slice(0,t.maxItems):e;for(const e of a){const a=e.querySelector(t.sel.candidateName),r=e.querySelector(t.sel.lastMessage),o=e.querySelector(t.sel.unreadBadge),i=e.querySelector(t.sel.timestamp),s=e.getAttribute("data-id")??e.getAttribute("data-conversation-id")??e.querySelector("a")?.getAttribute("href")?.match(/id=([^&]+)/)?.[1]??"";n.push({conversationId:s,candidateName:a?.textContent?.trim()??"",lastMessage:r?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return n},{sel:n,maxItems:t})}var oa=Gn.object({limit:Gn.number().optional().describe("最多返回的消息条数")}),ia=Gn.object({conversationId:Gn.string(),candidateName:Gn.string(),lastMessage:Gn.string(),unreadCount:Gn.number(),timestamp:Gn.string()}),sa=Gn.object({messages:Gn.array(ia),total:Gn.number()}),ca=Yn({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:oa,output:sa,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const n=h(),a=await n.getPage("yupao"),r=await ra(a,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as da}from"@roll-agent/sdk";import{z as ua}from"zod";import{waitForSelector as la,typeText as ma,clickElement as ga}from"@roll-agent/browser";async function fa(e,t,n){try{return await aa(e,t),await ma(e,Kn.chat.input,n),await ga(e,Kn.chat.sendButton),await la(e,Kn.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}var pa=ua.object({conversationId:ua.string().describe("对话 ID"),message:ua.string().describe("要发送的回复消息")}),ha=ua.object({success:ua.boolean(),conversationId:ua.string(),sentMessage:ua.string(),error:ua.string().optional()}),ba=da({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:pa,output:ha,execute:async(e,t)=>{const{conversationId:n,message:a}=e;t.logger.info(`Sending reply to yupao conversation ${n}`);const r=h(),o=await r.getPage("yupao"),i=await fa(o,n,a);return i.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${i.error}`),{success:i.success,conversationId:n,sentMessage:a,error:i.error}}}),wa=t("browser-use-agent");function ya(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 xa(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 va(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 Sa(){return n.parse({mode:process.env.BROWSER_MODE,headless:ya(process.env.BROWSER_HEADLESS),cdpUrl:process.env.BROWSER_CDP_URL,cdpHost:process.env.BROWSER_CDP_HOST,cdpPort:xa(process.env.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:process.env.BROWSER_CHANNEL,executablePath:process.env.BROWSER_EXECUTABLE_PATH,userDataDir:process.env.BROWSER_USER_DATA_DIR,args:va(process.env.BROWSER_ARGS_JSON),sessionsDir:process.env.BROWSER_SESSIONS_DIR})}var ka=e({name:"browser-use-agent",tools:[B,J,te,ue,be,Ke,et,ft,on,un,fn,yn,Nn,Bn,Un,Vn,ca,ba,S]},{onShutdown:x});async function Ia(){await f(Sa());try{await Xt(),w(!0)}catch(e){w(!1),wa.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await ka.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Ia().catch(async e=>{wa.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await x().catch(()=>{}),process.exit(1)});
|
|
1
|
+
import{defineAgent as e,createAgentLogger as t}from"@roll-agent/sdk";import{BrowserRuntimeConfigSchema as a}from"@roll-agent/browser";import{defineTool as n}from"@roll-agent/sdk";import{z as r}from"zod";import{createAgentLogger as o}from"@roll-agent/sdk";import{BrowserRuntime as i,BrowserContextManager as s,SessionStore as c}from"@roll-agent/browser";var d,l,u,m=!1,g=o("browser-use-agent");async function f(e){d||(m=!1,u=new c(e.sessionsDir),d=new i(e),await d.start(),l=new s(d,u))}function p(){if(!d)throw new Error("BrowserRuntime not initialized. Call initRuntime() first.");return d}function h(){if(!l)throw new Error("BrowserContextManager not initialized. Call initRuntime() first.");return l}function w(){if(!u)throw new Error("SessionStore not initialized. Call initRuntime() first.");return u}function y(e){m=e}function b(){return m}async function v(){const e=l,t=d,a=[];if(l=void 0,d=void 0,u=void 0,m=!1,e){g.info("Closing browser contexts...");try{await e.closeAll()}catch(e){a.push(new Error("Failed to close browser contexts",{cause:e})),g.error(`Failed to close browser contexts: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(t){g.info("Stopping browser process...");try{await t.stop()}catch(e){a.push(new Error("Failed to stop browser runtime",{cause:e})),g.error(`Failed to stop browser runtime: ${e instanceof Error?e.stack??e.message:String(e)}`)}}if(g.info("Browser runtime shutdown complete"),a.length>0)throw new AggregateError(a,"Browser runtime shutdown failed")}var x=r.object({success:r.boolean(),mode:r.string(),connected:r.boolean()}),S=n({name:"attach_browser_session",description:"调试工具:显式执行一次 connectOverCDP(),仅建立 Playwright Browser 连接,不做页面导航或 DOM 操作。",input:r.object({}),output:x,execute:async(e,t)=>{const a=p();return t.logger.info("Attaching Playwright browser session over CDP"),await a.getBrowser(),{success:!0,mode:a.mode,connected:!0}}});import{defineTool as I}from"@roll-agent/sdk";import{z as k}from"zod";import{BrowserStatusSchema as C}from"@roll-agent/browser";import{createHash as A}from"node:crypto";import{z as R}from"zod";var M=["REPLY_AUTHORITY_KEYS_URL","BROWSER_VISUAL_CURSOR","BROWSER_VISUAL_ACTIVITY"],P=/^[0-9a-f]{8}$/,E=R.object({present:R.boolean(),fingerprint:R.string().regex(P).optional()}),$=R.record(E);function B(e,t=process.env){return Object.fromEntries(e.map(e=>{const a=t[e];return"string"==typeof a&&a.length>0?[e,{present:!0,fingerprint:_(a)}]:[e,{present:!1}]}))}function _(e){return A("sha256").update(e).digest("hex").slice(0,8)}var N,T=180,z=60,q=280,L="roll-agent-visual-cursor-root",F="__rollVisualCursorState";function j(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function O(){return void 0!==N?N:j(process.env.BROWSER_VISUAL_CURSOR)??!0}async function U(e){return await e.evaluate(e=>{const t=e.getBoundingClientRect();return t.width<=0||t.height<=0?null:{x:Math.round(t.left+t.width/2),y:Math.round(t.top+t.height/2)}})}async function D(e,t){await e.evaluate(e=>{const t="roll-agent-visual-cursor-root",a="roll-agent-visual-cursor-pointer",n="__rollVisualCursorState",r=(()=>{const e=document.getElementById(t);if(e)return e;const n=document.createElement("div");n.id=t,n.style.position="fixed",n.style.left="0",n.style.top="0",n.style.width="0",n.style.height="0",n.style.pointerEvents="none",n.style.zIndex="2147483647";const r=document.createElement("div");return r.id=a,r.setAttribute("aria-hidden","true"),r.style.position="fixed",r.style.left="0",r.style.top="0",r.style.width="24px",r.style.height="24px",r.style.opacity="0",r.style.transform="translate(-9999px, -9999px)",r.style.willChange="transform, opacity",r.style.filter="drop-shadow(0 4px 8px rgba(15, 23, 42, 0.28))",r.innerHTML='<svg viewBox="0 0 24 24" width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M4 2L18 14L11.4 15.3L14.6 22L10.7 23.5L7.6 16.8L3 21V2Z" fill="#FFFFFF" stroke="#0F172A" stroke-width="1.5" stroke-linejoin="round"/></svg>',n.append(r),document.documentElement.append(n),n})(),o=r.querySelector(`#${a}`);if(!o)return;const i=window[n],s=e.point,c=e.durationMs,d=s.x-2,l=s.y-2;if(i?o.style.transition=`transform ${c}ms cubic-bezier(0.22, 1, 0.36, 1), opacity 120ms ease`:(o.style.transition="opacity 120ms ease",o.style.transform=`translate(${d}px, ${l}px)`),o.style.opacity="1",o.style.transform=`translate(${d}px, ${l}px)`,window[n]={x:s.x,y:s.y},!e.clickPulse)return;const u=document.createElement("div");u.setAttribute("aria-hidden","true"),u.style.position="fixed",u.style.left="0",u.style.top="0",u.style.width="18px",u.style.height="18px",u.style.borderRadius="9999px",u.style.border="2px solid rgba(20, 184, 166, 0.95)",u.style.background="rgba(20, 184, 166, 0.18)",u.style.pointerEvents="none",u.style.opacity="0.9",u.style.transform=`translate(${s.x-9}px, ${s.y-9}px) scale(0.55)`,u.style.transition=`transform ${e.pulseDurationMs}ms ease, opacity ${e.pulseDurationMs}ms ease`,r.append(u),requestAnimationFrame(()=>{u.style.opacity="0",u.style.transform=`translate(${s.x-18}px, ${s.y-18}px) scale(2)`}),globalThis.setTimeout(()=>{u.remove()},e.pulseDurationMs+40)},{point:t.point,durationMs:t.durationMs,clickPulse:t.clickPulse,pulseDurationMs:t.pulseDurationMs})}function V(e){const t=[e],a="function"==typeof e.frames?e.frames():[];return t.push(...a),t}async function W(e){await e.evaluate(({rootId:e,stateKey:t})=>{document.getElementById(e)?.remove(),delete window[t]},{rootId:L,stateKey:F})}async function H(e,t={}){if(e.isClosed())return!1;let a=!1;for(const n of V(e))if(n!==t.preserveTarget)try{await W(n),a=!0}catch{}return a}async function Z(e,t,a={}){if(!O()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const n=await U(t);if(!n)return!1;const r=a.durationMs??T,o=a.settleMs??z,i=a.target??e;return await H(e,{preserveTarget:i}),await D(a.target??e,{point:n,durationMs:r,clickPulse:!1,pulseDurationMs:q}),await e.waitForTimeout(Math.max(r+o,0)),!0}catch{return!1}}async function G(e,t,a={}){if(!O()||e.isClosed())return!1;try{const n=await U(t);if(!n)return!1;const r=a.pulseDurationMs??q,o=a.target??e;return await H(e,{preserveTarget:o}),await D(o,{point:n,durationMs:0,clickPulse:!0,pulseDurationMs:r}),await e.waitForTimeout(r),!0}catch{return!1}}async function Y(e,t,a){return await Z(e,e.locator(t).first(),a)}async function K(e,t){return await G(e,e.locator(t).first())}var J,X={info:{accent:"#14b8a6",accentSoft:"rgba(20, 184, 166, 0.42)",accentGlow:"rgba(20, 184, 166, 0.18)",capsuleBg:"rgba(15, 23, 42, 0.82)",capsuleBorder:"rgba(45, 212, 191, 0.38)",text:"#F8FAFC",dot:"#2DD4BF"},success:{accent:"#22c55e",accentSoft:"rgba(34, 197, 94, 0.42)",accentGlow:"rgba(34, 197, 94, 0.18)",capsuleBg:"rgba(10, 24, 16, 0.86)",capsuleBorder:"rgba(74, 222, 128, 0.38)",text:"#F0FDF4",dot:"#4ADE80"},error:{accent:"#f59e0b",accentSoft:"rgba(245, 158, 11, 0.42)",accentGlow:"rgba(245, 158, 11, 0.2)",capsuleBg:"rgba(41, 24, 10, 0.88)",capsuleBorder:"rgba(251, 191, 36, 0.4)",text:"#FFFBEB",dot:"#FBBF24"}},Q=14,ee=720;function te(e){if(void 0!==e)return"true"===e||"false"!==e&&void 0}function ae(e="info"){return X[e]}function ne(){return void 0!==J?J:te(process.env.BROWSER_VISUAL_ACTIVITY)??!0}async function re(e,t=Q){return await e.evaluate((e,t)=>{const a=e.getBoundingClientRect();if(a.width<=0||a.height<=0)return null;const n=globalThis.innerWidth,r=globalThis.innerHeight,o=Math.max(t,0),i=Math.max(a.left-o,0),s=Math.max(a.top-o,0),c=Math.min(a.right+o,n),d=Math.min(a.bottom+o,r);return{x:Math.round(i),y:Math.round(s),width:Math.max(Math.round(c-i),0),height:Math.max(Math.round(d-s),0)}},t)}async function oe(e,t){await e.evaluate(e=>{const t="roll-agent-visual-activity-style",a="roll-agent-visual-activity-root",n="roll-agent-visual-activity-viewport",r="roll-agent-visual-activity-region",o="roll-agent-visual-activity-region-shine",i="roll-agent-visual-activity-capsule",s="roll-agent-visual-activity-dot",c="roll-agent-visual-activity-label",d=(e,t,a,n=!1)=>{n&&(e.style.transition="none",t.style.transition="none",a.style.transition="none"),e.style.opacity="0",e.style.transform="scale(0.995)",t.style.opacity="0",a.style.opacity="0",a.style.transform="translate(-50%, -8px)",t.style.transform="translate(-9999px, -9999px)",n&&requestAnimationFrame(()=>{e.style.transition="opacity 180ms ease, transform 220ms ease",t.style.transition="transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease",a.style.transition="opacity 180ms ease, transform 220ms ease"})};(()=>{if(document.getElementById(t))return;const e=document.createElement("style");e.id=t,e.textContent="\n @keyframes roll-visual-activity-breathe {\n 0%, 100% { transform: translate(-50%, 0px) scale(1); }\n 50% { transform: translate(-50%, -1px) scale(1.01); }\n }\n @keyframes roll-visual-activity-scan {\n 0% { transform: translateX(-140%) skewX(-18deg); }\n 100% { transform: translateX(200%) skewX(-18deg); }\n }\n ",document.head.append(e)})(),(()=>{const e=document.getElementById(a);if(e)return e;const t=document.createElement("div");t.id=a,t.style.position="fixed",t.style.inset="0",t.style.pointerEvents="none",t.style.zIndex="2147483646";const d=document.createElement("div");d.id=n,d.setAttribute("aria-hidden","true"),d.style.position="fixed",d.style.inset="10px",d.style.borderRadius="20px",d.style.opacity="0",d.style.transform="scale(0.995)",d.style.transition="opacity 180ms ease, transform 220ms ease";const l=document.createElement("div");l.id=r,l.setAttribute("aria-hidden","true"),l.style.position="fixed",l.style.left="0",l.style.top="0",l.style.width="0",l.style.height="0",l.style.borderRadius="18px",l.style.opacity="0",l.style.overflow="hidden",l.style.transform="translate(-9999px, -9999px)",l.style.transition="transform 220ms cubic-bezier(0.22, 1, 0.36, 1), width 220ms ease, height 220ms ease, opacity 180ms ease";const u=document.createElement("div");u.id=o,u.setAttribute("aria-hidden","true"),u.style.position="absolute",u.style.inset="0",u.style.animation="roll-visual-activity-scan 1.6s linear infinite",u.style.opacity="0.9",l.append(u);const m=document.createElement("div");m.id=i,m.setAttribute("aria-hidden","true"),m.style.position="fixed",m.style.left="50%",m.style.top="20px",m.style.display="inline-flex",m.style.alignItems="center",m.style.gap="10px",m.style.padding="10px 14px",m.style.borderRadius="999px",m.style.opacity="0",m.style.transform="translate(-50%, -8px)",m.style.transition="opacity 180ms ease, transform 220ms ease",m.style.backdropFilter="blur(12px)",m.style.animation="roll-visual-activity-breathe 1.8s ease-in-out infinite",m.style.fontSize="13px",m.style.fontWeight="600",m.style.lineHeight="18px",m.style.letterSpacing="0.01em",m.style.whiteSpace="nowrap";const g=document.createElement("div");g.id=s,g.setAttribute("aria-hidden","true"),g.style.width="8px",g.style.height="8px",g.style.borderRadius="999px",g.style.flex="0 0 auto";const f=document.createElement("div");f.id=c,f.setAttribute("aria-live","polite"),m.append(g,f),t.append(d,l,m),document.documentElement.append(t)})();const l=document.getElementById(n),u=document.getElementById(r),m=document.getElementById(o),g=document.getElementById(i),f=document.getElementById(s),p=document.getElementById(c);if(!(l&&u&&m&&g&&f&&p))return;const h=window.__rollVisualActivityTimers??={};if(void 0!==h.hideTimer&&(globalThis.clearTimeout(h.hideTimer),delete h.hideTimer),"clear"===e.mode)return void d(l,u,g,!0);const w=e.theme;if(void 0!==w&&(l.style.border=`1px solid ${w.accentSoft}`,l.style.boxShadow=`inset 0 0 0 1px ${w.accentSoft}, 0 0 52px ${w.accentGlow}`,g.style.border=`1px solid ${w.capsuleBorder}`,g.style.background=w.capsuleBg,g.style.color=w.text,g.style.boxShadow=`0 18px 46px rgba(15, 23, 42, 0.24), 0 0 0 1px ${w.capsuleBorder}`,f.style.background=w.dot,f.style.boxShadow=`0 0 0 5px ${w.accentGlow}`,u.style.border=`1px solid ${w.accentSoft}`,u.style.background=w.accentGlow,u.style.boxShadow=`0 0 0 1px ${w.accentSoft}, 0 16px 42px ${w.accentGlow}`,m.style.background="linear-gradient(115deg, transparent 0%, rgba(255,255,255,0.08) 24%, rgba(255,255,255,0.42) 50%, transparent 76%)"),void 0!==e.label&&(p.textContent=e.label),g.style.opacity="1",g.style.transform="translate(-50%, 0)",l.style.opacity="complete"===e.mode?"0.9":"0.72",l.style.transform="scale(1)","begin"===e.mode)return u.style.opacity="0",void(u.style.transform="translate(-9999px, -9999px)");if(void 0!==e.rect&&(u.style.width=`${e.rect.width}px`,u.style.height=`${e.rect.height}px`,u.style.transform=`translate(${e.rect.x}px, ${e.rect.y}px)`,u.style.opacity="1"),"complete"!==e.mode)return;const y=Math.max(e.lingerMs??0,0);h.hideTimer=globalThis.setTimeout(()=>{d(l,u,g),delete h.hideTimer},y)},t)}async function ie(e,t){if(!ne()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"begin",label:t.label,theme:ae(t.tone??"info")}),!0}catch{return!1}}async function se(e,t,a={}){if(!ne()||e.isClosed())return!1;try{await t.scrollIntoViewIfNeeded();const n=await re(t,a.padding??Q);return!!n&&(await oe(a.target??e,{mode:"highlight",...void 0!==a.label?{label:a.label}:{},theme:ae(a.tone??"info"),rect:n}),!0)}catch{return!1}}async function ce(e,t,a){return await se(e,e.locator(t).first(),a)}async function de(e,t){if(!ne()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"complete",label:t.label,theme:ae("error"===t.status?"error":"success"),lingerMs:t.lingerMs??ee}),!0}catch{return!1}}async function le(e,t={}){if(!ne()||e.isClosed())return!1;try{return await oe(t.target??e,{mode:"clear"}),!0}catch{return!1}}var ue=C.extend({replyAuthorityKeysLoaded:k.boolean(),visualCursorEnabled:k.boolean(),visualActivityEnabled:k.boolean(),effectiveEnvSources:$}),me=I({name:"browser_status",description:"查询浏览器运行状态和活跃 session 信息",input:k.object({}),output:ue,execute:async(e,t)=>{t.logger.info("Querying browser status");const a=p(),n=h(),r=w(),o=a.isRunning(),{headless:i,mode:s}=a.getConfig(),c=n.getActivePlatforms(),d=[];for(const e of c){const t=n.getPageCount(e),o=n.getCurrentUrl(e);let i=null,s="unknown";if(a.shouldRestoreSessionSnapshot()){const[t,a]=await Promise.all([r.loadCookies(e),r.loadLocalStorage(e)]);i=void 0!==t&&t.length>0||void 0!==a&&Object.keys(a).length>0,s=i?"snapshot":"none"}else a.usesPersistentProfile()&&(i=null,s="profile");d.push({platform:e,pagesOpen:t,currentUrl:o,hasLoginState:i,loginStateSource:s})}return{running:o,headless:i,mode:s,activeSessions:d,replyAuthorityKeysLoaded:b(),visualCursorEnabled:O(),visualActivityEnabled:ne(),effectiveEnvSources:B(M)}}});import{defineTool as ge}from"@roll-agent/sdk";import{BrowserPageInfoSchema as fe,PlatformSchema as pe}from"@roll-agent/browser";import{z as he}from"zod";import{PLATFORMS as we}from"@roll-agent/browser";var ye={zhipin:"https://www.zhipin.com",yupao:"https://www.yupao.com"};function be(e){return new URL(ye[e]).host}function ve(e,t){try{return new URL(e).host.includes(be(t))}catch{return!1}}function xe(e){return we.find(t=>ve(e,t))}function Se(e,t){const a=xe(t.url)??null;return{pageId:t.targetId,url:t.url,title:t.title,boundPlatform:e.getBoundPlatformForNativePage(t.targetId)??null,detectedPlatform:a,isSelectedForPlatform:e.isNativePageSelected(t.targetId)}}async function Ie(e,t){const a=t.url();return{pageId:e.getPageId(t),url:a,title:await t.title().catch(()=>""),boundPlatform:e.getBoundPlatformForPage(t)??null,detectedPlatform:xe(a)??null,isSelectedForPlatform:e.isSelectedPageForPlatform(t)}}var ke=he.object({platform:pe.optional().describe("可选:仅返回指定平台相关的页面")}),Ce=he.object({pages:he.array(fe)}),Ae=ge({name:"list_pages",description:"通过原生 CDP 列出当前浏览器可见页面及其可选择的 pageId;登录前该值等同于原生 targetId。",input:ke,output:Ce,execute:async(e,t)=>{const a=h();t.logger.info("Listing browser pages");const n=(await a.listNativePages()).map(e=>Se(a,e));return{pages:void 0===e.platform?n:n.filter(t=>t.boundPlatform===e.platform||t.detectedPlatform===e.platform)}}});import{defineTool as Re}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Me}from"@roll-agent/browser";import{z as Pe}from"zod";async function Ee(e,t){return e.useTrackedPage(t,e=>ve(e.url(),t))}async function $e(e,t){return(await e.listNativePages()).find(e=>ve(e.url,t))}async function Be(e,t){const a=await $e(e,t);if(a)return await e.activateNativePage(a.targetId),{page:a,reusedExistingPage:!0};return{page:await e.openNativePage(ye[t]),reusedExistingPage:!1}}var _e,Ne=Pe.object({url:Pe.string().url().describe("要导航到的目标 URL")}),Te=Pe.object({success:Pe.boolean(),page:Me});function ze(){return{getContextManager:h,detectPlatformFromUrl:xe,matchesPlatformHost:ve,findTrackedPlatformPage:Ee,toAttachedPageInfo:Ie,..._e}}async function qe(e,t,a){const n=(await e.listNativePages()).find(e=>t.matchesPlatformHost(e.url,a));if(n)return await e.selectNativePage(a,n.targetId),await e.getPage(a)}async function Le(e,t,a,n){const r=t.detectPlatformFromUrl(a);if(!r){const t=await e.getActivePage();if(!t)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return{page:t}}const o=await t.findTrackedPlatformPage(e,r);if(o)return n.info(`Reusing tracked ${r} page instead of navigating the current unrelated tab`),{page:o,platform:r};const i=await qe(e,t,r);if(i)return n.info(`Reusing native ${r} page instead of navigating the current unrelated tab`),{page:i,platform:r};const s=await e.getActivePage();if(!s)throw new Error("No active browser tab detected. Use open_platform or select_page first.");return n.warn(`No existing ${r} page found; falling back to navigating the current active tab`),{page:s,platform:r}}async function Fe(e,t,a){return await e.selectAttachedPage(a,e.getPageId(t))}async function je(e,t){e.url()!==t&&await e.goto(t,{waitUntil:"domcontentloaded"})}var Oe=Re({name:"navigate_active_tab",description:"导航到指定 URL;若 URL 属于已知平台(Boss/鱼泡),优先复用已打开的平台页,避免把无关 tab 导航成第二个平台页。",input:Ne,output:Te,execute:async(e,t)=>{const a=ze(),n=a.getContextManager();t.logger.info(`Navigating active tab to ${e.url}`);const{page:r,platform:o}=await Le(n,a,e.url,t.logger);await r.bringToFront().catch(()=>{}),await je(r,e.url);const i=o??a.detectPlatformFromUrl(r.url()),s=i?await Fe(n,r,i):r;return i?t.logger.info(`Bound navigated page to ${i}`):n.clearBindingForPage(r),{success:!0,page:await a.toAttachedPageInfo(n,s)}}});import{defineTool as Ue}from"@roll-agent/sdk";import{BrowserPageInfoSchema as De,PlatformSchema as Ve}from"@roll-agent/browser";import{z as We}from"zod";var He=We.object({platform:Ve.describe("目标平台:`zhipin` 代表 BOSS直聘,`yupao` 代表鱼泡")}),Ze=We.object({success:We.boolean(),page:De,reusedExistingTab:We.boolean()}),Ge=Ue({name:"open_platform",description:"打开并聚焦招聘平台主页,供用户手动登录或后续执行站内操作。",input:He,output:Ze,execute:async(e,t)=>{const{platform:a}=e,n=p(),r=h();t.logger.info(`Opening platform page for ${a}`);const{page:o,reusedExistingPage:i}=await Be(n,a);return r.rememberNativePageSelection(a,o),{success:!0,page:Se(r,o),reusedExistingTab:i}}});import{defineTool as Ye}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Ke,PlatformSchema as Je}from"@roll-agent/browser";import{z as Xe}from"zod";var Qe=Xe.object({platform:Je.describe("要将该页面绑定为当前活跃页的平台"),pageId:Xe.string().describe("通过 list_pages 返回的 pageId;登录前就是原生 targetId,登录后仍可作为稳定选择句柄")}),et=Xe.object({success:Xe.boolean(),page:Ke}),tt=Ye({name:"select_page",description:"将指定 pageId 绑定为平台当前活跃页,并切换到前台;登录前走原生 CDP target 激活。",input:Qe,output:et,execute:async(e,t)=>{const a=h();t.logger.info(`Selecting page ${e.pageId} for ${e.platform}`);const n=await a.selectNativePage(e.platform,e.pageId);return{success:!0,page:Se(a,n)}}});import{defineTool as at}from"@roll-agent/sdk";import{z as nt}from"zod";async function rt(e,t=300,a=800){const n=Math.floor(Math.random()*(a-t))+t;await e.waitForTimeout(n)}async function ot(e){const t=Math.random();let a;a=t<.5?800+1200*Math.random():t<.8?500+300*Math.random():t<.95?2e3+2e3*Math.random():4e3+2e3*Math.random(),await e.waitForTimeout(Math.floor(a))}async function it(e,t){const a=t?.minDistance??50,n=t?.maxDistance??200,r=t?.direction??"both",o=Math.floor(Math.random()*(n-a))+a,i="up"===r?-1:"down"===r||Math.random()>.5?1:-1;await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},o*i),await rt(e,200,500)}async function st(e){if(Math.random()<.8){const t=100+Math.floor(100*Math.random());await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},t),await ot(e)}if(Math.random()<.5){const t=(50+Math.floor(100*Math.random()))*(Math.random()>.5?1:-1);await e.evaluate(e=>{window.scrollBy({top:e,behavior:"smooth"})},t)}}function ct(e=.3){return Math.random()<e}import{setTimeout as dt}from"node:timers/promises";var lt=".geek-item.selected";async function ut(e){try{await e.waitForSelector(lt,{timeout:5e3})}catch{return null}const t=await e.evaluate(()=>{const e=document.querySelector(".geek-item.selected");if(!e)return null;const t=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"";return{conversationId:t,candidateId:e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??t,candidateName:e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name')?.textContent?.trim()??""}});return t&&"string"==typeof t.conversationId&&"string"==typeof t.candidateId&&0!==t.conversationId.length&&0!==t.candidateId.length?t:null}async function mt(e){const t=await e.evaluate(()=>{const e=[".chat-conversation",".conversation-box",".conversation-message"],t=[".base-info-single-detial .name-box",".base-info-content .name-box",".base-info-single-container .name-box",".base-info-content .base-name",".chat-user-name",".name-box",".base-name"];for(const a of e){const e=document.querySelector(a);if(e)for(const a of t){const t=e.querySelector(a)?.textContent?.trim()??"";if(t.length>0)return{candidateName:t}}}return null});return t&&"string"==typeof t.candidateName&&0!==t.candidateName.length?t:null}var gt="https://www.zhipin.com/web/geek/chat",ft=".chat-list-wrap, .geek-item",pt=new Set(["消息"]),ht="data-roll-chat-entry-target",wt="data-roll-chat-item-target";function yt(e){return e.trim().toLocaleLowerCase("zh-CN")}function bt(e,t){const a=yt(e),n=yt(t);return a.length>0&&n.length>0&&(a===n||a.includes(n)||n.includes(a))}function vt(e,t){let a=0;for(const n of e)t.includes(n)&&(a+=1);return a}function xt(e,t){if(void 0!==t.conversationId){const a=e.find(e=>e.conversationId===t.conversationId);if(a)return a}const a=t.candidateName;if(a){const t=yt(a),n=e.filter(e=>e.name.length>0);let r=n.find(e=>yt(e.name)===t);if(r)return r;if(r=n.find(e=>{const a=yt(e.name);return a.includes(t)||t.includes(a)}),r)return r;const o=t.length<=2?1:t.length<=4?.75:.6;if(r=n.find(e=>{const a=yt(e.name);return vt(t,a)>=Math.ceil(Math.min(t.length,a.length)*o)}),r)return r}if(void 0!==t.index)return e[t.index]}function St(e){return e.includes("/web/geek/chat")||e.includes("/web/chat")}async function It(e,t=1e4){try{return await e.waitForSelector(ft,{timeout:t}),!0}catch{return!1}}async function kt(e,t){const a=(await e.listAttachedPages()).find(e=>e!==t&&St(e.url()));if(a)return await e.selectAttachedPage("zhipin",e.getPageId(a))}async function Ct(e,t){await e.evaluate(e=>{document.querySelectorAll(`[${e}]`).forEach(t=>{t.removeAttribute(e)})},t).catch(()=>{})}async function At(e,t){const a=e.locator(t).first();await a.scrollIntoViewIfNeeded(),await Z(e,a),await a.hover(),await rt(e,200,400),await G(e,a),await a.click()}async function Rt(e){const t=await e.evaluate(e=>{const t=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},a=t=>e.messageLabels.some(e=>t===e||t.includes(e));document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const n=Array.from(document.querySelectorAll('a[href*="/web/geek/chat"], a[href*="/web/chat"]'));for(const a of n)if(t(a))return a.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`};const r=Array.from(document.querySelectorAll('a, button, [role="link"], [role="button"], span, div'));for(const n of r){if(a(n.textContent?.trim()??"")&&t(n))return n.setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}}return{found:!1}},{markerAttr:ht,messageLabels:[...pt]});if(!t.found)return!1;try{return await At(e,t.selector),!0}finally{await Ct(e,ht)}}function Mt(e){return e instanceof Error&&/ERR_ABORTED/i.test(e.message)}async function Pt(e){try{return await e.goto(gt,{waitUntil:"domcontentloaded"}),!0}catch(t){return!!Mt(t)&&(!!St(e.url())||await It(e,2e3))}}async function Et(e,t){if(!await Rt(t))return!1;if(await It(t,5e3))return!0;const a=await kt(e,t);return!!a&&await It(a,5e3)}async function $t(e,t){if(St(t.url())&&await It(t))return!0;const a=await kt(e,t);if(a&&await It(a))return!0;if(await Et(e,t))return!0;if(!await Pt(t))return!1;if(await It(t))return!0;await dt(300);const n=await kt(e,t);return!!n&&await It(n,5e3)}async function Bt(e){return e.evaluate(()=>Array.from(document.querySelectorAll(".geek-item")).map((e,t)=>{const a=e.getAttribute("data-id")??e.closest('[role="listitem"]')?.getAttribute("key")??"",n=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??a,r=e.querySelector('[class*="name"], .nickname, .geek-name, .candidate-name'),o=r?.textContent?.trim()??"",i=e.querySelector(".source-job")?.textContent?.trim()??"",s=e.querySelector(".badge-count"),c=parseInt(s?.textContent?.trim()??"0",10)||0;return{conversationId:a,candidateId:n,name:o,index:t,position:i,hasUnread:c>0||null!==e.querySelector(".red-dot"),unreadCount:c,lastMessageTime:e.querySelector(".time, .time-shadow")?.textContent?.trim()??"",messagePreview:(e.querySelector(".push-text, .chat-last-msg")?.textContent?.trim()??"").slice(0,100)}}))}async function _t(e,t){const a=await e.evaluate(e=>{document.querySelectorAll(`[${e.markerAttr}]`).forEach(t=>{t.removeAttribute(e.markerAttr)});const t=Array.from(document.querySelectorAll(".geek-item")),a=t.find(t=>(t.getAttribute("data-id")??t.closest('[role="listitem"]')?.getAttribute("key")??"")===e.targetConversationId)??t[e.targetIndex];if(!a)return{found:!1};return(a.querySelector(".chat-item-content")??a).setAttribute(e.markerAttr,"true"),{found:!0,selector:`[${e.markerAttr}="true"]`}},{markerAttr:wt,targetConversationId:t.conversationId,targetIndex:t.index});if(!a.found)return!1;try{return await At(e,a.selector),!0}finally{await Ct(e,wt)}}async function Nt(e,t){if(0===t.conversationId.length&&0===t.name.length)return await rt(e,500,900),!0;try{return await e.waitForFunction(e=>{const t=e=>e.trim().toLocaleLowerCase("zh-CN"),a=document.querySelector(".geek-item.selected"),n=a?.getAttribute("data-id")??a?.closest('[role="listitem"]')?.getAttribute("key")??"",r=0===e.conversationId.length||n===e.conversationId,o=(()=>{const e=[".chat-conversation",".conversation-box",".conversation-message"],t=[".base-info-single-detial .name-box",".base-info-content .name-box",".base-info-single-container .name-box",".base-info-content .base-name",".chat-user-name",".name-box",".base-name"];for(const a of e){const e=document.querySelector(a);if(e)for(const a of t){const t=e.querySelector(a)?.textContent?.trim()??"";if(t.length>0)return t}}return""})(),i=0===e.candidateName.length||((e,a)=>{const n=t(e),r=t(a);return n.length>0&&r.length>0&&(n===r||n.includes(r)||r.includes(n))})(e.candidateName,o);return r&&i},{conversationId:t.conversationId,candidateName:t.name},{timeout:5e3}),!0}catch{return await rt(e,800,1200),!1}}async function Tt(e,t,a){if(void 0===a.conversationId&&void 0===a.candidateName&&void 0===a.index)return;if(!await $t(e,t))return{found:!1,conversationId:"",candidateId:"",name:"",index:-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const n=await e.getPage("zhipin"),r=xt(await Bt(n),a);if(!r){return{found:!1,conversationId:"",candidateId:"",name:"",index:-1,position:"",hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:`未找到候选人: ${a.conversationId??a.candidateName??`index ${a.index}`}`}}if(!await _t(n,r))return{...r,found:!1,error:`打开候选人聊天失败: ${r.name||`index ${r.index}`}`};let o=await Nt(n,r);if(!o){await _t(n,r)&&(o=await Nt(n,r))}if(!o)return{...r,found:!1,error:`打开候选人聊天后,右侧会话未同步切换到 ${r.name||r.conversationId}`};const i=await ut(n);if(!i||i.conversationId!==r.conversationId)return{...r,found:!1,error:`当前选中会话与目标会话不一致: ${r.name||r.conversationId}`};const s=await mt(n);return!(r.name.length>0)||s&&bt(r.name,s.candidateName)?{...r,found:!0}:{...r,found:!1,error:`右侧聊天面板仍未切换到 ${r.name}`}}function zt(e){return"function"==typeof e.page}function qt(e){return zt(e)?e.page():e}var Lt=class{page;target;constructor(e){this.page=qt(e),this.target=e}async retarget(e){if(e===this.target)return this.page=qt(e),this.target=e,!0;const t=this.page,a=this.target;return await le(t,{target:a}),this.page=qt(e),this.target=e,!0}async begin(e,t="info"){return await ie(this.page,{label:e,tone:t,target:this.target})}async highlightSelector(e,t={}){return await ce(this.page,e,{...t,target:this.target})}async highlightLocator(e,t={}){return await se(this.page,e,{...t,target:this.target})}async succeed(e,t){return await de(this.page,{label:e,...void 0!==t?{lingerMs:t}:{},status:"success",target:this.target})}async fail(e,t){return await de(this.page,{label:e,...void 0!==t?{lingerMs:t}:{},status:"error",target:this.target})}async clear(){return await le(this.page,{target:this.target})}},Ft=nt.object({name:nt.string(),conversationId:nt.string(),candidateId:nt.string(),position:nt.string(),time:nt.string(),preview:nt.string(),unreadCount:nt.number(),hasUnread:nt.boolean(),index:nt.number()}),jt=nt.object({success:nt.boolean(),candidates:nt.array(Ft),total:nt.number(),stats:nt.object({withName:nt.number(),withUnread:nt.number()})}),Ot=at({name:"zhipin_read_messages",description:"读取 BOSS直聘消息列表,默认返回全部候选人;若只看未读消息,传 onlyUnread=true",input:nt.object({limit:nt.number().optional().describe("最多返回条数"),onlyUnread:nt.boolean().default(!1).describe("是否只返回有未读消息的候选人;用户说“全部/所有消息列表”时应为 false,说“未读消息”时应为 true"),sortBy:nt.enum(["time","unreadCount","name"]).default("time")}),output:jt,execute:async(e,t)=>{const a=e.onlyUnread??!1;t.logger.info(`Reading zhipin messages (limit: ${e.limit??"all"}, onlyUnread: ${a})`);const n=h(),r=await n.getPage("zhipin"),o=new Lt(r),i=a?"正在读取未读消息列表":"正在读取消息列表";await o.begin("正在打开消息列表");try{const s=await $t(n,r),c=await n.getPage("zhipin");if(await o.retarget(c),!s)return await o.fail("未找到消息列表"),{success:!1,candidates:[],total:0,stats:{withName:0,withUnread:0}};await o.begin(i),await o.highlightSelector(".user-list.b-scroll-stable, .chat-user .user-container, .chat-list-wrap",{label:i,padding:8}),await st(c);const d=(await Bt(c)).map(e=>({name:e.name,conversationId:e.conversationId,candidateId:e.candidateId,position:e.position,time:e.lastMessageTime,preview:e.messagePreview,unreadCount:e.unreadCount,hasUnread:e.hasUnread,index:e.index}));let l=a?d.filter(e=>e.hasUnread):d;const u=e.sortBy??"time";"time"===u||("unreadCount"===u?l.sort((e,t)=>t.unreadCount-e.unreadCount):"name"===u&&l.sort((e,t)=>e.name.localeCompare(t.name))),void 0!==e.limit&&(l=l.slice(0,e.limit));const m={withName:d.filter(e=>e.name.length>0).length,withUnread:d.filter(e=>e.hasUnread).length};return await o.succeed(a?`已读取 ${l.length} 条未读消息`:`已读取 ${l.length} 条消息`),t.logger.info(`Found ${l.length} candidates (${m.withUnread} with unread)`),{success:!0,candidates:l,total:d.length,stats:m}}catch(e){throw await o.fail("读取消息列表失败"),e}}});import{defineTool as Ut}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Dt}from"@roll-agent/browser";import{z as Vt}from"zod";var Wt={login:{notLoggedIn:".header-login-btn"},unread:{container:".chat-list-wrap",listItem:'[role="listitem"]',geekItemWrap:".geek-item-wrap",item:".chat-item",unreadBadge:".badge-count",unreadBadgeNew:".badge-count.badge-count-common-less",unreadBadgeSpan:".badge-count span",unreadDot:".red-dot",figure:".figure",badge:".badge",candidateName:".candidate-name",candidateNameAlt:".chat-item-name",candidateNameSelectors:'[class*="name"], .nickname, .geek-name',candidateNameNew:".geek-name",jobTitle:".source-job",lastMessage:".push-text",lastMessageAlt:".chat-last-msg",messageTime:".time, .time-shadow",clickArea:".chat-item-content",unreadCandidates:".geek-item"},chat:{chatContainer:".chat-container",messageList:".message-list",messageItem:".message-item",messageContent:".message-content",messageText:".text-content",userMessage:".message-right",candidateMessage:".message-left",messageTime:".message-time",senderName:".sender-name",inputBox:".chat-input",inputTextarea:"textarea.chat-input",inputEditorId:"#boss-chat-editor-input",sendButton:".btn-send",conversationEditor:".conversation-editor",submitButton:".submit-content .submit",submitButtonActive:".submit-content .submit.active",submitContent:".submit-content",sendButtonAlt:".conversation-editor .submit-content",sendIcon:".submit-content .icon-send",systemMessage:".system-msg"},chatDetails:{candidateInfoContainer:".base-info-single-container, .base-info-content",candidateName:".name-box, .geek-name, .base-name",candidateInfoItem:".geek-info-item, .base-info-item, .base-info-single-detial > div",candidateTag:".geek-tag, .high-light-boss",communicationPosition:".position-name",communicationPositionAlt:".position-item:not(.expect) .value.high-light-boss",candidateExpectContainer:".position-item.expect",candidateExpectValue:".position-item.expect .value.job",candidateExpectSalary:".position-item.expect .high-light-orange",candidatePosition:".geek-position, .position-name",candidatePositionAlt:".position-content .value, .position-item .value",chatMessageContainer:".conversation-message, .message-list",messageItem:".message-item",messageTime:".message-time .time",messageTextSpan:".text span",systemMessage:".item-system",friendMessage:".item-friend",myMessage:".item-myself",resumeMessage:".item-resume",readStatus:".status-read"},exchangeWechat:{exchangeButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > span.operate-btn",exchangeButtonFallback:".operate-exchange-left .operate-btn",confirmDialog:".exchange-tooltip",confirmButton:".exchange-tooltip .btn-box .boss-btn-primary.boss-btn",confirmButtonPath:"#container > div:nth-child(1) > div > div.chat-box > div.chat-container > div.chat-conversation > div.conversation-box > div.conversation-operate > div.toolbar-box > div.toolbar-box-right > div.operate-exchange-left > div:nth-child(3) > div > div > span.boss-btn-primary.boss-btn",cancelButton:".exchange-tooltip .btn-box .boss-btn-outline.boss-btn",wechatCard:".message-card-top-wrap",wechatCardAlt:'[class*="d-top-text"]'},username:{primary:".nav-item.nav-logout .user-name",fallbacks:["#header > div > div > div.nav-item.nav-logout > div.top-profile-logout.ui-dropmenu.ui-dropmenu-drop-arrow > div.ui-dropmenu-label > div > span.user-name",".ui-dropmenu-label .user-name",".nav-logout .user-name","#header .user-name",".top-profile .user-name",".nav-user .user-name",".user-name",'[class*="user-name"]','[class*="username"]','[data-qa="user-name"]',".header-user-name","#header .label-name"]},nav:{sidebar:".side-wrap.side-wrap-v2",chatLink:'.side-wrap.side-wrap-v2 a[href*="/web/chat/index"]',recommendLink:'.side-wrap.side-wrap-v2 a[href*="/web/geek/recommend"]'},recommend:{iframe:"#recommendFrame",resumeIframe:'iframe[src*="c-resume"]',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"}},Ht={chat:"沟通",recommend:"推荐牛人"};function Zt(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}async function Gt(e){for(const t of e)try{if(await t.count()>0&&await t.first().isVisible())return t.first()}catch{}return null}function Yt(e){return Ht[e]}async function Kt(e,t){const a=e.locator(Wt.nav.sidebar).first(),n=Yt(t),r=new RegExp(`^${Zt(n)}(?:\\s|$)`),o="recommend"===t?[Wt.nav.recommendLink]:[Wt.nav.chatLink],i=[a.getByRole("link",{name:r}).first(),...o.map(e=>a.locator(e).first()),a.locator(`a:has-text("${n}")`).first(),e.getByRole("link",{name:r}).first()];return await Gt(i)}function Jt(e){return!!e.url().includes("/web/geek/recommend")||(null!==e.frame("recommendFrame")||e.frames().some(e=>e.url().includes("recommend")))}async function Xt(e){if(e.url().includes("/web/chat/index"))return!0;try{const t=e.locator("#container.chat-container-private").first();return await t.count()>0&&await t.isVisible()}catch{return!1}}async function Qt(e,t=1e4,a=250){const n=Date.now()+t;for(;Date.now()<n;){if(Jt(e))return!0;if(e.isClosed())return!1;await e.waitForTimeout(Math.min(a,Math.max(n-Date.now(),0)))}return Jt(e)}async function ea(e,t=1e4,a=250){const n=Date.now()+t;for(;Date.now()<n;){if(await Xt(e))return!0;if(e.isClosed())return!1;await e.waitForTimeout(Math.min(a,Math.max(n-Date.now(),0)))}return await Xt(e)}var ta,aa=Vt.object({success:Vt.boolean(),alreadyOnChat:Vt.boolean(),usedSidebarClick:Vt.boolean(),chatReady:Vt.boolean(),page:Dt.optional(),error:Vt.string().optional()});function na(){return{getContextManager:h,findZhipinSidebarSectionLink:Kt,isZhipinChatSurfaceOpen:Xt,waitForZhipinChatSurface:ea,moveVisualCursorToLocator:Z,showVisualClickOnLocator:G,randomDelay:rt,toAttachedPageInfo:Ie,createVisualActivitySession:e=>new Lt(e),...ta}}async function ra(e,t,a){return await t.toAttachedPageInfo(e,a)}async function oa(e,t,a,n,r,o){return await a.fail(r),{success:!1,...o,page:await ra(e,t,n),error:r}}async function ia(e,t,a,n){await a.scrollIntoViewIfNeeded(),await t.moveVisualCursorToLocator(e,a,{durationMs:110,settleMs:30}),await a.hover(),await t.randomDelay(e,100,180),await t.showVisualClickOnLocator(e,a,{pulseDurationMs:180}),await a.click(),n.info("Clicked Boss sidebar nav: 沟通")}var sa=Ut({name:"zhipin_open_chat_page",description:"通过点击 Boss 左侧导航切换回「沟通」页,避免让编排器依赖站内 URL 猜测。",input:Vt.object({}),output:aa,execute:async(e,t)=>{const a=na(),n=a.getContextManager();t.logger.info("Opening Boss chat page via sidebar navigation");const r=await n.getPage("zhipin");await r.bringToFront().catch(()=>{});const o=a.createVisualActivitySession(r),i="正在切换到沟通页";if(await o.begin(i),await o.highlightSelector(Wt.nav.sidebar,{label:i,padding:10}),await a.isZhipinChatSurfaceOpen(r))return await o.succeed("已在沟通页"),{success:!0,alreadyOnChat:!0,usedSidebarClick:!1,chatReady:!0,page:await ra(n,a,r)};const s=await a.findZhipinSidebarSectionLink(r,"chat");if(!s)return await oa(n,a,o,r,"未找到沟通导航",{alreadyOnChat:!1,usedSidebarClick:!1,chatReady:!1});try{await ia(r,a,s,t.logger)}catch(e){return await oa(n,a,o,r,e instanceof Error?e.message:"点击沟通导航失败",{alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1})}return await a.waitForZhipinChatSurface(r)?(await o.succeed("已切换到沟通页"),{success:!0,alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!0,page:await ra(n,a,r)}):await oa(n,a,o,r,"沟通页未就绪",{alreadyOnChat:!1,usedSidebarClick:!0,chatReady:!1})}});import{defineTool as ca}from"@roll-agent/sdk";import{z as da}from"zod";var la=da.object({success:da.boolean(),conversationId:da.string(),candidateId:da.string(),candidateName:da.string(),index:da.number(),hasUnread:da.boolean(),unreadCount:da.number(),lastMessageTime:da.string(),messagePreview:da.string(),error:da.string().optional()}),ua=ca({name:"zhipin_open_chat",description:"打开指定候选人的聊天窗口(优先按 conversationId,其次姓名,最后才用索引)",input:da.object({conversationId:da.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:da.string().optional().describe("候选人姓名。若用户说“打开鲁倩的聊天”,这里应提取为“鲁倩”"),index:da.number().optional().describe("候选人在列表中的索引。仅在缺少 conversationId 时兜底"),preferUnread:da.boolean().default(!1).describe("优先选择有未读消息的候选人")}),output:la,execute:async(e,t)=>{t.logger.info(`Opening chat: name=${e.candidateName??"N/A"}, index=${e.index??"N/A"}`);const a=h(),n=await a.getPage("zhipin");let r={conversationId:e.conversationId,candidateName:e.candidateName,index:e.index};if(e.preferUnread&&void 0===e.conversationId&&void 0===e.candidateName&&void 0===e.index){if(!await $t(a,n))return{success:!1,conversationId:"",candidateId:"",candidateName:"",index:-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:"消息列表未加载"};const e=await a.getPage("zhipin"),t=(await Bt(e)).find(e=>e.hasUnread);t&&(r={conversationId:t.conversationId,candidateName:t.name,index:t.index})}const o=await Tt(a,n,r);return o&&o.found?(t.logger.info(`Opened chat with ${o.name} (index: ${o.index})`),{success:!0,conversationId:o.conversationId,candidateId:o.candidateId,candidateName:o.name,index:o.index,hasUnread:o.hasUnread,unreadCount:o.unreadCount,lastMessageTime:o.lastMessageTime,messagePreview:o.messagePreview}):{success:!1,conversationId:"",candidateId:"",candidateName:e.candidateName??"",index:e.index??-1,hasUnread:!1,unreadCount:0,lastMessageTime:"",messagePreview:"",error:o?.error??`未找到候选人: ${e.candidateName??`index ${e.index}`}`}}});import{defineTool as ma}from"@roll-agent/sdk";import{z as ga}from"zod";var fa="·",pa=/[--—–]/;function ha(e){return(e??"").trim()}function wa(e){const t=ha(e);if(!t||!pa.test(t))return;const[a=""]=t.split(pa);return ha(a)||void 0}function ya(e){const t=ha(e);if(!t)return{expectedLocation:"",expectedPosition:""};const[a="",n=""]=t.split(fa).map(e=>ha(e));return{expectedLocation:a,expectedPosition:n}}function ba(e){const t=ha(e.communicationPosition),{expectedLocation:a,expectedPosition:n}=ya(e.expectedJobText),r=wa(t);return{communicationPosition:t,expectedLocation:a,expectedPosition:n,...void 0!==r?{preferredBrand:r}:{}}}var va=ga.object({index:ga.number(),sender:ga.enum(["candidate","recruiter","system"]),messageType:ga.enum(["text","system","resume","wechat-exchange"]),content:ga.string(),time:ga.string()}),xa=ga.object({name:ga.string(),age:ga.string(),experience:ga.string(),education:ga.string(),communicationPosition:ga.string(),expectedPosition:ga.string(),expectedLocation:ga.string(),expectedSalary:ga.string(),tags:ga.array(ga.string())}),Sa=ga.object({success:ga.boolean(),conversationId:ga.string(),candidateId:ga.string(),candidateInfo:xa,preferredBrand:ga.string().optional(),chatMessages:ga.array(va),formattedHistory:ga.array(ga.string()),stats:ga.object({totalMessages:ga.number(),candidateMessages:ga.number(),recruiterMessages:ga.number(),systemMessages:ga.number()}),error:ga.string().optional()});function Ia(){return{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]}}function ka(e){return{success:!1,conversationId:"",candidateId:"",candidateInfo:Ia(),chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:e}}function Ca(e,t){const a=e.trim().toLocaleLowerCase("zh-CN"),n=t.trim().toLocaleLowerCase("zh-CN");return a.length>0&&n.length>0&&(a===n||a.includes(n)||n.includes(a))}var Aa=ma({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 conversationId 或 candidateName 自动打开对应聊天;若已从 `zhipin_read_messages` 获取 conversationId,优先传它。",input:ga.object({conversationId:ga.string().optional().describe("会话 ID。若已从 `zhipin_read_messages` 获取,优先传这个,最稳定"),candidateName:ga.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:ga.number().optional().describe("候选人在列表中的索引(可选,仅兜底)"),maxMessages:ga.number().default(100).describe("最多返回的消息条数")}),output:Sa,execute:async(e,t)=>{const a=e.maxMessages??100,n=h(),r=await n.getPage("zhipin"),o=new Lt(r),i=void 0!==e.conversationId||void 0!==e.candidateName||void 0!==e.index?"正在打开目标聊天":"正在准备当前聊天",s="正在提取聊天记录",c=async(e,t)=>(await o.fail(e),ka(t));await o.begin(i);try{const i=await Tt(n,r,{conversationId:e.conversationId,candidateName:e.candidateName,index:e.index}),d=await n.getPage("zhipin");if(await o.retarget(d),i&&!i.found)return await c("打开聊天失败",i.error??"打开聊天失败");t.logger.info("Extracting candidate info"+(i?` for ${i.name}`:" (current window)")),await o.begin(s),await o.highlightSelector(".chat-conversation, .conversation-box, .conversation-message",{label:s,padding:12});const l=i?.name??e.candidateName??"",u=await mt(d);if(l.length>0&&(!u||!Ca(l,u.candidateName)))return await c("聊天面板未同步",`右侧聊天面板未切换到 ${l}`);const m=await ut(d);if(!m)return await c("未识别当前会话","未能提取当前选中聊天的 conversationId/candidateId");if(i&&m.conversationId!==i.conversationId)return await c("当前会话不一致",`当前选中会话与目标会话不一致: ${i.name||i.conversationId}`);if(u&&m.candidateName.length>0&&!Ca(m.candidateName,u.candidateName))return await c("左右面板不一致",`左侧选中会话与右侧聊天面板不一致: ${m.candidateName} / ${u.candidateName}`);try{await d.waitForSelector(".chat-message-list .message-item, .conversation-message .message-item",{timeout:8e3})}catch{}const g=await d.evaluate(e=>{const t=document.querySelector(".chat-conversation")??document.querySelector(".conversation-box")??document,a=t.querySelector(".base-info-single-detial, .base-info-content, .base-info-single-container"),n=a?.querySelector(".name-box, .base-name, .chat-user-name, .geek-name")?.textContent?.trim()??"",r=a?a.querySelectorAll(":scope > div"):t.querySelectorAll(".geek-info-item, .base-info-item"),o=[];r.forEach(e=>{const t=e.textContent?.trim();t&&o.push(t)});const i=o.join(" "),s=i.match(/(\d{2,3})岁/),c=s?s[1]+"岁":"",d=i.match(/(\d+年(?:以上)?|应届生|在校生)/),l=d?.[1]??"",u=i.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1]??"";let m="";const g=t.querySelector(".position-name");if(g){const e=g.cloneNode(!0);e.querySelectorAll(".popover-wrap, .tooltip-job").forEach(e=>e.remove()),m=e.textContent?.trim()??""}let f="";const p=t.querySelector(".position-item.expect .value.job");p&&(f=p.textContent?.trim()??"");const h=t.querySelector(".position-item.expect .high-light-orange")?.textContent?.trim()??"",w=[];a&&a.querySelectorAll(".geek-tag, .base-info-item .high-light-boss").forEach(e=>{const t=e.textContent?.trim();t&&!t.includes("更换职位")&&t.length<20&&w.push(t)});const y=t.querySelectorAll(".chat-message-list > .message-item, .conversation-message .message-item"),b=/\d{1,2}:\d{2}(?::\d{2})?|\d{4}-\d{2}-\d{2}/,v=[];let x=0;return y.forEach(t=>{if(x>=e)return;const a=null!==t.querySelector(".item-friend"),n=null!==t.querySelector(".item-myself"),r=null!==t.querySelector(".item-system"),o=null!==t.querySelector(".item-resume"),i=null!==t.querySelector(".message-dialog-center");let s="system",c="text";a?s="candidate":n?s="recruiter":(r||i)&&(s="system",c="system"),o&&(c="resume");const d=t.querySelector(".message-card-top-wrap, [class*='d-top-text']");if(d){const e=d.textContent??"";(e.includes("微信")||e.includes("WeChat"))&&(c="wechat-exchange")}const l=t.querySelector(".message-time .time, .message-time"),u=(l?.textContent??"").match(b),m=u?u[0]:"";let g="";if("wechat-exchange"===c&&d){const e=d.textContent??"",t=e.match(/\b(\d{8,15})\b/),a=e.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);g=t?`[微信号: ${t[1]}]`:a?`[微信号: ${a[1]}]`:"[交换微信]"}else if(d){const e=t.querySelector(".message-card-top-title"),a=t.querySelector(".dialog-content, .message-card-top-text");g=(e?.textContent?.trim()??a?.textContent?.trim()??"").trim()}else{const e=t.querySelector(".text span, .text-content, .text");e&&(g=(e.textContent?.trim()??"").replace(b,"").replace("已读","").trim())}(g||"text"!==c)&&(v.push({index:x,sender:s,messageType:c,content:g,time:m}),x++)}),{candidateInfo:{name:n,age:c,experience:l,education:u,communicationPosition:m,expectedJobText:f,expectedSalary:h,tags:w},messages:v}},a),f=ba({communicationPosition:g.candidateInfo.communicationPosition,expectedJobText:g.candidateInfo.expectedJobText}),p=g.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),h={totalMessages:g.messages.length,candidateMessages:g.messages.filter(e=>"candidate"===e.sender).length,recruiterMessages:g.messages.filter(e=>"recruiter"===e.sender).length,systemMessages:g.messages.filter(e=>"system"===e.sender).length};return await o.succeed(`已提取 ${h.totalMessages} 条聊天记录`),t.logger.info(`Extracted info for ${g.candidateInfo.name}: ${h.totalMessages} messages`),{success:!0,conversationId:m.conversationId,candidateId:m.candidateId,candidateInfo:{name:g.candidateInfo.name,age:g.candidateInfo.age,experience:g.candidateInfo.experience,education:g.candidateInfo.education,communicationPosition:f.communicationPosition,expectedPosition:f.expectedPosition,expectedLocation:f.expectedLocation,expectedSalary:g.candidateInfo.expectedSalary,tags:g.candidateInfo.tags},...void 0!==f.preferredBrand?{preferredBrand:f.preferredBrand}:{},chatMessages:g.messages,formattedHistory:p,stats:h}}catch(e){throw await o.fail("提取聊天记录失败"),e}}});import{defineTool as Ra}from"@roll-agent/sdk";import{z as Ma}from"zod";var Pa=30,Ea=new Set(["招聘规范","消息","首页","推荐牛人","看简历","我的客服","面试","招聘数据","账号权益","升级VIP","职位","职位管理","牛人","公司","数据统计","设置","帮助中心","登录","注册","退出登录","退出","BOSS直聘","下载APP","搜索","发布职位"]);function $a(e){const t=e.trim();return!(0===t.length||t.length>Pa)&&(!Ea.has(t)&&!/登录|注册|退出|下载|帮助|设置|管理/.test(t))}function Ba(e){const t=[],a=/(link|button|menuitem|img|heading)\s+"([^"]+)"/g;let n;for(;null!==(n=a.exec(e));){const e=n[2]?.trim()??"";e.length>0&&t.push({role:n[1]??"",name:e})}return t}function _a(e){let t=e.priority;return Ea.has(e.text.trim())&&(t+=10),e.text.trim().length>10&&(t+=5),/^[\u4e00-\u9fff]{2,4}$/.test(e.text.trim())&&(t-=.5),void 0!==e.xRatio&&(t-=4*(e.xRatio-.5)),t}function Na(e){const t=e.filter(e=>$a(e.text));if(0===t.length)return{found:!1};const a=new Map;for(const e of t){const t=e.text.trim(),n=a.get(t)??new Set;n.add(e.strategy),a.set(t,n)}const n=t.map(e=>{let t=_a(e);const n=a.get(e.text.trim())?.size??1;return n>1&&(t-=.5*n),{evidence:e,score:t}}).sort((e,t)=>e.score-t.score)[0];return n?{found:!0,username:n.evidence.text.trim(),strategy:n.evidence.strategy,source:n.evidence.source}:{found:!1}}async function Ta(e){const t=[e.getByRole("banner"),e.locator("header").first(),e.getByRole("navigation").first(),e.locator("#header")];for(const e of t)try{if(await e.count()>0&&await e.first().isVisible())return e.first()}catch{}return null}async function za(e,t,a,n){const r=[],o=t.viewportSize(),i=o?.width??1280;try{const t=await e.getByRole(a).all();for(const e of t)try{if(!await e.isVisible())continue;const t=(await e.textContent()??"").trim();if(t.length>0&&t.length<=Pa){let o;try{const t=await e.boundingBox();t&&(o=(t.x+t.width/2)/i)}catch{}r.push({text:t,strategy:n,priority:1,source:`role:${a}`,xRatio:o})}}catch{}}catch{}return r}async function qa(e){const t=[];try{const a=Ba(await e.ariaSnapshot({timeout:3e3}));for(const{role:e,name:n}of a)n.length>0&&n.length<=Pa&&t.push({text:n,strategy:"aria-snapshot",priority:2,source:`aria:${e}:${n}`})}catch{}return t}async function La(e){const t=[];try{const a=await e.evaluate((e,t)=>{const a=[],n=document.createTreeWalker(e,NodeFilter.SHOW_TEXT);for(;n.nextNode();){const e=n.currentNode.textContent?.trim();e&&e.length>0&&e.length<=t&&a.push(e)}return a},Pa);for(const e of a)t.push({text:e,strategy:"leaf-text",priority:3,source:"leaf-text"})}catch{}return t}async function Fa(e){const t=[];try{const a=[Wt.username.primary,...Wt.username.fallbacks],n=await e.evaluate(e=>e.map(e=>{try{return{selector:e,text:document.querySelector(e)?.textContent?.trim()??""}}catch{return{selector:e,text:""}}}),a);for(const{selector:e,text:a}of n)a.length>0&&a.length<=Pa&&t.push({text:a,strategy:"css-fallback",priority:4,source:e})}catch{}return t}async function ja(e){const t=await Ta(e),a=t?(await Promise.all([za(t,e,"link","role-link"),za(t,e,"button","role-button"),qa(t)])).flat():[],n=Na(a);if(n.found){const e=n.username;if(new Set(a.filter(t=>t.text.trim()===e).map(e=>e.strategy)).size>=2)return a}const r=(await Promise.all([t?La(t):Promise.resolve([]),Fa(e)])).flat();return[...a,...r]}async function Oa(e,t=ja){const a=Na(await t(e));if(!a.found)throw new Error("未找到用户名,请确认当前页面已登录招聘者账号。");return{platform:"zhipin",username:a.username,strategy:a.strategy,source:a.source}}function Ua(e,t){return void 0!==t.accountId?e.accountId===t.accountId:e.username===t.username}import{z as Da}from"zod";var Va="reply-authority-service",Wa="browser-use-agent/zhipin_send_reply",Ha="zhipin",Za=60,Ga=Da.object({platform:Da.literal(Ha),username:Da.string().min(1),accountId:Da.string().min(1).optional()}),Ya=Da.object({v:Da.literal(2),iss:Da.literal(Va),kid:Da.string().min(1),jti:Da.string().min(1),iat:Da.number().int(),exp:Da.number().int(),aud:Da.literal(Wa),platform:Da.literal(Ha),tenantId:Da.string().min(1),conversationId:Da.string().min(1),candidateId:Da.string().min(1),reply:Da.string(),policyVersion:Da.string().min(1),recruiterBinding:Ga}),Ka=Da.object({kid:Da.string().min(1),algorithm:Da.literal("Ed25519"),publicKey:Da.string().min(1),validFrom:Da.string().min(1),validUntil:Da.string().optional()}),Ja=Da.object({keys:Da.array(Ka)}),Xa=new Map;function Qa(e){for(const[t,a]of Xa)a<e-Za&&Xa.delete(t)}function en(e,t=Math.floor(Date.now()/1e3)){return Qa(t),Xa.has(e)}function tn(e,t,a=Math.floor(Date.now()/1e3)){Qa(a),Xa.set(e,t)}import{createPublicKey as an,verify as nn}from"node:crypto";var rn=new Map,on=null;function sn(){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 cn(e){return rn=new Map(e.map(e=>[e.kid,e]))}async function dn(){if(on)return on;on=(async()=>{const e=await fetch(sn()),t=await e.json();if(!e.ok)throw new Error(`Reply Authority 公钥拉取失败 (${e.status})`);return cn(Ja.parse(t).keys)})();try{return await on}finally{on=null}}async function ln(){await dn()}async function un(e){const t=rn.get(e);if(t)return t;const a=(await dn()).get(e);if(!a)throw new Error(`Unknown key ID: ${e}`);return a}function mn(e){if("object"==typeof e&&null!==e&&"v"in e&&"number"==typeof e.v&&2!==e.v)throw new Error("unexpected envelope version")}function gn(e){const t=e.split("."),a=t[0],n=t[1];if(2!==t.length||void 0===a||void 0===n||0===a.length||0===n.length)throw new Error("Invalid signed envelope format");let r,o="";try{o=Buffer.from(a,"base64url").toString("utf-8")}catch{throw new Error("Invalid signed envelope format")}try{r=JSON.parse(o)}catch{throw new Error("Envelope payload schema validation failed")}mn(r);const i=Ya.safeParse(r);if(!i.success)throw new Error("Envelope payload schema validation failed");return{payload:i.data,payloadJson:o,signatureBase64:n}}function fn(e,t){if(e.exp<=e.iat)throw new Error("Envelope expiry must be after issue time");if(e.exp<t-Za)throw new Error("Envelope expired");if(e.iat>t+Za)throw new Error("Envelope issued in the future")}async function pn(e,t=Math.floor(Date.now()/1e3)){const a=gn(e),n=await un(a.payload.kid),r=an({key:Buffer.from(n.publicKey,"base64url"),format:"der",type:"spki"});if(!nn(null,Buffer.from(a.payloadJson,"utf-8"),r,Buffer.from(a.signatureBase64,"base64url")))throw new Error("Signature verification failed");return fn(a.payload,t),a.payload}var hn=Ma.object({success:Ma.boolean(),sentMessage:Ma.string(),error:Ma.string().optional()});function wn(e,t){const a=e.trim().toLocaleLowerCase("zh-CN"),n=t.trim().toLocaleLowerCase("zh-CN");return a.length>0&&n.length>0&&(a===n||a.includes(n)||n.includes(a))}var yn=Ra({name:"zhipin_send_reply",description:"发送消息。只接受由 Reply Authority Service 签发的 signedEnvelope;可指定 candidateName 自动打开对应聊天后发送,或不传则发送到当前选中的聊天窗口。",input:Ma.object({signedEnvelope:Ma.string().describe("Reply Authority Service 返回的紧凑签名信封"),candidateName:Ma.string().optional().describe("候选人姓名。若用户说“回复鲁倩”,这里应提取为“鲁倩”"),index:Ma.number().optional().describe("候选人在列表中的索引(可选)")}),output:hn,execute:async(e,t)=>{let a="";if(!b()){const e="Reply Authority 公钥尚未成功预加载,当前无法发送签名回复。请检查启动日志、`REPLY_AUTHORITY_KEYS_URL` 配置,以及 `browser_status.replyAuthorityKeysLoaded`。";return t.logger.error(e),{success:!1,sentMessage:a,error:e}}const n=h(),r=await n.getPage("zhipin");let o=r;try{const i=await pn(e.signedEnvelope);if(a=i.reply,en(i.jti))return{success:!1,sentMessage:a,error:"token 已消费,禁止重放"};const s=await Tt(n,r,{conversationId:i.conversationId,candidateName:e.candidateName,index:e.index});if(s&&!s.found)return{success:!1,sentMessage:a,error:s.error};if(!s){if(!await $t(n,r))return{success:!1,sentMessage:a,error:"消息列表未加载"}}o=await n.getPage("zhipin");const c=await mt(o),d=await ut(o);if(!d)return{success:!1,sentMessage:a,error:"未能提取当前聊天的 conversationId/candidateId"};if(c&&d.candidateName.length>0&&!wn(d.candidateName,c.candidateName))return{success:!1,sentMessage:a,error:`左侧选中会话与右侧聊天面板不一致: ${d.candidateName} / ${c.candidateName}`};if(d.conversationId!==i.conversationId||d.candidateId!==i.candidateId)return{success:!1,sentMessage:a,error:"发送目标与签名不匹配"};const l=await Oa(o);if(!Ua(l,i.recruiterBinding))return{success:!1,sentMessage:a,error:`recruiter 绑定不匹配:当前账号 ${l.username} 与签发时 ${i.recruiterBinding.username} 不一致`};t.logger.info(`Sending message (${a.length} chars) to ${d.candidateName||d.candidateId}`);const u="#boss-chat-editor-input, textarea.chat-input, .chat-input";await o.waitForSelector(u,{timeout:5e3});const m=o.locator(u).first();await Z(o,m);await o.evaluate(e=>{const t=document.querySelector(e);return"true"===t?.getAttribute("contenteditable")},u)?(await m.focus(),await o.evaluate(e=>{const t=document.querySelector(e.sel);t&&(t.innerHTML=e.msg.split("\n").map(e=>`<p>${e}</p>`).join(""))},{sel:u,msg:a}),await m.dispatchEvent("input",{bubbles:!0})):await o.fill(u,a),await rt(o,200,500);const g=await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")});const e=[".submit-content .submit.active",".submit-content .submit",".submit-content",".btn-send"];for(const t of e){const e=document.querySelector(t);if(e&&e.offsetWidth>0)return{found:!0,selector:t}}const t=Array.from(document.querySelectorAll("span"));for(const e of t)if("发送"===e.textContent?.trim()&&e.offsetWidth>0)return e.setAttribute("data-roll-send-btn","true"),{found:!0,selector:'[data-roll-send-btn="true"]'};return{found:!1}});if(!g.found)return{success:!1,sentMessage:a,error:"未找到发送按钮"};const f=o.locator(g.selector).first();return await f.scrollIntoViewIfNeeded(),await Z(o,f),await f.hover(),await ot(o),await G(o,f),await f.click(),await rt(o,500,1200),tn(i.jti,i.exp),t.logger.info("Message sent successfully"),{success:!0,sentMessage:a}}catch(e){return{success:!1,sentMessage:a,error:e instanceof Error?e.message:String(e)}}finally{await o.evaluate(()=>{document.querySelectorAll("[data-roll-send-btn]").forEach(e=>{e.removeAttribute("data-roll-send-btn")})}).catch(()=>{})}}});import{defineTool as bn}from"@roll-agent/sdk";import{z as vn}from"zod";var xn=vn.object({success:vn.boolean(),exchanged:vn.boolean(),wechatNumber:vn.string().optional(),error:vn.string().optional()}),Sn=bn({name:"zhipin_exchange_wechat",description:'换微信。可指定 candidateName 自动打开对应聊天后执行,或不传则在当前窗口执行;例如"和鲁倩换微信"应提取 candidateName=鲁倩。',input:vn.object({conversationId:vn.string().optional().describe("会话 ID。若已从消息列表拿到,优先传这个"),candidateName:vn.string().optional().describe('候选人姓名。若用户说"和鲁倩换微信",这里应提取为"鲁倩"'),index:vn.number().optional().describe("候选人在列表中的索引(可选)")}),output:xn,execute:async(e,t)=>{const a=h(),n=await a.getPage("zhipin"),r=await Tt(a,n,{conversationId:e.conversationId,candidateName:e.candidateName,index:e.index});if(r&&!r.found)return{success:!1,exchanged:!1,error:r.error};t.logger.info("Starting WeChat exchange"+(r?` with ${r.name}`:""));const o=await a.getPage("zhipin");try{if(!(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=[".operate-exchange-left .operate-btn","span.operate-btn"];for(const a of t){const t=Array.from(document.querySelectorAll(a));for(const a of t){const t=a.textContent?.trim()??"";if(t.includes("换微信")&&e(a))return a.setAttribute("data-roll-wechat-btn","true"),{found:!0,text:t}}}const a=Array.from(document.querySelectorAll("span"));for(const t of a){const a=t.textContent?.trim()??"";if(a.includes("换微信")&&e(t))return t.setAttribute("data-roll-wechat-btn","true"),{found:!0,text:a}}return{found:!1}})).found)return{success:!1,exchanged:!1,error:"未找到「换微信」按钮"};await rt(o,200,400),await Y(o,'[data-roll-wechat-btn="true"]'),await K(o,'[data-roll-wechat-btn="true"]'),await o.click('[data-roll-wechat-btn="true"]'),await o.evaluate(()=>{document.querySelector("[data-roll-wechat-btn]")?.removeAttribute("data-roll-wechat-btn")}),await rt(o,400,800);let e=!1;for(let t=0;t<8;t++){if(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=document.querySelector(".exchange-tooltip");if(t&&e(t))return!0;const a=document.querySelectorAll("div, section, aside");for(const t of Array.from(a)){if((t.textContent??"").includes("交换微信")&&t.querySelector(".boss-btn-primary, .boss-btn")&&e(t))return!0}return!1})){e=!0;break}await rt(o,400,800)}if(!e)return{success:!1,exchanged:!1,error:"确认对话框未弹出"};await ot(o);if(!(await o.evaluate(()=>{const e=e=>{const t=e.getBoundingClientRect();return t.width>0&&t.height>0},t=document.querySelector(".exchange-tooltip");if(t){const a=[".btn-box .boss-btn-primary.boss-btn",".btn-box span.boss-btn-primary","span.boss-btn-primary",".boss-btn-primary"];for(const n of a){const a=t.querySelector(n);if(a&&e(a))return a.setAttribute("data-roll-confirm-btn","true"),{found:!0,text:a.textContent?.trim()??""}}}const a=document.querySelectorAll("div, section, aside");for(const t of Array.from(a)){if(!(t.textContent??"").includes("交换微信"))continue;const a=t.querySelectorAll("span.boss-btn-primary, button.boss-btn-primary, span.boss-btn, button.boss-btn");for(const t of Array.from(a)){const a=t.textContent?.trim()??"";if("确定"===a&&e(t))return t.setAttribute("data-roll-confirm-btn","true"),{found:!0,text:a}}}return{found:!1}})).found)return{success:!1,exchanged:!1,error:"未找到确认按钮"};await rt(o,200,400),await Y(o,'[data-roll-confirm-btn="true"]'),await K(o,'[data-roll-confirm-btn="true"]'),await o.click('[data-roll-confirm-btn="true"]'),await o.evaluate(()=>{document.querySelector("[data-roll-confirm-btn]")?.removeAttribute("data-roll-confirm-btn")}),await rt(o,1500,2500);const a=await o.evaluate(()=>{const e=[".message-card-top-wrap",'[class*="d-top-text"]',".message-card-top-title"];for(const t of e){const e=Array.from(document.querySelectorAll(t));for(let t=e.length-1;t>=0;t--){const a=e[t]?.textContent??"",n=a.match(/\b(\d{8,15})\b/);if(n)return n[1];const r=a.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);if(r)return r[1];const o=a.match(/\b([a-zA-Z][a-zA-Z0-9_-]{5,19})\b/);if(o&&!["微信","WeChat"].includes(o[1]))return o[1]}}const t=Array.from(document.querySelectorAll(".message-item"));for(let e=t.length-1;e>=0;e--){const a=t[e]?.querySelector('.message-card-top-wrap, [class*="d-top-text"]');if(a){const e=(a.textContent??"").match(/\b(\d{8,15})\b/);if(e)return e[1]}}return null});return t.logger.info("WeChat exchanged"+(a?`, number: ${a}`:"")),{success:!0,exchanged:!0,...null!==a?{wechatNumber:a}:{}}}catch(e){return{success:!1,exchanged:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as In}from"@roll-agent/sdk";import{z as kn}from"zod";var Cn,An=kn.object({success:kn.boolean(),username:kn.string(),usedSelector:kn.string().optional(),usedStrategy:kn.string().optional(),source:kn.string().optional(),error:kn.string().optional()});function Rn(){return{getContextManager:h,findHeaderScope:Ta,getCurrentZhipinRecruiterIdentity:Oa,createVisualActivitySession:e=>new Lt(e),...Cn}}var Mn=In({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名",input:kn.object({}),output:An,execute:async(e,t)=>{t.logger.info("Getting zhipin username");const a=Rn();let n;try{const e=a.getContextManager(),r=await e.getPage("zhipin");n=a.createVisualActivitySession(r),await r.bringToFront().catch(()=>{}),await n.begin("正在识别登录账号");const o=await a.findHeaderScope(r);o&&await n.highlightLocator(o,{label:"正在识别登录账号",padding:10});const i=await a.getCurrentZhipinRecruiterIdentity(r);return await n.succeed(`已识别账号:${i.username}`),t.logger.info(`Username: ${i.username} (strategy: ${i.strategy}, source: ${i.source})`),{success:!0,username:i.username,usedSelector:"css-fallback"===i.strategy?i.source:void 0,usedStrategy:i.strategy,source:i.source}}catch(e){return await(n?.fail("获取用户名失败")),{success:!1,username:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}}});import{defineTool as Pn}from"@roll-agent/sdk";import{z as En}from"zod";var $n=".candidate-card-wrap",Bn="[data-geek], .geek-item",_n=`${$n}, ${Bn}`;function Nn(e){return e.evaluate(e=>document.querySelectorAll(e.primarySelector).length>0?e.primarySelector:e.fallbackSelector,{primarySelector:$n,fallbackSelector:Bn})}function Tn(e){return e.frame("recommendFrame")??e.frames().find(e=>e.url().includes("recommend"))??e}async function zn(e,t=1e4){try{return await e.waitForSelector(_n,{timeout:t}),!0}catch{return!1}}async function qn(e,t){const a=await Nn(e),n=e.locator(a);if(await n.count()<=t)return{found:!1,cardSelector:a,candidateId:"",name:"",hasGreetButton:!1,error:"索引超出范围"};const r=n.nth(t),o=await r.evaluate(e=>{const t=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",a=e.querySelector(".name")?.textContent?.trim()??"",n=e.querySelector("button.btn.btn-greet");return{candidateId:t,name:a,hasGreetButton:null!==n&&n.offsetWidth>0}});return{found:!0,cardSelector:a,candidateId:o.candidateId,name:o.name,hasGreetButton:o.hasGreetButton}}var Ln,Fn=En.object({index:En.number(),candidateId:En.string(),name:En.string(),age:En.string(),experience:En.string(),education:En.string(),workStatus:En.string(),company:En.string(),currentPosition:En.string(),expectedLocation:En.string(),expectedPosition:En.string(),expectedSalary:En.string(),tags:En.array(En.string()),buttonText:En.string()}),jn=En.object({success:En.boolean(),candidates:En.array(Fn),total:En.number(),error:En.string().optional()});function On(){return{getContextManager:h,getRecommendTarget:Tn,waitForRecommendList:zn,createVisualActivitySession:e=>new Lt(e),...Ln}}var Un=Pn({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:En.object({maxResults:En.number().optional().describe("最多返回条数")}),output:jn,execute:async(e,t)=>{t.logger.info("Getting candidate list from recommend page");const a=On(),n=a.getContextManager(),r=await n.getPage("zhipin");let o=a.getRecommendTarget(r);const i=a.createVisualActivitySession(o);await i.begin("正在打开推荐列表");const s=await a.waitForRecommendList(o);if(o=a.getRecommendTarget(r),await i.retarget(o),!s)return await i.fail("推荐列表未加载"),{success:!1,candidates:[],total:0,error:"推荐列表未加载"};try{const a="正在读取推荐列表";await i.begin(a),await i.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:a,padding:8});const n=await o.evaluate(e=>{let t=Array.from(document.querySelectorAll(".candidate-card-wrap"));0===t.length&&(t=Array.from(document.querySelectorAll("[data-geek], .geek-item")));const a=e??t.length,n=[];return t.forEach((e,t)=>{if(t>=a)return;const r=e.getAttribute("data-geek")??e.querySelector("[data-geek]")?.getAttribute("data-geek")??"",o=e.querySelector(".name")?.textContent?.trim()??"";let i="",s="",c="",d="";const l=e.querySelector(".base-info.join-text-wrap, .base-info");if(l){const e=[];if(l.querySelectorAll(":scope > *").forEach(t=>{const a=t.textContent?.trim();a&&e.push(a)}),e.length<=1&&(e.length=0,l.childNodes.forEach(t=>{if(t.nodeType===Node.TEXT_NODE){const a=t.textContent?.trim();a&&e.push(a)}})),e.length<=1){const t=l.textContent?.trim()??"";e.length=0,t.split(/[丨·|]/).forEach(t=>{const a=t.trim();a&&e.push(a)})}for(const t of e)!i&&t.includes("岁")?i=t:!s&&(t.includes("年")||t.includes("应届")||t.includes("在校"))?s=t:!c&&/(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(t)?c=t:!d&&/(在职|离职|在校)/.test(t)&&(d=t)}const u=e.querySelector(".timeline-wrap.work-exps .content.join-text-wrap")??e.querySelector(".timeline-wrap.work-exps .content"),m=(u?.textContent?.trim()??"").split("·").map(e=>e.trim()),g=m[0]??"",f=m[1]??"";let p="",h="";const w=e.querySelector(".row-flex:not(.geek-desc)");if(w){const e=w.querySelector(".label"),t=w.querySelector(".content"),a=e?.textContent??"";if((a.includes("期望")||a.includes("最近关注"))&&t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());p=e[0]??"",h=e[1]??""}}if(!p){const t=e.querySelector(".timeline-wrap.expect .content.join-text-wrap")??e.querySelector(".timeline-wrap.expect .content");if(t){const e=(t.textContent?.trim()??"").split("·").map(e=>e.trim());p=e[0]??"",h=e[1]??""}}const y=e.querySelector(".salary-wrap")?.textContent?.trim()??"",b=[];e.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span").forEach(e=>{const t=e.textContent?.trim();t&&b.push(t)});const v=e.querySelector("button.btn.btn-greet")?.textContent?.trim()??"";n.push({index:t,candidateId:r,name:o,age:i,experience:s,education:c,workStatus:d,company:g,currentPosition:f,expectedLocation:p,expectedPosition:h,expectedSalary:y,tags:b,buttonText:v})}),n},e.maxResults);return await i.succeed(`已读取 ${n.length} 位候选人`),t.logger.info(`Found ${n.length} candidates in recommend list`),{success:!0,candidates:n,total:n.length}}catch(e){throw await i.fail("读取推荐列表失败"),e}}});import{defineTool as Dn}from"@roll-agent/sdk";import{BrowserPageInfoSchema as Vn}from"@roll-agent/browser";import{z as Wn}from"zod";var Hn,Zn=Wn.object({success:Wn.boolean(),alreadyOnRecommend:Wn.boolean(),usedSidebarClick:Wn.boolean(),recommendReady:Wn.boolean(),page:Vn.optional(),error:Wn.string().optional()});function Gn(){return{getContextManager:h,getRecommendTarget:Tn,findZhipinSidebarSectionLink:Kt,isZhipinRecommendSurfaceOpen:Jt,waitForZhipinRecommendSurface:Qt,moveVisualCursorToLocator:Z,showVisualClickOnLocator:G,randomDelay:rt,toAttachedPageInfo:Ie,createVisualActivitySession:e=>new Lt(e),...Hn}}async function Yn(e,t,a){return await t.toAttachedPageInfo(e,a)}async function Kn(e,t,a,n,r,o){return await a.fail(r),{success:!1,...o,page:await Yn(e,t,n),error:r}}async function Jn(e,t,a,n){await a.scrollIntoViewIfNeeded(),await t.moveVisualCursorToLocator(e,a,{durationMs:110,settleMs:30}),await a.hover(),await t.randomDelay(e,100,180),await t.showVisualClickOnLocator(e,a,{pulseDurationMs:180}),await a.click(),n.info("Clicked Boss sidebar nav: 推荐牛人")}var Xn=Dn({name:"zhipin_open_recommend_page",description:"通过点击 Boss 左侧导航切换到「推荐牛人」页,避免让编排器依赖站内 URL 猜测。",input:Wn.object({}),output:Zn,execute:async(e,t)=>{const a=Gn(),n=a.getContextManager();t.logger.info("Opening Boss recommend page via sidebar navigation");const r=await n.getPage("zhipin");await r.bringToFront().catch(()=>{});const o=a.createVisualActivitySession(r),i="正在切换到推荐牛人页";if(await o.begin(i),await o.highlightSelector(Wt.nav.sidebar,{label:i,padding:10}),a.isZhipinRecommendSurfaceOpen(r))return await o.retarget(a.getRecommendTarget(r)),await o.succeed("已在推荐牛人页"),{success:!0,alreadyOnRecommend:!0,usedSidebarClick:!1,recommendReady:!0,page:await Yn(n,a,r)};const s=await a.findZhipinSidebarSectionLink(r,"recommend");if(!s)return await Kn(n,a,o,r,"未找到推荐牛人导航",{alreadyOnRecommend:!1,usedSidebarClick:!1,recommendReady:!1});try{await Jn(r,a,s,t.logger)}catch(e){return await Kn(n,a,o,r,e instanceof Error?e.message:"点击推荐牛人导航失败",{alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1})}const c=await a.waitForZhipinRecommendSurface(r);return await o.retarget(a.getRecommendTarget(r)),c?(await o.succeed("已切换到推荐牛人页"),{success:!0,alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!0,page:await Yn(n,a,r)}):await Kn(n,a,o,r,"推荐牛人页未就绪",{alreadyOnRecommend:!1,usedSidebarClick:!0,recommendReady:!1})}});import{defineTool as Qn}from"@roll-agent/sdk";import{z as er}from"zod";var tr,ar=er.object({index:er.number(),candidateName:er.string(),candidateId:er.string(),success:er.boolean(),error:er.string().optional()}),nr=er.object({success:er.boolean(),results:er.array(ar),summary:er.object({total:er.number(),succeeded:er.number(),failed:er.number()})});function rr(){return{getContextManager:h,getRecommendTarget:Tn,waitForRecommendList:zn,inspectRecommendCard:qn,moveVisualCursorToLocator:Z,showVisualClickOnLocator:G,humanDelay:ot,shouldAddRandomBehavior:ct,performRandomScroll:it,createVisualActivitySession:e=>new Lt(e),...tr}}var or=Qn({name:"zhipin_say_hello",description:"在推荐列表页对候选人点击「打招呼」按钮(支持批量)",input:er.object({indices:er.array(er.number()).describe("要打招呼的候选人索引列表")}),output:nr,execute:async(e,t)=>{t.logger.info(`Saying hello to ${e.indices.length} candidates`);const a=rr(),n=a.getContextManager(),r=await n.getPage("zhipin");let o=a.getRecommendTarget(r);const i=a.createVisualActivitySession(o),s=e.indices.length>1?"正在批量打招呼":"正在打招呼";await i.begin("正在打开推荐列表");const c=await a.waitForRecommendList(o);if(o=a.getRecommendTarget(r),await i.retarget(o),!c){await i.fail("推荐列表未加载");const t=e.indices.map(e=>({index:e,candidateName:"",candidateId:"",success:!1,error:"推荐列表未加载"}));return{success:!1,results:t,summary:{total:t.length,succeeded:0,failed:t.length}}}await i.begin(s),await i.highlightSelector(".candidate-card-wrap, [data-geek], .geek-item",{label:s,padding:8});const d=[];for(const t of e.indices)try{const e=await a.inspectRecommendCard(o,t);if(e.found)if(e.hasGreetButton){const n=o.locator(e.cardSelector).nth(t),s=n.locator("button.btn.btn-greet").first();await i.highlightLocator(n,{label:`正在定位第 ${t+1} 位候选人`,padding:10}),await s.scrollIntoViewIfNeeded(),await a.moveVisualCursorToLocator(r,s,{durationMs:90,settleMs:20,target:o}),await s.hover(),await a.showVisualClickOnLocator(r,s,{pulseDurationMs:160,target:o}),await s.click(),d.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!0})}else d.push({index:t,candidateName:e.name,candidateId:e.candidateId,success:!1,error:"未找到打招呼按钮"});else d.push({index:t,candidateName:"",candidateId:"",success:!1,...void 0!==e.error?{error:e.error}:{}});await a.humanDelay(r),a.shouldAddRandomBehavior(.3)&&await a.performRandomScroll(r)}catch(e){d.push({index:t,candidateName:"",candidateId:"",success:!1,error:e instanceof Error?e.message:String(e)})}const l={total:d.length,succeeded:d.filter(e=>e.success).length,failed:d.filter(e=>!e.success).length};return 0===l.failed?await i.succeed(`已完成 ${l.succeeded}/${l.total} 位候选人`):await i.fail(`已完成 ${l.succeeded}/${l.total} 位候选人`),t.logger.info(`Say hello: ${l.succeeded}/${l.total} succeeded`),{success:0===l.failed,results:d,summary:l}}});import{defineTool as ir}from"@roll-agent/sdk";import{z as sr}from"zod";var cr=sr.object({success:sr.boolean(),candidateName:sr.string(),candidateId:sr.string(),error:sr.string().optional()}),dr=ir({name:"zhipin_open_resume",description:"在推荐列表页点击候选人卡片打开简历详情弹窗",input:sr.object({index:sr.number().describe("候选人在列表中的索引")}),output:cr,execute:async(e,t)=>{t.logger.info(`Opening resume for candidate at index ${e.index}`);const a=h(),n=await a.getPage("zhipin"),r=Tn(n);if(!await zn(r))return{success:!1,candidateName:"",candidateId:"",error:"推荐列表未加载"};const o=await qn(r,e.index);if(!o.found)return{success:!1,candidateName:"",candidateId:"",error:o.error??`索引 ${e.index} 超出范围`};const i=r.locator(o.cardSelector).nth(e.index),s=await i.locator("[data-geek], .card-inner, .geek-item").count()>0?i.locator("[data-geek], .card-inner, .geek-item").first():i;return await s.scrollIntoViewIfNeeded(),await Z(n,s,{target:r}),await s.hover(),await rt(n,200,400),await G(n,s,{target:r}),await s.click(),await rt(n,1e3,2e3),t.logger.info(`Opened resume for ${o.name}`),{success:!0,candidateName:o.name,candidateId:o.candidateId}}});import{defineTool as lr}from"@roll-agent/sdk";import{z as ur}from"zod";var mr=ur.object({x:ur.number(),y:ur.number(),width:ur.number(),height:ur.number()}),gr=ur.object({success:ur.boolean(),screenshotArea:mr.optional(),canvasInfo:ur.object({width:ur.number(),height:ur.number()}).optional(),error:ur.string().optional()}),fr=lr({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:ur.object({}),output:gr,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const a=h(),n=await a.getPage("zhipin");try{const e=n.frame("recommendFrame")??n.frames().find(e=>e.url().includes("recommend"));if(!e)return{success:!1,error:"未找到推荐页 iframe"};const a=await e.$('iframe[src*="c-resume"]');if(!a)return{success:!1,error:"未找到简历 iframe"};const r=await a.contentFrame();if(!r)return{success:!1,error:"无法访问简历 iframe 内容"};try{await r.waitForSelector("canvas#resume, div#resume canvas",{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const o=await r.evaluate(()=>{const e=document.querySelector("canvas#resume, div#resume canvas");if(!e)return null;const t=e.getBoundingClientRect();return{width:e.width,height:e.height,clientWidth:t.width,clientHeight:t.height,x:t.x,y:t.y}});if(!o)return{success:!1,error:"无法获取 canvas 信息"};const i=await n.evaluate(()=>{const e=document.querySelector("#recommendFrame");if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),s=await e.evaluate(()=>{const e=document.querySelector('iframe[src*="c-resume"]');if(!e)return null;const t=e.getBoundingClientRect();return{x:t.x,y:t.y}}),c=(i?.x??0)+(s?.x??0),d=(i?.y??0)+(s?.y??0);return t.logger.info(`Canvas located at (${c+o.x}, ${d+o.y})`),{success:!0,screenshotArea:{x:Math.round(c+o.x),y:Math.round(d+o.y),width:Math.round(o.clientWidth),height:Math.round(o.clientHeight)},canvasInfo:{width:o.width,height:o.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});import{defineTool as pr}from"@roll-agent/sdk";import{z as hr}from"zod";var wr=hr.object({success:hr.boolean(),closed:hr.boolean(),error:hr.string().optional()}),yr=[".recommendV2 .boss-popup__close",".dialog-lib-resume .boss-popup__close",".boss-dialog .boss-popup__close",".boss-popup__close",".close-btn",".dialog-close"],br=[".boss-popup__close",".close-btn",".dialog-close",".modal-close"],vr=pr({name:"zhipin_close_resume",description:"关闭简历详情弹窗",input:hr.object({}),output:wr,execute:async(e,t)=>{t.logger.info("Closing resume detail modal");const a=h(),n=await a.getPage("zhipin"),r=n.frame("recommendFrame")??n.frames().find(e=>e.url().includes("recommend"));if(!await(async()=>{if(r)for(const e of yr){const t=r.locator(e).first();if(await t.isVisible())return await Z(n,t,{target:r}),await G(n,t,{target:r}),await t.click(),!0}for(const e of br){const t=n.locator(e).first();if(await t.isVisible())return await Z(n,t),await G(n,t),await t.click(),!0}return!1})())return{success:!1,closed:!1,error:"未找到关闭按钮"};let o=!1;for(let e=0;e<5;e++){await n.waitForTimeout(300);const e=r?await r.$(".boss-popup__wrapper, .dialog-lib-resume, .boss-dialog"):await n.$(".boss-popup__wrapper");if(!e||!await e.isVisible()){o=!0;break}}return t.logger.info(o?"Resume modal closed and verified":"Resume modal close unverified"),{success:!0,closed:!0}}});import{defineTool as xr}from"@roll-agent/sdk";import{z as Sr}from"zod";import{waitForSelector as Ir}from"@roll-agent/browser";var kr={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 Cr,waitForSelector as Ar}from"@roll-agent/browser";var Rr="https://www.yupao.com",Mr=`${Rr}/chat`,Pr=`${Rr}/login`;async function Er(e){e.url().includes("/chat")||await Cr(e,Mr),await Ar(e,kr.messageList.container,{timeout:15e3})}async function $r(e,t){const a=`${Rr}/chat?id=${encodeURIComponent(t)}`;await Cr(e,a),await Ar(e,kr.chat.input,{timeout:15e3})}async function Br(e,t){await Er(e),await Ir(e,kr.messageList.item,{timeout:1e4});const a=kr.messageList;return await e.$$eval(a.item,(e,t)=>{const a=[],n=t.maxItems?e.slice(0,t.maxItems):e;for(const e of n){const n=e.querySelector(t.sel.candidateName),r=e.querySelector(t.sel.lastMessage),o=e.querySelector(t.sel.unreadBadge),i=e.querySelector(t.sel.timestamp),s=e.getAttribute("data-id")??e.getAttribute("data-conversation-id")??e.querySelector("a")?.getAttribute("href")?.match(/id=([^&]+)/)?.[1]??"";a.push({conversationId:s,candidateName:n?.textContent?.trim()??"",lastMessage:r?.textContent?.trim()??"",unreadCount:parseInt(o?.textContent?.trim()??"0",10)||0,timestamp:i?.textContent?.trim()??""})}return a},{sel:a,maxItems:t})}var _r=Sr.object({limit:Sr.number().optional().describe("最多返回的消息条数")}),Nr=Sr.object({conversationId:Sr.string(),candidateName:Sr.string(),lastMessage:Sr.string(),unreadCount:Sr.number(),timestamp:Sr.string()}),Tr=Sr.object({messages:Sr.array(Nr),total:Sr.number()}),zr=xr({name:"yupao_read_messages",description:"读取鱼泡未读消息列表",input:_r,output:Tr,execute:async(e,t)=>{t.logger.info(`Reading yupao messages (limit: ${e.limit??"all"})`);const a=h(),n=await a.getPage("yupao"),r=await Br(n,e.limit);return t.logger.info(`Found ${r.length} messages`),{messages:r.map(e=>({...e})),total:r.length}}});import{defineTool as qr}from"@roll-agent/sdk";import{z as Lr}from"zod";import{waitForSelector as Fr,typeText as jr,clickElement as Or}from"@roll-agent/browser";async function Ur(e,t,a){try{return await $r(e,t),await jr(e,kr.chat.input,a),await Or(e,kr.chat.sendButton),await Fr(e,kr.chat.messageItem,{timeout:5e3}),{success:!0}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}var Dr=Lr.object({conversationId:Lr.string().describe("对话 ID"),message:Lr.string().describe("要发送的回复消息")}),Vr=Lr.object({success:Lr.boolean(),conversationId:Lr.string(),sentMessage:Lr.string(),error:Lr.string().optional()}),Wr=qr({name:"yupao_send_reply",description:"向鱼泡指定对话发送回复消息",input:Dr,output:Vr,execute:async(e,t)=>{const{conversationId:a,message:n}=e;t.logger.info(`Sending reply to yupao conversation ${a}`);const r=h(),o=await r.getPage("yupao"),i=await Ur(o,a,n);return i.success?t.logger.info("Reply sent successfully"):t.logger.error(`Failed to send reply: ${i.error}`),{success:i.success,conversationId:a,sentMessage:n,error:i.error}}}),Hr=t("browser-use-agent");function Zr(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 Gr(e,t){if(void 0===e)return;const a=Number.parseInt(e,10);if(!Number.isInteger(a))throw new Error(`${t} must be an integer, received "${e}".`);return a}function Yr(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 Kr(){return a.parse({mode:process.env.BROWSER_MODE,headless:Zr(process.env.BROWSER_HEADLESS),cdpUrl:process.env.BROWSER_CDP_URL,cdpHost:process.env.BROWSER_CDP_HOST,cdpPort:Gr(process.env.BROWSER_CDP_PORT,"BROWSER_CDP_PORT"),channel:process.env.BROWSER_CHANNEL,executablePath:process.env.BROWSER_EXECUTABLE_PATH,userDataDir:process.env.BROWSER_USER_DATA_DIR,args:Yr(process.env.BROWSER_ARGS_JSON),sessionsDir:process.env.BROWSER_SESSIONS_DIR})}var Jr=e({name:"browser-use-agent",tools:[me,Ae,Oe,Ge,tt,Ot,sa,ua,Aa,yn,Sn,Mn,Xn,Un,or,dr,fr,vr,zr,Wr,S]},{onShutdown:v});async function Xr(){await f(Kr());try{await ln(),y(!0)}catch(e){y(!1),Hr.error(`Failed to preload Reply Authority keys during startup; browser_status.replyAuthorityKeysLoaded=false. ${e instanceof Error?e.stack??e.message:String(e)}`)}await Jr.listen({transport:{type:"http",port:parseInt(process.env.BROWSER_AGENT_PORT??"3100",10),host:process.env.BROWSER_AGENT_HOST??"127.0.0.1"}})}Xr().catch(async e=>{Hr.error(`Fatal error: ${e instanceof Error?e.stack??e.message:String(e)}`),await v().catch(()=>{}),process.exit(1)});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { BrowserContextManager, Page } from "@roll-agent/browser";
|
|
2
2
|
export interface ChatTarget {
|
|
3
|
+
readonly conversationId: string | undefined;
|
|
3
4
|
readonly candidateName: string | undefined;
|
|
4
5
|
readonly index: number | undefined;
|
|
5
6
|
}
|
|
6
7
|
export interface ChatListItem {
|
|
8
|
+
readonly conversationId: string;
|
|
9
|
+
readonly candidateId: string;
|
|
7
10
|
readonly name: string;
|
|
8
11
|
readonly index: number;
|
|
12
|
+
readonly position: string;
|
|
9
13
|
readonly hasUnread: boolean;
|
|
10
14
|
readonly unreadCount: number;
|
|
11
15
|
readonly lastMessageTime: string;
|
|
@@ -4,4 +4,8 @@ export interface ZhipinChatTarget {
|
|
|
4
4
|
readonly candidateId: string;
|
|
5
5
|
readonly candidateName: string;
|
|
6
6
|
}
|
|
7
|
+
export interface ZhipinActiveChatPanel {
|
|
8
|
+
readonly candidateName: string;
|
|
9
|
+
}
|
|
7
10
|
export declare function getSelectedChatTarget(page: Page): Promise<ZhipinChatTarget | null>;
|
|
11
|
+
export declare function getActiveChatPanel(page: Page): Promise<ZhipinActiveChatPanel | null>;
|
|
@@ -87,6 +87,11 @@ export declare const ZHIPIN_SELECTORS: {
|
|
|
87
87
|
readonly primary: ".nav-item.nav-logout .user-name";
|
|
88
88
|
readonly fallbacks: readonly string[];
|
|
89
89
|
};
|
|
90
|
+
readonly nav: {
|
|
91
|
+
readonly sidebar: ".side-wrap.side-wrap-v2";
|
|
92
|
+
readonly chatLink: ".side-wrap.side-wrap-v2 a[href*=\"/web/chat/index\"]";
|
|
93
|
+
readonly recommendLink: ".side-wrap.side-wrap-v2 a[href*=\"/web/geek/recommend\"]";
|
|
94
|
+
};
|
|
90
95
|
readonly recommend: {
|
|
91
96
|
readonly iframe: "#recommendFrame";
|
|
92
97
|
readonly resumeIframe: "iframe[src*=\"c-resume\"]";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
declare const ZHIPIN_SIDEBAR_SECTION_LABELS: {
|
|
3
|
+
readonly chat: "沟通";
|
|
4
|
+
readonly recommend: "推荐牛人";
|
|
5
|
+
};
|
|
6
|
+
export type ZhipinSidebarSection = keyof typeof ZHIPIN_SIDEBAR_SECTION_LABELS;
|
|
7
|
+
type Locator = ReturnType<Page["locator"]>;
|
|
8
|
+
export declare function getZhipinSidebarSectionLabel(section: ZhipinSidebarSection): string;
|
|
9
|
+
export declare function findZhipinSidebarSectionLink(page: Page, section: ZhipinSidebarSection): Promise<Locator | null>;
|
|
10
|
+
export declare function isZhipinRecommendSurfaceOpen(page: Page): boolean;
|
|
11
|
+
export declare function isZhipinChatSurfaceOpen(page: Page): Promise<boolean>;
|
|
12
|
+
export declare function waitForZhipinRecommendSurface(page: Page, timeout?: number, pollInterval?: number): Promise<boolean>;
|
|
13
|
+
export declare function waitForZhipinChatSurface(page: Page, timeout?: number, pollInterval?: number): Promise<boolean>;
|
|
14
|
+
export {};
|
|
@@ -10,6 +10,8 @@ export declare const browserStatus: import("@roll-agent/sdk").ToolDefinition<{},
|
|
|
10
10
|
currentUrl?: string | undefined;
|
|
11
11
|
}[];
|
|
12
12
|
replyAuthorityKeysLoaded: boolean;
|
|
13
|
+
visualCursorEnabled: boolean;
|
|
14
|
+
visualActivityEnabled: boolean;
|
|
13
15
|
effectiveEnvSources: Record<string, {
|
|
14
16
|
present: boolean;
|
|
15
17
|
fingerprint?: string | undefined;
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { findTrackedPlatformPage } from "../pages/platform-page.ts";
|
|
2
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
3
|
+
import { detectPlatformFromUrl, matchesPlatformHost } from "../platforms.ts";
|
|
4
|
+
import { toAttachedPageInfo } from "../page-info.ts";
|
|
5
|
+
type NavigateActiveTabDeps = {
|
|
6
|
+
readonly getContextManager: typeof getContextManager;
|
|
7
|
+
readonly detectPlatformFromUrl: typeof detectPlatformFromUrl;
|
|
8
|
+
readonly matchesPlatformHost: typeof matchesPlatformHost;
|
|
9
|
+
readonly findTrackedPlatformPage: typeof findTrackedPlatformPage;
|
|
10
|
+
readonly toAttachedPageInfo: typeof toAttachedPageInfo;
|
|
11
|
+
};
|
|
12
|
+
export declare function setNavigateActiveTabDepsForTests(override: Partial<NavigateActiveTabDeps> | undefined): void;
|
|
1
13
|
export declare const navigateActiveTab: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
14
|
url: string;
|
|
3
15
|
}, {
|
|
@@ -11,3 +23,4 @@ export declare const navigateActiveTab: import("@roll-agent/sdk").ToolDefinition
|
|
|
11
23
|
isSelectedForPlatform: boolean;
|
|
12
24
|
};
|
|
13
25
|
}>;
|
|
26
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const zhipinExchangeWechat: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
|
-
|
|
2
|
+
conversationId?: string | undefined;
|
|
3
3
|
candidateName?: string | undefined;
|
|
4
|
+
index?: number | undefined;
|
|
4
5
|
}, {
|
|
5
6
|
success: boolean;
|
|
6
7
|
exchanged: boolean;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const zhipinGetCandidateInfo: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
|
-
|
|
2
|
+
conversationId?: string | undefined;
|
|
3
3
|
candidateName?: string | undefined;
|
|
4
|
+
index?: number | undefined;
|
|
4
5
|
maxMessages?: number | undefined;
|
|
5
6
|
}, {
|
|
6
7
|
success: boolean;
|
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
import { getRecommendTarget, waitForRecommendList } from "../pages/zhipin/recommend-list.ts";
|
|
2
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
3
|
+
import { VisualActivitySession } from "../visual-activity-session.ts";
|
|
4
|
+
type RecommendTarget = ReturnType<typeof getRecommendTarget>;
|
|
5
|
+
type VisualActivitySessionLike = Pick<VisualActivitySession, "begin" | "highlightSelector" | "succeed" | "fail" | "retarget">;
|
|
6
|
+
type ZhipinGetCandidateListDeps = {
|
|
7
|
+
readonly getContextManager: typeof getContextManager;
|
|
8
|
+
readonly getRecommendTarget: typeof getRecommendTarget;
|
|
9
|
+
readonly waitForRecommendList: typeof waitForRecommendList;
|
|
10
|
+
readonly createVisualActivitySession: (target: RecommendTarget) => VisualActivitySessionLike;
|
|
11
|
+
};
|
|
12
|
+
export declare function setZhipinGetCandidateListDepsForTests(override: Partial<ZhipinGetCandidateListDeps> | undefined): void;
|
|
1
13
|
export declare const zhipinGetCandidateList: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
14
|
maxResults?: number | undefined;
|
|
3
15
|
}, {
|
|
4
16
|
success: boolean;
|
|
5
17
|
candidates: {
|
|
6
18
|
name: string;
|
|
7
|
-
index: number;
|
|
8
19
|
candidateId: string;
|
|
20
|
+
index: number;
|
|
9
21
|
expectedLocation: string;
|
|
10
22
|
expectedPosition: string;
|
|
11
23
|
age: string;
|
|
@@ -21,3 +33,4 @@ export declare const zhipinGetCandidateList: import("@roll-agent/sdk").ToolDefin
|
|
|
21
33
|
total: number;
|
|
22
34
|
error?: string | undefined;
|
|
23
35
|
}>;
|
|
36
|
+
export {};
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
import { getCurrentZhipinRecruiterIdentity } from "../pages/zhipin/recruiter-identity.ts";
|
|
3
|
+
import { findHeaderScope } from "../pages/zhipin/username.ts";
|
|
4
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
5
|
+
import { VisualActivitySession } from "../visual-activity-session.ts";
|
|
6
|
+
type ZhipinGetUsernameDeps = {
|
|
7
|
+
readonly getContextManager: typeof getContextManager;
|
|
8
|
+
readonly findHeaderScope: typeof findHeaderScope;
|
|
9
|
+
readonly getCurrentZhipinRecruiterIdentity: typeof getCurrentZhipinRecruiterIdentity;
|
|
10
|
+
readonly createVisualActivitySession: (page: Page) => VisualActivitySession;
|
|
11
|
+
};
|
|
12
|
+
export declare function setZhipinGetUsernameDepsForTests(override: Partial<ZhipinGetUsernameDeps> | undefined): void;
|
|
1
13
|
export declare const zhipinGetUsername: import("@roll-agent/sdk").ToolDefinition<{}, {
|
|
2
14
|
success: boolean;
|
|
3
15
|
username: string;
|
|
@@ -6,3 +18,4 @@ export declare const zhipinGetUsername: import("@roll-agent/sdk").ToolDefinition
|
|
|
6
18
|
usedSelector?: string | undefined;
|
|
7
19
|
usedStrategy?: string | undefined;
|
|
8
20
|
}>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
import { randomDelay } from "../pages/zhipin/anti-detection.ts";
|
|
3
|
+
import { findZhipinSidebarSectionLink, isZhipinChatSurfaceOpen, waitForZhipinChatSurface } from "../pages/zhipin/sidebar-navigation.ts";
|
|
4
|
+
import { toAttachedPageInfo } from "../page-info.ts";
|
|
5
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
6
|
+
import { VisualActivitySession } from "../visual-activity-session.ts";
|
|
7
|
+
import { moveVisualCursorToLocator, showVisualClickOnLocator } from "../visual-cursor.ts";
|
|
8
|
+
type VisualActivitySessionLike = Pick<VisualActivitySession, "begin" | "highlightSelector" | "succeed" | "fail">;
|
|
9
|
+
type ZhipinOpenChatPageDeps = {
|
|
10
|
+
readonly getContextManager: typeof getContextManager;
|
|
11
|
+
readonly findZhipinSidebarSectionLink: typeof findZhipinSidebarSectionLink;
|
|
12
|
+
readonly isZhipinChatSurfaceOpen: typeof isZhipinChatSurfaceOpen;
|
|
13
|
+
readonly waitForZhipinChatSurface: typeof waitForZhipinChatSurface;
|
|
14
|
+
readonly moveVisualCursorToLocator: typeof moveVisualCursorToLocator;
|
|
15
|
+
readonly showVisualClickOnLocator: typeof showVisualClickOnLocator;
|
|
16
|
+
readonly randomDelay: typeof randomDelay;
|
|
17
|
+
readonly toAttachedPageInfo: typeof toAttachedPageInfo;
|
|
18
|
+
readonly createVisualActivitySession: (page: Page) => VisualActivitySessionLike;
|
|
19
|
+
};
|
|
20
|
+
export declare function setZhipinOpenChatPageDepsForTests(override: Partial<ZhipinOpenChatPageDeps> | undefined): void;
|
|
21
|
+
export declare const zhipinOpenChatPage: import("@roll-agent/sdk").ToolDefinition<{}, {
|
|
22
|
+
success: boolean;
|
|
23
|
+
alreadyOnChat: boolean;
|
|
24
|
+
usedSidebarClick: boolean;
|
|
25
|
+
chatReady: boolean;
|
|
26
|
+
error?: string | undefined;
|
|
27
|
+
page?: {
|
|
28
|
+
url: string;
|
|
29
|
+
title: string;
|
|
30
|
+
pageId: string;
|
|
31
|
+
boundPlatform: "zhipin" | "yupao" | null;
|
|
32
|
+
detectedPlatform: "zhipin" | "yupao" | null;
|
|
33
|
+
isSelectedForPlatform: boolean;
|
|
34
|
+
} | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
export {};
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
export declare const zhipinOpenChat: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
|
-
|
|
2
|
+
conversationId?: string | undefined;
|
|
3
3
|
candidateName?: string | undefined;
|
|
4
|
+
index?: number | undefined;
|
|
4
5
|
preferUnread?: boolean | undefined;
|
|
5
6
|
}, {
|
|
6
7
|
success: boolean;
|
|
8
|
+
conversationId: string;
|
|
9
|
+
candidateId: string;
|
|
10
|
+
candidateName: string;
|
|
11
|
+
index: number;
|
|
7
12
|
hasUnread: boolean;
|
|
8
13
|
unreadCount: number;
|
|
9
14
|
lastMessageTime: string;
|
|
10
15
|
messagePreview: string;
|
|
11
|
-
index: number;
|
|
12
|
-
candidateName: string;
|
|
13
16
|
error?: string | undefined;
|
|
14
17
|
}>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { randomDelay } from "../pages/zhipin/anti-detection.ts";
|
|
2
|
+
import { getRecommendTarget } from "../pages/zhipin/recommend-list.ts";
|
|
3
|
+
import { findZhipinSidebarSectionLink, isZhipinRecommendSurfaceOpen, waitForZhipinRecommendSurface } from "../pages/zhipin/sidebar-navigation.ts";
|
|
4
|
+
import { toAttachedPageInfo } from "../page-info.ts";
|
|
5
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
6
|
+
import { VisualActivitySession } from "../visual-activity-session.ts";
|
|
7
|
+
import { moveVisualCursorToLocator, showVisualClickOnLocator } from "../visual-cursor.ts";
|
|
8
|
+
type RecommendTarget = ReturnType<typeof getRecommendTarget>;
|
|
9
|
+
type VisualActivitySessionLike = Pick<VisualActivitySession, "begin" | "highlightSelector" | "retarget" | "succeed" | "fail">;
|
|
10
|
+
type ZhipinOpenRecommendPageDeps = {
|
|
11
|
+
readonly getContextManager: typeof getContextManager;
|
|
12
|
+
readonly getRecommendTarget: typeof getRecommendTarget;
|
|
13
|
+
readonly findZhipinSidebarSectionLink: typeof findZhipinSidebarSectionLink;
|
|
14
|
+
readonly isZhipinRecommendSurfaceOpen: typeof isZhipinRecommendSurfaceOpen;
|
|
15
|
+
readonly waitForZhipinRecommendSurface: typeof waitForZhipinRecommendSurface;
|
|
16
|
+
readonly moveVisualCursorToLocator: typeof moveVisualCursorToLocator;
|
|
17
|
+
readonly showVisualClickOnLocator: typeof showVisualClickOnLocator;
|
|
18
|
+
readonly randomDelay: typeof randomDelay;
|
|
19
|
+
readonly toAttachedPageInfo: typeof toAttachedPageInfo;
|
|
20
|
+
readonly createVisualActivitySession: (target: RecommendTarget) => VisualActivitySessionLike;
|
|
21
|
+
};
|
|
22
|
+
export declare function setZhipinOpenRecommendPageDepsForTests(override: Partial<ZhipinOpenRecommendPageDeps> | undefined): void;
|
|
23
|
+
export declare const zhipinOpenRecommendPage: import("@roll-agent/sdk").ToolDefinition<{}, {
|
|
24
|
+
success: boolean;
|
|
25
|
+
usedSidebarClick: boolean;
|
|
26
|
+
alreadyOnRecommend: boolean;
|
|
27
|
+
recommendReady: boolean;
|
|
28
|
+
error?: string | undefined;
|
|
29
|
+
page?: {
|
|
30
|
+
url: string;
|
|
31
|
+
title: string;
|
|
32
|
+
pageId: string;
|
|
33
|
+
boundPlatform: "zhipin" | "yupao" | null;
|
|
34
|
+
detectedPlatform: "zhipin" | "yupao" | null;
|
|
35
|
+
isSelectedForPlatform: boolean;
|
|
36
|
+
} | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
export {};
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
import { getContextManager } from "../runtime-holder.ts";
|
|
2
|
+
import { humanDelay, shouldAddRandomBehavior, performRandomScroll } from "../pages/zhipin/anti-detection.ts";
|
|
3
|
+
import { getRecommendTarget, inspectRecommendCard, waitForRecommendList } from "../pages/zhipin/recommend-list.ts";
|
|
4
|
+
import { VisualActivitySession } from "../visual-activity-session.ts";
|
|
5
|
+
import { moveVisualCursorToLocator, showVisualClickOnLocator } from "../visual-cursor.ts";
|
|
6
|
+
type RecommendTarget = ReturnType<typeof getRecommendTarget>;
|
|
7
|
+
type VisualActivitySessionLike = Pick<VisualActivitySession, "begin" | "highlightSelector" | "highlightLocator" | "succeed" | "fail" | "retarget">;
|
|
8
|
+
type ZhipinSayHelloDeps = {
|
|
9
|
+
readonly getContextManager: typeof getContextManager;
|
|
10
|
+
readonly getRecommendTarget: typeof getRecommendTarget;
|
|
11
|
+
readonly waitForRecommendList: typeof waitForRecommendList;
|
|
12
|
+
readonly inspectRecommendCard: typeof inspectRecommendCard;
|
|
13
|
+
readonly moveVisualCursorToLocator: typeof moveVisualCursorToLocator;
|
|
14
|
+
readonly showVisualClickOnLocator: typeof showVisualClickOnLocator;
|
|
15
|
+
readonly humanDelay: typeof humanDelay;
|
|
16
|
+
readonly shouldAddRandomBehavior: typeof shouldAddRandomBehavior;
|
|
17
|
+
readonly performRandomScroll: typeof performRandomScroll;
|
|
18
|
+
readonly createVisualActivitySession: (target: RecommendTarget) => VisualActivitySessionLike;
|
|
19
|
+
};
|
|
20
|
+
export declare function setZhipinSayHelloDepsForTests(override: Partial<ZhipinSayHelloDeps> | undefined): void;
|
|
1
21
|
export declare const zhipinSayHello: import("@roll-agent/sdk").ToolDefinition<{
|
|
2
22
|
indices: number[];
|
|
3
23
|
}, {
|
|
@@ -9,9 +29,10 @@ export declare const zhipinSayHello: import("@roll-agent/sdk").ToolDefinition<{
|
|
|
9
29
|
success: boolean;
|
|
10
30
|
results: {
|
|
11
31
|
success: boolean;
|
|
12
|
-
index: number;
|
|
13
32
|
candidateId: string;
|
|
14
33
|
candidateName: string;
|
|
34
|
+
index: number;
|
|
15
35
|
error?: string | undefined;
|
|
16
36
|
}[];
|
|
17
37
|
}>;
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
import type { VisualActivityTone, VisualTarget } from "./visual-activity.ts";
|
|
3
|
+
type PageLocator = ReturnType<Page["locator"]>;
|
|
4
|
+
type VisualActivityHighlightOptions = {
|
|
5
|
+
readonly label?: string;
|
|
6
|
+
readonly padding?: number;
|
|
7
|
+
readonly tone?: VisualActivityTone;
|
|
8
|
+
};
|
|
9
|
+
export declare class VisualActivitySession {
|
|
10
|
+
page: Page;
|
|
11
|
+
target: VisualTarget;
|
|
12
|
+
constructor(initialTarget: VisualTarget);
|
|
13
|
+
retarget(nextTarget: VisualTarget): Promise<boolean>;
|
|
14
|
+
begin(label: string, tone?: VisualActivityTone): Promise<boolean>;
|
|
15
|
+
highlightSelector(selector: string, options?: VisualActivityHighlightOptions): Promise<boolean>;
|
|
16
|
+
highlightLocator(locator: PageLocator, options?: VisualActivityHighlightOptions): Promise<boolean>;
|
|
17
|
+
succeed(label: string, lingerMs?: number): Promise<boolean>;
|
|
18
|
+
fail(label: string, lingerMs?: number): Promise<boolean>;
|
|
19
|
+
clear(): Promise<boolean>;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
type PageLocator = ReturnType<Page["locator"]>;
|
|
3
|
+
export type VisualTarget = Page | NonNullable<ReturnType<Page["frame"]>>;
|
|
4
|
+
type VisualActivityOptions = {
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly target?: VisualTarget;
|
|
7
|
+
readonly tone?: VisualActivityTone;
|
|
8
|
+
};
|
|
9
|
+
type VisualHighlightOptions = {
|
|
10
|
+
readonly label?: string;
|
|
11
|
+
readonly padding?: number;
|
|
12
|
+
readonly target?: VisualTarget;
|
|
13
|
+
readonly tone?: VisualActivityTone;
|
|
14
|
+
};
|
|
15
|
+
type VisualCompletionOptions = {
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly lingerMs?: number;
|
|
18
|
+
readonly status?: "success" | "error";
|
|
19
|
+
readonly target?: VisualTarget;
|
|
20
|
+
};
|
|
21
|
+
declare const VISUAL_ACTIVITY_THEMES: {
|
|
22
|
+
readonly info: {
|
|
23
|
+
readonly accent: "#14b8a6";
|
|
24
|
+
readonly accentSoft: "rgba(20, 184, 166, 0.42)";
|
|
25
|
+
readonly accentGlow: "rgba(20, 184, 166, 0.18)";
|
|
26
|
+
readonly capsuleBg: "rgba(15, 23, 42, 0.82)";
|
|
27
|
+
readonly capsuleBorder: "rgba(45, 212, 191, 0.38)";
|
|
28
|
+
readonly text: "#F8FAFC";
|
|
29
|
+
readonly dot: "#2DD4BF";
|
|
30
|
+
};
|
|
31
|
+
readonly success: {
|
|
32
|
+
readonly accent: "#22c55e";
|
|
33
|
+
readonly accentSoft: "rgba(34, 197, 94, 0.42)";
|
|
34
|
+
readonly accentGlow: "rgba(34, 197, 94, 0.18)";
|
|
35
|
+
readonly capsuleBg: "rgba(10, 24, 16, 0.86)";
|
|
36
|
+
readonly capsuleBorder: "rgba(74, 222, 128, 0.38)";
|
|
37
|
+
readonly text: "#F0FDF4";
|
|
38
|
+
readonly dot: "#4ADE80";
|
|
39
|
+
};
|
|
40
|
+
readonly error: {
|
|
41
|
+
readonly accent: "#f59e0b";
|
|
42
|
+
readonly accentSoft: "rgba(245, 158, 11, 0.42)";
|
|
43
|
+
readonly accentGlow: "rgba(245, 158, 11, 0.2)";
|
|
44
|
+
readonly capsuleBg: "rgba(41, 24, 10, 0.88)";
|
|
45
|
+
readonly capsuleBorder: "rgba(251, 191, 36, 0.4)";
|
|
46
|
+
readonly text: "#FFFBEB";
|
|
47
|
+
readonly dot: "#FBBF24";
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
export type VisualActivityTone = keyof typeof VISUAL_ACTIVITY_THEMES;
|
|
51
|
+
export declare function isVisualActivityEnabled(): boolean;
|
|
52
|
+
export declare function setVisualActivityEnabledForTests(value: boolean | undefined): void;
|
|
53
|
+
export declare function beginVisualActivity(page: Page, options: VisualActivityOptions): Promise<boolean>;
|
|
54
|
+
export declare function highlightVisualRegionForLocator(page: Page, locator: PageLocator, options?: VisualHighlightOptions): Promise<boolean>;
|
|
55
|
+
export declare function highlightVisualRegionForSelector(page: Page, selector: string, options?: VisualHighlightOptions): Promise<boolean>;
|
|
56
|
+
export declare function completeVisualActivity(page: Page, options: VisualCompletionOptions): Promise<boolean>;
|
|
57
|
+
export declare function clearVisualActivity(page: Page, options?: {
|
|
58
|
+
readonly target?: VisualTarget;
|
|
59
|
+
}): Promise<boolean>;
|
|
60
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Page } from "@roll-agent/browser";
|
|
2
|
+
type PageLocator = ReturnType<Page["locator"]>;
|
|
3
|
+
type VisualTarget = Page | NonNullable<ReturnType<Page["frame"]>>;
|
|
4
|
+
type CursorMoveOptions = {
|
|
5
|
+
readonly durationMs?: number;
|
|
6
|
+
readonly settleMs?: number;
|
|
7
|
+
readonly target?: VisualTarget;
|
|
8
|
+
};
|
|
9
|
+
type CursorClickOptions = {
|
|
10
|
+
readonly pulseDurationMs?: number;
|
|
11
|
+
readonly target?: VisualTarget;
|
|
12
|
+
};
|
|
13
|
+
export declare function isVisualCursorEnabled(): boolean;
|
|
14
|
+
export declare function setVisualCursorEnabledForTests(value: boolean | undefined): void;
|
|
15
|
+
export declare function clearVisualCursor(page: Page, options?: {
|
|
16
|
+
readonly preserveTarget?: VisualTarget;
|
|
17
|
+
}): Promise<boolean>;
|
|
18
|
+
export declare function moveVisualCursorToLocator(page: Page, locator: PageLocator, options?: CursorMoveOptions): Promise<boolean>;
|
|
19
|
+
export declare function showVisualClickOnLocator(page: Page, locator: PageLocator, options?: CursorClickOptions): Promise<boolean>;
|
|
20
|
+
export declare function moveVisualCursorToSelector(page: Page, selector: string, options?: CursorMoveOptions): Promise<boolean>;
|
|
21
|
+
export declare function showVisualClickOnSelector(page: Page, selector: string): Promise<boolean>;
|
|
22
|
+
export {};
|
package/package.json
CHANGED
package/references/env.yaml
CHANGED
|
@@ -2,3 +2,12 @@ required:
|
|
|
2
2
|
- name: REPLY_AUTHORITY_KEYS_URL
|
|
3
3
|
purpose: Reply Authority Service 公钥分发端点;`zhipin_send_reply` 启动预热和本地 Ed25519 验签都依赖它
|
|
4
4
|
example: https://reply-authority.duliday.com/.well-known/reply-authority-keys
|
|
5
|
+
optional:
|
|
6
|
+
- name: BROWSER_VISUAL_CURSOR
|
|
7
|
+
purpose: 在可见浏览器页面内显示 browser-use 的页内虚拟指针和点击波纹;默认开启,设为 `"false"` 可关闭
|
|
8
|
+
default: "true"
|
|
9
|
+
example: "false"
|
|
10
|
+
- name: BROWSER_VISUAL_ACTIVITY
|
|
11
|
+
purpose: 在消息读取、账号识别、聊天详情提取等非点击型操作期间显示页内状态胶囊、区域柔光和完成态反馈;默认开启,设为 `"false"` 可关闭
|
|
12
|
+
default: "true"
|
|
13
|
+
example: "true"
|