@reconcrap/boss-recommend-mcp 1.3.36 → 1.3.38

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.
@@ -287,16 +287,17 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
287
287
  assert.equal(stateAfterPrepare.last_prepare_args["llm-timeout-ms"], "65000");
288
288
  assert.equal(stateAfterPrepare.last_prepare_args["llm-max-retries"], "4");
289
289
 
290
- const started = await startBossChatRun({
291
- workspaceRoot,
292
- input: {
293
- profile: "default",
294
- job: "算法工程师",
295
- start_from: "unread",
296
- criteria: "有 AI Agent 经验",
297
- target_count: 2
298
- }
299
- });
290
+ const started = await startBossChatRun({
291
+ workspaceRoot,
292
+ input: {
293
+ profile: "default",
294
+ job: "算法工程师",
295
+ start_from: "unread",
296
+ criteria: "有 AI Agent 经验",
297
+ greeting_text: "你好,方便发下简历吗?",
298
+ target_count: 2
299
+ }
300
+ });
300
301
  assert.equal(started.status, "ACCEPTED");
301
302
  assert.equal(Boolean(started.run_id), true);
302
303
 
@@ -304,9 +305,10 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
304
305
  assert.equal(stateAfterStart.last_start_args.profile, "default");
305
306
  assert.equal(stateAfterStart.last_start_args["data-dir"], getTestChatDataDir(workspaceRoot));
306
307
  assert.equal(stateAfterStart.last_start_args.job, "算法工程师");
307
- assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
308
- assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
309
- assert.equal(stateAfterStart.last_start_args.targetCount, "2");
308
+ assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
309
+ assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
310
+ assert.equal(stateAfterStart.last_start_args.greeting, "你好,方便发下简历吗?");
311
+ assert.equal(stateAfterStart.last_start_args.targetCount, "2");
310
312
  assert.equal(stateAfterStart.last_start_args.baseurl, "https://api.example.com/v1");
311
313
  assert.equal(stateAfterStart.last_start_args.apikey, "sk-test-key");
312
314
  assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
@@ -908,15 +910,20 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
908
910
  method: "tools/list",
909
911
  params: {}
910
912
  }, workspaceRoot);
911
- const tools = toolsResponse.result.tools;
912
- const prepareToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_PREPARE_RUN).inputSchema;
913
- const startToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_START_RUN).inputSchema;
914
- assert.equal(prepareToolSchema.required, undefined);
915
- assert.deepEqual(startToolSchema.required, ["job", "start_from", "criteria"]);
916
- assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("target_count")), true);
917
- assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("targetCount")), true);
918
- assert.equal(startToolSchema.properties.target_count.examples.includes("all"), true);
919
- assert.equal(startToolSchema.examples.some((item) => item.target_count === "all"), true);
913
+ const tools = toolsResponse.result.tools;
914
+ const runToolSchema = tools.find((item) => item.name === "start_recommend_pipeline_run").inputSchema;
915
+ const prepareToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_PREPARE_RUN).inputSchema;
916
+ const startToolSchema = tools.find((item) => item.name === TOOL_BOSS_CHAT_START_RUN).inputSchema;
917
+ assert.equal(prepareToolSchema.required, undefined);
918
+ assert.deepEqual(startToolSchema.required, ["job", "start_from", "criteria"]);
919
+ assert.equal(typeof startToolSchema.properties.greeting_text, "object");
920
+ assert.equal(typeof startToolSchema.properties.greetingText, "object");
921
+ assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("target_count")), true);
922
+ assert.equal(startToolSchema.anyOf.some((item) => item.required?.includes("targetCount")), true);
923
+ assert.equal(startToolSchema.properties.target_count.examples.includes("all"), true);
924
+ assert.equal(startToolSchema.examples.some((item) => item.target_count === "all"), true);
925
+ assert.equal(typeof runToolSchema.properties.follow_up.properties.chat.properties.greeting_text, "object");
926
+ assert.equal(typeof runToolSchema.properties.follow_up.properties.chat.properties.greetingText, "object");
920
927
 
921
928
  const prepared = await callTool(workspaceRoot, TOOL_BOSS_CHAT_PREPARE_RUN, {}, 101);
922
929
  assert.equal(prepared.status, "NEED_INPUT");
@@ -960,13 +967,20 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
960
967
  assert.equal(invalidTargetOnly.next_call_example.target_count, "all");
961
968
  assert.equal(invalidTargetOnly.recommended_argument_patch.target_count, "all");
962
969
 
