deepspider 0.3.0 → 0.3.2

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 (84) hide show
  1. package/.env.example +3 -0
  2. package/README.md +13 -13
  3. package/package.json +6 -6
  4. package/src/agent/core/PanelBridge.js +29 -77
  5. package/src/agent/core/StreamHandler.js +139 -14
  6. package/src/agent/index.js +51 -12
  7. package/src/agent/logger.js +184 -9
  8. package/src/agent/middleware/report.js +42 -16
  9. package/src/agent/middleware/subagent.js +233 -0
  10. package/src/agent/middleware/toolGuard.js +77 -0
  11. package/src/agent/middleware/validationWorkflow.js +171 -0
  12. package/src/agent/prompts/system.js +181 -59
  13. package/src/agent/run.js +41 -6
  14. package/src/agent/skills/crawler/SKILL.md +64 -3
  15. package/src/agent/skills/crawler/evolved.md +9 -1
  16. package/src/agent/skills/dynamic-analysis/SKILL.md +74 -7
  17. package/src/agent/skills/env/SKILL.md +75 -0
  18. package/src/agent/skills/evolve.js +0 -3
  19. package/src/agent/skills/sandbox/SKILL.md +35 -0
  20. package/src/agent/skills/static-analysis/SKILL.md +98 -2
  21. package/src/agent/subagents/anti-detect.js +10 -20
  22. package/src/agent/subagents/captcha.js +7 -19
  23. package/src/agent/subagents/crawler.js +25 -37
  24. package/src/agent/subagents/factory.js +109 -9
  25. package/src/agent/subagents/index.js +4 -13
  26. package/src/agent/subagents/js2python.js +7 -19
  27. package/src/agent/subagents/reverse.js +180 -0
  28. package/src/agent/tools/analysis.js +84 -1
  29. package/src/agent/tools/anti-detect.js +5 -2
  30. package/src/agent/tools/browser.js +160 -0
  31. package/src/agent/tools/captcha.js +1 -1
  32. package/src/agent/tools/capture.js +24 -3
  33. package/src/agent/tools/correlate.js +129 -15
  34. package/src/agent/tools/crawler.js +2 -1
  35. package/src/agent/tools/crawlerGenerator.js +90 -0
  36. package/src/agent/tools/debug.js +43 -6
  37. package/src/agent/tools/evolve.js +6 -3
  38. package/src/agent/tools/extractor.js +5 -1
  39. package/src/agent/tools/file.js +16 -7
  40. package/src/agent/tools/generateHook.js +66 -0
  41. package/src/agent/tools/hookManager.js +19 -9
  42. package/src/agent/tools/index.js +33 -20
  43. package/src/agent/tools/nodejs.js +41 -6
  44. package/src/agent/tools/python.js +4 -4
  45. package/src/agent/tools/report.js +2 -2
  46. package/src/agent/tools/runtime.js +1 -1
  47. package/src/agent/tools/sandbox.js +21 -1
  48. package/src/agent/tools/scratchpad.js +70 -0
  49. package/src/agent/tools/tracing.js +26 -0
  50. package/src/agent/tools/verifyAlgorithm.js +117 -0
  51. package/src/analyzer/EncryptionAnalyzer.js +2 -2
  52. package/src/browser/EnvBridge.js +27 -13
  53. package/src/browser/client.js +124 -18
  54. package/src/browser/collector.js +101 -22
  55. package/src/browser/defaultHooks.js +3 -1
  56. package/src/browser/hooks/index.js +5 -0
  57. package/src/browser/interceptors/AntiDebugInterceptor.js +132 -0
  58. package/src/browser/interceptors/NetworkInterceptor.js +77 -13
  59. package/src/browser/interceptors/ScriptInterceptor.js +34 -9
  60. package/src/browser/interceptors/index.js +1 -0
  61. package/src/browser/ui/analysisPanel.js +469 -464
  62. package/src/cli/commands/config.js +11 -3
  63. package/src/config/paths.js +9 -1
  64. package/src/config/settings.js +7 -1
  65. package/src/core/PatchGenerator.js +26 -6
  66. package/src/core/Sandbox.js +140 -3
  67. package/src/env/EnvCodeGenerator.js +60 -88
  68. package/src/env/modules/bom/history.js +6 -0
  69. package/src/env/modules/bom/location.js +6 -0
  70. package/src/env/modules/bom/navigator.js +13 -0
  71. package/src/env/modules/bom/screen.js +6 -0
  72. package/src/env/modules/bom/storage.js +7 -0
  73. package/src/env/modules/dom/document.js +14 -0
  74. package/src/env/modules/dom/event.js +4 -0
  75. package/src/env/modules/index.js +27 -10
  76. package/src/env/modules/webapi/fetch.js +4 -0
  77. package/src/env/modules/webapi/url.js +4 -0
  78. package/src/env/modules/webapi/xhr.js +8 -0
  79. package/src/store/DataStore.js +130 -47
  80. package/src/store/Store.js +2 -1
  81. package/src/agent/subagents/dynamic.js +0 -64
  82. package/src/agent/subagents/env-agent.js +0 -82
  83. package/src/agent/subagents/sandbox.js +0 -55
  84. package/src/agent/subagents/static.js +0 -66
