@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.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 (88) hide show
  1. package/README.md +86 -33
  2. package/package.json +62 -9
  3. package/skills/boss-chat/SKILL.md +5 -4
  4. package/skills/boss-recommend-pipeline/SKILL.md +21 -31
  5. package/skills/boss-recruit-pipeline/README.md +17 -0
  6. package/skills/boss-recruit-pipeline/SKILL.md +55 -0
  7. package/src/chat-mcp.js +1333 -0
  8. package/src/chat-runtime-config.js +559 -0
  9. package/src/cli.js +1254 -225
  10. package/src/core/browser/index.js +378 -0
  11. package/src/core/capture/index.js +298 -0
  12. package/src/core/cv-acquisition/index.js +219 -0
  13. package/src/core/greet-quota/index.js +54 -0
  14. package/src/core/infinite-list/index.js +459 -0
  15. package/src/core/reporting/legacy-csv.js +332 -0
  16. package/src/core/run/index.js +286 -0
  17. package/src/core/screening/index.js +1166 -0
  18. package/src/core/self-heal/index.js +848 -0
  19. package/src/domains/chat/cards.js +129 -0
  20. package/src/domains/chat/constants.js +183 -0
  21. package/src/domains/chat/detail.js +1369 -0
  22. package/src/domains/chat/index.js +7 -0
  23. package/src/domains/chat/jobs.js +334 -0
  24. package/src/domains/chat/page-guard.js +88 -0
  25. package/src/domains/chat/roots.js +56 -0
  26. package/src/domains/chat/run-service.js +1101 -0
  27. package/src/domains/recommend/actions.js +457 -0
  28. package/src/domains/recommend/cards.js +228 -0
  29. package/src/domains/recommend/constants.js +141 -0
  30. package/src/domains/recommend/detail.js +341 -0
  31. package/src/domains/recommend/filters.js +581 -0
  32. package/src/domains/recommend/index.js +10 -0
  33. package/src/domains/recommend/jobs.js +232 -0
  34. package/src/domains/recommend/refresh.js +204 -0
  35. package/src/domains/recommend/roots.js +78 -0
  36. package/src/domains/recommend/run-service.js +903 -0
  37. package/src/domains/recommend/scopes.js +245 -0
  38. package/src/domains/recruit/actions.js +277 -0
  39. package/src/domains/recruit/cards.js +66 -0
  40. package/src/domains/recruit/constants.js +130 -0
  41. package/src/domains/recruit/detail.js +414 -0
  42. package/src/domains/recruit/index.js +9 -0
  43. package/src/domains/recruit/instruction-parser.js +451 -0
  44. package/src/domains/recruit/refresh.js +40 -0
  45. package/src/domains/recruit/roots.js +67 -0
  46. package/src/domains/recruit/run-service.js +580 -0
  47. package/src/domains/recruit/search.js +1149 -0
  48. package/src/index.js +578 -419
  49. package/src/recommend-mcp.js +1257 -0
  50. package/src/recruit-mcp.js +1035 -0
  51. package/src/adapters.js +0 -3079
  52. package/src/boss-chat.js +0 -1037
  53. package/src/pipeline.js +0 -2249
  54. package/src/recommend-healing-config.js +0 -131
  55. package/src/recommend-healing-rules.json +0 -261
  56. package/src/self-heal.js +0 -2237
  57. package/src/test-adapters-runtime.js +0 -628
  58. package/src/test-boss-chat.js +0 -3196
  59. package/src/test-index-async.js +0 -498
  60. package/src/test-parser.js +0 -742
  61. package/src/test-pipeline.js +0 -2703
  62. package/src/test-run-state.js +0 -152
  63. package/src/test-self-heal.js +0 -224
  64. package/vendor/boss-chat-cli/README.md +0 -134
  65. package/vendor/boss-chat-cli/package.json +0 -53
  66. package/vendor/boss-chat-cli/src/app.js +0 -1501
  67. package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
  68. package/vendor/boss-chat-cli/src/cli.js +0 -1713
  69. package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
  70. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
  71. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
  72. package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
  73. package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
  74. package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
  75. package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
  76. package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
  77. package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
  78. package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
  79. package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
  80. package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
  81. package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
  82. package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
  83. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
  84. package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
  85. package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
  86. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
  87. package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
  88. 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
+ }