@reconcrap/boss-recommend-mcp 1.1.7 → 1.1.8
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/bin/boss-recommend-mcp.js +0 -0
- package/package.json +1 -1
- package/src/index.js +224 -65
|
File without changes
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
3
|
+
import { spawn } from "node:child_process";
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { runRecommendPipeline } from "./pipeline.js";
|
|
7
8
|
import {
|
|
8
9
|
RUN_MODE_ASYNC,
|
|
@@ -33,18 +34,39 @@ const TOOL_CANCEL_RUN = "cancel_recommend_pipeline_run";
|
|
|
33
34
|
const TOOL_PAUSE_RUN = "pause_recommend_pipeline_run";
|
|
34
35
|
const TOOL_RESUME_RUN = "resume_recommend_pipeline_run";
|
|
35
36
|
|
|
36
|
-
const SERVER_NAME = "boss-recommend-mcp";
|
|
37
|
-
const FRAMING_UNKNOWN = "unknown";
|
|
38
|
-
const FRAMING_HEADER = "header";
|
|
39
|
-
const FRAMING_LINE = "line";
|
|
37
|
+
const SERVER_NAME = "boss-recommend-mcp";
|
|
38
|
+
const FRAMING_UNKNOWN = "unknown";
|
|
39
|
+
const FRAMING_HEADER = "header";
|
|
40
|
+
const FRAMING_LINE = "line";
|
|
41
|
+
const ASYNC_WORKER_FLAG = "--async-worker";
|
|
42
|
+
const thisFilePath = fileURLToPath(import.meta.url);
|
|
40
43
|
|
|
41
44
|
const activeAsyncRuns = new Map();
|
|
42
45
|
let runPipelineImpl = runRecommendPipeline;
|
|
43
46
|
const TERMINAL_RUN_STATES = new Set([RUN_STATE_COMPLETED, RUN_STATE_FAILED, RUN_STATE_CANCELED]);
|
|
44
47
|
|
|
45
|
-
function normalizeText(value) {
|
|
46
|
-
return String(value || "").replace(/\s+/g, " ").trim();
|
|
47
|
-
}
|
|
48
|
+
function normalizeText(value) {
|
|
49
|
+
return String(value || "").replace(/\s+/g, " ").trim();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function encodeWorkerPayload(payload) {
|
|
53
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function decodeWorkerPayload(encoded) {
|
|
57
|
+
try {
|
|
58
|
+
const raw = Buffer.from(String(encoded || ""), "base64").toString("utf8");
|
|
59
|
+
const parsed = JSON.parse(raw);
|
|
60
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
61
|
+
return parsed;
|
|
62
|
+
} catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function shouldUseInProcessAsyncRunner() {
|
|
68
|
+
return normalizeText(process.env.BOSS_RECOMMEND_ASYNC_INPROC || "") === "1";
|
|
69
|
+
}
|
|
48
70
|
|
|
49
71
|
function parsePositiveInteger(raw, fallback) {
|
|
50
72
|
const value = Number.parseInt(String(raw || ""), 10);
|
|
@@ -726,14 +748,14 @@ async function executeTrackedPipeline({
|
|
|
726
748
|
};
|
|
727
749
|
}
|
|
728
750
|
|
|
729
|
-
function initializeRunStateOrThrow(runId, mode, workspaceRoot, args) {
|
|
730
|
-
const artifacts = getRunArtifacts(runId);
|
|
731
|
-
const snapshot = createRunStateSnapshot({
|
|
732
|
-
runId,
|
|
733
|
-
mode,
|
|
734
|
-
state: "queued",
|
|
735
|
-
stage: RUN_STAGE_PREFLIGHT,
|
|
736
|
-
pid
|
|
751
|
+
function initializeRunStateOrThrow(runId, mode, workspaceRoot, args, pid = process.pid) {
|
|
752
|
+
const artifacts = getRunArtifacts(runId);
|
|
753
|
+
const snapshot = createRunStateSnapshot({
|
|
754
|
+
runId,
|
|
755
|
+
mode,
|
|
756
|
+
state: "queued",
|
|
757
|
+
stage: RUN_STAGE_PREFLIGHT,
|
|
758
|
+
pid,
|
|
737
759
|
lastMessage: "流水线任务已创建,等待执行。",
|
|
738
760
|
context: buildRunContext(workspaceRoot, args),
|
|
739
761
|
control: {
|
|
@@ -754,8 +776,8 @@ function initializeRunStateOrThrow(runId, mode, workspaceRoot, args) {
|
|
|
754
776
|
return writeRunState(snapshot);
|
|
755
777
|
}
|
|
756
778
|
|
|
757
|
-
function launchAsyncRun({ runId, mode, workspaceRoot, args, resumeRun = false }) {
|
|
758
|
-
const abortController = new AbortController();
|
|
779
|
+
function launchAsyncRun({ runId, mode, workspaceRoot, args, resumeRun = false }) {
|
|
780
|
+
const abortController = new AbortController();
|
|
759
781
|
const promise = executeTrackedPipeline({
|
|
760
782
|
runId,
|
|
761
783
|
mode,
|
|
@@ -769,9 +791,80 @@ function launchAsyncRun({ runId, mode, workspaceRoot, args, resumeRun = false })
|
|
|
769
791
|
activeAsyncRuns.set(runId, {
|
|
770
792
|
abortController,
|
|
771
793
|
promise
|
|
772
|
-
});
|
|
773
|
-
return { abortController, promise };
|
|
774
|
-
}
|
|
794
|
+
});
|
|
795
|
+
return { abortController, promise };
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
function launchDetachedAsyncRun({ runId, mode, workspaceRoot, args, resumeRun = false }) {
|
|
799
|
+
const payload = encodeWorkerPayload({
|
|
800
|
+
runId,
|
|
801
|
+
mode,
|
|
802
|
+
workspaceRoot,
|
|
803
|
+
args,
|
|
804
|
+
resumeRun
|
|
805
|
+
});
|
|
806
|
+
const child = spawn(
|
|
807
|
+
process.execPath,
|
|
808
|
+
[thisFilePath, ASYNC_WORKER_FLAG, payload],
|
|
809
|
+
{
|
|
810
|
+
detached: true,
|
|
811
|
+
stdio: "ignore",
|
|
812
|
+
cwd: path.resolve(workspaceRoot),
|
|
813
|
+
env: process.env
|
|
814
|
+
}
|
|
815
|
+
);
|
|
816
|
+
child.unref();
|
|
817
|
+
return child;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
async function runAsyncWorkerMain(encodedPayload) {
|
|
821
|
+
const payload = decodeWorkerPayload(encodedPayload);
|
|
822
|
+
if (!payload) return;
|
|
823
|
+
const runId = normalizeText(payload.runId);
|
|
824
|
+
const mode = normalizeText(payload.mode) || RUN_MODE_ASYNC;
|
|
825
|
+
const workspaceRoot = normalizeText(payload.workspaceRoot);
|
|
826
|
+
const args = payload.args && typeof payload.args === "object" ? payload.args : {};
|
|
827
|
+
const resumeRun = payload.resumeRun === true;
|
|
828
|
+
if (!runId || !workspaceRoot || typeof args.instruction !== "string") return;
|
|
829
|
+
|
|
830
|
+
const existing = readRunState(runId);
|
|
831
|
+
if (!existing) {
|
|
832
|
+
try {
|
|
833
|
+
initializeRunStateOrThrow(runId, mode, workspaceRoot, args, process.pid);
|
|
834
|
+
} catch {
|
|
835
|
+
return;
|
|
836
|
+
}
|
|
837
|
+
} else {
|
|
838
|
+
safeUpdateRunState(runId, { mode, pid: process.pid });
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
try {
|
|
842
|
+
await executeTrackedPipeline({
|
|
843
|
+
runId,
|
|
844
|
+
mode,
|
|
845
|
+
workspaceRoot,
|
|
846
|
+
args,
|
|
847
|
+
signal: null,
|
|
848
|
+
resumeRun
|
|
849
|
+
});
|
|
850
|
+
} catch (error) {
|
|
851
|
+
const failedResult = {
|
|
852
|
+
status: "FAILED",
|
|
853
|
+
error: {
|
|
854
|
+
code: "UNEXPECTED_ERROR",
|
|
855
|
+
message: error?.message || "Unexpected worker error",
|
|
856
|
+
retryable: true
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
safeUpdateRunState(runId, {
|
|
860
|
+
state: RUN_STATE_FAILED,
|
|
861
|
+
stage: RUN_STAGE_PREFLIGHT,
|
|
862
|
+
last_message: failedResult.error.message,
|
|
863
|
+
error: failedResult.error,
|
|
864
|
+
result: failedResult
|
|
865
|
+
});
|
|
866
|
+
}
|
|
867
|
+
}
|
|
775
868
|
|
|
776
869
|
async function handleStartRunTool({ workspaceRoot, args }) {
|
|
777
870
|
const precheckArgs = buildAsyncPrecheckArgs(args);
|
|
@@ -805,29 +898,58 @@ async function handleStartRunTool({ workspaceRoot, args }) {
|
|
|
805
898
|
return precheckResult;
|
|
806
899
|
}
|
|
807
900
|
|
|
808
|
-
cleanupExpiredRuns();
|
|
809
|
-
const runId = createRunId();
|
|
810
|
-
try {
|
|
811
|
-
initializeRunStateOrThrow(runId, RUN_MODE_ASYNC, workspaceRoot, args);
|
|
812
|
-
} catch (error) {
|
|
813
|
-
return {
|
|
814
|
-
status: "FAILED",
|
|
901
|
+
cleanupExpiredRuns();
|
|
902
|
+
const runId = createRunId();
|
|
903
|
+
try {
|
|
904
|
+
initializeRunStateOrThrow(runId, RUN_MODE_ASYNC, workspaceRoot, args, process.pid);
|
|
905
|
+
} catch (error) {
|
|
906
|
+
return {
|
|
907
|
+
status: "FAILED",
|
|
815
908
|
error: {
|
|
816
909
|
code: "RUN_STATE_IO_ERROR",
|
|
817
910
|
message: `无法写入运行状态目录:${error.message || "unknown"}`,
|
|
818
911
|
retryable: false
|
|
819
|
-
}
|
|
820
|
-
};
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
try {
|
|
917
|
+
if (shouldUseInProcessAsyncRunner()) {
|
|
918
|
+
launchAsyncRun({
|
|
919
|
+
runId,
|
|
920
|
+
mode: RUN_MODE_ASYNC,
|
|
921
|
+
workspaceRoot,
|
|
922
|
+
args
|
|
923
|
+
});
|
|
924
|
+
} else {
|
|
925
|
+
const worker = launchDetachedAsyncRun({
|
|
926
|
+
runId,
|
|
927
|
+
mode: RUN_MODE_ASYNC,
|
|
928
|
+
workspaceRoot,
|
|
929
|
+
args
|
|
930
|
+
});
|
|
931
|
+
safeUpdateRunState(runId, { pid: worker.pid || process.pid });
|
|
932
|
+
}
|
|
933
|
+
} catch (error) {
|
|
934
|
+
const failed = {
|
|
935
|
+
status: "FAILED",
|
|
936
|
+
error: {
|
|
937
|
+
code: "RUN_START_FAILED",
|
|
938
|
+
message: error?.message || "无法启动异步 worker 进程。",
|
|
939
|
+
retryable: true
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
safeUpdateRunState(runId, {
|
|
943
|
+
state: RUN_STATE_FAILED,
|
|
944
|
+
stage: RUN_STAGE_PREFLIGHT,
|
|
945
|
+
last_message: failed.error.message,
|
|
946
|
+
error: failed.error,
|
|
947
|
+
result: failed
|
|
948
|
+
});
|
|
949
|
+
return failed;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
return {
|
|
831
953
|
status: "ACCEPTED",
|
|
832
954
|
run_id: runId,
|
|
833
955
|
state: "queued",
|
|
@@ -928,8 +1050,9 @@ function handleCancelRunTool(args) {
|
|
|
928
1050
|
};
|
|
929
1051
|
}
|
|
930
1052
|
|
|
931
|
-
const activeRun = activeAsyncRuns.get(runId);
|
|
932
|
-
|
|
1053
|
+
const activeRun = activeAsyncRuns.get(runId);
|
|
1054
|
+
const hasLiveDetachedWorker = isProcessAlive(snapshot.pid);
|
|
1055
|
+
if (!activeRun && !hasLiveDetachedWorker) {
|
|
933
1056
|
const canceledResult = {
|
|
934
1057
|
status: "FAILED",
|
|
935
1058
|
error: {
|
|
@@ -1112,15 +1235,45 @@ function handleResumeRunTool(args) {
|
|
|
1112
1235
|
}
|
|
1113
1236
|
})) || readRunState(runId) || snapshot;
|
|
1114
1237
|
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1238
|
+
try {
|
|
1239
|
+
if (shouldUseInProcessAsyncRunner()) {
|
|
1240
|
+
launchAsyncRun({
|
|
1241
|
+
runId,
|
|
1242
|
+
mode: RUN_MODE_ASYNC,
|
|
1243
|
+
workspaceRoot: executionContext.workspaceRoot,
|
|
1244
|
+
args: executionContext.args,
|
|
1245
|
+
resumeRun: true
|
|
1246
|
+
});
|
|
1247
|
+
} else {
|
|
1248
|
+
const worker = launchDetachedAsyncRun({
|
|
1249
|
+
runId,
|
|
1250
|
+
mode: RUN_MODE_ASYNC,
|
|
1251
|
+
workspaceRoot: executionContext.workspaceRoot,
|
|
1252
|
+
args: executionContext.args,
|
|
1253
|
+
resumeRun: true
|
|
1254
|
+
});
|
|
1255
|
+
safeUpdateRunState(runId, { pid: worker.pid || updated.pid || process.pid });
|
|
1256
|
+
}
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
const failed = {
|
|
1259
|
+
status: "FAILED",
|
|
1260
|
+
error: {
|
|
1261
|
+
code: "RUN_RESUME_FAILED",
|
|
1262
|
+
message: error?.message || "无法启动继续执行 worker 进程。",
|
|
1263
|
+
retryable: true
|
|
1264
|
+
}
|
|
1265
|
+
};
|
|
1266
|
+
safeUpdateRunState(runId, {
|
|
1267
|
+
state: RUN_STATE_FAILED,
|
|
1268
|
+
stage: updated.stage || RUN_STAGE_PREFLIGHT,
|
|
1269
|
+
last_message: failed.error.message,
|
|
1270
|
+
error: failed.error,
|
|
1271
|
+
result: failed
|
|
1272
|
+
});
|
|
1273
|
+
return failed;
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
return {
|
|
1124
1277
|
status: "RESUME_REQUESTED",
|
|
1125
1278
|
run: updated,
|
|
1126
1279
|
poll_after_sec: getDefaultPollAfterSec(),
|
|
@@ -1320,15 +1473,21 @@ export function startServer() {
|
|
|
1320
1473
|
});
|
|
1321
1474
|
}
|
|
1322
1475
|
|
|
1323
|
-
export const __testables = {
|
|
1324
|
-
handleRequest,
|
|
1325
|
-
activeAsyncRuns,
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === thisFilePath) {
|
|
1333
|
-
|
|
1334
|
-
|
|
1476
|
+
export const __testables = {
|
|
1477
|
+
handleRequest,
|
|
1478
|
+
activeAsyncRuns,
|
|
1479
|
+
runAsyncWorkerMain,
|
|
1480
|
+
setRunPipelineImplForTests(nextImpl) {
|
|
1481
|
+
runPipelineImpl = typeof nextImpl === "function" ? nextImpl : runRecommendPipeline;
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
|
|
1485
|
+
if (process.argv[1] && path.resolve(process.argv[1]) === thisFilePath) {
|
|
1486
|
+
const workerFlagIndex = process.argv.indexOf(ASYNC_WORKER_FLAG);
|
|
1487
|
+
if (workerFlagIndex !== -1) {
|
|
1488
|
+
const payloadArg = process.argv[workerFlagIndex + 1] || "";
|
|
1489
|
+
runAsyncWorkerMain(payloadArg).catch(() => {});
|
|
1490
|
+
} else {
|
|
1491
|
+
startServer();
|
|
1492
|
+
}
|
|
1493
|
+
}
|