@@ -3,19 +3,107 @@
3
3
  * 统一子代理创建,自动注入公共配置
4
4
  */
5
5
 
6
+ import { createMiddleware, toolRetryMiddleware, contextEditingMiddleware, ClearToolUsesEdit } from 'langchain';
6
7
  import { createSkillsMiddleware } from 'deepagents';
7
8
  import { SKILLS, skillsBackend } from '../skills/config.js';
8
9
  import { createFilterToolsMiddleware } from '../middleware/filterTools.js';
9
10
  import { evolveTools } from '../tools/evolve.js';
10
11
 
12
+ /**
13
+ * 子代理工具调用上限
14
+ * 正常任务 30-50 次足够,80 留有余量但能防止无限循环
15
+ */
16
+ const SUBAGENT_RUN_LIMIT = 80;
17
+
18
+ /**
19
+ * 子代理执行纪律提示(精简版)
20
+ * 强制纪律通过 middleware 实现,而非提示词
21
+ */
22
+ export const SUBAGENT_DISCIPLINE_PROMPT = `
23
+
24
+ ## 执行纪律
25
+ - 先用最小代价验证假设(一次工具调用),确认可行后再展开`;
26
+
27
+ /**
28
+ * 创建工具调用次数限制中间件
29
+ * - wrapToolCall 计数
30
+ * - wrapModelCall 达到上限后注入提示引导模型总结返回
31
+ * - beforeAgent 每次子代理被调用时重置计数器,避免跨任务累积
32
+ *
33
+ * 注意:callCount 通过闭包持有,假设同一子代理不会被并行调用。
34
+ * deepagents 当前是串行调度子代理,如果未来支持并行需改为 per-invocation 计数。
35
+ */
36
+ function createToolCallLimitMiddleware(runLimit = SUBAGENT_RUN_LIMIT) {
37
+ let callCount = 0;
38
+
39
+ return createMiddleware({
40
+ name: 'toolCallLimitMiddleware',
41
+
42
+ beforeAgent: async () => {
43
+ callCount = 0;
44
+ },
45
+
46
+ wrapToolCall: async (request, handler) => {
47
+ callCount++;
48
+
49
+ // 超过上限直接阻止,不经过 LLM
50
+ if (callCount > runLimit) {
51
+ return {
52
+ type: 'tool',
53
+ name: request.tool?.name || request.toolCall?.name || 'unknown',
54
+ content: JSON.stringify({
55
+ success: false,
56
+ error: `工具调用次数已达上限 (${runLimit})。请总结当前发现并返回。`,
57
+ callCount,
58
+ runLimit,
59
+ }),
60
+ tool_call_id: request.toolCall?.id || `limit_${callCount}`,
61
+ status: 'error',
62
+ };
63
+ }
64
+
65
+ return handler(request);
66
+ },
67
+ });
68
+ }
69
+
70
+ /**
71
+ * 创建子代理基础中间件数组
72
+ * 包含:工具错误兜底 + 工具过滤 + 调用次数限制 + 技能注入
73
+ * @param {Array} skillsSources - 技能源列表
74
+ */
75
+ export function createBaseMiddleware(skillsSources = []) {
76
+ return [
77
+ toolRetryMiddleware({ // 工具错误 → ToolMessage,LLM 自我修正
78
+ maxRetries: 0, // schema 错误是确定性的,不重试
79
+ onFailure: (err) => `Tool call failed: ${err.message}\nPlease fix the arguments and retry.`,
80
+ }),
81
+ createFilterToolsMiddleware(),
82
+ createToolCallLimitMiddleware(),
83
+ contextEditingMiddleware({ // 清理旧工具结果,防止上下文膨胀
84
+ edits: [new ClearToolUsesEdit({
85
+ trigger: { tokens: 80000 }, // 80k token 触发(子代理上下文较短)
86
+ keep: { messages: 5 }, // 保留最近 5 条工具结果
87
+ excludeTools: ['save_memo'], // scratchpad 内容不清理
88
+ })],
89
+ }),
90
+ createSkillsMiddleware({
91
+ backend: skillsBackend,
92
+ sources: skillsSources,
93
+ }),
94
+ ];
95
+ }
96
+
11
97
  /**
12
98
  * 创建子代理配置
99
+ * 自动注入 evolveTools + createBaseMiddleware,减少子代理定义中的重复代码
13
100
  * @param {Object} config - 子代理配置
14
101
  * @param {string} config.name - 子代理名称
15
102
  * @param {string} config.description - 子代理描述
16
103
  * @param {string} config.systemPrompt - 系统提示
17
104
  * @param {Array} config.tools - 工具列表
18
- * @param {Array} [config.skills] - 技能源列表(SKILLS 枚举值)
105
+ * @param {Array<string>} [config.skills] - SKILLS 的 key 列表(如 ['static', 'xpath']),自动映射为路径
106
+ * @param {string|string[]} [config.evolveSkill='general'] - evolve_skill 的目标 skill(对应 evolve.js skillMap key),支持数组表示多领域
19
107
  * @param {Array} [config.middleware] - 额外的中间件
20
108
  * @param {boolean} [config.includeEvolve=true] - 是否包含 evolve 工具
21
109
  */
