@reconcrap/boss-recommend-mcp 1.3.34 → 1.3.36

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 CHANGED
@@ -16,7 +16,7 @@ Boss 推荐页自动化流水线 MCP(stdio)服务。
16
16
 
17
17
  - 单独运行 chat-only 任务,不需要再单独安装 `boss-chat`
18
18
  - 在 recommend screen 完成后,通过同一个父 run 自动进入 `chat_followup`
19
- - 继续把聊天页状态保存在工作区下的 `.boss-chat/`
19
+ - 继续把聊天页状态保存在用户目录下的 `~/.boss-recommend-mcp/boss-chat/`(可通过 `BOSS_CHAT_HOME` 覆盖)
20
20
 
21
21
  MCP 工具:
22
22
 
@@ -68,7 +68,7 @@ MCP 工具:
68
68
  - 在真正开始 search/screen 前,会进行最后一轮全参数总确认(岗位 + 全部筛选参数 + criteria + target_count + post_action + max_greet_count)
69
69
  - npm 全局安装后会自动执行 install:生成 skill、导出 MCP 模板,并自动尝试写入已检测到的外部 agent MCP 配置(含 Trae / trae-cn / Cursor / Claude / OpenClaw)
70
70
  - npm / npx 安装后会自动初始化 `screening-config.json` 模板(优先写入 workspace 的 `config/`,不可写时回退到用户目录)
71
- - npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`<workspace>/.boss-chat` 及其 `logs/runs/profiles/reports/artifacts`
71
+ - npm 安装流程会预创建运行目录(跨平台):`~/.boss-recommend-mcp`、`~/.boss-recommend-mcp/runs`、`~/.boss-recommend-mcp/boss-chat` 及其 `logs/runs/profiles/reports/artifacts/state`
72
72
  - `post_action` 必须在每次完整运行开始时确认一次
73
73
  - `target_count` 会在每次运行开始时询问一次(可留空,不设上限)
74
74
  - 当 `post_action=greet` 时,必须在运行开始时确认 `max_greet_count`
@@ -111,6 +111,7 @@ node src/cli.js start
111
111
 
