@reconcrap/boss-recommend-mcp 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/parser.js +4 -5
- 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-parser.js +33 -6
- 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 +769 -0
- package/vendor/boss-chat-cli/src/browser/chat-page.js +2681 -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-index-async.js
CHANGED
|
@@ -21,6 +21,17 @@ function sleep(ms) {
|
|
|
21
21
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function createFollowUpChat(overrides = {}) {
|
|
25
|
+
return {
|
|
26
|
+
chat: {
|
|
27
|
+
criteria: "有 AI Agent 经验",
|
|
28
|
+
start_from: "unread",
|
|
29
|
+
target_count: 3,
|
|
30
|
+
...overrides
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
24
35
|
function makeToolCall(id, name, args = {}) {
|
|
25
36
|
return {
|
|
26
37
|
jsonrpc: "2.0",
|
|
@@ -57,14 +68,25 @@ async function waitForRunState(runId, acceptedStates, timeoutMs = 6000) {
|
|
|
57
68
|
throw new Error(`Timed out waiting run state (${Array.from(accepted).join(", ")}) for run_id=${runId}`);
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
async function
|
|
71
|
+
async function waitForRunSnapshot(runId, predicate, timeoutMs = 6000) {
|
|
72
|
+
const deadline = Date.now() + timeoutMs;
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
const payload = await callTool(TOOL_GET_RUN, { run_id: runId }, 1002);
|
|
75
|
+
if (predicate(payload?.run)) return payload.run;
|
|
76
|
+
await sleep(80);
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`Timed out waiting run snapshot for run_id=${runId}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function startAcceptedRun(instruction, idSeed = 1, extraArgs = {}) {
|
|
61
82
|
const payload = await callTool(TOOL_START_RUN, {
|
|
62
83
|
instruction,
|
|
63
84
|
confirmation: {
|
|
64
85
|
job_confirmed: true,
|
|
65
86
|
job_value: "mock job",
|
|
66
87
|
final_confirmed: true
|
|
67
|
-
}
|
|
88
|
+
},
|
|
89
|
+
...extraArgs
|
|
68
90
|
}, idSeed);
|
|
69
91
|
assert.equal(payload.status, "ACCEPTED");
|
|
70
92
|
assert.equal(typeof payload.run_id, "string");
|
|
@@ -73,6 +95,7 @@ async function startAcceptedRun(instruction, idSeed = 1) {
|
|
|
73
95
|
|
|
74
96
|
function setupPipelineMock() {
|
|
75
97
|
const checkpointStore = new Map();
|
|
98
|
+
const chatStateStore = new Map();
|
|
76
99
|
setRunPipelineImplForTests(async (input, _deps, runtime) => {
|
|
77
100
|
if (input.confirmation?.job_confirmed !== true) {
|
|
78
101
|
return {
|
|
@@ -128,15 +151,172 @@ function setupPipelineMock() {
|
|
|
128
151
|
}
|
|
129
152
|
|
|
130
153
|
checkpointStore.delete(checkpointPath);
|
|
131
|
-
|
|
154
|
+
const recommendResult = {
|
|
132
155
|
status: "COMPLETED",
|
|
156
|
+
search_params: {
|
|
157
|
+
school_tag: ["985"]
|
|
158
|
+
},
|
|
159
|
+
screen_params: {
|
|
160
|
+
criteria: "mock criteria",
|
|
161
|
+
target_count: 12,
|
|
162
|
+
post_action: "favorite",
|
|
163
|
+
max_greet_count: null
|
|
164
|
+
},
|
|
133
165
|
result: {
|
|
134
166
|
processed_count: total,
|
|
135
167
|
passed_count: Math.floor(total / 3),
|
|
136
168
|
skipped_count: total - Math.floor(total / 3),
|
|
137
169
|
greet_count: 0,
|
|
138
170
|
output_csv: outputCsv,
|
|
139
|
-
completion_reason: "screen_completed"
|
|
171
|
+
completion_reason: "screen_completed",
|
|
172
|
+
selected_job: {
|
|
173
|
+
title: "mock job"
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
if (!input.followUp?.chat) {
|
|
179
|
+
return recommendResult;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
runtime?.onStage?.({ stage: "chat_followup", message: "chat follow-up running" });
|
|
183
|
+
const chatCheckpointKey = `${checkpointPath}:chat`;
|
|
184
|
+
let chatState = "running";
|
|
185
|
+
if (input.resume?.resume === true && input.resume?.follow_up_phase === "chat_followup") {
|
|
186
|
+
const storedState = String(chatStateStore.get(chatCheckpointKey) || "running").trim().toLowerCase();
|
|
187
|
+
chatState = storedState === "paused" ? "running" : storedState;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
runtime?.onFollowUp?.({
|
|
191
|
+
stage: "chat_followup",
|
|
192
|
+
last_message: "chat follow-up running",
|
|
193
|
+
recommend_payload: recommendResult,
|
|
194
|
+
recommend_result: recommendResult.result,
|
|
195
|
+
follow_up: {
|
|
196
|
+
chat: {
|
|
197
|
+
run_id: "mock-chat-run",
|
|
198
|
+
state: chatState,
|
|
199
|
+
input: {
|
|
200
|
+
...input.followUp.chat
|
|
201
|
+
},
|
|
202
|
+
progress: {
|
|
203
|
+
inspected: 1,
|
|
204
|
+
passed: 0,
|
|
205
|
+
requested: 0,
|
|
206
|
+
skipped: 1,
|
|
207
|
+
errors: 0
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
for (let chatTick = 0; chatTick < 20; chatTick += 1) {
|
|
214
|
+
await sleep(25);
|
|
215
|
+
if (runtime?.isCancelRequested?.() === true) {
|
|
216
|
+
chatStateStore.set(chatCheckpointKey, "canceled");
|
|
217
|
+
runtime?.onFollowUp?.({
|
|
218
|
+
stage: "chat_followup",
|
|
219
|
+
last_message: "chat follow-up canceled",
|
|
220
|
+
recommend_payload: recommendResult,
|
|
221
|
+
recommend_result: recommendResult.result,
|
|
222
|
+
follow_up: {
|
|
223
|
+
chat: {
|
|
224
|
+
run_id: "mock-chat-run",
|
|
225
|
+
state: "canceled",
|
|
226
|
+
input: {
|
|
227
|
+
...input.followUp.chat
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
...recommendResult,
|
|
234
|
+
status: "PAUSED",
|
|
235
|
+
partial_result: recommendResult.result,
|
|
236
|
+
follow_up: {
|
|
237
|
+
chat: {
|
|
238
|
+
run_id: "mock-chat-run",
|
|
239
|
+
state: "canceled",
|
|
240
|
+
input: {
|
|
241
|
+
...input.followUp.chat
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (runtime?.isPauseRequested?.() === true) {
|
|
248
|
+
chatStateStore.set(chatCheckpointKey, "paused");
|
|
249
|
+
runtime?.onFollowUp?.({
|
|
250
|
+
stage: "chat_followup",
|
|
251
|
+
last_message: "chat follow-up paused",
|
|
252
|
+
recommend_payload: recommendResult,
|
|
253
|
+
recommend_result: recommendResult.result,
|
|
254
|
+
follow_up: {
|
|
255
|
+
chat: {
|
|
256
|
+
run_id: "mock-chat-run",
|
|
257
|
+
state: "paused",
|
|
258
|
+
input: {
|
|
259
|
+
...input.followUp.chat
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
return {
|
|
265
|
+
...recommendResult,
|
|
266
|
+
status: "PAUSED",
|
|
267
|
+
partial_result: recommendResult.result,
|
|
268
|
+
follow_up: {
|
|
269
|
+
chat: {
|
|
270
|
+
run_id: "mock-chat-run",
|
|
271
|
+
state: "paused",
|
|
272
|
+
input: {
|
|
273
|
+
...input.followUp.chat
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
chatStateStore.delete(chatCheckpointKey);
|
|
282
|
+
runtime?.onFollowUp?.({
|
|
283
|
+
stage: "chat_followup",
|
|
284
|
+
last_message: "chat follow-up completed",
|
|
285
|
+
recommend_payload: recommendResult,
|
|
286
|
+
recommend_result: recommendResult.result,
|
|
287
|
+
follow_up: {
|
|
288
|
+
chat: {
|
|
289
|
+
run_id: "mock-chat-run",
|
|
290
|
+
state: "completed",
|
|
291
|
+
input: {
|
|
292
|
+
...input.followUp.chat
|
|
293
|
+
},
|
|
294
|
+
progress: {
|
|
295
|
+
inspected: 3,
|
|
296
|
+
passed: 1,
|
|
297
|
+
requested: 1,
|
|
298
|
+
skipped: 2,
|
|
299
|
+
errors: 0
|
|
300
|
+
},
|
|
301
|
+
result: {
|
|
302
|
+
requested_count: 1
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
return {
|
|
308
|
+
...recommendResult,
|
|
309
|
+
follow_up: {
|
|
310
|
+
chat: {
|
|
311
|
+
run_id: "mock-chat-run",
|
|
312
|
+
state: "completed",
|
|
313
|
+
input: {
|
|
314
|
+
...input.followUp.chat
|
|
315
|
+
},
|
|
316
|
+
result: {
|
|
317
|
+
requested_count: 1
|
|
318
|
+
}
|
|
319
|
+
}
|
|
140
320
|
}
|
|
141
321
|
};
|
|
142
322
|
});
|
|
@@ -236,6 +416,58 @@ async function testCancelRunningRunKeepsCsv() {
|
|
|
236
416
|
assert.equal(Boolean(canceledRun.resume?.output_csv), true);
|
|
237
417
|
}
|
|
238
418
|
|
|
419
|
+
async function testPauseAndResumeDuringChatFollowUp() {
|
|
420
|
+
const runId = await startAcceptedRun("run with follow-up chat pause resume", 51, {
|
|
421
|
+
follow_up: createFollowUpChat()
|
|
422
|
+
});
|
|
423
|
+
const runningChatRun = await waitForRunSnapshot(
|
|
424
|
+
runId,
|
|
425
|
+
(run) => run?.stage === "chat_followup" && run?.state === "running" && run?.result?.follow_up?.chat?.state === "running"
|
|
426
|
+
);
|
|
427
|
+
assert.equal(runningChatRun.result?.result?.processed_count, 12);
|
|
428
|
+
assert.equal(runningChatRun.result?.follow_up?.chat?.run_id, "mock-chat-run");
|
|
429
|
+
|
|
430
|
+
const pausePayload = await callTool(TOOL_PAUSE_RUN, { run_id: runId }, 52);
|
|
431
|
+
assert.equal(pausePayload.status, "PAUSE_REQUESTED");
|
|
432
|
+
|
|
433
|
+
const pausedRun = await waitForRunSnapshot(
|
|
434
|
+
runId,
|
|
435
|
+
(run) => run?.state === "paused" && run?.stage === "chat_followup"
|
|
436
|
+
);
|
|
437
|
+
assert.equal(pausedRun.result?.follow_up?.chat?.state, "paused");
|
|
438
|
+
assert.equal(pausedRun.result?.result?.processed_count, 12);
|
|
439
|
+
|
|
440
|
+
const resumePayload = await callTool(TOOL_RESUME_RUN, { run_id: runId }, 53);
|
|
441
|
+
assert.equal(resumePayload.status, "RESUME_REQUESTED");
|
|
442
|
+
|
|
443
|
+
const completedRun = await waitForRunSnapshot(
|
|
444
|
+
runId,
|
|
445
|
+
(run) => run?.state === "completed" && run?.result?.follow_up?.chat?.state === "completed"
|
|
446
|
+
);
|
|
447
|
+
assert.equal(completedRun.result?.follow_up?.chat?.result?.requested_count, 1);
|
|
448
|
+
assert.equal(completedRun.result?.result?.processed_count, 12);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async function testCancelDuringChatFollowUp() {
|
|
452
|
+
const runId = await startAcceptedRun("run with follow-up chat cancel", 61, {
|
|
453
|
+
follow_up: createFollowUpChat()
|
|
454
|
+
});
|
|
455
|
+
await waitForRunSnapshot(
|
|
456
|
+
runId,
|
|
457
|
+
(run) => run?.stage === "chat_followup" && run?.state === "running" && run?.result?.follow_up?.chat?.state === "running"
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
const cancelPayload = await callTool(TOOL_CANCEL_RUN, { run_id: runId }, 62);
|
|
461
|
+
assert.equal(cancelPayload.status, "CANCEL_REQUESTED");
|
|
462
|
+
|
|
463
|
+
const canceledRun = await waitForRunSnapshot(
|
|
464
|
+
runId,
|
|
465
|
+
(run) => run?.state === "canceled" && run?.stage === "chat_followup"
|
|
466
|
+
);
|
|
467
|
+
assert.equal(canceledRun.result?.follow_up?.chat?.state, "canceled");
|
|
468
|
+
assert.equal(canceledRun.result?.result?.processed_count, 12);
|
|
469
|
+
}
|
|
470
|
+
|
|
239
471
|
async function main() {
|
|
240
472
|
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
241
473
|
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-index-async-"));
|
|
@@ -248,6 +480,8 @@ async function main() {
|
|
|
248
480
|
await testResumeAfterProcessRestartSimulation();
|
|
249
481
|
await testCancelPausedRun();
|
|
250
482
|
await testCancelRunningRunKeepsCsv();
|
|
483
|
+
await testPauseAndResumeDuringChatFollowUp();
|
|
484
|
+
await testCancelDuringChatFollowUp();
|
|
251
485
|
console.log("index async tests passed");
|
|
252
486
|
} finally {
|
|
253
487
|
setRunPipelineImplForTests(null);
|
package/src/test-parser.js
CHANGED
|
@@ -385,7 +385,7 @@ function testGreetMaxGreetCountCanComeFromOverrides() {
|
|
|
385
385
|
assert.equal(result.needs_max_greet_count_confirmation, true);
|
|
386
386
|
}
|
|
387
387
|
|
|
388
|
-
function
|
|
388
|
+
function testGreetAutoFilledMaxGreetCountShouldRequireReconfirmationWhenNotExplicitlyConfirmed() {
|
|
389
389
|
const result = parseRecommendInstruction({
|
|
390
390
|
instruction: "推荐页筛选985男生,有大模型工程经验,目标3人,符合标准直接沟通",
|
|
391
391
|
confirmation: {
|
|
@@ -398,11 +398,11 @@ function testGreetAutoFilledMaxGreetCountShouldRequireReconfirmation() {
|
|
|
398
398
|
target_count_confirmed: true,
|
|
399
399
|
target_count_value: 3,
|
|
400
400
|
post_action_confirmed: true,
|
|
401
|
-
post_action_value: "greet"
|
|
402
|
-
max_greet_count_confirmed: true,
|
|
403
|
-
max_greet_count_value: 3
|
|
401
|
+
post_action_value: "greet"
|
|
404
402
|
},
|
|
405
|
-
overrides:
|
|
403
|
+
overrides: {
|
|
404
|
+
max_greet_count: 3
|
|
405
|
+
}
|
|
406
406
|
});
|
|
407
407
|
|
|
408
408
|
assert.equal(result.screenParams.post_action, "greet");
|
|
@@ -412,6 +412,32 @@ function testGreetAutoFilledMaxGreetCountShouldRequireReconfirmation() {
|
|
|
412
412
|
assert.equal(result.suspicious_fields.some((item) => item.field === "max_greet_count"), true);
|
|
413
413
|
}
|
|
414
414
|
|
|
415
|
+
function testGreetMaxGreetCountEqualTargetShouldPassAfterExplicitConfirmation() {
|
|
416
|
+
const result = parseRecommendInstruction({
|
|
417
|
+
instruction: "推荐页筛选985男生,有大模型工程经验,目标3人,符合标准直接沟通",
|
|
418
|
+
confirmation: {
|
|
419
|
+
filters_confirmed: true,
|
|
420
|
+
school_tag_confirmed: true,
|
|
421
|
+
degree_confirmed: true,
|
|
422
|
+
gender_confirmed: true,
|
|
423
|
+
recent_not_view_confirmed: true,
|
|
424
|
+
criteria_confirmed: true,
|
|
425
|
+
target_count_confirmed: true,
|
|
426
|
+
target_count_value: 3,
|
|
427
|
+
post_action_confirmed: true,
|
|
428
|
+
post_action_value: "greet",
|
|
429
|
+
max_greet_count_confirmed: true,
|
|
430
|
+
max_greet_count_value: 3
|
|
431
|
+
},
|
|
432
|
+
overrides: null
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
assert.equal(result.screenParams.post_action, "greet");
|
|
436
|
+
assert.equal(result.screenParams.max_greet_count, 3);
|
|
437
|
+
assert.equal(result.needs_max_greet_count_confirmation, false);
|
|
438
|
+
assert.equal(result.pending_questions.some((q) => q.field === "max_greet_count"), false);
|
|
439
|
+
}
|
|
440
|
+
|
|
415
441
|
function testTargetCountNeedsConfirmationEvenWhenOptional() {
|
|
416
442
|
const result = parseRecommendInstruction({
|
|
417
443
|
instruction: "推荐页筛选985男生,有大模型平台经验,符合标准收藏",
|
|
@@ -602,7 +628,8 @@ function main() {
|
|
|
602
628
|
testMcpMentionShouldStayInCriteria();
|
|
603
629
|
testGreetNeedsMaxGreetCountConfirmation();
|
|
604
630
|
testGreetMaxGreetCountCanComeFromOverrides();
|
|
605
|
-
|
|
631
|
+
testGreetAutoFilledMaxGreetCountShouldRequireReconfirmationWhenNotExplicitlyConfirmed();
|
|
632
|
+
testGreetMaxGreetCountEqualTargetShouldPassAfterExplicitConfirmation();
|
|
606
633
|
testTargetCountNeedsConfirmationEvenWhenOptional();
|
|
607
634
|
testTargetCountCanBeSkippedAfterConfirmation();
|
|
608
635
|
testPostActionNoneCanBeConfirmed();
|