@reconcrap/boss-recommend-mcp 1.2.10 → 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.
Files changed (34) hide show
  1. package/README.md +82 -1
  2. package/package.json +2 -1
  3. package/skills/boss-chat/README.md +5 -0
  4. package/skills/boss-chat/SKILL.md +69 -0
  5. package/skills/boss-recommend-pipeline/SKILL.md +40 -4
  6. package/src/adapters.js +19 -5
  7. package/src/boss-chat.js +436 -0
  8. package/src/cli.js +294 -129
  9. package/src/index.js +459 -108
  10. package/src/pipeline.js +605 -8
  11. package/src/run-state.js +5 -0
  12. package/src/test-adapters-runtime.js +69 -0
  13. package/src/test-boss-chat.js +399 -0
  14. package/src/test-index-async.js +238 -4
  15. package/src/test-pipeline.js +408 -1
  16. package/vendor/boss-chat-cli/README.md +134 -0
  17. package/vendor/boss-chat-cli/package.json +53 -0
  18. package/vendor/boss-chat-cli/src/app.js +769 -0
  19. package/vendor/boss-chat-cli/src/browser/chat-page.js +2681 -0
  20. package/vendor/boss-chat-cli/src/cli.js +1350 -0
  21. package/vendor/boss-chat-cli/src/mcp/server.js +149 -0
  22. package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +193 -0
  23. package/vendor/boss-chat-cli/src/runtime/async-run-state.js +260 -0
  24. package/vendor/boss-chat-cli/src/runtime/interaction.js +102 -0
  25. package/vendor/boss-chat-cli/src/runtime/run-control.js +102 -0
  26. package/vendor/boss-chat-cli/src/services/chrome-client.js +97 -0
  27. package/vendor/boss-chat-cli/src/services/llm.js +352 -0
  28. package/vendor/boss-chat-cli/src/services/profile-store.js +157 -0
  29. package/vendor/boss-chat-cli/src/services/report-store.js +19 -0
  30. package/vendor/boss-chat-cli/src/services/resume-capture.js +554 -0
  31. package/vendor/boss-chat-cli/src/services/state-store.js +217 -0
  32. package/vendor/boss-chat-cli/src/utils/customer-key.js +82 -0
  33. package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +902 -56
  34. package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +387 -1
@@ -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 startAcceptedRun(instruction, idSeed = 1) {
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
- return {
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);