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.
- package/README.md +71 -24
- package/bin/cli.js +45 -0
- package/package.json +10 -4
- package/src/agent/core/PanelBridge.js +133 -0
- package/src/agent/core/RetryManager.js +51 -0
- package/src/agent/core/StreamHandler.js +263 -0
- package/src/agent/core/index.js +7 -0
- package/src/agent/errors/ErrorClassifier.js +43 -0
- package/src/agent/errors/SpiderError.js +68 -0
- package/src/agent/errors/index.js +19 -0
- package/src/agent/run.js +67 -460
- package/src/agent/setup.js +14 -14
- package/src/agent/subagents/factory.js +60 -0
- package/src/agent/subagents/index.js +3 -0
- package/src/agent/tools/report.js +36 -4
- package/src/browser/client.js +47 -10
- package/src/cli/commands/config.js +94 -0
- package/src/cli/commands/help.js +34 -0
- package/src/cli/commands/update.js +78 -0
- package/src/cli/commands/version.js +9 -0
- package/src/cli/config.js +15 -0
- package/src/config/settings.js +102 -0
- package/.claude/agents/check.md +0 -122
- package/.claude/agents/debug.md +0 -106
- package/.claude/agents/dispatch.md +0 -214
- package/.claude/agents/implement.md +0 -96
- package/.claude/agents/plan.md +0 -396
- package/.claude/agents/research.md +0 -120
- package/.claude/commands/evolve/merge.md +0 -80
- package/.claude/commands/trellis/before-backend-dev.md +0 -13
- package/.claude/commands/trellis/before-frontend-dev.md +0 -13
- package/.claude/commands/trellis/break-loop.md +0 -107
- package/.claude/commands/trellis/check-backend.md +0 -13
- package/.claude/commands/trellis/check-cross-layer.md +0 -153
- package/.claude/commands/trellis/check-frontend.md +0 -13
- package/.claude/commands/trellis/create-command.md +0 -154
- package/.claude/commands/trellis/finish-work.md +0 -129
- package/.claude/commands/trellis/integrate-skill.md +0 -219
- package/.claude/commands/trellis/onboard.md +0 -358
- package/.claude/commands/trellis/parallel.md +0 -193
- package/.claude/commands/trellis/record-session.md +0 -62
- package/.claude/commands/trellis/start.md +0 -280
- package/.claude/commands/trellis/update-spec.md +0 -213
- package/.claude/hooks/inject-subagent-context.py +0 -758
- package/.claude/hooks/ralph-loop.py +0 -374
- package/.claude/hooks/session-start.py +0 -126
- package/.claude/settings.json +0 -41
- package/.claude/skills/deepagents-guide/SKILL.md +0 -428
- package/.cursor/commands/trellis-before-backend-dev.md +0 -13
- package/.cursor/commands/trellis-before-frontend-dev.md +0 -13
- package/.cursor/commands/trellis-break-loop.md +0 -107
- package/.cursor/commands/trellis-check-backend.md +0 -13
- package/.cursor/commands/trellis-check-cross-layer.md +0 -153
- package/.cursor/commands/trellis-check-frontend.md +0 -13
- package/.cursor/commands/trellis-create-command.md +0 -154
- package/.cursor/commands/trellis-finish-work.md +0 -129
- package/.cursor/commands/trellis-integrate-skill.md +0 -219
- package/.cursor/commands/trellis-onboard.md +0 -358
- package/.cursor/commands/trellis-record-session.md +0 -62
- package/.cursor/commands/trellis-start.md +0 -156
- package/.cursor/commands/trellis-update-spec.md +0 -213
- package/.github/workflows/publish.yml +0 -63
- package/.husky/pre-commit +0 -1
- package/.mcp.json +0 -8
- package/.trellis/.template-hashes.json +0 -65
- package/.trellis/.version +0 -1
- package/.trellis/scripts/add-session.sh +0 -384
- package/.trellis/scripts/common/developer.sh +0 -129
- package/.trellis/scripts/common/git-context.sh +0 -263
- package/.trellis/scripts/common/paths.sh +0 -208
- package/.trellis/scripts/common/phase.sh +0 -150
- package/.trellis/scripts/common/registry.sh +0 -247
- package/.trellis/scripts/common/task-queue.sh +0 -142
- package/.trellis/scripts/common/task-utils.sh +0 -151
- package/.trellis/scripts/common/worktree.sh +0 -128
- package/.trellis/scripts/create-bootstrap.sh +0 -299
- package/.trellis/scripts/get-context.sh +0 -7
- package/.trellis/scripts/get-developer.sh +0 -15
- package/.trellis/scripts/init-developer.sh +0 -34
- package/.trellis/scripts/multi-agent/cleanup.sh +0 -396
- package/.trellis/scripts/multi-agent/create-pr.sh +0 -241
- package/.trellis/scripts/multi-agent/plan.sh +0 -207
- package/.trellis/scripts/multi-agent/start.sh +0 -310
- package/.trellis/scripts/multi-agent/status.sh +0 -828
- package/.trellis/scripts/task.sh +0 -1118
- package/.trellis/spec/backend/ci-cd-guidelines.md +0 -73
- package/.trellis/spec/backend/deepagents-guide.md +0 -380
- package/.trellis/spec/backend/directory-structure.md +0 -126
- package/.trellis/spec/backend/examples/skills/deepagents-guide/README.md +0 -11
- package/.trellis/spec/backend/examples/skills/deepagents-guide/agent.js.template +0 -20
- package/.trellis/spec/backend/examples/skills/deepagents-guide/skills-config.js.template +0 -13
- package/.trellis/spec/backend/examples/skills/deepagents-guide/subagent.js.template +0 -19
- package/.trellis/spec/backend/hook-guidelines.md +0 -218
- package/.trellis/spec/backend/index.md +0 -37
- package/.trellis/spec/backend/quality-guidelines.md +0 -302
- package/.trellis/spec/backend/state-management.md +0 -76
- package/.trellis/spec/backend/tool-guidelines.md +0 -144
- package/.trellis/spec/backend/type-safety.md +0 -71
- package/.trellis/spec/guides/code-reuse-thinking-guide.md +0 -92
- package/.trellis/spec/guides/cross-layer-thinking-guide.md +0 -94
- package/.trellis/spec/guides/index.md +0 -79
- package/.trellis/tasks/archive/02-02-evolving-skills/prd.md +0 -61
- package/.trellis/tasks/archive/02-02-evolving-skills/task.json +0 -29
- package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/prd.md +0 -86
- package/.trellis/tasks/archive/2026-02/00-bootstrap-guidelines/task.json +0 -27
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/check.jsonl +0 -3
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/debug.jsonl +0 -2
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/implement.jsonl +0 -5
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/prd.md +0 -33
- package/.trellis/tasks/archive/2026-02/02-02-skills-system/task.json +0 -41
- package/.trellis/workflow.md +0 -407
- package/.trellis/workspace/index.md +0 -123
- package/.trellis/workspace/pony/index.md +0 -42
- package/.trellis/workspace/pony/journal-1.md +0 -125
- package/.trellis/worktree.yaml +0 -47
- package/AGENTS.md +0 -18
- package/CLAUDE.md +0 -315
- package/agents/deepspider.md +0 -142
- package/docs/DEBUG.md +0 -42
- package/docs/GUIDE.md +0 -334
- package/docs/PROMPT.md +0 -60
- package/docs/USAGE.md +0 -226
- package/eslint.config.js +0 -51
- package/test/analyze.test.js +0 -90
- package/test/envdump.test.js +0 -74
- package/test/flow.test.js +0 -90
- package/test/hooks.test.js +0 -138
- package/test/plugin.test.js +0 -35
- package/test/refactor-full.test.js +0 -30
- package/test/refactor.test.js +0 -21
- package/test/samples/obfuscated.js +0 -61
- package/test/samples/original.js +0 -66
- package/test/samples/v10_eval_chain.js +0 -52
- package/test/samples/v11_bytecode_vm.js +0 -81
- package/test/samples/v12_polymorphic.js +0 -69
- package/test/samples/v1_ob_basic.js +0 -98
- package/test/samples/v2_ob_advanced.js +0 -99
- package/test/samples/v3_jjencode.js +0 -77
- package/test/samples/v4_aaencode.js +0 -73
- package/test/samples/v5_control_flow.js +0 -86
- package/test/samples/v6_string_encryption.js +0 -71
- package/test/samples/v7_jsvmp.js +0 -83
- package/test/samples/v8_anti_debug.js +0 -79
- package/test/samples/v9_proxy_trap.js +0 -49
- package/test/samples.test.js +0 -96
- 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
|
-
|
|
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
|
-
| `
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
67
|
-
|
|
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
|
-
|
|
92
|
+
# 启动 Agent - 指定目标网站
|
|
93
|
+
deepspider https://example.com
|
|
81
94
|
|
|
82
|
-
# Agent
|
|
83
|
-
|
|
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.
|
|
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": "./
|
|
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
|
|
19
|
+
"cli": "node bin/cli.js",
|
|
14
20
|
"mcp": "node src/mcp/server.js",
|
|
15
|
-
"agent": "node
|
|
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
|
+
}
|