@reconcrap/boss-recommend-mcp 1.3.35 → 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 +3 -1
- package/package.json +1 -1
- package/src/boss-chat.js +123 -61
- package/src/test-boss-chat.js +30 -1
- 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/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();
|
|
@@ -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,
|