@roll-agent/browser-use-agent 0.3.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/dist/index.d.ts +0 -1
  2. package/dist/index.js +1 -111
  3. package/dist/pages/platform-page.d.ts +0 -1
  4. package/dist/pages/platform-page.js +1 -17
  5. package/dist/pages/yupao/chat.d.ts +0 -1
  6. package/dist/pages/yupao/chat.js +1 -18
  7. package/dist/pages/yupao/message-list.d.ts +0 -1
  8. package/dist/pages/yupao/message-list.js +1 -36
  9. package/dist/pages/yupao/navigation.d.ts +0 -1
  10. package/dist/pages/yupao/navigation.js +1 -34
  11. package/dist/pages/yupao/selectors.d.ts +0 -1
  12. package/dist/pages/yupao/selectors.js +1 -26
  13. package/dist/pages/zhipin/anti-detection.d.ts +0 -1
  14. package/dist/pages/zhipin/anti-detection.js +1 -65
  15. package/dist/pages/zhipin/chat-navigation.d.ts +0 -1
  16. package/dist/pages/zhipin/chat-navigation.js +1 -158
  17. package/dist/pages/zhipin/selectors.d.ts +0 -1
  18. package/dist/pages/zhipin/selectors.js +1 -141
  19. package/dist/pages/zhipin/username.d.ts +0 -1
  20. package/dist/pages/zhipin/username.js +1 -302
  21. package/dist/platforms.d.ts +0 -1
  22. package/dist/platforms.js +1 -20
  23. package/dist/runtime-holder.d.ts +0 -1
  24. package/dist/runtime-holder.js +1 -50
  25. package/dist/tools/browser-status.d.ts +0 -1
  26. package/dist/tools/browser-status.js +1 -49
  27. package/dist/tools/list-pages.d.ts +0 -1
  28. package/dist/tools/list-pages.js +1 -40
  29. package/dist/tools/navigate-active-tab.d.ts +0 -1
  30. package/dist/tools/navigate-active-tab.js +1 -49
  31. package/dist/tools/open-platform.d.ts +0 -1
  32. package/dist/tools/open-platform.js +1 -33
  33. package/dist/tools/select-page.d.ts +0 -1
  34. package/dist/tools/select-page.js +1 -38
  35. package/dist/tools/yupao-read-messages.d.ts +0 -1
  36. package/dist/tools/yupao-read-messages.js +1 -36
  37. package/dist/tools/yupao-send-reply.d.ts +0 -1
  38. package/dist/tools/yupao-send-reply.js +1 -40
  39. package/dist/tools/zhipin-close-resume.d.ts +0 -1
  40. package/dist/tools/zhipin-close-resume.js +1 -73
  41. package/dist/tools/zhipin-exchange-wechat.d.ts +0 -1
  42. package/dist/tools/zhipin-exchange-wechat.js +1 -218
  43. package/dist/tools/zhipin-get-candidate-info.d.ts +0 -1
  44. package/dist/tools/zhipin-get-candidate-info.js +1 -247
  45. package/dist/tools/zhipin-get-candidate-list.d.ts +0 -1
  46. package/dist/tools/zhipin-get-candidate-list.js +1 -175
  47. package/dist/tools/zhipin-get-username.d.ts +0 -1
  48. package/dist/tools/zhipin-get-username.js +1 -58
  49. package/dist/tools/zhipin-locate-resume-canvas.d.ts +0 -1
  50. package/dist/tools/zhipin-locate-resume-canvas.js +1 -90
  51. package/dist/tools/zhipin-open-chat.d.ts +0 -1
  52. package/dist/tools/zhipin-open-chat.js +1 -82
  53. package/dist/tools/zhipin-open-resume.d.ts +0 -1
  54. package/dist/tools/zhipin-open-resume.js +1 -51
  55. package/dist/tools/zhipin-read-messages.d.ts +0 -1
  56. package/dist/tools/zhipin-read-messages.js +1 -90
  57. package/dist/tools/zhipin-say-hello.d.ts +0 -1
  58. package/dist/tools/zhipin-say-hello.js +1 -118
  59. package/dist/tools/zhipin-send-reply.d.ts +0 -1
  60. package/dist/tools/zhipin-send-reply.js +1 -99
  61. package/package.json +4 -4
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/pages/platform-page.d.ts.map +0 -1
  65. package/dist/pages/platform-page.js.map +0 -1
  66. package/dist/pages/yupao/chat.d.ts.map +0 -1
  67. package/dist/pages/yupao/chat.js.map +0 -1
  68. package/dist/pages/yupao/message-list.d.ts.map +0 -1
  69. package/dist/pages/yupao/message-list.js.map +0 -1
  70. package/dist/pages/yupao/navigation.d.ts.map +0 -1
  71. package/dist/pages/yupao/navigation.js.map +0 -1
  72. package/dist/pages/yupao/selectors.d.ts.map +0 -1
  73. package/dist/pages/yupao/selectors.js.map +0 -1
  74. package/dist/pages/zhipin/anti-detection.d.ts.map +0 -1
  75. package/dist/pages/zhipin/anti-detection.js.map +0 -1
  76. package/dist/pages/zhipin/chat-navigation.d.ts.map +0 -1
  77. package/dist/pages/zhipin/chat-navigation.js.map +0 -1
  78. package/dist/pages/zhipin/selectors.d.ts.map +0 -1
  79. package/dist/pages/zhipin/selectors.js.map +0 -1
  80. package/dist/pages/zhipin/username.d.ts.map +0 -1
  81. package/dist/pages/zhipin/username.js.map +0 -1
  82. package/dist/platforms.d.ts.map +0 -1
  83. package/dist/platforms.js.map +0 -1
  84. package/dist/runtime-holder.d.ts.map +0 -1
  85. package/dist/runtime-holder.js.map +0 -1
  86. package/dist/tools/browser-status.d.ts.map +0 -1
  87. package/dist/tools/browser-status.js.map +0 -1
  88. package/dist/tools/list-pages.d.ts.map +0 -1
  89. package/dist/tools/list-pages.js.map +0 -1
  90. package/dist/tools/navigate-active-tab.d.ts.map +0 -1
  91. package/dist/tools/navigate-active-tab.js.map +0 -1
  92. package/dist/tools/open-platform.d.ts.map +0 -1
  93. package/dist/tools/open-platform.js.map +0 -1
  94. package/dist/tools/select-page.d.ts.map +0 -1
  95. package/dist/tools/select-page.js.map +0 -1
  96. package/dist/tools/yupao-read-messages.d.ts.map +0 -1
  97. package/dist/tools/yupao-read-messages.js.map +0 -1
  98. package/dist/tools/yupao-send-reply.d.ts.map +0 -1
  99. package/dist/tools/yupao-send-reply.js.map +0 -1
  100. package/dist/tools/zhipin-close-resume.d.ts.map +0 -1
  101. package/dist/tools/zhipin-close-resume.js.map +0 -1
  102. package/dist/tools/zhipin-exchange-wechat.d.ts.map +0 -1
  103. package/dist/tools/zhipin-exchange-wechat.js.map +0 -1
  104. package/dist/tools/zhipin-get-candidate-info.d.ts.map +0 -1
  105. package/dist/tools/zhipin-get-candidate-info.js.map +0 -1
  106. package/dist/tools/zhipin-get-candidate-list.d.ts.map +0 -1
  107. package/dist/tools/zhipin-get-candidate-list.js.map +0 -1
  108. package/dist/tools/zhipin-get-username.d.ts.map +0 -1
  109. package/dist/tools/zhipin-get-username.js.map +0 -1
  110. package/dist/tools/zhipin-locate-resume-canvas.d.ts.map +0 -1
  111. package/dist/tools/zhipin-locate-resume-canvas.js.map +0 -1
  112. package/dist/tools/zhipin-open-chat.d.ts.map +0 -1
  113. package/dist/tools/zhipin-open-chat.js.map +0 -1
  114. package/dist/tools/zhipin-open-resume.d.ts.map +0 -1
  115. package/dist/tools/zhipin-open-resume.js.map +0 -1
  116. package/dist/tools/zhipin-read-messages.d.ts.map +0 -1
  117. package/dist/tools/zhipin-read-messages.js.map +0 -1
  118. package/dist/tools/zhipin-say-hello.d.ts.map +0 -1
  119. package/dist/tools/zhipin-say-hello.js.map +0 -1
  120. package/dist/tools/zhipin-send-reply.d.ts.map +0 -1
  121. package/dist/tools/zhipin-send-reply.js.map +0 -1
