devin-search-mcp 1.0.4 → 1.0.6

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/fetch.mjs +53 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "devin-search-mcp",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Devin AI 驱动的联网搜索与网页抓取 MCP 服务 (基于 Devin Desktop / CLI)",
5
5
  "main": "src/index.mjs",
6
6
  "type": "module",
package/src/fetch.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Devin 网页抓取与阅读模块
3
- * 调用 Devin CLI 的 webfetch 工具提取指定网页的清洁正文
3
+ * 严格调用 Devin CLI 的 webfetch 官方能力提取正文
4
+ * 遵循 Debug-First 准则:仅处理 retryable 官方重试,绝不引入隐式回退或静默降级代码,彻底暴露底层真实异常
4
5
  */
5
6
 
6
7
  import { execFile } from "node:child_process";
@@ -12,12 +13,14 @@ import { searchCache } from "./cache.mjs";
12
13
  * @param {Object} options
13
14
  * @param {string} options.url - 目标网页 URL
14
15
  * @param {"markdown"|"text"|"summary"} [options.extractMode="markdown"] - 提取格式
15
- * @param {number} [options.timeoutMs=35000] - 超时毫秒数
16
+ * @param {number} [options.timeoutMs=60000] - 超时毫秒数
17
+ * @param {number} [options.maxRetries=1] - retryable 瞬时网络错误重试次数
16
18
  */
17
19
  export async function executeWebFetch({
18
20
  url,
19
21
  extractMode = "markdown",
20
22
  timeoutMs = parseInt(process.env.DEVIN_TIMEOUT_MS || "60000", 10),
23
+ maxRetries = 1,
21
24
  }) {
22
25
  if (!url || typeof url !== "string" || !/^https?:\/\//i.test(url)) {
23
26
  throw new Error("请提供有效的 HTTP/HTTPS 网页 URL");
@@ -31,7 +34,7 @@ export async function executeWebFetch({
31
34
 
32
35
  const devinExe = findDevinExecutable();
33
36
  if (!devinExe) {
34
- throw new Error("未找到 Devin 可执行文件,请确认已安装 Devin 或设置 DEVIN_PATH 环境变量。");
37
+ throw new Error("未检测到本地 Devin 安装,请确认已安装 Devin 或配置 DEVIN_PATH 环境变量。");
35
38
  }
36
39
 
37
40
  let prompt = "";
@@ -43,29 +46,56 @@ export async function executeWebFetch({
43
46
  prompt = `请使用 webfetch 工具读取 ${url} 的内容,并将正文内容转换为整洁、规范的 Markdown 格式输出。不要添加任何额外多余的寒暄。`;
44
47
  }
45
48
 
46
- const output = await new Promise((resolve, reject) => {
47
- execFile(
48
- devinExe,
49
- ["--permission-mode", "dangerous", "--respect-workspace-trust", "false", "-p", prompt],
50
- {
51
- timeout: timeoutMs,
52
- maxBuffer: 10 * 1024 * 1024,
53
- encoding: "utf-8",
54
- env: { ...process.env, DEVIN_PERMISSION_MODE: "dangerous" },
55
- },
56
- (error, stdout, stderr) => {
57
- if (error) {
58
- if (error.killed) {
59
- reject(new Error(`网页读取超时(超过 ${timeoutMs / 1000} 秒): ${url}`));
60
- } else {
61
- reject(new Error(`网页读取失败: ${error.message}\n${stderr || ""}`));
49
+ const runDevinAttempt = () =>
50
+ new Promise((resolve, reject) => {
51
+ execFile(
52
+ devinExe,
53
+ ["--permission-mode", "dangerous", "--respect-workspace-trust", "false", "-p", prompt],
54
+ {
55
+ timeout: timeoutMs,
56
+ maxBuffer: 10 * 1024 * 1024,
57
+ encoding: "utf-8",
58
+ env: { ...process.env, DEVIN_PERMISSION_MODE: "dangerous" },
59
+ },
60
+ (error, stdout, stderr) => {
61
+ if (error) {
62
+ if (error.killed) {
63
+ reject(new Error(`网页读取超时(超过 ${timeoutMs / 1000} 秒): ${url}`));
64
+ } else {
65
+ reject(new Error(error.message + (stderr ? `\n${stderr}` : "")));
66
+ }
67
+ return;
62
68
  }
63
- return;
69
+ resolve(stdout.trim());
70
+ }
71
+ );
72
+ });
73
+
74
+ let output = "";
75
+ let lastErr = null;
76
+
77
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
78
+ try {
79
+ output = await runDevinAttempt();
80
+ if (output) break;
81
+ } catch (err) {
82
+ lastErr = err;
83
+ // 仅当官方返回 retryable: true 或连接瞬时重置时做退避重试
84
+ if (/retryable|ETIMEDOUT|ECONNRESET/i.test(err.message)) {
85
+ if (attempt < maxRetries) {
86
+ await new Promise((r) => setTimeout(r, 2000));
87
+ continue;
64
88
  }
65
- resolve(stdout.trim());
66
89
  }
67
- );
68
- });
90
+ // 非 retryable 错误不盲目重试,立即向上抛出
91
+ throw err;
92
+ }
93
+ }
94
+
95
+ // 彻底暴露问题:如果没有产出,坚决抛出真实异常,严禁任何伪造成功或隐式本地降级
96
+ if (!output) {
97
+ throw lastErr || new Error(`Devin 抓取网页未返回任何内容: ${url}`);
98
+ }
69
99
 
70
100
  const resultPayload = {
71
101
  url,