@reconcrap/boss-recommend-mcp 1.3.38 → 2.0.0

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 (85) hide show
  1. package/README.md +53 -33
  2. package/package.json +61 -9
  3. package/skills/boss-recommend-pipeline/SKILL.md +4 -0
  4. package/src/chat-mcp.js +1333 -0
  5. package/src/chat-runtime-config.js +559 -0
  6. package/src/cli.js +1095 -196
  7. package/src/core/browser/index.js +378 -0
  8. package/src/core/capture/index.js +298 -0
  9. package/src/core/cv-acquisition/index.js +219 -0
  10. package/src/core/greet-quota/index.js +54 -0
  11. package/src/core/infinite-list/index.js +459 -0
  12. package/src/core/reporting/legacy-csv.js +332 -0
  13. package/src/core/run/index.js +286 -0
  14. package/src/core/screening/index.js +1166 -0
  15. package/src/core/self-heal/index.js +848 -0
  16. package/src/domains/chat/cards.js +129 -0
  17. package/src/domains/chat/constants.js +183 -0
  18. package/src/domains/chat/detail.js +1369 -0
  19. package/src/domains/chat/index.js +7 -0
  20. package/src/domains/chat/jobs.js +334 -0
  21. package/src/domains/chat/page-guard.js +88 -0
  22. package/src/domains/chat/roots.js +56 -0
  23. package/src/domains/chat/run-service.js +1101 -0
  24. package/src/domains/recommend/actions.js +457 -0
  25. package/src/domains/recommend/cards.js +228 -0
  26. package/src/domains/recommend/constants.js +141 -0
  27. package/src/domains/recommend/detail.js +341 -0
  28. package/src/domains/recommend/filters.js +581 -0
  29. package/src/domains/recommend/index.js +10 -0
  30. package/src/domains/recommend/jobs.js +232 -0
  31. package/src/domains/recommend/refresh.js +204 -0
  32. package/src/domains/recommend/roots.js +78 -0
  33. package/src/domains/recommend/run-service.js +903 -0
  34. package/src/domains/recommend/scopes.js +245 -0
  35. package/src/domains/recruit/actions.js +277 -0
  36. package/src/domains/recruit/cards.js +67 -0
  37. package/src/domains/recruit/constants.js +130 -0
  38. package/src/domains/recruit/detail.js +414 -0
  39. package/src/domains/recruit/index.js +9 -0
  40. package/src/domains/recruit/instruction-parser.js +451 -0
  41. package/src/domains/recruit/refresh.js +40 -0
  42. package/src/domains/recruit/roots.js +68 -0
  43. package/src/domains/recruit/run-service.js +580 -0
  44. package/src/domains/recruit/search.js +1149 -0
  45. package/src/index.js +578 -419
  46. package/src/recommend-mcp.js +1257 -0
  47. package/src/recruit-mcp.js +1035 -0
  48. package/src/adapters.js +0 -3079
  49. package/src/boss-chat.js +0 -1037
  50. package/src/pipeline.js +0 -2249
  51. package/src/recommend-healing-config.js +0 -131
  52. package/src/recommend-healing-rules.json +0 -261
  53. package/src/self-heal.js +0 -2237
  54. package/src/test-adapters-runtime.js +0 -628
  55. package/src/test-boss-chat.js +0 -3196
  56. package/src/test-index-async.js +0 -498
  57. package/src/test-parser.js +0 -742
  58. package/src/test-pipeline.js +0 -2703
  59. package/src/test-run-state.js +0 -152
  60. package/src/test-self-heal.js +0 -224
  61. package/vendor/boss-chat-cli/README.md +0 -134
  62. package/vendor/boss-chat-cli/package.json +0 -53
  63. package/vendor/boss-chat-cli/src/app.js +0 -1501
  64. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  65. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  66. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  67. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  68. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  69. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  70. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  71. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  72. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  73. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  74. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  75. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  76. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  77. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  78. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  79. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  80. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -6927
  81. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  82. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  83. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2294
  84. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  85. package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