@@ -1,247 +1 @@
1
- import { defineTool } from "@roll-agent/sdk";
2
- import { z } from "zod";
3
- import { getContextManager } from "../runtime-holder.js";
4
- import { ensureChatOpen } from "../pages/zhipin/chat-navigation.js";
5
- const ChatMessageSchema = z.object({
6
- index: z.number(),
7
- sender: z.enum(["candidate", "recruiter", "system"]),
8
- messageType: z.enum(["text", "system", "resume", "wechat-exchange"]),
9
- content: z.string(),
10
- time: z.string(),
11
- });
12
- const CandidateInfoSchema = z.object({
13
- name: z.string(),
14
- age: z.string(),
15
- experience: z.string(),
16
- education: z.string(),
17
- communicationPosition: z.string(),
18
- expectedPosition: z.string(),
19
- expectedLocation: z.string(),
20
- expectedSalary: z.string(),
21
- tags: z.array(z.string()),
22
- });
23
- const OutputSchema = z.object({
24
- success: z.boolean(),
25
- candidateInfo: CandidateInfoSchema,
26
- chatMessages: z.array(ChatMessageSchema),
27
- formattedHistory: z.array(z.string()),
28
- stats: z.object({
29
- totalMessages: z.number(),
30
- candidateMessages: z.number(),
31
- recruiterMessages: z.number(),
32
- systemMessages: z.number(),
33
- }),
34
- error: z.string().optional(),
35
- });
36
- export const zhipinGetCandidateInfo = defineTool({
37
- name: "zhipin_get_candidate_info",
38
- description: "提取候选人资料和完整聊天记录。可指定 candidateName 自动打开对应聊天,或不传则读取当前窗口;例如“查看鲁倩的聊天详情”应提取 candidateName=鲁倩。",
39
- input: z.object({
40
- candidateName: z
41
- .string()
42
- .optional()
43
- .describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),
44
- index: z.number().optional().describe("候选人在列表中的索引(可选)"),
45
- maxMessages: z.number().default(100).describe("最多返回的消息条数"),
46
- }),
47
- output: OutputSchema,
48
- execute: async (input, ctx) => {
49
- const maxMessages = input.maxMessages ?? 100;
50
- const ctxManager = getContextManager();
51
- const page = await ctxManager.getPage("zhipin");
52
- // 如果指定了候选人,先导航到对应聊天
53
- const nav = await ensureChatOpen(page, {
54
- candidateName: input.candidateName,
55
- index: input.index,
56
- });
57
- if (nav && !nav.found) {
58
- const empty = {
59
- name: "",
60
- age: "",
61
- experience: "",
62
- education: "",
63
- communicationPosition: "",
64
- expectedPosition: "",
65
- expectedLocation: "",
66
- expectedSalary: "",
67
- tags: [],
68
- };
69
- return {
70
- success: false,
71
- candidateInfo: empty,
72
- chatMessages: [],
73
- formattedHistory: [],
74
- stats: { totalMessages: 0, candidateMessages: 0, recruiterMessages: 0, systemMessages: 0 },
75
- error: nav.error,
76
- };
77
- }
78
- ctx.logger.info(`Extracting candidate info${nav ? ` for ${nav.name}` : " (current window)"}`);
79
- // 等待聊天消息加载(DOM: .conversation-message > .chat-message-list > .message-item)
80
- try {
81
- await page.waitForSelector(".chat-message-list .message-item, .conversation-message .message-item", { timeout: 8_000 });
82
- }
83
- catch {
84
- // 可能是空对话(无消息),继续提取 candidateInfo
85
- }
86
- const data = await page.evaluate((maxMsgs) => {
87
- // ===== Candidate Info =====
88
- // 限定到聊天头部的详情区域,避免匹配左侧列表
89
- const detailArea = document.querySelector(".base-info-single-detial, .base-info-content");
90
- const name = detailArea?.querySelector(".name-box")?.textContent?.trim() ?? "";
91
- const infoItems = detailArea
92
- ? detailArea.querySelectorAll(":scope > div")
93
- : document.querySelectorAll(".geek-info-item, .base-info-item");
94
- const infoTexts = [];
95
- infoItems.forEach((el) => {
96
- const t = el.textContent?.trim();
97
- if (t)
98
- infoTexts.push(t);
99
- });
100
- const fullInfo = infoTexts.join(" ");
101
- const ageMatch = fullInfo.match(/(\d{2,3})岁/);
102
- const age = ageMatch ? ageMatch[1] + "岁" : "";
103
- const expMatch = fullInfo.match(/(\d+年(?:以上)?|应届生|在校生)/);
104
- const experience = expMatch?.[1] ?? "";
105
- const education = fullInfo.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1] ?? "";
106
- let communicationPosition = "";
107
- const posNameEl = document.querySelector(".position-name");
108
- if (posNameEl) {
109
- const cloned = posNameEl.cloneNode(true);
110
- cloned.querySelectorAll(".popover-wrap, .tooltip-job").forEach((e) => e.remove());
111
- communicationPosition = cloned.textContent?.trim() ?? "";
112
- }
113
- let expectedPosition = "";
114
- let expectedLocation = "";
115
- const expectValue = document.querySelector(".position-item.expect .value.job");
116
- if (expectValue) {
117
- const parts = (expectValue.textContent?.trim() ?? "").split("·").map((s) => s.trim());
118
- expectedLocation = parts[0] ?? "";
119
- expectedPosition = parts[1] ?? "";
120
- }
121
- const expectedSalary = document.querySelector(".position-item.expect .high-light-orange")?.textContent?.trim() ??
122
- "";
123
- // tags 只取详情区域内的标签,排除沟通职位区域的 .high-light-boss
124
- const tags = [];
125
- if (detailArea) {
126
- detailArea.querySelectorAll(".geek-tag, .base-info-item .high-light-boss").forEach((el) => {
127
- const t = el.textContent?.trim();
128
- // 过滤掉沟通职位文本(通常很长且包含"更换职位")
129
- if (t && !t.includes("更换职位") && t.length < 20)
130
- tags.push(t);
131
- });
132
- }
133
- // ===== Chat Messages =====
134
- // 实际 DOM: .conversation-message > .chat-message-list > .message-item
135
- // 直接查 .chat-message-list 的直接子 .message-item,跳过中间层
136
- const msgItems = document.querySelectorAll(".chat-message-list > .message-item");
137
- const timeRegex = /\d{1,2}:\d{2}(?::\d{2})?|\d{4}-\d{2}-\d{2}/;
138
- const messages = [];
139
- let msgIdx = 0;
140
- msgItems.forEach((item) => {
141
- if (msgIdx >= maxMsgs)
142
- return;
143
- // 从子元素判断消息类型(.message-item 本身只是包装层)
144
- const hasFriend = item.querySelector(".item-friend") !== null;
145
- const hasMyself = item.querySelector(".item-myself") !== null;
146
- const hasSystem = item.querySelector(".item-system") !== null;
147
- const hasResume = item.querySelector(".item-resume") !== null;
148
- const hasDialog = item.querySelector(".message-dialog-center") !== null;
149
- let sender = "system";
150
- let messageType = "text";
151
- if (hasFriend) {
152
- sender = "candidate";
153
- }
154
- else if (hasMyself) {
155
- sender = "recruiter";
156
- }
157
- else if (hasSystem || hasDialog) {
158
- sender = "system";
159
- messageType = "system";
160
- }
161
- if (hasResume)
162
- messageType = "resume";
163
- // 微信交换卡片检测
164
- const cardEl = item.querySelector(".message-card-top-wrap, [class*='d-top-text']");
165
- if (cardEl) {
166
- const cardText = cardEl.textContent ?? "";
167
- if (cardText.includes("微信") || cardText.includes("WeChat")) {
168
- messageType = "wechat-exchange";
169
- }
170
- }
171
- // 时间
172
- const timeEl = item.querySelector(".message-time .time, .message-time");
173
- const timeMatch = (timeEl?.textContent ?? "").match(timeRegex);
174
- const time = timeMatch ? timeMatch[0] : "";
175
- // 内容提取 — 按消息类型使用不同选择器
176
- let content = "";
177
- if (messageType === "wechat-exchange" && cardEl) {
178
- const ct = cardEl.textContent ?? "";
179
- const digitMatch = ct.match(/\b(\d{8,15})\b/);
180
- const wxMatch = ct.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);
181
- if (digitMatch)
182
- content = `[微信号: ${digitMatch[1]}]`;
183
- else if (wxMatch)
184
- content = `[微信号: ${wxMatch[1]}]`;
185
- else
186
- content = "[交换微信]";
187
- }
188
- else if (cardEl) {
189
- // 系统卡片:提取标题和描述
190
- const titleEl = item.querySelector(".message-card-top-title");
191
- const descEl = item.querySelector(".dialog-content, .message-card-top-text");
192
- content = (titleEl?.textContent?.trim() ?? descEl?.textContent?.trim() ?? "").trim();
193
- }
194
- else {
195
- // 普通文本消息
196
- const textEl = item.querySelector(".text span, .text-content, .text");
197
- if (textEl) {
198
- content = (textEl.textContent?.trim() ?? "")
199
- .replace(timeRegex, "")
200
- .replace("已读", "")
201
- .trim();
202
- }
203
- }
204
- if (content || messageType !== "text") {
205
- messages.push({ index: msgIdx, sender, messageType, content, time });
206
- msgIdx++;
207
- }
208
- });
209
- return {
210
- candidateInfo: {
211
- name,
212
- age,
213
- experience,
214
- education,
215
- communicationPosition,
216
- expectedPosition,
217
- expectedLocation,
218
- expectedSalary,
219
- tags,
220
- },
221
- messages,
222
- };
223
- }, maxMessages);
224
- // formattedHistory 只保留 candidate + recruiter 对话,过滤系统消息噪音
225
- const formattedHistory = data.messages
226
- .filter((m) => m.sender === "candidate" || m.sender === "recruiter")
227
- .map((m) => {
228
- const prefix = m.sender === "candidate" ? "求职者" : "我";
229
- return `${prefix}: ${m.content}`;
230
- });
231
- const stats = {
232
- totalMessages: data.messages.length,
233
- candidateMessages: data.messages.filter((m) => m.sender === "candidate").length,
234
- recruiterMessages: data.messages.filter((m) => m.sender === "recruiter").length,
235
- systemMessages: data.messages.filter((m) => m.sender === "system").length,
236
- };
237
- ctx.logger.info(`Extracted info for ${data.candidateInfo.name}: ${stats.totalMessages} messages`);
238
- return {
239
- success: true,
240
- candidateInfo: data.candidateInfo,
241
- chatMessages: data.messages,
242
- formattedHistory,
243
- stats,
244
- };
245
- },
246
- });
247
- //# sourceMappingURL=zhipin-get-candidate-info.js.map
1
+ import{defineTool as e}from"@roll-agent/sdk";import{z as t}from"zod";import{getContextManager as s}from"../runtime-holder.js";import{ensureChatOpen as n}from"../pages/zhipin/chat-navigation.js";const a=t.object({index:t.number(),sender:t.enum(["candidate","recruiter","system"]),messageType:t.enum(["text","system","resume","wechat-exchange"]),content:t.string(),time:t.string()}),o=t.object({name:t.string(),age:t.string(),experience:t.string(),education:t.string(),communicationPosition:t.string(),expectedPosition:t.string(),expectedLocation:t.string(),expectedSalary:t.string(),tags:t.array(t.string())}),r=t.object({success:t.boolean(),candidateInfo:o,chatMessages:t.array(a),formattedHistory:t.array(t.string()),stats:t.object({totalMessages:t.number(),candidateMessages:t.number(),recruiterMessages:t.number(),systemMessages:t.number()}),error:t.string().optional()});export const zhipinGetCandidateInfo=e({name:"zhipin_get_candidate_info",description:"提取候选人资料和完整聊天记录。可指定 candidateName 自动打开对应聊天,或不传则读取当前窗口;例如“查看鲁倩的聊天详情”应提取 candidateName=鲁倩。",input:t.object({candidateName:t.string().optional().describe("候选人姓名。若用户说“查看鲁倩的聊天详情”,这里应提取为“鲁倩”"),index:t.number().optional().describe("候选人在列表中的索引(可选)"),maxMessages:t.number().default(100).describe("最多返回的消息条数")}),output:r,execute:async(e,t)=>{const a=e.maxMessages??100,o=s(),r=await o.getPage("zhipin"),i=await n(r,{candidateName:e.candidateName,index:e.index});if(i&&!i.found){return{success:!1,candidateInfo:{name:"",age:"",experience:"",education:"",communicationPosition:"",expectedPosition:"",expectedLocation:"",expectedSalary:"",tags:[]},chatMessages:[],formattedHistory:[],stats:{totalMessages:0,candidateMessages:0,recruiterMessages:0,systemMessages:0},error:i.error}}t.logger.info("Extracting candidate info"+(i?` for ${i.name}`:" (current window)"));try{await r.waitForSelector(".chat-message-list .message-item, .conversation-message .message-item",{timeout:8e3})}catch{}const c=await r.evaluate(e=>{const t=document.querySelector(".base-info-single-detial, .base-info-content"),s=t?.querySelector(".name-box")?.textContent?.trim()??"",n=t?t.querySelectorAll(":scope > div"):document.querySelectorAll(".geek-info-item, .base-info-item"),a=[];n.forEach(e=>{const t=e.textContent?.trim();t&&a.push(t)});const o=a.join(" "),r=o.match(/(\d{2,3})岁/),i=r?r[1]+"岁":"",c=o.match(/(\d+年(?:以上)?|应届生|在校生)/),m=c?.[1]??"",d=o.match(/(初中|高中|中专|大专|本科|硕士|博士)/)?.[1]??"";let l="";const g=document.querySelector(".position-name");if(g){const e=g.cloneNode(!0);e.querySelectorAll(".popover-wrap, .tooltip-job").forEach(e=>e.remove()),l=e.textContent?.trim()??""}let u="",p="";const x=document.querySelector(".position-item.expect .value.job");if(x){const e=(x.textContent?.trim()??"").split("·").map(e=>e.trim());p=e[0]??"",u=e[1]??""}const f=document.querySelector(".position-item.expect .high-light-orange")?.textContent?.trim()??"",y=[];t&&t.querySelectorAll(".geek-tag, .base-info-item .high-light-boss").forEach(e=>{const t=e.textContent?.trim();t&&!t.includes("更换职位")&&t.length<20&&y.push(t)});const h=document.querySelectorAll(".chat-message-list > .message-item"),b=/\d{1,2}:\d{2}(?::\d{2})?|\d{4}-\d{2}-\d{2}/,S=[];let q=0;return h.forEach(t=>{if(q>=e)return;const s=null!==t.querySelector(".item-friend"),n=null!==t.querySelector(".item-myself"),a=null!==t.querySelector(".item-system"),o=null!==t.querySelector(".item-resume"),r=null!==t.querySelector(".message-dialog-center");let i="system",c="text";s?i="candidate":n?i="recruiter":(a||r)&&(i="system",c="system"),o&&(c="resume");const m=t.querySelector(".message-card-top-wrap, [class*='d-top-text']");if(m){const e=m.textContent??"";(e.includes("微信")||e.includes("WeChat"))&&(c="wechat-exchange")}const d=t.querySelector(".message-time .time, .message-time"),l=(d?.textContent??"").match(b),g=l?l[0]:"";let u="";if("wechat-exchange"===c&&m){const e=m.textContent??"",t=e.match(/\b(\d{8,15})\b/),s=e.match(/微信[::号]*\s*([a-zA-Z0-9_-]{5,20})/);u=t?`[微信号: ${t[1]}]`:s?`[微信号: ${s[1]}]`:"[交换微信]"}else if(m){const e=t.querySelector(".message-card-top-title"),s=t.querySelector(".dialog-content, .message-card-top-text");u=(e?.textContent?.trim()??s?.textContent?.trim()??"").trim()}else{const e=t.querySelector(".text span, .text-content, .text");e&&(u=(e.textContent?.trim()??"").replace(b,"").replace("已读","").trim())}(u||"text"!==c)&&(S.push({index:q,sender:i,messageType:c,content:u,time:g}),q++)}),{candidateInfo:{name:s,age:i,experience:m,education:d,communicationPosition:l,expectedPosition:u,expectedLocation:p,expectedSalary:f,tags:y},messages:S}},a),m=c.messages.filter(e=>"candidate"===e.sender||"recruiter"===e.sender).map(e=>`${"candidate"===e.sender?"求职者":"我"}: ${e.content}`),d={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}: ${d.totalMessages} messages`),{success:!0,candidateInfo:c.candidateInfo,chatMessages:c.messages,formattedHistory:m,stats:d}}});
@@ -21,4 +21,3 @@ export declare const zhipinGetCandidateList: import("@roll-agent/sdk").ToolDefin
21
21
  total: number;
22
22
  error?: string | undefined;
23
23
  }>;
24
- //# sourceMappingURL=zhipin-get-candidate-list.d.ts.map
@@ -1,175 +1 @@
1
- import { defineTool } from "@roll-agent/sdk";
2
- import { z } from "zod";
3
- import { getContextManager } from "../runtime-holder.js";
4
- const CandidateCardSchema = z.object({
5
- index: z.number(),
6
- candidateId: z.string(),
7
- name: z.string(),
8
- age: z.string(),
9
- experience: z.string(),
10
- education: z.string(),
11
- workStatus: z.string(),
12
- company: z.string(),
13
- currentPosition: z.string(),
14
- expectedLocation: z.string(),
15
- expectedPosition: z.string(),
16
- expectedSalary: z.string(),
17
- tags: z.array(z.string()),
18
- buttonText: z.string(),
19
- });
20
- const OutputSchema = z.object({
21
- success: z.boolean(),
22
- candidates: z.array(CandidateCardSchema),
23
- total: z.number(),
24
- error: z.string().optional(),
25
- });
26
- export const zhipinGetCandidateList = defineTool({
27
- name: "zhipin_get_candidate_list",
28
- description: "获取推荐列表页的候选人卡片信息",
29
- input: z.object({ maxResults: z.number().optional().describe("最多返回条数") }),
30
- output: OutputSchema,
31
- execute: async (input, ctx) => {
32
- ctx.logger.info("Getting candidate list from recommend page");
33
- const ctxManager = getContextManager();
34
- const page = await ctxManager.getPage("zhipin");
35
- const frame = page.frame("recommendFrame") ?? page.frames().find((f) => f.url().includes("recommend"));
36
- const target = frame ?? page;
37
- try {
38
- await target.waitForSelector("[data-geek], .geek-item", { timeout: 10_000 });
39
- }
40
- catch {
41
- return { success: false, candidates: [], total: 0, error: "推荐列表未加载" };
42
- }
43
- const candidates = await target.evaluate((maxRes) => {
44
- // 优先用 .candidate-card-wrap(按钮在这一层),fallback 到 [data-geek]
45
- let items = Array.from(document.querySelectorAll(".candidate-card-wrap"));
46
- if (items.length === 0) {
47
- // fallback: 直接用 card-inner[data-geek]
48
- items = Array.from(document.querySelectorAll("[data-geek], .geek-item"));
49
- }
50
- const limit = maxRes ?? items.length;
51
- const result = [];
52
- items.forEach((item, idx) => {
53
- if (idx >= limit)
54
- return;
55
- // candidateId 可能在自身或子元素 .card-inner 上
56
- const candidateId = item.getAttribute("data-geek") ??
57
- item.querySelector("[data-geek]")?.getAttribute("data-geek") ??
58
- "";
59
- const name = item.querySelector(".name")?.textContent?.trim() ?? "";
60
- // base-info 解析 — join-text-wrap 的分隔符由 CSS 伪元素渲染
61
- // 实际 DOM: <div class="base-info join-text-wrap"><span>47岁</span><span>10年以上</span>...</div>
62
- let age = "";
63
- let experience = "";
64
- let education = "";
65
- let workStatus = "";
66
- const baseInfoEl = item.querySelector(".base-info.join-text-wrap, .base-info");
67
- if (baseInfoEl) {
68
- const textParts = [];
69
- // 策略 1: 遍历子元素(span 等),每个子元素是一个独立字段
70
- const children = baseInfoEl.querySelectorAll(":scope > *");
71
- children.forEach((child) => {
72
- const t = child.textContent?.trim();
73
- if (t)
74
- textParts.push(t);
75
- });
76
- // 策略 2: 子元素为空时,尝试文本节点
77
- if (textParts.length <= 1) {
78
- textParts.length = 0;
79
- baseInfoEl.childNodes.forEach((node) => {
80
- if (node.nodeType === Node.TEXT_NODE) {
81
- const t = node.textContent?.trim();
82
- if (t)
83
- textParts.push(t);
84
- }
85
- });
86
- }
87
- // 策略 3: 仍为空时,整体 textContent 按分隔符切割
88
- if (textParts.length <= 1) {
89
- const fullText = baseInfoEl.textContent?.trim() ?? "";
90
- textParts.length = 0;
91
- fullText.split(/[丨·|]/).forEach((s) => {
92
- const t = s.trim();
93
- if (t)
94
- textParts.push(t);
95
- });
96
- }
97
- for (const p of textParts) {
98
- if (!age && p.includes("岁")) {
99
- age = p;
100
- }
101
- else if (!experience &&
102
- (p.includes("年") || p.includes("应届") || p.includes("在校"))) {
103
- experience = p;
104
- }
105
- else if (!education && /(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(p)) {
106
- education = p;
107
- }
108
- else if (!workStatus && /(在职|离职|在校)/.test(p)) {
109
- workStatus = p;
110
- }
111
- }
112
- }
113
- // 工作经历
114
- const workExpEl = item.querySelector(".timeline-wrap.work-exps .content.join-text-wrap") ??
115
- item.querySelector(".timeline-wrap.work-exps .content");
116
- const workText = workExpEl?.textContent?.trim() ?? "";
117
- const workParts = workText.split("·").map((s) => s.trim());
118
- const company = workParts[0] ?? "";
119
- const currentPosition = workParts[1] ?? "";
120
- // 期望信息 — 兼容新版 .row-flex 和旧版 .timeline-wrap.expect
121
- let expectedLocation = "";
122
- let expectedPosition = "";
123
- const expectRow = item.querySelector(".row-flex:not(.geek-desc)");
124
- if (expectRow) {
125
- const labelEl = expectRow.querySelector(".label");
126
- const contentEl = expectRow.querySelector(".content");
127
- const labelText = labelEl?.textContent ?? "";
128
- if ((labelText.includes("期望") || labelText.includes("最近关注")) && contentEl) {
129
- const parts = (contentEl.textContent?.trim() ?? "").split("·").map((s) => s.trim());
130
- expectedLocation = parts[0] ?? "";
131
- expectedPosition = parts[1] ?? "";
132
- }
133
- }
134
- if (!expectedLocation) {
135
- const expectEl = item.querySelector(".timeline-wrap.expect .content.join-text-wrap") ??
136
- item.querySelector(".timeline-wrap.expect .content");
137
- if (expectEl) {
138
- const parts = (expectEl.textContent?.trim() ?? "").split("·").map((s) => s.trim());
139
- expectedLocation = parts[0] ?? "";
140
- expectedPosition = parts[1] ?? "";
141
- }
142
- }
143
- const expectedSalary = item.querySelector(".salary-wrap")?.textContent?.trim() ?? "";
144
- const tags = [];
145
- item.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span").forEach((t) => {
146
- const text = t.textContent?.trim();
147
- if (text)
148
- tags.push(text);
149
- });
150
- // 按钮在 .candidate-card-wrap 层
151
- const buttonText = item.querySelector("button.btn.btn-greet")?.textContent?.trim() ?? "";
152
- result.push({
153
- index: idx,
154
- candidateId,
155
- name,
156
- age,
157
- experience,
158
- education,
159
- workStatus,
160
- company,
161
- currentPosition,
162
- expectedLocation,
163
- expectedPosition,
164
- expectedSalary,
165
- tags,
166
- buttonText,
167
- });
168
- });
169
- return result;
170
- }, input.maxResults);
171
- ctx.logger.info(`Found ${candidates.length} candidates in recommend list`);
172
- return { success: true, candidates, total: candidates.length };
173
- },
174
- });
175
- //# sourceMappingURL=zhipin-get-candidate-list.js.map
1
+ import{defineTool as t}from"@roll-agent/sdk";import{z as e}from"zod";import{getContextManager as n}from"../runtime-holder.js";const r=e.object({index:e.number(),candidateId:e.string(),name:e.string(),age:e.string(),experience:e.string(),education:e.string(),workStatus:e.string(),company:e.string(),currentPosition:e.string(),expectedLocation:e.string(),expectedPosition:e.string(),expectedSalary:e.string(),tags:e.array(e.string()),buttonText:e.string()}),o=e.object({success:e.boolean(),candidates:e.array(r),total:e.number(),error:e.string().optional()});export const zhipinGetCandidateList=t({name:"zhipin_get_candidate_list",description:"获取推荐列表页的候选人卡片信息",input:e.object({maxResults:e.number().optional().describe("最多返回条数")}),output:o,execute:async(t,e)=>{e.logger.info("Getting candidate list from recommend page");const r=n(),o=await r.getPage("zhipin"),i=o.frame("recommendFrame")??o.frames().find(t=>t.url().includes("recommend"))??o;try{await i.waitForSelector("[data-geek], .geek-item",{timeout:1e4})}catch{return{success:!1,candidates:[],total:0,error:"推荐列表未加载"}}const a=await i.evaluate(t=>{let e=Array.from(document.querySelectorAll(".candidate-card-wrap"));0===e.length&&(e=Array.from(document.querySelectorAll("[data-geek], .geek-item")));const n=t??e.length,r=[];return e.forEach((t,e)=>{if(e>=n)return;const o=t.getAttribute("data-geek")??t.querySelector("[data-geek]")?.getAttribute("data-geek")??"",i=t.querySelector(".name")?.textContent?.trim()??"";let a="",c="",s="",l="";const d=t.querySelector(".base-info.join-text-wrap, .base-info");if(d){const t=[];if(d.querySelectorAll(":scope > *").forEach(e=>{const n=e.textContent?.trim();n&&t.push(n)}),t.length<=1&&(t.length=0,d.childNodes.forEach(e=>{if(e.nodeType===Node.TEXT_NODE){const n=e.textContent?.trim();n&&t.push(n)}})),t.length<=1){const e=d.textContent?.trim()??"";t.length=0,e.split(/[丨·|]/).forEach(e=>{const n=e.trim();n&&t.push(n)})}for(const e of t)!a&&e.includes("岁")?a=e:!c&&(e.includes("年")||e.includes("应届")||e.includes("在校"))?c=e:!s&&/(初中|高中|中专|中技|大专|本科|硕士|博士)/.test(e)?s=e:!l&&/(在职|离职|在校)/.test(e)&&(l=e)}const u=t.querySelector(".timeline-wrap.work-exps .content.join-text-wrap")??t.querySelector(".timeline-wrap.work-exps .content"),m=(u?.textContent?.trim()??"").split("·").map(t=>t.trim()),p=m[0]??"",g=m[1]??"";let x="",f="";const y=t.querySelector(".row-flex:not(.geek-desc)");if(y){const t=y.querySelector(".label"),e=y.querySelector(".content"),n=t?.textContent??"";if((n.includes("期望")||n.includes("最近关注"))&&e){const t=(e.textContent?.trim()??"").split("·").map(t=>t.trim());x=t[0]??"",f=t[1]??""}}if(!x){const e=t.querySelector(".timeline-wrap.expect .content.join-text-wrap")??t.querySelector(".timeline-wrap.expect .content");if(e){const t=(e.textContent?.trim()??"").split("·").map(t=>t.trim());x=t[0]??"",f=t[1]??""}}const h=t.querySelector(".salary-wrap")?.textContent?.trim()??"",w=[];t.querySelectorAll(".tags-wrap .tag-item, .tags-wrap .tag, .tags-wrap span").forEach(t=>{const e=t.textContent?.trim();e&&w.push(e)});const S=t.querySelector("button.btn.btn-greet")?.textContent?.trim()??"";r.push({index:e,candidateId:o,name:i,age:a,experience:c,education:s,workStatus:l,company:p,currentPosition:g,expectedLocation:x,expectedPosition:f,expectedSalary:h,tags:w,buttonText:S})}),r},t.maxResults);return e.logger.info(`Found ${a.length} candidates in recommend list`),{success:!0,candidates:a,total:a.length}}});
@@ -6,4 +6,3 @@ export declare const zhipinGetUsername: import("@roll-agent/sdk").ToolDefinition
6
6
  usedSelector?: string | undefined;
7
7
  usedStrategy?: string | undefined;
8
8
  }>;
9
- //# sourceMappingURL=zhipin-get-username.d.ts.map
@@ -1,58 +1 @@
1
- import { defineTool } from "@roll-agent/sdk";
2
- import { z } from "zod";
3
- import { collectUsernameEvidence, pickBestUsername, selectExistingZhipinPage, } from "../pages/zhipin/username.js";
4
- import { getContextManager } from "../runtime-holder.js";
5
- const OutputSchema = z.object({
6
- success: z.boolean(),
7
- userName: z.string(),
8
- usedSelector: z.string().optional(),
9
- usedStrategy: z.string().optional(),
10
- source: z.string().optional(),
11
- error: z.string().optional(),
12
- });
13
- export const zhipinGetUsername = defineTool({
14
- name: "zhipin_get_username",
15
- description: "获取当前登录的招聘者用户名,仅复用当前 runtime 已跟踪的 BOSS直聘页面。",
16
- input: z.object({}),
17
- output: OutputSchema,
18
- execute: async (_input, ctx) => {
19
- ctx.logger.info("Getting zhipin username");
20
- try {
21
- const ctxManager = getContextManager();
22
- const page = await selectExistingZhipinPage(ctxManager);
23
- if (!page) {
24
- return {
25
- success: false,
26
- userName: "",
27
- error: "未找到当前 runtime 已跟踪的 BOSS直聘页面,请先执行 open_platform,或通过 list_pages + select_page 恢复跟踪。",
28
- };
29
- }
30
- await page.bringToFront().catch(() => { });
31
- const evidence = await collectUsernameEvidence(page);
32
- const result = pickBestUsername(evidence);
33
- if (!result.found) {
34
- return {
35
- success: false,
36
- userName: "",
37
- error: "未找到用户名,请确认当前页面已登录招聘者账号。",
38
- };
39
- }
40
- ctx.logger.info(`Username: ${result.userName} (strategy: ${result.strategy}, source: ${result.source})`);
41
- return {
42
- success: true,
43
- userName: result.userName,
44
- usedSelector: result.strategy === "css-fallback" ? result.source : undefined,
45
- usedStrategy: result.strategy,
46
- source: result.source,
47
- };
48
- }
49
- catch (error) {
50
- return {
51
- success: false,
52
- userName: "",
53
- error: error instanceof Error ? `获取用户名失败:${error.message}` : "获取用户名失败",
54
- };
55
- }
56
- },
57
- });
58
- //# sourceMappingURL=zhipin-get-username.js.map
1
+ import{defineTool as e}from"@roll-agent/sdk";import{z as r}from"zod";import{collectUsernameEvidence as s,pickBestUsername as t,selectExistingZhipinPage as o}from"../pages/zhipin/username.js";import{getContextManager as n}from"../runtime-holder.js";const a=r.object({success:r.boolean(),userName:r.string(),usedSelector:r.string().optional(),usedStrategy:r.string().optional(),source:r.string().optional(),error:r.string().optional()});export const zhipinGetUsername=e({name:"zhipin_get_username",description:"获取当前登录的招聘者用户名,仅复用当前 runtime 已跟踪的 BOSS直聘页面。",input:r.object({}),output:a,execute:async(e,r)=>{r.logger.info("Getting zhipin username");try{const e=n(),a=await o(e);if(!a)return{success:!1,userName:"",error:"未找到当前 runtime 已跟踪的 BOSS直聘页面,请先执行 open_platform,或通过 list_pages + select_page 恢复跟踪。"};await a.bringToFront().catch(()=>{});const i=await s(a),u=t(i);return u.found?(r.logger.info(`Username: ${u.userName} (strategy: ${u.strategy}, source: ${u.source})`),{success:!0,userName:u.userName,usedSelector:"css-fallback"===u.strategy?u.source:void 0,usedStrategy:u.strategy,source:u.source}):{success:!1,userName:"",error:"未找到用户名,请确认当前页面已登录招聘者账号。"}}catch(e){return{success:!1,userName:"",error:e instanceof Error?`获取用户名失败:${e.message}`:"获取用户名失败"}}}});
@@ -12,4 +12,3 @@ export declare const zhipinLocateResumeCanvas: import("@roll-agent/sdk").ToolDef
12
12
  height: number;
13
13
  } | undefined;
14
14
  }>;
15
- //# sourceMappingURL=zhipin-locate-resume-canvas.d.ts.map
@@ -1,90 +1 @@
1
- import { defineTool } from "@roll-agent/sdk";
2
- import { z } from "zod";
3
- import { getContextManager } from "../runtime-holder.js";
4
- const CanvasPositionSchema = z.object({
5
- x: z.number(),
6
- y: z.number(),
7
- width: z.number(),
8
- height: z.number(),
9
- });
10
- const OutputSchema = z.object({
11
- success: z.boolean(),
12
- screenshotArea: CanvasPositionSchema.optional(),
13
- canvasInfo: z.object({ width: z.number(), height: z.number() }).optional(),
14
- error: z.string().optional(),
15
- });
16
- export const zhipinLocateResumeCanvas = defineTool({
17
- name: "zhipin_locate_resume_canvas",
18
- description: "定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",
19
- input: z.object({}),
20
- output: OutputSchema,
21
- execute: async (_input, ctx) => {
22
- ctx.logger.info("Locating resume canvas in nested iframes");
23
- const ctxManager = getContextManager();
24
- const page = await ctxManager.getPage("zhipin");
25
- try {
26
- const recommendFrame = page.frame("recommendFrame") ?? page.frames().find((f) => f.url().includes("recommend"));
27
- if (!recommendFrame)
28
- return { success: false, error: "未找到推荐页 iframe" };
29
- const resumeFrameHandle = await recommendFrame.$('iframe[src*="c-resume"]');
30
- if (!resumeFrameHandle)
31
- return { success: false, error: "未找到简历 iframe" };
32
- const resumeFrame = await resumeFrameHandle.contentFrame();
33
- if (!resumeFrame)
34
- return { success: false, error: "无法访问简历 iframe 内容" };
35
- try {
36
- await resumeFrame.waitForSelector("canvas#resume, div#resume canvas", { timeout: 5_000 });
37
- }
38
- catch {
39
- return { success: false, error: "简历 canvas 未加载" };
40
- }
41
- const canvasInfo = await resumeFrame.evaluate(() => {
42
- const canvas = document.querySelector("canvas#resume, div#resume canvas");
43
- if (!canvas)
44
- return null;
45
- const rect = canvas.getBoundingClientRect();
46
- return {
47
- width: canvas.width,
48
- height: canvas.height,
49
- clientWidth: rect.width,
50
- clientHeight: rect.height,
51
- x: rect.x,
52
- y: rect.y,
53
- };
54
- });
55
- if (!canvasInfo)
56
- return { success: false, error: "无法获取 canvas 信息" };
57
- const recommendFrameRect = await page.evaluate(() => {
58
- const iframe = document.querySelector("#recommendFrame");
59
- if (!iframe)
60
- return null;
61
- const rect = iframe.getBoundingClientRect();
62
- return { x: rect.x, y: rect.y };
63
- });
64
- const resumeFrameRect = await recommendFrame.evaluate(() => {
65
- const iframe = document.querySelector('iframe[src*="c-resume"]');
66
- if (!iframe)
67
- return null;
68
- const rect = iframe.getBoundingClientRect();
69
- return { x: rect.x, y: rect.y };
70
- });
71
- const offsetX = (recommendFrameRect?.x ?? 0) + (resumeFrameRect?.x ?? 0);
72
- const offsetY = (recommendFrameRect?.y ?? 0) + (resumeFrameRect?.y ?? 0);
73
- ctx.logger.info(`Canvas located at (${offsetX + canvasInfo.x}, ${offsetY + canvasInfo.y})`);
74
- return {
75
- success: true,
76
- screenshotArea: {
77
- x: Math.round(offsetX + canvasInfo.x),
78
- y: Math.round(offsetY + canvasInfo.y),
79
- width: Math.round(canvasInfo.clientWidth),
80
- height: Math.round(canvasInfo.clientHeight),
81
- },
82
- canvasInfo: { width: canvasInfo.width, height: canvasInfo.height },
83
- };
84
- }
85
- catch (err) {
86
- return { success: false, error: err instanceof Error ? err.message : String(err) };
87
- }
88
- },
89
- });
90
- //# sourceMappingURL=zhipin-locate-resume-canvas.js.map
1
+ import{defineTool as e}from"@roll-agent/sdk";import{z as t}from"zod";import{getContextManager as r}from"../runtime-holder.js";const n=t.object({x:t.number(),y:t.number(),width:t.number(),height:t.number()}),a=t.object({success:t.boolean(),screenshotArea:n.optional(),canvasInfo:t.object({width:t.number(),height:t.number()}).optional(),error:t.string().optional()});export const zhipinLocateResumeCanvas=e({name:"zhipin_locate_resume_canvas",description:"定位简历详情中嵌套 iframe 内的 canvas 元素坐标(用于截图)",input:t.object({}),output:a,execute:async(e,t)=>{t.logger.info("Locating resume canvas in nested iframes");const n=r(),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 r=await e.$('iframe[src*="c-resume"]');if(!r)return{success:!1,error:"未找到简历 iframe"};const n=await r.contentFrame();if(!n)return{success:!1,error:"无法访问简历 iframe 内容"};try{await n.waitForSelector("canvas#resume, div#resume canvas",{timeout:5e3})}catch{return{success:!1,error:"简历 canvas 未加载"}}const c=await n.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(!c)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}}),o=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}}),s=(i?.x??0)+(o?.x??0),u=(i?.y??0)+(o?.y??0);return t.logger.info(`Canvas located at (${s+c.x}, ${u+c.y})`),{success:!0,screenshotArea:{x:Math.round(s+c.x),y:Math.round(u+c.y),width:Math.round(c.clientWidth),height:Math.round(c.clientHeight)},canvasInfo:{width:c.width,height:c.height}}}catch(e){return{success:!1,error:e instanceof Error?e.message:String(e)}}}});
@@ -12,4 +12,3 @@ export declare const zhipinOpenChat: import("@roll-agent/sdk").ToolDefinition<{
12
12
  candidateName: string;
13
13
  error?: string | undefined;
14
14
  }>;
15
- //# sourceMappingURL=zhipin-open-chat.d.ts.map