@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,459 @@
1
+ import crypto from "node:crypto";
2
+ import {
3
+ getNodeBox,
4
+ scrollNodeIntoView,
5
+ sleep
6
+ } from "../browser/index.js";
7
+
8
+ function nowIso() {
9
+ return new Date().toISOString();
10
+ }
11
+
12
+ function normalizeText(value) {
13
+ return String(value || "").replace(/\s+/g, " ").trim();
14
+ }
15
+
16
+ function shortHash(value) {
17
+ return crypto.createHash("sha256").update(String(value || "")).digest("hex").slice(0, 16);
18
+ }
19
+
20
+ function pickAttribute(attributes = {}, names = []) {
21
+ for (const name of names) {
22
+ const value = normalizeText(attributes[name]);
23
+ if (value) return value;
24
+ }
25
+ return "";
26
+ }
27
+
28
+ export function candidateKeyFromProfile(candidate = {}, {
29
+ nodeId = null,
30
+ attributes = candidate.attributes || candidate.metadata?.attributes || {}
31
+ } = {}) {
32
+ const text = normalizeText(candidate.text?.raw || candidate.text || "");
33
+ const textSuffix = text ? `:text:${shortHash(text.slice(0, 1000))}` : "";
34
+ const id = normalizeText(candidate.id);
35
+ const stableAttrKey = pickAttribute(attributes, [
36
+ "data-geek",
37
+ "data-geekid",
38
+ "data-expect",
39
+ "data-uid",
40
+ "data-securityid",
41
+ "encryptgeekid",
42
+ "geekid",
43
+ "expect",
44
+ "uid",
45
+ "securityid"
46
+ ]);
47
+ if (id && stableAttrKey && id === stableAttrKey) return `${candidate.domain || "candidate"}:id:${id}`;
48
+ if (id && !stableAttrKey) return `${candidate.domain || "candidate"}:id:${id}${textSuffix}`;
49
+ if (id) return `${candidate.domain || "candidate"}:id:${id}${textSuffix}`;
50
+
51
+ const attrKey = pickAttribute(attributes, [
52
+ "data-geek",
53
+ "data-geekid",
54
+ "data-expect",
55
+ "data-jid",
56
+ "data-id",
57
+ "data-uid",
58
+ "data-securityid",
59
+ "encryptgeekid",
60
+ "href",
61
+ "key",
62
+ "id"
63
+ ]);
64
+ if (attrKey) return `${candidate.domain || "candidate"}:attr:${attrKey}${textSuffix}`;
65
+
66
+ const identity = candidate.identity || {};
67
+ const identityKey = [
68
+ identity.name,
69
+ identity.current_company,
70
+ identity.current_position,
71
+ identity.school,
72
+ identity.major,
73
+ identity.degree,
74
+ identity.age,
75
+ identity.gender
76
+ ].map(normalizeText).filter(Boolean).join("|");
77
+ if (identityKey) return `${candidate.domain || "candidate"}:identity:${shortHash(identityKey)}`;
78
+
79
+ if (text) return `${candidate.domain || "candidate"}:text:${shortHash(text.slice(0, 1000))}`;
80
+
81
+ return `${candidate.domain || "candidate"}:node:${nodeId || "unknown"}`;
82
+ }
83
+
84
+ export function createInfiniteListState({
85
+ domain = "unknown",
86
+ listName = "candidate-list"
87
+ } = {}) {
88
+ return {
89
+ schema_version: 1,
90
+ domain,
91
+ list_name: listName,
92
+ created_at: nowIso(),
93
+ seen_keys: new Set(),
94
+ queued_keys: new Set(),
95
+ processed_keys: new Set(),
96
+ skipped_duplicate_count: 0,
97
+ read_error_count: 0,
98
+ scroll_count: 0,
99
+ stable_signature_count: 0,
100
+ last_visible_signature: "",
101
+ last_result: null,
102
+ ledger: []
103
+ };
104
+ }
105
+
106
+ export function compactInfiniteListState(state = {}) {
107
+ return {
108
+ domain: state.domain || "unknown",
109
+ list_name: state.list_name || "candidate-list",
110
+ seen_count: state.seen_keys?.size || 0,
111
+ queued_count: state.queued_keys?.size || 0,
112
+ processed_count: state.processed_keys?.size || 0,
113
+ skipped_duplicate_count: state.skipped_duplicate_count || 0,
114
+ read_error_count: state.read_error_count || 0,
115
+ scroll_count: state.scroll_count || 0,
116
+ stable_signature_count: state.stable_signature_count || 0,
117
+ last_visible_signature: state.last_visible_signature || "",
118
+ last_result: state.last_result || null
119
+ };
120
+ }
121
+
122
+ export function markInfiniteListCandidateProcessed(state, key, {
123
+ status = "processed",
124
+ metadata = {}
125
+ } = {}) {
126
+ if (!state || !key) return compactInfiniteListState(state);
127
+ state.queued_keys?.delete(key);
128
+ state.processed_keys?.add(key);
129
+ state.ledger?.push({
130
+ at: nowIso(),
131
+ event: "candidate_processed",
132
+ key,
133
+ status,
134
+ metadata
135
+ });
136
+ return compactInfiniteListState(state);
137
+ }
138
+
139
+ export function markInfiniteListCandidateSkipped(state, key, {
140
+ reason = "skipped",
141
+ metadata = {}
142
+ } = {}) {
143
+ if (!state || !key) return compactInfiniteListState(state);
144
+ state.queued_keys?.delete(key);
145
+ state.ledger?.push({
146
+ at: nowIso(),
147
+ event: "candidate_skipped",
148
+ key,
149
+ reason,
150
+ metadata
151
+ });
152
+ return compactInfiniteListState(state);
153
+ }
154
+
155
+ export function resetInfiniteListForRefreshRound(state, {
156
+ reason = "refresh_round",
157
+ round = 0,
158
+ method = "",
159
+ metadata = {}
160
+ } = {}) {
161
+ if (!state) return compactInfiniteListState(state);
162
+ state.queued_keys?.clear();
163
+ state.stable_signature_count = 0;
164
+ state.last_visible_signature = "";
165
+ state.last_result = null;
166
+ state.ledger?.push({
167
+ at: nowIso(),
168
+ event: "refresh_round_started",
169
+ reason,
170
+ round,
171
+ method,
172
+ metadata
173
+ });
174
+ return compactInfiniteListState(state);
175
+ }
176
+
177
+ export async function readVisibleInfiniteListItems({
178
+ nodeIds = [],
179
+ readCandidate,
180
+ keyForCandidate = candidateKeyFromProfile,
181
+ state = null
182
+ } = {}) {
183
+ if (typeof readCandidate !== "function") {
184
+ throw new Error("readVisibleInfiniteListItems requires readCandidate");
185
+ }
186
+ const items = [];
187
+ for (let visibleIndex = 0; visibleIndex < nodeIds.length; visibleIndex += 1) {
188
+ const nodeId = nodeIds[visibleIndex];
189
+ let candidate;
190
+ try {
191
+ candidate = await readCandidate(nodeId, { visibleIndex });
192
+ } catch (error) {
193
+ if (state) {
194
+ state.read_error_count = (state.read_error_count || 0) + 1;
195
+ state.ledger?.push({
196
+ at: nowIso(),
197
+ event: "candidate_read_error",
198
+ node_id: nodeId,
199
+ visible_index: visibleIndex,
200
+ error: error?.message || String(error)
201
+ });
202
+ }
203
+ continue;
204
+ }
205
+ const key = keyForCandidate(candidate, {
206
+ nodeId,
207
+ visibleIndex,
208
+ attributes: candidate?.attributes || candidate?.metadata?.attributes || {}
209
+ });
210
+ items.push({
211
+ key,
212
+ node_id: nodeId,
213
+ visible_index: visibleIndex,
214
+ candidate
215
+ });
216
+ }
217
+ return items;
218
+ }
219
+
220
+ export function updateInfiniteListVisibleSignature(state, items = []) {
221
+ const signature = items.map((item) => item.key).filter(Boolean).join("|");
222
+ const unchanged = Boolean(signature) && signature === state.last_visible_signature;
223
+ state.stable_signature_count = unchanged ? (state.stable_signature_count || 0) + 1 : 0;
224
+ state.last_visible_signature = signature;
225
+ return {
226
+ signature,
227
+ unchanged,
228
+ stable_signature_count: state.stable_signature_count
229
+ };
230
+ }
231
+
232
+ export function firstUnseenInfiniteListItem(state, items = []) {
233
+ for (const item of items) {
234
+ if (!item.key) continue;
235
+ if (state.processed_keys.has(item.key) || state.queued_keys.has(item.key)) {
236
+ state.skipped_duplicate_count += 1;
237
+ continue;
238
+ }
239
+ state.seen_keys.add(item.key);
240
+ state.queued_keys.add(item.key);
241
+ state.ledger.push({
242
+ at: nowIso(),
243
+ event: "candidate_queued",
244
+ key: item.key,
245
+ node_id: item.node_id,
246
+ visible_index: item.visible_index
247
+ });
248
+ return item;
249
+ }
250
+ return null;
251
+ }
252
+
253
+ export async function scrollInfiniteListByVisibleItems(client, items = [], {
254
+ wheelDeltaY = 850,
255
+ settleMs = 1200,
256
+ fallbackPoint = null
257
+ } = {}) {
258
+ const candidates = items.filter((item) => item?.node_id);
259
+ if (!candidates.length) {
260
+ return {
261
+ ok: false,
262
+ reason: "no_visible_items"
263
+ };
264
+ }
265
+
266
+ const errors = [];
267
+ for (const anchor of candidates.slice().reverse()) {
268
+ try {
269
+ await scrollNodeIntoView(client, anchor.node_id);
270
+ await sleep(150);
271
+ const box = await getNodeBox(client, anchor.node_id);
272
+ const x = box.center.x;
273
+ const y = box.center.y;
274
+ await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
275
+ await client.Input.dispatchMouseEvent({
276
+ type: "mouseWheel",
277
+ x,
278
+ y,
279
+ deltaX: 0,
280
+ deltaY: Math.max(1, Number(wheelDeltaY) || 850)
281
+ });
282
+ if (settleMs > 0) await sleep(settleMs);
283
+ return {
284
+ ok: true,
285
+ anchor_key: anchor.key,
286
+ anchor_node_id: anchor.node_id,
287
+ point: { x, y },
288
+ wheel_delta_y: Math.max(1, Number(wheelDeltaY) || 850),
289
+ settle_ms: settleMs,
290
+ skipped_stale_anchor_count: errors.length
291
+ };
292
+ } catch (error) {
293
+ errors.push({
294
+ anchor_key: anchor.key,
295
+ anchor_node_id: anchor.node_id,
296
+ error: error?.message || String(error)
297
+ });
298
+ }
299
+ }
300
+
301
+ if (fallbackPoint && Number.isFinite(Number(fallbackPoint.x)) && Number.isFinite(Number(fallbackPoint.y))) {
302
+ const x = Number(fallbackPoint.x);
303
+ const y = Number(fallbackPoint.y);
304
+ await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y, button: "none" });
305
+ await client.Input.dispatchMouseEvent({
306
+ type: "mouseWheel",
307
+ x,
308
+ y,
309
+ deltaX: 0,
310
+ deltaY: Math.max(1, Number(wheelDeltaY) || 850)
311
+ });
312
+ if (settleMs > 0) await sleep(settleMs);
313
+ return {
314
+ ok: true,
315
+ mode: "fallback_point",
316
+ point: { x, y },
317
+ wheel_delta_y: Math.max(1, Number(wheelDeltaY) || 850),
318
+ settle_ms: settleMs,
319
+ skipped_stale_anchor_count: errors.length,
320
+ stale_anchor_errors: errors
321
+ };
322
+ }
323
+
324
+ return {
325
+ ok: false,
326
+ reason: "scroll_anchor_unavailable",
327
+ errors
328
+ };
329
+ }
330
+
331
+ export async function getNextInfiniteListCandidate({
332
+ client,
333
+ state,
334
+ findNodeIds,
335
+ readCandidate,
336
+ keyForCandidate = candidateKeyFromProfile,
337
+ maxScrolls = 20,
338
+ stableSignatureLimit = 2,
339
+ wheelDeltaY = 850,
340
+ settleMs = 1200,
341
+ fallbackPoint = null
342
+ } = {}) {
343
+ if (!client) throw new Error("getNextInfiniteListCandidate requires client");
344
+ if (!state) throw new Error("getNextInfiniteListCandidate requires state");
345
+ if (typeof findNodeIds !== "function") throw new Error("getNextInfiniteListCandidate requires findNodeIds");
346
+ if (typeof readCandidate !== "function") throw new Error("getNextInfiniteListCandidate requires readCandidate");
347
+
348
+ const attempts = [];
349
+ const maxAttempts = Math.max(0, Number(maxScrolls) || 0);
350
+ for (let scrollAttempt = 0; scrollAttempt <= maxAttempts; scrollAttempt += 1) {
351
+ const nodeIds = await findNodeIds();
352
+ const items = await readVisibleInfiniteListItems({
353
+ nodeIds,
354
+ readCandidate,
355
+ keyForCandidate,
356
+ state
357
+ });
358
+ const signature = updateInfiniteListVisibleSignature(state, items);
359
+ const next = firstUnseenInfiniteListItem(state, items);
360
+ attempts.push({
361
+ scroll_attempt: scrollAttempt,
362
+ visible_count: items.length,
363
+ signature: signature.signature,
364
+ stable_signature_count: signature.stable_signature_count,
365
+ found_next: Boolean(next)
366
+ });
367
+
368
+ if (next) {
369
+ state.stable_signature_count = 0;
370
+ const result = {
371
+ ok: true,
372
+ end_reached: false,
373
+ item: next,
374
+ attempts,
375
+ state: compactInfiniteListState(state)
376
+ };
377
+ state.last_result = {
378
+ at: nowIso(),
379
+ ok: true,
380
+ key: next.key,
381
+ visible_index: next.visible_index
382
+ };
383
+ return result;
384
+ }
385
+
386
+ if (!items.length) {
387
+ const result = {
388
+ ok: false,
389
+ end_reached: true,
390
+ reason: "empty_visible_list",
391
+ attempts,
392
+ state: compactInfiniteListState(state)
393
+ };
394
+ state.last_result = {
395
+ at: nowIso(),
396
+ ok: false,
397
+ end_reached: true,
398
+ reason: result.reason
399
+ };
400
+ return result;
401
+ }
402
+
403
+ if (signature.stable_signature_count >= Math.max(1, Number(stableSignatureLimit) || 1)) {
404
+ const result = {
405
+ ok: false,
406
+ end_reached: true,
407
+ reason: "stable_visible_signature",
408
+ attempts,
409
+ state: compactInfiniteListState(state)
410
+ };
411
+ state.last_result = {
412
+ at: nowIso(),
413
+ ok: false,
414
+ end_reached: true,
415
+ reason: result.reason
416
+ };
417
+ return result;
418
+ }
419
+
420
+ const scrollResult = await scrollInfiniteListByVisibleItems(client, items, {
421
+ wheelDeltaY,
422
+ settleMs,
423
+ fallbackPoint
424
+ });
425
+ state.scroll_count += scrollResult.ok ? 1 : 0;
426
+ attempts[attempts.length - 1].scroll_result = scrollResult;
427
+ if (!scrollResult.ok) {
428
+ const result = {
429
+ ok: false,
430
+ end_reached: true,
431
+ reason: scrollResult.reason || "scroll_failed",
432
+ attempts,
433
+ state: compactInfiniteListState(state)
434
+ };
435
+ state.last_result = {
436
+ at: nowIso(),
437
+ ok: false,
438
+ end_reached: true,
439
+ reason: result.reason
440
+ };
441
+ return result;
442
+ }
443
+ }
444
+
445
+ const result = {
446
+ ok: false,
447
+ end_reached: false,
448
+ reason: "max_scrolls_exhausted",
449
+ attempts,
450
+ state: compactInfiniteListState(state)
451
+ };
452
+ state.last_result = {
453
+ at: nowIso(),
454
+ ok: false,
455
+ end_reached: false,
456
+ reason: result.reason
457
+ };
458
+ return result;
459
+ }