@reconcrap/boss-recommend-mcp 1.3.39 → 2.0.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 +53 -33
- package/package.json +61 -9
- package/skills/boss-recommend-pipeline/SKILL.md +4 -0
- package/src/chat-mcp.js +1333 -0
- package/src/chat-runtime-config.js +559 -0
- package/src/cli.js +1095 -196
- package/src/core/browser/index.js +378 -0
- package/src/core/capture/index.js +298 -0
- package/src/core/cv-acquisition/index.js +219 -0
- package/src/core/greet-quota/index.js +54 -0
- package/src/core/infinite-list/index.js +459 -0
- package/src/core/reporting/legacy-csv.js +332 -0
- package/src/core/run/index.js +286 -0
- package/src/core/screening/index.js +1166 -0
- package/src/core/self-heal/index.js +848 -0
- package/src/domains/chat/cards.js +129 -0
- package/src/domains/chat/constants.js +183 -0
- package/src/domains/chat/detail.js +1369 -0
- package/src/domains/chat/index.js +7 -0
- package/src/domains/chat/jobs.js +334 -0
- package/src/domains/chat/page-guard.js +88 -0
- package/src/domains/chat/roots.js +56 -0
- package/src/domains/chat/run-service.js +1101 -0
- package/src/domains/recommend/actions.js +457 -0
- package/src/domains/recommend/cards.js +228 -0
- package/src/domains/recommend/constants.js +141 -0
- package/src/domains/recommend/detail.js +341 -0
- package/src/domains/recommend/filters.js +581 -0
- package/src/domains/recommend/index.js +10 -0
- package/src/domains/recommend/jobs.js +232 -0
- package/src/domains/recommend/refresh.js +204 -0
- package/src/domains/recommend/roots.js +78 -0
- package/src/domains/recommend/run-service.js +903 -0
- package/src/domains/recommend/scopes.js +245 -0
- package/src/domains/recruit/actions.js +277 -0
- package/src/domains/recruit/cards.js +67 -0
- package/src/domains/recruit/constants.js +130 -0
- package/src/domains/recruit/detail.js +414 -0
- package/src/domains/recruit/index.js +9 -0
- package/src/domains/recruit/instruction-parser.js +451 -0
- package/src/domains/recruit/refresh.js +40 -0
- package/src/domains/recruit/roots.js +68 -0
- package/src/domains/recruit/run-service.js +580 -0
- package/src/domains/recruit/search.js +1149 -0
- package/src/index.js +578 -419
- package/src/recommend-mcp.js +1257 -0
- package/src/recruit-mcp.js +1035 -0
- package/src/adapters.js +0 -3079
- package/src/boss-chat.js +0 -1037
- package/src/pipeline.js +0 -2249
- package/src/recommend-healing-config.js +0 -131
- package/src/recommend-healing-rules.json +0 -261
- package/src/self-heal.js +0 -2237
- package/src/test-adapters-runtime.js +0 -628
- package/src/test-boss-chat.js +0 -3196
- package/src/test-index-async.js +0 -498
- package/src/test-parser.js +0 -742
- package/src/test-pipeline.js +0 -2703
- package/src/test-run-state.js +0 -152
- package/src/test-self-heal.js +0 -224
- package/vendor/boss-chat-cli/README.md +0 -134
- package/vendor/boss-chat-cli/package.json +0 -53
- package/vendor/boss-chat-cli/src/app.js +0 -1501
- package/vendor/boss-chat-cli/src/browser/chat-page.js +0 -3562
- package/vendor/boss-chat-cli/src/cli.js +0 -1713
- package/vendor/boss-chat-cli/src/mcp/server.js +0 -149
- package/vendor/boss-chat-cli/src/mcp/tool-runtime.js +0 -193
- package/vendor/boss-chat-cli/src/runtime/async-run-state.js +0 -260
- package/vendor/boss-chat-cli/src/runtime/interaction.js +0 -102
- package/vendor/boss-chat-cli/src/runtime/run-control.js +0 -102
- package/vendor/boss-chat-cli/src/services/chrome-client.js +0 -107
- package/vendor/boss-chat-cli/src/services/llm.js +0 -1292
- package/vendor/boss-chat-cli/src/services/llm.test.js +0 -326
- package/vendor/boss-chat-cli/src/services/profile-store.js +0 -173
- package/vendor/boss-chat-cli/src/services/report-store.js +0 -317
- package/vendor/boss-chat-cli/src/services/resume-capture.js +0 -469
- package/vendor/boss-chat-cli/src/services/resume-network.js +0 -727
- package/vendor/boss-chat-cli/src/services/state-store.js +0 -90
- package/vendor/boss-chat-cli/src/utils/customer-key.js +0 -82
- package/vendor/boss-recommend-screen-cli/boss-recommend-screen-cli.cjs +0 -7072
- package/vendor/boss-recommend-screen-cli/scripts/capture-full-resume-canvas.cjs +0 -817
- package/vendor/boss-recommend-screen-cli/scripts/stitch_resume_chunks.py +0 -141
- package/vendor/boss-recommend-screen-cli/test-recoverable-resume-failures.cjs +0 -2423
- package/vendor/boss-recommend-search-cli/src/cli.js +0 -1698
- package/vendor/boss-recommend-search-cli/src/test-job-selection.js +0 -211
package/src/test-run-state.js
DELETED
|
@@ -1,152 +0,0 @@
|
|
|
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 {
|
|
6
|
-
RUN_MODE_ASYNC,
|
|
7
|
-
RUN_STATE_PAUSED,
|
|
8
|
-
RUN_STAGE_SCREEN,
|
|
9
|
-
RUN_STATE_COMPLETED,
|
|
10
|
-
RUN_STATE_QUEUED,
|
|
11
|
-
RUN_STATE_RUNNING,
|
|
12
|
-
cleanupExpiredRuns,
|
|
13
|
-
createRunId,
|
|
14
|
-
createRunStateSnapshot,
|
|
15
|
-
getRunsDir,
|
|
16
|
-
readRunState,
|
|
17
|
-
touchRunHeartbeat,
|
|
18
|
-
updateRunProgress,
|
|
19
|
-
updateRunState,
|
|
20
|
-
writeRunState
|
|
21
|
-
} from "./run-state.js";
|
|
22
|
-
|
|
23
|
-
function withTempHome(testFn) {
|
|
24
|
-
const previous = process.env.BOSS_RECOMMEND_HOME;
|
|
25
|
-
const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-run-state-"));
|
|
26
|
-
process.env.BOSS_RECOMMEND_HOME = tempHome;
|
|
27
|
-
try {
|
|
28
|
-
testFn(tempHome);
|
|
29
|
-
} finally {
|
|
30
|
-
if (previous === undefined) {
|
|
31
|
-
delete process.env.BOSS_RECOMMEND_HOME;
|
|
32
|
-
} else {
|
|
33
|
-
process.env.BOSS_RECOMMEND_HOME = previous;
|
|
34
|
-
}
|
|
35
|
-
fs.rmSync(tempHome, { recursive: true, force: true });
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function testRunStateLifecycle() {
|
|
40
|
-
withTempHome(() => {
|
|
41
|
-
const runId = createRunId();
|
|
42
|
-
const queued = writeRunState(createRunStateSnapshot({
|
|
43
|
-
runId,
|
|
44
|
-
mode: RUN_MODE_ASYNC,
|
|
45
|
-
state: RUN_STATE_QUEUED,
|
|
46
|
-
stage: "preflight",
|
|
47
|
-
context: {
|
|
48
|
-
workspace_root: "C:/workspace",
|
|
49
|
-
instruction: "筛选有 MCP 经验候选人",
|
|
50
|
-
confirmation: {
|
|
51
|
-
final_confirmed: true
|
|
52
|
-
}
|
|
53
|
-
},
|
|
54
|
-
control: {
|
|
55
|
-
pause_requested: false
|
|
56
|
-
},
|
|
57
|
-
resume: {
|
|
58
|
-
checkpoint_path: `C:/workspace/.state/${runId}.checkpoint.json`,
|
|
59
|
-
pause_control_path: `C:/workspace/.state/${runId}.json`,
|
|
60
|
-
output_csv: "C:/workspace/result.csv"
|
|
61
|
-
}
|
|
62
|
-
}));
|
|
63
|
-
assert.equal(queued.run_id, runId);
|
|
64
|
-
assert.equal(queued.state, RUN_STATE_QUEUED);
|
|
65
|
-
assert.equal(queued.context.workspace_root, "C:/workspace");
|
|
66
|
-
assert.equal(queued.resume.output_csv, "C:/workspace/result.csv");
|
|
67
|
-
assert.equal(queued.control.cancel_requested, false);
|
|
68
|
-
|
|
69
|
-
const running = updateRunState(runId, {
|
|
70
|
-
state: RUN_STATE_RUNNING,
|
|
71
|
-
stage: RUN_STAGE_SCREEN,
|
|
72
|
-
last_message: "screening in progress"
|
|
73
|
-
});
|
|
74
|
-
assert.equal(running.state, RUN_STATE_RUNNING);
|
|
75
|
-
assert.equal(running.stage, RUN_STAGE_SCREEN);
|
|
76
|
-
const heartbeatBeforeProgress = running.heartbeat_at;
|
|
77
|
-
|
|
78
|
-
const progressed = updateRunProgress(runId, {
|
|
79
|
-
processed: 7,
|
|
80
|
-
passed: 2,
|
|
81
|
-
skipped: 5,
|
|
82
|
-
greet_count: 1
|
|
83
|
-
});
|
|
84
|
-
assert.equal(progressed.progress.processed, 7);
|
|
85
|
-
assert.equal(progressed.progress.passed, 2);
|
|
86
|
-
assert.equal(progressed.progress.skipped, 5);
|
|
87
|
-
assert.equal(progressed.progress.greet_count, 1);
|
|
88
|
-
assert.equal(progressed.heartbeat_at, heartbeatBeforeProgress);
|
|
89
|
-
|
|
90
|
-
const paused = updateRunState(runId, {
|
|
91
|
-
state: RUN_STATE_PAUSED,
|
|
92
|
-
control: {
|
|
93
|
-
pause_requested: true,
|
|
94
|
-
pause_requested_at: "2026-01-01T00:00:00.000Z",
|
|
95
|
-
pause_requested_by: "pause_recommend_pipeline_run",
|
|
96
|
-
cancel_requested: true
|
|
97
|
-
},
|
|
98
|
-
resume: {
|
|
99
|
-
output_csv: "C:/workspace/result-partial.csv",
|
|
100
|
-
last_paused_at: "2026-01-01T00:00:01.000Z"
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
assert.equal(paused.state, RUN_STATE_PAUSED);
|
|
104
|
-
assert.equal(paused.control.pause_requested, true);
|
|
105
|
-
assert.equal(paused.control.pause_requested_by, "pause_recommend_pipeline_run");
|
|
106
|
-
assert.equal(paused.control.cancel_requested, true);
|
|
107
|
-
assert.equal(paused.resume.output_csv, "C:/workspace/result-partial.csv");
|
|
108
|
-
|
|
109
|
-
const heartbeated = touchRunHeartbeat(runId, "still running");
|
|
110
|
-
assert.equal(heartbeated.last_message, "still running");
|
|
111
|
-
assert.equal(Date.parse(heartbeated.heartbeat_at) >= Date.parse(heartbeatBeforeProgress), true);
|
|
112
|
-
|
|
113
|
-
const completed = updateRunState(runId, {
|
|
114
|
-
state: RUN_STATE_COMPLETED,
|
|
115
|
-
stage: "finalize",
|
|
116
|
-
result: {
|
|
117
|
-
status: "COMPLETED",
|
|
118
|
-
result: {
|
|
119
|
-
processed_count: 7
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
assert.equal(completed.state, RUN_STATE_COMPLETED);
|
|
124
|
-
assert.equal(completed.result.status, "COMPLETED");
|
|
125
|
-
|
|
126
|
-
const reloaded = readRunState(runId);
|
|
127
|
-
assert.equal(reloaded.state, RUN_STATE_COMPLETED);
|
|
128
|
-
assert.equal(reloaded.progress.processed, 7);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function testRunStateCleanup() {
|
|
133
|
-
withTempHome(() => {
|
|
134
|
-
const runId = createRunId();
|
|
135
|
-
writeRunState(createRunStateSnapshot({ runId, mode: RUN_MODE_ASYNC }));
|
|
136
|
-
const runFile = path.join(getRunsDir(), `${runId}.json`);
|
|
137
|
-
const oldSeconds = Math.floor((Date.now() - 3 * 24 * 60 * 60 * 1000) / 1000);
|
|
138
|
-
fs.utimesSync(runFile, oldSeconds, oldSeconds);
|
|
139
|
-
|
|
140
|
-
const cleaned = cleanupExpiredRuns(1000);
|
|
141
|
-
assert.equal(cleaned.removed.some((item) => item.endsWith(`${runId}.json`)), true);
|
|
142
|
-
assert.equal(fs.existsSync(runFile), false);
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function main() {
|
|
147
|
-
testRunStateLifecycle();
|
|
148
|
-
testRunStateCleanup();
|
|
149
|
-
console.log("run-state tests passed");
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
main();
|
package/src/test-self-heal.js
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
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();
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
# boss-chat
|
|
2
|
-
|
|
3
|
-
基于 Chrome DevTools Protocol 和 OpenAI 兼容 LLM 的 Boss 直聘聊天页教育筛选与自动沟通 CLI。
|
|
4
|
-
|
|
5
|
-
## 功能
|
|
6
|
-
|
|
7
|
-
- 连接已登录、已打开聊天页的 Chrome
|
|
8
|
-
- 遍历聊天列表客户卡片,读取教育信息
|
|
9
|
-
- 用自定义 `baseUrl + apiKey + model` 调用大模型判断是否符合教育要求
|
|
10
|
-
- 命中时自动生成简短话术并写入聊天框,随后发送
|
|
11
|
-
- 默认记录已处理客户,避免重复触达
|
|
12
|
-
- 支持选择从`未读`或`全部`列表开始处理
|
|
13
|
-
- 支持 `--dry-run` 先验证页面稳定性和命中效果
|
|
14
|
-
|
|
15
|
-
## 依赖
|
|
16
|
-
|
|
17
|
-
- Node.js(建议 18+)
|
|
18
|
-
- Google Chrome(需开启远程调试端口)
|
|
19
|
-
- npm 包依赖:
|
|
20
|
-
- `chrome-remote-interface@^0.33.3`
|
|
21
|
-
|
|
22
|
-
## 使用前准备
|
|
23
|
-
|
|
24
|
-
1. 用远程调试模式启动 Chrome:
|
|
25
|
-
|
|
26
|
-
```powershell
|
|
27
|
-
chrome.exe --remote-debugging-port=9222
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
2. 登录 Boss 直聘并打开聊天页:
|
|
31
|
-
|
|
32
|
-
```text
|
|
33
|
-
https://www.zhipin.com/web/chat/index
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
3. 全局安装:
|
|
37
|
-
|
|
38
|
-
```powershell
|
|
39
|
-
npm install -g @reconcrap/boss-chat-cli
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
也可以直接用 `npx`(适合 agent 平台托管 MCP 时):
|
|
43
|
-
|
|
44
|
-
```powershell
|
|
45
|
-
npx -y -p @reconcrap/boss-chat-cli@latest boss-chat-mcp
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## 运行
|
|
49
|
-
|
|
50
|
-
首次运行会交互式询问教育要求、话术样例、LLM 参数等配置;每次运行也可选择从`未读`或`全部`开始:
|
|
51
|
-
|
|
52
|
-
```powershell
|
|
53
|
-
boss-chat run
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
建议先用 dry-run:
|
|
57
|
-
|
|
58
|
-
```powershell
|
|
59
|
-
boss-chat run --dry-run --targetCount 3
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## MCP Agent 集成(openclaw / codex / trae-cn)
|
|
63
|
-
|
|
64
|
-
包内已内置 MCP stdio server,可直接被三类平台调用:
|
|
65
|
-
|
|
66
|
-
- `openclaw-boss-chat-mcp`
|
|
67
|
-
- `codex-boss-chat-mcp`
|
|
68
|
-
- `trae-cn-boss-chat-mcp`
|
|
69
|
-
- 通用入口:`boss-chat-mcp`
|
|
70
|
-
|
|
71
|
-
### 工具列表
|
|
72
|
-
|
|
73
|
-
- `health_check`: 检查服务是否可用
|
|
74
|
-
- `start_run`: 启动异步任务,返回 `run_id`
|
|
75
|
-
- `get_run`: 查询任务状态
|
|
76
|
-
- `pause_run`: 暂停任务
|
|
77
|
-
- `resume_run`: 继续任务
|
|
78
|
-
- `cancel_run`: 取消任务
|
|
79
|
-
|
|
80
|
-
### 平台配置示例
|
|
81
|
-
|
|
82
|
-
项目里提供了三份可直接复制的模板:
|
|
83
|
-
|
|
84
|
-
- `configs/mcp/openclaw.json`
|
|
85
|
-
- `configs/mcp/codex.json`
|
|
86
|
-
- `configs/mcp/trae-cn.json`
|
|
87
|
-
|
|
88
|
-
三份配置都通过 `npx` 拉取最新包并启动对应 MCP 入口,例如:
|
|
89
|
-
|
|
90
|
-
```json
|
|
91
|
-
{
|
|
92
|
-
"mcpServers": {
|
|
93
|
-
"boss-chat": {
|
|
94
|
-
"command": "npx",
|
|
95
|
-
"args": ["-y", "-p", "@reconcrap/boss-chat-cli@latest", "boss-chat-mcp"]
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
> 不同平台的 MCP 配置文件路径可能不同,但 `command + args` 可直接复用。
|
|
102
|
-
|
|
103
|
-
## 运行中控制
|
|
104
|
-
|
|
105
|
-
程序运行时可以直接用键盘控制:
|
|
106
|
-
|
|
107
|
-
- `p`: 暂停;再次按 `p` 或按 `r` 继续
|
|
108
|
-
- `r`: 继续运行
|
|
109
|
-
- `q`: 请求停止,当前步骤结束后安全退出
|
|
110
|
-
- `Ctrl+C`: 请求停止,当前步骤结束后安全退出
|
|
111
|
-
|
|
112
|
-
停止后仍会写入本次运行报告,已记录的客户状态也会保留。
|
|
113
|
-
|
|
114
|
-
## 常用参数
|
|
115
|
-
|
|
116
|
-
- `--profile <name>`: 使用指定 profile
|
|
117
|
-
- `--dry-run`: 只检查和生成文案,不实际发送
|
|
118
|
-
- `--no-state`: 不记录已处理客户
|
|
119
|
-
- `--targetCount <n>`: 覆盖本次检查人数
|
|
120
|
-
- `--educationRequirement <text>`: 覆盖教育要求
|
|
121
|
-
- `--messageSample <text>`: 覆盖话术样例
|
|
122
|
-
- `--start-from <unread|all>`: 本次从未读或全部列表开始
|
|
123
|
-
- `--baseurl <url>`: 覆盖 LLM base URL
|
|
124
|
-
- `--apikey <key>`: 覆盖 LLM API key
|
|
125
|
-
- `--model <name>`: 覆盖 LLM 模型
|
|
126
|
-
- `--port <n>`: 覆盖 Chrome 远程调试端口
|
|
127
|
-
|
|
128
|
-
## 数据目录
|
|
129
|
-
|
|
130
|
-
运行产生的数据默认保存在项目下的 `.boss-chat/`:
|
|
131
|
-
|
|
132
|
-
- `profiles/`: 保存 profile 配置
|
|
133
|
-
- `state/`: 保存已处理客户状态
|
|
134
|
-
- `reports/`: 保存每次运行的 JSON 报告
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@reconcrap/boss-chat-cli",
|
|
3
|
-
"version": "1.1.0",
|
|
4
|
-
"description": "Boss chat education screening and outreach CLI with MCP agent bridge",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"boss-chat": "src/cli.js",
|
|
8
|
-
"boss-chat-mcp": "src/mcp/server.js",
|
|
9
|
-
"openclaw-boss-chat-mcp": "src/mcp/server.js",
|
|
10
|
-
"codex-boss-chat-mcp": "src/mcp/server.js",
|
|
11
|
-
"trae-cn-boss-chat-mcp": "src/mcp/server.js"
|
|
12
|
-
},
|
|
13
|
-
"main": "./src/cli.js",
|
|
14
|
-
"files": [
|
|
15
|
-
"src/",
|
|
16
|
-
"configs/",
|
|
17
|
-
"README.md"
|
|
18
|
-
],
|
|
19
|
-
"scripts": {
|
|
20
|
-
"start": "node ./src/cli.js run",
|
|
21
|
-
"start:mcp": "node ./src/mcp/server.js",
|
|
22
|
-
"test": "node --test"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"boss",
|
|
26
|
-
"zhipin",
|
|
27
|
-
"chrome-devtools",
|
|
28
|
-
"cli",
|
|
29
|
-
"automation",
|
|
30
|
-
"mcp",
|
|
31
|
-
"openclaw",
|
|
32
|
-
"codex",
|
|
33
|
-
"trae-cn"
|
|
34
|
-
],
|
|
35
|
-
"author": "",
|
|
36
|
-
"license": "MIT",
|
|
37
|
-
"engines": {
|
|
38
|
-
"node": ">=18.0.0"
|
|
39
|
-
},
|
|
40
|
-
"publishConfig": {
|
|
41
|
-
"access": "public"
|
|
42
|
-
},
|
|
43
|
-
"repository": {
|
|
44
|
-
"type": "git",
|
|
45
|
-
"url": ""
|
|
46
|
-
},
|
|
47
|
-
"dependencies": {
|
|
48
|
-
"@modelcontextprotocol/sdk": "^1.18.1",
|
|
49
|
-
"chrome-remote-interface": "^0.33.3",
|
|
50
|
-
"sharp": "^0.34.5",
|
|
51
|
-
"zod": "^4.1.12"
|
|
52
|
-
}
|
|
53
|
-
}
|