deepspider 0.2.11 → 0.3.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 (146) hide show
  1. package/README.md +71 -24
  2. package/bin/cli.js +45 -0
  3. package/package.json +10 -4
  4. package/src/agent/core/PanelBridge.js +133 -0
  5. package/src/agent/core/RetryManager.js +51 -0
  6. package/src/agent/core/StreamHandler.js +263 -0
  7. package/src/agent/core/index.js +7 -0
  8. package/src/agent/errors/ErrorClassifier.js +43 -0
  9. package/src/agent/errors/SpiderError.js +68 -0
  10. package/src/agent/errors/index.js +19 -0
  11. package/src/agent/run.js +67 -460
  12. package/src/agent/setup.js +14 -14
  13. package/src/agent/subagents/factory.js +60 -0
  14. package/src/agent/subagents/index.js +3 -0
  15. package/src/agent/tools/report.js +36 -4
  16. package/src/browser/client.js +47 -10
  17. package/src/cli/commands/config.js +94 -0
  18. package/src/cli/commands/help.js +34 -0
  19. package/src/cli/commands/update.js +78 -0
  20. package/src/cli/commands/version.js +9 -0
  21. package/src/cli/config.js +15 -0
  22. package/src/config/settings.js +102 -0
  23. package/.claude/agents/check.md +0 -122
  24. package/.claude/agents/debug.md +0 -106
  25. package/.claude/agents/dispatch.md +0 -214
  26. package/.claude/agents/implement.md +0 -96
  27. package/.claude/agents/plan.md +0 -396
  28. package/.claude/agents/research.md +0 -120
  29. package/.claude/commands/evolve/merge.md +0 -80
  30. package/.claude/commands/trellis/before-backend-dev.md +0 -13
  31. package/.claude/commands/trellis/before-frontend-dev.md +0 -13
  32. package/.claude/commands/trellis/break-loop.md +0 -107
  33. package/.claude/commands/trellis/check-backend.md +0 -13
  34. package/.claude/commands/trellis/check-cross-layer.md +0 -153
  35. package/.claude/commands/trellis/check-frontend.md +0 -13
  36. package/.claude/commands/trellis/create-command.md +0 -154
  37. package/.claude/commands/trellis/finish-work.md +0 -129
  38. package/.claude/commands/trellis/integrate-skill.md +0 -219
  39. package/.claude/commands/trellis/onboard.md +0 -358
  40. package/.claude/commands/trellis/parallel.md +0 -193
  41. package/.claude/commands/trellis/record-session.md +0 -62
  42. package/.claude/commands/trellis/start.md +0 -280
  43. package/.claude/commands/trellis/update-spec.md +0 -213
  44. package/.claude/hooks/inject-subagent-context.py +0 -758
  45. package/.claude/hooks/ralph-loop.py +0 -374
  46. package/.claude/hooks/session-start.py +0 -126
  47. package/.claude/settings.json +0 -41
  48. package/.claude/skills/deepagents-guide/SKILL.md +0 -428
  49. package/.cursor/commands/trellis-before-backend-dev.md +0 -13
  50. package/.cursor/commands/trellis-before-frontend-dev.md +0 -13
  51. package/.cursor/commands/trellis-break-loop.md +0 -107
  52. package/.cursor/commands/trellis-check-backend.md +0 -13
  53. package/.cursor/commands/trellis-check-cross-layer.md +0 -153
  54. package/.cursor/commands/trellis-check-frontend.md +0 -13
  55. package/.cursor/commands/trellis-create-command.md +0 -154
  56. package/.cursor/commands/trellis-finish-work.md +0 -129
  57. package/.cursor/commands/trellis-integrate-skill.md +0 -219
  58. package/.cursor/commands/trellis-onboard.md +0 -358
  59. package/.cursor/commands/trellis-record-session.md +0 -62
  60. package/.cursor/commands/trellis-start.md +0 -156
  61. package/.cursor/commands/trellis-update-spec.md +0 -213
  62. package/.github/workflows/publish.yml +0 -63
  63. package/.husky/pre-commit +0 -1
  64. package/.mcp.json +0 -8
  65. package/.trellis/.template-hashes.json +0 -65
  66. package/.trellis/.version +0 -1
  67. package/.trellis/scripts/add-session.sh +0 -384
  68. package/.trellis/scripts/common/developer.sh +0 -129
  69. package/.trellis/scripts/common/git-context.sh +0 -263
  70. package/.trellis/scripts/common/paths.sh +0 -208
  71. package/.trellis/scripts/common/phase.sh +0 -150
  72. package/.trellis/scripts/common/registry.sh +0 -247
  73. package/.trellis/scripts/common/task-queue.sh +0 -142
  74. package/.trellis/scripts/common/task-utils.sh +0 -151
  75. package/.trellis/scripts/common/worktree.sh +0 -128
  76. package/.trellis/scripts/create-bootstrap.sh +0 -299
  77. package/.trellis/scripts/get-context.sh +0 -7
  78. package/.trellis/scripts/get-developer.sh +0 -15
  79. package/.trellis/scripts/init-developer.sh +0 -34
  80. package/.trellis/scripts/multi-agent/cleanup.sh +0 -396
  81. package/.trellis/scripts/multi-agent/create-pr.sh +0 -241
  82. package/.trellis/scripts/multi-agent/plan.sh +0 -207
  83. package/.trellis/scripts/multi-agent/start.sh +0 -310
  84. package/.trellis/scripts/multi-agent/status.sh +0 -828
  85. package/.trellis/scripts/task.sh +0 -1118
  86. package/.trellis/spec/backend/ci-cd-guidelines.md +0 -73
  87. package/.trellis/spec/backend/deepagents-guide.md +0 -380
  88. package/.trellis/spec/backend/directory-structure.md +0 -126
  89. package/.trellis/spec/backend/examples/skills/deepagents-guide/README.md +0 -11
  90. package/.trellis/spec/backend/examples/skills/deepagents-guide/agent.js.template +0 -20
  91. package/.trellis/spec/backend/examples/skills/deepagents-guide/skills-config.js.template +0 -13
  92. package/.trellis/spec/backend/examples/skills/deepagents-guide/subagent.js.template +0 -19
  93. package/.trellis/spec/backend/hook-guidelines.md +0 -218
  94. package/.trellis/spec/backend/index.md +0 -37
  95. package/.trellis/spec/backend/quality-guidelines.md +0 -302
  96. package/.trellis/spec/backend/state-management.md +0 -76
  97. package/.trellis/spec/backend/tool-guidelines.md +0 -144
  98. package/.trellis/spec/backend/type-safety.md +0 -71
  99. package/.trellis/spec/guides/code-reuse-thinking-guide.md +0 -92
  100. package/.trellis/spec/guides/cross-layer-thinking-guide.md +0 -94
  101. package/.trellis/spec/guides/index.md +0 -79
  102. package/.trellis/tasks/archive/02-02-evolving-skills/prd.md +0 -61
  103. package/.trellis/tasks/archive/02-02-evolving-skills/task.json +0 -29
  104. package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/prd.md +0 -86
  105. package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/task.json +0 -27
  106. package/.trellis/tasks/archive/2026-02/02-02-skills-system/check.jsonl +0 -3
  107. package/.trellis/tasks/archive/2026-02/02-02-skills-system/debug.jsonl +0 -2
  108. package/.trellis/tasks/archive/2026-02/02-02-skills-system/implement.jsonl +0 -5
  109. package/.trellis/tasks/archive/2026-02/02-02-skills-system/prd.md +0 -33
  110. package/.trellis/tasks/archive/2026-02/02-02-skills-system/task.json +0 -41
  111. package/.trellis/workflow.md +0 -407
  112. package/.trellis/workspace/index.md +0 -123
  113. package/.trellis/workspace/pony/index.md +0 -42
  114. package/.trellis/workspace/pony/journal-1.md +0 -125
  115. package/.trellis/worktree.yaml +0 -47
  116. package/AGENTS.md +0 -18
  117. package/CLAUDE.md +0 -315
  118. package/agents/deepspider.md +0 -142
  119. package/docs/DEBUG.md +0 -42
  120. package/docs/GUIDE.md +0 -334
  121. package/docs/PROMPT.md +0 -60
  122. package/docs/USAGE.md +0 -226
  123. package/eslint.config.js +0 -51
  124. package/test/analyze.test.js +0 -90
  125. package/test/envdump.test.js +0 -74
  126. package/test/flow.test.js +0 -90
  127. package/test/hooks.test.js +0 -138
  128. package/test/plugin.test.js +0 -35
  129. package/test/refactor-full.test.js +0 -30
  130. package/test/refactor.test.js +0 -21
  131. package/test/samples/obfuscated.js +0 -61
  132. package/test/samples/original.js +0 -66
  133. package/test/samples/v10_eval_chain.js +0 -52
  134. package/test/samples/v11_bytecode_vm.js +0 -81
  135. package/test/samples/v12_polymorphic.js +0 -69
  136. package/test/samples/v1_ob_basic.js +0 -98
  137. package/test/samples/v2_ob_advanced.js +0 -99
  138. package/test/samples/v3_jjencode.js +0 -77
  139. package/test/samples/v4_aaencode.js +0 -73
  140. package/test/samples/v5_control_flow.js +0 -86
  141. package/test/samples/v6_string_encryption.js +0 -71
  142. package/test/samples/v7_jsvmp.js +0 -83
  143. package/test/samples/v8_anti_debug.js +0 -79
  144. package/test/samples/v9_proxy_trap.js +0 -49
  145. package/test/samples.test.js +0 -96
  146. package/test/webcrack.test.js +0 -55