112
112
  ```bash
113
113
  BOSS_RECOMMEND_HOME # 统一状态目录,默认 ~/.boss-recommend-mcp
114
+ BOSS_CHAT_HOME # 覆盖 boss-chat 运行态目录;默认 ~/.boss-recommend-mcp/boss-chat
114
115
  BOSS_RECOMMEND_SCREEN_CONFIG # 显式指定 screening-config.json 路径(最高优先级)
115
116
  BOSS_RECOMMEND_MCP_CONFIG_TARGETS # JSON 数组或系统 path 分隔路径列表,指定额外 mcp.json 目标文件
116
117
  BOSS_RECOMMEND_EXTERNAL_SKILL_DIRS # JSON 数组或系统 path 分隔路径列表,指定额外 skills 根目录
@@ -149,6 +150,8 @@ node src/cli.js run --instruction "推荐页筛选985男生,近14天没有,
149
150
  - `doctor` / `run` 默认优先读取 `~/.boss-recommend-mcp/screening-config.json`;如需强制其它路径,请设置 `BOSS_RECOMMEND_SCREEN_CONFIG`
150
151
  - 首次运行时,若仍检测到默认占位词(如 `replace-with-openai-api-key`),pipeline 会返回配置目录并要求用户修改后确认“已修改完成”再继续
151
152
  - 在 `npx` 临时目录(如 `AppData\\Local\\npm-cache\\_npx\\...`)执行时,不会再把该临时目录当作 `screening-config.json` 目标路径
153
+ - `boss-chat` 运行态默认固定写入 `~/.boss-recommend-mcp/boss-chat`;即使宿主把 MCP 进程启动在系统根目录,也不会再尝试写入 `/.boss-chat`
154
+ - 若当前工作区存在历史 `.boss-chat` 且新用户目录尚未初始化,首次运行会自动把 `logs/runs/profiles/reports/artifacts/state` 迁移到新目录,并保留旧目录作为只读历史来源
152
155
 
153
156
  配置样例见:
154
157
 
@@ -217,6 +220,7 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
217
220
  - `baseUrl` / `apiKey` / `model` 不再单独传入,固定复用 recommend 的 `screening-config.json`
218
221
  - 若缺少 `follow_up.chat` 必填项,pipeline 会返回 `NEED_INPUT`
219
222
  - recommend 成功后,父 run 继续存活并进入 `chat_followup`;chat 结束后父 run 才会进入最终终态
223
+ - `boss-chat` 子任务状态统一写入 `~/.boss-recommend-mcp/boss-chat`(或 `BOSS_CHAT_HOME` 指定目录),不再依赖工作区 `cwd`
220
224
 
221
225
  ## Chat-only
222
226
 
@@ -230,11 +234,14 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
230
234
  - MCP:
231
235
  - `boss_chat_health_check`
232
236
  - `prepare_boss_chat_run`
233
- - `start_boss_chat_run`
234
- - `get_boss_chat_run`
235
- - `pause_boss_chat_run`
236
- - `resume_boss_chat_run`
237
- - `cancel_boss_chat_run`
237
+ - `start_boss_chat_run`
238
+ - `get_boss_chat_run`
239
+ - `pause_boss_chat_run`
240
+ - `resume_boss_chat_run`
241
+ - `cancel_boss_chat_run`
242
+ - vendored `boss-chat` CLI 还支持 `--data-dir <path>` 与 `BOSS_CHAT_HOME`,默认目录为 `~/.boss-recommend-mcp/boss-chat`(若设置 `BOSS_RECOMMEND_HOME`,则默认 `<BOSS_RECOMMEND_HOME>/boss-chat`)
243
+ - 对 `/.boss-chat`、系统目录等危险运行目录会主动拒绝启动并返回 `UNSAFE_DATA_DIR`,避免在 harness 丢参时误写根目录
244
+ - `boss_chat_health_check` 与 chat run 返回结果会包含 `data_dir` 与 `data_dir_source`,便于定位是参数/环境变量/默认路径生效
238
245
 
239
246
  chat-only 交互建议:
240
247
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reconcrap/boss-recommend-mcp",
3
- "version": "1.3.34",
3
+ "version": "1.3.36",
4
4
  "description": "Unified MCP pipeline for recommend-page filtering and screening on Boss Zhipin",
5
5
  "keywords": [
6
6
  "boss",
package/src/boss-chat.js CHANGED
@@ -1,8 +1,9 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import process from "node:process";
4
- import { spawn } from "node:child_process";
5
- import { fileURLToPath } from "node:url";
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { spawn } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
6
7
 
7
8
  import { getScreenConfigResolution, resolveSharedLlmTransportConfig } from "./adapters.js";
8
9
 
@@ -11,12 +12,14 @@ const packageRoot = path.resolve(path.dirname(currentFilePath), "..");
11
12
  const VENDORED_BOSS_CHAT_DIR = path.join(packageRoot, "vendor", "boss-chat-cli");
12
13
  const DEFAULT_BOSS_CHAT_POLL_MS = 1500;
13
14
  const PREPARE_BOSS_CHAT_MAX_ATTEMPTS = 3;
14
- const PREPARE_BOSS_CHAT_RETRY_DELAY_MS = 1200;
15
- const BOSS_CHAT_TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
16
- const CHAT_REQUIRED_FIELDS = ["job", "start_from", "target_count", "criteria"];
17
- export const TARGET_COUNT_CANONICAL_ALL = "all";
18
- export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
19
- const TARGET_COUNT_WRAPPER_KEYS = ["target_count", "targetCount", "value", "count", "limit"];
15
+ const PREPARE_BOSS_CHAT_RETRY_DELAY_MS = 1200;
16
+ const BOSS_CHAT_TERMINAL_STATES = new Set(["completed", "failed", "canceled"]);
17
+ const CHAT_REQUIRED_FIELDS = ["job", "start_from", "target_count", "criteria"];
18
+ const BOSS_CHAT_RUNTIME_SUBDIR = "boss-chat";
19
+ const BOSS_CHAT_RUNTIME_CHILD_DIRS = ["logs", "runs", "profiles", "reports", "artifacts", "state"];
20
+ export const TARGET_COUNT_CANONICAL_ALL = "all";
21
+ export const TARGET_COUNT_ACCEPTED_EXAMPLES = [TARGET_COUNT_CANONICAL_ALL, -1, 20, "全部候选人"];
22
+ const TARGET_COUNT_WRAPPER_KEYS = ["target_count", "targetCount", "value", "count", "limit"];
20
23
  const LLM_THINKING_LEVEL_FIELDS = [
21
24
  "llmThinkingLevel",
22
25
  "thinkingLevel",
@@ -28,13 +31,210 @@ function normalizeText(value) {
28
31
  return String(value || "").replace(/\s+/g, " ").trim();
29
32
  }
30
33
 
31
- function pathExists(targetPath) {
32
- try {
33
- return fs.existsSync(targetPath);
34
+ function pathExists(targetPath) {
35
+ try {
36
+ return fs.existsSync(targetPath);
34
37
  } catch {
35
38
  return false;
36
- }
37
- }
39
+ }
40
+ }
41
+
42
+ function getStateHome() {
43
+ return process.env.BOSS_RECOMMEND_HOME
44
+ ? path.resolve(process.env.BOSS_RECOMMEND_HOME)
45
+ : path.join(os.homedir(), ".boss-recommend-mcp");
46
+ }
47
+
48
+ function isRootDirectory(targetPath) {
49
+ const resolved = path.resolve(String(targetPath || ""));
50
+ const parsed = path.parse(resolved);
51
+ return resolved.toLowerCase() === String(parsed.root || "").toLowerCase();
52
+ }
53
+
54
+ function isSystemDirectoryWorkspaceRoot(workspaceRoot) {
55
+ const root = path.resolve(String(workspaceRoot || ""));
56
+ const normalized = root.replace(/\\/g, "/").toLowerCase();
57
+ if (process.platform === "win32") {
58
+ return (
59
+ normalized.endsWith("/windows")
60
+ || normalized.endsWith("/windows/system32")
61
+ || normalized.endsWith("/windows/syswow64")
62
+ || normalized.endsWith("/program files")
63
+ || normalized.endsWith("/program files (x86)")
64
+ );
65
+ }
66
+ return (
67
+ normalized === "/system"
68
+ || normalized.startsWith("/system/")
69
+ || normalized === "/usr"
70
+ || normalized.startsWith("/usr/")
71
+ || normalized === "/bin"
72
+ || normalized.startsWith("/bin/")
73
+ || normalized === "/sbin"
74
+ || normalized.startsWith("/sbin/")
75
+ );
76
+ }
77
+
78
+ function isEphemeralWorkspaceRoot(workspaceRoot) {
79
+ const normalized = path.resolve(String(workspaceRoot || ""))
80
+ .replace(/\\/g, "/")
81
+ .toLowerCase();
82
+ return (
83
+ normalized.includes("/appdata/local/npm-cache/_npx/")
84
+ || normalized.includes("/node_modules/@reconcrap/boss-recommend-mcp")
85
+ );
86
+ }
87
+
88
+ function isSafeBossChatLegacyWorkspaceRoot(workspaceRoot) {
89
+ const root = path.resolve(String(workspaceRoot || ""));
90
+ if (!root) return false;
91
+ const home = path.resolve(os.homedir());
92
+ return !(
93
+ isEphemeralWorkspaceRoot(root)
94
+ || isRootDirectory(root)
95
+ || root.toLowerCase() === home.toLowerCase()
96
+ || isSystemDirectoryWorkspaceRoot(root)
97
+ );
98
+ }
99
+
100
+ function isUnsafeBossChatDataDir(targetPath) {
101
+ const resolved = path.resolve(String(targetPath || ""));
102
+ return isRootDirectory(resolved) || isSystemDirectoryWorkspaceRoot(resolved);
103
+ }
104
+
105
+ function resolveBossChatDataDir() {
106
+ if (process.env.BOSS_CHAT_HOME) {
107
+ return {
108
+ data_dir: path.resolve(process.env.BOSS_CHAT_HOME),
109
+ data_dir_source: "env:BOSS_CHAT_HOME"
110
+ };
111
+ }
112
+ const stateHome = getStateHome();
113
+ const source = process.env.BOSS_RECOMMEND_HOME
114
+ ? "default:env:BOSS_RECOMMEND_HOME"
115
+ : "default:user_home";
116
+ return {
117
+ data_dir: path.join(stateHome, BOSS_CHAT_RUNTIME_SUBDIR),
118
+ data_dir_source: source
119
+ };
120
+ }
121
+
122
+ export function getBossChatDataDir() {
123
+ return resolveBossChatDataDir().data_dir;
124
+ }
125
+
126
+ export function getLegacyBossChatWorkspaceDataDir(workspaceRoot) {
127
+ if (!isSafeBossChatLegacyWorkspaceRoot(workspaceRoot)) return null;
128
+ return path.join(path.resolve(String(workspaceRoot)), ".boss-chat");
129
+ }
130
+
131
+ export function resolveBossChatRuntimeLayout(workspaceRoot) {
132
+ const resolvedDataDir = resolveBossChatDataDir();
133
+ const dataDir = resolvedDataDir.data_dir;
134
+ const legacyWorkspaceDir = getLegacyBossChatWorkspaceDataDir(workspaceRoot);
135
+ const migrationSourceDir =
136
+ legacyWorkspaceDir && pathExists(legacyWorkspaceDir) && !pathExists(dataDir)
137
+ ? legacyWorkspaceDir
138
+ : null;
139
+ return {
140
+ workspace_root: workspaceRoot ? path.resolve(String(workspaceRoot)) : null,
141
+ data_dir: dataDir,
142
+ data_dir_source: resolvedDataDir.data_dir_source,
143
+ legacy_workspace_dir: legacyWorkspaceDir,
144
+ migration_source_dir: migrationSourceDir,
145
+ migration_pending: Boolean(migrationSourceDir),
146
+ directories: [
147
+ dataDir,
148
+ ...BOSS_CHAT_RUNTIME_CHILD_DIRS.map((name) => path.join(dataDir, name))
149
+ ]
150
+ };
151
+ }
152
+
153
+ export function ensureBossChatRuntimeReady(workspaceRoot) {
154
+ const runtime = resolveBossChatRuntimeLayout(workspaceRoot);
155
+ const created = [];
156
+ const existed = [];
157
+ const failed = [];
158
+ let migration = {
159
+ attempted: false,
160
+ performed: false,
161
+ source: runtime.migration_source_dir,
162
+ target: runtime.data_dir,
163
+ message: runtime.migration_source_dir
164
+ ? `Pending legacy boss-chat migration from ${runtime.migration_source_dir}`
165
+ : ""
166
+ };
167
+
168
+ if (isUnsafeBossChatDataDir(runtime.data_dir)) {
169
+ return {
170
+ ...runtime,
171
+ created,
172
+ existed,
173
+ failed: [
174
+ {
175
+ path: runtime.data_dir,
176
+ message: `Refusing unsafe boss-chat runtime path: ${runtime.data_dir}. Please use BOSS_CHAT_HOME in a writable user directory.`
177
+ }
178
+ ],
179
+ migration,
180
+ blocked_reason: "UNSAFE_DATA_DIR"
181
+ };
182
+ }
183
+
184
+ if (runtime.migration_source_dir) {
185
+ try {
186
+ fs.cpSync(runtime.migration_source_dir, runtime.data_dir, {
187
+ recursive: true,
188
+ force: false,
189
+ errorOnExist: false
190
+ });
191
+ migration = {
192
+ attempted: true,
193
+ performed: true,
194
+ source: runtime.migration_source_dir,
195
+ target: runtime.data_dir,
196
+ message: `Migrated legacy boss-chat runtime from ${runtime.migration_source_dir} to ${runtime.data_dir}. Legacy source was preserved.`
197
+ };
198
+ } catch (error) {
199
+ migration = {
200
+ attempted: true,
201
+ performed: false,
202
+ source: runtime.migration_source_dir,
203
+ target: runtime.data_dir,
204
+ message: error?.message || "Legacy boss-chat migration failed."
205
+ };
206
+ failed.push({
207
+ path: runtime.data_dir,
208
+ message: `Legacy migration failed: ${migration.message}`
209
+ });
210
+ }
211
+ }
212
+
213
+ for (const directory of runtime.directories) {
214
+ try {
215
+ const existedBefore = pathExists(directory);
216
+ fs.mkdirSync(directory, { recursive: true });
217
+ if (existedBefore) {
218
+ existed.push(directory);
219
+ } else {
220
+ created.push(directory);
221
+ }
222
+ } catch (error) {
223
+ failed.push({
224
+ path: directory,
225
+ message: error?.message || String(error)
226
+ });
227
+ }
228
+ }
229
+
230
+ return {
231
+ ...runtime,
232
+ created,
233
+ existed,
234
+ failed,
235
+ migration
236
+ };
237
+ }
38
238
 
39
239
  function parsePositiveInteger(value, fallback = null) {
40
240
  const parsed = Number.parseInt(String(value ?? ""), 10);
@@ -469,9 +669,12 @@ function buildTargetCountNeedInputDiagnostics(input = {}, missingFields = []) {
469
669
  };
470
670
  }
471
671
 
472
- function buildBossChatCliArgs(command, input, resolvedConfig) {
473
- const args = [command, "--json"];
474
- if (command === "prepare-run") {
672
+ function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = null) {
673
+ const args = [command, "--json"];
674
+ if (runtimeLayout?.data_dir) {
675
+ args.push("--data-dir", runtimeLayout.data_dir);
676
+ }
677
+ if (command === "prepare-run") {
475
678
  const normalized = normalizeBossChatStartInput(input);
476
679
  args.push("--profile", normalized.profile);
477
680
  if (normalized.job) args.push("--job", normalized.job);
@@ -532,29 +735,65 @@ function buildBossChatCliArgs(command, input, resolvedConfig) {
532
735
  const runId = normalizeBossChatRunId(input);
533
736
  args.push("--profile", normalizeText(input.profile) || "default");
534
737
  args.push("--run-id", runId);
535
- return args;
536
- }
537
-
538
- async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
539
- const cliPath = resolveBossChatCliPath(workspaceRoot);
540
- if (!cliPath) {
541
- return {
738
+ return args;
739
+ }
740
+
741
+ function withRuntimeDiagnostics(payload, runtimeLayout) {
742
+ if (!payload || typeof payload !== "object") return payload;
743
+ return {
744
+ ...payload,
745
+ data_dir: runtimeLayout?.data_dir || null,
746
+ data_dir_source: runtimeLayout?.data_dir_source || null
747
+ };
748
+ }
749
+
750
+ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
751
+ const runtimeLayout = ensureBossChatRuntimeReady(workspaceRoot);
752
+ const cliPath = resolveBossChatCliPath(workspaceRoot);
753
+ if (!cliPath) {
754
+ return {
542
755
  ok: false,
543
756
  exitCode: -1,
544
757
  stdout: "",
545
758
  stderr: "",
546
- payload: {
547
- status: "FAILED",
548
- error: {
549
- code: "BOSS_CHAT_CLI_MISSING",
550
- message: "未找到 vendored boss-chat CLI。"
551
- }
552
- }
553
- };
554
- }
555
-
556
- let configResolution = null;
557
- if (command === "start-run" || command === "prepare-run") {
759
+ payload: {
760
+ status: "FAILED",
761
+ error: {
762
+ code: "BOSS_CHAT_CLI_MISSING",
763
+ message: "未找到 vendored boss-chat CLI。"
764
+ },
765
+ data_dir: runtimeLayout?.data_dir || null,
766
+ data_dir_source: runtimeLayout?.data_dir_source || null
767
+ }
768
+ };
769
+ }
770
+
771
+ const runtimeInitFailed = runtimeLayout.failed.some((item) => item.path === runtimeLayout.data_dir)
772
+ && !pathExists(runtimeLayout.data_dir);
773
+ if (runtimeInitFailed) {
774
+ return {
775
+ ok: false,
776
+ exitCode: 1,
777
+ stdout: "",
778
+ stderr: "",
779
+ payload: {
780
+ status: "FAILED",
781
+ error: {
782
+ code: "BOSS_CHAT_RUNTIME_INIT_FAILED",
783
+ message: runtimeLayout.failed
784
+ .filter((item) => item.path === runtimeLayout.data_dir)
785
+ .map((item) => item.message)
786
+ .join("; ") || "无法初始化 boss-chat runtime 目录。"
787
+ },
788
+ data_dir: runtimeLayout.data_dir,
789
+ data_dir_source: runtimeLayout.data_dir_source,
790
+ migration: runtimeLayout.migration
791
+ }
792
+ };
793
+ }
794
+
795
+ let configResolution = null;
796
+ if (command === "start-run" || command === "prepare-run") {
558
797
  configResolution = resolveBossChatScreenConfig(workspaceRoot);
559
798
  if (!configResolution.ok) {
560
799
  return {
@@ -562,25 +801,30 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
562
801
  exitCode: 1,
563
802
  stdout: "",
564
803
  stderr: "",
565
- payload: {
566
- status: "FAILED",
567
- error: configResolution.error,
568
- config_path: configResolution.config_path,
569
- config_dir: configResolution.config_dir
570
- }
571
- };
572
- }
573
- }
574
-
575
- const args = [cliPath, ...buildBossChatCliArgs(command, input, configResolution?.config || {})];
576
- const cwd = path.resolve(String(workspaceRoot || process.cwd()));
577
- return new Promise((resolve) => {
578
- const child = spawn(process.execPath, args, {
579
- cwd,
580
- env: process.env,
581
- windowsHide: true,
582
- stdio: ["ignore", "pipe", "pipe"]
583
- });
804
+ payload: {
805
+ status: "FAILED",
806
+ error: configResolution.error,
807
+ config_path: configResolution.config_path,
808
+ config_dir: configResolution.config_dir,
809
+ data_dir: runtimeLayout.data_dir,
810
+ data_dir_source: runtimeLayout.data_dir_source
811
+ }
812
+ };
813
+ }
814
+ }
815
+
816
+ const args = [cliPath, ...buildBossChatCliArgs(command, input, configResolution?.config || {}, runtimeLayout)];
817
+ const cwd = path.resolve(String(workspaceRoot || process.cwd()));
818
+ return new Promise((resolve) => {
819
+ const child = spawn(process.execPath, args, {
820
+ cwd,
821
+ env: {
822
+ ...process.env,
823
+ BOSS_CHAT_HOME: runtimeLayout.data_dir
824
+ },
825
+ windowsHide: true,
826
+ stdio: ["ignore", "pipe", "pipe"]
827
+ });
584
828
 
585
829
  let stdout = "";
586
830
  let stderr = "";
@@ -596,84 +840,103 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
596
840
  exitCode: -1,
597
841
  stdout,
598
842
  stderr,
599
- payload: {
600
- status: "FAILED",
601
- error: {
602
- code: "BOSS_CHAT_CLI_SPAWN_FAILED",
603
- message: error?.message || "无法启动 vendored boss-chat CLI。"
604
- }
605
- }
606
- });
607
- });
843
+ payload: {
844
+ status: "FAILED",
845
+ error: {
846
+ code: "BOSS_CHAT_CLI_SPAWN_FAILED",
847
+ message: error?.message || "无法启动 vendored boss-chat CLI。"
848
+ },
849
+ data_dir: runtimeLayout.data_dir,
850
+ data_dir_source: runtimeLayout.data_dir_source
851
+ }
852
+ });
853
+ });
608
854
  child.on("close", (code) => {
609
855
  const parsed = parseJsonOutput(stdout) || parseJsonOutput(stderr);
610
856
  if (parsed && typeof parsed === "object") {
611
857
  resolve({
612
858
  ok: Number(code) === 0 && String(parsed.status || "").toUpperCase() !== "FAILED",
613
- exitCode: Number.isInteger(code) ? code : 1,
614
- stdout,
615
- stderr,
616
- payload: parsed
617
- });
618
- return;
619
- }
620
- resolve({
859
+ exitCode: Number.isInteger(code) ? code : 1,
860
+ stdout,
861
+ stderr,
862
+ payload: withRuntimeDiagnostics(parsed, runtimeLayout)
863
+ });
864
+ return;
865
+ }
866
+ resolve({
621
867
  ok: Number(code) === 0,
622
868
  exitCode: Number.isInteger(code) ? code : 1,
623
869
  stdout,
624
- stderr,
625
- payload: Number(code) === 0
626
- ? {
627
- status: "OK",
628
- message: normalizeText(stdout) || `${command} 执行成功。`
629
- }
630
- : {
631
- status: "FAILED",
632
- error: {
633
- code: "BOSS_CHAT_CLI_EXECUTION_FAILED",
634
- message: normalizeText(stderr || stdout) || `${command} 执行失败。`
635
- }
636
- }
637
- });
638
- });
639
- });
640
- }
870
+ stderr,
871
+ payload: withRuntimeDiagnostics(
872
+ Number(code) === 0
873
+ ? {
874
+ status: "OK",
875
+ message: normalizeText(stdout) || `${command} 执行成功。`
876
+ }
877
+ : {
878
+ status: "FAILED",
879
+ error: {
880
+ code: "BOSS_CHAT_CLI_EXECUTION_FAILED",
881
+ message: normalizeText(stderr || stdout) || `${command} 执行失败。`
882
+ }
883
+ },
884
+ runtimeLayout
885
+ )
886
+ });
887
+ });
888
+ });
889
+ }
641
890
 
642
- export function getBossChatHealthCheck(workspaceRoot, input = {}) {
643
- const cliDir = resolveBossChatCliDir(workspaceRoot);
644
- const cliPath = resolveBossChatCliPath(workspaceRoot);
645
- const configResolution = resolveBossChatScreenConfig(workspaceRoot);
646
- const resolvedPort = parsePositiveInteger(input.port)
647
- || (configResolution.ok ? configResolution.config.debugPort : 9222);
648
- if (!cliDir || !cliPath) {
891
+ export function getBossChatHealthCheck(workspaceRoot, input = {}) {
892
+ const cliDir = resolveBossChatCliDir(workspaceRoot);
893
+ const cliPath = resolveBossChatCliPath(workspaceRoot);
894
+ const configResolution = resolveBossChatScreenConfig(workspaceRoot);
895
+ const runtimeLayout = resolveBossChatRuntimeLayout(workspaceRoot);
896
+ const resolvedPort = parsePositiveInteger(input.port)
897
+ || (configResolution.ok ? configResolution.config.debugPort : 9222);
898
+ if (!cliDir || !cliPath) {
649
899
  return {
650
900
  status: "FAILED",
651
- error: {
652
- code: "BOSS_CHAT_CLI_MISSING",
653
- message: "未找到 vendored boss-chat CLI。"
654
- }
655
- };
656
- }
657
- if (!configResolution.ok) {
901
+ error: {
902
+ code: "BOSS_CHAT_CLI_MISSING",
903
+ message: "未找到 vendored boss-chat CLI。"
904
+ },
905
+ data_dir: runtimeLayout.data_dir,
906
+ data_dir_source: runtimeLayout.data_dir_source,
907
+ legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
908
+ migration_pending: runtimeLayout.migration_pending
909
+ };
910
+ }
911
+ if (!configResolution.ok) {
658
912
  return {
659
913
  status: "FAILED",
660
- error: configResolution.error,
661
- config_path: configResolution.config_path,
662
- config_dir: configResolution.config_dir,
663
- cli_dir: cliDir,
664
- cli_path: cliPath
665
- };
666
- }
667
- return {
914
+ error: configResolution.error,
915
+ config_path: configResolution.config_path,
916
+ config_dir: configResolution.config_dir,
917
+ cli_dir: cliDir,
918
+ cli_path: cliPath,
919
+ data_dir: runtimeLayout.data_dir,
920
+ data_dir_source: runtimeLayout.data_dir_source,
921
+ legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
922
+ migration_pending: runtimeLayout.migration_pending
923
+ };
924
+ }
925
+ return {
668
926
  status: "OK",
669
927
  server: "boss-chat",
670
- cli_dir: cliDir,
671
- cli_path: cliPath,
672
- config_path: configResolution.config_path,
673
- debug_port: resolvedPort,
674
- shared_llm_config: true
675
- };
676
- }
928
+ cli_dir: cliDir,
929
+ cli_path: cliPath,
930
+ config_path: configResolution.config_path,
931
+ debug_port: resolvedPort,
932
+ shared_llm_config: true,
933
+ data_dir: runtimeLayout.data_dir,
934
+ data_dir_source: runtimeLayout.data_dir_source,
935
+ legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
936
+ migration_source_dir: runtimeLayout.migration_source_dir,
937
+ migration_pending: runtimeLayout.migration_pending
938
+ };
939
+ }
677
940
 
678
941
  export async function startBossChatRun({ workspaceRoot, input = {} }) {
679
942
  const missingFields = getMissingBossChatStartFields(input);