@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,2703 +0,0 @@
1
- import assert from "node:assert/strict";
2
- import { runRecommendPipeline as runRecommendPipelineImpl } from "./pipeline.js";
3
-
4
- const DEFAULT_JOB_OPTIONS = [
5
- {
6
- value: "job-data-analyst",
7
- title: "数据分析实习生 _ 杭州",
8
- label: "数据分析实习生 _ 杭州 100-150元/天",
9
- current: true
10
- },
11
- {
12
- value: "job-algorithm",
13
- title: "算法工程师(视频/图像模型方向) _ 杭州",
14
- label: "算法工程师(视频/图像模型方向) _ 杭州 25-50K",
15
- current: false
16
- }
17
- ];
18
-
19
- function createJobListResult() {
20
- return {
21
- ok: true,
22
- jobs: DEFAULT_JOB_OPTIONS,
23
- structured: {
24
- status: "COMPLETED",
25
- result: {
26
- jobs: DEFAULT_JOB_OPTIONS
27
- }
28
- }
29
- };
30
- }
31
-
32
- function createJobConfirmedConfirmation() {
33
- return {
34
- page_confirmed: true,
35
- page_value: "recommend",
36
- job_confirmed: true,
37
- job_value: "数据分析实习生 _ 杭州",
38
- final_confirmed: true
39
- };
40
- }
41
-
42
- function createJobConfirmedWithoutFinalConfirmation() {
43
- return {
44
- page_confirmed: true,
45
- page_value: "recommend",
46
- job_confirmed: true,
47
- job_value: "数据分析实习生 _ 杭州"
48
- };
49
- }
50
-
51
- function createParsed(overrides = {}) {
52
- return {
53
- searchParams: {
54
- school_tag: ["985"],
55
- degree: ["本科"],
56
- gender: "男",
57
- recent_not_view: "近14天没有"
58
- },
59
- screenParams: {
60
- criteria: "候选人需要有大模型平台经验",
61
- target_count: 10,
62
- post_action: "favorite",
63
- max_greet_count: null
64
- },
65
- missing_fields: [],
66
- suspicious_fields: [],
67
- needs_filters_confirmation: false,
68
- needs_school_tag_confirmation: false,
69
- needs_degree_confirmation: false,
70
- needs_gender_confirmation: false,
71
- needs_recent_not_view_confirmation: false,
72
- needs_criteria_confirmation: false,
73
- needs_target_count_confirmation: false,
74
- needs_post_action_confirmation: false,
75
- needs_max_greet_count_confirmation: false,
76
- needs_page_confirmation: false,
77
- page_scope: "recommend",
78
- proposed_page_scope: "recommend",
79
- proposed_post_action: "favorite",
80
- proposed_max_greet_count: null,
81
- job_selection_hint: null,
82
- pending_questions: [],
83
- review: {},
84
- ...overrides
85
- };
86
- }
87
-
88
- function createBasePipelineDeps(overrides = {}) {
89
- return {
90
- readRecommendTabState: async () => ({
91
- ok: true,
92
- active_status: "0",
93
- active_tab_status: "0"
94
- }),
95
- switchRecommendTab: async () => ({
96
- ok: true,
97
- state: "TAB_READY",
98
- after_state: {
99
- active_status: "0",
100
- active_tab_status: "0"
101
- }
102
- }),
103
- ...overrides
104
- };
105
- }
106
-
107
- async function runRecommendPipeline(args, dependencies = {}, runtime = null) {
108
- return runRecommendPipelineImpl(args, createBasePipelineDeps(dependencies), runtime);
109
- }
110
-
111
- function createFollowUpChat(overrides = {}) {
112
- return {
113
- chat: {
114
- criteria: "有 AI Agent 经验",
115
- start_from: "unread",
116
- target_count: 5,
117
- ...overrides
118
- }
119
- };
120
- }
121
-
122
- async function testPauseRequestedBeforeScreenShouldReturnPaused() {
123
- let screenCalled = false;
124
- const result = await runRecommendPipeline(
125
- {
126
- workspaceRoot: process.cwd(),
127
- instruction: "test",
128
- confirmation: createJobConfirmedConfirmation(),
129
- overrides: {}
130
- },
131
- {
132
- parseRecommendInstruction: () => createParsed(),
133
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
134
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
135
- listRecommendJobs: async () => createJobListResult(),
136
- runRecommendSearchCli: async () => ({
137
- ok: true,
138
- summary: {
139
- candidate_count: 5,
140
- applied_filters: {},
141
- page_state: { state: "RECOMMEND_READY" }
142
- }
143
- }),
144
- runRecommendScreenCli: async () => {
145
- screenCalled = true;
146
- return { ok: true, summary: {} };
147
- }
148
- },
149
- {
150
- isPauseRequested: () => true
151
- }
152
- );
153
-
154
- assert.equal(result.status, "PAUSED");
155
- assert.equal(result.partial_result.completion_reason, "paused_before_screen");
156
- assert.equal(screenCalled, false);
157
- }
158
-
159
- async function testPausedScreenResultShouldBubbleUp() {
160
- const result = await runRecommendPipeline(
161
- {
162
- workspaceRoot: process.cwd(),
163
- instruction: "test",
164
- confirmation: createJobConfirmedConfirmation(),
165
- overrides: {},
166
- resume: {
167
- output_csv: "C:/temp/resume.csv",
168
- checkpoint_path: "C:/temp/checkpoint.json",
169
- pause_control_path: "C:/temp/run.json"
170
- }
171
- },
172
- {
173
- parseRecommendInstruction: () => createParsed(),
174
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
175
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
176
- listRecommendJobs: async () => createJobListResult(),
177
- runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 9, applied_filters: {} } }),
178
- runRecommendScreenCli: async () => ({
179
- ok: false,
180
- paused: true,
181
- summary: {
182
- processed_count: 3,
183
- passed_count: 1,
184
- skipped_count: 2,
185
- output_csv: "C:/temp/resume.csv",
186
- checkpoint_path: "C:/temp/checkpoint.json",
187
- completion_reason: "paused"
188
- }
189
- })
190
- }
191
- );
192
-
193
- assert.equal(result.status, "PAUSED");
194
- assert.equal(result.partial_result.output_csv, "C:/temp/resume.csv");
195
- assert.equal(result.partial_result.completion_reason, "paused");
196
- }
197
-
198
- async function testResumeFromScreenPauseShouldSkipSearch() {
199
- let searchCalled = false;
200
- let receivedResume = null;
201
- const result = await runRecommendPipeline(
202
- {
203
- workspaceRoot: process.cwd(),
204
- instruction: "test",
205
- confirmation: createJobConfirmedConfirmation(),
206
- overrides: {},
207
- resume: {
208
- resume: true,
209
- output_csv: "C:/temp/resume.csv",
210
- checkpoint_path: "C:/temp/checkpoint.json",
211
- pause_control_path: "C:/temp/run.json",
212
- previous_completion_reason: "paused"
213
- }
214
- },
215
- {
216
- parseRecommendInstruction: () => createParsed(),
217
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
218
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
219
- listRecommendJobs: async () => createJobListResult(),
220
- runRecommendSearchCli: async () => {
221
- searchCalled = true;
222
- return { ok: true, summary: { candidate_count: 9, applied_filters: {} } };
223
- },
224
- runRecommendScreenCli: async ({ resume }) => {
225
- receivedResume = resume;
226
- return {
227
- ok: true,
228
- summary: {
229
- processed_count: 6,
230
- passed_count: 2,
231
- skipped_count: 4,
232
- output_csv: "C:/temp/resume.csv",
233
- completion_reason: "page_exhausted"
234
- }
235
- };
236
- }
237
- }
238
- );
239
-
240
- assert.equal(result.status, "COMPLETED");
241
- assert.equal(searchCalled, false);
242
- assert.equal(receivedResume.resume, true);
243
- assert.equal(receivedResume.require_checkpoint, true);
244
- assert.equal(result.result.candidate_count, null);
245
- }
246
-
247
- async function testResumeFromPausedBeforeScreenShouldRerunSearch() {
248
- let searchCalled = false;
249
- let receivedResume = null;
250
- const result = await runRecommendPipeline(
251
- {
252
- workspaceRoot: process.cwd(),
253
- instruction: "test",
254
- confirmation: createJobConfirmedConfirmation(),
255
- overrides: {},
256
- resume: {
257
- resume: true,
258
- output_csv: "C:/temp/resume.csv",
259
- checkpoint_path: "C:/temp/checkpoint.json",
260
- pause_control_path: "C:/temp/run.json",
261
- previous_completion_reason: "paused_before_screen"
262
- }
263
- },
264
- {
265
- parseRecommendInstruction: () => createParsed(),
266
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
267
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
268
- listRecommendJobs: async () => createJobListResult(),
269
- runRecommendSearchCli: async () => {
270
- searchCalled = true;
271
- return { ok: true, summary: { candidate_count: 9, applied_filters: { degree: ["本科"] } } };
272
- },
273
- runRecommendScreenCli: async ({ resume }) => {
274
- receivedResume = resume;
275
- return {
276
- ok: true,
277
- summary: {
278
- processed_count: 4,
279
- passed_count: 1,
280
- skipped_count: 3,
281
- output_csv: "C:/temp/resume.csv",
282
- completion_reason: "page_exhausted"
283
- }
284
- };
285
- }
286
- }
287
- );
288
-
289
- assert.equal(result.status, "COMPLETED");
290
- assert.equal(searchCalled, true);
291
- assert.equal(receivedResume.resume, true);
292
- assert.equal(receivedResume.require_checkpoint, false);
293
- assert.equal(result.result.candidate_count, 9);
294
- }
295
-
296
- async function testConsecutiveResumeCaptureFailuresShouldRefreshAndRerunSearchWithForcedRecentFilter() {
297
- const searchCalls = [];
298
- const screenCalls = [];
299
- let reloadCalls = 0;
300
- let pageReadyCalls = 0;
301
- const parsed = createParsed();
302
- parsed.searchParams = {
303
- ...parsed.searchParams,
304
- recent_not_view: "不限"
305
- };
306
- const result = await runRecommendPipeline(
307
- {
308
- workspaceRoot: process.cwd(),
309
- instruction: "test",
310
- confirmation: createJobConfirmedConfirmation(),
311
- overrides: {},
312
- resume: {
313
- resume: false,
314
- output_csv: "C:/temp/resume.csv",
315
- checkpoint_path: "C:/temp/checkpoint.json",
316
- pause_control_path: "C:/temp/run.json",
317
- previous_completion_reason: ""
318
- }
319
- },
320
- {
321
- parseRecommendInstruction: () => parsed,
322
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
323
- ensureBossRecommendPageReady: async () => {
324
- pageReadyCalls += 1;
325
- return { ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } };
326
- },
327
- listRecommendJobs: async () => createJobListResult(),
328
- reloadBossRecommendPage: async () => {
329
- reloadCalls += 1;
330
- return {
331
- ok: true,
332
- state: "RECOMMEND_READY",
333
- reloaded_url: "https://www.zhipin.com/web/chat/recommend"
334
- };
335
- },
336
- runRecommendSearchCli: async ({ searchParams, selectedJob }) => {
337
- searchCalls.push({
338
- searchParams,
339
- selectedJob
340
- });
341
- return {
342
- ok: true,
343
- summary: {
344
- candidate_count: 9,
345
- applied_filters: searchParams,
346
- selected_job: selectedJob,
347
- page_state: { state: "RECOMMEND_READY" }
348
- }
349
- };
350
- },
351
- runRecommendScreenCli: async ({ resume }) => {
352
- screenCalls.push(resume);
353
- if (screenCalls.length === 1) {
354
- return {
355
- ok: false,
356
- error: {
357
- code: "RESUME_CAPTURE_FAILED_CONSECUTIVE_LIMIT",
358
- message: "连续 10 位候选人截图失败"
359
- },
360
- summary: {
361
- processed_count: 216,
362
- passed_count: 83,
363
- skipped_count: 133,
364
- output_csv: "C:/temp/resume.csv"
365
- }
366
- };
367
- }
368
- return {
369
- ok: true,
370
- summary: {
371
- processed_count: 240,
372
- passed_count: 90,
373
- skipped_count: 150,
374
- output_csv: "C:/temp/resume.csv",
375
- completion_reason: "page_exhausted"
376
- }
377
- };
378
- }
379
- }
380
- );
381
-
382
- assert.equal(result.status, "COMPLETED");
383
- assert.equal(searchCalls.length, 2);
384
- assert.equal(searchCalls[0].searchParams.recent_not_view, "不限");
385
- assert.equal(searchCalls[1].searchParams.recent_not_view, "近14天没有");
386
- assert.equal(screenCalls.length, 2);
387
- assert.equal(screenCalls[0].resume, false);
388
- assert.equal(screenCalls[1].resume, true);
389
- assert.equal(screenCalls[1].require_checkpoint, true);
390
- assert.equal(screenCalls[1].output_csv, "C:/temp/resume.csv");
391
- assert.equal(reloadCalls, 1);
392
- assert.equal(pageReadyCalls, 2);
393
- assert.equal(result.result.output_csv, "C:/temp/resume.csv");
394
- assert.equal(result.result.auto_recovery.reload.ok, true);
395
- assert.equal(result.search_params.recent_not_view, "近14天没有");
396
- }
397
-
398
- async function testPageExhaustedBeforeTargetShouldRefreshInPageAndResumeScreen() {
399
- const searchCalls = [];
400
- const screenCalls = [];
401
- let refreshCalls = 0;
402
- let reloadCalls = 0;
403
- let pageReadyCalls = 0;
404
- const parsed = createParsed();
405
- parsed.searchParams = {
406
- ...parsed.searchParams,
407
- recent_not_view: "不限"
408
- };
409
- const result = await runRecommendPipeline(
410
- {
411
- workspaceRoot: process.cwd(),
412
- instruction: "test",
413
- confirmation: createJobConfirmedConfirmation(),
414
- overrides: {},
415
- resume: {
416
- resume: false,
417
- output_csv: "C:/temp/resume.csv",
418
- checkpoint_path: "C:/temp/checkpoint.json",
419
- pause_control_path: "C:/temp/run.json",
420
- previous_completion_reason: ""
421
- }
422
- },
423
- {
424
- parseRecommendInstruction: () => parsed,
425
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
426
- ensureBossRecommendPageReady: async () => {
427
- pageReadyCalls += 1;
428
- return { ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } };
429
- },
430
- listRecommendJobs: async () => createJobListResult(),
431
- refreshBossRecommendList: async () => {
432
- refreshCalls += 1;
433
- return {
434
- ok: true,
435
- action: "in_page_refresh",
436
- state: "RECOMMEND_READY",
437
- before_state: {
438
- finished_wrap_visible: true,
439
- refresh_button_visible: true
440
- },
441
- after_state: {
442
- finished_wrap_visible: false,
443
- list_ready: true
444
- }
445
- };
446
- },
447
- reloadBossRecommendPage: async () => {
448
- reloadCalls += 1;
449
- return {
450
- ok: true,
451
- state: "RECOMMEND_READY",
452
- reloaded_url: "https://www.zhipin.com/web/chat/recommend"
453
- };
454
- },
455
- runRecommendSearchCli: async ({ searchParams }) => {
456
- searchCalls.push({ ...searchParams });
457
- return {
458
- ok: true,
459
- summary: {
460
- candidate_count: 9,
461
- applied_filters: searchParams,
462
- page_state: { state: "RECOMMEND_READY" }
463
- }
464
- };
465
- },
466
- runRecommendScreenCli: async ({ resume }) => {
467
- screenCalls.push({ ...resume });
468
- if (screenCalls.length === 1) {
469
- return {
470
- ok: false,
471
- error: {
472
- code: "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED",
473
- message: "推荐列表已到底,但尚未达到目标数。",
474
- page_exhaustion: {
475
- reason: "bottom_reached",
476
- bottom: {
477
- isBottom: true,
478
- finished_wrap_visible: true,
479
- refresh_button_visible: true
480
- }
481
- }
482
- },
483
- summary: {
484
- processed_count: 4,
485
- passed_count: 1,
486
- skipped_count: 3,
487
- output_csv: "C:/temp/resume.csv",
488
- checkpoint_path: "C:/temp/checkpoint.json",
489
- completion_reason: "page_exhausted_before_target_count"
490
- }
491
- };
492
- }
493
- return {
494
- ok: true,
495
- summary: {
496
- processed_count: 10,
497
- passed_count: 3,
498
- skipped_count: 7,
499
- output_csv: "C:/temp/resume.csv",
500
- checkpoint_path: "C:/temp/checkpoint.json",
501
- completion_reason: "target_count_reached"
502
- }
503
- };
504
- }
505
- }
506
- );
507
-
508
- assert.equal(result.status, "COMPLETED");
509
- assert.equal(searchCalls.length, 1);
510
- assert.equal(searchCalls[0].recent_not_view, "不限");
511
- assert.equal(screenCalls.length, 2);
512
- assert.equal(screenCalls[0].resume, false);
513
- assert.equal(screenCalls[1].resume, true);
514
- assert.equal(screenCalls[1].require_checkpoint, true);
515
- assert.equal(screenCalls[1].output_csv, "C:/temp/resume.csv");
516
- assert.equal(refreshCalls, 1);
517
- assert.equal(reloadCalls, 0);
518
- assert.equal(pageReadyCalls, 1);
519
- assert.equal(result.result.candidate_count, null);
520
- assert.equal(result.result.completion_reason, "target_count_reached");
521
- assert.equal(result.result.auto_recovery.action, "in_page_refresh");
522
- assert.equal(result.result.auto_recovery.refresh.ok, true);
523
- assert.equal(result.result.auto_recovery.page_exhaustion.reason, "bottom_reached");
524
- assert.equal(result.search_params.recent_not_view, "不限");
525
- }
526
-
527
- async function testPageExhaustedBeforeTargetShouldReloadWhenRefreshButtonMissing() {
528
- const searchCalls = [];
529
- const screenCalls = [];
530
- let refreshCalls = 0;
531
- let reloadCalls = 0;
532
- let pageReadyCalls = 0;
533
- const parsed = createParsed();
534
- parsed.searchParams = {
535
- ...parsed.searchParams,
536
- recent_not_view: "不限"
537
- };
538
- const result = await runRecommendPipeline(
539
- {
540
- workspaceRoot: process.cwd(),
541
- instruction: "test",
542
- confirmation: createJobConfirmedConfirmation(),
543
- overrides: {},
544
- resume: {
545
- resume: false,
546
- output_csv: "C:/temp/resume.csv",
547
- checkpoint_path: "C:/temp/checkpoint.json",
548
- pause_control_path: "C:/temp/run.json",
549
- previous_completion_reason: ""
550
- }
551
- },
552
- {
553
- parseRecommendInstruction: () => parsed,
554
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
555
- ensureBossRecommendPageReady: async () => {
556
- pageReadyCalls += 1;
557
- return { ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } };
558
- },
559
- listRecommendJobs: async () => createJobListResult(),
560
- refreshBossRecommendList: async () => {
561
- refreshCalls += 1;
562
- return {
563
- ok: false,
564
- action: "in_page_refresh",
565
- state: "REFRESH_BUTTON_NOT_FOUND",
566
- message: "未找到页内刷新按钮。"
567
- };
568
- },
569
- reloadBossRecommendPage: async () => {
570
- reloadCalls += 1;
571
- return {
572
- ok: true,
573
- state: "RECOMMEND_READY",
574
- reloaded_url: "https://www.zhipin.com/web/chat/recommend"
575
- };
576
- },
577
- runRecommendSearchCli: async ({ searchParams }) => {
578
- searchCalls.push({ ...searchParams });
579
- return {
580
- ok: true,
581
- summary: {
582
- candidate_count: searchCalls.length === 1 ? 9 : 12,
583
- applied_filters: searchParams,
584
- page_state: { state: "RECOMMEND_READY" }
585
- }
586
- };
587
- },
588
- runRecommendScreenCli: async ({ resume }) => {
589
- screenCalls.push({ ...resume });
590
- if (screenCalls.length === 1) {
591
- return {
592
- ok: false,
593
- error: {
594
- code: "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED",
595
- message: "推荐列表已到底,但尚未达到目标数。",
596
- page_exhaustion: {
597
- reason: "bottom_reached",
598
- bottom: {
599
- isBottom: true,
600
- finished_wrap_visible: true,
601
- refresh_button_visible: false
602
- }
603
- }
604
- },
605
- summary: {
606
- processed_count: 4,
607
- passed_count: 1,
608
- skipped_count: 3,
609
- output_csv: "C:/temp/resume.csv",
610
- checkpoint_path: "C:/temp/checkpoint.json",
611
- completion_reason: "page_exhausted_before_target_count"
612
- }
613
- };
614
- }
615
- return {
616
- ok: true,
617
- summary: {
618
- processed_count: 10,
619
- passed_count: 3,
620
- skipped_count: 7,
621
- output_csv: "C:/temp/resume.csv",
622
- checkpoint_path: "C:/temp/checkpoint.json",
623
- completion_reason: "target_count_reached"
624
- }
625
- };
626
- }
627
- }
628
- );
629
-
630
- assert.equal(result.status, "COMPLETED");
631
- assert.equal(searchCalls.length, 2);
632
- assert.equal(searchCalls[0].recent_not_view, "不限");
633
- assert.equal(searchCalls[1].recent_not_view, "近14天没有");
634
- assert.equal(screenCalls.length, 2);
635
- assert.equal(screenCalls[0].resume, false);
636
- assert.equal(screenCalls[1].resume, true);
637
- assert.equal(screenCalls[1].require_checkpoint, true);
638
- assert.equal(refreshCalls, 1);
639
- assert.equal(reloadCalls, 1);
640
- assert.equal(pageReadyCalls, 2);
641
- assert.equal(result.result.candidate_count, 12);
642
- assert.equal(result.result.auto_recovery.action, "reload_page_and_rerun_search");
643
- assert.equal(result.result.auto_recovery.refresh.state, "REFRESH_BUTTON_NOT_FOUND");
644
- assert.equal(result.result.auto_recovery.reload.ok, true);
645
- assert.equal(result.search_params.recent_not_view, "近14天没有");
646
- }
647
-
648
- async function testPageExhaustedBeforeTargetShouldReloadWhenRefreshDoesNotRecoverList() {
649
- const searchCalls = [];
650
- const screenCalls = [];
651
- let refreshCalls = 0;
652
- let reloadCalls = 0;
653
- const parsed = createParsed();
654
- parsed.searchParams = {
655
- ...parsed.searchParams,
656
- recent_not_view: "不限"
657
- };
658
- const result = await runRecommendPipeline(
659
- {
660
- workspaceRoot: process.cwd(),
661
- instruction: "test",
662
- confirmation: createJobConfirmedConfirmation(),
663
- overrides: {},
664
- resume: {
665
- resume: false,
666
- output_csv: "C:/temp/resume.csv",
667
- checkpoint_path: "C:/temp/checkpoint.json",
668
- pause_control_path: "C:/temp/run.json",
669
- previous_completion_reason: ""
670
- }
671
- },
672
- {
673
- parseRecommendInstruction: () => parsed,
674
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
675
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
676
- listRecommendJobs: async () => createJobListResult(),
677
- refreshBossRecommendList: async () => {
678
- refreshCalls += 1;
679
- return {
680
- ok: false,
681
- action: "in_page_refresh",
682
- state: "LIST_NOT_RELOADED",
683
- message: "点击刷新后列表没有重新就绪。"
684
- };
685
- },
686
- reloadBossRecommendPage: async () => {
687
- reloadCalls += 1;
688
- return {
689
- ok: true,
690
- state: "RECOMMEND_READY",
691
- reloaded_url: "https://www.zhipin.com/web/chat/recommend"
692
- };
693
- },
694
- runRecommendSearchCli: async ({ searchParams }) => {
695
- searchCalls.push({ ...searchParams });
696
- return {
697
- ok: true,
698
- summary: {
699
- candidate_count: searchCalls.length === 1 ? 9 : 11,
700
- applied_filters: searchParams,
701
- page_state: { state: "RECOMMEND_READY" }
702
- }
703
- };
704
- },
705
- runRecommendScreenCli: async ({ resume }) => {
706
- screenCalls.push({ ...resume });
707
- if (screenCalls.length === 1) {
708
- return {
709
- ok: false,
710
- error: {
711
- code: "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED",
712
- message: "推荐列表已到底,但尚未达到目标数。"
713
- },
714
- summary: {
715
- processed_count: 4,
716
- passed_count: 1,
717
- skipped_count: 3,
718
- output_csv: "C:/temp/resume.csv",
719
- checkpoint_path: "C:/temp/checkpoint.json",
720
- completion_reason: "page_exhausted_before_target_count"
721
- }
722
- };
723
- }
724
- return {
725
- ok: true,
726
- summary: {
727
- processed_count: 10,
728
- passed_count: 3,
729
- skipped_count: 7,
730
- output_csv: "C:/temp/resume.csv",
731
- checkpoint_path: "C:/temp/checkpoint.json",
732
- completion_reason: "target_count_reached"
733
- }
734
- };
735
- }
736
- }
737
- );
738
-
739
- assert.equal(result.status, "COMPLETED");
740
- assert.equal(searchCalls.length, 2);
741
- assert.equal(searchCalls[1].recent_not_view, "近14天没有");
742
- assert.equal(screenCalls.length, 2);
743
- assert.equal(screenCalls[1].resume, true);
744
- assert.equal(refreshCalls, 1);
745
- assert.equal(reloadCalls, 1);
746
- assert.equal(result.result.candidate_count, 11);
747
- assert.equal(result.result.auto_recovery.action, "reload_page_and_rerun_search");
748
- assert.equal(result.result.auto_recovery.refresh.state, "LIST_NOT_RELOADED");
749
- assert.equal(result.search_params.recent_not_view, "近14天没有");
750
- }
751
-
752
- async function testPageExhaustedBeforeTargetShouldFailAfterFiveRecoveryAttempts() {
753
- const searchCalls = [];
754
- const screenCalls = [];
755
- let refreshCalls = 0;
756
- const parsed = createParsed();
757
- parsed.searchParams = {
758
- ...parsed.searchParams,
759
- recent_not_view: "不限"
760
- };
761
- const result = await runRecommendPipeline(
762
- {
763
- workspaceRoot: process.cwd(),
764
- instruction: "test",
765
- confirmation: createJobConfirmedConfirmation(),
766
- overrides: {},
767
- resume: {
768
- resume: false,
769
- output_csv: "C:/temp/resume.csv",
770
- checkpoint_path: "C:/temp/checkpoint.json",
771
- pause_control_path: "C:/temp/run.json",
772
- previous_completion_reason: ""
773
- }
774
- },
775
- {
776
- parseRecommendInstruction: () => parsed,
777
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
778
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
779
- listRecommendJobs: async () => createJobListResult(),
780
- refreshBossRecommendList: async () => {
781
- refreshCalls += 1;
782
- return {
783
- ok: true,
784
- action: "in_page_refresh",
785
- state: "RECOMMEND_READY",
786
- before_state: {
787
- finished_wrap_visible: true,
788
- refresh_button_visible: true
789
- },
790
- after_state: {
791
- finished_wrap_visible: false,
792
- list_ready: true
793
- }
794
- };
795
- },
796
- runRecommendSearchCli: async ({ searchParams }) => {
797
- searchCalls.push({ ...searchParams });
798
- return {
799
- ok: true,
800
- summary: {
801
- candidate_count: 9,
802
- applied_filters: searchParams,
803
- page_state: { state: "RECOMMEND_READY" }
804
- }
805
- };
806
- },
807
- runRecommendScreenCli: async ({ resume }) => {
808
- screenCalls.push({ ...resume });
809
- return {
810
- ok: false,
811
- error: {
812
- code: "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED",
813
- message: "推荐列表已到底,但尚未达到目标数。",
814
- page_exhaustion: {
815
- reason: "bottom_reached",
816
- bottom: {
817
- isBottom: true,
818
- finished_wrap_visible: true,
819
- refresh_button_visible: true
820
- }
821
- }
822
- },
823
- summary: {
824
- processed_count: 4,
825
- passed_count: 1,
826
- skipped_count: 3,
827
- output_csv: "C:/temp/resume.csv",
828
- checkpoint_path: "C:/temp/checkpoint.json",
829
- completion_reason: "page_exhausted_before_target_count"
830
- }
831
- };
832
- }
833
- }
834
- );
835
-
836
- assert.equal(result.status, "FAILED");
837
- assert.equal(result.error.code, "TARGET_COUNT_NOT_REACHED_PAGE_EXHAUSTED");
838
- assert.match(result.error.message, /已达到自动恢复上限 5 次/);
839
- assert.equal(searchCalls.length, 1);
840
- assert.equal(screenCalls.length, 6);
841
- assert.equal(refreshCalls, 5);
842
- assert.equal(result.partial_result.output_csv, "C:/temp/resume.csv");
843
- assert.equal(result.diagnostics.auto_recovery.attempt, 5);
844
- assert.equal(result.diagnostics.auto_recovery.action, "in_page_refresh");
845
- assert.equal(result.diagnostics.auto_recovery.partial_result.checkpoint_path, "C:/temp/checkpoint.json");
846
- }
847
-
848
- async function testNullTargetCountShouldKeepPageExhaustedCompletion() {
849
- const parsed = createParsed();
850
- parsed.screenParams = {
851
- ...parsed.screenParams,
852
- target_count: null
853
- };
854
- let receivedScreenParams = null;
855
- const result = await runRecommendPipeline(
856
- {
857
- workspaceRoot: process.cwd(),
858
- instruction: "test",
859
- confirmation: createJobConfirmedConfirmation(),
860
- overrides: {}
861
- },
862
- {
863
- parseRecommendInstruction: () => parsed,
864
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
865
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
866
- listRecommendJobs: async () => createJobListResult(),
867
- runRecommendSearchCli: async () => ({
868
- ok: true,
869
- summary: {
870
- candidate_count: 9,
871
- applied_filters: {},
872
- page_state: { state: "RECOMMEND_READY" }
873
- }
874
- }),
875
- runRecommendScreenCli: async ({ screenParams }) => {
876
- receivedScreenParams = screenParams;
877
- return {
878
- ok: true,
879
- summary: {
880
- processed_count: 4,
881
- passed_count: 1,
882
- skipped_count: 3,
883
- output_csv: "C:/temp/resume.csv",
884
- completion_reason: "page_exhausted"
885
- }
886
- };
887
- }
888
- }
889
- );
890
-
891
- assert.equal(receivedScreenParams.target_count, null);
892
- assert.equal(result.status, "COMPLETED");
893
- assert.equal(result.result.completion_reason, "page_exhausted");
894
- assert.equal(result.result.auto_recovery, null);
895
- }
896
-
897
- async function testNeedConfirmationGate() {
898
- let preflightCalled = false;
899
- const result = await runRecommendPipeline(
900
- {
901
- workspaceRoot: process.cwd(),
902
- instruction: "test",
903
- confirmation: {},
904
- overrides: {}
905
- },
906
- {
907
- parseRecommendInstruction: () => createParsed({ needs_post_action_confirmation: true }),
908
- runPipelinePreflight: () => {
909
- preflightCalled = true;
910
- return { ok: true, checks: [], debug_port: 9222 };
911
- },
912
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
913
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
914
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
915
- }
916
- );
917
-
918
- assert.equal(result.status, "NEED_CONFIRMATION");
919
- assert.equal(preflightCalled, false);
920
- }
921
-
922
- async function testNeedPageScopeConfirmationGate() {
923
- let preflightCalled = false;
924
- const result = await runRecommendPipeline(
925
- {
926
- workspaceRoot: process.cwd(),
927
- instruction: "test",
928
- confirmation: {},
929
- overrides: {}
930
- },
931
- {
932
- parseRecommendInstruction: () => createParsed({
933
- needs_page_confirmation: true,
934
- page_scope: null,
935
- proposed_page_scope: "featured",
936
- pending_questions: [{ field: "page_scope" }]
937
- }),
938
- runPipelinePreflight: () => {
939
- preflightCalled = true;
940
- return { ok: true, checks: [], debug_port: 9222 };
941
- },
942
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
943
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
944
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
945
- }
946
- );
947
-
948
- assert.equal(result.status, "NEED_CONFIRMATION");
949
- assert.equal(result.required_confirmations.includes("page_scope"), true);
950
- assert.equal(result.selected_page, "featured");
951
- assert.equal(preflightCalled, false);
952
- }
953
-
954
- async function testNeedTargetCountConfirmationGate() {
955
- let preflightCalled = false;
956
- const result = await runRecommendPipeline(
957
- {
958
- workspaceRoot: process.cwd(),
959
- instruction: "test",
960
- confirmation: {},
961
- overrides: {}
962
- },
963
- {
964
- parseRecommendInstruction: () => createParsed({
965
- needs_target_count_confirmation: true,
966
- pending_questions: [{ field: "target_count" }]
967
- }),
968
- runPipelinePreflight: () => {
969
- preflightCalled = true;
970
- return { ok: true, checks: [], debug_port: 9222 };
971
- },
972
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
973
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
974
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
975
- }
976
- );
977
-
978
- assert.equal(result.status, "NEED_CONFIRMATION");
979
- assert.equal(result.required_confirmations.includes("target_count"), true);
980
- assert.equal(preflightCalled, false);
981
- }
982
-
983
- async function testNeedSchoolTagConfirmationGate() {
984
- let preflightCalled = false;
985
- const result = await runRecommendPipeline(
986
- {
987
- workspaceRoot: process.cwd(),
988
- instruction: "test",
989
- confirmation: {},
990
- overrides: {}
991
- },
992
- {
993
- parseRecommendInstruction: () => createParsed({
994
- needs_school_tag_confirmation: true,
995
- pending_questions: [{ field: "school_tag" }]
996
- }),
997
- runPipelinePreflight: () => {
998
- preflightCalled = true;
999
- return { ok: true, checks: [], debug_port: 9222 };
1000
- },
1001
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1002
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1003
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1004
- }
1005
- );
1006
-
1007
- assert.equal(result.status, "NEED_CONFIRMATION");
1008
- assert.equal(result.required_confirmations.includes("school_tag"), true);
1009
- assert.equal(preflightCalled, false);
1010
- }
1011
-
1012
- async function testNeedMaxGreetCountConfirmationGate() {
1013
- const result = await runRecommendPipeline(
1014
- {
1015
- workspaceRoot: process.cwd(),
1016
- instruction: "test",
1017
- confirmation: {},
1018
- overrides: {}
1019
- },
1020
- {
1021
- parseRecommendInstruction: () => createParsed({
1022
- screenParams: {
1023
- criteria: "候选人需要有大模型平台经验",
1024
- target_count: 10,
1025
- post_action: "greet",
1026
- max_greet_count: null
1027
- },
1028
- needs_max_greet_count_confirmation: true,
1029
- proposed_post_action: "greet",
1030
- proposed_max_greet_count: null,
1031
- pending_questions: [{ field: "max_greet_count" }]
1032
- }),
1033
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1034
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1035
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1036
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1037
- }
1038
- );
1039
-
1040
- assert.equal(result.status, "NEED_CONFIRMATION");
1041
- assert.equal(result.required_confirmations.includes("max_greet_count"), true);
1042
- }
1043
-
1044
- async function testNeedInputGate() {
1045
- const result = await runRecommendPipeline(
1046
- {
1047
- workspaceRoot: process.cwd(),
1048
- instruction: "test",
1049
- confirmation: {},
1050
- overrides: {}
1051
- },
1052
- {
1053
- parseRecommendInstruction: () => createParsed({ missing_fields: ["criteria"] }),
1054
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1055
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1056
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1057
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1058
- }
1059
- );
1060
-
1061
- assert.equal(result.status, "NEED_INPUT");
1062
- }
1063
-
1064
- async function testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen() {
1065
- const calls = [];
1066
- const result = await runRecommendPipeline(
1067
- {
1068
- workspaceRoot: process.cwd(),
1069
- instruction: "test",
1070
- confirmation: {
1071
- ...createJobConfirmedConfirmation(),
1072
- page_confirmed: true,
1073
- page_value: "featured"
1074
- },
1075
- overrides: {
1076
- page_scope: "featured"
1077
- }
1078
- },
1079
- {
1080
- parseRecommendInstruction: () => createParsed({
1081
- page_scope: "featured",
1082
- proposed_page_scope: "featured"
1083
- }),
1084
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1085
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1086
- listRecommendJobs: async () => createJobListResult(),
1087
- runRecommendSearchCli: async ({ pageScope }) => {
1088
- calls.push({ type: "search", pageScope });
1089
- return {
1090
- ok: true,
1091
- summary: {
1092
- candidate_count: 6,
1093
- applied_filters: { degree: ["本科"] },
1094
- page_state: { state: "RECOMMEND_READY" }
1095
- }
1096
- };
1097
- },
1098
- readRecommendTabState: async () => ({
1099
- ok: true,
1100
- active_status: "0",
1101
- tab_state: { active_status: "0" }
1102
- }),
1103
- switchRecommendTab: async (_workspaceRoot, { target_status }) => {
1104
- calls.push({ type: "switch", target_status });
1105
- return {
1106
- ok: true,
1107
- state: "TAB_SWITCHED",
1108
- active_status: "3",
1109
- tab_state: { active_status: "3" }
1110
- };
1111
- },
1112
- runRecommendScreenCli: async ({ pageScope }) => {
1113
- calls.push({ type: "screen", pageScope });
1114
- return {
1115
- ok: true,
1116
- summary: {
1117
- processed_count: 6,
1118
- passed_count: 2,
1119
- skipped_count: 4,
1120
- output_csv: "C:/temp/result.csv",
1121
- completion_reason: "page_exhausted",
1122
- active_tab_status: "3",
1123
- resume_source: "network"
1124
- }
1125
- };
1126
- }
1127
- }
1128
- );
1129
-
1130
- assert.equal(result.status, "COMPLETED");
1131
- assert.deepEqual(calls.map((item) => item.type), ["search", "switch", "screen"]);
1132
- assert.equal(calls[0].pageScope, "featured");
1133
- assert.equal(calls[2].pageScope, "featured");
1134
- assert.equal(result.result.selected_page, "featured");
1135
- assert.equal(result.result.active_tab_status, "3");
1136
- assert.equal(result.result.resume_source, "network");
1137
- }
1138
-
1139
- async function testLatestPipelineShouldRunSearchThenSwitchTabThenScreen() {
1140
- const calls = [];
1141
- const result = await runRecommendPipeline(
1142
- {
1143
- workspaceRoot: process.cwd(),
1144
- instruction: "test",
1145
- confirmation: {
1146
- ...createJobConfirmedConfirmation(),
1147
- page_confirmed: true,
1148
- page_value: "latest"
1149
- },
1150
- overrides: {
1151
- page_scope: "latest"
1152
- }
1153
- },
1154
- {
1155
- parseRecommendInstruction: () => createParsed({
1156
- page_scope: "latest",
1157
- proposed_page_scope: "latest"
1158
- }),
1159
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1160
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1161
- listRecommendJobs: async () => createJobListResult(),
1162
- runRecommendSearchCli: async ({ pageScope }) => {
1163
- calls.push({ type: "search", pageScope });
1164
- return {
1165
- ok: true,
1166
- summary: {
1167
- candidate_count: 8,
1168
- applied_filters: { degree: ["本科"] },
1169
- page_state: { state: "RECOMMEND_READY" }
1170
- }
1171
- };
1172
- },
1173
- readRecommendTabState: async () => ({
1174
- ok: true,
1175
- active_status: "0",
1176
- tab_state: { active_status: "0" }
1177
- }),
1178
- switchRecommendTab: async (_workspaceRoot, { target_status }) => {
1179
- calls.push({ type: "switch", target_status });
1180
- return {
1181
- ok: true,
1182
- state: "TAB_SWITCHED",
1183
- active_status: "1",
1184
- tab_state: { active_status: "1" }
1185
- };
1186
- },
1187
- runRecommendScreenCli: async ({ pageScope }) => {
1188
- calls.push({ type: "screen", pageScope });
1189
- return {
1190
- ok: true,
1191
- summary: {
1192
- processed_count: 8,
1193
- passed_count: 3,
1194
- skipped_count: 5,
1195
- output_csv: "C:/temp/result.csv",
1196
- completion_reason: "page_exhausted",
1197
- active_tab_status: "1",
1198
- resume_source: "image_fallback"
1199
- }
1200
- };
1201
- }
1202
- }
1203
- );
1204
-
1205
- assert.equal(result.status, "COMPLETED");
1206
- assert.deepEqual(calls.map((item) => item.type), ["search", "switch", "screen"]);
1207
- assert.equal(calls[0].pageScope, "latest");
1208
- assert.equal(calls[1].target_status, "1");
1209
- assert.equal(calls[2].pageScope, "latest");
1210
- assert.equal(result.result.selected_page, "latest");
1211
- assert.equal(result.result.active_tab_status, "1");
1212
- assert.equal(result.result.resume_source, "image_fallback");
1213
- }
1214
-
1215
- async function testPipelineShouldPassInputSummaryToScreenCli() {
1216
- let capturedInputSummary = null;
1217
- const result = await runRecommendPipeline(
1218
- {
1219
- workspaceRoot: process.cwd(),
1220
- instruction: "帮我找有 AI/LLM 项目经验的人",
1221
- confirmation: createJobConfirmedConfirmation(),
1222
- overrides: {}
1223
- },
1224
- {
1225
- parseRecommendInstruction: () => createParsed(),
1226
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1227
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1228
- listRecommendJobs: async () => createJobListResult(),
1229
- runRecommendSearchCli: async () => ({
1230
- ok: true,
1231
- summary: {
1232
- candidate_count: 3,
1233
- applied_filters: {
1234
- school_tag: ["985"],
1235
- degree: ["本科"],
1236
- gender: "男",
1237
- recent_not_view: "近14天没有"
1238
- }
1239
- }
1240
- }),
1241
- runRecommendScreenCli: async ({ inputSummary }) => {
1242
- capturedInputSummary = inputSummary;
1243
- return {
1244
- ok: true,
1245
- summary: {
1246
- processed_count: 3,
1247
- passed_count: 1,
1248
- skipped_count: 2,
1249
- output_csv: "C:/temp/result.csv",
1250
- active_tab_status: "0",
1251
- resume_source: "network"
1252
- }
1253
- };
1254
- }
1255
- }
1256
- );
1257
-
1258
- assert.equal(result.status, "COMPLETED");
1259
- assert.equal(Boolean(capturedInputSummary), true);
1260
- assert.equal(capturedInputSummary.instruction, "帮我找有 AI/LLM 项目经验的人");
1261
- assert.equal(capturedInputSummary.selected_page, "recommend");
1262
- assert.equal(capturedInputSummary.selected_job?.title, "数据分析实习生 _ 杭州");
1263
- assert.equal(capturedInputSummary.user_search_params?.recent_not_view, "近14天没有");
1264
- assert.equal(capturedInputSummary.effective_search_params?.recent_not_view, "近14天没有");
1265
- assert.equal(capturedInputSummary.screen_params?.criteria, "候选人需要有大模型平台经验");
1266
- assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "baseUrl"), false);
1267
- assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "apiKey"), false);
1268
- assert.equal(Object.prototype.hasOwnProperty.call(capturedInputSummary, "model"), false);
1269
- }
1270
-
1271
- async function testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue() {
1272
- const calls = [];
1273
- let preflightCallCount = 0;
1274
- const result = await runRecommendPipeline(
1275
- {
1276
- workspaceRoot: process.cwd(),
1277
- instruction: "test",
1278
- confirmation: {
1279
- ...createJobConfirmedConfirmation(),
1280
- page_confirmed: true,
1281
- page_value: "featured"
1282
- },
1283
- overrides: {}
1284
- },
1285
- {
1286
- parseRecommendInstruction: () => createParsed({
1287
- page_scope: "featured",
1288
- proposed_page_scope: "featured"
1289
- }),
1290
- runPipelinePreflight: () => {
1291
- preflightCallCount += 1;
1292
- if (preflightCallCount === 1) {
1293
- return {
1294
- ok: false,
1295
- debug_port: 9222,
1296
- page_scope: "featured",
1297
- checks: [
1298
- {
1299
- key: "favorite_calibration",
1300
- ok: false,
1301
- path: "C:/Users/test/.codex/boss-recommend-mcp/favorite-calibration.json",
1302
- message: "favorite-calibration.json 不存在或无效"
1303
- }
1304
- ]
1305
- };
1306
- }
1307
- return {
1308
- ok: true,
1309
- debug_port: 9222,
1310
- page_scope: "featured",
1311
- checks: []
1312
- };
1313
- },
1314
- ensureFeaturedCalibrationReady: async () => {
1315
- calls.push({ type: "calibrate" });
1316
- return {
1317
- ok: true,
1318
- calibration_path: "C:/Users/test/.codex/boss-recommend-mcp/favorite-calibration.json",
1319
- auto_started: true
1320
- };
1321
- },
1322
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1323
- listRecommendJobs: async () => createJobListResult(),
1324
- runRecommendSearchCli: async () => {
1325
- calls.push({ type: "search" });
1326
- return {
1327
- ok: true,
1328
- summary: { candidate_count: 2, applied_filters: {} }
1329
- };
1330
- },
1331
- readRecommendTabState: async () => ({
1332
- ok: true,
1333
- active_status: "3",
1334
- tab_state: { active_status: "3" }
1335
- }),
1336
- switchRecommendTab: async () => ({
1337
- ok: true,
1338
- state: "TAB_SWITCHED",
1339
- active_status: "3",
1340
- tab_state: { active_status: "3" }
1341
- }),
1342
- runRecommendScreenCli: async () => {
1343
- calls.push({ type: "screen" });
1344
- return {
1345
- ok: true,
1346
- summary: {
1347
- processed_count: 2,
1348
- passed_count: 1,
1349
- skipped_count: 1,
1350
- output_csv: "C:/temp/result.csv",
1351
- active_tab_status: "3",
1352
- resume_source: "network"
1353
- }
1354
- };
1355
- }
1356
- }
1357
- );
1358
-
1359
- assert.equal(result.status, "COMPLETED");
1360
- assert.equal(preflightCallCount, 2);
1361
- assert.deepEqual(calls.map((item) => item.type), ["calibrate", "search", "screen"]);
1362
- }
1363
-
1364
- async function testFeaturedCalibrationFailureShouldReturnCalibrationRequired() {
1365
- let searchCalled = false;
1366
- const result = await runRecommendPipeline(
1367
- {
1368
- workspaceRoot: process.cwd(),
1369
- instruction: "test",
1370
- confirmation: {
1371
- ...createJobConfirmedConfirmation(),
1372
- page_confirmed: true,
1373
- page_value: "featured"
1374
- },
1375
- overrides: {}
1376
- },
1377
- {
1378
- parseRecommendInstruction: () => createParsed({
1379
- page_scope: "featured",
1380
- proposed_page_scope: "featured"
1381
- }),
1382
- runPipelinePreflight: () => ({
1383
- ok: false,
1384
- debug_port: 9222,
1385
- page_scope: "featured",
1386
- checks: [
1387
- {
1388
- key: "favorite_calibration",
1389
- ok: false,
1390
- path: "C:/Users/test/.codex/boss-recommend-mcp/favorite-calibration.json",
1391
- message: "favorite-calibration.json 不存在或无效"
1392
- }
1393
- ]
1394
- }),
1395
- ensureFeaturedCalibrationReady: async () => ({
1396
- ok: false,
1397
- calibration_path: "C:/Users/test/.codex/boss-recommend-mcp/favorite-calibration.json",
1398
- calibration_script_path: "C:/Users/test/boss-recruit-mcp/vendor/boss-screen-cli/calibrate-favorite-position-v2.cjs",
1399
- error: {
1400
- code: "CALIBRATION_REQUIRED",
1401
- message: "校准失败"
1402
- }
1403
- }),
1404
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1405
- listRecommendJobs: async () => createJobListResult(),
1406
- runRecommendSearchCli: async () => {
1407
- searchCalled = true;
1408
- return { ok: true, summary: {} };
1409
- },
1410
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1411
- }
1412
- );
1413
-
1414
- assert.equal(result.status, "FAILED");
1415
- assert.equal(result.error.code, "CALIBRATION_REQUIRED");
1416
- assert.equal(result.required_user_action, "run_featured_calibration");
1417
- assert.equal(result.guidance.calibration_path.includes("favorite-calibration.json"), true);
1418
- assert.equal(searchCalled, false);
1419
- }
1420
-
1421
- async function testFeaturedTabSwitchFailureShouldReturnRetryableError() {
1422
- const result = await runRecommendPipeline(
1423
- {
1424
- workspaceRoot: process.cwd(),
1425
- instruction: "test",
1426
- confirmation: {
1427
- ...createJobConfirmedConfirmation(),
1428
- page_confirmed: true,
1429
- page_value: "featured"
1430
- },
1431
- overrides: {
1432
- page_scope: "featured"
1433
- }
1434
- },
1435
- {
1436
- parseRecommendInstruction: () => createParsed({
1437
- page_scope: "featured",
1438
- proposed_page_scope: "featured"
1439
- }),
1440
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1441
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1442
- listRecommendJobs: async () => createJobListResult(),
1443
- runRecommendSearchCli: async () => ({
1444
- ok: true,
1445
- summary: { candidate_count: 3, applied_filters: {} }
1446
- }),
1447
- readRecommendTabState: async () => ({
1448
- ok: true,
1449
- active_status: "0",
1450
- tab_state: { active_status: "0" }
1451
- }),
1452
- switchRecommendTab: async () => ({
1453
- ok: false,
1454
- state: "TAB_NOT_FOUND",
1455
- message: "未找到精选 tab。"
1456
- }),
1457
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1458
- }
1459
- );
1460
-
1461
- assert.equal(result.status, "FAILED");
1462
- assert.equal(result.error.code, "TAB_NOT_FOUND");
1463
- assert.equal(result.required_user_action, "retry_switch_recommend_tab");
1464
- assert.equal(result.selected_page, "featured");
1465
- }
1466
-
1467
- async function testCompletedPipeline() {
1468
- const calls = [];
1469
- const result = await runRecommendPipeline(
1470
- {
1471
- workspaceRoot: process.cwd(),
1472
- instruction: "test",
1473
- confirmation: createJobConfirmedConfirmation(),
1474
- overrides: {}
1475
- },
1476
- {
1477
- parseRecommendInstruction: () => createParsed(),
1478
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1479
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: { state: "RECOMMEND_READY" } }),
1480
- listRecommendJobs: async () => createJobListResult(),
1481
- runRecommendSearchCli: async ({ searchParams, selectedJob }) => {
1482
- calls.push({ type: "search", searchParams, selectedJob });
1483
- return {
1484
- ok: true,
1485
- summary: {
1486
- candidate_count: 18,
1487
- applied_filters: searchParams,
1488
- selected_job: {
1489
- value: "job-data-analyst",
1490
- title: "数据分析实习生 _ 杭州"
1491
- },
1492
- page_state: { state: "RECOMMEND_READY" }
1493
- }
1494
- };
1495
- },
1496
- runRecommendScreenCli: async ({ screenParams }) => {
1497
- calls.push({ type: "screen", screenParams });
1498
- return {
1499
- ok: true,
1500
- summary: {
1501
- processed_count: 10,
1502
- passed_count: 3,
1503
- skipped_count: 7,
1504
- output_csv: "C:/temp/result.csv",
1505
- completion_reason: "page_exhausted"
1506
- }
1507
- };
1508
- }
1509
- }
1510
- );
1511
-
1512
- assert.equal(result.status, "COMPLETED");
1513
- assert.equal(result.result.candidate_count, 18);
1514
- assert.equal(result.result.processed_count, 10);
1515
- assert.equal(result.result.passed_count, 3);
1516
- assert.equal(result.result.post_action, "favorite");
1517
- assert.deepEqual(result.result.applied_filters.degree, ["本科"]);
1518
- assert.equal(result.result.selected_job.title, "数据分析实习生 _ 杭州");
1519
- assert.equal(calls[0].selectedJob, "job-data-analyst");
1520
- assert.equal(calls[0].type, "search");
1521
- assert.equal(calls[1].type, "screen");
1522
- }
1523
-
1524
- async function testSearchFailure() {
1525
- let searchCallCount = 0;
1526
- const result = await runRecommendPipeline(
1527
- {
1528
- workspaceRoot: process.cwd(),
1529
- instruction: "test",
1530
- confirmation: createJobConfirmedConfirmation(),
1531
- overrides: {}
1532
- },
1533
- {
1534
- parseRecommendInstruction: () => createParsed(),
1535
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1536
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1537
- listRecommendJobs: async () => createJobListResult(),
1538
- runRecommendSearchCli: async () => {
1539
- searchCallCount += 1;
1540
- return {
1541
- ok: false,
1542
- stdout: "",
1543
- stderr: "boom",
1544
- structured: null,
1545
- error: {
1546
- code: "RECOMMEND_FILTER_PANEL_UNAVAILABLE",
1547
- message: "筛选面板不可用。"
1548
- }
1549
- };
1550
- },
1551
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1552
- }
1553
- );
1554
-
1555
- assert.equal(result.status, "FAILED");
1556
- assert.equal(result.error.code, "RECOMMEND_FILTER_PANEL_UNAVAILABLE");
1557
- assert.equal(searchCallCount, 3);
1558
- }
1559
-
1560
- async function testSearchFilterFailureShouldRetryAndRecover() {
1561
- let searchCallCount = 0;
1562
- const result = await runRecommendPipeline(
1563
- {
1564
- workspaceRoot: process.cwd(),
1565
- instruction: "test",
1566
- confirmation: createJobConfirmedConfirmation(),
1567
- overrides: {}
1568
- },
1569
- {
1570
- parseRecommendInstruction: () => createParsed(),
1571
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1572
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1573
- listRecommendJobs: async () => createJobListResult(),
1574
- runRecommendSearchCli: async () => {
1575
- searchCallCount += 1;
1576
- if (searchCallCount === 1) {
1577
- return {
1578
- ok: false,
1579
- stdout: "",
1580
- stderr: "FILTER_CONFIRM_FAILED",
1581
- structured: null,
1582
- error: {
1583
- code: "FILTER_CONFIRM_FAILED",
1584
- message: "FILTER_CONFIRM_FAILED"
1585
- }
1586
- };
1587
- }
1588
- return {
1589
- ok: true,
1590
- summary: {
1591
- candidate_count: 6,
1592
- applied_filters: {}
1593
- }
1594
- };
1595
- },
1596
- runRecommendScreenCli: async () => ({
1597
- ok: true,
1598
- summary: {
1599
- processed_count: 3,
1600
- passed_count: 2,
1601
- skipped_count: 1,
1602
- output_csv: "C:/temp/search-filter-retry.csv"
1603
- }
1604
- })
1605
- }
1606
- );
1607
-
1608
- assert.equal(result.status, "COMPLETED");
1609
- assert.equal(searchCallCount, 2);
1610
- assert.equal(result.result.auto_recovery.trigger, "SEARCH_FILTER_RETRY");
1611
- assert.equal(result.result.auto_recovery.attempt, 1);
1612
- }
1613
-
1614
- async function testSearchNoIframeWithLoginShouldReturnLoginRequired() {
1615
- const result = await runRecommendPipeline(
1616
- {
1617
- workspaceRoot: process.cwd(),
1618
- instruction: "test",
1619
- confirmation: createJobConfirmedConfirmation(),
1620
- overrides: {}
1621
- },
1622
- {
1623
- parseRecommendInstruction: () => createParsed(),
1624
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1625
- ensureBossRecommendPageReady: async () => ({
1626
- ok: false,
1627
- debug_port: 9222,
1628
- state: "LOGIN_REQUIRED",
1629
- page_state: {
1630
- state: "LOGIN_REQUIRED",
1631
- expected_url: "https://www.zhipin.com/web/chat/recommend",
1632
- current_url: "https://www.zhipin.com/web/user/?ka=bticket",
1633
- login_url: "https://www.zhipin.com/web/user/?ka=bticket"
1634
- }
1635
- }),
1636
- listRecommendJobs: async () => createJobListResult(),
1637
- runRecommendSearchCli: async () => ({
1638
- ok: false,
1639
- stdout: "",
1640
- stderr: "NO_RECOMMEND_IFRAME",
1641
- structured: null,
1642
- error: {
1643
- code: "NO_RECOMMEND_IFRAME",
1644
- message: "NO_RECOMMEND_IFRAME"
1645
- }
1646
- }),
1647
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1648
- }
1649
- );
1650
-
1651
- assert.equal(result.status, "FAILED");
1652
- assert.equal(result.error.code, "BOSS_LOGIN_REQUIRED");
1653
- assert.equal(result.required_user_action, "prepare_boss_recommend_page");
1654
- assert.equal(result.guidance.agent_prompt.includes("https://www.zhipin.com/web/user/?ka=bticket"), true);
1655
- }
1656
-
1657
- async function testSearchNoIframeShouldRetryOnceWhenPageRecheckReady() {
1658
- let searchCallCount = 0;
1659
- let recheckCount = 0;
1660
- const result = await runRecommendPipeline(
1661
- {
1662
- workspaceRoot: process.cwd(),
1663
- instruction: "test",
1664
- confirmation: createJobConfirmedConfirmation(),
1665
- overrides: {}
1666
- },
1667
- {
1668
- parseRecommendInstruction: () => createParsed(),
1669
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1670
- ensureBossRecommendPageReady: async () => {
1671
- recheckCount += 1;
1672
- return {
1673
- ok: true,
1674
- debug_port: 9222,
1675
- state: "RECOMMEND_READY",
1676
- page_state: {
1677
- state: "RECOMMEND_READY",
1678
- expected_url: "https://www.zhipin.com/web/chat/recommend",
1679
- current_url: "https://www.zhipin.com/web/chat/recommend"
1680
- }
1681
- };
1682
- },
1683
- listRecommendJobs: async () => createJobListResult(),
1684
- runRecommendSearchCli: async () => {
1685
- searchCallCount += 1;
1686
- if (searchCallCount === 1) {
1687
- return {
1688
- ok: false,
1689
- stdout: "",
1690
- stderr: "NO_RECOMMEND_IFRAME",
1691
- structured: null,
1692
- error: {
1693
- code: "NO_RECOMMEND_IFRAME",
1694
- message: "NO_RECOMMEND_IFRAME"
1695
- }
1696
- };
1697
- }
1698
- return {
1699
- ok: true,
1700
- summary: {
1701
- candidate_count: 5,
1702
- applied_filters: {}
1703
- }
1704
- };
1705
- },
1706
- runRecommendScreenCli: async () => ({
1707
- ok: true,
1708
- summary: {
1709
- processed_count: 2,
1710
- passed_count: 1,
1711
- skipped_count: 1,
1712
- output_csv: "C:/temp/retry.csv"
1713
- }
1714
- })
1715
- }
1716
- );
1717
-
1718
- assert.equal(result.status, "COMPLETED");
1719
- assert.equal(searchCallCount, 2);
1720
- assert.equal(recheckCount >= 2, true);
1721
- }
1722
-
1723
- async function testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin() {
1724
- const result = await runRecommendPipeline(
1725
- {
1726
- workspaceRoot: process.cwd(),
1727
- instruction: "test",
1728
- confirmation: {},
1729
- overrides: {}
1730
- },
1731
- {
1732
- parseRecommendInstruction: () => createParsed(),
1733
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1734
- ensureBossRecommendPageReady: async () => ({
1735
- ok: false,
1736
- debug_port: 9222,
1737
- state: "LOGIN_REQUIRED",
1738
- page_state: {
1739
- state: "LOGIN_REQUIRED",
1740
- expected_url: "https://www.zhipin.com/web/chat/recommend",
1741
- current_url: "https://www.zhipin.com/web/user/?ka=bticket",
1742
- login_url: "https://www.zhipin.com/web/user/?ka=bticket"
1743
- }
1744
- }),
1745
- listRecommendJobs: async () => ({
1746
- ok: false,
1747
- stdout: "",
1748
- stderr: "",
1749
- structured: null,
1750
- jobs: [],
1751
- error: {
1752
- code: "JOB_TRIGGER_NOT_FOUND",
1753
- message: "JOB_TRIGGER_NOT_FOUND"
1754
- }
1755
- }),
1756
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1757
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1758
- }
1759
- );
1760
-
1761
- assert.equal(result.status, "FAILED");
1762
- assert.equal(result.error.code, "BOSS_LOGIN_REQUIRED");
1763
- assert.equal(result.required_user_action, "prepare_boss_recommend_page");
1764
- assert.equal(result.guidance.agent_prompt.includes("https://www.zhipin.com/web/user/?ka=bticket"), true);
1765
- }
1766
-
1767
- async function testNeedJobConfirmationGate() {
1768
- const result = await runRecommendPipeline(
1769
- {
1770
- workspaceRoot: process.cwd(),
1771
- instruction: "test",
1772
- confirmation: {},
1773
- overrides: {}
1774
- },
1775
- {
1776
- parseRecommendInstruction: () => createParsed(),
1777
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1778
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1779
- listRecommendJobs: async () => createJobListResult(),
1780
- readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
1781
- switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
1782
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1783
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1784
- }
1785
- );
1786
-
1787
- assert.equal(result.status, "NEED_CONFIRMATION");
1788
- assert.equal(result.required_confirmations.includes("job"), true);
1789
- assert.equal(result.pending_questions.some((item) => item.field === "job"), true);
1790
- assert.equal(Array.isArray(result.job_options), true);
1791
- assert.equal(result.job_options.length, 2);
1792
- }
1793
-
1794
- async function testNeedFinalReviewConfirmationGate() {
1795
- const result = await runRecommendPipeline(
1796
- {
1797
- workspaceRoot: process.cwd(),
1798
- instruction: "test",
1799
- confirmation: createJobConfirmedWithoutFinalConfirmation(),
1800
- overrides: {}
1801
- },
1802
- {
1803
- parseRecommendInstruction: () => createParsed(),
1804
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1805
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1806
- listRecommendJobs: async () => createJobListResult(),
1807
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1808
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1809
- }
1810
- );
1811
-
1812
- assert.equal(result.status, "NEED_CONFIRMATION");
1813
- assert.equal(result.required_confirmations.includes("final_review"), true);
1814
- assert.equal(result.pending_questions.some((item) => item.field === "final_review"), true);
1815
- }
1816
-
1817
- async function testLoginRequiredShouldReturnGuidance() {
1818
- const result = await runRecommendPipeline(
1819
- {
1820
- workspaceRoot: process.cwd(),
1821
- instruction: "test",
1822
- confirmation: {},
1823
- overrides: {}
1824
- },
1825
- {
1826
- parseRecommendInstruction: () => createParsed(),
1827
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
1828
- ensureBossRecommendPageReady: async () => ({
1829
- ok: false,
1830
- debug_port: 9555,
1831
- state: "LOGIN_REQUIRED",
1832
- page_state: {
1833
- state: "LOGIN_REQUIRED",
1834
- expected_url: "https://www.zhipin.com/web/chat/recommend",
1835
- current_url: "https://www.zhipin.com/web/geek/job",
1836
- login_url: "https://www.zhipin.com/web/user/?ka=bticket"
1837
- }
1838
- }),
1839
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1840
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1841
- }
1842
- );
1843
-
1844
- assert.equal(result.status, "FAILED");
1845
- assert.equal(result.error.code, "BOSS_LOGIN_REQUIRED");
1846
- assert.equal(result.required_user_action, "prepare_boss_recommend_page");
1847
- assert.equal(result.guidance.debug_port, 9555);
1848
- assert.equal(result.guidance.expected_url, "https://www.zhipin.com/web/chat/recommend");
1849
- assert.equal(result.guidance.agent_prompt.includes("9555"), true);
1850
- assert.equal(result.guidance.agent_prompt.includes("https://www.zhipin.com/web/user/?ka=bticket"), true);
1851
- assert.equal(result.guidance.agent_prompt.includes("已就绪"), true);
1852
- }
1853
-
1854
- async function testDebugPortUnreachableShouldReturnConnectionCode() {
1855
- const result = await runRecommendPipeline(
1856
- {
1857
- workspaceRoot: process.cwd(),
1858
- instruction: "test",
1859
- confirmation: {},
1860
- overrides: {}
1861
- },
1862
- {
1863
- parseRecommendInstruction: () => createParsed(),
1864
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
1865
- ensureBossRecommendPageReady: async () => ({
1866
- ok: false,
1867
- debug_port: 9222,
1868
- state: "DEBUG_PORT_UNREACHABLE",
1869
- page_state: {
1870
- state: "DEBUG_PORT_UNREACHABLE",
1871
- expected_url: "https://www.zhipin.com/web/chat/recommend",
1872
- current_url: null
1873
- }
1874
- }),
1875
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1876
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1877
- }
1878
- );
1879
-
1880
- assert.equal(result.status, "FAILED");
1881
- assert.equal(result.error.code, "BOSS_CHROME_NOT_CONNECTED");
1882
- assert.equal(result.required_user_action, "prepare_boss_recommend_page");
1883
- assert.equal(result.guidance.agent_prompt.includes("--remote-debugging-port=9222"), true);
1884
- }
1885
-
1886
- async function testPreflightRecoveryPlanOrder() {
1887
- const result = await runRecommendPipeline(
1888
- {
1889
- workspaceRoot: "C:/workspace/boss-recommend-mcp",
1890
- instruction: "test",
1891
- confirmation: {},
1892
- overrides: {}
1893
- },
1894
- {
1895
- parseRecommendInstruction: () => createParsed(),
1896
- runPipelinePreflight: () => ({
1897
- ok: false,
1898
- debug_port: 9222,
1899
- checks: [
1900
- { key: "node_cli", ok: false },
1901
- { key: "npm_dep_ws", ok: false, install_cwd: "C:/workspace/boss-recommend-mcp" },
1902
- { key: "npm_dep_sharp", ok: false, install_cwd: "C:/workspace/boss-recommend-mcp" }
1903
- ]
1904
- }),
1905
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1906
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1907
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1908
- }
1909
- );
1910
-
1911
- assert.equal(result.status, "FAILED");
1912
- assert.equal(result.error.code, "PIPELINE_PREFLIGHT_FAILED");
1913
- assert.deepEqual(
1914
- result.diagnostics.recovery.ordered_steps.map((item) => item.id),
1915
- ["install_nodejs", "install_npm_dependencies"]
1916
- );
1917
- assert.deepEqual(result.diagnostics.recovery.ordered_steps[1].blocked_by, ["install_nodejs"]);
1918
- assert.equal(result.diagnostics.recovery.agent_prompt.includes("不要并行跳步"), true);
1919
- }
1920
-
1921
- async function testPreflightAutoRepairCanUnblockPipeline() {
1922
- let repairCalled = false;
1923
- const result = await runRecommendPipeline(
1924
- {
1925
- workspaceRoot: process.cwd(),
1926
- instruction: "test",
1927
- confirmation: createJobConfirmedConfirmation(),
1928
- overrides: {}
1929
- },
1930
- {
1931
- parseRecommendInstruction: () => createParsed(),
1932
- runPipelinePreflight: () => ({
1933
- ok: false,
1934
- debug_port: 9222,
1935
- checks: [{ key: "npm_dep_ws", ok: false, install_cwd: process.cwd() }]
1936
- }),
1937
- attemptPipelineAutoRepair: () => {
1938
- repairCalled = true;
1939
- return {
1940
- attempted: true,
1941
- actions: [{ ok: true, action: "install_npm_dependencies" }],
1942
- preflight: { ok: true, debug_port: 9222, checks: [] }
1943
- };
1944
- },
1945
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1946
- listRecommendJobs: async () => createJobListResult(),
1947
- runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
1948
- runRecommendScreenCli: async () => ({ ok: true, summary: { processed_count: 1, passed_count: 1, skipped_count: 0 } })
1949
- }
1950
- );
1951
-
1952
- assert.equal(repairCalled, true);
1953
- assert.equal(result.status, "COMPLETED");
1954
- }
1955
-
1956
- async function testPreflightAutoRepairStillFailShouldExposeDiagnostics() {
1957
- const result = await runRecommendPipeline(
1958
- {
1959
- workspaceRoot: process.cwd(),
1960
- instruction: "test",
1961
- confirmation: {},
1962
- overrides: {}
1963
- },
1964
- {
1965
- parseRecommendInstruction: () => createParsed(),
1966
- runPipelinePreflight: () => ({
1967
- ok: false,
1968
- debug_port: 9222,
1969
- checks: [{ key: "node_cli", ok: false }]
1970
- }),
1971
- attemptPipelineAutoRepair: () => ({
1972
- attempted: true,
1973
- actions: [{ ok: false, action: "install_npm_dependencies" }],
1974
- preflight: {
1975
- ok: false,
1976
- debug_port: 9222,
1977
- checks: [{ key: "node_cli", ok: false }]
1978
- }
1979
- }),
1980
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
1981
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
1982
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
1983
- }
1984
- );
1985
-
1986
- assert.equal(result.status, "FAILED");
1987
- assert.equal(result.error.code, "PIPELINE_PREFLIGHT_FAILED");
1988
- assert.equal(result.diagnostics.auto_repair.attempted, true);
1989
- }
1990
-
1991
- async function testScreenConfigFailureShouldRequireUserProvidedConfig() {
1992
- const result = await runRecommendPipeline(
1993
- {
1994
- workspaceRoot: process.cwd(),
1995
- instruction: "test",
1996
- confirmation: {},
1997
- overrides: {}
1998
- },
1999
- {
2000
- parseRecommendInstruction: () => createParsed(),
2001
- runPipelinePreflight: () => ({
2002
- ok: false,
2003
- debug_port: 9222,
2004
- checks: [
2005
- {
2006
- key: "screen_config",
2007
- ok: false,
2008
- path: "C:/Users/test/.boss-recommend-mcp/screening-config.json",
2009
- reason: "MISSING_REQUIRED_FIELDS",
2010
- message: "screening-config.json 缺失或格式无效"
2011
- }
2012
- ]
2013
- }),
2014
- attemptPipelineAutoRepair: () => ({
2015
- attempted: false,
2016
- actions: [],
2017
- preflight: {
2018
- ok: false,
2019
- debug_port: 9222,
2020
- checks: [
2021
- {
2022
- key: "screen_config",
2023
- ok: false,
2024
- path: "C:/Users/test/.boss-recommend-mcp/screening-config.json",
2025
- reason: "MISSING_REQUIRED_FIELDS",
2026
- message: "screening-config.json 缺失或格式无效"
2027
- }
2028
- ]
2029
- }
2030
- }),
2031
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2032
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
2033
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
2034
- }
2035
- );
2036
-
2037
- assert.equal(result.status, "FAILED");
2038
- assert.equal(result.error.code, "PIPELINE_PREFLIGHT_FAILED");
2039
- assert.equal(result.required_user_action, "provide_screening_config");
2040
- assert.equal(result.guidance.config_path.includes("screening-config.json"), true);
2041
- assert.equal(result.guidance.config_dir.includes(".boss-recommend-mcp"), true);
2042
- assert.equal(result.guidance.agent_prompt.includes("baseUrl"), true);
2043
- assert.equal(result.guidance.agent_prompt.includes("apiKey"), true);
2044
- assert.equal(result.guidance.agent_prompt.includes("model"), true);
2045
- }
2046
-
2047
- async function testScreenConfigPlaceholderShouldRequireUserConfirmationAfterUpdate() {
2048
- const result = await runRecommendPipeline(
2049
- {
2050
- workspaceRoot: process.cwd(),
2051
- instruction: "test",
2052
- confirmation: {},
2053
- overrides: {}
2054
- },
2055
- {
2056
- parseRecommendInstruction: () => createParsed(),
2057
- runPipelinePreflight: () => ({
2058
- ok: false,
2059
- debug_port: 9222,
2060
- checks: [
2061
- {
2062
- key: "screen_config",
2063
- ok: false,
2064
- path: "C:/Users/test/workspace/config/screening-config.json",
2065
- reason: "PLACEHOLDER_API_KEY",
2066
- message: "screening-config.json 的 apiKey 仍是模板占位符,请填写真实 API Key。"
2067
- }
2068
- ]
2069
- }),
2070
- attemptPipelineAutoRepair: () => ({
2071
- attempted: false,
2072
- actions: [],
2073
- preflight: {
2074
- ok: false,
2075
- debug_port: 9222,
2076
- checks: [
2077
- {
2078
- key: "screen_config",
2079
- ok: false,
2080
- path: "C:/Users/test/workspace/config/screening-config.json",
2081
- reason: "PLACEHOLDER_API_KEY",
2082
- message: "screening-config.json 的 apiKey 仍是模板占位符,请填写真实 API Key。"
2083
- }
2084
- ]
2085
- }
2086
- }),
2087
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2088
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
2089
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
2090
- }
2091
- );
2092
-
2093
- assert.equal(result.status, "FAILED");
2094
- assert.equal(result.error.code, "PIPELINE_PREFLIGHT_FAILED");
2095
- assert.equal(result.required_user_action, "confirm_screening_config_updated");
2096
- assert.equal(result.guidance.config_dir, "C:/Users/test/workspace/config");
2097
- assert.equal(result.guidance.agent_prompt.includes("已修改完成"), true);
2098
- }
2099
-
2100
- async function testScreenConfigRecoveryStepShouldBeFirst() {
2101
- const result = await runRecommendPipeline(
2102
- {
2103
- workspaceRoot: "C:/workspace/boss-recommend-mcp",
2104
- instruction: "test",
2105
- confirmation: {},
2106
- overrides: {}
2107
- },
2108
- {
2109
- parseRecommendInstruction: () => createParsed(),
2110
- runPipelinePreflight: () => ({
2111
- ok: false,
2112
- debug_port: 9222,
2113
- checks: [
2114
- {
2115
- key: "screen_config",
2116
- ok: false,
2117
- path: "C:/Users/test/.boss-recommend-mcp/screening-config.json",
2118
- message: "screening-config.json 缺失或格式无效"
2119
- },
2120
- { key: "node_cli", ok: false }
2121
- ]
2122
- }),
2123
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2124
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
2125
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
2126
- }
2127
- );
2128
-
2129
- assert.equal(result.status, "FAILED");
2130
- assert.equal(result.diagnostics.recovery.ordered_steps[0].id, "fill_screening_config");
2131
- }
2132
-
2133
- async function testFollowUpChatMissingCriteriaShouldNeedInput() {
2134
- const result = await runRecommendPipeline(
2135
- {
2136
- workspaceRoot: process.cwd(),
2137
- instruction: "test",
2138
- confirmation: {},
2139
- overrides: {},
2140
- followUp: createFollowUpChat({ criteria: "" })
2141
- },
2142
- {
2143
- parseRecommendInstruction: () => createParsed()
2144
- }
2145
- );
2146
-
2147
- assert.equal(result.status, "NEED_INPUT");
2148
- assert.equal(result.missing_fields.includes("follow_up.chat.criteria"), true);
2149
- assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.criteria"), true);
2150
- }
2151
-
2152
- async function testFollowUpChatMissingFieldsShouldExposeRecommendDefaults() {
2153
- const result = await runRecommendPipeline(
2154
- {
2155
- workspaceRoot: process.cwd(),
2156
- instruction: "test",
2157
- confirmation: {},
2158
- overrides: {},
2159
- followUp: createFollowUpChat({
2160
- criteria: "",
2161
- start_from: "",
2162
- target_count: null
2163
- })
2164
- },
2165
- {
2166
- parseRecommendInstruction: () => createParsed({
2167
- screenParams: {
2168
- criteria: "默认沿用 recommend 的筛选条件",
2169
- target_count: 18,
2170
- post_action: "favorite",
2171
- max_greet_count: null
2172
- }
2173
- })
2174
- }
2175
- );
2176
-
2177
- assert.equal(result.status, "NEED_INPUT");
2178
- assert.equal(result.missing_fields.includes("follow_up.chat.criteria"), true);
2179
- assert.equal(result.missing_fields.includes("follow_up.chat.start_from"), true);
2180
- assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2181
- const criteriaQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.criteria");
2182
- const startFromQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.start_from");
2183
- const targetCountQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2184
- assert.equal(criteriaQuestion?.value, "默认沿用 recommend 的筛选条件");
2185
- assert.equal(startFromQuestion?.value, "unread");
2186
- assert.equal(targetCountQuestion?.value, "通过筛选数");
2187
- assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2188
- assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes("通过筛选数(推荐)")), true);
2189
- assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes('follow_up.chat.target_count="all"')), true);
2190
- }
2191
-
2192
- async function testFollowUpChatMissingStartFromShouldNeedInput() {
2193
- const result = await runRecommendPipeline(
2194
- {
2195
- workspaceRoot: process.cwd(),
2196
- instruction: "test",
2197
- confirmation: {},
2198
- overrides: {},
2199
- followUp: createFollowUpChat({ start_from: "" })
2200
- },
2201
- {
2202
- parseRecommendInstruction: () => createParsed()
2203
- }
2204
- );
2205
-
2206
- assert.equal(result.status, "NEED_INPUT");
2207
- assert.equal(result.missing_fields.includes("follow_up.chat.start_from"), true);
2208
- assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.start_from"), true);
2209
- }
2210
-
2211
- async function testFollowUpChatMissingTargetCountShouldNeedInput() {
2212
- const result = await runRecommendPipeline(
2213
- {
2214
- workspaceRoot: process.cwd(),
2215
- instruction: "test",
2216
- confirmation: {},
2217
- overrides: {},
2218
- followUp: createFollowUpChat({ target_count: null })
2219
- },
2220
- {
2221
- parseRecommendInstruction: () => createParsed()
2222
- }
2223
- );
2224
-
2225
- assert.equal(result.status, "NEED_INPUT");
2226
- assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2227
- const targetQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2228
- assert.equal(Boolean(targetQuestion), true);
2229
- assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2230
- assert.equal(targetQuestion.options?.some((item) => item.value === "通过筛选数"), true);
2231
- assert.equal(targetQuestion.options?.some((item) => item.value === "all"), true);
2232
- }
2233
-
2234
- async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics() {
2235
- const result = await runRecommendPipeline(
2236
- {
2237
- workspaceRoot: process.cwd(),
2238
- instruction: "test",
2239
- confirmation: {},
2240
- overrides: {},
2241
- followUp: createFollowUpChat({ target_count: "not a target" })
2242
- },
2243
- {
2244
- parseRecommendInstruction: () => createParsed()
2245
- }
2246
- );
2247
-
2248
- assert.equal(result.status, "NEED_INPUT");
2249
- assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2250
- const targetQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2251
- assert.equal(targetQuestion?.received_target_count, "not a target");
2252
- assert.equal(Boolean(targetQuestion?.target_count_parse_error), true);
2253
- assert.equal(targetQuestion?.accepted_examples.includes("all"), true);
2254
- assert.equal(targetQuestion?.accepted_examples.includes("通过筛选数"), true);
2255
- assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2256
- }
2257
-
2258
- async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
2259
- let capturedChatInput = null;
2260
- const result = await runRecommendPipeline(
2261
- {
2262
- workspaceRoot: process.cwd(),
2263
- instruction: "test",
2264
- confirmation: createJobConfirmedConfirmation(),
2265
- overrides: {},
2266
- followUp: createFollowUpChat({ target_count: "all" })
2267
- },
2268
- {
2269
- parseRecommendInstruction: () => createParsed(),
2270
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2271
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2272
- listRecommendJobs: async () => createJobListResult(),
2273
- readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2274
- switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2275
- runRecommendSearchCli: async () => ({
2276
- ok: true,
2277
- summary: {
2278
- candidate_count: 8,
2279
- applied_filters: { degree: ["本科"] },
2280
- selected_job: DEFAULT_JOB_OPTIONS[0]
2281
- }
2282
- }),
2283
- runRecommendScreenCli: async () => ({
2284
- ok: true,
2285
- summary: {
2286
- processed_count: 6,
2287
- passed_count: 2,
2288
- skipped_count: 4,
2289
- output_csv: "C:/temp/recommend.csv",
2290
- completion_reason: "screen_completed"
2291
- }
2292
- }),
2293
- startBossChatRun: async ({ input }) => {
2294
- capturedChatInput = input;
2295
- return {
2296
- status: "ACCEPTED",
2297
- run_id: "chat-run-unlimited",
2298
- message: "chat started"
2299
- };
2300
- },
2301
- getBossChatRun: async () => ({
2302
- status: "RUN_STATUS",
2303
- run: {
2304
- runId: "chat-run-unlimited",
2305
- state: "completed",
2306
- lastMessage: "chat completed",
2307
- progress: {
2308
- inspected: 3,
2309
- passed: 1,
2310
- requested: 1,
2311
- skipped: 2,
2312
- errors: 0
2313
- }
2314
- }
2315
- })
2316
- }
2317
- );
2318
-
2319
- assert.equal(result.status, "COMPLETED");
2320
- assert.equal(capturedChatInput.target_count, "all");
2321
- assert.equal(result.follow_up?.chat?.target_count, "all");
2322
- }
2323
-
2324
- async function testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount() {
2325
- let capturedChatInput = null;
2326
- const result = await runRecommendPipeline(
2327
- {
2328
- workspaceRoot: process.cwd(),
2329
- instruction: "test",
2330
- confirmation: createJobConfirmedConfirmation(),
2331
- overrides: {},
2332
- followUp: createFollowUpChat({ target_count: "通过筛选数" })
2333
- },
2334
- {
2335
- parseRecommendInstruction: () => createParsed(),
2336
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2337
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2338
- listRecommendJobs: async () => createJobListResult(),
2339
- runRecommendSearchCli: async () => ({
2340
- ok: true,
2341
- summary: {
2342
- candidate_count: 6,
2343
- applied_filters: {},
2344
- page_state: {}
2345
- }
2346
- }),
2347
- runRecommendScreenCli: async () => ({
2348
- ok: true,
2349
- summary: {
2350
- processed_count: 6,
2351
- passed_count: 2,
2352
- skipped_count: 0
2353
- }
2354
- }),
2355
- startBossChatRun: async ({ input }) => {
2356
- capturedChatInput = input;
2357
- return {
2358
- status: "ACCEPTED",
2359
- run_id: "chat-run-pass-count",
2360
- message: "chat started"
2361
- };
2362
- },
2363
- getBossChatRun: async () => ({
2364
- status: "COMPLETED",
2365
- run: {
2366
- runId: "chat-run-pass-count",
2367
- state: "completed",
2368
- lastMessage: "chat completed",
2369
- progress: { processed: 2, matched: 2 }
2370
- }
2371
- })
2372
- }
2373
- );
2374
-
2375
- assert.equal(result.status, "COMPLETED");
2376
- assert.equal(capturedChatInput?.target_count, 2);
2377
- assert.equal(result.follow_up?.chat?.input?.target_count, 2);
2378
- assert.equal(result.follow_up?.chat?.input?.target_count_requested, "passed_count");
2379
- }
2380
-
2381
- async function testFinalReviewShouldIncludeFollowUpChatSummary() {
2382
- const result = await runRecommendPipeline(
2383
- {
2384
- workspaceRoot: process.cwd(),
2385
- instruction: "test",
2386
- confirmation: createJobConfirmedWithoutFinalConfirmation(),
2387
- overrides: {},
2388
- followUp: createFollowUpChat()
2389
- },
2390
- {
2391
- parseRecommendInstruction: () => createParsed(),
2392
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2393
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2394
- listRecommendJobs: async () => createJobListResult(),
2395
- runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
2396
- runRecommendScreenCli: async () => ({ ok: true, summary: {} })
2397
- }
2398
- );
2399
-
2400
- assert.equal(result.status, "NEED_CONFIRMATION");
2401
- assert.equal(result.follow_up?.chat?.criteria, "有 AI Agent 经验");
2402
- const finalReview = result.pending_questions.find((item) => item.field === "final_review");
2403
- assert.equal(finalReview?.value?.follow_up?.chat?.start_from, "unread");
2404
- assert.equal(finalReview?.value?.follow_up?.chat?.target_count, 5);
2405
- }
2406
-
2407
- async function testFollowUpChatGreetingTextShouldPassThroughWhenProvided() {
2408
- let capturedChatInput = null;
2409
- const result = await runRecommendPipeline(
2410
- {
2411
- workspaceRoot: process.cwd(),
2412
- instruction: "test",
2413
- confirmation: createJobConfirmedConfirmation(),
2414
- overrides: {},
2415
- followUp: createFollowUpChat({ greeting_text: "您好,方便发下简历吗?" })
2416
- },
2417
- {
2418
- parseRecommendInstruction: () => createParsed(),
2419
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2420
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2421
- listRecommendJobs: async () => createJobListResult(),
2422
- runRecommendSearchCli: async () => ({
2423
- ok: true,
2424
- summary: {
2425
- candidate_count: 6,
2426
- applied_filters: {},
2427
- page_state: {}
2428
- }
2429
- }),
2430
- runRecommendScreenCli: async () => ({
2431
- ok: true,
2432
- summary: {
2433
- processed_count: 6,
2434
- passed_count: 2,
2435
- skipped_count: 0
2436
- }
2437
- }),
2438
- startBossChatRun: async ({ input }) => {
2439
- capturedChatInput = input;
2440
- return {
2441
- status: "ACCEPTED",
2442
- run_id: "chat-run-greeting",
2443
- message: "chat started"
2444
- };
2445
- },
2446
- getBossChatRun: async () => ({
2447
- status: "COMPLETED",
2448
- run: {
2449
- runId: "chat-run-greeting",
2450
- state: "completed",
2451
- lastMessage: "chat completed",
2452
- progress: { processed: 2, matched: 2 }
2453
- }
2454
- })
2455
- }
2456
- );
2457
-
2458
- assert.equal(result.status, "COMPLETED");
2459
- assert.equal(capturedChatInput?.greeting_text, "您好,方便发下简历吗?");
2460
- assert.equal(result.follow_up?.chat?.input?.greeting_text, "您好,方便发下简历吗?");
2461
- }
2462
-
2463
- async function testCompletedPipelineShouldRunChatFollowUp() {
2464
- let getChatRunCalls = 0;
2465
- const result = await runRecommendPipeline(
2466
- {
2467
- workspaceRoot: process.cwd(),
2468
- instruction: "test",
2469
- confirmation: createJobConfirmedConfirmation(),
2470
- overrides: {},
2471
- followUp: createFollowUpChat()
2472
- },
2473
- {
2474
- parseRecommendInstruction: () => createParsed(),
2475
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
2476
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2477
- listRecommendJobs: async () => createJobListResult(),
2478
- readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2479
- switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2480
- runRecommendSearchCli: async () => ({
2481
- ok: true,
2482
- summary: {
2483
- candidate_count: 8,
2484
- applied_filters: { degree: ["本科"] },
2485
- selected_job: DEFAULT_JOB_OPTIONS[0]
2486
- }
2487
- }),
2488
- runRecommendScreenCli: async () => ({
2489
- ok: true,
2490
- summary: {
2491
- processed_count: 6,
2492
- passed_count: 2,
2493
- skipped_count: 4,
2494
- output_csv: "C:/temp/recommend.csv",
2495
- completion_reason: "screen_completed"
2496
- }
2497
- }),
2498
- startBossChatRun: async ({ input }) => {
2499
- assert.equal(input.job, DEFAULT_JOB_OPTIONS[0].title);
2500
- assert.equal(input.port, 9555);
2501
- return {
2502
- status: "ACCEPTED",
2503
- run_id: "chat-run-1",
2504
- message: "chat started"
2505
- };
2506
- },
2507
- getBossChatRun: async () => {
2508
- getChatRunCalls += 1;
2509
- if (getChatRunCalls === 1) {
2510
- return {
2511
- status: "RUN_STATUS",
2512
- run: {
2513
- runId: "chat-run-1",
2514
- state: "running",
2515
- lastMessage: "chat running",
2516
- progress: {
2517
- inspected: 1,
2518
- passed: 0,
2519
- requested: 0,
2520
- skipped: 1,
2521
- errors: 0
2522
- }
2523
- }
2524
- };
2525
- }
2526
- return {
2527
- status: "RUN_STATUS",
2528
- run: {
2529
- runId: "chat-run-1",
2530
- state: "completed",
2531
- lastMessage: "chat completed",
2532
- progress: {
2533
- inspected: 3,
2534
- passed: 1,
2535
- requested: 1,
2536
- skipped: 2,
2537
- errors: 0
2538
- },
2539
- result: {
2540
- requested_count: 1
2541
- }
2542
- }
2543
- };
2544
- }
2545
- }
2546
- );
2547
-
2548
- assert.equal(result.status, "COMPLETED");
2549
- assert.equal(result.result.output_csv, "C:/temp/recommend.csv");
2550
- assert.equal(result.follow_up?.chat?.run_id, "chat-run-1");
2551
- assert.equal(result.follow_up?.chat?.state, "completed");
2552
- assert.equal(result.follow_up?.chat?.input?.port, 9555);
2553
- }
2554
-
2555
- async function testCompletedPipelineShouldFailWhenChatLaunchFails() {
2556
- const result = await runRecommendPipeline(
2557
- {
2558
- workspaceRoot: process.cwd(),
2559
- instruction: "test",
2560
- confirmation: createJobConfirmedConfirmation(),
2561
- overrides: {},
2562
- followUp: createFollowUpChat()
2563
- },
2564
- {
2565
- parseRecommendInstruction: () => createParsed(),
2566
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2567
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2568
- listRecommendJobs: async () => createJobListResult(),
2569
- readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2570
- switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2571
- runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
2572
- runRecommendScreenCli: async () => ({
2573
- ok: true,
2574
- summary: {
2575
- processed_count: 1,
2576
- passed_count: 1,
2577
- skipped_count: 0,
2578
- output_csv: "C:/temp/recommend.csv",
2579
- completion_reason: "screen_completed"
2580
- }
2581
- }),
2582
- startBossChatRun: async () => ({
2583
- status: "FAILED",
2584
- error: {
2585
- code: "CHAT_START_FAILED",
2586
- message: "cannot start chat"
2587
- }
2588
- })
2589
- }
2590
- );
2591
-
2592
- assert.equal(result.status, "FAILED");
2593
- assert.equal(result.error.code, "CHAT_START_FAILED");
2594
- assert.equal(result.follow_up?.chat?.launch_result?.status, "FAILED");
2595
- }
2596
-
2597
- async function testCompletedPipelineShouldFailWhenChatRunFails() {
2598
- const result = await runRecommendPipeline(
2599
- {
2600
- workspaceRoot: process.cwd(),
2601
- instruction: "test",
2602
- confirmation: createJobConfirmedConfirmation(),
2603
- overrides: {},
2604
- followUp: createFollowUpChat()
2605
- },
2606
- {
2607
- parseRecommendInstruction: () => createParsed(),
2608
- runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
2609
- ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
2610
- listRecommendJobs: async () => createJobListResult(),
2611
- readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
2612
- switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
2613
- runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
2614
- runRecommendScreenCli: async () => ({
2615
- ok: true,
2616
- summary: {
2617
- processed_count: 1,
2618
- passed_count: 1,
2619
- skipped_count: 0,
2620
- output_csv: "C:/temp/recommend.csv",
2621
- completion_reason: "screen_completed"
2622
- }
2623
- }),
2624
- startBossChatRun: async () => ({
2625
- status: "ACCEPTED",
2626
- run_id: "chat-run-2",
2627
- message: "chat started"
2628
- }),
2629
- getBossChatRun: async () => ({
2630
- status: "RUN_STATUS",
2631
- run: {
2632
- runId: "chat-run-2",
2633
- state: "failed",
2634
- lastMessage: "chat failed",
2635
- error: {
2636
- code: "CHAT_RUNTIME_FAILED",
2637
- message: "chat runtime failed"
2638
- }
2639
- }
2640
- })
2641
- }
2642
- );
2643
-
2644
- assert.equal(result.status, "FAILED");
2645
- assert.equal(result.error.code, "CHAT_RUNTIME_FAILED");
2646
- assert.equal(result.follow_up?.chat?.state, "failed");
2647
- }
2648
-
2649
- async function main() {
2650
- await testPauseRequestedBeforeScreenShouldReturnPaused();
2651
- await testPausedScreenResultShouldBubbleUp();
2652
- await testResumeFromScreenPauseShouldSkipSearch();
2653
- await testResumeFromPausedBeforeScreenShouldRerunSearch();
2654
- await testConsecutiveResumeCaptureFailuresShouldRefreshAndRerunSearchWithForcedRecentFilter();
2655
- await testPageExhaustedBeforeTargetShouldRefreshInPageAndResumeScreen();
2656
- await testPageExhaustedBeforeTargetShouldReloadWhenRefreshButtonMissing();
2657
- await testPageExhaustedBeforeTargetShouldReloadWhenRefreshDoesNotRecoverList();
2658
- await testPageExhaustedBeforeTargetShouldFailAfterFiveRecoveryAttempts();
2659
- await testNullTargetCountShouldKeepPageExhaustedCompletion();
2660
- await testNeedConfirmationGate();
2661
- await testNeedPageScopeConfirmationGate();
2662
- await testNeedSchoolTagConfirmationGate();
2663
- await testNeedTargetCountConfirmationGate();
2664
- await testNeedMaxGreetCountConfirmationGate();
2665
- await testNeedInputGate();
2666
- await testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen();
2667
- await testLatestPipelineShouldRunSearchThenSwitchTabThenScreen();
2668
- await testPipelineShouldPassInputSummaryToScreenCli();
2669
- await testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue();
2670
- await testFeaturedCalibrationFailureShouldReturnCalibrationRequired();
2671
- await testFeaturedTabSwitchFailureShouldReturnRetryableError();
2672
- await testNeedJobConfirmationGate();
2673
- await testNeedFinalReviewConfirmationGate();
2674
- await testCompletedPipeline();
2675
- await testSearchFailure();
2676
- await testSearchFilterFailureShouldRetryAndRecover();
2677
- await testSearchNoIframeWithLoginShouldReturnLoginRequired();
2678
- await testSearchNoIframeShouldRetryOnceWhenPageRecheckReady();
2679
- await testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin();
2680
- await testLoginRequiredShouldReturnGuidance();
2681
- await testDebugPortUnreachableShouldReturnConnectionCode();
2682
- await testPreflightRecoveryPlanOrder();
2683
- await testPreflightAutoRepairCanUnblockPipeline();
2684
- await testPreflightAutoRepairStillFailShouldExposeDiagnostics();
2685
- await testScreenConfigFailureShouldRequireUserProvidedConfig();
2686
- await testScreenConfigPlaceholderShouldRequireUserConfirmationAfterUpdate();
2687
- await testScreenConfigRecoveryStepShouldBeFirst();
2688
- await testFollowUpChatMissingCriteriaShouldNeedInput();
2689
- await testFollowUpChatMissingFieldsShouldExposeRecommendDefaults();
2690
- await testFollowUpChatMissingStartFromShouldNeedInput();
2691
- await testFollowUpChatMissingTargetCountShouldNeedInput();
2692
- await testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics();
2693
- await testFinalReviewShouldIncludeFollowUpChatSummary();
2694
- await testFollowUpChatGreetingTextShouldPassThroughWhenProvided();
2695
- await testCompletedPipelineShouldRunChatFollowUp();
2696
- await testFollowUpChatAllTargetCountShouldLaunchUnlimited();
2697
- await testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount();
2698
- await testCompletedPipelineShouldFailWhenChatLaunchFails();
2699
- await testCompletedPipelineShouldFailWhenChatRunFails();
2700
- console.log("pipeline tests passed");
2701
- }
2702
-
2703
- await main();