@reconcrap/boss-recommend-mcp 1.2.10 → 1.3.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.
- package/README.md +82 -1
- package/package.json +2 -1
- package/skills/boss-chat/README.md +5 -0
- package/skills/boss-chat/SKILL.md +69 -0
- package/skills/boss-recommend-pipeline/SKILL.md +40 -4
- package/src/adapters.js +19 -5
- package/src/boss-chat.js +436 -0
- package/src/cli.js +294 -129
- package/src/index.js +459 -108
- package/src/pipeline.js +605 -8
- package/src/run-state.js +5 -0
- package/src/test-adapters-runtime.js +69 -0
- package/src/test-boss-chat.js +399 -0
- package/src/test-index-async.js +238 -4
- package/src/test-pipeline.js +408 -1
- package/vendor/boss-chat-cli/README.md +134 -0
- package/vendor/boss-chat-cli/package.json +53 -0
- package/vendor/boss-chat-cli/src/app.js +783 -0
- package/vendor/boss-chat-cli/src/browser/chat-page.js +2698 -0
- package/vendor/boss-chat-cli/src/cli.js +1350 -0
- package/vendor/boss-chat-cli/src/mcp/server.js +149 -0
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +193 -0
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +260 -0
- package/vendor/boss-chat-cli/src/runtime/interaction.js +102 -0
- package/vendor/boss-chat-cli/src/runtime/run-control.js +102 -0
- package/vendor/boss-chat-cli/src/services/chrome-client.js +97 -0
- package/vendor/boss-chat-cli/src/services/llm.js +352 -0
- package/vendor/boss-chat-cli/src/services/profile-store.js +157 -0
- package/vendor/boss-chat-cli/src/services/report-store.js +19 -0
- package/vendor/boss-chat-cli/src/services/resume-capture.js +554 -0
- package/vendor/boss-chat-cli/src/services/state-store.js +217 -0
- package/vendor/boss-chat-cli/src/utils/customer-key.js +82 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +902 -56
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +387 -1
package/src/test-pipeline.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { runRecommendPipeline } from "./pipeline.js";
|
|
2
|
+
import { runRecommendPipeline as runRecommendPipelineImpl } from "./pipeline.js";
|
|
3
3
|
|
|
4
4
|
const DEFAULT_JOB_OPTIONS = [
|
|
5
5
|
{
|
|
@@ -85,6 +85,40 @@ function createParsed(overrides = {}) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
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
|
+
|
|
88
122
|
async function testPauseRequestedBeforeScreenShouldReturnPaused() {
|
|
89
123
|
let screenCalled = false;
|
|
90
124
|
const result = await runRecommendPipeline(
|
|
@@ -1178,6 +1212,62 @@ async function testLatestPipelineShouldRunSearchThenSwitchTabThenScreen() {
|
|
|
1178
1212
|
assert.equal(result.result.resume_source, "image_fallback");
|
|
1179
1213
|
}
|
|
1180
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
|
+
|
|
1181
1271
|
async function testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue() {
|
|
1182
1272
|
const calls = [];
|
|
1183
1273
|
let preflightCallCount = 0;
|
|
@@ -1687,6 +1777,8 @@ async function testNeedJobConfirmationGate() {
|
|
|
1687
1777
|
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
1688
1778
|
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
1689
1779
|
listRecommendJobs: async () => createJobListResult(),
|
|
1780
|
+
readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
|
|
1781
|
+
switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
|
|
1690
1782
|
runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
|
|
1691
1783
|
runRecommendScreenCli: async () => ({ ok: true, summary: {} })
|
|
1692
1784
|
}
|
|
@@ -2038,6 +2130,312 @@ async function testScreenConfigRecoveryStepShouldBeFirst() {
|
|
|
2038
2130
|
assert.equal(result.diagnostics.recovery.ordered_steps[0].id, "fill_screening_config");
|
|
2039
2131
|
}
|
|
2040
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, 18);
|
|
2187
|
+
}
|
|
2188
|
+
|
|
2189
|
+
async function testFollowUpChatMissingStartFromShouldNeedInput() {
|
|
2190
|
+
const result = await runRecommendPipeline(
|
|
2191
|
+
{
|
|
2192
|
+
workspaceRoot: process.cwd(),
|
|
2193
|
+
instruction: "test",
|
|
2194
|
+
confirmation: {},
|
|
2195
|
+
overrides: {},
|
|
2196
|
+
followUp: createFollowUpChat({ start_from: "" })
|
|
2197
|
+
},
|
|
2198
|
+
{
|
|
2199
|
+
parseRecommendInstruction: () => createParsed()
|
|
2200
|
+
}
|
|
2201
|
+
);
|
|
2202
|
+
|
|
2203
|
+
assert.equal(result.status, "NEED_INPUT");
|
|
2204
|
+
assert.equal(result.missing_fields.includes("follow_up.chat.start_from"), true);
|
|
2205
|
+
assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.start_from"), true);
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
async function testFollowUpChatMissingTargetCountShouldNeedInput() {
|
|
2209
|
+
const result = await runRecommendPipeline(
|
|
2210
|
+
{
|
|
2211
|
+
workspaceRoot: process.cwd(),
|
|
2212
|
+
instruction: "test",
|
|
2213
|
+
confirmation: {},
|
|
2214
|
+
overrides: {},
|
|
2215
|
+
followUp: createFollowUpChat({ target_count: null })
|
|
2216
|
+
},
|
|
2217
|
+
{
|
|
2218
|
+
parseRecommendInstruction: () => createParsed()
|
|
2219
|
+
}
|
|
2220
|
+
);
|
|
2221
|
+
|
|
2222
|
+
assert.equal(result.status, "NEED_INPUT");
|
|
2223
|
+
assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
|
|
2224
|
+
assert.equal(result.pending_questions.some((item) => item.field === "follow_up.chat.target_count"), true);
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
async function testFinalReviewShouldIncludeFollowUpChatSummary() {
|
|
2228
|
+
const result = await runRecommendPipeline(
|
|
2229
|
+
{
|
|
2230
|
+
workspaceRoot: process.cwd(),
|
|
2231
|
+
instruction: "test",
|
|
2232
|
+
confirmation: createJobConfirmedWithoutFinalConfirmation(),
|
|
2233
|
+
overrides: {},
|
|
2234
|
+
followUp: createFollowUpChat()
|
|
2235
|
+
},
|
|
2236
|
+
{
|
|
2237
|
+
parseRecommendInstruction: () => createParsed(),
|
|
2238
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
2239
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
2240
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
2241
|
+
runRecommendSearchCli: async () => ({ ok: true, summary: {} }),
|
|
2242
|
+
runRecommendScreenCli: async () => ({ ok: true, summary: {} })
|
|
2243
|
+
}
|
|
2244
|
+
);
|
|
2245
|
+
|
|
2246
|
+
assert.equal(result.status, "NEED_CONFIRMATION");
|
|
2247
|
+
assert.equal(result.follow_up?.chat?.criteria, "有 AI Agent 经验");
|
|
2248
|
+
const finalReview = result.pending_questions.find((item) => item.field === "final_review");
|
|
2249
|
+
assert.equal(finalReview?.value?.follow_up?.chat?.start_from, "unread");
|
|
2250
|
+
assert.equal(finalReview?.value?.follow_up?.chat?.target_count, 5);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
async function testCompletedPipelineShouldRunChatFollowUp() {
|
|
2254
|
+
let getChatRunCalls = 0;
|
|
2255
|
+
const result = await runRecommendPipeline(
|
|
2256
|
+
{
|
|
2257
|
+
workspaceRoot: process.cwd(),
|
|
2258
|
+
instruction: "test",
|
|
2259
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
2260
|
+
overrides: {},
|
|
2261
|
+
followUp: createFollowUpChat()
|
|
2262
|
+
},
|
|
2263
|
+
{
|
|
2264
|
+
parseRecommendInstruction: () => createParsed(),
|
|
2265
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
|
|
2266
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
2267
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
2268
|
+
readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
|
|
2269
|
+
switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
|
|
2270
|
+
runRecommendSearchCli: async () => ({
|
|
2271
|
+
ok: true,
|
|
2272
|
+
summary: {
|
|
2273
|
+
candidate_count: 8,
|
|
2274
|
+
applied_filters: { degree: ["本科"] },
|
|
2275
|
+
selected_job: DEFAULT_JOB_OPTIONS[0]
|
|
2276
|
+
}
|
|
2277
|
+
}),
|
|
2278
|
+
runRecommendScreenCli: async () => ({
|
|
2279
|
+
ok: true,
|
|
2280
|
+
summary: {
|
|
2281
|
+
processed_count: 6,
|
|
2282
|
+
passed_count: 2,
|
|
2283
|
+
skipped_count: 4,
|
|
2284
|
+
output_csv: "C:/temp/recommend.csv",
|
|
2285
|
+
completion_reason: "screen_completed"
|
|
2286
|
+
}
|
|
2287
|
+
}),
|
|
2288
|
+
startBossChatRun: async ({ input }) => {
|
|
2289
|
+
assert.equal(input.job, DEFAULT_JOB_OPTIONS[0].title);
|
|
2290
|
+
assert.equal(input.port, 9555);
|
|
2291
|
+
return {
|
|
2292
|
+
status: "ACCEPTED",
|
|
2293
|
+
run_id: "chat-run-1",
|
|
2294
|
+
message: "chat started"
|
|
2295
|
+
};
|
|
2296
|
+
},
|
|
2297
|
+
getBossChatRun: async () => {
|
|
2298
|
+
getChatRunCalls += 1;
|
|
2299
|
+
if (getChatRunCalls === 1) {
|
|
2300
|
+
return {
|
|
2301
|
+
status: "RUN_STATUS",
|
|
2302
|
+
run: {
|
|
2303
|
+
runId: "chat-run-1",
|
|
2304
|
+
state: "running",
|
|
2305
|
+
lastMessage: "chat running",
|
|
2306
|
+
progress: {
|
|
2307
|
+
inspected: 1,
|
|
2308
|
+
passed: 0,
|
|
2309
|
+
requested: 0,
|
|
2310
|
+
skipped: 1,
|
|
2311
|
+
errors: 0
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
return {
|
|
2317
|
+
status: "RUN_STATUS",
|
|
2318
|
+
run: {
|
|
2319
|
+
runId: "chat-run-1",
|
|
2320
|
+
state: "completed",
|
|
2321
|
+
lastMessage: "chat completed",
|
|
2322
|
+
progress: {
|
|
2323
|
+
inspected: 3,
|
|
2324
|
+
passed: 1,
|
|
2325
|
+
requested: 1,
|
|
2326
|
+
skipped: 2,
|
|
2327
|
+
errors: 0
|
|
2328
|
+
},
|
|
2329
|
+
result: {
|
|
2330
|
+
requested_count: 1
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
);
|
|
2337
|
+
|
|
2338
|
+
assert.equal(result.status, "COMPLETED");
|
|
2339
|
+
assert.equal(result.result.output_csv, "C:/temp/recommend.csv");
|
|
2340
|
+
assert.equal(result.follow_up?.chat?.run_id, "chat-run-1");
|
|
2341
|
+
assert.equal(result.follow_up?.chat?.state, "completed");
|
|
2342
|
+
assert.equal(result.follow_up?.chat?.input?.port, 9555);
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
async function testCompletedPipelineShouldFailWhenChatLaunchFails() {
|
|
2346
|
+
const result = await runRecommendPipeline(
|
|
2347
|
+
{
|
|
2348
|
+
workspaceRoot: process.cwd(),
|
|
2349
|
+
instruction: "test",
|
|
2350
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
2351
|
+
overrides: {},
|
|
2352
|
+
followUp: createFollowUpChat()
|
|
2353
|
+
},
|
|
2354
|
+
{
|
|
2355
|
+
parseRecommendInstruction: () => createParsed(),
|
|
2356
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
2357
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
2358
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
2359
|
+
readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
|
|
2360
|
+
switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
|
|
2361
|
+
runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
|
|
2362
|
+
runRecommendScreenCli: async () => ({
|
|
2363
|
+
ok: true,
|
|
2364
|
+
summary: {
|
|
2365
|
+
processed_count: 1,
|
|
2366
|
+
passed_count: 1,
|
|
2367
|
+
skipped_count: 0,
|
|
2368
|
+
output_csv: "C:/temp/recommend.csv",
|
|
2369
|
+
completion_reason: "screen_completed"
|
|
2370
|
+
}
|
|
2371
|
+
}),
|
|
2372
|
+
startBossChatRun: async () => ({
|
|
2373
|
+
status: "FAILED",
|
|
2374
|
+
error: {
|
|
2375
|
+
code: "CHAT_START_FAILED",
|
|
2376
|
+
message: "cannot start chat"
|
|
2377
|
+
}
|
|
2378
|
+
})
|
|
2379
|
+
}
|
|
2380
|
+
);
|
|
2381
|
+
|
|
2382
|
+
assert.equal(result.status, "FAILED");
|
|
2383
|
+
assert.equal(result.error.code, "CHAT_START_FAILED");
|
|
2384
|
+
assert.equal(result.follow_up?.chat?.launch_result?.status, "FAILED");
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
async function testCompletedPipelineShouldFailWhenChatRunFails() {
|
|
2388
|
+
const result = await runRecommendPipeline(
|
|
2389
|
+
{
|
|
2390
|
+
workspaceRoot: process.cwd(),
|
|
2391
|
+
instruction: "test",
|
|
2392
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
2393
|
+
overrides: {},
|
|
2394
|
+
followUp: createFollowUpChat()
|
|
2395
|
+
},
|
|
2396
|
+
{
|
|
2397
|
+
parseRecommendInstruction: () => createParsed(),
|
|
2398
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
2399
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
2400
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
2401
|
+
readRecommendTabState: async () => ({ ok: true, active_tab_status: "0" }),
|
|
2402
|
+
switchRecommendTab: async () => ({ ok: true, state: "TAB_READY" }),
|
|
2403
|
+
runRecommendSearchCli: async () => ({ ok: true, summary: { candidate_count: 1, applied_filters: {} } }),
|
|
2404
|
+
runRecommendScreenCli: async () => ({
|
|
2405
|
+
ok: true,
|
|
2406
|
+
summary: {
|
|
2407
|
+
processed_count: 1,
|
|
2408
|
+
passed_count: 1,
|
|
2409
|
+
skipped_count: 0,
|
|
2410
|
+
output_csv: "C:/temp/recommend.csv",
|
|
2411
|
+
completion_reason: "screen_completed"
|
|
2412
|
+
}
|
|
2413
|
+
}),
|
|
2414
|
+
startBossChatRun: async () => ({
|
|
2415
|
+
status: "ACCEPTED",
|
|
2416
|
+
run_id: "chat-run-2",
|
|
2417
|
+
message: "chat started"
|
|
2418
|
+
}),
|
|
2419
|
+
getBossChatRun: async () => ({
|
|
2420
|
+
status: "RUN_STATUS",
|
|
2421
|
+
run: {
|
|
2422
|
+
runId: "chat-run-2",
|
|
2423
|
+
state: "failed",
|
|
2424
|
+
lastMessage: "chat failed",
|
|
2425
|
+
error: {
|
|
2426
|
+
code: "CHAT_RUNTIME_FAILED",
|
|
2427
|
+
message: "chat runtime failed"
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2430
|
+
})
|
|
2431
|
+
}
|
|
2432
|
+
);
|
|
2433
|
+
|
|
2434
|
+
assert.equal(result.status, "FAILED");
|
|
2435
|
+
assert.equal(result.error.code, "CHAT_RUNTIME_FAILED");
|
|
2436
|
+
assert.equal(result.follow_up?.chat?.state, "failed");
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2041
2439
|
async function main() {
|
|
2042
2440
|
await testPauseRequestedBeforeScreenShouldReturnPaused();
|
|
2043
2441
|
await testPausedScreenResultShouldBubbleUp();
|
|
@@ -2057,6 +2455,7 @@ async function main() {
|
|
|
2057
2455
|
await testNeedInputGate();
|
|
2058
2456
|
await testFeaturedPipelineShouldRunSearchThenSwitchTabThenScreen();
|
|
2059
2457
|
await testLatestPipelineShouldRunSearchThenSwitchTabThenScreen();
|
|
2458
|
+
await testPipelineShouldPassInputSummaryToScreenCli();
|
|
2060
2459
|
await testFeaturedMissingCalibrationShouldAutoCalibrateThenContinue();
|
|
2061
2460
|
await testFeaturedCalibrationFailureShouldReturnCalibrationRequired();
|
|
2062
2461
|
await testFeaturedTabSwitchFailureShouldReturnRetryableError();
|
|
@@ -2076,6 +2475,14 @@ async function main() {
|
|
|
2076
2475
|
await testScreenConfigFailureShouldRequireUserProvidedConfig();
|
|
2077
2476
|
await testScreenConfigPlaceholderShouldRequireUserConfirmationAfterUpdate();
|
|
2078
2477
|
await testScreenConfigRecoveryStepShouldBeFirst();
|
|
2478
|
+
await testFollowUpChatMissingCriteriaShouldNeedInput();
|
|
2479
|
+
await testFollowUpChatMissingFieldsShouldExposeRecommendDefaults();
|
|
2480
|
+
await testFollowUpChatMissingStartFromShouldNeedInput();
|
|
2481
|
+
await testFollowUpChatMissingTargetCountShouldNeedInput();
|
|
2482
|
+
await testFinalReviewShouldIncludeFollowUpChatSummary();
|
|
2483
|
+
await testCompletedPipelineShouldRunChatFollowUp();
|
|
2484
|
+
await testCompletedPipelineShouldFailWhenChatLaunchFails();
|
|
2485
|
+
await testCompletedPipelineShouldFailWhenChatRunFails();
|
|
2079
2486
|
console.log("pipeline tests passed");
|
|
2080
2487
|
}
|
|
2081
2488
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# boss-chat
|
|
2
|
+
|
|
3
|
+
基于 Chrome DevTools Protocol 和 OpenAI 兼容 LLM 的 Boss 直聘聊天页教育筛选与自动沟通 CLI。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 连接已登录、已打开聊天页的 Chrome
|
|
8
|
+
- 遍历聊天列表客户卡片,读取教育信息
|
|
9
|
+
- 用自定义 `baseUrl + apiKey + model` 调用大模型判断是否符合教育要求
|
|
10
|
+
- 命中时自动生成简短话术并写入聊天框,随后发送
|
|
11
|
+
- 默认记录已处理客户,避免重复触达
|
|
12
|
+
- 支持选择从`未读`或`全部`列表开始处理
|
|
13
|
+
- 支持 `--dry-run` 先验证页面稳定性和命中效果
|
|
14
|
+
|
|
15
|
+
## 依赖
|
|
16
|
+
|
|
17
|
+
- Node.js(建议 18+)
|
|
18
|
+
- Google Chrome(需开启远程调试端口)
|
|
19
|
+
- npm 包依赖:
|
|
20
|
+
- `chrome-remote-interface@^0.33.3`
|
|
21
|
+
|
|
22
|
+
## 使用前准备
|
|
23
|
+
|
|
24
|
+
1. 用远程调试模式启动 Chrome:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
chrome.exe --remote-debugging-port=9222
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
2. 登录 Boss 直聘并打开聊天页:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
https://www.zhipin.com/web/chat/index
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
3. 全局安装:
|
|
37
|
+
|
|
38
|
+
```powershell
|
|
39
|
+
npm install -g @reconcrap/boss-chat-cli
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
也可以直接用 `npx`(适合 agent 平台托管 MCP 时):
|
|
43
|
+
|
|
44
|
+
```powershell
|
|
45
|
+
npx -y -p @reconcrap/boss-chat-cli@latest boss-chat-mcp
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 运行
|
|
49
|
+
|
|
50
|
+
首次运行会交互式询问教育要求、话术样例、LLM 参数等配置;每次运行也可选择从`未读`或`全部`开始:
|
|
51
|
+
|
|
52
|
+
```powershell
|
|
53
|
+
boss-chat run
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
建议先用 dry-run:
|
|
57
|
+
|
|
58
|
+
```powershell
|
|
59
|
+
boss-chat run --dry-run --targetCount 3
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## MCP Agent 集成(openclaw / codex / trae-cn)
|
|
63
|
+
|
|
64
|
+
包内已内置 MCP stdio server,可直接被三类平台调用:
|
|
65
|
+
|
|
66
|
+
- `openclaw-boss-chat-mcp`
|
|
67
|
+
- `codex-boss-chat-mcp`
|
|
68
|
+
- `trae-cn-boss-chat-mcp`
|
|
69
|
+
- 通用入口:`boss-chat-mcp`
|
|
70
|
+
|
|
71
|
+
### 工具列表
|
|
72
|
+
|
|
73
|
+
- `health_check`: 检查服务是否可用
|
|
74
|
+
- `start_run`: 启动异步任务,返回 `run_id`
|
|
75
|
+
- `get_run`: 查询任务状态
|
|
76
|
+
- `pause_run`: 暂停任务
|
|
77
|
+
- `resume_run`: 继续任务
|
|
78
|
+
- `cancel_run`: 取消任务
|
|
79
|
+
|
|
80
|
+
### 平台配置示例
|
|
81
|
+
|
|
82
|
+
项目里提供了三份可直接复制的模板:
|
|
83
|
+
|
|
84
|
+
- `configs/mcp/openclaw.json`
|
|
85
|
+
- `configs/mcp/codex.json`
|
|
86
|
+
- `configs/mcp/trae-cn.json`
|
|
87
|
+
|
|
88
|
+
三份配置都通过 `npx` 拉取最新包并启动对应 MCP 入口,例如:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"mcpServers": {
|
|
93
|
+
"boss-chat": {
|
|
94
|
+
"command": "npx",
|
|
95
|
+
"args": ["-y", "-p", "@reconcrap/boss-chat-cli@latest", "boss-chat-mcp"]
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
> 不同平台的 MCP 配置文件路径可能不同,但 `command + args` 可直接复用。
|
|
102
|
+
|
|
103
|
+
## 运行中控制
|
|
104
|
+
|
|
105
|
+
程序运行时可以直接用键盘控制:
|
|
106
|
+
|
|
107
|
+
- `p`: 暂停;再次按 `p` 或按 `r` 继续
|
|
108
|
+
- `r`: 继续运行
|
|
109
|
+
- `q`: 请求停止,当前步骤结束后安全退出
|
|
110
|
+
- `Ctrl+C`: 请求停止,当前步骤结束后安全退出
|
|
111
|
+
|
|
112
|
+
停止后仍会写入本次运行报告,已记录的客户状态也会保留。
|
|
113
|
+
|
|
114
|
+
## 常用参数
|
|
115
|
+
|
|
116
|
+
- `--profile <name>`: 使用指定 profile
|
|
117
|
+
- `--dry-run`: 只检查和生成文案,不实际发送
|
|
118
|
+
- `--no-state`: 不记录已处理客户
|
|
119
|
+
- `--targetCount <n>`: 覆盖本次检查人数
|
|
120
|
+
- `--educationRequirement <text>`: 覆盖教育要求
|
|
121
|
+
- `--messageSample <text>`: 覆盖话术样例
|
|
122
|
+
- `--start-from <unread|all>`: 本次从未读或全部列表开始
|
|
123
|
+
- `--baseurl <url>`: 覆盖 LLM base URL
|
|
124
|
+
- `--apikey <key>`: 覆盖 LLM API key
|
|
125
|
+
- `--model <name>`: 覆盖 LLM 模型
|
|
126
|
+
- `--port <n>`: 覆盖 Chrome 远程调试端口
|
|
127
|
+
|
|
128
|
+
## 数据目录
|
|
129
|
+
|
|
130
|
+
运行产生的数据默认保存在项目下的 `.boss-chat/`:
|
|
131
|
+
|
|
132
|
+
- `profiles/`: 保存 profile 配置
|
|
133
|
+
- `state/`: 保存已处理客户状态
|
|
134
|
+
- `reports/`: 保存每次运行的 JSON 报告
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@reconcrap/boss-chat-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Boss chat education screening and outreach CLI with MCP agent bridge",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"boss-chat": "src/cli.js",
|
|
8
|
+
"boss-chat-mcp": "src/mcp/server.js",
|
|
9
|
+
"openclaw-boss-chat-mcp": "src/mcp/server.js",
|
|
10
|
+
"codex-boss-chat-mcp": "src/mcp/server.js",
|
|
11
|
+
"trae-cn-boss-chat-mcp": "src/mcp/server.js"
|
|
12
|
+
},
|
|
13
|
+
"main": "./src/cli.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"src/",
|
|
16
|
+
"configs/",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"start": "node ./src/cli.js run",
|
|
21
|
+
"start:mcp": "node ./src/mcp/server.js",
|
|
22
|
+
"test": "node --test"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"boss",
|
|
26
|
+
"zhipin",
|
|
27
|
+
"chrome-devtools",
|
|
28
|
+
"cli",
|
|
29
|
+
"automation",
|
|
30
|
+
"mcp",
|
|
31
|
+
"openclaw",
|
|
32
|
+
"codex",
|
|
33
|
+
"trae-cn"
|
|
34
|
+
],
|
|
35
|
+
"author": "",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0"
|
|
39
|
+
},
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": ""
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
49
|
+
"chrome-remote-interface": "^0.33.3",
|
|
50
|
+
"sharp": "^0.34.5",
|
|
51
|
+
"zod": "^4.1.12"
|
|
52
|
+
}
|
|
53
|
+
}
|