963
- const invalidStartResponse = await handleRequest(
964
- makeToolCall(11, TOOL_BOSS_CHAT_START_RUN, {
965
- start_from: "invalid-value"
970
+ const invalidStartResponse = await handleRequest(
971
+ makeToolCall(11, TOOL_BOSS_CHAT_START_RUN, {
972
+ start_from: "invalid-value"
966
973
  }),
967
974
  workspaceRoot
968
- );
969
- assert.equal(invalidStartResponse.error.code, -32602);
975
+ );
976
+ assert.equal(invalidStartResponse.error.code, -32602);
977
+ const invalidGreetingResponse = await handleRequest(
978
+ makeToolCall(1112, TOOL_BOSS_CHAT_START_RUN, {
979
+ greeting_text: 123
980
+ }),
981
+ workspaceRoot
982
+ );
983
+ assert.equal(invalidGreetingResponse.error.code, -32602);
970
984
 
971
985
  const invalidGetResponse = await handleRequest(
972
986
  makeToolCall(12, TOOL_BOSS_CHAT_GET_RUN, {}),
@@ -977,13 +991,15 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
977
991
  const health = await callTool(workspaceRoot, TOOL_BOSS_CHAT_HEALTH_CHECK, {}, 13);
978
992
  assert.equal(health.status, "OK");
979
993
 
980
- const started = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
981
- job: "算法工程师",
982
- start_from: "unread",
983
- criteria: "有 AI Agent 经验",
984
- target_count: 2
985
- }, 14);
986
- assert.equal(started.status, "ACCEPTED");
994
+ const started = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
995
+ job: "算法工程师",
996
+ start_from: "unread",
997
+ criteria: "有 AI Agent 经验",
998
+ greeting_text: "您好,方便投递一份简历吗?",
999
+ target_count: 2
1000
+ }, 14);
1001
+ assert.equal(started.status, "ACCEPTED");
1002
+ assert.equal(readStubState(workspaceRoot).last_start_args.greeting, "您好,方便投递一份简历吗?");
987
1003
 
988
1004
  const startedAll = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
989
1005
  job: "算法工程师",
@@ -1033,15 +1049,17 @@ async function testBossChatMcpToolsShouldValidateAndRoute() {
1033
1049
  async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
1034
1050
  const followUpJson = cliTestables.getRunFollowUp({
1035
1051
  "follow-up-json": JSON.stringify({
1036
- chat: {
1037
- criteria: "有 AI Agent 经验",
1038
- start_from: "unread",
1039
- target_count: 2
1040
- }
1041
- })
1042
- });
1043
- assert.equal(followUpJson.chat.criteria, "有 AI Agent 经验");
1044
- assert.equal(followUpJson.chat.target_count, 2);
1052
+ chat: {
1053
+ criteria: "有 AI Agent 经验",
1054
+ start_from: "unread",
1055
+ greeting_text: "您好,方便发下简历吗?",
1056
+ target_count: 2
1057
+ }
1058
+ })
1059
+ });
1060
+ assert.equal(followUpJson.chat.criteria, "有 AI Agent 经验");
1061
+ assert.equal(followUpJson.chat.greeting_text, "您好,方便发下简历吗?");
1062
+ assert.equal(followUpJson.chat.target_count, 2);
1045
1063
 
1046
1064
  const tempFile = path.join(os.tmpdir(), `boss-recommend-follow-up-${Date.now()}.json`);