package/README.md CHANGED
@@ -37,50 +37,91 @@ cp .env.example .env # 配置环境变量
37
37
  pnpm run setup:crypto # 安装 Python 加密库(可选)
38
38
  ```
39
39
 
40
- 安装完成后,首次运行会提示配置环境变量(LLM API)。
40
+ 安装完成后,首次运行会提示配置 LLM API
41
41
 
42
42
  > **注意**: 项目依赖 `isolated-vm` 原生模块,需要 C++ 编译环境:
43
43
  > - macOS: `xcode-select --install`
44
44
  > - Ubuntu: `sudo apt install build-essential`
45
45
  > - Windows: 安装 [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/)
46
46
 
47
- ### 环境变量配置
47
+ ### 配置
48
48
 
49
49
  DeepSpider 需要配置 LLM API 才能运行。支持任何兼容 OpenAI 格式的供应商。
50
50
 
51
- | 变量名 | 必填 | 说明 |
52
- |--------|------|------|
53
- | `DEEPSPIDER_API_KEY` | | API 密钥 |
54
- | `DEEPSPIDER_BASE_URL` | | API 地址 |
55
- | `DEEPSPIDER_MODEL` | | 模型名称 |
51
+ | 配置键 | 环境变量 | 说明 |
52
+ |--------|----------|------|
53
+ | `apiKey` | `DEEPSPIDER_API_KEY` | API 密钥 |
54
+ | `baseUrl` | `DEEPSPIDER_BASE_URL` | API 地址 |
55
+ | `model` | `DEEPSPIDER_MODEL` | 模型名称 |
56
56
 
57
- **常用供应商配置示例**:
57
+ 优先级:环境变量 > 配置文件 (`~/.deepspider/config/settings.json`) > 默认值
58
+
59
+ **方式一:使用 CLI 命令(推荐)**
60
+
61
+ ```bash
62
+ deepspider config set apiKey sk-xxx
63
+ deepspider config set baseUrl https://api.openai.com/v1
64
+ deepspider config set model gpt-4o
65
+ ```
66
+
67
+ **方式二:环境变量**
68
+
69
+ ```bash
70
+ export DEEPSPIDER_API_KEY=sk-xxx
71
+ export DEEPSPIDER_BASE_URL=https://api.openai.com/v1
72
+ export DEEPSPIDER_MODEL=gpt-4o
73
+ ```
74
+
75
+ **常用供应商示例**:
58
76
 
59
77
  ```bash
