@reconcrap/boss-recommend-mcp 0.1.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.
@@ -0,0 +1,637 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const currentFilePath = fileURLToPath(import.meta.url);
8
+ const packagedMcpDir = path.resolve(path.dirname(currentFilePath), "..");
9
+ const bossRecommendUrl = "https://www.zhipin.com/web/chat/recommend";
10
+ const chromeOnboardingUrlPattern = /^chrome:\/\/(welcome|intro|newtab|signin|history-sync|settings\/syncSetup)/i;
11
+
12
+ function getCodexHome() {
13
+ return process.env.CODEX_HOME
14
+ ? path.resolve(process.env.CODEX_HOME)
15
+ : path.join(os.homedir(), ".codex");
16
+ }
17
+
18
+ function getUserConfigPath() {
19
+ return path.join(getCodexHome(), "boss-recommend-mcp", "screening-config.json");
20
+ }
21
+
22
+ function getDesktopDir() {
23
+ return path.join(os.homedir(), "Desktop");
24
+ }
25
+
26
+ function pathExists(targetPath) {
27
+ try {
28
+ return fs.existsSync(targetPath);
29
+ } catch {
30
+ return false;
31
+ }
32
+ }
33
+
34
+ function parsePositiveInteger(raw) {
35
+ const value = Number.parseInt(String(raw || ""), 10);
36
+ return Number.isFinite(value) && value > 0 ? value : null;
37
+ }
38
+
39
+ function resolveScreenConfigPath(workspaceRoot) {
40
+ const envConfigPath = process.env.BOSS_RECOMMEND_SCREEN_CONFIG
41
+ ? path.resolve(process.env.BOSS_RECOMMEND_SCREEN_CONFIG)
42
+ : null;
43
+ const workspaceConfigPath = path.join(workspaceRoot, "boss-recommend-mcp", "config", "screening-config.json");
44
+ const userConfigPath = getUserConfigPath();
45
+ const packagedConfigPath = path.join(packagedMcpDir, "config", "screening-config.json");
46
+ const candidates = [
47
+ envConfigPath,
48
+ workspaceConfigPath,
49
+ userConfigPath,
50
+ packagedConfigPath
51
+ ].filter(Boolean);
52
+ return candidates.find((candidate) => pathExists(candidate)) || candidates[0];
53
+ }
54
+
55
+ function readJsonFile(filePath) {
56
+ if (!filePath || !pathExists(filePath)) return null;
57
+ try {
58
+ const raw = fs.readFileSync(filePath, "utf8");
59
+ return JSON.parse(raw);
60
+ } catch {
61
+ return null;
62
+ }
63
+ }
64
+
65
+ function resolveWorkspaceDebugPort(workspaceRoot) {
66
+ const fromEnv = parsePositiveInteger(process.env.BOSS_RECOMMEND_CHROME_PORT);
67
+ if (fromEnv) return fromEnv;
68
+ const config = readJsonFile(resolveScreenConfigPath(workspaceRoot));
69
+ return parsePositiveInteger(config?.debugPort) || 9222;
70
+ }
71
+
72
+ function resolveRecommendSearchCliDir(workspaceRoot) {
73
+ const localDir = path.join(workspaceRoot, "boss-recommend-search-cli");
74
+ if (pathExists(localDir)) return localDir;
75
+ const vendoredDir = path.join(packagedMcpDir, "vendor", "boss-recommend-search-cli");
76
+ if (pathExists(vendoredDir)) return vendoredDir;
77
+ return null;
78
+ }
79
+
80
+ function resolveRecommendScreenCliDir(workspaceRoot) {
81
+ const localDir = path.join(workspaceRoot, "boss-recommend-screen-cli");
82
+ if (pathExists(localDir)) return localDir;
83
+ const vendoredDir = path.join(packagedMcpDir, "vendor", "boss-recommend-screen-cli");
84
+ if (pathExists(vendoredDir)) return vendoredDir;
85
+ return null;
86
+ }
87
+
88
+ function resolveRecommendScreenCliEntry(screenDir) {
89
+ const candidates = [
90
+ path.join(screenDir, "boss-recommend-screen-cli.cjs"),
91
+ path.join(screenDir, "boss-recommend-screen-cli.js")
92
+ ];
93
+ return candidates.find((candidate) => pathExists(candidate)) || candidates[0];
94
+ }
95
+
96
+ function resolveRecommendSearchCliEntry(searchDir) {
97
+ const candidates = [
98
+ path.join(searchDir, "src", "cli.js"),
99
+ path.join(searchDir, "src", "cli.cjs")
100
+ ];
101
+ return candidates.find((candidate) => pathExists(candidate)) || candidates[0];
102
+ }
103
+
104
+ function runProcess({ command, args, cwd, timeoutMs }) {
105
+ return new Promise((resolve) => {
106
+ let stdout = "";
107
+ let stderr = "";
108
+ let settled = false;
109
+ let timer = null;
110
+
111
+ function finish(payload) {
112
+ if (settled) return;
113
+ settled = true;
114
+ if (timer) clearTimeout(timer);
115
+ resolve(payload);
116
+ }
117
+
118
+ let child;
119
+ try {
120
+ child = spawn(command, args, {
121
+ cwd,
122
+ windowsHide: true,
123
+ shell: false,
124
+ env: process.env
125
+ });
126
+ } catch (error) {
127
+ finish({
128
+ code: -1,
129
+ stdout,
130
+ stderr: error.message,
131
+ error_code: error.code || "SPAWN_FAILED"
132
+ });
133
+ return;
134
+ }
135
+
136
+ if (timeoutMs && Number.isFinite(timeoutMs) && timeoutMs > 0) {
137
+ timer = setTimeout(() => {
138
+ try {
139
+ child.kill();
140
+ } catch {}
141
+ finish({
142
+ code: -1,
143
+ stdout,
144
+ stderr: `${stderr}\nProcess timed out after ${timeoutMs}ms`.trim(),
145
+ error_code: "TIMEOUT"
146
+ });
147
+ }, timeoutMs);
148
+ }
149
+
150
+ child.stdout.on("data", (chunk) => {
151
+ stdout += chunk.toString();
152
+ });
153
+ child.stderr.on("data", (chunk) => {
154
+ stderr += chunk.toString();
155
+ });
156
+ child.on("close", (code) => finish({ code, stdout, stderr }));
157
+ child.on("error", (error) => {
158
+ finish({
159
+ code: -1,
160
+ stdout,
161
+ stderr: `${stderr}\n${error.message}`.trim(),
162
+ error_code: error.code || "SPAWN_FAILED"
163
+ });
164
+ });
165
+ });
166
+ }
167
+
168
+ function parseJsonOutput(text) {
169
+ const trimmed = String(text || "").trim();
170
+ if (!trimmed) return null;
171
+ try {
172
+ return JSON.parse(trimmed);
173
+ } catch {}
174
+ const lines = trimmed.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
175
+ for (let index = lines.length - 1; index >= 0; index -= 1) {
176
+ try {
177
+ return JSON.parse(lines[index]);
178
+ } catch {
179
+ continue;
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+
185
+ function loadScreenConfig(configPath) {
186
+ const parsed = readJsonFile(configPath);
187
+ if (!parsed) {
188
+ return {
189
+ ok: false,
190
+ error: `Screen config file not found or invalid: ${configPath}`
191
+ };
192
+ }
193
+ if (!parsed.baseUrl || !parsed.apiKey || !parsed.model) {
194
+ return {
195
+ ok: false,
196
+ error: "Invalid screen config: baseUrl/apiKey/model are required"
197
+ };
198
+ }
199
+ return { ok: true, config: parsed };
200
+ }
201
+
202
+ function localDirHint(workspaceRoot, dirName) {
203
+ return path.join(workspaceRoot, dirName);
204
+ }
205
+
206
+ export function runPipelinePreflight(workspaceRoot) {
207
+ const searchDir = resolveRecommendSearchCliDir(workspaceRoot);
208
+ const screenDir = resolveRecommendScreenCliDir(workspaceRoot);
209
+ const screenConfigPath = resolveScreenConfigPath(workspaceRoot);
210
+ const checks = [
211
+ {
212
+ key: "recommend_search_cli_dir",
213
+ ok: Boolean(searchDir && pathExists(searchDir)),
214
+ path: searchDir || localDirHint(workspaceRoot, "boss-recommend-search-cli"),
215
+ message: "boss-recommend-search-cli 目录不存在"
216
+ },
217
+ {
218
+ key: "recommend_search_cli_entry",
219
+ ok: Boolean(searchDir && pathExists(resolveRecommendSearchCliEntry(searchDir))),
220
+ path: searchDir ? resolveRecommendSearchCliEntry(searchDir) : path.join(localDirHint(workspaceRoot, "boss-recommend-search-cli"), "src", "cli.js"),
221
+ message: "boss-recommend-search-cli 入口文件缺失"
222
+ },
223
+ {
224
+ key: "recommend_screen_cli_dir",
225
+ ok: Boolean(screenDir && pathExists(screenDir)),
226
+ path: screenDir || localDirHint(workspaceRoot, "boss-recommend-screen-cli"),
227
+ message: "boss-recommend-screen-cli 目录不存在"
228
+ },
229
+ {
230
+ key: "recommend_screen_cli_entry",
231
+ ok: Boolean(screenDir && pathExists(resolveRecommendScreenCliEntry(screenDir))),
232
+ path: screenDir ? resolveRecommendScreenCliEntry(screenDir) : path.join(localDirHint(workspaceRoot, "boss-recommend-screen-cli"), "boss-recommend-screen-cli.cjs"),
233
+ message: "boss-recommend-screen-cli 入口文件缺失"
234
+ },
235
+ {
236
+ key: "screen_config",
237
+ ok: pathExists(screenConfigPath),
238
+ path: screenConfigPath,
239
+ message: "screening-config.json 不存在"
240
+ }
241
+ ];
242
+
243
+ return {
244
+ ok: checks.every((item) => item.ok),
245
+ checks,
246
+ debug_port: resolveWorkspaceDebugPort(workspaceRoot)
247
+ };
248
+ }
249
+
250
+ function sleep(ms) {
251
+ return new Promise((resolve) => setTimeout(resolve, ms));
252
+ }
253
+
254
+ async function listChromeTabs(port) {
255
+ const response = await fetch(`http://127.0.0.1:${port}/json/list`);
256
+ if (!response.ok) {
257
+ throw new Error(`DevTools endpoint returned ${response.status}`);
258
+ }
259
+ const data = await response.json();
260
+ return Array.isArray(data) ? data : [];
261
+ }
262
+
263
+ function buildBossPageState(payload) {
264
+ return {
265
+ key: "boss_page_state",
266
+ ...payload
267
+ };
268
+ }
269
+
270
+ function extractSampleUrls(tabs, limit = 5) {
271
+ return tabs
272
+ .map((tab) => tab?.url)
273
+ .filter(Boolean)
274
+ .slice(0, limit);
275
+ }
276
+
277
+ function findChromeOnboardingUrl(tabs) {
278
+ for (const tab of tabs) {
279
+ if (typeof tab?.url === "string" && chromeOnboardingUrlPattern.test(tab.url)) {
280
+ return tab.url;
281
+ }
282
+ }
283
+ return null;
284
+ }
285
+
286
+ export async function inspectBossRecommendPageState(port, options = {}) {
287
+ const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : 6000;
288
+ const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 1000;
289
+ const expectedUrl = options.expectedUrl || bossRecommendUrl;
290
+ const deadline = Date.now() + timeoutMs;
291
+ let lastError = null;
292
+ let lastTabs = [];
293
+
294
+ while (Date.now() < deadline) {
295
+ try {
296
+ const tabs = await listChromeTabs(port);
297
+ lastTabs = tabs;
298
+ const exactTab = tabs.find(
299
+ (tab) => typeof tab?.url === "string" && tab.url.includes("/web/chat/recommend")
300
+ );
301
+ if (exactTab) {
302
+ return buildBossPageState({
303
+ ok: true,
304
+ state: "RECOMMEND_READY",
305
+ path: exactTab.url,
306
+ current_url: exactTab.url,
307
+ title: exactTab.title || null,
308
+ requires_login: false,
309
+ message: "Boss 推荐页已打开,且当前仍停留在 recommend 页面。"
310
+ });
311
+ }
312
+
313
+ const bossTab = tabs.find(
314
+ (tab) => typeof tab?.url === "string" && tab.url.includes("zhipin.com")
315
+ );
316
+ if (bossTab) {
317
+ return buildBossPageState({
318
+ ok: false,
319
+ state: "LOGIN_REQUIRED",
320
+ path: bossTab.url,
321
+ current_url: bossTab.url,
322
+ title: bossTab.title || null,
323
+ requires_login: true,
324
+ expected_url: expectedUrl,
325
+ message: "Boss 页面没有停留在 recommend 页面,通常表示需要重新登录。"
326
+ });
327
+ }
328
+ } catch (error) {
329
+ lastError = error;
330
+ }
331
+
332
+ await sleep(pollMs);
333
+ }
334
+
335
+ if (lastError) {
336
+ return buildBossPageState({
337
+ ok: false,
338
+ state: "DEBUG_PORT_UNREACHABLE",
339
+ path: `http://127.0.0.1:${port}`,
340
+ current_url: null,
341
+ title: null,
342
+ requires_login: false,
343
+ expected_url,
344
+ message: `无法连接到 Chrome DevTools 端口 ${port}。请确认 Chrome 已以远程调试模式启动。`,
345
+ error: lastError.message
346
+ });
347
+ }
348
+
349
+ const onboardingUrl = findChromeOnboardingUrl(lastTabs);
350
+ if (onboardingUrl) {
351
+ return buildBossPageState({
352
+ ok: false,
353
+ state: "CHROME_ONBOARDING_INTERCEPTED",
354
+ path: onboardingUrl,
355
+ current_url: onboardingUrl,
356
+ title: null,
357
+ requires_login: false,
358
+ expected_url,
359
+ message: "Chrome 当前停留在登录或引导页,尚未稳定到 Boss 推荐页。",
360
+ sample_urls: extractSampleUrls(lastTabs)
361
+ });
362
+ }
363
+
364
+ return buildBossPageState({
365
+ ok: false,
366
+ state: "BOSS_TAB_NOT_FOUND",
367
+ path: expectedUrl,
368
+ current_url: null,
369
+ title: null,
370
+ requires_login: false,
371
+ expected_url,
372
+ message: "未检测到 Boss 推荐页标签页。",
373
+ sample_urls: extractSampleUrls(lastTabs)
374
+ });
375
+ }
376
+
377
+ async function openBossRecommendTab(port) {
378
+ const endpoint = `http://127.0.0.1:${port}/json/new?${encodeURIComponent(bossRecommendUrl)}`;
379
+ const attempts = ["PUT", "GET"];
380
+ let lastError = null;
381
+
382
+ for (const method of attempts) {
383
+ try {
384
+ const response = await fetch(endpoint, { method });
385
+ if (response.ok) {
386
+ return { ok: true, method };
387
+ }
388
+ lastError = new Error(`DevTools /json/new returned ${response.status}`);
389
+ } catch (error) {
390
+ lastError = error;
391
+ }
392
+ }
393
+
394
+ return {
395
+ ok: false,
396
+ error: lastError?.message || "Failed to open Boss recommend tab via DevTools /json/new"
397
+ };
398
+ }
399
+
400
+ async function verifyRecommendPageStable(port, options = {}) {
401
+ const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 1500;
402
+ const recheckTimeoutMs = Number.isFinite(options.recheckTimeoutMs) ? options.recheckTimeoutMs : 2500;
403
+ const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 600;
404
+
405
+ await sleep(settleMs);
406
+ const recheck = await inspectBossRecommendPageState(port, {
407
+ timeoutMs: recheckTimeoutMs,
408
+ pollMs
409
+ });
410
+ if (recheck.state === "RECOMMEND_READY") {
411
+ return recheck;
412
+ }
413
+ if (recheck.state === "LOGIN_REQUIRED") {
414
+ return buildBossPageState({
415
+ ...recheck,
416
+ state: "LOGIN_REQUIRED_AFTER_REDIRECT",
417
+ message: "Boss 页面曾进入 recommend 但随后跳转到其他页面,通常表示登录态失效。"
418
+ });
419
+ }
420
+ return recheck;
421
+ }
422
+
423
+ export async function ensureBossRecommendPageReady(workspaceRoot, options = {}) {
424
+ const debugPort = Number.isFinite(options.port)
425
+ ? options.port
426
+ : resolveWorkspaceDebugPort(workspaceRoot);
427
+ const attempts = Number.isFinite(options.attempts) ? Math.max(0, options.attempts) : 3;
428
+ const inspectTimeoutMs = Number.isFinite(options.inspectTimeoutMs) ? options.inspectTimeoutMs : 6000;
429
+ const pollMs = Number.isFinite(options.pollMs) ? options.pollMs : 800;
430
+ const settleMs = Number.isFinite(options.settleMs) ? options.settleMs : 800;
431
+
432
+ let pageState = await inspectBossRecommendPageState(debugPort, {
433
+ timeoutMs: inspectTimeoutMs,
434
+ pollMs
435
+ });
436
+ if (pageState.state === "RECOMMEND_READY") {
437
+ const stableState = await verifyRecommendPageStable(debugPort, { settleMs, pollMs });
438
+ return {
439
+ ok: stableState.state === "RECOMMEND_READY",
440
+ debug_port: debugPort,
441
+ state: stableState.state,
442
+ page_state: stableState
443
+ };
444
+ }
445
+ if (pageState.state === "LOGIN_REQUIRED") {
446
+ return {
447
+ ok: false,
448
+ debug_port: debugPort,
449
+ state: pageState.state,
450
+ page_state: pageState
451
+ };
452
+ }
453
+
454
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
455
+ if (pageState.state === "DEBUG_PORT_UNREACHABLE") {
456
+ break;
457
+ }
458
+ await openBossRecommendTab(debugPort);
459
+ await sleep(settleMs);
460
+ pageState = await inspectBossRecommendPageState(debugPort, {
461
+ timeoutMs: inspectTimeoutMs,
462
+ pollMs
463
+ });
464
+ if (pageState.state === "RECOMMEND_READY") {
465
+ const stableState = await verifyRecommendPageStable(debugPort, { settleMs, pollMs });
466
+ return {
467
+ ok: stableState.state === "RECOMMEND_READY",
468
+ debug_port: debugPort,
469
+ state: stableState.state,
470
+ page_state: stableState
471
+ };
472
+ }
473
+ if (pageState.state === "LOGIN_REQUIRED") {
474
+ return {
475
+ ok: false,
476
+ debug_port: debugPort,
477
+ state: pageState.state,
478
+ page_state: pageState
479
+ };
480
+ }
481
+ }
482
+
483
+ return {
484
+ ok: false,
485
+ debug_port: debugPort,
486
+ state: pageState.state || "UNKNOWN",
487
+ page_state: pageState
488
+ };
489
+ }
490
+
491
+ export async function runRecommendSearchCli({ workspaceRoot, searchParams }) {
492
+ const searchDir = resolveRecommendSearchCliDir(workspaceRoot);
493
+ if (!searchDir) {
494
+ return {
495
+ ok: false,
496
+ stdout: "",
497
+ stderr: "boss-recommend-search-cli package not found",
498
+ error: {
499
+ code: "RECOMMEND_SEARCH_CLI_MISSING",
500
+ message: "boss-recommend-search-cli 目录不存在。"
501
+ }
502
+ };
503
+ }
504
+ const cliPath = resolveRecommendSearchCliEntry(searchDir);
505
+ const args = [
506
+ cliPath,
507
+ "--school-tag",
508
+ searchParams.school_tag,
509
+ "--gender",
510
+ searchParams.gender,
511
+ "--recent-not-view",
512
+ searchParams.recent_not_view,
513
+ "--port",
514
+ String(resolveWorkspaceDebugPort(workspaceRoot))
515
+ ];
516
+ const result = await runProcess({
517
+ command: "node",
518
+ args,
519
+ cwd: searchDir,
520
+ timeoutMs: 180000
521
+ });
522
+ const structured = parseJsonOutput(result.stdout);
523
+ return {
524
+ ok: result.code === 0 && structured?.status === "COMPLETED",
525
+ stdout: result.stdout,
526
+ stderr: result.stderr,
527
+ structured,
528
+ summary: structured?.result || null,
529
+ error: structured?.error || (
530
+ result.code === 0
531
+ ? null
532
+ : {
533
+ code: "RECOMMEND_SEARCH_FAILED",
534
+ message: "推荐页筛选命令执行失败。"
535
+ }
536
+ )
537
+ };
538
+ }
539
+
540
+ export async function runRecommendScreenCli({ workspaceRoot, screenParams }) {
541
+ const screenDir = resolveRecommendScreenCliDir(workspaceRoot);
542
+ if (!screenDir) {
543
+ return {
544
+ ok: false,
545
+ stdout: "",
546
+ stderr: "boss-recommend-screen-cli package not found",
547
+ error: {
548
+ code: "RECOMMEND_SCREEN_CLI_MISSING",
549
+ message: "boss-recommend-screen-cli 目录不存在。"
550
+ }
551
+ };
552
+ }
553
+ const configPath = resolveScreenConfigPath(workspaceRoot);
554
+ const loaded = loadScreenConfig(configPath);
555
+ if (!loaded.ok) {
556
+ return {
557
+ ok: false,
558
+ stdout: "",
559
+ stderr: loaded.error,
560
+ error: {
561
+ code: "SCREEN_CONFIG_ERROR",
562
+ message: loaded.error
563
+ }
564
+ };
565
+ }
566
+
567
+ const outputName = `recommend_screen_result_${Date.now()}.csv`;
568
+ let outputPath = outputName;
569
+ if (loaded.config.outputDir) {
570
+ const resolvedOutputDir = path.resolve(path.dirname(configPath), loaded.config.outputDir);
571
+ fs.mkdirSync(resolvedOutputDir, { recursive: true });
572
+ outputPath = path.join(resolvedOutputDir, outputName);
573
+ } else {
574
+ const desktopDir = getDesktopDir();
575
+ fs.mkdirSync(desktopDir, { recursive: true });
576
+ outputPath = path.join(desktopDir, outputName);
577
+ }
578
+
579
+ const cliPath = resolveRecommendScreenCliEntry(screenDir);
580
+ const args = [
581
+ cliPath,
582
+ "--baseurl",
583
+ loaded.config.baseUrl,
584
+ "--apikey",
585
+ loaded.config.apiKey,
586
+ "--model",
587
+ loaded.config.model,
588
+ "--port",
589
+ String(resolveWorkspaceDebugPort(workspaceRoot)),
590
+ "--criteria",
591
+ screenParams.criteria,
592
+ "--post-action",
593
+ screenParams.post_action,
594
+ "--post-action-confirmed",
595
+ "true",
596
+ "--output",
597
+ outputPath
598
+ ];
599
+
600
+ if (loaded.config.openaiOrganization) {
601
+ args.push("--openai-organization", loaded.config.openaiOrganization);
602
+ }
603
+ if (loaded.config.openaiProject) {
604
+ args.push("--openai-project", loaded.config.openaiProject);
605
+ }
606
+ if (Number.isInteger(screenParams.target_count) && screenParams.target_count > 0) {
607
+ args.push("--targetCount", String(screenParams.target_count));
608
+ }
609
+ if (screenParams.post_action === "greet"
610
+ && Number.isInteger(screenParams.max_greet_count)
611
+ && screenParams.max_greet_count > 0) {
612
+ args.push("--max-greet-count", String(screenParams.max_greet_count));
613
+ }
614
+
615
+ const result = await runProcess({
616
+ command: "node",
617
+ args,
618
+ cwd: screenDir,
619
+ timeoutMs: 60 * 60 * 1000
620
+ });
621
+ const structured = parseJsonOutput(result.stdout);
622
+ return {
623
+ ok: result.code === 0 && structured?.status === "COMPLETED",
624
+ stdout: result.stdout,
625
+ stderr: result.stderr,
626
+ structured,
627
+ summary: structured?.result || null,
628
+ error: structured?.error || (
629
+ result.code === 0
630
+ ? null
631
+ : {
632
+ code: "RECOMMEND_SCREEN_FAILED",
633
+ message: "推荐页筛选命令执行失败。"
634
+ }
635
+ )
636
+ };
637
+ }