1047
1065
  fs.writeFileSync(tempFile, JSON.stringify({
@@ -1074,18 +1092,20 @@ async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
1074
1092
  const logs = await captureConsoleLogs(async () => {
1075
1093
  await cliTestables.runBossChatCliCommand("run", {
1076
1094
  "workspace-root": workspaceRoot,
1077
- job: "算法工程师",
1078
- "start-from": "unread",
1079
- criteria: "有 AI Agent 经验",
1080
- targetCount: "2"
1081
- });
1095
+ job: "算法工程师",
1096
+ "start-from": "unread",
1097
+ criteria: "有 AI Agent 经验",
1098
+ "greeting-text": "您好,方便发下简历吗?",
1099
+ targetCount: "2"
1100
+ });
1082
1101
  });
1083
1102
  assert.equal(logs.length > 0, true);
1084
- const payload = JSON.parse(logs[0]);
1085
- assert.equal(payload.status, "ACCEPTED");
1086
- assert.equal(typeof payload.run_id, "string");
1087
- const state = readStubState(workspaceRoot);
1088
- assert.equal(state.get_calls[payload.run_id] || 0, 0);
1103
+ const payload = JSON.parse(logs[0]);
1104
+ assert.equal(payload.status, "ACCEPTED");
1105
+ assert.equal(typeof payload.run_id, "string");
1106
+ const state = readStubState(workspaceRoot);
1107
+ assert.equal(state.last_start_args.greeting, "您好,方便发下简历吗?");
1108
+ assert.equal(state.get_calls[payload.run_id] || 0, 0);
1089
1109
 
1090
1110
  await captureConsoleLogs(async () => {
1091
1111
  await cliTestables.runBossChatCliCommand("run", {
@@ -1179,14 +1199,106 @@ async function testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile() {
1179
1199
  overrides: {
1180
1200
  jobSelection: "AI应用开发工程师(2026) _ 杭州",
1181
1201
  startFrom: "unread",
1182
- screeningCriteria: "小样本联通性验证",
1183
- targetCount: 1,
1184
- },
1185
- });
1186
- assert.equal(profile.jobSelection.label, "AI应用开发工程师(2026) _ 杭州");
1187
- assert.equal(profile.startFrom, "unread");
1188
- assert.equal(profile.targetCount, 1);
1189
- }
1202
+ screeningCriteria: "小样本联通性验证",
1203
+ greetingText: "你好,方便发下简历吗?",
1204
+ targetCount: 1,
1205
+ },
1206
+ });
1207
+ assert.equal(profile.jobSelection.label, "AI应用开发工程师(2026) _ 杭州");
1208
+ assert.equal(profile.startFrom, "unread");
1209
+ assert.equal(profile.greetingText, "你好,方便发下简历吗?");
1210
+ assert.equal(profile.targetCount, 1);
1211
+ }
1212
+
1213
+ async function testVendorBossChatCliShouldUseGreetingFallbacksInPromptRunProfile() {
1214
+ const page = {
1215
+ async ensureOnChatPage() {
1216
+ return {
1217
+ href: "https://www.zhipin.com/web/chat/index",
1218
+ hasListContainer: true,
1219
+ listItemCount: 8,
1220
+ };
1221
+ },
1222
+ async listJobs() {
1223
+ return [
1224
+ {
1225
+ value: "job-1",
1226
+ label: "算法工程师 _ 杭州",
1227
+ active: true,
1228
+ },
1229
+ ];
1230
+ },
1231
+ };
1232
+
1233
+ const fromLastProfile = await vendorCliTestables.promptRunProfile({
1234
+ page,
1235
+ persistentProfile: {
1236
+ greetingText: "上次招呼语",
1237
+ llm: {
1238
+ baseUrl: "https://api.example.com/v1",
1239
+ apiKey: "sk-test-key",
1240
+ model: "gpt-4.1-mini",
1241
+ },
1242
+ chrome: {
1243
+ port: 9222,
1244
+ },
1245
+ runtime: {},
1246
+ },
1247
+ overrides: {
1248
+ jobSelection: "算法工程师 _ 杭州",
1249
+ startFrom: "unread",
1250
+ screeningCriteria: "有 AI Agent 经验",
1251
+ targetCount: 1,
1252
+ },
1253
+ });
1254
+ assert.equal(fromLastProfile.greetingText, "上次招呼语");
1255
+
1256
+ const fromExplicitOverride = await vendorCliTestables.promptRunProfile({
1257
+ page,
1258
+ persistentProfile: {
1259
+ greetingText: "上次招呼语",
1260
+ llm: {
1261
+ baseUrl: "https://api.example.com/v1",
1262
+ apiKey: "sk-test-key",
1263
+ model: "gpt-4.1-mini",
1264
+ },
1265
+ chrome: {
1266
+ port: 9222,
1267
+ },
1268
+ runtime: {},
1269
+ },
1270
+ overrides: {
1271
+ jobSelection: "算法工程师 _ 杭州",
1272
+ startFrom: "unread",
1273
+ screeningCriteria: "有 AI Agent 经验",
1274
+ greetingText: "本次新招呼语",
1275
+ targetCount: 1,
1276
+ },
1277
+ });
1278
+ assert.equal(fromExplicitOverride.greetingText, "本次新招呼语");
1279
+
1280
+ const fromBuiltInDefault = await vendorCliTestables.promptRunProfile({
1281
+ page,
1282
+ persistentProfile: {
1283
+ llm: {
1284
+ baseUrl: "https://api.example.com/v1",
1285
+ apiKey: "sk-test-key",
1286
+ model: "gpt-4.1-mini",
1287
+ },
1288
+ chrome: {
1289
+ port: 9222,
1290
+ },
1291
+ runtime: {},
1292
+ },
1293
+ overrides: {
1294
+ jobSelection: "算法工程师 _ 杭州",
1295
+ startFrom: "unread",
1296
+ screeningCriteria: "有 AI Agent 经验",
1297
+ targetCount: 1,
1298
+ },
1299
+ });
1300
+ assert.equal(fromBuiltInDefault.greetingText, "Hi同学,能麻烦发下简历吗?");
1301
+ }
1190
1302
 