60
78
  # OpenAI
61
- DEEPSPIDER_API_KEY=sk-xxx
62
- DEEPSPIDER_BASE_URL=https://api.openai.com/v1
63
- DEEPSPIDER_MODEL=gpt-4o
79
+ deepspider config set baseUrl https://api.openai.com/v1
80
+ deepspider config set model gpt-4o
64
81
 
65
82
  # DeepSeek
66
- DEEPSPIDER_API_KEY=sk-xxx
67
- DEEPSPIDER_BASE_URL=https://api.deepseek.com/v1
68
- DEEPSPIDER_MODEL=deepseek-chat
69
-
70
- # 其他 OpenAI 兼容供应商
71
- DEEPSPIDER_API_KEY=your-key
72
- DEEPSPIDER_BASE_URL=https://your-provider.com/v1
73
- DEEPSPIDER_MODEL=model-name
83
+ deepspider config set baseUrl https://api.deepseek.com/v1
84
+ deepspider config set model deepseek-chat
74
85
  ```
75
86
 
76
87
  ### 使用
77
88
 
89
+ #### 全局安装(npm/pnpm install -g)
90
+
78
91
  ```bash
79
- # Agent 模式(推荐)- 指定目标网站
80
- pnpm run agent https://example.com
92
+ # 启动 Agent - 指定目标网站
93
+ deepspider https://example.com
81
94
 
