@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.
- 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 +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/run-state.js
CHANGED
|
@@ -18,6 +18,7 @@ export const RUN_STAGE_PAGE_READY = "page_ready";
|
|
|
18
18
|
export const RUN_STAGE_JOB_LIST = "job_list";
|
|
19
19
|
export const RUN_STAGE_SEARCH = "search";
|
|
20
20
|
export const RUN_STAGE_SCREEN = "screen";
|
|
21
|
+
export const RUN_STAGE_CHAT_FOLLOWUP = "chat_followup";
|
|
21
22
|
export const RUN_STAGE_FINALIZE = "finalize";
|
|
22
23
|
|
|
23
24
|
const DEFAULT_HEARTBEAT_INTERVAL_MS = 120_000;
|
|
@@ -38,6 +39,7 @@ const VALID_RUN_STAGES = new Set([
|
|
|
38
39
|
RUN_STAGE_JOB_LIST,
|
|
39
40
|
RUN_STAGE_SEARCH,
|
|
40
41
|
RUN_STAGE_SCREEN,
|
|
42
|
+
RUN_STAGE_CHAT_FOLLOWUP,
|
|
41
43
|
RUN_STAGE_FINALIZE
|
|
42
44
|
]);
|
|
43
45
|
|
|
@@ -158,6 +160,9 @@ function defaultResume(resume = {}) {
|
|
|
158
160
|
checkpoint_path: normalizeMessage(resume?.checkpoint_path || ""),
|
|
159
161
|
pause_control_path: normalizeMessage(resume?.pause_control_path || ""),
|
|
160
162
|
output_csv: normalizeMessage(resume?.output_csv || ""),
|
|
163
|
+
follow_up_phase: normalizeMessage(resume?.follow_up_phase || ""),
|
|
164
|
+
chat_run_id: normalizeMessage(resume?.chat_run_id || ""),
|
|
165
|
+
chat_state: normalizeMessage(resume?.chat_state || ""),
|
|
161
166
|
resume_count: Number.isInteger(resume?.resume_count) && resume.resume_count >= 0 ? resume.resume_count : 0,
|
|
162
167
|
last_resumed_at: normalizeMessage(resume?.last_resumed_at || ""),
|
|
163
168
|
last_paused_at: normalizeMessage(resume?.last_paused_at || "")
|
|
@@ -446,6 +446,74 @@ async function testScreenCliShouldPassLatestPageScopeArgument() {
|
|
|
446
446
|
}
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
async function testScreenCliShouldPassInputSummaryArgument() {
|
|
450
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
451
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-input-summary-home-"));
|
|
452
|
+
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-input-summary-workspace-"));
|
|
453
|
+
const cliDir = path.join(workspaceRoot, "boss-recommend-screen-cli");
|
|
454
|
+
fs.mkdirSync(cliDir, { recursive: true });
|
|
455
|
+
const cliPath = path.join(cliDir, "boss-recommend-screen-cli.cjs");
|
|
456
|
+
fs.writeFileSync(
|
|
457
|
+
cliPath,
|
|
458
|
+
[
|
|
459
|
+
"#!/usr/bin/env node",
|
|
460
|
+
"console.log(JSON.stringify({",
|
|
461
|
+
" status: 'COMPLETED',",
|
|
462
|
+
" result: {",
|
|
463
|
+
" processed_count: 0,",
|
|
464
|
+
" passed_count: 0,",
|
|
465
|
+
" skipped_count: 0,",
|
|
466
|
+
" argv: process.argv.slice(2),",
|
|
467
|
+
" resume_source: 'network',",
|
|
468
|
+
" active_tab_status: '0'",
|
|
469
|
+
" }",
|
|
470
|
+
"}));"
|
|
471
|
+
].join("\n"),
|
|
472
|
+
"utf8"
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
476
|
+
fs.writeFileSync(path.join(tempHome, "screening-config.json"), JSON.stringify({
|
|
477
|
+
baseUrl: "https://api.openai.com/v1",
|
|
478
|
+
apiKey: "sk-valid-test",
|
|
479
|
+
model: "gpt-4.1-mini"
|
|
480
|
+
}, null, 2));
|
|
481
|
+
|
|
482
|
+
try {
|
|
483
|
+
const inputSummary = {
|
|
484
|
+
instruction: "测试输入摘要",
|
|
485
|
+
search_params: { school_tag: ["985"], gender: "男" },
|
|
486
|
+
screen_params: { criteria: "有 MCP 经验" }
|
|
487
|
+
};
|
|
488
|
+
const result = await runRecommendScreenCli({
|
|
489
|
+
workspaceRoot,
|
|
490
|
+
screenParams: {
|
|
491
|
+
criteria: "有 MCP 经验",
|
|
492
|
+
target_count: null,
|
|
493
|
+
post_action: "none",
|
|
494
|
+
max_greet_count: null
|
|
495
|
+
},
|
|
496
|
+
inputSummary
|
|
497
|
+
});
|
|
498
|
+
assert.equal(result.ok, true);
|
|
499
|
+
const argv = result.summary?.argv || [];
|
|
500
|
+
const summaryIndex = argv.indexOf("--input-summary-json");
|
|
501
|
+
assert.equal(summaryIndex >= 0, true);
|
|
502
|
+
const parsedSummary = JSON.parse(String(argv[summaryIndex + 1] || "{}"));
|
|
503
|
+
assert.equal(parsedSummary.instruction, "测试输入摘要");
|
|
504
|
+
assert.equal(parsedSummary.search_params?.gender, "男");
|
|
505
|
+
assert.equal(parsedSummary.screen_params?.criteria, "有 MCP 经验");
|
|
506
|
+
} finally {
|
|
507
|
+
if (previousHome === undefined) {
|
|
508
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
509
|
+
} else {
|
|
510
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
511
|
+
}
|
|
512
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
513
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
449
517
|
async function main() {
|
|
450
518
|
await testRunProcessHeartbeatAndOutput();
|
|
451
519
|
await testRunProcessAbortSignal();
|
|
@@ -463,6 +531,7 @@ async function main() {
|
|
|
463
531
|
await testSearchCliShouldPassLatestPageScopeWithoutCalibration();
|
|
464
532
|
await testScreenCliShouldPassPageScopeArgument();
|
|
465
533
|
await testScreenCliShouldPassLatestPageScopeArgument();
|
|
534
|
+
await testScreenCliShouldPassInputSummaryArgument();
|
|
466
535
|
console.log("adapters runtime tests passed");
|
|
467
536
|
}
|
|
468
537
|
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
cancelBossChatRun,
|
|
8
|
+
getBossChatHealthCheck,
|
|
9
|
+
getBossChatRun,
|
|
10
|
+
pauseBossChatRun,
|
|
11
|
+
resumeBossChatRun,
|
|
12
|
+
startBossChatRun
|
|
13
|
+
} from "./boss-chat.js";
|
|
14
|
+
import { __testables as cliTestables } from "./cli.js";
|
|
15
|
+
import { __testables as indexTestables } from "./index.js";
|
|
16
|
+
|
|
17
|
+
const { handleRequest } = indexTestables;
|
|
18
|
+
|
|
19
|
+
const TOOL_BOSS_CHAT_HEALTH_CHECK = "boss_chat_health_check";
|
|
20
|
+
const TOOL_BOSS_CHAT_START_RUN = "start_boss_chat_run";
|
|
21
|
+
const TOOL_BOSS_CHAT_GET_RUN = "get_boss_chat_run";
|
|
22
|
+
const TOOL_BOSS_CHAT_PAUSE_RUN = "pause_boss_chat_run";
|
|
23
|
+
const TOOL_BOSS_CHAT_RESUME_RUN = "resume_boss_chat_run";
|
|
24
|
+
const TOOL_BOSS_CHAT_CANCEL_RUN = "cancel_boss_chat_run";
|
|
25
|
+
|
|
26
|
+
function makeToolCall(id, name, args = {}) {
|
|
27
|
+
return {
|
|
28
|
+
jsonrpc: "2.0",
|
|
29
|
+
id,
|
|
30
|
+
method: "tools/call",
|
|
31
|
+
params: {
|
|
32
|
+
name,
|
|
33
|
+
arguments: args
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function callTool(workspaceRoot, name, args = {}, id = 1) {
|
|
39
|
+
const response = await handleRequest(makeToolCall(id, name, args), workspaceRoot);
|
|
40
|
+
return response?.result?.structuredContent;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createBossChatTestWorkspace() {
|
|
44
|
+
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-boss-chat-"));
|
|
45
|
+
const configDir = path.join(workspaceRoot, "config");
|
|
46
|
+
const cliDir = path.join(workspaceRoot, "boss-chat-cli", "src");
|
|
47
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
48
|
+
fs.mkdirSync(cliDir, { recursive: true });
|
|
49
|
+
|
|
50
|
+
fs.writeFileSync(path.join(configDir, "screening-config.json"), JSON.stringify({
|
|
51
|
+
baseUrl: "https://api.example.com/v1",
|
|
52
|
+
apiKey: "sk-test-key",
|
|
53
|
+
model: "gpt-4.1-mini",
|
|
54
|
+
debugPort: 9666
|
|
55
|
+
}, null, 2));
|
|
56
|
+
|
|
57
|
+
fs.writeFileSync(path.join(cliDir, "cli.js"), [
|
|
58
|
+
"#!/usr/bin/env node",
|
|
59
|
+
"const fs = require('node:fs');",
|
|
60
|
+
"const path = require('node:path');",
|
|
61
|
+
"const cwd = process.cwd();",
|
|
62
|
+
"const statePath = path.join(cwd, '.boss-chat', 'stub-state.json');",
|
|
63
|
+
"fs.mkdirSync(path.dirname(statePath), { recursive: true });",
|
|
64
|
+
"const raw = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '{}';",
|
|
65
|
+
"const state = JSON.parse(raw || '{}');",
|
|
66
|
+
"state.counter = Number.isInteger(state.counter) ? state.counter : 0;",
|
|
67
|
+
"state.runs = state.runs && typeof state.runs === 'object' ? state.runs : {};",
|
|
68
|
+
"state.get_calls = state.get_calls && typeof state.get_calls === 'object' ? state.get_calls : {};",
|
|
69
|
+
"const argv = process.argv.slice(2);",
|
|
70
|
+
"const command = String(argv[0] || '').trim();",
|
|
71
|
+
"const options = {};",
|
|
72
|
+
"for (let index = 1; index < argv.length; index += 1) {",
|
|
73
|
+
" const token = String(argv[index] || '');",
|
|
74
|
+
" if (!token.startsWith('--')) continue;",
|
|
75
|
+
" const key = token.slice(2);",
|
|
76
|
+
" const next = argv[index + 1];",
|
|
77
|
+
" if (next && !String(next).startsWith('--')) {",
|
|
78
|
+
" options[key] = String(next);",
|
|
79
|
+
" index += 1;",
|
|
80
|
+
" } else {",
|
|
81
|
+
" options[key] = true;",
|
|
82
|
+
" }",
|
|
83
|
+
"}",
|
|
84
|
+
"function saveAndPrint(payload) {",
|
|
85
|
+
" fs.writeFileSync(statePath, JSON.stringify(state, null, 2));",
|
|
86
|
+
" process.stdout.write(`${JSON.stringify(payload)}\\n`);",
|
|
87
|
+
"}",
|
|
88
|
+
"if (command === 'prepare-run') {",
|
|
89
|
+
" state.last_prepare_args = options;",
|
|
90
|
+
" saveAndPrint({",
|
|
91
|
+
" status: 'NEED_INPUT',",
|
|
92
|
+
" stage: 'chat_run_setup',",
|
|
93
|
+
" page_url: 'https://www.zhipin.com/web/chat/index',",
|
|
94
|
+
" required_fields: ['job', 'start_from', 'target_count', 'criteria'],",
|
|
95
|
+
" job_options: [",
|
|
96
|
+
" { index: 1, label: '算法工程师', value: '算法工程师', active: true },",
|
|
97
|
+
" { index: 2, label: '大模型算法', value: '大模型算法', active: false }",
|
|
98
|
+
" ],",
|
|
99
|
+
" pending_questions: [",
|
|
100
|
+
" { field: 'job', question: '请选择岗位(必须从岗位列表中选择)', required: true },",
|
|
101
|
+
" { field: 'start_from', question: '请选择起始范围', required: true },",
|
|
102
|
+
" { field: 'target_count', question: '请输入目标数量(正整数)', required: true },",
|
|
103
|
+
" { field: 'criteria', question: '请输入筛选标准(自然语言)', required: true }",
|
|
104
|
+
" ],",
|
|
105
|
+
" message: 'prepared'",
|
|
106
|
+
" });",
|
|
107
|
+
" process.exit(0);",
|
|
108
|
+
"}",
|
|
109
|
+
"if (command === 'start-run') {",
|
|
110
|
+
" state.counter += 1;",
|
|
111
|
+
" const runId = `chat-${state.counter}`;",
|
|
112
|
+
" state.last_start_args = options;",
|
|
113
|
+
" state.runs[runId] = { state: 'queued' };",
|
|
114
|
+
" state.get_calls[runId] = 0;",
|
|
115
|
+
" saveAndPrint({ status: 'ACCEPTED', run_id: runId, message: 'chat started' });",
|
|
116
|
+
" process.exit(0);",
|
|
117
|
+
"}",
|
|
118
|
+
"const runId = String(options['run-id'] || '');",
|
|
119
|
+
"const current = state.runs[runId] || { state: 'queued' };",
|
|
120
|
+
"if (command === 'get-run') {",
|
|
121
|
+
" state.get_calls[runId] = (state.get_calls[runId] || 0) + 1;",
|
|
122
|
+
" if (!['paused', 'canceled'].includes(current.state)) {",
|
|
123
|
+
" current.state = state.get_calls[runId] >= 2 ? 'completed' : 'running';",
|
|
124
|
+
" }",
|
|
125
|
+
" state.runs[runId] = current;",
|
|
126
|
+
" saveAndPrint({",
|
|
127
|
+
" status: 'RUN_STATUS',",
|
|
128
|
+
" run: {",
|
|
129
|
+
" runId,",
|
|
130
|
+
" state: current.state,",
|
|
131
|
+
" lastMessage: `state=${current.state}`,",
|
|
132
|
+
" progress: { inspected: state.get_calls[runId], passed: current.state === 'completed' ? 1 : 0, requested: current.state === 'completed' ? 1 : 0, skipped: 0, errors: 0 },",
|
|
133
|
+
" result: current.state === 'completed' ? { requested_count: 1 } : null",
|
|
134
|
+
" }",
|
|
135
|
+
" });",
|
|
136
|
+
" process.exit(0);",
|
|
137
|
+
"}",
|
|
138
|
+
"if (command === 'pause-run') {",
|
|
139
|
+
" current.state = 'paused';",
|
|
140
|
+
" state.runs[runId] = current;",
|
|
141
|
+
" saveAndPrint({ status: 'PAUSE_REQUESTED', run: { runId, state: 'paused' } });",
|
|
142
|
+
" process.exit(0);",
|
|
143
|
+
"}",
|
|
144
|
+
"if (command === 'resume-run') {",
|
|
145
|
+
" current.state = 'running';",
|
|
146
|
+
" state.runs[runId] = current;",
|
|
147
|
+
" saveAndPrint({ status: 'RESUME_REQUESTED', run: { runId, state: 'running' } });",
|
|
148
|
+
" process.exit(0);",
|
|
149
|
+
"}",
|
|
150
|
+
"if (command === 'cancel-run') {",
|
|
151
|
+
" current.state = 'canceled';",
|
|
152
|
+
" state.runs[runId] = current;",
|
|
153
|
+
" saveAndPrint({ status: 'CANCEL_REQUESTED', run: { runId, state: 'canceled' } });",
|
|
154
|
+
" process.exit(0);",
|
|
155
|
+
"}",
|
|
156
|
+
"saveAndPrint({ status: 'FAILED', error: { code: 'UNKNOWN_COMMAND', message: command || 'missing command' } });",
|
|
157
|
+
"process.exit(1);"
|
|
158
|
+
].join("\n"), "utf8");
|
|
159
|
+
|
|
160
|
+
return workspaceRoot;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readStubState(workspaceRoot) {
|
|
164
|
+
const statePath = path.join(workspaceRoot, ".boss-chat", "stub-state.json");
|
|
165
|
+
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function withBossChatWorkspace(testFn) {
|
|
169
|
+
const workspaceRoot = createBossChatTestWorkspace();
|
|
170
|
+
const previousScreenConfig = process.env.BOSS_RECOMMEND_SCREEN_CONFIG;
|
|
171
|
+
process.env.BOSS_RECOMMEND_SCREEN_CONFIG = path.join(workspaceRoot, "config", "screening-config.json");
|
|
172
|
+
try {
|
|
173
|
+
await testFn(workspaceRoot);
|
|
174
|
+
} finally {
|
|
175
|
+
if (previousScreenConfig === undefined) {
|
|
176
|
+
delete process.env.BOSS_RECOMMEND_SCREEN_CONFIG;
|
|
177
|
+
} else {
|
|
178
|
+
process.env.BOSS_RECOMMEND_SCREEN_CONFIG = previousScreenConfig;
|
|
179
|
+
}
|
|
180
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function captureConsoleLogs(fn) {
|
|
185
|
+
const messages = [];
|
|
186
|
+
const originalLog = console.log;
|
|
187
|
+
console.log = (...args) => {
|
|
188
|
+
messages.push(args.join(" "));
|
|
189
|
+
};
|
|
190
|
+
try {
|
|
191
|
+
await fn();
|
|
192
|
+
} finally {
|
|
193
|
+
console.log = originalLog;
|
|
194
|
+
}
|
|
195
|
+
return messages;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
199
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
200
|
+
const health = getBossChatHealthCheck(workspaceRoot);
|
|
201
|
+
assert.equal(health.status, "OK");
|
|
202
|
+
assert.equal(health.shared_llm_config, true);
|
|
203
|
+
assert.equal(health.debug_port, 9666);
|
|
204
|
+
|
|
205
|
+
const preflight = await startBossChatRun({
|
|
206
|
+
workspaceRoot,
|
|
207
|
+
input: {}
|
|
208
|
+
});
|
|
209
|
+
assert.equal(preflight.status, "NEED_INPUT");
|
|
210
|
+
assert.deepEqual(preflight.required_fields, ["job", "start_from", "target_count", "criteria"]);
|
|
211
|
+
assert.equal(Array.isArray(preflight.job_options), true);
|
|
212
|
+
assert.equal(preflight.job_options.length, 2);
|
|
213
|
+
|
|
214
|
+
const stateAfterPrepare = readStubState(workspaceRoot);
|
|
215
|
+
assert.equal(stateAfterPrepare.last_prepare_args.profile, "default");
|
|
216
|
+
assert.equal(stateAfterPrepare.last_prepare_args.port, "9666");
|
|
217
|
+
assert.equal(stateAfterPrepare.last_prepare_args.baseurl, "https://api.example.com/v1");
|
|
218
|
+
assert.equal(stateAfterPrepare.last_prepare_args.apikey, "sk-test-key");
|
|
219
|
+
assert.equal(stateAfterPrepare.last_prepare_args.model, "gpt-4.1-mini");
|
|
220
|
+
|
|
221
|
+
const started = await startBossChatRun({
|
|
222
|
+
workspaceRoot,
|
|
223
|
+
input: {
|
|
224
|
+
profile: "default",
|
|
225
|
+
job: "算法工程师",
|
|
226
|
+
start_from: "unread",
|
|
227
|
+
criteria: "有 AI Agent 经验",
|
|
228
|
+
target_count: 2
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
assert.equal(started.status, "ACCEPTED");
|
|
232
|
+
assert.equal(Boolean(started.run_id), true);
|
|
233
|
+
|
|
234
|
+
const stateAfterStart = readStubState(workspaceRoot);
|
|
235
|
+
assert.equal(stateAfterStart.last_start_args.profile, "default");
|
|
236
|
+
assert.equal(stateAfterStart.last_start_args.job, "算法工程师");
|
|
237
|
+
assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
|
|
238
|
+
assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
|
|
239
|
+
assert.equal(stateAfterStart.last_start_args.targetCount, "2");
|
|
240
|
+
assert.equal(stateAfterStart.last_start_args.baseurl, "https://api.example.com/v1");
|
|
241
|
+
assert.equal(stateAfterStart.last_start_args.apikey, "sk-test-key");
|
|
242
|
+
assert.equal(stateAfterStart.last_start_args.model, "gpt-4.1-mini");
|
|
243
|
+
assert.equal(stateAfterStart.last_start_args.port, "9666");
|
|
244
|
+
|
|
245
|
+
const running = await getBossChatRun({
|
|
246
|
+
workspaceRoot,
|
|
247
|
+
input: {
|
|
248
|
+
profile: "default",
|
|
249
|
+
run_id: started.run_id
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
assert.equal(running.run.state, "running");
|
|
253
|
+
|
|
254
|
+
const paused = await pauseBossChatRun({
|
|
255
|
+
workspaceRoot,
|
|
256
|
+
input: {
|
|
257
|
+
profile: "default",
|
|
258
|
+
run_id: started.run_id
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
assert.equal(paused.run.state, "paused");
|
|
262
|
+
|
|
263
|
+
const resumed = await resumeBossChatRun({
|
|
264
|
+
workspaceRoot,
|
|
265
|
+
input: {
|
|
266
|
+
profile: "default",
|
|
267
|
+
run_id: started.run_id
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
assert.equal(resumed.run.state, "running");
|
|
271
|
+
|
|
272
|
+
const canceled = await cancelBossChatRun({
|
|
273
|
+
workspaceRoot,
|
|
274
|
+
input: {
|
|
275
|
+
profile: "default",
|
|
276
|
+
run_id: started.run_id
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
assert.equal(canceled.run.state, "canceled");
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function testBossChatMcpToolsShouldValidateAndRoute() {
|
|
284
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
285
|
+
const needInput = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {}, 11);
|
|
286
|
+
assert.equal(needInput.status, "NEED_INPUT");
|
|
287
|
+
assert.deepEqual(needInput.required_fields, ["job", "start_from", "target_count", "criteria"]);
|
|
288
|
+
assert.equal(Array.isArray(needInput.job_options), true);
|
|
289
|
+
assert.equal(needInput.job_options.length, 2);
|
|
290
|
+
|
|
291
|
+
const invalidStartResponse = await handleRequest(
|
|
292
|
+
makeToolCall(11, TOOL_BOSS_CHAT_START_RUN, {
|
|
293
|
+
start_from: "invalid-value"
|
|
294
|
+
}),
|
|
295
|
+
workspaceRoot
|
|
296
|
+
);
|
|
297
|
+
assert.equal(invalidStartResponse.error.code, -32602);
|
|
298
|
+
|
|
299
|
+
const invalidGetResponse = await handleRequest(
|
|
300
|
+
makeToolCall(12, TOOL_BOSS_CHAT_GET_RUN, {}),
|
|
301
|
+
workspaceRoot
|
|
302
|
+
);
|
|
303
|
+
assert.equal(invalidGetResponse.error.code, -32602);
|
|
304
|
+
|
|
305
|
+
const health = await callTool(workspaceRoot, TOOL_BOSS_CHAT_HEALTH_CHECK, {}, 13);
|
|
306
|
+
assert.equal(health.status, "OK");
|
|
307
|
+
|
|
308
|
+
const started = await callTool(workspaceRoot, TOOL_BOSS_CHAT_START_RUN, {
|
|
309
|
+
job: "算法工程师",
|
|
310
|
+
start_from: "unread",
|
|
311
|
+
criteria: "有 AI Agent 经验",
|
|
312
|
+
target_count: 2
|
|
313
|
+
}, 14);
|
|
314
|
+
assert.equal(started.status, "ACCEPTED");
|
|
315
|
+
|
|
316
|
+
const running = await callTool(workspaceRoot, TOOL_BOSS_CHAT_GET_RUN, {
|
|
317
|
+
run_id: started.run_id,
|
|
318
|
+
profile: "default"
|
|
319
|
+
}, 15);
|
|
320
|
+
assert.equal(running.run.state, "running");
|
|
321
|
+
|
|
322
|
+
const paused = await callTool(workspaceRoot, TOOL_BOSS_CHAT_PAUSE_RUN, {
|
|
323
|
+
run_id: started.run_id,
|
|
324
|
+
profile: "default"
|
|
325
|
+
}, 16);
|
|
326
|
+
assert.equal(paused.run.state, "paused");
|
|
327
|
+
|
|
328
|
+
const resumed = await callTool(workspaceRoot, TOOL_BOSS_CHAT_RESUME_RUN, {
|
|
329
|
+
run_id: started.run_id,
|
|
330
|
+
profile: "default"
|
|
331
|
+
}, 17);
|
|
332
|
+
assert.equal(resumed.run.state, "running");
|
|
333
|
+
|
|
334
|
+
const canceled = await callTool(workspaceRoot, TOOL_BOSS_CHAT_CANCEL_RUN, {
|
|
335
|
+
run_id: started.run_id,
|
|
336
|
+
profile: "default"
|
|
337
|
+
}, 18);
|
|
338
|
+
assert.equal(canceled.run.state, "canceled");
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
async function testBossChatCliShouldSupportRunAndFollowUpParsing() {
|
|
343
|
+
const followUpJson = cliTestables.getRunFollowUp({
|
|
344
|
+
"follow-up-json": JSON.stringify({
|
|
345
|
+
chat: {
|
|
346
|
+
criteria: "有 AI Agent 经验",
|
|
347
|
+
start_from: "unread",
|
|
348
|
+
target_count: 2
|
|
349
|
+
}
|
|
350
|
+
})
|
|
351
|
+
});
|
|
352
|
+
assert.equal(followUpJson.chat.criteria, "有 AI Agent 经验");
|
|
353
|
+
assert.equal(followUpJson.chat.target_count, 2);
|
|
354
|
+
|
|
355
|
+
const tempFile = path.join(os.tmpdir(), `boss-recommend-follow-up-${Date.now()}.json`);
|
|
356
|
+
fs.writeFileSync(tempFile, JSON.stringify({
|
|
357
|
+
chat: {
|
|
358
|
+
criteria: "熟悉 MCP",
|
|
359
|
+
start_from: "all",
|
|
360
|
+
target_count: 3
|
|
361
|
+
}
|
|
362
|
+
}, null, 2));
|
|
363
|
+
try {
|
|
364
|
+
const followUpFile = cliTestables.getRunFollowUp({
|
|
365
|
+
"follow-up-file": tempFile
|
|
366
|
+
});
|
|
367
|
+
assert.equal(followUpFile.chat.criteria, "熟悉 MCP");
|
|
368
|
+
assert.equal(followUpFile.chat.start_from, "all");
|
|
369
|
+
} finally {
|
|
370
|
+
fs.rmSync(tempFile, { force: true });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
374
|
+
const logs = await captureConsoleLogs(async () => {
|
|
375
|
+
await cliTestables.runBossChatCliCommand("run", {
|
|
376
|
+
"workspace-root": workspaceRoot,
|
|
377
|
+
job: "算法工程师",
|
|
378
|
+
"start-from": "unread",
|
|
379
|
+
criteria: "有 AI Agent 经验",
|
|
380
|
+
targetCount: "2"
|
|
381
|
+
});
|
|
382
|
+
});
|
|
383
|
+
assert.equal(logs.length > 0, true);
|
|
384
|
+
const payload = JSON.parse(logs[0]);
|
|
385
|
+
assert.equal(payload.status, "ACCEPTED");
|
|
386
|
+
assert.equal(typeof payload.run_id, "string");
|
|
387
|
+
const state = readStubState(workspaceRoot);
|
|
388
|
+
assert.equal(state.get_calls[payload.run_id] || 0, 0);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async function main() {
|
|
393
|
+
await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
|
|
394
|
+
await testBossChatMcpToolsShouldValidateAndRoute();
|
|
395
|
+
await testBossChatCliShouldSupportRunAndFollowUpParsing();
|
|
396
|
+
console.log("boss-chat tests passed");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
await main();
|