@@ -26,29 +114,41 @@ export function createSubagent(config) {
26
114
  systemPrompt,
27
115
  tools,
28
116
  skills = [],
117
+ evolveSkill = 'general',
29
118
  middleware = [],
30
119
  includeEvolve = true,
31
120
  } = config;
32
121
 
33
- // 合并工具列表
34
122
  const finalTools = includeEvolve
35
123
  ? [...tools, ...evolveTools]
36
124
  : tools;
37
125
 
38
- // 构建中间件列表
126
+ // skills key → SKILLS 路径值
127
+ const skillsSources = skills.map((key) => {
128
+ const source = SKILLS[key];
129
+ if (!source) throw new Error(`Unknown skill key: "${key}". Valid keys: ${Object.keys(SKILLS).join(', ')}`);
130
+ return source;
131
+ });
132
+
39
133
  const finalMiddleware = [
40
- createFilterToolsMiddleware(),
41
- createSkillsMiddleware({
42
- backend: skillsBackend,
43
- sources: skills,
44
- }),
134
+ ...createBaseMiddleware(skillsSources),
45
135
  ...middleware,
46
136
  ];
47
137
 
138
+ // 自动拼接通用 prompt 段落:经验记录 + 执行纪律
139
+ let evolvePrompt;
140
+ if (Array.isArray(evolveSkill)) {
141
+ const list = evolveSkill.map(s => ` - "${s}"`).join('\n');
142
+ evolvePrompt = `\n\n## 经验记录\n完成任务后,如发现有价值的经验,使用 evolve_skill 记录。根据经验所属领域选择对应 skill:\n${list}`;
143
+ } else {
144
+ evolvePrompt = `\n\n## 经验记录\n完成任务后,如发现有价值的经验,使用 evolve_skill 记录:\n- skill: "${evolveSkill}"`;
145
+ }
146
+ const fullPrompt = systemPrompt + evolvePrompt + SUBAGENT_DISCIPLINE_PROMPT;
147
+
48
148
  return {
49
149
  name,
50
150
  description,
51
- systemPrompt,
151
+ systemPrompt: fullPrompt,
52
152
  tools: finalTools,
53
153
  middleware: finalMiddleware,
54
154
  };
