@reconcrap/boss-recommend-mcp 1.2.5 → 1.2.7
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 +7 -0
- package/package.json +2 -1
- package/src/adapters.js +402 -95
- package/src/index.js +97 -0
- package/src/pipeline.js +34 -2
- package/src/recommend-healing-config.js +131 -0
- package/src/recommend-healing-rules.json +261 -0
- package/src/self-heal.js +2237 -0
- package/src/test-pipeline.js +67 -0
- package/src/test-self-heal.js +224 -0
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +259 -153
- package/vendor/boss-recommend-search-cli/src/cli.js +98 -50
package/src/test-pipeline.js
CHANGED
|
@@ -1505,6 +1505,72 @@ async function testSearchNoIframeWithLoginShouldReturnLoginRequired() {
|
|
|
1505
1505
|
assert.equal(result.guidance.agent_prompt.includes("https://www.zhipin.com/web/user/?ka=bticket"), true);
|
|
1506
1506
|
}
|
|
1507
1507
|
|
|
1508
|
+
async function testSearchNoIframeShouldRetryOnceWhenPageRecheckReady() {
|
|
1509
|
+
let searchCallCount = 0;
|
|
1510
|
+
let recheckCount = 0;
|
|
1511
|
+
const result = await runRecommendPipeline(
|
|
1512
|
+
{
|
|
1513
|
+
workspaceRoot: process.cwd(),
|
|
1514
|
+
instruction: "test",
|
|
1515
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
1516
|
+
overrides: {}
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
parseRecommendInstruction: () => createParsed(),
|
|
1520
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9222 }),
|
|
1521
|
+
ensureBossRecommendPageReady: async () => {
|
|
1522
|
+
recheckCount += 1;
|
|
1523
|
+
return {
|
|
1524
|
+
ok: true,
|
|
1525
|
+
debug_port: 9222,
|
|
1526
|
+
state: "RECOMMEND_READY",
|
|
1527
|
+
page_state: {
|
|
1528
|
+
state: "RECOMMEND_READY",
|
|
1529
|
+
expected_url: "https://www.zhipin.com/web/chat/recommend",
|
|
1530
|
+
current_url: "https://www.zhipin.com/web/chat/recommend"
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
},
|
|
1534
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
1535
|
+
runRecommendSearchCli: async () => {
|
|
1536
|
+
searchCallCount += 1;
|
|
1537
|
+
if (searchCallCount === 1) {
|
|
1538
|
+
return {
|
|
1539
|
+
ok: false,
|
|
1540
|
+
stdout: "",
|
|
1541
|
+
stderr: "NO_RECOMMEND_IFRAME",
|
|
1542
|
+
structured: null,
|
|
1543
|
+
error: {
|
|
1544
|
+
code: "NO_RECOMMEND_IFRAME",
|
|
1545
|
+
message: "NO_RECOMMEND_IFRAME"
|
|
1546
|
+
}
|
|
1547
|
+
};
|
|
1548
|
+
}
|
|
1549
|
+
return {
|
|
1550
|
+
ok: true,
|
|
1551
|
+
summary: {
|
|
1552
|
+
candidate_count: 5,
|
|
1553
|
+
applied_filters: {}
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
},
|
|
1557
|
+
runRecommendScreenCli: async () => ({
|
|
1558
|
+
ok: true,
|
|
1559
|
+
summary: {
|
|
1560
|
+
processed_count: 2,
|
|
1561
|
+
passed_count: 1,
|
|
1562
|
+
skipped_count: 1,
|
|
1563
|
+
output_csv: "C:/temp/retry.csv"
|
|
1564
|
+
}
|
|
1565
|
+
})
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1568
|
+
|
|
1569
|
+
assert.equal(result.status, "COMPLETED");
|
|
1570
|
+
assert.equal(searchCallCount, 2);
|
|
1571
|
+
assert.equal(recheckCount >= 2, true);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1508
1574
|
async function testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin() {
|
|
1509
1575
|
const result = await runRecommendPipeline(
|
|
1510
1576
|
{
|
|
@@ -1940,6 +2006,7 @@ async function main() {
|
|
|
1940
2006
|
await testCompletedPipeline();
|
|
1941
2007
|
await testSearchFailure();
|
|
1942
2008
|
await testSearchNoIframeWithLoginShouldReturnLoginRequired();
|
|
2009
|
+
await testSearchNoIframeShouldRetryOnceWhenPageRecheckReady();
|
|
1943
2010
|
await testJobTriggerNotFoundShouldMapToLoginRequiredWhenRecheckShowsLogin();
|
|
1944
2011
|
await testLoginRequiredShouldReturnGuidance();
|
|
1945
2012
|
await testDebugPortUnreachableShouldReturnConnectionCode();
|
|
@@ -0,0 +1,224 @@
|
|
|
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
|
+
import { __testables as indexTestables } from "./index.js";
|
|
6
|
+
import { runRecommendSelfHeal, __testables as selfHealTestables } from "./self-heal.js";
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
handleRequest,
|
|
10
|
+
setRunSelfHealImplForTests
|
|
11
|
+
} = indexTestables;
|
|
12
|
+
|
|
13
|
+
const TOOL_RUN_RECOMMEND_SELF_HEAL = "run_recommend_self_heal";
|
|
14
|
+
|
|
15
|
+
function makeToolCall(id, name, args = {}) {
|
|
16
|
+
return {
|
|
17
|
+
jsonrpc: "2.0",
|
|
18
|
+
id,
|
|
19
|
+
method: "tools/call",
|
|
20
|
+
params: {
|
|
21
|
+
name,
|
|
22
|
+
arguments: args
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function readToolPayload(response) {
|
|
28
|
+
return response?.result?.structuredContent;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function callTool(name, args, id = 1) {
|
|
32
|
+
const response = await handleRequest(makeToolCall(id, name, args), process.cwd());
|
|
33
|
+
return {
|
|
34
|
+
payload: await readToolPayload(response),
|
|
35
|
+
response
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function testToolsListShouldIncludeSelfHeal() {
|
|
40
|
+
const response = await handleRequest({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }, process.cwd());
|
|
41
|
+
const tools = response?.result?.tools || [];
|
|
42
|
+
assert.equal(tools.some((tool) => tool?.name === TOOL_RUN_RECOMMEND_SELF_HEAL), true);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function testIndexShouldRouteSelfHealTool() {
|
|
46
|
+
setRunSelfHealImplForTests(async () => ({ status: "HEALTHY", message: "ok" }));
|
|
47
|
+
try {
|
|
48
|
+
const { payload } = await callTool(TOOL_RUN_RECOMMEND_SELF_HEAL, {}, 2);
|
|
49
|
+
assert.equal(payload?.status, "HEALTHY");
|
|
50
|
+
} finally {
|
|
51
|
+
setRunSelfHealImplForTests(null);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function testScanShouldCreateRepairSession() {
|
|
56
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
57
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-home-"));
|
|
58
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
59
|
+
try {
|
|
60
|
+
const result = await runRecommendSelfHeal(
|
|
61
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
62
|
+
{
|
|
63
|
+
scanRuntimeSurface: async () => ({
|
|
64
|
+
selector_checks: [
|
|
65
|
+
{
|
|
66
|
+
rule_id: "filter_trigger",
|
|
67
|
+
path: ["frame", "filter_trigger"],
|
|
68
|
+
root: "frame",
|
|
69
|
+
matches: [
|
|
70
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
71
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
],
|
|
75
|
+
network_checks: [],
|
|
76
|
+
side_effect_summary: { opened_candidate_detail: false }
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
);
|
|
80
|
+
assert.equal(result.status, "NEED_CONFIRMATION");
|
|
81
|
+
assert.equal(typeof result.repair_session_id, "string");
|
|
82
|
+
assert.equal(result.proposed_repairs.length, 1);
|
|
83
|
+
const sessionPath = path.join(selfHealTestables.getSelfHealSessionsDir(), `${result.repair_session_id}.json`);
|
|
84
|
+
assert.equal(fs.existsSync(sessionPath), true);
|
|
85
|
+
} finally {
|
|
86
|
+
if (previousHome === undefined) {
|
|
87
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
88
|
+
} else {
|
|
89
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
90
|
+
}
|
|
91
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function testOptionalSelectorMissShouldNotBecomeDrift() {
|
|
96
|
+
const drifts = selfHealTestables.analyzeSelectorChecks([
|
|
97
|
+
{
|
|
98
|
+
rule_id: "featured_cards",
|
|
99
|
+
path: ["frame", "featured_cards"],
|
|
100
|
+
root: "frame",
|
|
101
|
+
required: false,
|
|
102
|
+
report_on_no_match: false,
|
|
103
|
+
skipped: false,
|
|
104
|
+
matches: [
|
|
105
|
+
{ selector: "li.geek-info-card", index: 0, count: 0 }
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
]);
|
|
109
|
+
assert.equal(drifts.length, 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async function testApplyShouldRequireConfirmation() {
|
|
113
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
114
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-apply-home-"));
|
|
115
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
116
|
+
try {
|
|
117
|
+
const scanResult = await runRecommendSelfHeal(
|
|
118
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
119
|
+
{
|
|
120
|
+
scanRuntimeSurface: async () => ({
|
|
121
|
+
selector_checks: [
|
|
122
|
+
{
|
|
123
|
+
rule_id: "filter_trigger",
|
|
124
|
+
path: ["frame", "filter_trigger"],
|
|
125
|
+
root: "frame",
|
|
126
|
+
matches: [
|
|
127
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
128
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
network_checks: [],
|
|
133
|
+
side_effect_summary: null
|
|
134
|
+
})
|
|
135
|
+
}
|
|
136
|
+
);
|
|
137
|
+
const result = await runRecommendSelfHeal({
|
|
138
|
+
workspaceRoot: process.cwd(),
|
|
139
|
+
args: {
|
|
140
|
+
mode: "apply",
|
|
141
|
+
repair_session_id: scanResult.repair_session_id
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
assert.equal(result.status, "FAILED");
|
|
145
|
+
assert.equal(result.error?.code, "SELF_HEAL_CONFIRMATION_REQUIRED");
|
|
146
|
+
} finally {
|
|
147
|
+
if (previousHome === undefined) {
|
|
148
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
149
|
+
} else {
|
|
150
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
151
|
+
}
|
|
152
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function testApplyShouldUpdateRulesFile() {
|
|
157
|
+
const previousHome = process.env.BOSS_RECOMMEND_HOME;
|
|
158
|
+
const previousRulesPath = process.env.BOSS_RECOMMEND_HEALING_RULES_FILE;
|
|
159
|
+
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-rules-home-"));
|
|
160
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-self-heal-rules-"));
|
|
161
|
+
const rulesSourcePath = path.join(process.cwd(), "src", "recommend-healing-rules.json");
|
|
162
|
+
const tempRulesPath = path.join(tempDir, "recommend-healing-rules.json");
|
|
163
|
+
fs.copyFileSync(rulesSourcePath, tempRulesPath);
|
|
164
|
+
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
165
|
+
process.env.BOSS_RECOMMEND_HEALING_RULES_FILE = tempRulesPath;
|
|
166
|
+
try {
|
|
167
|
+
const scanResult = await runRecommendSelfHeal(
|
|
168
|
+
{ workspaceRoot: process.cwd(), args: { mode: "scan" } },
|
|
169
|
+
{
|
|
170
|
+
scanRuntimeSurface: async () => ({
|
|
171
|
+
selector_checks: [
|
|
172
|
+
{
|
|
173
|
+
rule_id: "filter_trigger",
|
|
174
|
+
path: ["frame", "filter_trigger"],
|
|
175
|
+
root: "frame",
|
|
176
|
+
matches: [
|
|
177
|
+
{ selector: ".filter-label-wrap", index: 0, count: 0 },
|
|
178
|
+
{ selector: ".recommend-filter.op-filter", index: 1, count: 1 }
|
|
179
|
+
]
|
|
180
|
+
}
|
|
181
|
+
],
|
|
182
|
+
network_checks: [],
|
|
183
|
+
side_effect_summary: null
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
const result = await runRecommendSelfHeal({
|
|
188
|
+
workspaceRoot: process.cwd(),
|
|
189
|
+
args: {
|
|
190
|
+
mode: "apply",
|
|
191
|
+
repair_session_id: scanResult.repair_session_id,
|
|
192
|
+
confirm_apply: true
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
assert.equal(result.status, "REPAIRED");
|
|
196
|
+
const updatedRules = JSON.parse(fs.readFileSync(tempRulesPath, "utf8"));
|
|
197
|
+
assert.equal(updatedRules.selectors.frame.filter_trigger[0], ".recommend-filter.op-filter");
|
|
198
|
+
} finally {
|
|
199
|
+
if (previousHome === undefined) {
|
|
200
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
201
|
+
} else {
|
|
202
|
+
process.env.BOSS_RECOMMEND_HOME = previousHome;
|
|
203
|
+
}
|
|
204
|
+
if (previousRulesPath === undefined) {
|
|
205
|
+
delete process.env.BOSS_RECOMMEND_HEALING_RULES_FILE;
|
|
206
|
+
} else {
|
|
207
|
+
process.env.BOSS_RECOMMEND_HEALING_RULES_FILE = previousRulesPath;
|
|
208
|
+
}
|
|
209
|
+
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
210
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function main() {
|
|
215
|
+
await testToolsListShouldIncludeSelfHeal();
|
|
216
|
+
await testIndexShouldRouteSelfHealTool();
|
|
217
|
+
await testScanShouldCreateRepairSession();
|
|
218
|
+
await testOptionalSelectorMissShouldNotBecomeDrift();
|
|
219
|
+
await testApplyShouldRequireConfirmation();
|
|
220
|
+
await testApplyShouldUpdateRulesFile();
|
|
221
|
+
console.log("self-heal tests passed");
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await main();
|