@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 +14 -7
- package/package.json +1 -1
- package/src/boss-chat.js +383 -120
- package/src/cli.js +34 -24
- package/src/test-boss-chat.js +242 -84
- package/vendor/boss-chat-cli/src/cli.js +160 -45
package/src/cli.js
CHANGED
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
} from "./adapters.js";
|
|
19
19
|
import {
|
|
20
20
|
cancelBossChatRun,
|
|
21
|
+
ensureBossChatRuntimeReady,
|
|
21
22
|
getBossChatHealthCheck,
|
|
22
23
|
getBossChatRun,
|
|
23
24
|
pauseBossChatRun,
|
|
24
25
|
prepareBossChatRun,
|
|
26
|
+
resolveBossChatRuntimeLayout,
|
|
25
27
|
resumeBossChatRun,
|
|
26
28
|
startBossChatRun
|
|
27
29
|
} from "./boss-chat.js";
|
|
@@ -742,30 +744,22 @@ function ensureUserConfig(options = {}) {
|
|
|
742
744
|
throw lastError || new Error("No writable target for screening-config.json");
|
|
743
745
|
}
|
|
744
746
|
|
|
745
|
-
function getBossChatDataDir(workspaceRoot) {
|
|
746
|
-
return path.join(path.resolve(String(workspaceRoot || process.cwd())), ".boss-chat");
|
|
747
|
-
}
|
|
748
|
-
|
|
749
747
|
function collectRuntimeDirectories(options = {}) {
|
|
750
748
|
const workspaceRoot = getWorkspaceRoot(options);
|
|
751
749
|
const stateHome = getStateHome();
|
|
752
|
-
const
|
|
750
|
+
const runtime = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
751
|
+
const bossChatRoot = runtime.data_dir;
|
|
753
752
|
const recommendRuntimeDirs = [
|
|
754
753
|
stateHome,
|
|
755
754
|
path.join(stateHome, "runs")
|
|
756
755
|
];
|
|
757
|
-
const bossChatRuntimeDirs = [
|
|
758
|
-
bossChatRoot,
|
|
759
|
-
path.join(bossChatRoot, "logs"),
|
|
760
|
-
path.join(bossChatRoot, "runs"),
|
|
761
|
-
path.join(bossChatRoot, "profiles"),
|
|
762
|
-
path.join(bossChatRoot, "reports"),
|
|
763
|
-
path.join(bossChatRoot, "artifacts")
|
|
764
|
-
];
|
|
756
|
+
const bossChatRuntimeDirs = runtime.directories || [bossChatRoot];
|
|
765
757
|
return {
|
|
766
758
|
workspaceRoot,
|
|
767
759
|
stateHome,
|
|
768
760
|
bossChatRoot,
|
|
761
|
+
legacyBossChatRoot: runtime.legacy_workspace_dir,
|
|
762
|
+
migrationPending: runtime.migration_pending,
|
|
769
763
|
directories: dedupePaths([
|
|
770
764
|
...recommendRuntimeDirs,
|
|
771
765
|
...bossChatRuntimeDirs
|
|
@@ -774,19 +768,20 @@ function collectRuntimeDirectories(options = {}) {
|
|
|
774
768
|
}
|
|
775
769
|
|
|
776
770
|
function ensureRuntimeDirectories(options = {}) {
|
|
777
|
-
const { workspaceRoot, stateHome
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
const
|
|
771
|
+
const { workspaceRoot, stateHome } = collectRuntimeDirectories(options);
|
|
772
|
+
const runtime = ensureBossChatRuntimeReady(workspaceRoot);
|
|
773
|
+
const recommendCreated = [];
|
|
774
|
+
const recommendExisted = [];
|
|
775
|
+
const failed = [...runtime.failed];
|
|
781
776
|
|
|
782
|
-
for (const directory of
|
|
777
|
+
for (const directory of [stateHome, path.join(stateHome, "runs")]) {
|
|
783
778
|
try {
|
|
784
779
|
const existedBefore = fs.existsSync(directory);
|
|
785
780
|
ensureDir(directory);
|
|
786
781
|
if (existedBefore) {
|
|
787
|
-
|
|
782
|
+
recommendExisted.push(directory);
|
|
788
783
|
} else {
|
|
789
|
-
|
|
784
|
+
recommendCreated.push(directory);
|
|
790
785
|
}
|
|
791
786
|
} catch (error) {
|
|
792
787
|
failed.push({
|
|
@@ -799,9 +794,12 @@ function ensureRuntimeDirectories(options = {}) {
|
|
|
799
794
|
return {
|
|
800
795
|
workspaceRoot,
|
|
801
796
|
stateHome,
|
|
802
|
-
bossChatRoot,
|
|
803
|
-
|
|
804
|
-
|
|
797
|
+
bossChatRoot: runtime.data_dir,
|
|
798
|
+
legacyBossChatRoot: runtime.legacy_workspace_dir,
|
|
799
|
+
migrationPending: runtime.migration_pending,
|
|
800
|
+
migration: runtime.migration,
|
|
801
|
+
created: dedupePaths([...recommendCreated, ...runtime.created]),
|
|
802
|
+
existed: dedupePaths([...recommendExisted, ...runtime.existed]),
|
|
805
803
|
failed
|
|
806
804
|
};
|
|
807
805
|
}
|
|
@@ -1343,10 +1341,13 @@ function printPaths() {
|
|
|
1343
1341
|
const codexHome = getCodexHome();
|
|
1344
1342
|
const stateHome = getStateHome();
|
|
1345
1343
|
const calibrationResolution = getFeaturedCalibrationResolution(process.cwd());
|
|
1344
|
+
const bossChatRuntime = resolveBossChatRuntimeLayout(getWorkspaceRoot({}));
|
|
1346
1345
|
console.log(`package_root=${packageRoot}`);
|
|
1347
1346
|
console.log(`skill_sources=${bundledSkillNames.map((name) => getSkillSourceDir(name)).join(" | ")}`);
|
|
1348
1347
|
console.log(`codex_home=${codexHome}`);
|
|
1349
1348
|
console.log(`state_home=${stateHome}`);
|
|
1349
|
+
console.log(`boss_chat_runtime=${bossChatRuntime.data_dir}`);
|
|
1350
|
+
console.log(`boss_chat_legacy_workspace_runtime=${bossChatRuntime.legacy_workspace_dir || ""}`);
|
|
1350
1351
|
console.log(`skill_targets=${bundledSkillNames.map((name) => path.join(codexHome, "skills", name)).join(" | ")}`);
|
|
1351
1352
|
console.log(`config_target=${getUserConfigPath()}`);
|
|
1352
1353
|
console.log(`legacy_config_target=${getLegacyUserConfigPath()}`);
|
|
@@ -1408,6 +1409,9 @@ function installAll(options = {}) {
|
|
|
1408
1409
|
);
|
|
1409
1410
|
console.log(`- recommend runtime: ${runtimeDirsResult.stateHome}`);
|
|
1410
1411
|
console.log(`- boss-chat runtime: ${runtimeDirsResult.bossChatRoot}`);
|
|
1412
|
+
if (runtimeDirsResult.migration?.performed) {
|
|
1413
|
+
console.log(`- boss-chat migration: ${runtimeDirsResult.migration.message}`);
|
|
1414
|
+
}
|
|
1411
1415
|
if (runtimeDirsResult.failed.length > 0) {
|
|
1412
1416
|
for (const item of runtimeDirsResult.failed) {
|
|
1413
1417
|
console.warn(`Runtime dir warning: ${item.path} -> ${item.message}`);
|
|
@@ -1628,6 +1632,9 @@ export async function runCli(argv = process.argv) {
|
|
|
1628
1632
|
);
|
|
1629
1633
|
console.log(`- recommend runtime: ${runtimeDirsResult.stateHome}`);
|
|
1630
1634
|
console.log(`- boss-chat runtime: ${runtimeDirsResult.bossChatRoot}`);
|
|
1635
|
+
if (runtimeDirsResult.migration?.performed) {
|
|
1636
|
+
console.log(`- boss-chat migration: ${runtimeDirsResult.migration.message}`);
|
|
1637
|
+
}
|
|
1631
1638
|
if (runtimeDirsResult.failed.length > 0) {
|
|
1632
1639
|
for (const item of runtimeDirsResult.failed) {
|
|
1633
1640
|
console.warn(`Runtime dir warning: ${item.path} -> ${item.message}`);
|
|
@@ -1716,12 +1723,15 @@ export const __testables = {
|
|
|
1716
1723
|
buildBossChatCliInput,
|
|
1717
1724
|
buildDefaultMcpArgs,
|
|
1718
1725
|
buildMcpLaunchConfig,
|
|
1726
|
+
collectRuntimeDirectories,
|
|
1727
|
+
ensureBossChatRuntimeReady,
|
|
1728
|
+
ensureRuntimeDirectories,
|
|
1719
1729
|
getBossChatCliRunTarget,
|
|
1720
1730
|
getDefaultMcpPackageSpecifier,
|
|
1721
1731
|
getRunFollowUp,
|
|
1722
1732
|
installSkill,
|
|
1723
1733
|
isInstalledPackageRoot,
|
|
1724
|
-
|
|
1734
|
+
resolveBossChatRuntimeLayout,
|
|
1725
1735
|
runBossChatCliCommand,
|
|
1726
1736
|
runPipelineOnce
|
|
1727
1737
|
};
|
package/src/test-boss-chat.js
CHANGED
|
@@ -4,16 +4,18 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
cancelBossChatRun,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
7
|
+
import {
|
|
8
|
+
cancelBossChatRun,
|
|
9
|
+
ensureBossChatRuntimeReady,
|
|
10
|
+
getBossChatHealthCheck,
|
|
11
|
+
getBossChatRun,
|
|
12
|
+
pauseBossChatRun,
|
|
13
|
+
prepareBossChatRun,
|
|
14
|
+
resolveBossChatRuntimeLayout,
|
|
15
|
+
resumeBossChatRun,
|
|
16
|
+
startBossChatRun
|
|
17
|
+
} from "./boss-chat.js";
|
|
18
|
+
import { __testables as cliTestables, runCli } from "./cli.js";
|
|
17
19
|
import { __testables as indexTestables } from "./index.js";
|
|
18
20
|
import { BossChatApp } from "../vendor/boss-chat-cli/src/app.js";
|
|
19
21
|
import { __testables as vendorCliTestables } from "../vendor/boss-chat-cli/src/cli.js";
|
|
@@ -56,8 +58,8 @@ async function callTool(workspaceRoot, name, args = {}, id = 1) {
|
|
|
56
58
|
return response?.result?.structuredContent;
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function createBossChatTestWorkspace() {
|
|
60
|
-
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-boss-chat-"));
|
|
61
|
+
function createBossChatTestWorkspace() {
|
|
62
|
+
const workspaceRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-recommend-boss-chat-"));
|
|
61
63
|
const configDir = path.join(workspaceRoot, "config");
|
|
62
64
|
const cliDir = path.join(workspaceRoot, "boss-chat-cli", "src");
|
|
63
65
|
fs.mkdirSync(configDir, { recursive: true });
|
|
@@ -72,26 +74,13 @@ function createBossChatTestWorkspace() {
|
|
|
72
74
|
debugPort: 9666
|
|
73
75
|
}, null, 2));
|
|
74
76
|
|
|
75
|
-
fs.writeFileSync(path.join(cliDir, "cli.js"), [
|
|
76
|
-
"#!/usr/bin/env node",
|
|
77
|
-
"const fs = require('node:fs');",
|
|
78
|
-
"const path = require('node:path');",
|
|
79
|
-
"const
|
|
80
|
-
"const
|
|
81
|
-
"
|
|
82
|
-
"const raw = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '{}';",
|
|
83
|
-
"const state = JSON.parse(raw || '{}');",
|
|
84
|
-
"state.counter = Number.isInteger(state.counter) ? state.counter : 0;",
|
|
85
|
-
"state.prepare_calls = Number.isInteger(state.prepare_calls) ? state.prepare_calls : 0;",
|
|
86
|
-
"if (!Number.isInteger(state.prepare_fail_budget)) {",
|
|
87
|
-
" const configured = Number.parseInt(process.env.BOSS_CHAT_STUB_PREPARE_FAILS || '0', 10);",
|
|
88
|
-
" state.prepare_fail_budget = Number.isFinite(configured) && configured > 0 ? configured : 0;",
|
|
89
|
-
"}",
|
|
90
|
-
"state.runs = state.runs && typeof state.runs === 'object' ? state.runs : {};",
|
|
91
|
-
"state.get_calls = state.get_calls && typeof state.get_calls === 'object' ? state.get_calls : {};",
|
|
92
|
-
"const argv = process.argv.slice(2);",
|
|
93
|
-
"const command = String(argv[0] || '').trim();",
|
|
94
|
-
"const options = {};",
|
|
77
|
+
fs.writeFileSync(path.join(cliDir, "cli.js"), [
|
|
78
|
+
"#!/usr/bin/env node",
|
|
79
|
+
"const fs = require('node:fs');",
|
|
80
|
+
"const path = require('node:path');",
|
|
81
|
+
"const argv = process.argv.slice(2);",
|
|
82
|
+
"const command = String(argv[0] || '').trim();",
|
|
83
|
+
"const options = {};",
|
|
95
84
|
"for (let index = 1; index < argv.length; index += 1) {",
|
|
96
85
|
" const token = String(argv[index] || '');",
|
|
97
86
|
" if (!token.startsWith('--')) continue;",
|
|
@@ -102,11 +91,25 @@ function createBossChatTestWorkspace() {
|
|
|
102
91
|
" index += 1;",
|
|
103
92
|
" } else {",
|
|
104
93
|
" options[key] = true;",
|
|
105
|
-
" }",
|
|
106
|
-
"}",
|
|
107
|
-
"
|
|
108
|
-
"
|
|
109
|
-
"
|
|
94
|
+
" }",
|
|
95
|
+
"}",
|
|
96
|
+
"const cwd = process.cwd();",
|
|
97
|
+
"const dataDir = String(options['data-dir'] || process.env.BOSS_CHAT_HOME || path.join(cwd, '.boss-chat'));",
|
|
98
|
+
"const statePath = path.join(dataDir, 'stub-state.json');",
|
|
99
|
+
"fs.mkdirSync(path.dirname(statePath), { recursive: true });",
|
|
100
|
+
"const raw = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '{}';",
|
|
101
|
+
"const state = JSON.parse(raw || '{}');",
|
|
102
|
+
"state.counter = Number.isInteger(state.counter) ? state.counter : 0;",
|
|
103
|
+
"state.prepare_calls = Number.isInteger(state.prepare_calls) ? state.prepare_calls : 0;",
|
|
104
|
+
"if (!Number.isInteger(state.prepare_fail_budget)) {",
|
|
105
|
+
" const configured = Number.parseInt(process.env.BOSS_CHAT_STUB_PREPARE_FAILS || '0', 10);",
|
|
106
|
+
" state.prepare_fail_budget = Number.isFinite(configured) && configured > 0 ? configured : 0;",
|
|
107
|
+
"}",
|
|
108
|
+
"state.runs = state.runs && typeof state.runs === 'object' ? state.runs : {};",
|
|
109
|
+
"state.get_calls = state.get_calls && typeof state.get_calls === 'object' ? state.get_calls : {};",
|
|
110
|
+
"function saveAndPrint(payload) {",
|
|
111
|
+
" fs.writeFileSync(statePath, JSON.stringify(state, null, 2));",
|
|
112
|
+
" process.stdout.write(`${JSON.stringify(payload)}\\n`);",
|
|
110
113
|
"}",
|
|
111
114
|
"if (command === 'prepare-run') {",
|
|
112
115
|
" state.prepare_calls += 1;",
|
|
@@ -186,29 +189,40 @@ function createBossChatTestWorkspace() {
|
|
|
186
189
|
"process.exit(1);"
|
|
187
190
|
].join("\n"), "utf8");
|
|
188
191
|
|
|
189
|
-
return workspaceRoot;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
192
|
+
return workspaceRoot;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function getTestChatDataDir(workspaceRoot) {
|
|
196
|
+
return resolveBossChatRuntimeLayout(workspaceRoot).data_dir;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function readStubState(workspaceRoot) {
|
|
200
|
+
const statePath = path.join(getTestChatDataDir(workspaceRoot), "stub-state.json");
|
|
201
|
+
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function withBossChatWorkspace(testFn) {
|
|
205
|
+
const workspaceRoot = createBossChatTestWorkspace();
|
|
206
|
+
const previousScreenConfig = process.env.BOSS_RECOMMEND_SCREEN_CONFIG;
|
|
207
|
+
const previousBossChatHome = process.env.BOSS_CHAT_HOME;
|
|
208
|
+
process.env.BOSS_RECOMMEND_SCREEN_CONFIG = path.join(workspaceRoot, "config", "screening-config.json");
|
|
209
|
+
process.env.BOSS_CHAT_HOME = path.join(workspaceRoot, "user-boss-chat");
|
|
210
|
+
try {
|
|
211
|
+
await testFn(workspaceRoot);
|
|
212
|
+
} finally {
|
|
213
|
+
if (previousScreenConfig === undefined) {
|
|
214
|
+
delete process.env.BOSS_RECOMMEND_SCREEN_CONFIG;
|
|
215
|
+
} else {
|
|
216
|
+
process.env.BOSS_RECOMMEND_SCREEN_CONFIG = previousScreenConfig;
|
|
217
|
+
}
|
|
218
|
+
if (previousBossChatHome === undefined) {
|
|
219
|
+
delete process.env.BOSS_CHAT_HOME;
|
|
220
|
+
} else {
|
|
221
|
+
process.env.BOSS_CHAT_HOME = previousBossChatHome;
|
|
222
|
+
}
|
|
223
|
+
fs.rmSync(workspaceRoot, { recursive: true, force: true });
|
|
224
|
+
}
|
|
225
|
+
}
|
|
212
226
|
|
|
213
227
|
async function captureConsoleLogs(fn) {
|
|
214
228
|
const messages = [];
|
|
@@ -224,12 +238,16 @@ async function captureConsoleLogs(fn) {
|
|
|
224
238
|
return messages;
|
|
225
239
|
}
|
|
226
240
|
|
|
227
|
-
async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
228
|
-
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
229
|
-
const health = getBossChatHealthCheck(workspaceRoot);
|
|
230
|
-
assert.equal(health.status, "OK");
|
|
231
|
-
assert.equal(health.shared_llm_config, true);
|
|
232
|
-
assert.equal(health.debug_port, 9666);
|
|
241
|
+
async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
242
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
243
|
+
const health = getBossChatHealthCheck(workspaceRoot);
|
|
244
|
+
assert.equal(health.status, "OK");
|
|
245
|
+
assert.equal(health.shared_llm_config, true);
|
|
246
|
+
assert.equal(health.debug_port, 9666);
|
|
247
|
+
assert.equal(health.data_dir_source, "env:BOSS_CHAT_HOME");
|
|
248
|
+
assert.equal(health.data_dir, getTestChatDataDir(workspaceRoot));
|
|
249
|
+
assert.equal(health.legacy_workspace_dir, path.join(workspaceRoot, ".boss-chat"));
|
|
250
|
+
assert.equal(health.migration_pending, false);
|
|
233
251
|
|
|
234
252
|
const prepared = await prepareBossChatRun({
|
|
235
253
|
workspaceRoot,
|
|
@@ -259,9 +277,10 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
259
277
|
assert.equal(preflightTargetQuestion.recommended_argument_patch.target_count, "all");
|
|
260
278
|
assert.equal(Array.isArray(preflightTargetQuestion.options), true);
|
|
261
279
|
|
|
262
|
-
const stateAfterPrepare = readStubState(workspaceRoot);
|
|
263
|
-
assert.equal(stateAfterPrepare.last_prepare_args.profile, "default");
|
|
264
|
-
assert.equal(stateAfterPrepare.last_prepare_args
|
|
280
|
+
const stateAfterPrepare = readStubState(workspaceRoot);
|
|
281
|
+
assert.equal(stateAfterPrepare.last_prepare_args.profile, "default");
|
|
282
|
+
assert.equal(stateAfterPrepare.last_prepare_args["data-dir"], getTestChatDataDir(workspaceRoot));
|
|
283
|
+
assert.equal(stateAfterPrepare.last_prepare_args.port, "9666");
|
|
265
284
|
assert.equal(stateAfterPrepare.last_prepare_args.baseurl, "https://api.example.com/v1");
|
|
266
285
|
assert.equal(stateAfterPrepare.last_prepare_args.apikey, "sk-test-key");
|
|
267
286
|
assert.equal(stateAfterPrepare.last_prepare_args.model, "gpt-4.1-mini");
|
|
@@ -281,9 +300,10 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
281
300
|
assert.equal(started.status, "ACCEPTED");
|
|
282
301
|
assert.equal(Boolean(started.run_id), true);
|
|
283
302
|
|
|
284
|
-
const stateAfterStart = readStubState(workspaceRoot);
|
|
285
|
-
assert.equal(stateAfterStart.last_start_args.profile, "default");
|
|
286
|
-
assert.equal(stateAfterStart.last_start_args
|
|
303
|
+
const stateAfterStart = readStubState(workspaceRoot);
|
|
304
|
+
assert.equal(stateAfterStart.last_start_args.profile, "default");
|
|
305
|
+
assert.equal(stateAfterStart.last_start_args["data-dir"], getTestChatDataDir(workspaceRoot));
|
|
306
|
+
assert.equal(stateAfterStart.last_start_args.job, "算法工程师");
|
|
287
307
|
assert.equal(stateAfterStart.last_start_args["start-from"], "unread");
|
|
288
308
|
assert.equal(stateAfterStart.last_start_args.criteria, "有 AI Agent 经验");
|
|
289
309
|
assert.equal(stateAfterStart.last_start_args.targetCount, "2");
|
|
@@ -389,11 +409,99 @@ async function testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli() {
|
|
|
389
409
|
}
|
|
390
410
|
});
|
|
391
411
|
assert.equal(canceled.run.state, "canceled");
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async function
|
|
396
|
-
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async function testBossChatRuntimeShouldMigrateLegacyWorkspaceDataOnce() {
|
|
416
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
417
|
+
const legacyDir = path.join(workspaceRoot, ".boss-chat");
|
|
418
|
+
const legacyStatePath = path.join(legacyDir, "state", "default.json");
|
|
419
|
+
const legacyRunPath = path.join(legacyDir, "runs", "legacy-run.json");
|
|
420
|
+
fs.mkdirSync(path.dirname(legacyStatePath), { recursive: true });
|
|
421
|
+
fs.mkdirSync(path.dirname(legacyRunPath), { recursive: true });
|
|
422
|
+
fs.writeFileSync(legacyStatePath, JSON.stringify({ cursor: 7 }, null, 2));
|
|
423
|
+
fs.writeFileSync(legacyRunPath, JSON.stringify({ run_id: "legacy-run" }, null, 2));
|
|
424
|
+
|
|
425
|
+
const before = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
426
|
+
assert.equal(before.data_dir, getTestChatDataDir(workspaceRoot));
|
|
427
|
+
assert.equal(before.legacy_workspace_dir, legacyDir);
|
|
428
|
+
assert.equal(before.migration_source_dir, legacyDir);
|
|
429
|
+
assert.equal(before.migration_pending, true);
|
|
430
|
+
|
|
431
|
+
const ready = ensureBossChatRuntimeReady(workspaceRoot);
|
|
432
|
+
assert.equal(ready.migration.attempted, true);
|
|
433
|
+
assert.equal(ready.migration.performed, true);
|
|
434
|
+
assert.equal(fs.existsSync(path.join(ready.data_dir, "state", "default.json")), true);
|
|
435
|
+
assert.equal(fs.existsSync(path.join(ready.data_dir, "runs", "legacy-run.json")), true);
|
|
436
|
+
assert.deepEqual(
|
|
437
|
+
JSON.parse(fs.readFileSync(path.join(ready.data_dir, "state", "default.json"), "utf8")),
|
|
438
|
+
{ cursor: 7 }
|
|
439
|
+
);
|
|
440
|
+
assert.equal(fs.existsSync(legacyStatePath), true);
|
|
441
|
+
|
|
442
|
+
const after = resolveBossChatRuntimeLayout(workspaceRoot);
|
|
443
|
+
assert.equal(after.migration_pending, false);
|
|
444
|
+
assert.equal(after.migration_source_dir, null);
|
|
445
|
+
|
|
446
|
+
const secondReady = ensureBossChatRuntimeReady(workspaceRoot);
|
|
447
|
+
assert.equal(secondReady.migration.attempted, false);
|
|
448
|
+
assert.equal(secondReady.migration.performed, false);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function testBossChatRuntimeShouldResolveUserDirForRootWorkspace() {
|
|
453
|
+
const previousBossChatHome = process.env.BOSS_CHAT_HOME;
|
|
454
|
+
const previousRecommendHome = process.env.BOSS_RECOMMEND_HOME;
|
|
455
|
+
const runtimeRoot = fs.mkdtempSync(path.join(os.tmpdir(), "boss-chat-root-runtime-"));
|
|
456
|
+
try {
|
|
457
|
+
delete process.env.BOSS_CHAT_HOME;
|
|
458
|
+
process.env.BOSS_RECOMMEND_HOME = runtimeRoot;
|
|
459
|
+
const rootWorkspace = path.parse(process.cwd()).root;
|
|
460
|
+
const runtime = resolveBossChatRuntimeLayout(rootWorkspace);
|
|
461
|
+
assert.equal(runtime.data_dir, path.join(runtimeRoot, "boss-chat"));
|
|
462
|
+
assert.equal(runtime.legacy_workspace_dir, null);
|
|
463
|
+
assert.equal(runtime.migration_pending, false);
|
|
464
|
+
|
|
465
|
+
const ready = ensureBossChatRuntimeReady(rootWorkspace);
|
|
466
|
+
assert.equal(fs.existsSync(ready.data_dir), true);
|
|
467
|
+
assert.equal(fs.existsSync(path.join(ready.data_dir, "runs")), true);
|
|
468
|
+
} finally {
|
|
469
|
+
if (previousBossChatHome === undefined) {
|
|
470
|
+
delete process.env.BOSS_CHAT_HOME;
|
|
471
|
+
} else {
|
|
472
|
+
process.env.BOSS_CHAT_HOME = previousBossChatHome;
|
|
473
|
+
}
|
|
474
|
+
if (previousRecommendHome === undefined) {
|
|
475
|
+
delete process.env.BOSS_RECOMMEND_HOME;
|
|
476
|
+
} else {
|
|
477
|
+
process.env.BOSS_RECOMMEND_HOME = previousRecommendHome;
|
|
478
|
+
}
|
|
479
|
+
fs.rmSync(runtimeRoot, { recursive: true, force: true });
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async function testBossChatWhereShouldPrintUserRuntimePath() {
|
|
484
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
485
|
+
const previousWorkspaceRoot = process.env.BOSS_WORKSPACE_ROOT;
|
|
486
|
+
process.env.BOSS_WORKSPACE_ROOT = workspaceRoot;
|
|
487
|
+
try {
|
|
488
|
+
const logs = await captureConsoleLogs(async () => {
|
|
489
|
+
await runCli(["node", "src/cli.js", "where"]);
|
|
490
|
+
});
|
|
491
|
+
assert.equal(logs.some((line) => line.includes(`boss_chat_runtime=${getTestChatDataDir(workspaceRoot)}`)), true);
|
|
492
|
+
assert.equal(logs.some((line) => line.includes(`boss_chat_legacy_workspace_runtime=${path.join(workspaceRoot, ".boss-chat")}`)), true);
|
|
493
|
+
} finally {
|
|
494
|
+
if (previousWorkspaceRoot === undefined) {
|
|
495
|
+
delete process.env.BOSS_WORKSPACE_ROOT;
|
|
496
|
+
} else {
|
|
497
|
+
process.env.BOSS_WORKSPACE_ROOT = previousWorkspaceRoot;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
async function testBossChatPrepareShouldRetryWhenChatPageIsNotReady() {
|
|
504
|
+
await withBossChatWorkspace(async (workspaceRoot) => {
|
|
397
505
|
const previousPrepareFails = process.env.BOSS_CHAT_STUB_PREPARE_FAILS;
|
|
398
506
|
process.env.BOSS_CHAT_STUB_PREPARE_FAILS = "2";
|
|
399
507
|
try {
|
|
@@ -412,8 +520,53 @@ async function testBossChatPrepareShouldRetryWhenChatPageIsNotReady() {
|
|
|
412
520
|
process.env.BOSS_CHAT_STUB_PREPARE_FAILS = previousPrepareFails;
|
|
413
521
|
}
|
|
414
522
|
}
|
|
415
|
-
});
|
|
416
|
-
}
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function testVendorBossChatCliShouldResolveExplicitDataDir() {
|
|
527
|
+
const cwd = path.join(path.parse(process.cwd()).root, "workspace");
|
|
528
|
+
const args = vendorCliTestables.parseArgs(["start-run", "--data-dir", "/tmp/boss-chat-data"]);
|
|
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"));
|
|
533
|
+
assert.equal(
|
|
534
|
+
vendorCliTestables.resolveDataDir(args, { BOSS_CHAT_HOME: "/tmp/ignored" }, cwd),
|
|
535
|
+
path.resolve("/tmp/boss-chat-data")
|
|
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"));
|
|
540
|
+
assert.equal(
|
|
541
|
+
vendorCliTestables.resolveDataDir({}, { BOSS_CHAT_HOME: "/tmp/from-env" }, cwd),
|
|
542
|
+
path.resolve("/tmp/from-env")
|
|
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"));
|
|
547
|
+
assert.equal(
|
|
548
|
+
vendorCliTestables.resolveDataDir({}, {}, cwd),
|
|
549
|
+
path.join(os.homedir(), ".boss-recommend-mcp", "boss-chat")
|
|
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"));
|
|
569
|
+
}
|
|
417
570
|
|
|
418
571
|
async function testBossChatPageShouldTreatBlankChatShellAsOnChatPage() {
|
|
419
572
|
const fakeChromeClient = {
|
|
@@ -2879,9 +3032,12 @@ async function testBossChatReportStoreShouldWriteReadableMarkdownAndCsv() {
|
|
|
2879
3032
|
assert.match(csvContent, /先看项目经历,再看实习时长/);
|
|
2880
3033
|
}
|
|
2881
3034
|
|
|
2882
|
-
async function main() {
|
|
2883
|
-
await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
|
|
2884
|
-
await
|
|
3035
|
+
async function main() {
|
|
3036
|
+
await testBossChatAdapterShouldResolveSharedConfigAndInvokeLocalCli();
|
|
3037
|
+
await testBossChatRuntimeShouldMigrateLegacyWorkspaceDataOnce();
|
|
3038
|
+
testBossChatRuntimeShouldResolveUserDirForRootWorkspace();
|
|
3039
|
+
await testBossChatWhereShouldPrintUserRuntimePath();
|
|
3040
|
+
await testBossChatPrepareShouldRetryWhenChatPageIsNotReady();
|
|
2885
3041
|
await testBossChatPageShouldTreatBlankChatShellAsOnChatPage();
|
|
2886
3042
|
await testBossChatRecoverToChatIndexShouldForceNavigateAndWaitForCompleteLoad();
|
|
2887
3043
|
await testBossChatPageShouldFallbackToEscapeWhenClosingCandidateDetail();
|
|
@@ -2889,8 +3045,10 @@ async function main() {
|
|
|
2889
3045
|
await testBossChatPageShouldWaitForPanelsClosedInStrictConversationReady();
|
|
2890
3046
|
await testBossChatPageShouldSurfaceCandidateDetailOverlayAndContentState();
|
|
2891
3047
|
await testBossChatMcpToolsShouldValidateAndRoute();
|
|
2892
|
-
await testBossChatCliShouldSupportRunAndFollowUpParsing();
|
|
2893
|
-
|
|
3048
|
+
await testBossChatCliShouldSupportRunAndFollowUpParsing();
|
|
3049
|
+
testVendorBossChatCliShouldResolveExplicitDataDir();
|
|
3050
|
+
testVendorBossChatCliShouldUseRecommendHomeForDefaultDataDir();
|
|
3051
|
+
await testVendorBossChatCliShouldWaitForHydratedChatShell();
|
|
2894
3052
|
await testVendorBossChatCliShouldRetryJobListDuringPromptRunProfile();
|
|
2895
3053
|
testCliShouldPinInstalledPackageVersionInGeneratedMcpConfig();
|
|
2896
3054
|
testVendorBossChatCliShouldParseSharedLlmTransportArgs();
|