@reconcrap/boss-recommend-mcp 1.3.35 → 1.3.37
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 +3 -1
- package/package.json +1 -1
- package/src/boss-chat.js +123 -61
- package/src/pipeline.js +132 -14
- package/src/test-boss-chat.js +30 -1
- package/src/test-pipeline.js +66 -4
- package/vendor/boss-chat-cli/src/cli.js +120 -20
package/README.md
CHANGED
|
@@ -239,7 +239,9 @@ node src/cli.js chat run --job "算法工程师" --start-from unread --criteria
|
|
|
239
239
|
- `pause_boss_chat_run`
|
|
240
240
|
- `resume_boss_chat_run`
|
|
241
241
|
- `cancel_boss_chat_run`
|
|
242
|
-
- vendored `boss-chat` CLI 还支持 `--data-dir <path>` 与 `BOSS_CHAT_HOME
|
|
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`,便于定位是参数/环境变量/默认路径生效
|
|
243
245
|
|
|
244
246
|
chat-only 交互建议:
|
|
245
247
|
|
package/package.json
CHANGED
package/src/boss-chat.js
CHANGED
|
@@ -97,11 +97,30 @@ function isSafeBossChatLegacyWorkspaceRoot(workspaceRoot) {
|
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
function isUnsafeBossChatDataDir(targetPath) {
|
|
101
|
+
const resolved = path.resolve(String(targetPath || ""));
|
|
102
|
+
return isRootDirectory(resolved) || isSystemDirectoryWorkspaceRoot(resolved);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveBossChatDataDir() {
|
|
101
106
|
if (process.env.BOSS_CHAT_HOME) {
|
|
102
|
-
return
|
|
107
|
+
return {
|
|
108
|
+
data_dir: path.resolve(process.env.BOSS_CHAT_HOME),
|
|
109
|
+
data_dir_source: "env:BOSS_CHAT_HOME"
|
|
110
|
+
};
|
|
103
111
|
}
|
|
104
|
-
|
|
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;
|
|
105
124
|
}
|
|
106
125
|
|
|
107
126
|
export function getLegacyBossChatWorkspaceDataDir(workspaceRoot) {
|
|
@@ -110,7 +129,8 @@ export function getLegacyBossChatWorkspaceDataDir(workspaceRoot) {
|
|
|
110
129
|
}
|
|
111
130
|
|
|
112
131
|
export function resolveBossChatRuntimeLayout(workspaceRoot) {
|
|
113
|
-
const
|
|
132
|
+
const resolvedDataDir = resolveBossChatDataDir();
|
|
133
|
+
const dataDir = resolvedDataDir.data_dir;
|
|
114
134
|
const legacyWorkspaceDir = getLegacyBossChatWorkspaceDataDir(workspaceRoot);
|
|
115
135
|
const migrationSourceDir =
|
|
116
136
|
legacyWorkspaceDir && pathExists(legacyWorkspaceDir) && !pathExists(dataDir)
|
|
@@ -119,6 +139,7 @@ export function resolveBossChatRuntimeLayout(workspaceRoot) {
|
|
|
119
139
|
return {
|
|
120
140
|
workspace_root: workspaceRoot ? path.resolve(String(workspaceRoot)) : null,
|
|
121
141
|
data_dir: dataDir,
|
|
142
|
+
data_dir_source: resolvedDataDir.data_dir_source,
|
|
122
143
|
legacy_workspace_dir: legacyWorkspaceDir,
|
|
123
144
|
migration_source_dir: migrationSourceDir,
|
|
124
145
|
migration_pending: Boolean(migrationSourceDir),
|
|
@@ -144,6 +165,22 @@ export function ensureBossChatRuntimeReady(workspaceRoot) {
|
|
|
144
165
|
: ""
|
|
145
166
|
};
|
|
146
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
|
+
|
|
147
184
|
if (runtime.migration_source_dir) {
|
|
148
185
|
try {
|
|
149
186
|
fs.cpSync(runtime.migration_source_dir, runtime.data_dir, {
|
|
@@ -698,28 +735,39 @@ function buildBossChatCliArgs(command, input, resolvedConfig, runtimeLayout = nu
|
|
|
698
735
|
const runId = normalizeBossChatRunId(input);
|
|
699
736
|
args.push("--profile", normalizeText(input.profile) || "default");
|
|
700
737
|
args.push("--run-id", runId);
|
|
701
|
-
return args;
|
|
702
|
-
}
|
|
703
|
-
|
|
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
|
+
|
|
704
750
|
async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
751
|
+
const runtimeLayout = ensureBossChatRuntimeReady(workspaceRoot);
|
|
705
752
|
const cliPath = resolveBossChatCliPath(workspaceRoot);
|
|
706
753
|
if (!cliPath) {
|
|
707
|
-
return {
|
|
754
|
+
return {
|
|
708
755
|
ok: false,
|
|
709
756
|
exitCode: -1,
|
|
710
757
|
stdout: "",
|
|
711
758
|
stderr: "",
|
|
712
|
-
payload: {
|
|
713
|
-
status: "FAILED",
|
|
714
|
-
error: {
|
|
715
|
-
code: "BOSS_CHAT_CLI_MISSING",
|
|
716
|
-
message: "未找到 vendored boss-chat CLI。"
|
|
717
|
-
}
|
|
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
|
|
718
767
|
}
|
|
719
768
|
};
|
|
720
769
|
}
|
|
721
770
|
|
|
722
|
-
const runtimeLayout = ensureBossChatRuntimeReady(workspaceRoot);
|
|
723
771
|
const runtimeInitFailed = runtimeLayout.failed.some((item) => item.path === runtimeLayout.data_dir)
|
|
724
772
|
&& !pathExists(runtimeLayout.data_dir);
|
|
725
773
|
if (runtimeInitFailed) {
|
|
@@ -738,6 +786,7 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
738
786
|
.join("; ") || "无法初始化 boss-chat runtime 目录。"
|
|
739
787
|
},
|
|
740
788
|
data_dir: runtimeLayout.data_dir,
|
|
789
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
741
790
|
migration: runtimeLayout.migration
|
|
742
791
|
}
|
|
743
792
|
};
|
|
@@ -752,12 +801,14 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
752
801
|
exitCode: 1,
|
|
753
802
|
stdout: "",
|
|
754
803
|
stderr: "",
|
|
755
|
-
payload: {
|
|
756
|
-
status: "FAILED",
|
|
757
|
-
error: configResolution.error,
|
|
758
|
-
config_path: configResolution.config_path,
|
|
759
|
-
config_dir: configResolution.config_dir
|
|
760
|
-
|
|
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
|
+
}
|
|
761
812
|
};
|
|
762
813
|
}
|
|
763
814
|
}
|
|
@@ -765,12 +816,15 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
765
816
|
const args = [cliPath, ...buildBossChatCliArgs(command, input, configResolution?.config || {}, runtimeLayout)];
|
|
766
817
|
const cwd = path.resolve(String(workspaceRoot || process.cwd()));
|
|
767
818
|
return new Promise((resolve) => {
|
|
768
|
-
const child = spawn(process.execPath, args, {
|
|
769
|
-
cwd,
|
|
770
|
-
env:
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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
|
+
});
|
|
774
828
|
|
|
775
829
|
let stdout = "";
|
|
776
830
|
let stderr = "";
|
|
@@ -786,48 +840,53 @@ async function spawnBossChatCli({ workspaceRoot, command, input = {} }) {
|
|
|
786
840
|
exitCode: -1,
|
|
787
841
|
stdout,
|
|
788
842
|
stderr,
|
|
789
|
-
payload: {
|
|
790
|
-
status: "FAILED",
|
|
791
|
-
error: {
|
|
792
|
-
code: "BOSS_CHAT_CLI_SPAWN_FAILED",
|
|
793
|
-
message: error?.message || "无法启动 vendored boss-chat CLI。"
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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
|
+
});
|
|
798
854
|
child.on("close", (code) => {
|
|
799
855
|
const parsed = parseJsonOutput(stdout) || parseJsonOutput(stderr);
|
|
800
856
|
if (parsed && typeof parsed === "object") {
|
|
801
857
|
resolve({
|
|
802
858
|
ok: Number(code) === 0 && String(parsed.status || "").toUpperCase() !== "FAILED",
|
|
803
|
-
exitCode: Number.isInteger(code) ? code : 1,
|
|
804
|
-
stdout,
|
|
805
|
-
stderr,
|
|
806
|
-
payload: parsed
|
|
807
|
-
});
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
resolve({
|
|
859
|
+
exitCode: Number.isInteger(code) ? code : 1,
|
|
860
|
+
stdout,
|
|
861
|
+
stderr,
|
|
862
|
+
payload: withRuntimeDiagnostics(parsed, runtimeLayout)
|
|
863
|
+
});
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
resolve({
|
|
811
867
|
ok: Number(code) === 0,
|
|
812
868
|
exitCode: Number.isInteger(code) ? code : 1,
|
|
813
869
|
stdout,
|
|
814
|
-
stderr,
|
|
815
|
-
payload:
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
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
|
+
}
|
|
831
890
|
|
|
832
891
|
export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
833
892
|
const cliDir = resolveBossChatCliDir(workspaceRoot);
|
|
@@ -844,6 +903,7 @@ export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
|
844
903
|
message: "未找到 vendored boss-chat CLI。"
|
|
845
904
|
},
|
|
846
905
|
data_dir: runtimeLayout.data_dir,
|
|
906
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
847
907
|
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
848
908
|
migration_pending: runtimeLayout.migration_pending
|
|
849
909
|
};
|
|
@@ -857,6 +917,7 @@ export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
|
857
917
|
cli_dir: cliDir,
|
|
858
918
|
cli_path: cliPath,
|
|
859
919
|
data_dir: runtimeLayout.data_dir,
|
|
920
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
860
921
|
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
861
922
|
migration_pending: runtimeLayout.migration_pending
|
|
862
923
|
};
|
|
@@ -870,6 +931,7 @@ export function getBossChatHealthCheck(workspaceRoot, input = {}) {
|
|
|
870
931
|
debug_port: resolvedPort,
|
|
871
932
|
shared_llm_config: true,
|
|
872
933
|
data_dir: runtimeLayout.data_dir,
|
|
934
|
+
data_dir_source: runtimeLayout.data_dir_source,
|
|
873
935
|
legacy_workspace_dir: runtimeLayout.legacy_workspace_dir,
|
|
874
936
|
migration_source_dir: runtimeLayout.migration_source_dir,
|
|
875
937
|
migration_pending: runtimeLayout.migration_pending
|
package/src/pipeline.js
CHANGED
|
@@ -30,6 +30,15 @@ const SEARCH_NO_IFRAME_RETRY_DELAY_MS = 1200;
|
|
|
30
30
|
const MAX_SEARCH_FILTER_AUTO_RETRY_ATTEMPTS = 2;
|
|
31
31
|
const SEARCH_FILTER_AUTO_RETRY_DELAY_MS = 1200;
|
|
32
32
|
const BOSS_CHAT_FOLLOW_UP_POLL_MS = 1500;
|
|
33
|
+
const FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN = "passed_count";
|
|
34
|
+
const FOLLOW_UP_TARGET_COUNT_PASSED_LABEL = "通过筛选数";
|
|
35
|
+
const FOLLOW_UP_TARGET_COUNT_PASSED_ALIASES = new Set([
|
|
36
|
+
FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
|
|
37
|
+
"passed",
|
|
38
|
+
"通过筛选数",
|
|
39
|
+
"筛选通过数",
|
|
40
|
+
"通过数"
|
|
41
|
+
]);
|
|
33
42
|
const SEARCH_FILTER_RETRY_TOKENS = [
|
|
34
43
|
"FILTER_CONFIRM_FAILED",
|
|
35
44
|
"FILTER_DOM_CLASS_VERIFY_FAILED",
|
|
@@ -72,6 +81,75 @@ function normalizePipelineTargetCountValue(value) {
|
|
|
72
81
|
return normalizeTargetCountInput(value).publicValue;
|
|
73
82
|
}
|
|
74
83
|
|
|
84
|
+
function isFollowUpPassedTargetCountToken(value) {
|
|
85
|
+
const normalized = normalizeText(value).toLowerCase().replace(/\s+/g, "");
|
|
86
|
+
if (!normalized) return false;
|
|
87
|
+
return FOLLOW_UP_TARGET_COUNT_PASSED_ALIASES.has(normalized);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function normalizeFollowUpTargetCountInput(value) {
|
|
91
|
+
const normalized = normalizeTargetCountInput(value);
|
|
92
|
+
if (normalized.provided) {
|
|
93
|
+
return {
|
|
94
|
+
...normalized,
|
|
95
|
+
launchValue: normalized.publicValue,
|
|
96
|
+
passedCountMode: false
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (isFollowUpPassedTargetCountToken(value)) {
|
|
100
|
+
return {
|
|
101
|
+
provided: true,
|
|
102
|
+
targetCount: null,
|
|
103
|
+
cliArg: null,
|
|
104
|
+
publicValue: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
|
|
105
|
+
rawValue: value,
|
|
106
|
+
parseError: null,
|
|
107
|
+
launchValue: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
|
|
108
|
+
passedCountMode: true
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
...normalized,
|
|
113
|
+
launchValue: null,
|
|
114
|
+
passedCountMode: false
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function resolveFollowUpChatTargetCountForLaunch(targetCount, recommendSummary = null) {
|
|
119
|
+
if (isFollowUpPassedTargetCountToken(targetCount)) {
|
|
120
|
+
const passedCount = parsePositiveIntegerValue(recommendSummary?.passed_count);
|
|
121
|
+
if (passedCount) {
|
|
122
|
+
return {
|
|
123
|
+
ok: true,
|
|
124
|
+
target_count: passedCount,
|
|
125
|
+
resolved_from: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
|
|
126
|
+
passed_count: recommendSummary?.passed_count ?? null
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
code: "FOLLOW_UP_TARGET_COUNT_PASSED_UNAVAILABLE",
|
|
132
|
+
message: "boss-chat follow-up 选择了“通过筛选数”,但本次通过人数为空或 0。请改为正整数,或填写 all(扫到底)。",
|
|
133
|
+
passed_count: recommendSummary?.passed_count ?? null
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const normalized = normalizeTargetCountInput(targetCount);
|
|
137
|
+
if (normalized.provided) {
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
target_count: normalized.publicValue,
|
|
141
|
+
resolved_from: "explicit",
|
|
142
|
+
passed_count: recommendSummary?.passed_count ?? null
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
ok: false,
|
|
147
|
+
code: "FOLLOW_UP_TARGET_COUNT_INVALID",
|
|
148
|
+
message: normalized.parseError || "boss-chat follow-up target_count 无效。",
|
|
149
|
+
passed_count: recommendSummary?.passed_count ?? null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
75
153
|
function sleep(ms) {
|
|
76
154
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
77
155
|
}
|
|
@@ -394,13 +472,12 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
|
|
|
394
472
|
const defaultCriteria = normalizeText(defaults?.criteria || "");
|
|
395
473
|
const defaultStartFromRaw = normalizeText(defaults?.start_from || "").toLowerCase();
|
|
396
474
|
const defaultStartFrom = defaultStartFromRaw === "all" ? "all" : "unread";
|
|
397
|
-
const defaultTargetCount = normalizePipelineTargetCountValue(defaults?.target_count);
|
|
398
475
|
|
|
399
476
|
const explicitCriteria = normalizeText(raw.criteria);
|
|
400
477
|
const explicitStartFromRaw = normalizeText(raw.start_from).toLowerCase();
|
|
401
478
|
const explicitStartFrom = explicitStartFromRaw === "all" ? "all" : explicitStartFromRaw === "unread" ? "unread" : "";
|
|
402
|
-
const explicitTarget =
|
|
403
|
-
const explicitTargetCount = explicitTarget.
|
|
479
|
+
const explicitTarget = normalizeFollowUpTargetCountInput(raw.target_count);
|
|
480
|
+
const explicitTargetCount = explicitTarget.launchValue;
|
|
404
481
|
|
|
405
482
|
const hasExplicitCriteria = Boolean(explicitCriteria);
|
|
406
483
|
const hasExplicitStartFrom = Boolean(explicitStartFrom);
|
|
@@ -408,14 +485,15 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
|
|
|
408
485
|
|
|
409
486
|
const criteria = explicitCriteria || defaultCriteria;
|
|
410
487
|
const startFrom = explicitStartFrom || defaultStartFrom;
|
|
411
|
-
const targetCount = explicitTargetCount ||
|
|
488
|
+
const targetCount = explicitTargetCount || FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN;
|
|
489
|
+
const targetCountSummaryValue = explicitTarget.publicValue || FOLLOW_UP_TARGET_COUNT_PASSED_LABEL;
|
|
412
490
|
|
|
413
491
|
const profile = normalizeText(raw.profile) || "default";
|
|
414
492
|
const summary = {
|
|
415
493
|
profile,
|
|
416
494
|
criteria: criteria || null,
|
|
417
495
|
start_from: startFrom || null,
|
|
418
|
-
target_count:
|
|
496
|
+
target_count: targetCountSummaryValue,
|
|
419
497
|
dry_run: raw.dry_run === true,
|
|
420
498
|
no_state: raw.no_state === true,
|
|
421
499
|
safe_pacing: typeof raw.safe_pacing === "boolean" ? raw.safe_pacing : null,
|
|
@@ -446,21 +524,39 @@ function normalizeFollowUpChatInput(followUp = null, defaults = null) {
|
|
|
446
524
|
});
|
|
447
525
|
}
|
|
448
526
|
if (!hasExplicitTargetCount) {
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
chat: {
|
|
454
|
-
target_count: "all"
|
|
455
|
-
}
|
|
527
|
+
const recommendedTargetCountPatch = {
|
|
528
|
+
follow_up: {
|
|
529
|
+
chat: {
|
|
530
|
+
target_count: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL
|
|
456
531
|
}
|
|
457
532
|
}
|
|
533
|
+
};
|
|
534
|
+
const targetCountHints = buildTargetCountCompatibilityHints({
|
|
535
|
+
argumentName: "follow_up.chat.target_count",
|
|
536
|
+
recommendedArgumentPatch: recommendedTargetCountPatch
|
|
458
537
|
});
|
|
459
538
|
missing_fields.push("follow_up.chat.target_count");
|
|
460
539
|
pending_questions.push({
|
|
461
540
|
...targetCountHints,
|
|
541
|
+
answer_format: `follow_up.chat.target_count = "${FOLLOW_UP_TARGET_COUNT_PASSED_LABEL}" | 正整数 | "all"`,
|
|
542
|
+
recommended_value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
|
|
543
|
+
recommended_argument_patch: recommendedTargetCountPatch,
|
|
544
|
+
canonical_passed_count_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
|
|
545
|
+
accepted_examples: [
|
|
546
|
+
FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
|
|
547
|
+
...targetCountHints.accepted_examples
|
|
548
|
+
],
|
|
549
|
+
options: [
|
|
550
|
+
{
|
|
551
|
+
label: `通过筛选数(推荐)`,
|
|
552
|
+
value: FOLLOW_UP_TARGET_COUNT_PASSED_LABEL,
|
|
553
|
+
canonical_value: FOLLOW_UP_TARGET_COUNT_PASSED_TOKEN,
|
|
554
|
+
argument_patch: recommendedTargetCountPatch
|
|
555
|
+
},
|
|
556
|
+
...(Array.isArray(targetCountHints.options) ? targetCountHints.options : [])
|
|
557
|
+
],
|
|
462
558
|
field: "follow_up.chat.target_count",
|
|
463
|
-
question: "请填写 boss-chat follow-up
|
|
559
|
+
question: "请填写 boss-chat follow-up 目标人数。默认建议填写“通过筛选数”;若要扫到底,请在 follow_up.chat.target_count 里字面填写 \"all\"。",
|
|
464
560
|
value: summary.target_count,
|
|
465
561
|
...(explicitTarget.rawValue !== undefined ? { received_target_count: explicitTarget.rawValue } : {}),
|
|
466
562
|
...(explicitTarget.parseError ? { target_count_parse_error: explicitTarget.parseError } : {})
|
|
@@ -857,10 +953,32 @@ async function runBossChatFollowUpPhase({
|
|
|
857
953
|
cancelChatRun
|
|
858
954
|
}) {
|
|
859
955
|
const recommendSummary = recommendResult?.result || null;
|
|
860
|
-
const
|
|
956
|
+
const requestedChatInput = buildResolvedFollowUpChatInput(followUpChat, {
|
|
861
957
|
selectedJob,
|
|
862
958
|
debugPort
|
|
863
959
|
});
|
|
960
|
+
const targetCountResolution = resolveFollowUpChatTargetCountForLaunch(
|
|
961
|
+
requestedChatInput.target_count,
|
|
962
|
+
recommendSummary
|
|
963
|
+
);
|
|
964
|
+
if (!targetCountResolution.ok) {
|
|
965
|
+
return buildFollowUpFailedResponse(
|
|
966
|
+
targetCountResolution.code || "BOSS_CHAT_FOLLOW_UP_TARGET_COUNT_INVALID",
|
|
967
|
+
targetCountResolution.message || "boss-chat follow-up target_count 无法解析。",
|
|
968
|
+
recommendResult,
|
|
969
|
+
{
|
|
970
|
+
enabled: true,
|
|
971
|
+
input: requestedChatInput,
|
|
972
|
+
target_count_resolution: targetCountResolution
|
|
973
|
+
}
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
const resolvedChatInput = {
|
|
977
|
+
...requestedChatInput,
|
|
978
|
+
target_count: targetCountResolution.target_count,
|
|
979
|
+
target_count_source: targetCountResolution.resolved_from,
|
|
980
|
+
target_count_requested: requestedChatInput.target_count
|
|
981
|
+
};
|
|
864
982
|
let chatRunId = normalizeText(resume?.chat_run_id || "");
|
|
865
983
|
const resumeFromChatPhase = resume?.resume === true && normalizeText(resume?.follow_up_phase) === "chat_followup";
|
|
866
984
|
let pauseRequested = false;
|
package/src/test-boss-chat.js
CHANGED
|
@@ -244,6 +244,7 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
244
244
|
assert.equal(health.status, "OK");
|
|
245
245
|
assert.equal(health.shared_llm_config, true);
|
|
246
246
|
assert.equal(health.debug_port, 9666);
|
|
247
|
+
assert.equal(health.data_dir_source, "env:BOSS_CHAT_HOME");
|
|
247
248
|
assert.equal(health.data_dir, getTestChatDataDir(workspaceRoot));
|
|
248
249
|
assert.equal(health.legacy_workspace_dir, path.join(workspaceRoot, ".boss-chat"));
|
|
249
250
|
assert.equal(health.migration_pending, false);
|
|
@@ -526,18 +527,45 @@ function testVendorBossChatCliShouldResolveExplicitDataDir() {
|
|
|
526
527
|
const cwd = path.join(path.parse(process.cwd()).root, "workspace");
|
|
527
528
|
const args = vendorCliTestables.parseArgs(["start-run", "--data-dir", "/tmp/boss-chat-data"]);
|
|
528
529
|
assert.equal(args.dataDir, "/tmp/boss-chat-data");
|
|
530
|
+
const explicitResolved = vendorCliTestables.resolveDataDirDetails(args, { BOSS_CHAT_HOME: "/tmp/ignored" }, cwd);
|
|
531
|
+
assert.equal(explicitResolved.source, "arg:data-dir");
|
|
532
|
+
assert.equal(explicitResolved.path, path.resolve("/tmp/boss-chat-data"));
|
|
529
533
|
assert.equal(
|
|
530
534
|
vendorCliTestables.resolveDataDir(args, { BOSS_CHAT_HOME: "/tmp/ignored" }, cwd),
|
|
531
535
|
path.resolve("/tmp/boss-chat-data")
|
|
532
536
|
);
|
|
537
|
+
const envResolved = vendorCliTestables.resolveDataDirDetails({}, { BOSS_CHAT_HOME: "/tmp/from-env" }, cwd);
|
|
538
|
+
assert.equal(envResolved.source, "env:BOSS_CHAT_HOME");
|
|
539
|
+
assert.equal(envResolved.path, path.resolve("/tmp/from-env"));
|
|
533
540
|
assert.equal(
|
|
534
541
|
vendorCliTestables.resolveDataDir({}, { BOSS_CHAT_HOME: "/tmp/from-env" }, cwd),
|
|
535
542
|
path.resolve("/tmp/from-env")
|
|
536
543
|
);
|
|
544
|
+
const defaultResolved = vendorCliTestables.resolveDataDirDetails({}, {}, cwd);
|
|
545
|
+
assert.equal(defaultResolved.source, "default:user_home");
|
|
546
|
+
assert.equal(defaultResolved.path, path.join(os.homedir(), ".boss-recommend-mcp", "boss-chat"));
|
|
537
547
|
assert.equal(
|
|
538
548
|
vendorCliTestables.resolveDataDir({}, {}, cwd),
|
|
539
|
-
path.join(
|
|
549
|
+
path.join(os.homedir(), ".boss-recommend-mcp", "boss-chat")
|
|
540
550
|
);
|
|
551
|
+
|
|
552
|
+
const unsafeRoot = vendorCliTestables.validateDataDir(path.parse(process.cwd()).root);
|
|
553
|
+
assert.equal(unsafeRoot.ok, false);
|
|
554
|
+
assert.equal(unsafeRoot.code, "UNSAFE_DATA_DIR");
|
|
555
|
+
assert.equal(unsafeRoot.message.includes("Refusing unsafe boss-chat data dir"), true);
|
|
556
|
+
|
|
557
|
+
const safePath = vendorCliTestables.validateDataDir(path.join(os.homedir(), ".boss-recommend-mcp", "boss-chat"));
|
|
558
|
+
assert.equal(safePath.ok, true);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function testVendorBossChatCliShouldUseRecommendHomeForDefaultDataDir() {
|
|
562
|
+
const resolved = vendorCliTestables.resolveDataDirDetails(
|
|
563
|
+
{},
|
|
564
|
+
{ BOSS_RECOMMEND_HOME: "/tmp/recommend-home" },
|
|
565
|
+
path.join(path.parse(process.cwd()).root, "workspace")
|
|
566
|
+
);
|
|
567
|
+
assert.equal(resolved.source, "default:env:BOSS_RECOMMEND_HOME");
|
|
568
|
+
assert.equal(resolved.path, path.resolve("/tmp/recommend-home/boss-chat"));
|
|
541
569
|
}
|
|
542
570
|
|
|
543
571
|
async function testBossChatPageShouldTreatBlankChatShellAsOnChatPage() {
|
|
@@ -3019,6 +3047,7 @@ async function main() {
|
|
|
3019
3047
|
await testBossChatMcpToolsShouldValidateAndRoute();
|
|
3020
3048
|
await testBossChatCliShouldSupportRunAndFollowUpParsing();
|
|
3021
3049
|
testVendorBossChatCliShouldResolveExplicitDataDir();
|
|
3050
|
+
testVendorBossChatCliShouldUseRecommendHomeForDefaultDataDir();
|
|
3022
3051
|
await testVendorBossChatCliShouldWaitForHydratedChatShell();
|
|
3023
3052
|
await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
|
|
3024
3053
|
testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
|
package/src/test-pipeline.js
CHANGED
|
@@ -2183,8 +2183,9 @@ async function testFollowUpChatMissingFieldsShouldExposeRecommendDefaults() {
|
|
|
2183
2183
|
const targetCountQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
|
|
2184
2184
|
assert.equal(criteriaQuestion?.value, "默认沿用 recommend 的筛选条件");
|
|
2185
2185
|
assert.equal(startFromQuestion?.value, "unread");
|
|
2186
|
-
assert.equal(targetCountQuestion?.value,
|
|
2187
|
-
assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "
|
|
2186
|
+
assert.equal(targetCountQuestion?.value, "通过筛选数");
|
|
2187
|
+
assert.equal(targetCountQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
|
|
2188
|
+
assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes("通过筛选数(推荐)")), true);
|
|
2188
2189
|
assert.equal(targetCountQuestion?.options?.some((item) => item.label.includes('follow_up.chat.target_count="all"')), true);
|
|
2189
2190
|
}
|
|
2190
2191
|
|
|
@@ -2225,7 +2226,9 @@ async function testFollowUpChatMissingTargetCountShouldNeedInput() {
|
|
|
2225
2226
|
assert.equal(result.missing_fields.includes("follow_up.chat.target_count"), true);
|
|
2226
2227
|
const targetQuestion = result.pending_questions.find((item) => item.field === "follow_up.chat.target_count");
|
|
2227
2228
|
assert.equal(Boolean(targetQuestion), true);
|
|
2228
|
-
assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "
|
|
2229
|
+
assert.equal(targetQuestion.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
|
|
2230
|
+
assert.equal(targetQuestion.options?.some((item) => item.value === "通过筛选数"), true);
|
|
2231
|
+
assert.equal(targetQuestion.options?.some((item) => item.value === "all"), true);
|
|
2229
2232
|
}
|
|
2230
2233
|
|
|
2231
2234
|
async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics() {
|
|
@@ -2248,7 +2251,8 @@ async function testFollowUpChatInvalidTargetCountShouldNeedInputWithDiagnostics(
|
|
|
2248
2251
|
assert.equal(targetQuestion?.received_target_count, "not a target");
|
|
2249
2252
|
assert.equal(Boolean(targetQuestion?.target_count_parse_error), true);
|
|
2250
2253
|
assert.equal(targetQuestion?.accepted_examples.includes("all"), true);
|
|
2251
|
-
assert.equal(targetQuestion?.
|
|
2254
|
+
assert.equal(targetQuestion?.accepted_examples.includes("通过筛选数"), true);
|
|
2255
|
+
assert.equal(targetQuestion?.recommended_argument_patch?.follow_up?.chat?.target_count, "通过筛选数");
|
|
2252
2256
|
}
|
|
2253
2257
|
|
|
2254
2258
|
async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
|
|
@@ -2317,6 +2321,63 @@ async function testFollowUpChatAllTargetCountShouldLaunchUnlimited() {
|
|
|
2317
2321
|
assert.equal(result.follow_up?.chat?.target_count, "all");
|
|
2318
2322
|
}
|
|
2319
2323
|
|
|
2324
|
+
async function testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount() {
|
|
2325
|
+
let capturedChatInput = null;
|
|
2326
|
+
const result = await runRecommendPipeline(
|
|
2327
|
+
{
|
|
2328
|
+
workspaceRoot: process.cwd(),
|
|
2329
|
+
instruction: "test",
|
|
2330
|
+
confirmation: createJobConfirmedConfirmation(),
|
|
2331
|
+
overrides: {},
|
|
2332
|
+
followUp: createFollowUpChat({ target_count: "通过筛选数" })
|
|
2333
|
+
},
|
|
2334
|
+
{
|
|
2335
|
+
parseRecommendInstruction: () => createParsed(),
|
|
2336
|
+
runPipelinePreflight: () => ({ ok: true, checks: [], debug_port: 9555 }),
|
|
2337
|
+
ensureBossRecommendPageReady: async () => ({ ok: true, state: "RECOMMEND_READY", page_state: {} }),
|
|
2338
|
+
listRecommendJobs: async () => createJobListResult(),
|
|
2339
|
+
runRecommendSearchCli: async () => ({
|
|
2340
|
+
ok: true,
|
|
2341
|
+
summary: {
|
|
2342
|
+
candidate_count: 6,
|
|
2343
|
+
applied_filters: {},
|
|
2344
|
+
page_state: {}
|
|
2345
|
+
}
|
|
2346
|
+
}),
|
|
2347
|
+
runRecommendScreenCli: async () => ({
|
|
2348
|
+
ok: true,
|
|
2349
|
+
summary: {
|
|
2350
|
+
processed_count: 6,
|
|
2351
|
+
passed_count: 2,
|
|
2352
|
+
skipped_count: 0
|
|
2353
|
+
}
|
|
2354
|
+
}),
|
|
2355
|
+
startBossChatRun: async ({ input }) => {
|
|
2356
|
+
capturedChatInput = input;
|
|
2357
|
+
return {
|
|
2358
|
+
status: "ACCEPTED",
|
|
2359
|
+
run_id: "chat-run-pass-count",
|
|
2360
|
+
message: "chat started"
|
|
2361
|
+
};
|
|
2362
|
+
},
|
|
2363
|
+
getBossChatRun: async () => ({
|
|
2364
|
+
status: "COMPLETED",
|
|
2365
|
+
run: {
|
|
2366
|
+
runId: "chat-run-pass-count",
|
|
2367
|
+
state: "completed",
|
|
2368
|
+
lastMessage: "chat completed",
|
|
2369
|
+
progress: { processed: 2, matched: 2 }
|
|
2370
|
+
}
|
|
2371
|
+
})
|
|
2372
|
+
}
|
|
2373
|
+
);
|
|
2374
|
+
|
|
2375
|
+
assert.equal(result.status, "COMPLETED");
|
|
2376
|
+
assert.equal(capturedChatInput?.target_count, 2);
|
|
2377
|
+
assert.equal(result.follow_up?.chat?.input?.target_count, 2);
|
|
2378
|
+
assert.equal(result.follow_up?.chat?.input?.target_count_requested, "passed_count");
|
|
2379
|
+
}
|
|
2380
|
+
|
|
2320
2381
|
async function testFinalReviewShouldIncludeFollowUpChatSummary() {
|
|
2321
2382
|
const result = await runRecommendPipeline(
|
|
2322
2383
|
{
|
|
@@ -2576,6 +2637,7 @@ async function main() {
|
|
|
2576
2637
|
await testFinalReviewShouldIncludeFollowUpChatSummary();
|
|
2577
2638
|
await testCompletedPipelineShouldRunChatFollowUp();
|
|
2578
2639
|
await testFollowUpChatAllTargetCountShouldLaunchUnlimited();
|
|
2640
|
+
await testFollowUpChatPassedTargetCountShouldLaunchWithPassedCount();
|
|
2579
2641
|
await testCompletedPipelineShouldFailWhenChatLaunchFails();
|
|
2580
2642
|
await testCompletedPipelineShouldFailWhenChatRunFails();
|
|
2581
2643
|
console.log("pipeline tests passed");
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { spawn } from 'node:child_process';
|
|
3
|
-
import { appendFile, mkdir } from 'node:fs/promises';
|
|
4
|
-
import
|
|
5
|
-
import
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import process from 'node:process';
|
|
6
7
|
import * as readlineCore from 'node:readline';
|
|
7
8
|
import readline from 'node:readline/promises';
|
|
8
9
|
import util from 'node:util';
|
|
@@ -301,12 +302,82 @@ function parseArgs(argv) {
|
|
|
301
302
|
return args;
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
function
|
|
305
|
+
function isRootDirectory(targetPath) {
|
|
306
|
+
const resolved = path.resolve(String(targetPath || ''));
|
|
307
|
+
const parsed = path.parse(resolved);
|
|
308
|
+
return resolved.toLowerCase() === String(parsed.root || '').toLowerCase();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isSystemDirectoryPath(targetPath) {
|
|
312
|
+
const resolved = path.resolve(String(targetPath || ''));
|
|
313
|
+
const normalized = resolved.replace(/\\/g, '/').toLowerCase();
|
|
314
|
+
if (process.platform === 'win32') {
|
|
315
|
+
return (
|
|
316
|
+
normalized.endsWith('/windows')
|
|
317
|
+
|| normalized.endsWith('/windows/system32')
|
|
318
|
+
|| normalized.endsWith('/windows/syswow64')
|
|
319
|
+
|| normalized.endsWith('/program files')
|
|
320
|
+
|| normalized.endsWith('/program files (x86)')
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return (
|
|
324
|
+
normalized === '/system'
|
|
325
|
+
|| normalized.startsWith('/system/')
|
|
326
|
+
|| normalized === '/usr'
|
|
327
|
+
|| normalized.startsWith('/usr/')
|
|
328
|
+
|| normalized === '/bin'
|
|
329
|
+
|| normalized.startsWith('/bin/')
|
|
330
|
+
|| normalized === '/sbin'
|
|
331
|
+
|| normalized.startsWith('/sbin/')
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function resolveDefaultDataDir(env = process.env) {
|
|
336
|
+
const stateHomeRaw = String(env?.BOSS_RECOMMEND_HOME || '').trim();
|
|
337
|
+
const stateHome = stateHomeRaw
|
|
338
|
+
? path.resolve(stateHomeRaw)
|
|
339
|
+
: path.join(os.homedir(), '.boss-recommend-mcp');
|
|
340
|
+
const source = stateHomeRaw
|
|
341
|
+
? 'default:env:BOSS_RECOMMEND_HOME'
|
|
342
|
+
: 'default:user_home';
|
|
343
|
+
return {
|
|
344
|
+
path: path.join(stateHome, 'boss-chat'),
|
|
345
|
+
source,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function resolveDataDirDetails(args = {}, env = process.env, cwd = process.cwd()) {
|
|
305
350
|
const explicit = String(args?.dataDir || '').trim();
|
|
306
|
-
if (explicit)
|
|
351
|
+
if (explicit) {
|
|
352
|
+
return {
|
|
353
|
+
path: path.resolve(explicit),
|
|
354
|
+
source: 'arg:data-dir',
|
|
355
|
+
};
|
|
356
|
+
}
|
|
307
357
|
const fromEnv = String(env?.BOSS_CHAT_HOME || '').trim();
|
|
308
|
-
if (fromEnv)
|
|
309
|
-
|
|
358
|
+
if (fromEnv) {
|
|
359
|
+
return {
|
|
360
|
+
path: path.resolve(fromEnv),
|
|
361
|
+
source: 'env:BOSS_CHAT_HOME',
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
return resolveDefaultDataDir(env);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function resolveDataDir(args = {}, env = process.env, cwd = process.cwd()) {
|
|
368
|
+
return resolveDataDirDetails(args, env, cwd).path;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function validateDataDir(targetPath) {
|
|
372
|
+
const resolved = path.resolve(String(targetPath || ''));
|
|
373
|
+
if (isRootDirectory(resolved) || isSystemDirectoryPath(resolved)) {
|
|
374
|
+
return {
|
|
375
|
+
ok: false,
|
|
376
|
+
code: 'UNSAFE_DATA_DIR',
|
|
377
|
+
message: `Refusing unsafe boss-chat data dir: ${resolved}. Please use --data-dir or BOSS_CHAT_HOME to a writable user directory.`,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
return { ok: true };
|
|
310
381
|
}
|
|
311
382
|
|
|
312
383
|
function printUsage() {
|
|
@@ -323,7 +394,7 @@ function printUsage() {
|
|
|
323
394
|
console.log('');
|
|
324
395
|
console.log('Common options:');
|
|
325
396
|
console.log(' --profile <name> Profile name (default: default)');
|
|
326
|
-
console.log(' --data-dir <path> Runtime data dir (default: $BOSS_CHAT_HOME or
|
|
397
|
+
console.log(' --data-dir <path> Runtime data dir (default: $BOSS_CHAT_HOME or ~/.boss-recommend-mcp/boss-chat)');
|
|
327
398
|
console.log(' --json JSON output for agent integration');
|
|
328
399
|
console.log(' --run-id <id> Target async run_id (for get/pause/resume/cancel)');
|
|
329
400
|
console.log('');
|
|
@@ -1527,7 +1598,27 @@ async function executeRunCommand(args, dataDir) {
|
|
|
1527
1598
|
|
|
1528
1599
|
async function main() {
|
|
1529
1600
|
const args = parseArgs(process.argv.slice(2));
|
|
1530
|
-
const
|
|
1601
|
+
const resolvedDataDir = resolveDataDirDetails(args);
|
|
1602
|
+
const dataDir = resolvedDataDir.path;
|
|
1603
|
+
const dataDirValidation = validateDataDir(dataDir);
|
|
1604
|
+
if (!dataDirValidation.ok) {
|
|
1605
|
+
const failedPayload = {
|
|
1606
|
+
status: 'FAILED',
|
|
1607
|
+
error: {
|
|
1608
|
+
code: dataDirValidation.code,
|
|
1609
|
+
message: dataDirValidation.message,
|
|
1610
|
+
},
|
|
1611
|
+
data_dir: dataDir,
|
|
1612
|
+
data_dir_source: resolvedDataDir.source,
|
|
1613
|
+
};
|
|
1614
|
+
if (args.json) {
|
|
1615
|
+
console.log(JSON.stringify(failedPayload));
|
|
1616
|
+
} else {
|
|
1617
|
+
console.error(failedPayload.error.message);
|
|
1618
|
+
}
|
|
1619
|
+
process.exitCode = 1;
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1531
1622
|
await mkdir(dataDir, { recursive: true });
|
|
1532
1623
|
|
|
1533
1624
|
if (args.command === 'help') {
|
|
@@ -1562,19 +1653,28 @@ async function main() {
|
|
|
1562
1653
|
break;
|
|
1563
1654
|
default:
|
|
1564
1655
|
printUsage();
|
|
1565
|
-
process.exitCode = 1;
|
|
1566
|
-
return;
|
|
1567
|
-
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1656
|
+
process.exitCode = 1;
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
if (payload && typeof payload === 'object') {
|
|
1661
|
+
payload = {
|
|
1662
|
+
...payload,
|
|
1663
|
+
data_dir: dataDir,
|
|
1664
|
+
data_dir_source: resolvedDataDir.source,
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
outputCommandResult(args, payload);
|
|
1668
|
+
if (payload?.status === 'FAILED') {
|
|
1669
|
+
process.exitCode = 1;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1574
1672
|
|
|
1575
1673
|
export const __testables = {
|
|
1576
1674
|
parseArgs,
|
|
1675
|
+
resolveDataDirDetails,
|
|
1577
1676
|
resolveDataDir,
|
|
1677
|
+
validateDataDir,
|
|
1578
1678
|
connectBossChatPage,
|
|
1579
1679
|
hasHydratedChatShell,
|
|
1580
1680
|
promptRunProfile,
|