@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/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 child = spawnProcessImpl(process.execPath, childArgs, {
1558
- detached: true,
1559
- stdio: "ignore",
1560
- windowsHide: true,
1561
- env: process.env
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
- safeUpdateRunState(runId, { pid: worker.pid || process.pid });
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 {