deepspider 0.3.2 → 0.5.0

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 (39) hide show
  1. package/README.md +17 -21
  2. package/package.json +4 -2
  3. package/src/agent/core/PanelBridge.js +34 -8
  4. package/src/agent/core/StreamHandler.js +142 -26
  5. package/src/agent/index.js +72 -14
  6. package/src/agent/middleware/memoryFlush.js +48 -0
  7. package/src/agent/middleware/report.js +77 -45
  8. package/src/agent/middleware/subagent.js +4 -1
  9. package/src/agent/middleware/toolAvailability.js +37 -0
  10. package/src/agent/middleware/toolGuard.js +141 -31
  11. package/src/agent/prompts/system.js +144 -1
  12. package/src/agent/run.js +127 -14
  13. package/src/agent/sessions.js +88 -0
  14. package/src/agent/skills/anti-detect/SKILL.md +89 -14
  15. package/src/agent/skills/captcha/SKILL.md +93 -19
  16. package/src/agent/skills/crawler/SKILL.md +86 -0
  17. package/src/agent/skills/crawler/evolved.md +14 -13
  18. package/src/agent/skills/general/evolved.md +12 -1
  19. package/src/agent/skills/js2python/SKILL.md +40 -0
  20. package/src/agent/skills/js2python/evolved.md +13 -1
  21. package/src/agent/skills/sandbox/SKILL.md +33 -0
  22. package/src/agent/skills/sandbox/evolved.md +12 -5
  23. package/src/agent/skills/static-analysis/SKILL.md +39 -0
  24. package/src/agent/skills/static-analysis/evolved.md +88 -2
  25. package/src/agent/subagents/anti-detect.js +27 -5
  26. package/src/agent/subagents/captcha.js +28 -9
  27. package/src/agent/subagents/crawler.js +26 -79
  28. package/src/agent/subagents/factory.js +24 -4
  29. package/src/agent/subagents/js2python.js +18 -16
  30. package/src/agent/tools/analysis.js +17 -7
  31. package/src/agent/tools/browser.js +26 -13
  32. package/src/agent/tools/crawler.js +1 -1
  33. package/src/agent/tools/crawlerGenerator.js +2 -2
  34. package/src/agent/tools/evolve.js +47 -8
  35. package/src/agent/tools/index.js +7 -3
  36. package/src/agent/tools/patch.js +1 -1
  37. package/src/agent/tools/store.js +1 -1
  38. package/src/browser/client.js +5 -1
  39. package/src/browser/ui/analysisPanel.js +72 -0
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * DeepSpider - 报告中间件
3
- * 在 Agent 执行完成后自动检测并准备报告
3
+ * 检测文件保存事件,触发报告显示和面板通知
4
4
  */
5
5
 
6
6
  import { createMiddleware } from 'langchain';
@@ -14,89 +14,121 @@ const reportStateSchema = z.object({
14
14
  });
15
15
 
16
16
  /**
17
- * 检测并触发报告显示
17
+ * 从工具返回值中提取 .md 文件路径
18
+ * 兼容两种工具的返回格式:
19
+ * artifact_save: { success, path: "/xxx/analysis.md" }
20
+ * save_analysis_report: { success, paths: { markdown: "/xxx/analysis.md" }, dir }
18
21
  */