@@ -0,0 +1,141 @@
1
+ export const RECOMMEND_TARGET_URL = "https://www.zhipin.com/web/chat/recommend";
2
+
3
+ export const RECOMMEND_PAGE_SCOPE_DEFAULT = "recommend";
4
+
5
+ export const RECOMMEND_PAGE_SCOPE_STATUS = Object.freeze({
6
+ recommend: "0",
7
+ latest: "1",
8
+ featured: "3"
9
+ });
10
+
11
+ export const RECOMMEND_PAGE_SCOPE_LABELS = Object.freeze({
12
+ recommend: "推荐",
13
+ latest: "最新",
14
+ featured: "精选"
15
+ });
16
+
17
+ export const RECOMMEND_IFRAME_SELECTORS = Object.freeze([
18
+ 'iframe[name="recommendFrame"]',
19
+ 'iframe[src*="/web/frame/recommend/"]',
20
+ "iframe"
21
+ ]);
22
+
23
+ export const RECOMMEND_PAGE_SCOPE_TAB_SELECTOR = [
24
+ ".tab-list .tab-item[data-status]",
25
+ ".tab-wrap .tab-item[data-status]",
26
+ ".tab-item[data-status]",
27
+ "[data-status]"
28
+ ].join(", ");
29
+
30
+ export const RECOMMEND_FILTER_SELECTORS = Object.freeze({
31
+ trigger: ".filter-label-wrap",
32
+ panel: ".filter-panel",
33
+ groups: Object.freeze({
34
+ recentNotView: ".filter-panel .check-box.recentNotView",
35
+ degree: ".filter-panel .check-box.degree",
36
+ gender: ".filter-panel .check-box.gender",
37
+ school: ".filter-panel .check-box.school"
38
+ }),
39
+ option: ".default.option, .options .option, .option",
40
+ activeOption: ".default.option.active, .options .option.active, .option.active",
41
+ confirmButton: ".filter-panel .btn, .filter-panel button",
42
+ checkBox: ".filter-panel .check-box"
43
+ });
44
+
45
+ export const RECOMMEND_FILTER_GROUP_ORDER = Object.freeze([
46
+ "recentNotView",
47
+ "degree",
48
+ "gender",
49
+ "school"
50
+ ]);
51
+
52
+ export const RECOMMEND_RECENT_NOT_VIEW_LABEL = "近14天没有";
53
+
54
+ export const RECOMMEND_CARD_SELECTOR = [
55
+ ".candidate-card-wrap .card-inner[data-geek]",
56
+ ".candidate-card-wrap [data-geek]",
57
+ "li.geek-info-card a[data-geekid]",
58
+ "a[data-geekid]"
59
+ ].join(", ");
60
+
61
+ export const RECOMMEND_END_REFRESH_SELECTOR = [
62
+ ".btn",
63
+ "button",
64
+ '[role="button"]',
65
+ '[class*="refresh"]',
66
+ '[ka*="refresh"]',
67
+ "a"
68
+ ].join(", ");
69
+
70
+ export const DETAIL_POPUP_SELECTORS = Object.freeze([
71
+ ".dialog-wrap.active",
72
+ ".boss-popup__wrapper",
73
+ ".boss-popup_wrapper",
74
+ ".boss-dialog_wrapper",
75
+ ".boss-dialog",
76
+ ".resume-item-detail",
77
+ ".geek-detail-modal",
78
+ '[class*="popup"][class*="wrapper"]',
79
+ '[class*="dialog"][class*="wrapper"]'
80
+ ]);
81
+
82
+ export const DETAIL_RESUME_IFRAME_SELECTORS = Object.freeze([
83
+ 'iframe[src*="/web/frame/c-resume/"]',
84
+ 'iframe[name*="resume"]'
85
+ ]);
86
+
87
+ export const DETAIL_CLOSE_SELECTORS = Object.freeze([
88
+ ".boss-popup__close",
89
+ ".popup-close",
90
+ ".modal-close",
91
+ ".dialog-close",
92
+ ".close-btn",
93
+ 'button[aria-label*="关闭"]',
94
+ 'button[title*="关闭"]',
95
+ ".icon-close",
96
+ '[aria-label*="关闭"]',
97
+ '[title*="关闭"]',
98
+ '[class*="close"]'
99
+ ]);
100
+
101
+ export const DETAIL_NETWORK_PATTERNS = Object.freeze([
102
+ /\/wapi\/zpjob\/view\/geek\/info\b/i,
103
+ /\/wapi\/zpitem\/web\/boss\/[^?#]*\/geek\/info\b/i,
104
+ /\/boss\/[^?#]*\/geek\/info\b/i,
105
+ /\/geek\/info\b/i,
106
+ /\/web\/frame\/c-resume\//i,
107
+ /resume/i
108
+ ]);
109
+
110
+ export const FAVORITE_BUTTON_SELECTORS = Object.freeze([
111
+ ".like-icon-and-text",
112
+ ".resume-footer.item-operate [class*=\"collect\"]",
113
+ ".resume-footer.item-operate [class*=\"favorite\"]",
114
+ ".resume-footer.item-operate [class*=\"like\"]",
115
+ ".resume-footer-wrap [class*=\"collect\"]",
116
+ ".resume-footer-wrap [class*=\"favorite\"]",
117
+ ".resume-footer-wrap [class*=\"like\"]",
118
+ ".resume-footer [class*=\"collect\"]",
119
+ ".resume-footer [class*=\"favorite\"]",
120
+ ".resume-footer [class*=\"like\"]",
121
+ ".resume-footer.item-operate button",
122
+ ".resume-footer.item-operate .btn",
123
+ ".resume-footer.item-operate span",
124
+ ".resume-footer-wrap button",
125
+ ".resume-footer-wrap .btn",
126
+ ".resume-footer-wrap span",
127
+ ".resume-footer button",
128
+ ".resume-footer .btn",
129
+ ".resume-footer span"
130
+ ]);
131
+
132
+ export const GREET_BUTTON_RECOMMEND_SELECTORS = Object.freeze([
133
+ "button.btn-v2.btn-sure-v2.btn-greet",
134
+ ".resume-footer.item-operate button.btn-v2",
135
+ ".resume-footer-wrap button.btn-v2",
136
+ ".resume-footer.item-operate button",
137
+ ".resume-footer-wrap button",
138
+ ".resume-footer button",
139
+ "button[class*=\"greet\"]",
140
+ "button[class*=\"sure\"]"
141
+ ]);
@@ -0,0 +1,341 @@
1
+ import {
2
+ clickNodeCenter,
3
+ clickPoint,
4
+ getFrameDocumentNodeId,
5
+ getNodeBox,
6
+ getOuterHTML,
7
+ pressKey,
8
+ querySelectorAll,
9
+ sleep
10
+ } from "../../core/browser/index.js";
11
+ import {
12
+ buildScreeningCandidateFromDetail,
13
+ htmlToText
14
+ } from "../../core/screening/index.js";
15
+ import {
16
+ DETAIL_CLOSE_SELECTORS,
17
+ DETAIL_NETWORK_PATTERNS,
18
+ DETAIL_POPUP_SELECTORS,
19
+ DETAIL_RESUME_IFRAME_SELECTORS
20
+ } from "./constants.js";
21
+ import {
22
+ getRecommendRoots,
23
+ queryFirstAcrossRoots
24
+ } from "./roots.js";
25
+
26
+ export function matchesRecommendDetailNetwork(url) {
27
+ return DETAIL_NETWORK_PATTERNS.some((pattern) => pattern.test(String(url || "")));
28
+ }
29
+
30
+ export function createRecommendDetailNetworkRecorder(client) {
31
+ const events = [];
32
+ client.Network.responseReceived((event) => {
33
+ const url = event?.response?.url || "";
34
+ if (!matchesRecommendDetailNetwork(url)) return;
35
+ events.push({
36
+ requestId: event.requestId,
37
+ url,
38
+ status: event.response?.status,
39
+ mimeType: event.response?.mimeType,
40
+ type: event.type
41
+ });
42
+ });
43
+ if (typeof client.Network.loadingFinished === "function") {
44
+ client.Network.loadingFinished((event) => {
45
+ const found = events.find((item) => item.requestId === event.requestId);
46
+ if (!found) return;
47
+ found.loading_finished = true;
48
+ found.encodedDataLength = event.encodedDataLength;
49
+ });
50
+ }
51
+ if (typeof client.Network.loadingFailed === "function") {
52
+ client.Network.loadingFailed((event) => {
53
+ const found = events.find((item) => item.requestId === event.requestId);
54
+ if (!found) return;
55
+ found.loading_failed = true;
56
+ found.loading_error = event.errorText || event.blockedReason || "Network loading failed";
57
+ });
58
+ }
59
+ return {
60
+ events,
61
+ clear() {
62
+ events.length = 0;
63
+ }
64
+ };
65
+ }
66
+
67
+ export async function waitForRecommendDetailNetworkEvents(recorder, {
68
+ minCount = 1,
69
+ requireLoaded = true,
70
+ timeoutMs = 3500,
71
+ intervalMs = 100
72
+ } = {}) {
73
+ const started = Date.now();
74
+ const events = Array.isArray(recorder) ? recorder : recorder?.events || [];
75
+ let matching = [];
76
+ while (Date.now() - started <= timeoutMs) {
77
+ matching = events.filter((event) => (
78
+ !requireLoaded
79
+ || event.loading_finished === true
80
+ || event.loading_failed === true
81
+ ));
82
+ if (matching.length >= minCount) {
83
+ return {
84
+ ok: true,
85
+ elapsed_ms: Date.now() - started,
86
+ count: matching.length,
87
+ events: matching
88
+ };
89
+ }
90
+ await sleep(intervalMs);
91
+ }
92
+ return {
93
+ ok: false,
94
+ elapsed_ms: Date.now() - started,
95
+ count: matching.length,
96
+ events: matching,
97
+ total_event_count: events.length
98
+ };
99
+ }
100
+
101
+ export async function readRecommendDetailNetworkBodies(client, events = [], {
102
+ limit = 10
103
+ } = {}) {
104
+ const bodies = [];
105
+ for (const event of events.slice(0, limit)) {
106
+ try {
107
+ const body = await client.Network.getResponseBody({ requestId: event.requestId });
108
+ bodies.push({
109
+ ...event,
110
+ body,
111
+ body_length: String(body?.body || "").length
112
+ });
113
+ } catch (error) {
114
+ bodies.push({
115
+ ...event,
116
+ body_error: error?.message || String(error)
117
+ });
118
+ }
119
+ }
120
+ return bodies;
121
+ }
122
+
123
+ export async function waitForRecommendDetail(client, {
124
+ timeoutMs = 10000,
125
+ intervalMs = 250
126
+ } = {}) {
127
+ const started = Date.now();
128
+ let lastState = null;
129
+ while (Date.now() - started <= timeoutMs) {
130
+ const rootState = await getRecommendRoots(client);
131
+ const popup = await queryFirstAcrossRoots(client, rootState.roots, DETAIL_POPUP_SELECTORS);
132
+ const resumeIframe = await queryFirstAcrossRoots(client, rootState.roots, DETAIL_RESUME_IFRAME_SELECTORS);
133
+ lastState = {
134
+ iframe: rootState.iframe,
135
+ roots: rootState.roots,
136
+ popup,
137
+ resumeIframe
138
+ };
139
+ if (popup || resumeIframe) return lastState;
140
+ await sleep(intervalMs);
141
+ }
142
+ return lastState;
143
+ }
144
+
145
+ export async function readRecommendDetailHtml(client, detailState) {
146
+ let popupHTML = "";
147
+ let resumeHTML = "";
148
+ let resumeIframeDocumentNodeId = null;
149
+
150
+ if (detailState?.popup?.node_id) {
151
+ popupHTML = await getOuterHTML(client, detailState.popup.node_id);
152
+ }
153
+
154
+ if (detailState?.resumeIframe?.node_id) {
155
+ resumeIframeDocumentNodeId = await getFrameDocumentNodeId(client, detailState.resumeIframe.node_id);
156
+ resumeHTML = await getOuterHTML(client, resumeIframeDocumentNodeId);
157
+ }
158
+
159
+ return {
160
+ popupHTML,
161
+ resumeHTML,
162
+ resumeIframeDocumentNodeId,
163
+ popupText: htmlToText(popupHTML),
164
+ resumeText: htmlToText(resumeHTML)
165
+ };
166
+ }
167
+
168
+ export async function openRecommendCardDetail(client, cardNodeId, {
169
+ timeoutMs = 12000,
170
+ scrollIntoView = true
171
+ } = {}) {
172
+ const cardBox = await clickNodeCenter(client, cardNodeId, { scrollIntoView });
173
+ const detailState = await waitForRecommendDetail(client, { timeoutMs });
174
+ if (!detailState?.popup && !detailState?.resumeIframe) {
175
+ throw new Error("Candidate detail did not open or no known detail selectors mounted");
176
+ }
177
+
178
+ return {
179
+ card_box: cardBox,
180
+ detail_state: detailState
181
+ };
182
+ }
183
+
184
+ export async function closeRecommendDetail(client, {
185
+ attemptsLimit = 3
186
+ } = {}) {
187
+ const attempts = [];
188
+ for (let index = 0; index < attemptsLimit; index += 1) {
189
+ const existingState = await waitForRecommendDetail(client, { timeoutMs: 500 });
190
+ if (!existingState?.popup && !existingState?.resumeIframe) {
191
+ return {
192
+ closed: true,
193
+ attempts
194
+ };
195
+ }
196
+
197
+ const rootState = await getRecommendRoots(client);
198
+ const closeTarget = await findVisibleCloseTarget(client, rootState.roots, DETAIL_CLOSE_SELECTORS);
199
+ if (closeTarget) {
200
+ try {
201
+ if (closeTarget.center) {
202
+ await clickPoint(client, closeTarget.center.x, closeTarget.center.y);
203
+ } else {
204
+ await clickNodeCenter(client, closeTarget.node_id);
205
+ }
206
+ attempts.push({
207
+ mode: "close-selector",
208
+ selector: closeTarget.selector,
209
+ root: closeTarget.root
210
+ });
211
+ } catch (error) {
212
+ attempts.push({
213
+ mode: "close-selector-error",
214
+ selector: closeTarget.selector,
215
+ root: closeTarget.root,
216
+ error: error?.message || String(error)
217
+ });
218
+ await pressEscape(client);
219
+ attempts.push({ mode: "Escape-after-close-selector-error" });
220
+ }
221
+ await sleep(700);
222
+ } else {
223
+ await pressEscape(client);
224
+ attempts.push({ mode: "Escape" });
225
+ await sleep(700);
226
+ }
227
+
228
+ let state = await waitForRecommendDetail(client, { timeoutMs: 1000 });
229
+ if (!state?.popup && !state?.resumeIframe) {
230
+ return {
231
+ closed: true,
232
+ attempts
233
+ };
234
+ }
235
+
236
+ await pressEscape(client);
237
+ attempts.push({ mode: "Escape-fallback" });
238
+ await sleep(700);
239
+
240
+ state = await waitForRecommendDetail(client, { timeoutMs: 1000 });
241
+ if (!state?.popup && !state?.resumeIframe) {
242
+ return {
243
+ closed: true,
244
+ attempts
245
+ };
246
+ }
247
+ }
248
+
249
+ return {
250
+ closed: false,
251
+ attempts
252
+ };
253
+ }
254
+
255
+ async function findVisibleCloseTarget(client, roots, selectors) {
256
+ let fallback = null;
257
+ for (const root of roots) {
258
+ if (!root?.nodeId) continue;
259
+ for (const selector of selectors) {
260
+ const nodeIds = await querySelectorAll(client, root.nodeId, selector);
261
+ for (const nodeId of nodeIds) {
262
+ const target = {
263
+ root: root.name,
264
+ root_node_id: root.nodeId,
265
+ selector,
266
+ node_id: nodeId
267
+ };
268
+ if (!fallback) fallback = target;
269
+ try {
270
+ const box = await getNodeBox(client, nodeId);
271
+ if (box.rect.width > 2 && box.rect.height > 2) {
272
+ return {
273
+ ...target,
274
+ center: box.center,
275
+ rect: box.rect
276
+ };
277
+ }
278
+ } catch {}
279
+ }
280
+ }
281
+ }
282
+ return fallback;
283
+ }
284
+
285
+ async function pressEscape(client) {
286
+ await pressKey(client, "Escape", {
287
+ code: "Escape",
288
+ windowsVirtualKeyCode: 27,
289
+ nativeVirtualKeyCode: 27
290
+ });
291
+ }
292
+
293
+ export async function extractRecommendDetailCandidate(client, {
294
+ cardCandidate,
295
+ cardNodeId,
296
+ detailState,
297
+ networkEvents = [],
298
+ targetUrl = "",
299
+ closeDetail = true
300
+ } = {}) {
301
+ await sleep(1000);
302
+ const networkBodies = await readRecommendDetailNetworkBodies(client, networkEvents);
303
+ const detailHtml = await readRecommendDetailHtml(client, detailState);
304
+ const detailText = [
305
+ detailHtml.popupText,
306
+ detailHtml.resumeText
307
+ ].filter(Boolean).join("\n\n");
308
+
309
+ const detailCandidateResult = buildScreeningCandidateFromDetail({
310
+ cardCandidate,
311
+ detailText,
312
+ networkBodies,
313
+ metadata: {
314
+ target_url: targetUrl,
315
+ card_node_id: cardNodeId,
316
+ detail_popup_selector: detailState?.popup?.selector || null,
317
+ detail_popup_root: detailState?.popup?.root || null,
318
+ resume_iframe_selector: detailState?.resumeIframe?.selector || null,
319
+ resume_iframe_root: detailState?.resumeIframe?.root || null,
320
+ resume_iframe_document_node_id: detailHtml.resumeIframeDocumentNodeId
321
+ }
322
+ });
323
+
324
+ let closeResult = null;
325
+ if (closeDetail) {
326
+ closeResult = await closeRecommendDetail(client);
327
+ }
328
+
329
+ return {
330
+ candidate: detailCandidateResult.candidate,
331
+ parsed_network_profiles: detailCandidateResult.parsed_network_profiles,
332
+ network_bodies: networkBodies,
333
+ detail: {
334
+ popup_text: detailHtml.popupText,
335
+ resume_text: detailHtml.resumeText,
336
+ popup_html_length: detailHtml.popupHTML.length,
337
+ resume_html_length: detailHtml.resumeHTML.length
338
+ },
339
+ close_result: closeResult
340
+ };
341
+ }