@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
@@ -1,498 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import fs from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { __testables } from "./index.js";
6
-
7
- const {
8
- handleRequest,
9
- runDetachedWorkerForTests,
10
- setSpawnProcessImplForTests,
11
- setRunPipelineImplForTests
12
- } = __testables;
13
-
14
- const TOOL_START_RUN = "start_recommend_pipeline_run";
15
- const TOOL_GET_RUN = "get_recommend_pipeline_run";
16
- const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
17
- const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
18
- const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
19
-
20
- function sleep(ms) {
21
- return new Promise((resolve) => setTimeout(resolve, ms));
22
- }
23
-
24
- function createFollowUpChat(overrides = {}) {
25
- return {
26
- chat: {
27
- criteria: "有 AI Agent 经验",
28
- start_from: "unread",
29
- target_count: 3,
30
- ...overrides
31
- }
32
- };
33
- }
34
-
35
- function makeToolCall(id, name, args = {}) {
36
- return {
37
- jsonrpc: "2.0",
38
- id,
39
- method: "tools/call",
40
- params: {
41
- name,
42
- arguments: args
43
- }
44
- };
45
- }
46
-
47
- async function readToolPayload(response) {
48
- return response?.result?.structuredContent;
49
- }
50
-
51
- async function callTool(name, args, id = 1) {
52
- const response = await handleRequest(
53
- makeToolCall(id, name, args),
54
- process.cwd()
55
- );
56
- return readToolPayload(response);
57
- }
58
-
59
- async function waitForRunState(runId, acceptedStates, timeoutMs = 6000) {
60
- const accepted = new Set(acceptedStates.map((item) => String(item).toLowerCase()));
61
- const deadline = Date.now() + timeoutMs;
62
- while (Date.now() < deadline) {
63
- const payload = await callTool(TOOL_GET_RUN, { run_id: runId }, 1001);
64
- const state = String(payload?.run?.state || "").toLowerCase();
65
- if (accepted.has(state)) return payload.run;
66
- await sleep(80);
67
- }
68
- throw new Error(`Timed out waiting run state (${Array.from(accepted).join(", ")}) for run_id=${runId}`);
69
- }
70
-
71
- async function waitForRunSnapshot(runId, predicate, timeoutMs = 6000) {
72
- const deadline = Date.now() + timeoutMs;
73
- while (Date.now() < deadline) {
74
- const payload = await callTool(TOOL_GET_RUN, { run_id: runId }, 1002);
75
- if (predicate(payload?.run)) return payload.run;
76
- await sleep(80);
77
- }
78
- throw new Error(`Timed out waiting run snapshot for run_id=${runId}`);
79
- }
80
-
81
- async function startAcceptedRun(instruction, idSeed = 1, extraArgs = {}) {
82
- const payload = await callTool(TOOL_START_RUN, {
83
- instruction,
84
- confirmation: {
85
- job_confirmed: true,
86
- job_value: "mock job",
87
- final_confirmed: true
88
- },
89
- ...extraArgs
90
- }, idSeed);
91
- assert.equal(payload.status, "ACCEPTED");
92
- assert.equal(typeof payload.run_id, "string");
93
- return payload.run_id;
94
- }
95
-
96
- function setupPipelineMock() {
97
- const checkpointStore = new Map();
98
- const chatStateStore = new Map();
99
- setRunPipelineImplForTests(async (input, _deps, runtime) => {
100
- if (input.confirmation?.job_confirmed !== true) {
101
- return {
102
- status: "NEED_CONFIRMATION",
103
- required_confirmations: ["job"],
104
- pending_questions: [{ field: "job", question: "请确认岗位" }]
105
- };
106
- }
107
- if (input.confirmation?.final_confirmed !== true) {
108
- return {
109
- status: "NEED_CONFIRMATION",
110
- required_confirmations: ["final_review"],
111
- pending_questions: [{ field: "final_review", question: "请最终确认" }]
112
- };
113
- }
114
-
115
- runtime?.onStage?.({ stage: "preflight", message: "preflight started" });
116
- await sleep(30);
117
- runtime?.onStage?.({ stage: "screen", message: "screen running" });
118
-
119
- const checkpointPath = String(input.resume?.checkpoint_path || "mem://default");
120
- const outputCsv = String(input.resume?.output_csv || "C:/tmp/mock.csv");
121
- const total = 12;
122
- let processed = input.resume?.resume === true ? Number(checkpointStore.get(checkpointPath) || 0) : 0;
123
- if (!Number.isInteger(processed) || processed < 0) processed = 0;
124
-
125
- while (processed < total) {
126
- processed += 1;
127
- runtime?.onProgress?.({
128
- stage: "screen",
129
- processed,
130
- passed: Math.floor(processed / 3),
131
- skipped: processed - Math.floor(processed / 3),
132
- greet_count: 0,
133
- line: `处理第 ${processed} 位候选人`
134
- });
135
- await sleep(25);
136
- if (runtime?.isPauseRequested?.() === true) {
137
- checkpointStore.set(checkpointPath, processed);
138
- return {
139
- status: "PAUSED",
140
- result: {
141
- processed_count: processed,
142
- passed_count: Math.floor(processed / 3),
143
- skipped_count: processed - Math.floor(processed / 3),
144
- greet_count: 0,
145
- output_csv: outputCsv,
146
- checkpoint_path: checkpointPath,
147
- completion_reason: "paused"
148
- }
149
- };
150
- }
151
- }
152
-
153
- checkpointStore.delete(checkpointPath);
154
- const recommendResult = {
155
- status: "COMPLETED",
156
- search_params: {
157
- school_tag: ["985"]
158
- },
159
- screen_params: {
160
- criteria: "mock criteria",
161
- target_count: 12,
162
- post_action: "favorite",
163
- max_greet_count: null
164
- },
165
- result: {
166
- processed_count: total,
167
- passed_count: Math.floor(total / 3),
168
- skipped_count: total - Math.floor(total / 3),
169
- greet_count: 0,
170
- output_csv: outputCsv,
171
- completion_reason: "screen_completed",
172
- selected_job: {
173
- title: "mock job"
174
- }
175
- }
176
- };
177
-
178
- if (!input.followUp?.chat) {
179
- return recommendResult;
180
- }
181
-
182
- runtime?.onStage?.({ stage: "chat_followup", message: "chat follow-up running" });
183
- const chatCheckpointKey = `${checkpointPath}:chat`;
184
- let chatState = "running";
185
- if (input.resume?.resume === true && input.resume?.follow_up_phase === "chat_followup") {
186
- const storedState = String(chatStateStore.get(chatCheckpointKey) || "running").trim().toLowerCase();
187
- chatState = storedState === "paused" ? "running" : storedState;
188
- }
189
-
190
- runtime?.onFollowUp?.({
191
- stage: "chat_followup",
192
- last_message: "chat follow-up running",
193
- recommend_payload: recommendResult,
194
- recommend_result: recommendResult.result,
195
- follow_up: {
196
- chat: {
197
- run_id: "mock-chat-run",
198
- state: chatState,
199
- input: {
200
- ...input.followUp.chat
201
- },
202
- progress: {
203
- inspected: 1,
204
- passed: 0,
205
- requested: 0,
206
- skipped: 1,
207
- errors: 0
208
- }
209
- }
210
- }
211
- });
212
-
213
- for (let chatTick = 0; chatTick < 20; chatTick += 1) {
214
- await sleep(25);
215
- if (runtime?.isCancelRequested?.() === true) {
216
- chatStateStore.set(chatCheckpointKey, "canceled");
217
- runtime?.onFollowUp?.({
218
- stage: "chat_followup",
219
- last_message: "chat follow-up canceled",
220
- recommend_payload: recommendResult,
221
- recommend_result: recommendResult.result,
222
- follow_up: {
223
- chat: {
224
- run_id: "mock-chat-run",
225
- state: "canceled",
226
- input: {
227
- ...input.followUp.chat
228
- }
229
- }
230
- }
231
- });
232
- return {
233
- ...recommendResult,
234
- status: "PAUSED",
235
- partial_result: recommendResult.result,
236
- follow_up: {
237
- chat: {
238
- run_id: "mock-chat-run",
239
- state: "canceled",
240
- input: {
241
- ...input.followUp.chat
242
- }
243
- }
244
- }
245
- };
246
- }
247
- if (runtime?.isPauseRequested?.() === true) {
248
- chatStateStore.set(chatCheckpointKey, "paused");
249
- runtime?.onFollowUp?.({
250
- stage: "chat_followup",
251
- last_message: "chat follow-up paused",
252
- recommend_payload: recommendResult,
253
- recommend_result: recommendResult.result,
254
- follow_up: {
255
- chat: {
256
- run_id: "mock-chat-run",
257
- state: "paused",
258
- input: {
259
- ...input.followUp.chat
260
- }
261
- }
262
- }
263
- });
264
- return {
265
- ...recommendResult,
266
- status: "PAUSED",
267
- partial_result: recommendResult.result,
268
- follow_up: {
269
- chat: {
270
- run_id: "mock-chat-run",
271
- state: "paused",
272
- input: {
273
- ...input.followUp.chat
274
- }
275
- }
276
- }
277
- };
278
- }
279
- }
280
-
281
- chatStateStore.delete(chatCheckpointKey);
282
- runtime?.onFollowUp?.({
283
- stage: "chat_followup",
284
- last_message: "chat follow-up completed",
285
- recommend_payload: recommendResult,
286
- recommend_result: recommendResult.result,
287
- follow_up: {
288
- chat: {
289
- run_id: "mock-chat-run",
290
- state: "completed",
291
- input: {
292
- ...input.followUp.chat
293
- },
294
- progress: {
295
- inspected: 3,
296
- passed: 1,
297
- requested: 1,
298
- skipped: 2,
299
- errors: 0
300
- },
301
- result: {
302
- requested_count: 1
303
- }
304
- }
305
- }
306
- });
307
- return {
308
- ...recommendResult,
309
- follow_up: {
310
- chat: {
311
- run_id: "mock-chat-run",
312
- state: "completed",
313
- input: {
314
- ...input.followUp.chat
315
- },
316
- result: {
317
- requested_count: 1
318
- }
319
- }
320
- }
321
- };
322
- });
323
- }
324
-
325
- function parseDetachedSpawnArgs(argv = []) {
326
- const normalized = Array.isArray(argv) ? argv.map((item) => String(item || "")) : [];
327
- const runIdFlagIndex = normalized.indexOf("--run-id");
328
- return {
329
- runId: runIdFlagIndex >= 0 ? String(normalized[runIdFlagIndex + 1] || "").trim() : "",
330
- resumeRun: normalized.includes("--resume")
331
- };
332
- }
333
-
334
- function setupDetachedWorkerStub() {
335
- setSpawnProcessImplForTests((command, argv = []) => {
336
- assert.equal(typeof command, "string");
337
- const { runId, resumeRun } = parseDetachedSpawnArgs(argv);
338
- assert.equal(Boolean(runId), true, "detached worker spawn must include --run-id");
339
- const pid = process.pid;
340
- setTimeout(() => {
341
- runDetachedWorkerForTests({
342
- runId,
343
- resumeRun,
344
- workerPid: pid
345
- }).catch(() => {});
346
- }, 0);
347
- return {
348
- pid,
349
- unref() {}
350
- };
351
- });
352
- }
353
-
354
- async function testPauseAndResumeFlow() {
355
- const runId = await startAcceptedRun("run for pause and resume", 11);
356
- await waitForRunState(runId, ["running"]);
357
-
358
- const pausePayload = await callTool(TOOL_PAUSE_RUN, { run_id: runId }, 12);
359
- assert.equal(pausePayload.status, "PAUSE_REQUESTED");
360
-
361
- const pausedRun = await waitForRunState(runId, ["paused"]);
362
- assert.equal(pausedRun.state, "paused");
363
- assert.equal(pausedRun.result?.status, "PAUSED");
364
- assert.equal(Boolean(pausedRun.resume?.output_csv), true);
365
-
366
- const resumePayload = await callTool(TOOL_RESUME_RUN, { run_id: runId }, 13);
367
- assert.equal(resumePayload.status, "RESUME_REQUESTED");
368
- assert.equal(resumePayload.run.run_id, runId);
369
-
370
- const completedRun = await waitForRunState(runId, ["completed"]);
371
- assert.equal(completedRun.state, "completed");
372
- assert.equal(completedRun.result?.status, "COMPLETED");
373
- }
374
-
375
- async function testResumeAfterProcessRestartSimulation() {
376
- const runId = await startAcceptedRun("run for restart resume", 21);
377
- await waitForRunState(runId, ["running"]);
378
-
379
- const pausePayload = await callTool(TOOL_PAUSE_RUN, { run_id: runId }, 22);
380
- assert.equal(pausePayload.status, "PAUSE_REQUESTED");
381
- await waitForRunState(runId, ["paused"]);
382
-
383
- const resumePayload = await callTool(TOOL_RESUME_RUN, { run_id: runId }, 23);
384
- assert.equal(resumePayload.status, "RESUME_REQUESTED");
385
- assert.equal(resumePayload.run.run_id, runId);
386
-
387
- const completedRun = await waitForRunState(runId, ["completed"]);
388
- assert.equal(completedRun.state, "completed");
389
- }
390
-
391
- async function testCancelPausedRun() {
392
- const runId = await startAcceptedRun("run for cancel paused", 31);
393
- await waitForRunState(runId, ["running"]);
394
-
395
- const pausePayload = await callTool(TOOL_PAUSE_RUN, { run_id: runId }, 32);
396
- assert.equal(pausePayload.status, "PAUSE_REQUESTED");
397
- await waitForRunState(runId, ["paused"]);
398
-
399
- const cancelPayload = await callTool(TOOL_CANCEL_RUN, { run_id: runId }, 33);
400
- assert.equal(cancelPayload.status, "CANCEL_REQUESTED");
401
-
402
- const canceledRun = await waitForRunState(runId, ["canceled"]);
403
- assert.equal(canceledRun.state, "canceled");
404
- }
405
-
406
- async function testCancelRunningRunKeepsCsv() {
407
- const runId = await startAcceptedRun("run for cancel while running", 41);
408
- await waitForRunState(runId, ["running"]);
409
-
410
- const cancelPayload = await callTool(TOOL_CANCEL_RUN, { run_id: runId }, 42);
411
- assert.equal(cancelPayload.status, "CANCEL_REQUESTED");
412
-
413
- const canceledRun = await waitForRunState(runId, ["canceled"]);
414
- assert.equal(canceledRun.state, "canceled");
415
- assert.equal(canceledRun.error?.code, "PIPELINE_CANCELED");
416
- assert.equal(Boolean(canceledRun.resume?.output_csv), true);
417
- }
418
-
419
- async function testPauseAndResumeDuringChatFollowUp() {
420
- const runId = await startAcceptedRun("run with follow-up chat pause resume", 51, {
421
- follow_up: createFollowUpChat()
422
- });
423
- const runningChatRun = await waitForRunSnapshot(
424
- runId,
425
- (run) => run?.stage === "chat_followup" && run?.state === "running" && run?.result?.follow_up?.chat?.state === "running"
426
- );
427
- assert.equal(runningChatRun.result?.result?.processed_count, 12);
428
- assert.equal(runningChatRun.result?.follow_up?.chat?.run_id, "mock-chat-run");
429
-
430
- const pausePayload = await callTool(TOOL_PAUSE_RUN, { run_id: runId }, 52);
431
- assert.equal(pausePayload.status, "PAUSE_REQUESTED");
432
-
433
- const pausedRun = await waitForRunSnapshot(
434
- runId,
435
- (run) => run?.state === "paused" && run?.stage === "chat_followup"
436
- );
437
- assert.equal(pausedRun.result?.follow_up?.chat?.state, "paused");
438
- assert.equal(pausedRun.result?.result?.processed_count, 12);
439
-
440
- const resumePayload = await callTool(TOOL_RESUME_RUN, { run_id: runId }, 53);
441
- assert.equal(resumePayload.status, "RESUME_REQUESTED");
442
-
443
- const completedRun = await waitForRunSnapshot(
444
- runId,
445
- (run) => run?.state === "completed" && run?.result?.follow_up?.chat?.state === "completed"
446
- );
447
- assert.equal(completedRun.result?.follow_up?.chat?.result?.requested_count, 1);
448
- assert.equal(completedRun.result?.result?.processed_count, 12);
449
- }
450
-
451
- async function testCancelDuringChatFollowUp() {
452
- const runId = await startAcceptedRun("run with follow-up chat cancel", 61, {
453
- follow_up: createFollowUpChat()
454
- });
455
- await waitForRunSnapshot(
456
- runId,
457
- (run) => run?.stage === "chat_followup" && run?.state === "running" && run?.result?.follow_up?.chat?.state === "running"
458
- );
459
-
460
- const cancelPayload = await callTool(TOOL_CANCEL_RUN, { run_id: runId }, 62);
461
- assert.equal(cancelPayload.status, "CANCEL_REQUESTED");
462
-
463
- const canceledRun = await waitForRunSnapshot(
464
- runId,
465
- (run) => run?.state === "canceled" && run?.stage === "chat_followup"
466
- );
467
- assert.equal(canceledRun.result?.follow_up?.chat?.state, "canceled");
468
- assert.equal(canceledRun.result?.result?.processed_count, 12);
469
- }
470
-
471
- async function main() {
472
- const previousHome = process.env.BOSS_RECOMMEND_HOME;
473
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-index-async-"));
474
- process.env.BOSS_RECOMMEND_HOME = tempHome;
475
- setupPipelineMock();
476
- setupDetachedWorkerStub();
477
-
478
- try {
479
- await testPauseAndResumeFlow();
480
- await testResumeAfterProcessRestartSimulation();
481
- await testCancelPausedRun();
482
- await testCancelRunningRunKeepsCsv();
483
- await testPauseAndResumeDuringChatFollowUp();
484
- await testCancelDuringChatFollowUp();
485
- console.log("index async tests passed");
486
- } finally {
487
- setRunPipelineImplForTests(null);
488
- setSpawnProcessImplForTests(null);
489
- if (previousHome === undefined) {
490
- delete process.env.BOSS_RECOMMEND_HOME;
491
- } else {
492
- process.env.BOSS_RECOMMEND_HOME = previousHome;
493
- }
494
- fs.rmSync(tempHome, { recursive: true, force: true });
495
- }
496
- }
497
-
498
- await main();