@@ -8,33 +8,24 @@ export { createSubagent, SKILLS } from './factory.js';
8
8
  // 编排层
9
9
  export { crawlerSubagent } from './crawler.js';
10
10
 
11
- // 逆向分析
12
- export { staticSubagent } from './static.js';
13
- export { dynamicSubagent } from './dynamic.js';
14
- export { sandboxSubagent } from './sandbox.js';
11
+ // 逆向分析(合并原 static + dynamic + sandbox + env-agent)
12
+ export { reverseSubagent } from './reverse.js';
15
13
  export { js2pythonSubagent } from './js2python.js';
16
- export { envAgentSubagent } from './env-agent.js';
17
14
 
18
15
  // 爬虫能力
19
16
  export { captchaSubagent } from './captcha.js';
20
17
  export { antiDetectSubagent } from './anti-detect.js';
21
18
 
22
19
  import { crawlerSubagent } from './crawler.js';
23
- import { staticSubagent } from './static.js';
24
- import { dynamicSubagent } from './dynamic.js';
25
- import { sandboxSubagent } from './sandbox.js';
20
+ import { reverseSubagent } from './reverse.js';
26
21
  import { js2pythonSubagent } from './js2python.js';
27
- import { envAgentSubagent } from './env-agent.js';
28
22
  import { captchaSubagent } from './captcha.js';
29
23
  import { antiDetectSubagent } from './anti-detect.js';
30
24
 
31
25
  export const allSubagents = [
32
26
  crawlerSubagent,
33
- staticSubagent,
34
- dynamicSubagent,
35
- sandboxSubagent,
27
+ reverseSubagent,
36
28
  js2pythonSubagent,
37
- envAgentSubagent,
38
29
  captchaSubagent,
39
30
  antiDetectSubagent,
40
31
  ];
@@ -3,19 +3,16 @@
3
3
  * 将 JS 加密逻辑转换为 Python 代码
4
4
  */
5
5
 
6
- import { createSkillsMiddleware } from 'deepagents';
7
- import { SKILLS, skillsBackend } from '../skills/config.js';
8
- import { createFilterToolsMiddleware } from '../middleware/filterTools.js';
6
+ import { createSubagent } from './factory.js';
9
7
 
10
8
  import { pythonTools } from '../tools/python.js';
11
9
  import { nodejsTools } from '../tools/nodejs.js';
12
10
  import { analyzerTools } from '../tools/analyzer.js';
13
11
  import { fileTools } from '../tools/file.js';
14
- import { evolveTools } from '../tools/evolve.js';
15
12
 
