@reconcrap/boss-recommend-mcp 1.3.39 → 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 -7072
  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 -2423
  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,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
+ }