82
- # Agent 模式 - 纯交互(不启动浏览器)
83
- pnpm run agent
95
+ # 启动 Agent - 纯交互模式
96
+ deepspider
97
+
98
+ # 查看帮助
99
+ deepspider --help
100
+
101
+ # 管理配置
102
+ deepspider config list # 查看所有配置
103
+ deepspider config set apiKey sk-xxx
104
+ deepspider config set model gpt-4o
105
+
106
+ # 检查更新
107
+ deepspider update
108
+ ```
109
+
110
+ #### 克隆仓库
111
+
112
+ ```bash
113
+ # 配置(二选一)
114
+ cp .env.example .env # 编辑 .env 文件
115
+ # 或使用 CLI 命令
116
+ node bin/cli.js config set apiKey sk-xxx
117
+ node bin/cli.js config set baseUrl https://api.openai.com/v1
118
+ node bin/cli.js config set model gpt-4o
119
+
120
+ # 安装 Python 依赖(可选,用于执行生成的 Python 代码)
121
+ pnpm run setup:crypto
122
+
123
+ # 启动 Agent
124
+ pnpm run agent https://example.com
84
125
 
85
126
  # MCP 服务(供 Claude Code 等调用)
86
127
  pnpm run mcp
@@ -140,12 +181,19 @@ pnpm test
140
181
 
141
182
  ```
142
183
  deepspider/
184
+ ├── bin/cli.js # CLI 入口(命令路由)
143
185
  ├── src/
144
186
  │ ├── agent/ # DeepAgent 系统
145
187
  │ │ ├── tools/ # 工具集(90+)
146
188
  │ │ ├── subagents/ # 子代理
147
189
  │ │ ├── skills/ # 领域技能
148
190
  │ │ └── prompts/ # 系统提示
191
+ │ ├── cli/ # CLI 命令
192
+ │ │ ├── config.js # 配置 re-export
193
+ │ │ └── commands/ # 子命令(version/help/config/update)
194
+ │ ├── config/ # 核心配置
195
+ │ │ ├── paths.js # 路径常量
196
+ │ │ └── settings.js # 配置读写(环境变量/文件/默认值)
149
197
  │ ├── browser/ # 浏览器运行时
150
198
  │ │ ├── client.js # Patchright 客户端
151
199
  │ │ ├── cdp.js # CDP 会话管理
@@ -156,7 +204,6 @@ deepspider/
156
204
  │ ├── env/ # 环境补丁模块
157
205
  │ ├── store/ # 数据存储
158
206
  │ └── mcp/ # MCP 服务
159
- ├── bin/cli.js # CLI 入口
160
207
  └── test/ # 测试
161
208
  ```
162
209
 
package/bin/cli.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DeepSpider CLI 入口
4
+ * 路由命令到对应处理模块
5
+ */
6
+
7
+ import 'dotenv/config';
8
+
9
+ const args = process.argv.slice(2);
10
+ const first = args[0];
11
+
12
+ switch (first) {
13
+ case '-v':
14
+ case '--version': {
15
+ const { run } = await import('../src/cli/commands/version.js');
16
+ run();
17
+ break;
18
+ }
19
+
20
+ case '-h':
21
+ case '--help': {
22
+ const { run } = await import('../src/cli/commands/help.js');
23
+ run();
24
+ break;
25
+ }
26
+
27
+ case 'config': {
28
+ const { run } = await import('../src/cli/commands/config.js');
29
+ run(args.slice(1));
30
+ break;
31
+ }
32
+
33
+ case 'update': {
34
+ const { run } = await import('../src/cli/commands/update.js');
35
+ await run();
36
+ break;
37
+ }
38
+
39
+ default: {
40
+ // URL 或无参数 → 启动 Agent
41
+ const { init } = await import('../src/agent/run.js');
42
+ await init();
43
+ break;
44
+ }
45
+ }
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "deepspider",
3
- "version": "0.2.11",
3
+ "version": "0.3.0",
4
4
  "description": "智能爬虫工程平台 - 基于 DeepAgents + Patchright 的 AI 爬虫 Agent",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
+ "files": [
8
+ "bin/",
9
+ "src/",
10
+ "requirements-crypto.txt",
11
+ ".env.example"
12
+ ],
7
13
  "bin": {
8
- "deepspider": "./src/agent/run.js"
14
+ "deepspider": "./bin/cli.js"
9
15
  },