1191
1303
  function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
1192
1304
  const installedSpecifier = cliTestables.getDefaultMcpPackageSpecifier({
@@ -1213,18 +1325,21 @@ function testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig() {
1213
1325
  assert.equal(launchConfig.args[0], "-y");
1214
1326
  }
1215
1327
 
1216
- function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
1217
- const parsed = vendorCliTestables.parseArgs([
1218
- "start-run",
1219
- "--llm-timeout-ms",
1220
- "70000",
1221
- "--llm-max-retries",
1222
- "5",
1223
- ]);
1224
- assert.equal(parsed.command, "start-run");
1225
- assert.equal(parsed.overrides.llm.timeoutMs, 70000);
1226
- assert.equal(parsed.overrides.llm.maxRetries, 5);
1227
- }
1328
+ function testVendorBossChatCliShouldParseSharedLlmTransportArgs() {
1329
+ const parsed = vendorCliTestables.parseArgs([
1330
+ "start-run",
1331
+ "--llm-timeout-ms",
1332
+ "70000",
1333
+ "--llm-max-retries",
1334
+ "5",
1335
+ "--greeting",
1336
+ "您好,方便发下简历吗?",
1337
+ ]);
1338
+ assert.equal(parsed.command, "start-run");
1339
+ assert.equal(parsed.overrides.llm.timeoutMs, 70000);
1340
+ assert.equal(parsed.overrides.llm.maxRetries, 5);
1341
+ assert.equal(parsed.overrides.greetingText, "您好,方便发下简历吗?");
1342
+ }
1228
1343
 
1229
1344
  function testBossChatLlmParserShouldAcceptMinimalDecisionJson() {
1230
1345
  const parsed = parseLlmJson(
@@ -3049,8 +3164,9 @@ async function main() {
3049
3164
  testVendorBossChatCliShouldResolveExplicitDataDir();
3050
3165
  testVendorBossChatCliShouldUseRecommendHomeForDefaultDataDir();
3051
3166
  await testVendorBossChatCliShouldWaitForHydratedChatShell();
3052
- await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
3053
- testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
3167
+ await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
3168
+ await testVendorBossChatCliShouldUseGreetingFallbacksInPromptRunProfile();
3169
+ testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
3054
3170
  testVendorBossChatCliShouldParseSharedLlmTransportArgs();
3055
3171
  testBossChatLlmParserShouldAcceptMinimalDecisionJson();
3056
3172
  testBossChatLlmParserShouldAcceptPlainPassFailText();
@@ -2183,8 +2183,9 @@ async function testFollowUpChatMissingFieldsShouldExposeRecommendDefaults() {
2183
2183
  const targetCountQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2184
2184
  assert.equal(criteriaQuestion?.value, "默认沿用 recommend 的筛选条件");
2185
2185
  assert.equal(startFromQuestion?.value, "unread");
2186
- assert.equal(targetCountQuestion?.value, 18);
2187
- assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
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);
2188
2189
  assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes('follow_up.chat.target_count="all"')), true);
2189
2190
  }
2190
2191
 
@@ -2225,7 +2226,9 @@ async function testFollowUpChatMissingTargetCountShouldNeedInput() {
2225
2226
  assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
2226
2227
  const targetQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
2227
2228
  assert.equal(Boolean(targetQuestion), true);
2228
- assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
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);
2229
2232
  }
2230
2233
 
2231
2234
  async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics() {
@@ -2248,7 +2251,8 @@ async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics(
2248
2251
  assert.equal(targetQuestion?.received_target_count, "not a target");
2249
2252
  assert.equal(Boolean(targetQuestion?.target_count_parse_error), true);
2250
2253
  assert.equal(targetQuestion?.accepted_examples.includes("all"), true);
2251
- assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "all");
2254
+ assert.equal(targetQuestion?.accepted_examples.includes("通过筛选数"), true);
2255
+ assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
2252
2256
  }
2253
2257
 
2254
2258
  async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
@@ -2317,6 +2321,63 @@ async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
2317
2321
  assert.equal(result.follow_up?.chat?.target_count, "all");
2318
2322
  }
