@reconcrap/boss-recommend-mcp 2.0.37 → 2.0.39
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/config/screening-config.example.json +1 -1
- package/package.json +119 -119
- package/src/core/run/index.js +310 -310
- package/src/domains/recommend/detail.js +612 -569
- package/src/domains/recommend/refresh.js +78 -6
- package/src/domains/recommend/run-service.js +1421 -1414
- package/src/index.js +148 -9
- package/src/recommend-mcp.js +1719 -1715
- package/src/run-state.js +358 -356
package/src/index.js
CHANGED
|
@@ -295,7 +295,9 @@ function getRunArtifacts(runId) {
|
|
|
295
295
|
const normalizedRunId = normalizeText(runId);
|
|
296
296
|
return {
|
|
297
297
|
run_state_path: path.join(getRunsDir(), `${normalizedRunId}.json`),
|
|
298
|
-
checkpoint_path: path.join(getRunsDir(), `${normalizedRunId}.checkpoint.json`)
|
|
298
|
+
checkpoint_path: path.join(getRunsDir(), `${normalizedRunId}.checkpoint.json`),
|
|
299
|
+
worker_stdout_path: path.join(getRunsDir(), `${normalizedRunId}.worker.stdout.log`),
|
|
300
|
+
worker_stderr_path: path.join(getRunsDir(), `${normalizedRunId}.worker.stderr.log`)
|
|
299
301
|
};
|
|
300
302
|
}
|
|
301
303
|
|
|
@@ -1554,18 +1556,145 @@ function launchDetachedRunWorker({ runId, resumeRun = false }) {
|
|
|
1554
1556
|
if (resumeRun) {
|
|
1555
1557
|
childArgs.push(DETACHED_WORKER_RESUME_FLAG);
|
|
1556
1558
|
}
|
|
1557
|
-
const
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1559
|
+
const artifacts = getRunArtifacts(runId);
|
|
1560
|
+
fs.mkdirSync(path.dirname(artifacts.worker_stdout_path), { recursive: true });
|
|
1561
|
+
const stdoutFd = fs.openSync(artifacts.worker_stdout_path, "a");
|
|
1562
|
+
const stderrFd = fs.openSync(artifacts.worker_stderr_path, "a");
|
|
1563
|
+
let child;
|
|
1564
|
+
try {
|
|
1565
|
+
child = spawnProcessImpl(process.execPath, childArgs, {
|
|
1566
|
+
detached: true,
|
|
1567
|
+
stdio: ["ignore", stdoutFd, stderrFd],
|
|
1568
|
+
windowsHide: true,
|
|
1569
|
+
env: process.env
|
|
1570
|
+
});
|
|
1571
|
+
} finally {
|
|
1572
|
+
fs.closeSync(stdoutFd);
|
|
1573
|
+
fs.closeSync(stderrFd);
|
|
1574
|
+
}
|
|
1575
|
+
child.workerLogPaths = {
|
|
1576
|
+
stdoutPath: artifacts.worker_stdout_path,
|
|
1577
|
+
stderrPath: artifacts.worker_stderr_path
|
|
1578
|
+
};
|
|
1563
1579
|
if (typeof child?.unref === "function") {
|
|
1564
1580
|
child.unref();
|
|
1565
1581
|
}
|
|
1566
1582
|
return child;
|
|
1567
1583
|
}
|
|
1568
1584
|
|
|
1585
|
+
function errorToDetachedWorkerPayload(error, fallbackMessage = "detached recommend worker exited unexpectedly") {
|
|
1586
|
+
const message = normalizeText(error?.message || error || fallbackMessage) || fallbackMessage;
|
|
1587
|
+
const payload = {
|
|
1588
|
+
code: normalizeText(error?.code || "") || "RUN_WORKER_UNHANDLED_EXCEPTION",
|
|
1589
|
+
message,
|
|
1590
|
+
retryable: true
|
|
1591
|
+
};
|
|
1592
|
+
if (normalizeText(error?.name || "")) {
|
|
1593
|
+
payload.name = normalizeText(error.name);
|
|
1594
|
+
}
|
|
1595
|
+
if (normalizeText(error?.stack || "")) {
|
|
1596
|
+
payload.stack = String(error.stack).slice(0, 8000);
|
|
1597
|
+
}
|
|
1598
|
+
return payload;
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
function markDetachedWorkerFailed(runId, error, options = {}) {
|
|
1602
|
+
const normalizedRunId = normalizeText(runId);
|
|
1603
|
+
if (!normalizedRunId) return null;
|
|
1604
|
+
const existing = readRawRunState(normalizedRunId) || {};
|
|
1605
|
+
const existingState = normalizeText(existing.state || existing.status);
|
|
1606
|
+
if (TERMINAL_RUN_STATES.has(existingState)) return existing;
|
|
1607
|
+
|
|
1608
|
+
const now = new Date().toISOString();
|
|
1609
|
+
const artifacts = getRunArtifacts(normalizedRunId);
|
|
1610
|
+
const errorPayload = {
|
|
1611
|
+
...errorToDetachedWorkerPayload(error, options.message),
|
|
1612
|
+
...(options.code ? { code: options.code } : {})
|
|
1613
|
+
};
|
|
1614
|
+
const previousResult = existing.result && typeof existing.result === "object" ? existing.result : {};
|
|
1615
|
+
const result = {
|
|
1616
|
+
...previousResult,
|
|
1617
|
+
status: "FAILED",
|
|
1618
|
+
completion_reason: "failed",
|
|
1619
|
+
error: errorPayload,
|
|
1620
|
+
worker_stdout_path: artifacts.worker_stdout_path,
|
|
1621
|
+
worker_stderr_path: artifacts.worker_stderr_path
|
|
1622
|
+
};
|
|
1623
|
+
|
|
1624
|
+
return writeRawRunState(normalizedRunId, {
|
|
1625
|
+
...existing,
|
|
1626
|
+
run_id: normalizedRunId,
|
|
1627
|
+
mode: existing.mode || RUN_MODE_ASYNC,
|
|
1628
|
+
state: RUN_STATE_FAILED,
|
|
1629
|
+
status: RUN_STATE_FAILED,
|
|
1630
|
+
stage: existing.stage || RUN_STAGE_PREFLIGHT,
|
|
1631
|
+
started_at: existing.started_at || now,
|
|
1632
|
+
updated_at: now,
|
|
1633
|
+
heartbeat_at: now,
|
|
1634
|
+
completed_at: now,
|
|
1635
|
+
pid: Number.isInteger(existing.pid) && existing.pid > 0 ? existing.pid : process.pid,
|
|
1636
|
+
progress: existing.progress || {},
|
|
1637
|
+
last_message: errorPayload.message,
|
|
1638
|
+
context: existing.context || {},
|
|
1639
|
+
control: existing.control || {
|
|
1640
|
+
pause_requested: false,
|
|
1641
|
+
pause_requested_at: null,
|
|
1642
|
+
pause_requested_by: null,
|
|
1643
|
+
cancel_requested: false
|
|
1644
|
+
},
|
|
1645
|
+
resume: {
|
|
1646
|
+
...(existing.resume && typeof existing.resume === "object" ? existing.resume : {}),
|
|
1647
|
+
checkpoint_path: existing.resume?.checkpoint_path || artifacts.checkpoint_path,
|
|
1648
|
+
pause_control_path: existing.resume?.pause_control_path || artifacts.run_state_path,
|
|
1649
|
+
worker_stdout_path: artifacts.worker_stdout_path,
|
|
1650
|
+
worker_stderr_path: artifacts.worker_stderr_path
|
|
1651
|
+
},
|
|
1652
|
+
artifacts: {
|
|
1653
|
+
...(existing.artifacts && typeof existing.artifacts === "object" ? existing.artifacts : {}),
|
|
1654
|
+
worker_stdout_path: artifacts.worker_stdout_path,
|
|
1655
|
+
worker_stderr_path: artifacts.worker_stderr_path
|
|
1656
|
+
},
|
|
1657
|
+
error: errorPayload,
|
|
1658
|
+
result
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1662
|
+
function installDetachedWorkerFailureHandlers(runId) {
|
|
1663
|
+
let handled = false;
|
|
1664
|
+
const failOnce = (error, options = {}) => {
|
|
1665
|
+
if (handled) return;
|
|
1666
|
+
handled = true;
|
|
1667
|
+
try {
|
|
1668
|
+
markDetachedWorkerFailed(runId, error, options);
|
|
1669
|
+
} catch (markError) {
|
|
1670
|
+
console.error("[boss-recommend-mcp] failed to persist detached worker failure", markError);
|
|
1671
|
+
}
|
|
1672
|
+
};
|
|
1673
|
+
|
|
1674
|
+
process.on("uncaughtException", (error) => {
|
|
1675
|
+
console.error("[boss-recommend-mcp] detached worker uncaught exception", error);
|
|
1676
|
+
failOnce(error, { code: "RUN_WORKER_UNCAUGHT_EXCEPTION" });
|
|
1677
|
+
process.exit(1);
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
process.on("unhandledRejection", (reason) => {
|
|
1681
|
+
console.error("[boss-recommend-mcp] detached worker unhandled rejection", reason);
|
|
1682
|
+
const error = reason instanceof Error ? reason : new Error(normalizeText(reason) || "Unhandled promise rejection");
|
|
1683
|
+
failOnce(error, { code: "RUN_WORKER_UNHANDLED_REJECTION" });
|
|
1684
|
+
process.exit(1);
|
|
1685
|
+
});
|
|
1686
|
+
|
|
1687
|
+
for (const signal of ["SIGTERM", "SIGINT", "SIGHUP"]) {
|
|
1688
|
+
process.on(signal, () => {
|
|
1689
|
+
const error = new Error(`detached recommend worker received ${signal}`);
|
|
1690
|
+
console.error("[boss-recommend-mcp] detached worker received signal", signal);
|
|
1691
|
+
failOnce(error, { code: "RUN_WORKER_SIGNAL" });
|
|
1692
|
+
const signalExitCodes = { SIGHUP: 129, SIGINT: 130, SIGTERM: 143 };
|
|
1693
|
+
process.exit(signalExitCodes[signal] || 1);
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1569
1698
|
function buildWorkerLaunchFailedPayload(message) {
|
|
1570
1699
|
return {
|
|
1571
1700
|
status: "FAILED",
|
|
@@ -1993,7 +2122,14 @@ async function handleStartRunTool({ workspaceRoot, args }) {
|
|
|
1993
2122
|
let worker;
|
|
1994
2123
|
try {
|
|
1995
2124
|
worker = launchDetachedRunWorker({ runId });
|
|
1996
|
-
|
|
2125
|
+
const workerLogPaths = worker.workerLogPaths || {};
|
|
2126
|
+
safeUpdateRunState(runId, {
|
|
2127
|
+
pid: worker.pid || process.pid,
|
|
2128
|
+
resume: {
|
|
2129
|
+
worker_stdout_path: workerLogPaths.stdoutPath || getRunArtifacts(runId).worker_stdout_path,
|
|
2130
|
+
worker_stderr_path: workerLogPaths.stderrPath || getRunArtifacts(runId).worker_stderr_path
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
1997
2133
|
} catch (error) {
|
|
1998
2134
|
const failed = buildWorkerLaunchFailedPayload(error?.message || "无法启动 detached recommend worker。");
|
|
1999
2135
|
safeUpdateRunState(runId, {
|
|
@@ -2602,6 +2738,7 @@ const thisFilePath = fileURLToPath(import.meta.url);
|
|
|
2602
2738
|
if (process.argv[1] && path.resolve(process.argv[1]) === thisFilePath) {
|
|
2603
2739
|
const detachedWorkerOptions = parseDetachedWorkerOptions(process.argv.slice(2));
|
|
2604
2740
|
if (detachedWorkerOptions) {
|
|
2741
|
+
installDetachedWorkerFailureHandlers(detachedWorkerOptions.runId);
|
|
2605
2742
|
runDetachedWorker({
|
|
2606
2743
|
runId: detachedWorkerOptions.runId,
|
|
2607
2744
|
resumeRun: detachedWorkerOptions.resumeRun
|
|
@@ -2609,7 +2746,9 @@ if (process.argv[1] && path.resolve(process.argv[1]) === thisFilePath) {
|
|
|
2609
2746
|
if (!result?.ok) {
|
|
2610
2747
|
process.exitCode = 1;
|
|
2611
2748
|
}
|
|
2612
|
-
}).catch(() => {
|
|
2749
|
+
}).catch((error) => {
|
|
2750
|
+
console.error("[boss-recommend-mcp] detached worker failed", error);
|
|
2751
|
+
markDetachedWorkerFailed(detachedWorkerOptions.runId, error);
|
|
2613
2752
|
process.exitCode = 1;
|
|
2614
2753
|
});
|
|
2615
2754
|
} else {
|