@reconcrap/boss-recommend-mcp 1.1.2 → 1.1.4

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.
@@ -1,163 +1,163 @@
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 { runRecommendScreenCli, __testables as adapterTestables } from "./adapters.js";
6
-
7
- const {
8
- runProcess,
9
- parseJsonOutput,
10
- parseScreenProgressLine,
11
- resolveRecommendScreenTimeoutMs,
12
- buildRecommendScreenProcessError
13
- } = adapterTestables;
14
-
15
- async function testRunProcessHeartbeatAndOutput() {
16
- const heartbeats = [];
17
- const lines = [];
18
- const result = await runProcess({
19
- command: "node",
20
- args: [
21
- "-e",
22
- "let i=0; const t=setInterval(()=>{console.error(`tick ${++i}`); if(i===3){clearInterval(t); console.log('{\"status\":\"COMPLETED\"}');}}, 120);"
23
- ],
24
- timeoutMs: 5000,
25
- heartbeatIntervalMs: 40,
26
- onHeartbeat: (event) => {
27
- heartbeats.push(event?.source || "unknown");
28
- },
29
- onLine: (event) => {
30
- lines.push(event?.line || "");
31
- }
32
- });
33
-
34
- assert.equal(result.code, 0);
35
- assert.equal(result.error_code, undefined);
36
- assert.equal(heartbeats.length >= 3, true);
37
- assert.equal(lines.some((line) => line.includes("tick 1")), true);
38
- assert.equal(lines.some((line) => line.includes("\"status\":\"COMPLETED\"")), true);
39
- }
40
-
41
- async function testRunProcessAbortSignal() {
42
- const controller = new AbortController();
43
- setTimeout(() => controller.abort(), 120);
44
-
45
- const result = await runProcess({
46
- command: "node",
47
- args: ["-e", "setTimeout(() => console.log('done'), 5000);"],
48
- timeoutMs: 6000,
49
- signal: controller.signal
50
- });
51
-
52
- assert.equal(result.code, -1);
53
- assert.equal(result.error_code, "ABORTED");
54
- assert.equal(String(result.stderr || "").includes("aborted"), true);
55
- }
56
-
57
- function testParsePausedStructuredOutput() {
58
- const parsed = parseJsonOutput(`
59
- [log] doing work
60
- {"status":"PAUSED","result":{"processed_count":3,"output_csv":"C:/tmp/test.csv"}}
61
- `);
62
- assert.equal(parsed?.status, "PAUSED");
63
- assert.equal(parsed?.result?.processed_count, 3);
64
- }
65
-
66
- function testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped() {
67
- let progress = { processed: 0, passed: 0, skipped: 0, greet_count: 0 };
68
- let tracker = {};
69
- const feed = (line) => {
70
- const parsed = parseScreenProgressLine(line, progress, tracker);
71
- if (!parsed) return;
72
- progress = parsed.progress;
73
- tracker = parsed.tracker;
74
- };
75
-
76
- feed("处理第 1 位候选人: 甲");
77
- feed("筛选结果: 通过");
78
- feed("[关闭详情] 成功: no popup or detail signal visible");
79
- feed("处理第 2 位候选人: 乙");
80
- feed("筛选结果: 通过");
81
- feed("候选人处理失败: FAVORITE_BUTTON_FAILED");
82
- feed("[关闭详情] 成功: no popup or detail signal visible");
83
-
84
- assert.equal(progress.processed, 2);
85
- assert.equal(progress.passed, 1);
86
- assert.equal(progress.skipped, 1);
87
- }
88
-
89
- function testResolveScreenTimeoutDefaultsTo24Hours() {
90
- const previous = process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
91
- delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
92
- try {
93
- assert.equal(resolveRecommendScreenTimeoutMs(null), 24 * 60 * 60 * 1000);
94
- assert.equal(resolveRecommendScreenTimeoutMs({ timeoutMs: 1234 }), 1234);
95
- } finally {
96
- if (previous === undefined) {
97
- delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
98
- } else {
99
- process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS = previous;
100
- }
101
- }
102
- }
103
-
104
- function testBuildRecommendScreenProcessErrorMapsTimeout() {
105
- const error = buildRecommendScreenProcessError({ code: -1, error_code: "TIMEOUT" }, 86400000);
106
- assert.equal(error?.code, "TIMEOUT");
107
- assert.equal(String(error?.message || "").includes("86400000"), true);
108
- }
109
-
110
- async function testResumeRequiresCheckpointFile() {
111
- const previousHome = process.env.BOSS_RECOMMEND_HOME;
112
- const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-resume-"));
113
- process.env.BOSS_RECOMMEND_HOME = tempHome;
114
- try {
115
- const configPath = path.join(tempHome, "screening-config.json");
116
- fs.writeFileSync(configPath, JSON.stringify({
117
- baseUrl: "https://api.openai.com/v1",
118
- apiKey: "sk-test-valid",
119
- model: "gpt-4.1-mini"
120
- }, null, 2));
121
-
122
- const missingCheckpoint = path.join(tempHome, "missing-checkpoint.json");
123
- const result = await runRecommendScreenCli({
124
- workspaceRoot: process.cwd(),
125
- screenParams: {
126
- criteria: "有MCP经验",
127
- target_count: 10,
128
- post_action: "favorite",
129
- max_greet_count: null
130
- },
131
- resume: {
132
- resume: true,
133
- require_checkpoint: true,
134
- checkpoint_path: missingCheckpoint,
135
- pause_control_path: path.join(tempHome, "run-state.json"),
136
- output_csv: path.join(tempHome, "resume.csv")
137
- }
138
- });
139
-
140
- assert.equal(result.ok, false);
141
- assert.equal(result.error?.code, "RESUME_CHECKPOINT_MISSING");
142
- } finally {
143
- if (previousHome === undefined) {
144
- delete process.env.BOSS_RECOMMEND_HOME;
145
- } else {
146
- process.env.BOSS_RECOMMEND_HOME = previousHome;
147
- }
148
- fs.rmSync(tempHome, { recursive: true, force: true });
149
- }
150
- }
151
-
152
- async function main() {
153
- await testRunProcessHeartbeatAndOutput();
154
- await testRunProcessAbortSignal();
155
- testParsePausedStructuredOutput();
156
- testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped();
157
- testResolveScreenTimeoutDefaultsTo24Hours();
158
- testBuildRecommendScreenProcessErrorMapsTimeout();
159
- await testResumeRequiresCheckpointFile();
160
- console.log("adapters runtime tests passed");
161
- }
162
-
163
- await main();
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 { runRecommendScreenCli, __testables as adapterTestables } from "./adapters.js";
6
+
7
+ const {
8
+ runProcess,
9
+ parseJsonOutput,
10
+ parseScreenProgressLine,
11
+ resolveRecommendScreenTimeoutMs,
12
+ buildRecommendScreenProcessError
13
+ } = adapterTestables;
14
+
15
+ async function testRunProcessHeartbeatAndOutput() {
16
+ const heartbeats = [];
17
+ const lines = [];
18
+ const result = await runProcess({
19
+ command: "node",
20
+ args: [
21
+ "-e",
22
+ "let i=0; const t=setInterval(()=>{console.error(`tick ${++i}`); if(i===3){clearInterval(t); console.log('{\"status\":\"COMPLETED\"}');}}, 120);"
23
+ ],
24
+ timeoutMs: 5000,
25
+ heartbeatIntervalMs: 40,
26
+ onHeartbeat: (event) => {
27
+ heartbeats.push(event?.source || "unknown");
28
+ },
29
+ onLine: (event) => {
30
+ lines.push(event?.line || "");
31
+ }
32
+ });
33
+
34
+ assert.equal(result.code, 0);
35
+ assert.equal(result.error_code, undefined);
36
+ assert.equal(heartbeats.length >= 3, true);
37
+ assert.equal(lines.some((line) => line.includes("tick 1")), true);
38
+ assert.equal(lines.some((line) => line.includes("\"status\":\"COMPLETED\"")), true);
39
+ }
40
+
41
+ async function testRunProcessAbortSignal() {
42
+ const controller = new AbortController();
43
+ setTimeout(() => controller.abort(), 120);
44
+
45
+ const result = await runProcess({
46
+ command: "node",
47
+ args: ["-e", "setTimeout(() => console.log('done'), 5000);"],
48
+ timeoutMs: 6000,
49
+ signal: controller.signal
50
+ });
51
+
52
+ assert.equal(result.code, -1);
53
+ assert.equal(result.error_code, "ABORTED");
54
+ assert.equal(String(result.stderr || "").includes("aborted"), true);
55
+ }
56
+
57
+ function testParsePausedStructuredOutput() {
58
+ const parsed = parseJsonOutput(`
59
+ [log] doing work
60
+ {"status":"PAUSED","result":{"processed_count":3,"output_csv":"C:/tmp/test.csv"}}
61
+ `);
62
+ assert.equal(parsed?.status, "PAUSED");
63
+ assert.equal(parsed?.result?.processed_count, 3);
64
+ }
65
+
66
+ function testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped() {
67
+ let progress = { processed: 0, passed: 0, skipped: 0, greet_count: 0 };
68
+ let tracker = {};
69
+ const feed = (line) => {
70
+ const parsed = parseScreenProgressLine(line, progress, tracker);
71
+ if (!parsed) return;
72
+ progress = parsed.progress;
73
+ tracker = parsed.tracker;
74
+ };
75
+
76
+ feed("处理第 1 位候选人: 甲");
77
+ feed("筛选结果: 通过");
78
+ feed("[关闭详情] 成功: no popup or detail signal visible");
79
+ feed("处理第 2 位候选人: 乙");
80
+ feed("筛选结果: 通过");
81
+ feed("候选人处理失败: FAVORITE_BUTTON_FAILED");
82
+ feed("[关闭详情] 成功: no popup or detail signal visible");
83
+
84
+ assert.equal(progress.processed, 2);
85
+ assert.equal(progress.passed, 1);
86
+ assert.equal(progress.skipped, 1);
87
+ }
88
+
89
+ function testResolveScreenTimeoutDefaultsTo24Hours() {
90
+ const previous = process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
91
+ delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
92
+ try {
93
+ assert.equal(resolveRecommendScreenTimeoutMs(null), 24 * 60 * 60 * 1000);
94
+ assert.equal(resolveRecommendScreenTimeoutMs({ timeoutMs: 1234 }), 1234);
95
+ } finally {
96
+ if (previous === undefined) {
97
+ delete process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS;
98
+ } else {
99
+ process.env.BOSS_RECOMMEND_SCREEN_TIMEOUT_MS = previous;
100
+ }
101
+ }
102
+ }
103
+
104
+ function testBuildRecommendScreenProcessErrorMapsTimeout() {
105
+ const error = buildRecommendScreenProcessError({ code: -1, error_code: "TIMEOUT" }, 86400000);
106
+ assert.equal(error?.code, "TIMEOUT");
107
+ assert.equal(String(error?.message || "").includes("86400000"), true);
108
+ }
109
+
110
+ async function testResumeRequiresCheckpointFile() {
111
+ const previousHome = process.env.BOSS_RECOMMEND_HOME;
112
+ const tempHome = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-screen-resume-"));
113
+ process.env.BOSS_RECOMMEND_HOME = tempHome;
114
+ try {
115
+ const configPath = path.join(tempHome, "screening-config.json");
116
+ fs.writeFileSync(configPath, JSON.stringify({
117
+ baseUrl: "https://api.openai.com/v1",
118
+ apiKey: "sk-test-valid",
119
+ model: "gpt-4.1-mini"
120
+ }, null, 2));
121
+
122
+ const missingCheckpoint = path.join(tempHome, "missing-checkpoint.json");
123
+ const result = await runRecommendScreenCli({
124
+ workspaceRoot: process.cwd(),
125
+ screenParams: {
126
+ criteria: "有MCP经验",
127
+ target_count: 10,
128
+ post_action: "favorite",
129
+ max_greet_count: null
130
+ },
131
+ resume: {
132
+ resume: true,
133
+ require_checkpoint: true,
134
+ checkpoint_path: missingCheckpoint,
135
+ pause_control_path: path.join(tempHome, "run-state.json"),
136
+ output_csv: path.join(tempHome, "resume.csv")
137
+ }
138
+ });
139
+
140
+ assert.equal(result.ok, false);
141
+ assert.equal(result.error?.code, "RESUME_CHECKPOINT_MISSING");
142
+ } finally {
143
+ if (previousHome === undefined) {
144
+ delete process.env.BOSS_RECOMMEND_HOME;
145
+ } else {
146
+ process.env.BOSS_RECOMMEND_HOME = previousHome;
147
+ }
148
+ fs.rmSync(tempHome, { recursive: true, force: true });
149
+ }
150
+ }
151
+
152
+ async function main() {
153
+ await testRunProcessHeartbeatAndOutput();
154
+ await testRunProcessAbortSignal();
155
+ testParsePausedStructuredOutput();
156
+ testParseScreenProgressLineShouldCountFavoriteFailureAsSkipped();
157
+ testResolveScreenTimeoutDefaultsTo24Hours();
158
+ testBuildRecommendScreenProcessErrorMapsTimeout();
159
+ await testResumeRequiresCheckpointFile();
160
+ console.log("adapters runtime tests passed");
161
+ }
162
+
163
+ await main();