19
- async function detectAndTriggerReport(result, onReportReady) {
22
+ function extractMdPath(content) {
23
+ if (!content?.success) return null;
24
+
25
+ // artifact_save 格式
26
+ if (content.path?.endsWith('.md')) return content.path;
27
+
28
+ // save_analysis_report 格式
29
+ if (content.paths?.markdown) return content.paths.markdown;
30
+
31
+ return null;
32
+ }
33
+
34
+ /**
35
+ * 从工具返回值中提取已保存的文件信息(用于面板通知)
36
+ * 返回 { path, type } 或 null
37
+ */
38
+ function extractSavedFile(content) {
39
+ if (!content?.success) return null;
40
+
41
+ // artifact_save: 单文件
42
+ if (content.path) {
43
+ const ext = content.path.split('.').pop();
44
+ return { path: content.path, type: ext };
45
+ }
46
+
47
+ // save_analysis_report: 多文件
48
+ if (content.paths) {
49
+ return { path: content.dir || content.paths.markdown, type: 'report' };
50
+ }
51
+
52
+ return null;
53
+ }
54
+
55
+ function parseContent(result) {
20
56
  try {
21
- const content = typeof result?.content === 'string'
57
+ return typeof result?.content === 'string'
22
58
  ? JSON.parse(result.content)
23
59
  : result?.content;
24
-
25
- if (content?.success && content?.path?.endsWith('.md')) {
26
- console.log('[reportMiddleware] 检测到 .md 文件:', content.path);
27
-
28
- if (onReportReady) {
29
- await onReportReady(content.path);
30
- }
31
- return true;
32
- }
33
60
  } catch {
34
- // 解析失败,忽略
61
+ return null;
35
62
  }
36
- return false;
37
63
  }
38
64
 
39
65
  /**
40
66
  * 创建报告中间件
41
- * wrapToolCall 中检测 artifact_save 工具调用结果,立即触发报告
42
- * 同时在 afterModel 和 afterAgent 中保留检测逻辑作为备选
67
+ * 监听 artifact_save save_analysis_report,触发报告显示 + 面板通知
43
68
  */
44
69
  export function createReportMiddleware(options = {}) {
45
- const { onReportReady } = options;
70
+ const { onReportReady, onFileSaved } = options;
71
+
72
+ const WATCHED_TOOLS = new Set(['artifact_save', 'save_analysis_report']);
46
73
 
47
74
  return createMiddleware({
48
75
  name: 'reportMiddleware',
49
76
  stateSchema: reportStateSchema,
50
77
 
51
- // 工具调用包装器:在 artifact_save 完成时立即检测
52
78
  wrapToolCall: async (request, handler) => {
53
79
  const toolName = request.tool?.name ?? request.toolCall?.name;
54
80
  const result = await handler(request);
55
81
 
56
- // 检测 artifact_save 工具返回的 .md 文件
57
- if (toolName === 'artifact_save') {
58
- await detectAndTriggerReport(result, onReportReady);
82
+ if (!WATCHED_TOOLS.has(toolName)) return result;
83
+
84
+ const content = parseContent(result);
85
+ if (!content) return result;
86
+
87
+ // 检测 .md 文件 → 触发报告显示
88
+ const mdPath = extractMdPath(content);
89
+ if (mdPath) {
90
+ console.log('[reportMiddleware] 检测到报告文件:', mdPath);
91
+ if (onReportReady) {
92
+ await onReportReady(mdPath);
93
+ }
94
+ }
95
+
96
+ // 通知文件已保存(面板可显示提示)
97
+ const saved = extractSavedFile(content);
98
+ if (saved && onFileSaved) {
99
+ await onFileSaved(saved);
59
100
  }
60
101
 
61
102
  return result;
62
103
  },
63
104
 
64
- // 模型调用后,检测工具调用结果(备选方案)
105
+ // 备选:afterModel 检测 ToolMessage 中的报告文件
65
106
  afterModel: (state) => {
66
107
  const messages = state.messages;
67
- if (!messages || messages.length === 0) return undefined;
108
+ if (!messages?.length) return undefined;
68
109
 
69
- // 查找最近的 ToolMessage
70
110
  for (let i = messages.length - 1; i >= 0; i--) {
71
111
  const msg = messages[i];
72
- if (ToolMessage.isInstance(msg)) {
73
- try {
74
- const content = typeof msg.content === 'string'
75
- ? JSON.parse(msg.content)
76
- : msg.content;
77
-
78
- // 检测是否是 artifact_save 写入的 .md 文件
79
- if (content.success && content.path?.endsWith('.md')) {
80
- console.log('[reportMiddleware] afterModel 检测到 .md 文件:', content.path);
81
- return { lastWrittenMdFile: content.path };
82
- }
83
- } catch {
84
- // 解析失败,忽略
85
- }
112
+ if (!ToolMessage.isInstance(msg)) continue;
113
+
114
+ const content = parseContent(msg);
115
+ if (!content) continue;
116
+
117
+ const mdPath = extractMdPath(content);
118
+ if (mdPath) {
119
+ console.log('[reportMiddleware] afterModel 检测到报告:', mdPath);
120
+ return { lastWrittenMdFile: mdPath };
86
121
  }
87
122
  }
88
123
  return undefined;
89
124
  },
90
125
 
91
- // Agent 执行完成后(streamEvents 模式下可能不被调用)
126
+ // streamEvents 模式下可能不被调用
92
127
  afterAgent: async (state) => {
93
- const mdFile = state.lastWrittenMdFile;
94
-
95
- if (mdFile) {
96
- console.log('[reportMiddleware] afterAgent: 准备显示报告:', mdFile);
128
+ if (state.lastWrittenMdFile) {
129
+ console.log('[reportMiddleware] afterAgent: 报告就绪:', state.lastWrittenMdFile);
97
130
  return { reportReady: true };
98
131
  }
99
-
100
132
  return undefined;
101
133
  },
102
134
  });
@@ -175,7 +175,10 @@ function createEnhancedTaskTool(options) {
175
175
  schema: z.object({
176
176
  description: z.string().describe('The task to execute with the selected agent'),
177
177
  subagent_type: z.string().describe(`Name of the agent to use. Available: ${availableTypes}`),
178
- context: z.record(z.string(), z.string()).optional().describe('Structured key-value context to pass to the subagent (e.g. site, requestId, targetParam)'),
178
+ // NOTE: 不用 z.record() 因为 Zod v4 toJSONSchema 会生成 propertyNames,
179
+ // 而 Anthropic API 不支持 propertyNames 关键字
180
+ // 改用 z.object({}) + additionalProperties 模式
181
+ context: z.object({}).passthrough().optional().describe('Structured key-value context to pass to the subagent (e.g. site, requestId, targetParam)'),
179
182
  }),
180
183
  },
181
184
  );
@@ -0,0 +1,37 @@
1
+ /**
2
+ * DeepSpider - 工具可用性拦截中间件
3
+ * 拦截已知不可用的 stub/缺依赖工具,返回友好错误而非无意义结果
4
+ */
5
+
6
+ import { createMiddleware } from 'langchain';
7
+ import { ToolMessage } from '@langchain/core/messages';
8
+
9
+ // 不可用工具清单及原因
10
+ const UNAVAILABLE_TOOLS = {
11
+ captcha_ocr: '需要集成 OCR 服务(ddddocr 或打码平台),当前为占位实现',
12
+ captcha_slide_detect: '需要集成缺口检测算法(OpenCV),当前为占位实现',
13
+ proxy_test: '缺少依赖 https-proxy-agent,无法测试代理',
14
+ };
15
+
16
+ export function createToolAvailabilityMiddleware() {
17
+ return createMiddleware({
18
+ name: 'toolAvailabilityMiddleware',
19
+
20
+ wrapToolCall: async (request, handler) => {
21
+ const toolName = request.tool?.name ?? request.toolCall?.name;
22
+ const reason = UNAVAILABLE_TOOLS[toolName];
23
+
24
+ if (reason) {
25
+ return new ToolMessage({
26
+ content: JSON.stringify({
27
+ success: false,
28
+ error: `工具 ${toolName} 当前不可用:${reason}。请使用其他策略完成任务。`,
29
+ }),
30
+ tool_call_id: request.toolCall.id,
31
+ });
32
+ }
33
+
34
+ return handler(request);
35
+ },
36
+ });
37
+ }
@@ -1,71 +1,181 @@
1
1
  /**
2
- * DeepSpider - 工具连续失败检测中间件
3
- * 检测同一工具连续失败(超时、错误),在 ToolMessage 中追加警告引导 LLM 换策略
2
+ * DeepSpider - 工具调用守卫中间件
3
+ * 1. 连续失败检测(原有)
4
+ * 2. 重复调用检测:同一工具相同参数连续调用
5
+ * 3. 双工具循环检测:A→B→A→B 交替调用且参数不变
4
6
  */
5
7
 
6
8
  import { createMiddleware } from 'langchain';
9
+ import { createHash } from 'crypto';
7
10
 
8
- // 默认配置
11
+ // ── 默认配置 ──────────────────────────────────────────────
9
12
  const DEFAULTS = {
10
- maxConsecutiveFailures: 3, // 连续失败 N 次后触发强警告
11
- warnAfter: 2, // 连续失败 N 次后开始追加提示
12
- resetOnSuccess: true, // 成功时重置计数
13
+ // 连续失败检测
14
+ maxConsecutiveFailures: 3,
15
+ warnAfter: 2,
16
+ resetOnSuccess: true,
17
+
18
+ // 重复调用检测阈值
19
+ repeatWarnAt: 3, // ≥3 次追加警告
20
+ repeatStrongWarnAt: 5, // ≥5 次强警告 + 换策略指令
21
+ repeatBlockAt: 8, // ≥8 次阻断
22
+
23
+ // 双工具循环检测(A→B→A→B 算 2 轮)
24
+ loopWarnAt: 2, // ≥2 轮警告
25
+ loopStrongWarnAt: 3, // ≥3 轮强警告
26
+ loopBlockAt: 5, // ≥5 轮阻断
13
27
  };
14
28
 
15
- /**
16
- * 判断 ToolMessage 是否表示失败
17
- */
29
+ // ── 工具参数签名 ─────────────────────────────────────────
30
+ function argsSignature(toolCall) {
31
+ const args = toolCall?.args ?? toolCall?.input ?? {};
32
+ const str = JSON.stringify(args, Object.keys(args).sort());
33
+ return createHash('md5').update(str).digest('hex').slice(0, 12);
34
+ }
35
+
36
+ // ── 判断 ToolMessage 是否表示失败 ────────────────────────
18
37
  function isToolFailure(result) {
19
- // ToolMessage.status === 'error' (toolRetryMiddleware 设置)
20
38
  if (result?.status === 'error') return true;
21
-
22
- // 工具返回的 JSON 中 success === false
23
39
  const content = typeof result?.content === 'string' ? result.content : '';
24
40
  if (!content.startsWith('{')) return false;
25
41
  try {
26
- const parsed = JSON.parse(content);
27
- return parsed.success === false;
42
+ return JSON.parse(content).success === false;
28
43
  } catch {
29
44
  return false;
30
45
  }
31
46
  }
32
47
 
48
+ // ── 构造阻断 ToolMessage ─────────────────────────────────
49
+ function makeBlockedResult(request, reason) {
50
+ const name = request.tool?.name || request.toolCall?.name || 'unknown';
51
+ return {
52
+ type: 'tool',
53
+ name,
54
+ content: `🚫 调用被阻断:${reason}\n你必须立即停止当前策略,换用完全不同的工具或方法。如果已经获得足够信息,请直接总结并返回结果。`,
55
+ tool_call_id: request.toolCall?.id || `blocked_${Date.now()}`,
56
+ status: 'error',
57
+ };
58
+ }
59
+
60
+ function appendToContent(result, text) {
61
+ if (typeof result.content === 'string') {
62
+ result.content += text;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * 规范化双工具 pair key(字典序排列,避免 A|B ≠ B|A)
68
+ */
69
+ function pairKey(nameA, sigA, nameB, sigB) {
70
+ const a = `${nameA}:${sigA}`;
71
+ const b = `${nameB}:${sigB}`;
72
+ return a <= b ? `${a}|${b}` : `${b}|${a}`;
73
+ }
74
+
33
75
  /**
34
- * 创建工具连续失败检测中间件
76
+ * 创建工具调用守卫中间件
77
+ * - 连续失败检测(原有逻辑)
78
+ * - 重复调用检测(同工具同参数)
79
+ * - 双工具循环检测(A↔B 交替,用规范化 pair key 避免顺序翻转)
35
80
  */
36
81
  export function createToolGuardMiddleware(options = {}) {
37
- const config = { ...DEFAULTS, ...options };
82
+ const cfg = { ...DEFAULTS, ...options };
38
83
 
39
- // toolName → { count, lastArgs }
40
- const failureTracker = new Map();
84
+ const failureTracker = new Map(); // toolName → { count }
85
+ const repeatTracker = new Map(); // toolName → { sig, count }
86
+
87
+ // 循环检测:只需记住上一次调用
88
+ let lastCall = null; // { name, sig }
89
+ let loopCount = 0;
90
+ let loopKey = null; // 规范化 pair key
41
91
 
42
92
  return createMiddleware({
43
93
  name: 'toolGuardMiddleware',
44
94
 
45
95
  wrapToolCall: async (request, handler) => {
46
96
  const toolName = request.tool?.name ?? request.toolCall?.name;
97
+ if (!toolName) return handler(request);
98
+
99
+ const sig = argsSignature(request.toolCall);
100
+
101
+ // ── 1) 重复调用检测(阻断优先) ──
102
+ const prev = repeatTracker.get(toolName);
103
+ if (prev && prev.sig === sig) {
104
+ prev.count++;
105
+ } else {
106
+ repeatTracker.set(toolName, { sig, count: 1 });
107
+ }
108
+ const repeatCount = repeatTracker.get(toolName).count;
109
+
110
+ if (repeatCount >= cfg.repeatBlockAt) {
111
+ return makeBlockedResult(request,
112
+ `工具 ${toolName} 以相同参数连续调用 ${repeatCount} 次(上限 ${cfg.repeatBlockAt})`);
113
+ }
114
+
115
+ // ── 2) 双工具循环检测(阻断优先) ──
116
+ // 用规范化 pair key 检测 A→B→A→B 模式,不受窗口顺序影响
117
+ if (lastCall && lastCall.name !== toolName) {
118
+ const key = pairKey(lastCall.name, lastCall.sig, toolName, sig);
119
+ if (loopKey === key) {
120
+ loopCount++;
121
+ } else if (loopKey === null) {
122
+ // 首次出现双工具交替,开始追踪
123
+ loopKey = key;
124
+ loopCount = 1;
125
+ } else {
126
+ // 模式变了,重置
127
+ loopKey = key;
128
+ loopCount = 1;
129
+ }
130
+ } else if (lastCall && lastCall.name === toolName) {
131
+ // 同一工具连续调用不算循环,但也不重置(可能是 A→A→B→A→B)
132
+ // 只有当出现新的不同 pair 时才重置
133
+ }
134
+
135
+ if (loopCount >= cfg.loopBlockAt) {
136
+ lastCall = { name: toolName, sig };
137
+ return makeBlockedResult(request,
138
+ `检测到双工具循环模式已持续 ${loopCount} 轮(上限 ${cfg.loopBlockAt})`);
139
+ }
140
+
141
+ // 更新 lastCall(在执行工具之前,确保阻断路径也更新)
142
+ lastCall = { name: toolName, sig };
143
+
144
+ // ── 3) 执行工具 ──
47
145
  const result = await handler(request);
48
146
 
49
- if (!toolName) return result;
147
+ // ── 4) 重复调用警告(追加到结果) ──
148
+ if (repeatCount >= cfg.repeatStrongWarnAt) {
149
+ appendToContent(result,
150
+ `\n\n🚫 工具 ${toolName} 以相同参数已连续调用 ${repeatCount} 次(阻断阈值 ${cfg.repeatBlockAt})。你必须立即换用其他策略,不要再以相同参数调用此工具。`);
151
+ } else if (repeatCount >= cfg.repeatWarnAt) {
152
+ appendToContent(result,
153
+ `\n\n⚠️ 工具 ${toolName} 以相同参数已连续调用 ${repeatCount} 次。建议考虑替代方案,避免重复操作。`);
154
+ }
155
+
156
+ // ── 5) 循环模式警告 ──
157
+ if (loopCount >= cfg.loopStrongWarnAt && loopCount < cfg.loopBlockAt) {
158
+ appendToContent(result,
159
+ `\n\n🚫 检测到双工具循环模式已持续 ${loopCount} 轮(阻断阈值 ${cfg.loopBlockAt})。你必须立即停止这个循环,换用完全不同的分析策略。`);
160
+ } else if (loopCount >= cfg.loopWarnAt && loopCount < cfg.loopStrongWarnAt) {
161
+ appendToContent(result,
162
+ `\n\n⚠️ 检测到可能的双工具循环模式(${loopCount} 轮)。如果你在重复相同操作,请考虑换一种方法。`);
163
+ }
50
164
 
165
+ // ── 6) 连续失败检测(原有逻辑) ──
51
166
  if (isToolFailure(result)) {
52
167
  const tracker = failureTracker.get(toolName) || { count: 0 };
53
168
  tracker.count++;
54
169
  failureTracker.set(toolName, tracker);
55
170
 
56
- // 追加警告到 ToolMessage content
57
- if (tracker.count >= config.maxConsecutiveFailures) {
58
- const warning = `\n\n🚫 工具 ${toolName} 已连续失败 ${tracker.count} 次。请停止使用该工具重试相同逻辑,必须换用其他工具或策略。`;
59
- if (typeof result.content === 'string') {
60
- result.content += warning;
61
- }
62
- } else if (tracker.count >= config.warnAfter) {
63
- const warning = `\n\n⚠️ 工具 ${toolName} 已连续失败 ${tracker.count} 次(上限 ${config.maxConsecutiveFailures})。如果继续失败将被限制使用,建议考虑替代方案。`;
64
- if (typeof result.content === 'string') {
65
- result.content += warning;
66
- }
171
+ if (tracker.count >= cfg.maxConsecutiveFailures) {
172
+ appendToContent(result,
173
+ `\n\n🚫 工具 ${toolName} 已连续失败 ${tracker.count} 次。请停止使用该工具重试相同逻辑,必须换用其他工具或策略。`);
174
+ } else if (tracker.count >= cfg.warnAfter) {
175
+ appendToContent(result,
176
+ `\n\n⚠️ 工具 ${toolName} 已连续失败 ${tracker.count} 次(上限 ${cfg.maxConsecutiveFailures})。如果继续失败将被限制使用,建议考虑替代方案。`);
67
177
  }
68
- } else if (config.resetOnSuccess) {
178
+ } else if (cfg.resetOnSuccess) {
69
179
  failureTracker.delete(toolName);
70
180
  }
71
181
 
@@ -8,6 +8,61 @@
8
8
  */
9
9
  export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你的目标是帮助用户分析网站、理解加密逻辑、回答爬虫相关问题。
10
10
 
11
+ ## 核心工作循环(Agent Loop)
12
+
13
+ 每次收到用户请求后,按以下循环执行,直到任务完成:
14
+
15
+ \`\`\`
16
+ 分析 → 规划 → 执行 → 验证 → 反思
17
+ ↑ ↓
18
+ └──────── 未通过则回到 ────────┘
19
+ \`\`\`
20
+
21
+ 1. **分析**:理解用户意图,收集已有信息(已捕获的请求、memo、todo 状态)
22
+ 2. **规划**:拆解任务为具体步骤,创建/更新 todo 清单
23
+ 3. **执行**:调用工具或委托子代理,优先并行调用无依赖的操作
24
+ 4. **验证**:检查执行结果是否符合预期,端到端验证代码可运行
25
+ 5. **反思**:总结本轮收获,save_memo 保存关键进度,判断是否需要下一轮
26
+
27
+ **每轮只推进一步,不要跳步。验证未通过不要继续推进。**
28
+
29
+ ## Think/Reflect — 关键决策前暂停
30
+
31
+ 遇到以下场景时,必须先在回复中输出思考过程,再采取行动:
32
+
33
+ - 用户需求模糊或有多种理解方式时
34
+ - 前一步执行结果与预期不符时
35
+ - 即将委托子代理执行不可逆操作时
36
+ - 连续 2 次工具调用失败时
37
+ - 需要在多个候选方案中选择时
38
+
39
+ 格式:先用一段文字说明你的判断和理由,再调用工具。不要默默行动。
40
+
41
+ ## 信息优先级
42
+
43
+ 做决策时,按以下优先级使用信息:
44
+
45
+ 1. **已捕获的数据**(浏览器拦截的请求/响应、Hook 记录)— 最可靠
46
+ 2. **用户提供的信息**(选中的元素、补充说明)— 直接采信
47
+ 3. **工具实时获取**(run_node_code / run_python_code 验证、get_request_detail 查看)— 需验证
48
+ 4. **模型推断**(基于经验猜测加密类型、参数含义)— 仅作参考,必须验证
49
+
50
+ **禁止仅凭推断就下结论。所有关键判断必须有数据支撑。**
51
+
52
+ ## 代码执行能力
53
+
54
+ 你有两个代码执行工具可用:
55
+
56
+ | 工具 | 用途 | 示例 |
57
+ |------|------|------|
58
+ | \`run_node_code\` | JS代码快速验证、加密库调用测试 | \`const CryptoJS = require('crypto-js'); ...\` |
59
+ | \`run_python_code\` | Python代码验证、加密算法对比、数据处理 | \`from gmssl import sm2; ...\` |
60
+
61
+ **使用原则**:
62
+ - 快速验证假设时优先用 \`run_node_code\`(JS环境与浏览器更接近)
63
+ - 需要验证 Python 加密实现时用 \`run_python_code\`
64
+ - 复杂的加密代码转换仍需委托 js2python 子代理
65
+
11
66
  ## 浏览器面板
12
67
 
13
68
  当用户通过浏览器面板发送消息时(消息以"[浏览器已就绪]"开头):
@@ -43,6 +98,44 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
43
98
  - **description 包含分析结论**:调用栈摘要、可疑参数、加密特征
44
99
  - 示例:"调用栈:send@match/1:652 → oo0O0@match/1:960,可疑参数:m=<32位hex>"
45
100
 
101
+ ## 自主数据搜寻
102
+
103
+ 当用户描述数据抓取目标(如"抓取爆仓数据"、"获取商品列表"),而非选中具体元素时,
104
+ 你需要自主搜寻目标数据的 API 接口。
105
+
106
+ ### 识别条件
107
+
108
+ 同时满足以下条件时进入搜寻模式:
109
+ 1. 消息以 \`[浏览器已就绪]\` 开头
110
+ 2. 用户描述的是数据目标(如"抓取XX数据"),而非技术指令(如"分析这个加密参数")
111
+ 3. 没有附带已选中的元素
112
+
113
+ ### 搜寻流程
114
+
115
+ **第一步:搜索已捕获的请求**
116
+ 1. 从用户描述中提取关键词(中英文都试,如"爆仓"+"liquidation")
117
+ 2. 用 \`search_in_responses\` 搜索
118
+ 3. 找到匹配 → 跳到第三步
119
+
120
+ **第二步:交互触发请求**(仅在第一步无结果时)
121
+ 1. \`get_interactive_elements\` 了解页面结构
122
+ 2. 尝试操作触发 API:\`scroll_page\` 滚动、\`click_element\` 点击选项卡/按钮
123
+ 3. 每次交互后重新搜索检查新请求
124
+ 4. 最多 3 轮,避免无限循环
125
+
126
+ **第三步:展示候选接口**
127
+ 1. \`get_request_detail\` 查看候选请求详情
128
+ 2. 向用户列出候选接口,每个包含:接口地址、方法、返回数据摘要
129
+ 3. 让用户确认目标接口后再进入下一步
130
+
131
+ **第四步:用户确认后执行完整分析**
132
+ 按照完整分析流程执行:加密检测 → 逆向分析(如需)→ 端到端验证 → 生成爬虫脚本
133
+
134
+ ### 注意事项
135
+ - 搜寻阶段只做发现和确认,不提前进入逆向分析
136
+ - 多个候选接口时全部列出让用户选择
137
+ - 3 轮交互仍无结果时,建议用户手动操作页面触发目标请求后重试
138
+
46
139
  ## 禁止行为(必须遵守)
47
140
 
48
141
  **你禁止自己编写代码**。当用户需要代码时,你必须:
@@ -64,7 +157,55 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
64
157
 
65
158
  **错误示例**:
66
159
  - ❌ 在回复中直接写 "\`\`\`python\\nimport requests\\n..." 代码块
67
- - ❌ 说"这是一个简单的示例代码"然后输出代码`;
160
+ - ❌ 说"这是一个简单的示例代码"然后输出代码
161
+
162
+ ## 工具并行调用
163
+
164
+ 无数据依赖的工具调用必须并行,不要串行等待。
165
+
166
+ **并行场景**:同时搜索多个关键词、同时获取多个请求详情、同时分析多个脚本。
167
+ **原则**:除非 A 的输出是 B 的输入,否则默认并行。
168
+
169
+ ## 循环检测与脱困
170
+
171
+ **同一操作最多重试 3 次。** 第 3 次失败后必须:
172
+
173
+ 1. 停下来,分析失败模式(不是简单重试)
174
+ 2. 尝试替代方案(换工具、换参数、换思路)
175
+ 3. 如果替代方案也失败,向用户说明情况并请求指导
176
+
177
+ **常见脱困策略**:
178
+ - 搜索无结果 → 换关键词(中英文、缩写、同义词)
179
+ - 工具报错 → 检查参数格式,查看错误信息中的线索
180
+ - 子代理失败 → 检查传递的 context 是否完整,简化任务描述重试
181
+ - 加密分析卡住 → 换入口点,或建议用户手动触发更多请求
182
+
183
+ **禁止**:无脑重复同一个失败的操作。
184
+
185
+ ## 经验记忆(save_memo)
186
+
187
+ save_memo 不只是保存进度,更是积累可复用的经验。
188
+
189
+ **必须保存的时机**:
190
+ 1. 发现关键接口后 — 接口地址、方法、参数结构
191
+ 2. 识别加密算法后 — 算法类型、关键函数名、调用链路
192
+ 3. 子代理返回结果后 — 核心结论(不要等到最后)
193
+ 4. 每完成一个分析阶段后 — 阶段总结和下一步计划
194
+ 5. **发现可复用的模式时** — 如"该网站的签名算法是 HMAC-SHA256 + 时间戳"
195
+
196
+ **memo 格式建议**:用结构化的 key-value,方便后续检索。
197
+ 不要等系统提醒。主动保存是你的责任,丢失进度意味着重复工作。
198
+
199
+ ## 任务规划(todo)
200
+
201
+ 多步骤任务必须在开始前创建 todo 清单,执行中实时更新:
202
+
203
+ - **开始前**:拆解为具体步骤,创建 todo
204
+ - **执行中**:每完成一步立即标记,不要攒批
205
+ - **遇到变化时**:及时更新或新增 todo 项
206
+ - **完成后**:确认所有 todo 项已完成或标记为跳过(附原因)
207
+
208
+ todo 是你的工作记忆,不用 todo 规划的复杂任务容易遗漏步骤。`;
68
209
 
69
210
  /**
70
211
  * 完整分析专用提示 - 仅在用户请求完整分析时使用
@@ -72,6 +213,8 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
72
213
  export const fullAnalysisPrompt = `
73
214
  ## 完整分析任务要求
74
215
 
216
+ 如果目标请求已在搜寻阶段确认,跳过步骤1直接从步骤2(识别加密类型)开始。
217
+
75
218
  这是一个完整分析任务,你需要完成以下所有步骤:
76
219
 
77
220
  ### 分析思路