2319
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
+
2320
2381
  async function testFinalReviewShouldIncludeFollowUpChatSummary() {
2321
2382
  const result = await runRecommendPipeline(
2322
2383
  {
@@ -2343,6 +2404,62 @@ async function testFinalReviewShouldIncludeFollowUpChatSummary() {
2343
2404
  assert.equal(finalReview?.value?.follow_up?.chat?.target_count, 5);
2344
2405
  }
2345
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
+
2346
2463
  async function testCompletedPipelineShouldRunChatFollowUp() {
2347
2464
  let getChatRunCalls = 0;
2348
2465
  const result = await runRecommendPipeline(
@@ -2574,8 +2691,10 @@ async function main() {
2574
2691
  await testFollowUpChatMissingTargetCountShouldNeedInput();
2575
2692
  await testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics();
2576
2693
  await testFinalReviewShouldIncludeFollowUpChatSummary();
2694
+ await testFollowUpChatGreetingTextShouldPassThroughWhenProvided();
2577
2695
  await testCompletedPipelineShouldRunChatFollowUp();
2578
2696
  await testFollowUpChatAllTargetCountShouldLaunchUnlimited();
2697
+ await testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount();
2579
2698
  await testCompletedPipelineShouldFailWhenChatLaunchFails();
2580
2699
  await testCompletedPipelineShouldFailWhenChatRunFails();
2581
2700
  console.log("pipeline tests passed");
@@ -1,8 +1,9 @@
1
- import { mkdir } from 'node:fs/promises';
2
- import path from 'node:path';
3
-
4
- import { isDomProfileConsistentWithCard, NETWORK_RESUME_RETRY_WAIT_MS } from './services/resume-network.js';
5
- import { createCustomerAliases, createCustomerKey } from './utils/customer-key.js';
1
+ import { mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+
4
+ import { isDomProfileConsistentWithCard, NETWORK_RESUME_RETRY_WAIT_MS } from './services/resume-network.js';
5
+ import { DEFAULT_GREETING_TEXT } from './services/profile-store.js';
6
+ import { createCustomerAliases, createCustomerKey } from './utils/customer-key.js';
6
7
 
7
8
  function runToken(date = new Date()) {
8
9
  return date.toISOString().replace(/[:.]/g, '-');
@@ -37,12 +38,16 @@ function shouldContinue(summary, targetCount) {
37
38
  return summary.inspected < targetCount;
38
39
  }
39
40
 
40
- function hasResumeRequestSentMessage(state = {}) {
41
- const lastText = normalizeText(state?.lastText || '');
42
- const recent = Array.isArray(state?.recent) ? state.recent : [];
43
- if (lastText.includes('简历请求已发送')) return true;
44
- return recent.some((item) => normalizeText(item).includes('简历请求已发送'));
45
- }
41
+ function hasResumeRequestSentMessage(state = {}) {
42
+ const lastText = normalizeText(state?.lastText || '');
43
+ const recent = Array.isArray(state?.recent) ? state.recent : [];
44
+ if (lastText.includes('简历请求已发送')) return true;
45
+ return recent.some((item) => normalizeText(item).includes('简历请求已发送'));
46
+ }
47
+
48
+ function resolveGreetingText(profile = {}) {
49
+ return normalizeText(profile?.greetingText || '') || DEFAULT_GREETING_TEXT;
50
+ }
46
51
 
47
52
  const CANDIDATE_LIST_WAIT_AFTER_CONTEXT_MS = 5000;
48
53
  const CANDIDATE_LIST_WAIT_POLL_MS = 500;
@@ -1323,13 +1328,13 @@ export class BossChatApp {
1323
1328
  return baseResult;
1324
1329
  }
1325
1330
 
1326
- const greetingText = 'Hi同学,能麻烦发下简历吗?';
1331
+ const greetingText = resolveGreetingText(profile);
1327
1332
  this.logger.log(`候选人通过,先发送消息:${greetingText}`);
1328
1333
  await this.checkpoint();
1329
- const editorState = await this.page.setEditorMessage(greetingText);
1330
- if (!String(editorState?.value || '').includes('Hi同学')) {
1331
- throw new Error('CHAT_EDITOR_MESSAGE_MISMATCH');
1332
- }
1334
+ const editorState = await this.page.setEditorMessage(greetingText);
1335
+ if (!normalizeText(editorState?.value || '').includes(normalizeText(greetingText))) {
1336
+ throw new Error('CHAT_EDITOR_MESSAGE_MISMATCH');
1337
+ }
1333
1338
  this.logger.log(
1334
1339
  `招呼语写入输入框:activeSubmit=${Boolean(editorState?.activeSubmit)} | valueLen=${String(editorState?.value || '').length}`,
1335
1340
  );