16
- export const js2pythonSubagent = {
13
+ export const js2pythonSubagent = createSubagent({
17
14
  name: 'js2python',
18
- description: 'JS转Python专家。当需要将JS加密代码转换为Python时使用,适用于:爬虫项目需要Python实现、标准加密算法转换、复杂算法使用execjs方案。',
15
+ description: 'JS转Python专家。适用于:将已还原的 JS 加密代码转换为 Python 实现、标准加密算法转换、复杂算法 execjs 方案。输入必须是已分析清楚的 JS 代码。不能做代码分析、不能反混淆、不能控制浏览器。',
19
16
  systemPrompt: `你是 DeepSpider 的 JS 转 Python 专家,负责将 JS 加密逻辑转换为 Python 代码。
20
17
 
21
18
  ## 核心职责
@@ -51,22 +48,13 @@ export const js2pythonSubagent = {
51
48
  纯 Python 转换失败 3 次 → 改用 execjs 方案
52
49
 
53
50
  目标是保证最终输出可用的代码。
54
-
55
- ## 经验记录
56
- 完成转换后,如发现有价值的经验,使用 evolve_skill 记录:
57
- - skill: "js2python"`,
51
+ `,
58
52
  tools: [
59
53
  ...pythonTools,
60
54
  ...nodejsTools,
61
55
  ...analyzerTools,
62
56
  ...fileTools,
63
- ...evolveTools,
64
- ],
65
- middleware: [
66
- createFilterToolsMiddleware(),
67
- createSkillsMiddleware({
68
- backend: skillsBackend,
69
- sources: [SKILLS.js2python],
70
- }),
71
57
  ],
72
- };
58
+ skills: ['js2python'],
59
+ evolveSkill: 'js2python',
60
+ });
@@ -0,0 +1,180 @@
1
+ /**
2
+ * DeepSpider - 逆向分析子代理
3
+ * 合并原 static + dynamic + sandbox + env-agent 的核心能力
4
+ */
5
+
6
+ import { createSubagent } from './factory.js';
7
+
8
+ // 数据查询(读脚本、搜索请求,不含 clear 工具)
9
+ import {
10
+ getSiteList, searchInResponses, getRequestDetail, getRequestList,
11
+ getRequestInitiator, getScriptList, getScriptSource, searchInScripts,
12
+ } from '../tools/tracing.js';
13
+ // 静态分析
14
+ import { preprocessTools } from '../tools/preprocess.js';
15
+ import { webcrackTools } from '../tools/webcrack.js';
16
+ import { analyzerTools } from '../tools/analyzer.js';
17
+ import { deobfuscatorTools } from '../tools/deobfuscator.js';
18
+ import { correlateTools } from '../tools/correlate.js';
19
+ import { traceTools } from '../tools/trace.js';
20
+ import { extractorTools } from '../tools/extractor.js';
21
+ // 动态分析
22
+ import { debugTools } from '../tools/debug.js';
23
+ import { captureTools } from '../tools/capture.js';
24
+ import { hookManagerTools } from '../tools/hookManager.js';
25
+ import { generateHookTools } from '../tools/generateHook.js';
26
+ // 沙箱 + 补环境
27
+ import { sandboxTools } from '../tools/sandbox.js';
28
+ import { envTools } from '../tools/env.js';
29
+ import { envDumpTools } from '../tools/envdump.js';
30
+ import { extractTools } from '../tools/extract.js';
31
+ import { patchTools } from '../tools/patch.js';
32
+ // 验证 + 执行
33
+ import { verifyAlgorithmTools } from '../tools/verifyAlgorithm.js';
34
+ import { nodejsTools } from '../tools/nodejs.js';
35
+ // 输出
36
+ import { fileTools } from '../tools/file.js';
37
+ import { storeTools } from '../tools/store.js';
38
+ // 页面交互(仅断点触发 + Cookie 采集所需的最小集)
39
+ import { reloadPage, clickElement, scrollPage, getCookies, getPageSource } from '../tools/browser.js';
40
+ // 工作记忆
41
+ import { scratchpadTools } from '../tools/scratchpad.js';
42
+
43
+ export const reverseSubagent = createSubagent({
44
+ name: 'reverse-agent',
45
+ description: '逆向分析专家。适用于:分析加密参数、反混淆、定位加密入口、还原算法、浏览器断点调试、运行时变量采集、Hook注入、沙箱执行验证、补环境。覆盖逆向分析全流程,无需在多个子代理间切换。不能生成 Python 代码(用 js2python)、不能编排爬虫(用 crawler)。',
46
+ systemPrompt: `你是 DeepSpider 的逆向分析专家,覆盖从代码分析到验证的完整逆向流程。
47
+
48
+ ## 核心能力
49
+ - **请求追溯**:从目标请求的 initiator(调用栈)直接定位发起请求的代码位置
50
+ - **静态分析**:预处理、解包、反混淆、AST 分析、加密入口定位
51
+ - **动态分析**:断点调试、Hook 注入、运行时变量采集、加密调用追踪
52
+ - **沙箱执行**:补环境、沙箱运行、算法验证
53
+ - **关联分析**:请求参数与加密函数的关联、Cookie/Header 加密定位
54
+
55
+ ## 工作流程
56
+
57
+ ### 标准逆向路径(请求驱动,必须遵守)
58
+
59
+ **核心原则:从请求出发,沿调用链向上追溯,精准定位加密入口。**
60
+
61
+ 1. **找请求** — get_request_list 找到目标 API 请求(主 agent 通常会在任务描述中提供 site 和 requestId)
62
+ 2. **看调用栈** — get_request_initiator 获取请求的 initiator(调用栈)
63
+ - 有 callFrames → 直接定位到发起请求的函数(脚本URL + 行号)→ 跳到第 3 步
64
+ - 没有 initiator → 走退化路径(见下方)
65
+ 3. **定位函数** — 根据调用栈中的脚本 URL 和行号:
66
+ - get_script_source 获取对应脚本(只取相关片段,用 offset/limit 控制,不要全量拉取)
67
+ - get_function_code 提取目标函数及其依赖
68
+ 4. **分析加密** — 在定位到的函数范围内分析加密逻辑
69
+ - 代码混淆严重 → preprocess_code + deobfuscate
70
+ - 引用运行时变量(window.x、document.x 等)→ **必须用断点采集,禁止猜测**:
71
+ a. set_breakpoint 在目标函数入口设断点
72
+ b. 触发目标代码执行(根据场景选择触发方式,见「断点触发」)
73
+ c. evaluate_at_breakpoint 采集所有运行时变量值
74
+ d. resume_execution 继续执行
75
+ - collect_property 返回 undefined 时,说明变量仅在函数执行时存在,必须走断点路径
76
+ - **禁止用 search_in_scripts 搜索变量定义来猜测值,禁止用 run_node_code 穷举尝试**
77
+ 5. **验证** — sandbox_execute 或 run_node_code 验证算法正确性
78
+
79
+ ### 退化路径(initiator 不可用时)
80
+ **触发条件:get_request_initiator 返回 error,或返回的 callFrames 为空数组。**
81
+ 1. analyze_request_params 快速识别可疑加密参数(自动检测 hex/base64/hash 模式,不依赖 Hook)
82
+ 2. search_in_scripts 搜索参数名或参数赋值位置
83
+ 3. 如果搜不到(参数名被混淆)→ set_xhr_breakpoint + get_call_stack 动态获取调用栈
84
+ 4. 定位到函数后回到标准路径第 3 步
85
+
86
+ ### 何时使用断点(优先于 Hook)
87
+ - 需要获取函数入参/返回值 → set_breakpoint + evaluate_at_breakpoint
88
+ - 需要追踪调用链 → set_xhr_breakpoint + get_call_stack
89
+ - 断点是 CDP 原生能力,不修改页面 JS,不触发反调试检测
90
+
91
+ ### 何时使用 Hook
92
+ - 需要持续监控(如观察多次请求的加密参数变化)→ Hook
93
+ - 断点无法使用时(如异步回调链复杂)→ Hook
94
+ - 注意:inject_hook 可能触发反调试检测,优先用内置 Hook(enable_hook)
95
+ - 需要生成 Hook 代码时,使用 generate_hook(type) 生成,再通过 inject_hook 注入
96
+
97
+ ### 补环境路径(算法复杂难还原时)
98
+ 1. generate_env_dump_code 生成环境自吐代码
99
+ 2. collect_env / collect_property 采集真实环境
100
+ 3. generate_patch / load_env_module 生成补丁
101
+ 4. sandbox_inject + sandbox_execute 运行
102
+
103
+ ### 禁止行为
104
+ - 禁止在有目标请求的情况下,跳过 initiator 直接拉全量源码
105
+ - 禁止把超过 5000 字符的混淆代码塞入分析上下文
106
+ - 禁止在没有明确目标函数的情况下使用 inject_hook
107
+
108
+ ## 浏览器状态
109
+ - 浏览器生命周期由主 agent 管理,你没有 launch_browser / navigate_to 工具
110
+ - 如果任务描述中包含"浏览器已就绪",你可以直接使用断点、Hook、采集工具、页面交互工具
111
+ - 如果浏览器未启动,返回结果告知主 agent 需要先启动浏览器
112
+ - 你有 reload_page、click_element、scroll_page 用于触发断点,get_cookies 用于采集 Cookie,get_page_source 用于获取页面源码(定位内联脚本等)
113
+
114
+ ### 断点触发(必须掌握)
115
+ 设置断点后,断点不会立即命中 — 需要目标代码被执行。根据目标代码的执行时机选择触发方式:
116
+ - 代码在页面加载时执行(如初始化、首次请求)→ **reload_page**
117
+ - 代码在用户交互时执行(如翻页、点击按钮、提交表单)→ **click_element**
118
+ - 代码在滚动时执行(如懒加载、无限滚动)→ **scroll_page**
119
+ - 不确定触发时机 → 先看调用栈上下文判断,或用 **reload_page** 尝试
120
+
121
+ 断点命中后,用 **evaluate_at_breakpoint** 采集运行时变量,用 **resume_execution** 继续执行
122
+
123
+ ## 工作记忆
124
+ 多步分析时,用 save_memo 记录关键发现,防止上下文丢失:
125
+ - 定位到加密函数后 → save_memo("crypto-func", "函数名 + 位置 + 算法类型")
126
+ - 找到 key/iv 来源后 → save_memo("key-source", "来源 + 值")
127
+ - 验证成功后 → save_memo("verified-code", "可运行的完整代码")
128
+ 分析开始时用 list_memo 检查是否有之前的记录可以复用。
129
+
130
+ ## 输出要求
131
+ - 返回加密算法的完整分析(入口函数、参数来源、算法类型)
132
+ - 如已验证成功,返回可独立运行的 JS 代码片段
133
+ - 如需转 Python,明确告知主 agent 委托 js2python
134
+
135
+ ### 保存前置条件(必须遵守)
136
+ - **只有通过 run_node_code 或 sandbox_execute 验证算法正确后,才能调用 artifact_save 保存代码**
137
+ - 未验证成功时,返回文本说明当前进度和卡点,不保存文件
138
+ - 禁止保存"框架代码"或"需要动态采集"的半成品代码
139
+
140
+ ## 能力边界
141
+ - 你不能生成 Python 代码,需要转换时返回结果让主 agent 委托 js2python
142
+ - 你不能编排完整爬虫流程,那是 crawler 的工作
143
+ - 你不能启动/关闭浏览器或导航到新 URL,需要时返回让主 agent 处理`,
144
+ tools: [
145
+ // 数据查询(不含 clear_site_data / clear_all_data)
146
+ getSiteList, searchInResponses, getRequestDetail, getRequestList,
147
+ getRequestInitiator, getScriptList, getScriptSource, searchInScripts,
148
+ // 静态分析
149
+ ...preprocessTools,
150
+ ...webcrackTools,
151
+ ...analyzerTools,
152
+ ...deobfuscatorTools,
153
+ ...correlateTools,
154
+ ...traceTools,
155
+ ...extractorTools,
156
+ // 动态分析
157
+ ...debugTools,
158
+ ...captureTools,
159
+ ...hookManagerTools,
160
+ ...generateHookTools,
161
+ // 沙箱 + 补环境
162
+ ...sandboxTools,
163
+ ...envTools,
164
+ ...envDumpTools,
165
+ ...extractTools,
166
+ ...patchTools,
167
+ // 验证 + 执行
168
+ ...verifyAlgorithmTools,
169
+ ...nodejsTools,
170
+ // 页面交互(断点触发 + Cookie 采集 + 页面源码)
171
+ reloadPage, clickElement, scrollPage, getCookies, getPageSource,
172
+ // 输出
173
+ ...fileTools,
174
+ ...storeTools,
175
+ // 工作记忆
176
+ ...scratchpadTools,
177
+ ],
178
+ skills: ['static', 'dynamic', 'sandbox', 'env'],
179
+ evolveSkill: ['static-analysis', 'dynamic-analysis', 'sandbox', 'env'],
180
+ });
@@ -89,7 +89,26 @@ export const sendPanelMessage = tool(
89
89
 
90
90
  const escaped = JSON.stringify(message);
91
91
  const r = role || 'assistant';
92
- await evaluateViaCDP(client, `window.__deepspider__?.addMessage?.('${r}', ${escaped})`);
92
+
93
+ // 检查 __deepspider__ 和 addMessage 是否存在
94
+ const checkResult = await evaluateViaCDP(client, `
95
+ (function() {
96
+ if (!window.__deepspider__) return { error: '__deepspider__ not found' };
97
+ if (!window.__deepspider__.addMessage) return { error: 'addMessage not found' };
98
+ return { ok: true };
99
+ })()
100
+ `);
101
+
102
+ if (checkResult?.error) {
103
+ return JSON.stringify({ error: `面板未就绪: ${checkResult.error}` });
104
+ }
105
+
106
+ // 调用 addMessage
107
+ const result = await evaluateViaCDP(client, `window.__deepspider__.addMessage(${JSON.stringify(r)}, ${escaped})`);
108
+
109
+ if (result === null || result === undefined) {
110
+ return JSON.stringify({ error: 'addMessage 调用失败或返回空值' });
111
+ }
93
112
 
94
113
  return JSON.stringify({ success: true });
95
114
  },
@@ -127,9 +146,73 @@ export const startSelector = tool(
127
146
  }
128
147
  );
129
148
 
149
+ /**
150
+ * 向浏览器面板发送可点击选项
151
+ */
152
+ export const sendPanelChoices = tool(
153
+ async ({ question, options, allowCustom }) => {
154
+ const client = getBrowserClient();
155
+ if (!client?.page) {
156
+ return JSON.stringify({ error: '浏览器未启动' });
157
+ }
158
+
159
+ const data = { question, options, allowCustom: allowCustom ?? false };
160
+ const escapedType = JSON.stringify('choices');
161
+ const escapedData = JSON.stringify(data);
162
+ await evaluateViaCDP(client,
163
+ `window.__deepspider__?.addStructuredMessage?.(${escapedType}, ${escapedData})`
164
+ );
165
+ return JSON.stringify({ success: true, message: '已发送选项到面板,等待用户选择' });
166
+ },
167
+ {
168
+ name: 'send_panel_choices',
169
+ description: '向浏览器面板发送可点击的选项让用户选择。用户点击后结果会通过对话返回。',
170
+ schema: z.object({
171
+ question: z.string().describe('问题描述'),
172
+ options: z.array(z.object({
173
+ id: z.string().describe('选项唯一标识'),
174
+ label: z.string().describe('选项显示文字'),
175
+ description: z.string().optional().describe('选项补充说明'),
176
+ })).describe('可选项列表'),
177
+ allowCustom: z.boolean().optional().default(false).describe('是否允许用户自定义输入'),
178
+ }),
179
+ }
180
+ );
181
+
182
+ /**
183
+ * 向浏览器面板发送确认对话框
184
+ */
185
+ export const sendPanelConfirm = tool(
186
+ async ({ question, confirmText, cancelText }) => {
187
+ const client = getBrowserClient();
188
+ if (!client?.page) {
189
+ return JSON.stringify({ error: '浏览器未启动' });
190
+ }
191
+
192
+ const data = { question, confirmText: confirmText ?? '确认', cancelText: cancelText ?? '取消' };
193
+ const escapedType = JSON.stringify('confirm');
194
+ const escapedData = JSON.stringify(data);
195
+ await evaluateViaCDP(client,
196
+ `window.__deepspider__?.addStructuredMessage?.(${escapedType}, ${escapedData})`
197
+ );
198
+ return JSON.stringify({ success: true, message: '已发送确认请求到面板' });
199
+ },
200
+ {
201
+ name: 'send_panel_confirm',
202
+ description: '向浏览器面板发送确认/取消对话框。用于执行爬虫、删除数据等需要用户确认的操作。',
203
+ schema: z.object({
204
+ question: z.string().describe('确认问题'),
205
+ confirmText: z.string().optional().default('确认'),
206
+ cancelText: z.string().optional().default('取消'),
207
+ }),
208
+ }
209
+ );
210
+
130
211
  export const analysisTools = [
131
212
  getPendingAnalysis,
132
213
  getPendingChat,
133
214
  sendPanelMessage,
134
215
  startSelector,
216
+ sendPanelChoices,
217
+ sendPanelConfirm,
135
218
  ];
@@ -9,11 +9,14 @@ import { getBrowser } from '../../browser/index.js';
9
9
  export const proxyTest = tool(
10
10
  async ({ proxy_url }) => {
11
11
  try {
12
- // 测试代理可用性
12
+ // 测试代理可用性(用 AbortController 实现超时,Node.js fetch 不支持 timeout 选项)
13
+ const controller = new globalThis.AbortController();
14
+ const timer = setTimeout(() => controller.abort(), 10000);
13
15
  const response = await fetch('https://httpbin.org/ip', {
14
16
  agent: proxy_url ? new (await import('https-proxy-agent')).HttpsProxyAgent(proxy_url) : undefined,
15
- timeout: 10000,
17
+ signal: controller.signal,
16
18
  });
19
+ clearTimeout(timer);
17
20
  const data = await response.json();
18
21
  return JSON.stringify({ success: true, ip: data.origin, proxy: proxy_url });
19
22
  } catch (e) {