10
16
  "scripts": {
11
17
  "start": "node src/index.js",
12
18
  "dev": "node --watch src/index.js",
13
- "cli": "node src/agent/run.js",
19
+ "cli": "node bin/cli.js",
14
20
  "mcp": "node src/mcp/server.js",
15
- "agent": "node src/agent/run.js",
21
+ "agent": "node bin/cli.js",
16
22
  "test": "node --test test/",
17
23
  "lint": "eslint src/",
18
24
  "lint:fix": "eslint src/ --fix",
@@ -0,0 +1,133 @@
1
+ /**
2
+ * DeepSpider - 面板通信桥接
3
+ * 处理与浏览器面板的消息通信
4
+ */
5
+
6
+ export class PanelBridge {
7
+ constructor(browserGetter, debugFn = () => {}) {
8
+ this.getBrowser = browserGetter;
9
+ this.debug = debugFn;
10
+ this.textBuffer = '';
11
+ this.hasStartedAssistantMsg = false;
12
+ }
13
+
14
+ /**
15
+ * 重置状态
16
+ */
17
+ reset() {
18
+ this.textBuffer = '';
19
+ this.hasStartedAssistantMsg = false;
20
+ }
21
+
22
+ /**
23
+ * 通过 CDP 在页面主世界执行 JavaScript
24
+ */
25
+ async evaluateInPage(code) {
26
+ const browser = this.getBrowser();
27
+ const cdp = await browser?.getCDPSession?.();
28
+ if (!cdp) return null;
29
+
30
+ try {
31
+ const result = await cdp.send('Runtime.evaluate', {
32
+ expression: code,
33
+ returnByValue: true,
34
+ });
35
+ return result.result?.value;
36
+ } catch (e) {
37
+ this.debug('evaluateInPage 失败:', e.message);
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * 发送消息到前端面板
44
+ */
45
+ async sendToPanel(role, content) {
46
+ if (!content?.trim()) return;
47
+
48
+ const browser = this.getBrowser();
49
+ const page = browser?.getPage?.();
50
+ if (!page) return;
51
+
52
+ try {
53
+ const escaped = JSON.stringify(content.trim());
54
+ const code = `window.__deepspider__?.addMessage?.('${role}', ${escaped})`;
55
+ await this.evaluateInPage(code);
56
+ } catch (e) {
57
+ // ignore
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 累积文本到缓冲区(用于 LLM 流式输出)
63
+ */
64
+ async appendToPanel(text) {
65
+ if (!text) return;
66
+ this.textBuffer += text;
67
+
68
+ // 每累积一定量或遇到换行时刷新
69
+ if (this.textBuffer.length > 200 || text.includes('\n')) {
70
+ await this.flushPanelText();
71
+ }
72
+ }
73
+
74
+ /**
75
+ * 刷新累积的文本到面板
76
+ */
77
+ async flushPanelText() {
78
+ if (!this.textBuffer.trim()) return;
79
+
80
+ const browser = this.getBrowser();
81
+ const page = browser?.getPage?.();
82
+ if (!page) {
83
+ this.textBuffer = '';
84
+ return;
85
+ }
86
+
87
+ try {
88
+ const content = this.textBuffer.trim();
89
+ const escaped = JSON.stringify(content);
90
+
91
+ if (!this.hasStartedAssistantMsg) {
92
+ const code = `(function() {
93
+ const fn = window.__deepspider__?.addMessage;
94
+ if (typeof fn === 'function') {
95
+ fn('assistant', ${escaped});
96
+ return { ok: true };
97
+ }
98
+ return { ok: false };
99
+ })()`;
100
+ await this.evaluateInPage(code);
101
+ this.hasStartedAssistantMsg = true;
102
+ } else {
103
+ const code = `(function() {
104
+ const fn = window.__deepspider__?.appendToLastMessage;
105
+ if (typeof fn === 'function') {
106
+ fn('assistant', ${escaped});
107
+ return { ok: true };
108
+ }
109
+ return { ok: false };
110
+ })()`;
111
+ await this.evaluateInPage(code);
112
+ }
113
+ } catch (e) {
114
+ // ignore
115
+ }
116
+
117
+ this.textBuffer = '';
118
+ }
119
+
120
+ /**
121
+ * 设置忙碌状态
122
+ */
123
+ async setBusy(busy) {
124
+ await this.evaluateInPage(`window.__deepspider__?.setBusy?.(${busy})`);
125
+ }
126
+
127
+ /**
128
+ * 完成消息,触发渲染
129
+ */
130
+ async finalizeMessage(role) {
131
+ await this.evaluateInPage(`window.__deepspider__?.finalizeMessage?.("${role}")`);
132
+ }
133
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * DeepSpider - 重试管理器
3
+ * 处理 API 调用的重试策略
4
+ */
5
+
6
+ // 默认重试配置
7
+ const DEFAULT_CONFIG = {
8
+ maxRetries: 3,
9
+ baseDelayMs: 2000,
10
+ maxDelayMs: 30000,
11
+ };
12
+
13
+ export class RetryManager {
14
+ constructor(config = {}) {
15
+ this.config = { ...DEFAULT_CONFIG, ...config };
16
+ }
17
+
18
+ /**
19
+ * 计算重试延迟(指数退避 + 抖动)
20
+ */
21
+ getDelay(retryCount) {
22
+ const delay = Math.min(
23
+ this.config.baseDelayMs * Math.pow(2, retryCount),
24
+ this.config.maxDelayMs
25
+ );
26
+ // 添加 0-25% 的随机抖动
27
+ const jitter = delay * Math.random() * 0.25;
28
+ return Math.round(delay + jitter);
29
+ }
30
+
31
+ /**
32
+ * 是否可以重试
33
+ */
34
+ canRetry(retryCount) {
35
+ return retryCount < this.config.maxRetries;
36
+ }
37
+
38
+ /**
39
+ * 获取最大重试次数
40
+ */
41
+ get maxRetries() {
42
+ return this.config.maxRetries;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * 延迟函数
48
+ */
49
+ export function sleep(ms) {
50
+ return new Promise(resolve => setTimeout(resolve, ms));
51
+ }
@@ -0,0 +1,263 @@
1
+ /**
2
+ * DeepSpider - 流式输出处理器
3
+ * 处理 Agent 的流式事件
4
+ */
5
+
6
+ import { isApiServiceError, isToolSchemaError } from '../errors/ErrorClassifier.js';
7
+ import { RetryManager, sleep } from './RetryManager.js';
8
+
9
+ // DeepSeek 特殊标记清理
10
+ const DSML_PATTERN = /|DSML|/g;
11
+ function cleanDSML(text) {
12
+ return text ? text.replace(DSML_PATTERN, '') : text;
13
+ }
14
+
15
+ // 人工介入配置
16
+ const INTERVENTION_CONFIG = {
17
+ idleTimeoutMs: 120000, // 2分钟无响应触发提示
18
+ checkIntervalMs: 30000, // 30秒检测一次
19
+ };
20
+
21
+ export class StreamHandler {
22
+ constructor({ agent, config, panelBridge, riskTools = [], debug = () => {} }) {
23
+ this.agent = agent;
24
+ this.config = config;
25
+ this.panelBridge = panelBridge;
26
+ this.riskTools = riskTools;
27
+ this.debug = debug;
28
+ this.retryManager = new RetryManager();
29
+ }
30
+
31
+ /**
32
+ * 流式对话 - 显示思考过程(带重试)
33
+ */
34
+ async chatStream(input, retryCount = 0) {
35
+ let finalResponse = '';
36
+ let lastEventTime = Date.now();
37
+ let eventCount = 0;
38
+ let lastToolCall = null;
39
+
40
+ // 重置面板状态
41
+ this.panelBridge.reset();
42
+ await this.panelBridge.setBusy(true);
43
+
44
+ this.debug(`chatStream: 开始处理, 输入长度=${input.length}`);
45
+
46
+ // 心跳检测
47
+ let interventionNotified = false;
48
+ const heartbeat = this._createHeartbeat(
49
+ () => lastEventTime,
50
+ () => eventCount,
51
+ () => lastToolCall,
52
+ () => interventionNotified,
53
+ (v) => { interventionNotified = v; }
54
+ );
55
+
56
+ try {
57
+ this.debug('chatStream: 创建事件流');
58
+ const eventStream = await this.agent.streamEvents(
59
+ { messages: [{ role: 'user', content: input }] },
60
+ { ...this.config, version: 'v2' }
61
+ );
62
+
63
+ this.debug('chatStream: 开始遍历事件');
64
+ for await (const event of eventStream) {
65
+ lastEventTime = Date.now();
66
+ eventCount++;
67
+
68
+ if (event.event === 'on_tool_start') {
69
+ lastToolCall = event.name;
70
+ }
71
+
72
+ await this._handleStreamEvent(event);
73
+
74
+ if (event.event === 'on_chat_model_end' && event.name === 'ChatOpenAI') {
75
+ const output = event.data?.output;
76
+ if (output?.content) {
77
+ finalResponse = output.content;
78
+ this.debug(`chatStream: 收到最终响应, 长度=${finalResponse.length}`);
79
+ }
80
+ }
81
+ }
82
+
83
+ clearInterval(heartbeat);
84
+ console.log(`\n[完成] 共处理 ${eventCount} 个事件`);
85
+
86
+ await this.panelBridge.flushPanelText();
87
+ await this.panelBridge.finalizeMessage('assistant');
88
+ await this.panelBridge.setBusy(false);
89
+
90
+ this.debug(`chatStream: 完成, 响应长度=${finalResponse.length}`);
91
+ return finalResponse || '[无响应]';
92
+ } catch (error) {
93
+ clearInterval(heartbeat);
94
+ return this._handleError(error, input, eventCount, lastToolCall, retryCount);
95
+ }
96
+ }
97
+
98
+ /**
99
+ * 从检查点恢复流式对话
100
+ */
101
+ async chatStreamResume(retryCount = 0) {
102
+ let finalResponse = '';
103
+ let lastEventTime = Date.now();
104
+ let eventCount = 0;
105
+
106
+ await this.panelBridge.setBusy(true);
107
+ this.debug(`chatStreamResume: 从检查点恢复, retryCount=${retryCount}`);
108
+
109
+ const heartbeat = setInterval(() => {
110
+ const elapsed = Math.round((Date.now() - lastEventTime) / 1000);
111
+ if (elapsed > 30) {
112
+ console.log(`\n[心跳] 恢复中,已等待 ${elapsed}s`);
113
+ }
114
+ }, 30000);
115
+
116
+ try {
117
+ const eventStream = await this.agent.streamEvents(
118
+ { messages: [] },
119
+ { ...this.config, version: 'v2' }
120
+ );
121
+
122
+ for await (const event of eventStream) {
123
+ lastEventTime = Date.now();
124
+ eventCount++;
125
+ await this._handleStreamEvent(event);
126
+
127
+ if (event.event === 'on_chat_model_end' && event.name === 'ChatOpenAI') {
128
+ const output = event.data?.output;
129
+ if (output?.content) {
130
+ finalResponse = output.content;
131
+ }
132
+ }
133
+ }
134
+
135
+ clearInterval(heartbeat);
136
+ await this.panelBridge.flushPanelText();
137
+ await this.panelBridge.setBusy(false);
138
+ console.log(`\n[恢复完成] 共处理 ${eventCount} 个事件`);
139
+ return finalResponse || '[无响应]';
140
+ } catch (error) {
141
+ clearInterval(heartbeat);
142
+ await this.panelBridge.setBusy(false);
143
+ const errMsg = error.message || String(error);
144
+ console.error(`\n[恢复失败] ${errMsg}`);
145
+
146
+ if (isApiServiceError(errMsg) && this.retryManager.canRetry(retryCount)) {
147
+ const delay = this.retryManager.getDelay(retryCount);
148
+ console.log(`\n[重试 ${retryCount + 1}/${this.retryManager.maxRetries}] ${delay}ms 后再次恢复...`);
149
+ await sleep(delay);
150
+ return this.chatStreamResume(retryCount + 1);
151
+ }
152
+
153
+ return `恢复失败: ${errMsg}`;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * 创建心跳检测定时器
159
+ */
160
+ _createHeartbeat(getLastEventTime, getEventCount, getLastToolCall, getNotified, setNotified) {
161
+ return setInterval(() => {
162
+ const elapsed = Math.round((Date.now() - getLastEventTime()) / 1000);
163
+ if (elapsed > 30) {
164
+ console.log(`\n[心跳] 已等待 ${elapsed}s, 事件数=${getEventCount()}, 最后工具=${getLastToolCall() || '无'}`);
165
+ }
166
+
167
+ const isRiskTool = getLastToolCall() && this.riskTools.includes(getLastToolCall());
168
+ if (elapsed * 1000 > INTERVENTION_CONFIG.idleTimeoutMs && !getNotified() && isRiskTool) {
169
+ setNotified(true);
170
+ const msg = '⚠️ 页面操作后长时间无响应,可能遇到验证码或风控,请检查浏览器';
171
+ console.log('\n[提示] ' + msg);
172
+ this.panelBridge.sendToPanel('system', msg).catch(() => {});
173
+ }
174
+ }, INTERVENTION_CONFIG.checkIntervalMs);
175
+ }
176
+
177
+ /**
178
+ * 处理流式事件
179
+ */
180
+ async _handleStreamEvent(event) {
181
+ const { event: eventType, name, data } = event;
182
+
183
+ // 过滤内部事件
184
+ if (name?.startsWith('ChannelWrite') ||
185
+ name?.startsWith('Branch') ||
186
+ name?.includes('Middleware') ||
187
+ name === 'RunnableSequence' ||
188
+ name === 'model_request' ||
189
+ name === 'tools') {
190
+ return;
191
+ }
192
+
193
+ this.debug(`handleStreamEvent: ${eventType}, name=${name}`);
194
+
195
+ switch (eventType) {
196
+ case 'on_chat_model_stream':
197
+ let chunk = data?.chunk?.content;
198
+ if (chunk && typeof chunk === 'string') {
199
+ chunk = cleanDSML(chunk);
200
+ process.stdout.write(chunk);
201
+ await this.panelBridge.appendToPanel(chunk);
202
+ }
203
+ break;
204
+
205
+ case 'on_tool_start':
206
+ this.debug('handleStreamEvent: 工具开始,先刷新缓冲区');
207
+ await this.panelBridge.flushPanelText();
208
+ this.panelBridge.hasStartedAssistantMsg = false;
209
+ const input = data?.input || {};
210
+ const inputStr = typeof input === 'string' ? input : JSON.stringify(input);
211
+ const preview = inputStr.length > 100 ? inputStr.slice(0, 100) + '...' : inputStr;
212
+ console.log(`\n[调用] ${name}(${preview})`);
213
+ await this.panelBridge.sendToPanel('system', `[调用] ${name}`);
214
+ break;
215
+
216
+ case 'on_tool_end':
217
+ const output = data?.output;
218
+ let result = '';
219
+ this.debug(`on_tool_end: name=${name}, output type=${typeof output}`);
220
+
221
+ if (typeof output === 'string') {
222
+ result = output.slice(0, 80);
223
+ } else if (output?.content) {
224
+ result = String(output.content).slice(0, 80);
225
+ }
226
+ if (result) {
227
+ console.log(`[结果] ${result}${result.length >= 80 ? '...' : ''}`);
228
+ await this.panelBridge.sendToPanel('system', `[结果] ${result.slice(0, 50)}${result.length > 50 ? '...' : ''}`);
229
+ }
230
+ break;
231
+ }
232
+ }
233
+
234
+ /**
235
+ * 处理错误和重试
236
+ */
237
+ async _handleError(error, input, eventCount, lastToolCall, retryCount) {
238
+ const errMsg = error.message || String(error);
239
+ await this.panelBridge.setBusy(false);
240
+ console.error(`\n[异常] 事件数=${eventCount}, 最后工具=${lastToolCall || '无'}, 错误: ${errMsg}`);
241
+
242
+ if (this.retryManager.canRetry(retryCount)) {
243
+ if (isApiServiceError(errMsg)) {
244
+ const delay = this.retryManager.getDelay(retryCount);
245
+ console.log(`\n[重试 ${retryCount + 1}/${this.retryManager.maxRetries}] API错误,${delay}ms 后从检查点恢复...`);
246
+ await this.panelBridge.sendToPanel('system',
247
+ `服务暂时不可用,${Math.round(delay/1000)}s 后重试 (${retryCount + 1}/${this.retryManager.maxRetries})`);
248
+ await sleep(delay);
249
+ return this.chatStreamResume(retryCount + 1);
250
+ }
251
+
252
+ if (isToolSchemaError(errMsg)) {
253
+ console.log(`\n[重试 ${retryCount + 1}/${this.retryManager.maxRetries}] 工具参数错误,发送修正请求...`);
254
+ await this.panelBridge.sendToPanel('system',
255
+ `工具调用失败,正在修正 (${retryCount + 1}/${this.retryManager.maxRetries})`);
256
+ const resumeInput = `工具调用失败: ${errMsg}\n请检查参数格式并重试。`;
257
+ return this.chatStream(resumeInput, retryCount + 1);
258
+ }
259
+ }
260
+
261
+ return `错误: ${errMsg}`;
262
+ }
263
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * DeepSpider - Core 模块索引
3
+ */
4
+
5
+ export { StreamHandler } from './StreamHandler.js';
6
+ export { RetryManager, sleep } from './RetryManager.js';
7
+ export { PanelBridge } from './PanelBridge.js';