@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-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);
|