yiyan-browser-agent 1.10.2 → 1.11.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 +5 -5
- package/docs/USAGE_GUIDE.md +366 -0
- package/package.json +2 -2
- package/src/browser.js +177 -97
- package/src/config.js +1 -1
- package/src/logger.js +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
**An autonomous AI coding agent that runs entirely for free — no API key required.**
|
|
11
11
|
|
|
12
|
-
It drives a real browser to talk to [Yiyan (文心一言)](https://
|
|
12
|
+
It drives a real browser to talk to [Yiyan (文心一言)](https://chat.baidu.com), giving you a Claude Code / Cursor-style coding agent powered by Baidu's AI models at zero cost.
|
|
13
13
|
|
|
14
14
|
[Installation](#-installation) · [Quick Start](#-quick-start) · [Usage](#-usage) · [HTTP API](#-http-api) · [Configuration](#-configuration) · [Tools](#-available-tools) · [Contributing](#-contributing)
|
|
15
15
|
|
|
@@ -26,7 +26,7 @@ It drives a real browser to talk to [Yiyan (文心一言)](https://yiyan.baidu.c
|
|
|
26
26
|
|
|
27
27
|
Most AI coding agents talk to a paid API. This one doesn't.
|
|
28
28
|
|
|
29
|
-
Instead, it uses **Playwright** to control a real Chromium browser, navigates to `
|
|
29
|
+
Instead, it uses **Playwright** to control a real Chromium browser, navigates to `chat.baidu.com`, sends your task, waits for the response, and parses it to extract tool calls — all automatically. Your local files and terminal are wired up as tools the AI can use, so it can read code, write files, run commands, and and build complete projects step by step.
|
|
30
30
|
|
|
31
31
|
```
|
|
32
32
|
Your Terminal
|
|
@@ -34,7 +34,7 @@ Your Terminal
|
|
|
34
34
|
▼
|
|
35
35
|
Agent Core ← orchestrates the loop
|
|
36
36
|
│
|
|
37
|
-
├──► Browser (Playwright) ← talks to
|
|
37
|
+
├──► Browser (Playwright) ← talks to chat.baidu.com
|
|
38
38
|
│ │
|
|
39
39
|
│ Yiyan AI (文心一言) ← thinks, decides what tool to use
|
|
40
40
|
│ │
|
|
@@ -551,7 +551,7 @@ yiyan-browser-agent/
|
|
|
551
551
|
├── src/
|
|
552
552
|
│ ├── index.js ← CLI entry point and argument parsing
|
|
553
553
|
│ ├── agent.js ← Core agent loop (send → wait → parse → execute)
|
|
554
|
-
│ ├── browser.js ← Playwright controller for
|
|
554
|
+
│ ├── browser.js ← Playwright controller for chat.baidu.com
|
|
555
555
|
│ ├── server.js ← HTTP server for process communication (v1.5.0+)
|
|
556
556
|
│ ├── client.js ← HTTP client to forward tasks (v1.5.0+)
|
|
557
557
|
│ ├── tools.js ← All 15 filesystem and shell tools
|
|
@@ -611,7 +611,7 @@ Open an issue on GitHub with:
|
|
|
611
611
|
|
|
612
612
|
## ⚠️ Disclaimer
|
|
613
613
|
|
|
614
|
-
This project automates a web browser to interact with
|
|
614
|
+
This project automates a web browser to interact with chat.baidu.com. Automating web UIs may violate the terms of service of the website being automated. Use this tool for **personal and development purposes only**. The authors take no responsibility for account suspensions or other consequences of use.
|
|
615
615
|
|
|
616
616
|
---
|
|
617
617
|
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Yiyan Browser Agent 使用指南
|
|
2
|
+
|
|
3
|
+
## 📖 完整使用流程
|
|
4
|
+
|
|
5
|
+
### 第一步:首次登录(有头模式)
|
|
6
|
+
|
|
7
|
+
**首次使用需要登录百度账号,必须使用有头模式:**
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
# 启动交互模式(有头,浏览器可见)
|
|
11
|
+
yiyan-agent -i
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
**操作步骤:**
|
|
15
|
+
|
|
16
|
+
1. 浏览器窗口自动打开 Yiyan 网站 (chat.baidu.com)
|
|
17
|
+
2. 在浏览器中手动登录你的百度账号
|
|
18
|
+
3. 登录成功后回到终端按 Enter 继续
|
|
19
|
+
4. 会话会自动保存到 `~/.yiyan-agent/session/`
|
|
20
|
+
5. **只需登录一次,后续自动复用**
|
|
21
|
+
|
|
22
|
+
**为什么需要有头模式登录?**
|
|
23
|
+
- 需要手动输入账号密码
|
|
24
|
+
- 可能需要验证码
|
|
25
|
+
- 需要看到登录界面操作
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
### 第二步:切换到后台服务(无头模式)
|
|
30
|
+
|
|
31
|
+
**登录成功后,切换到无头模式后台运行:**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# 关闭当前有头模式服务器(Ctrl+C)
|
|
35
|
+
|
|
36
|
+
# 启动无头交互服务器(后台运行)
|
|
37
|
+
yiyan-agent -i --headless
|
|
38
|
+
|
|
39
|
+
# 或使用 nohup 真正后台运行
|
|
40
|
+
nohup yiyan-agent -i --headless > yiyan.log 2>&1 &
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**无头模式优势:**
|
|
44
|
+
- ✅ **无浏览器窗口**(节省资源)
|
|
45
|
+
- ✅ **性能最优**(30-40% 加速)
|
|
46
|
+
- ✅ **后台服务**(适合服务器部署)
|
|
47
|
+
- ✅ **自动复用登录会话**(无需重复登录)
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
### 第三步:提交任务(客户端)
|
|
52
|
+
|
|
53
|
+
**后台服务启动后,客户端提交任务:**
|
|
54
|
+
|
|
55
|
+
#### 方式 1:命令行客户端
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# 自动检测后台服务器并转发任务
|
|
59
|
+
yiyan-agent "创建 Express REST API"
|
|
60
|
+
|
|
61
|
+
# 无需浏览器,任务自动转发到后台服务器
|
|
62
|
+
# 后台服务器使用保存的登录会话访问 Yiyan
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### 方式 2:HTTP API
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 直接调用后台服务器 HTTP 接口
|
|
69
|
+
curl -X POST http://localhost:9527/task \
|
|
70
|
+
-H "Content-Type: application/json" \
|
|
71
|
+
-d '{"task":"创建基础 Express 服务器"}'
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 🔄 模式切换流程图
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
80
|
+
│ 完整使用流程 │
|
|
81
|
+
└─────────────────────────────────────────────────────────────┘
|
|
82
|
+
|
|
83
|
+
首次使用:
|
|
84
|
+
步骤 1: yiyan-agent -i (有头模式)
|
|
85
|
+
│
|
|
86
|
+
├─ 浏览器打开 Yiyan 网站
|
|
87
|
+
├─ 手动登录百度账号
|
|
88
|
+
├─ 会话保存到 ~/.yiyan-agent/session/
|
|
89
|
+
│
|
|
90
|
+
步骤 2: Ctrl+C 关闭 (停止有头服务器)
|
|
91
|
+
│
|
|
92
|
+
步骤 3: yiyan-agent -i --headless (无头模式)
|
|
93
|
+
│
|
|
94
|
+
├─ 后台运行,无浏览器窗口
|
|
95
|
+
├─ 自动复用登录会话
|
|
96
|
+
├─ 监听端口 9527
|
|
97
|
+
│
|
|
98
|
+
步骤 4: yiyan-agent "任务" (客户端提交)
|
|
99
|
+
│
|
|
100
|
+
├─ 自动转发到后台服务器
|
|
101
|
+
├─ 后台服务器处理任务
|
|
102
|
+
└─ 返回结果到客户端
|
|
103
|
+
|
|
104
|
+
后续使用:
|
|
105
|
+
yiyan-agent -i --headless (直接启动无头服务)
|
|
106
|
+
yiyan-agent "任务" (提交任务)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📊 模式对比表
|
|
112
|
+
|
|
113
|
+
| 模式 | 命令 | 浏览器 | 性能 | 适用场景 |
|
|
114
|
+
|------|------|--------|------|---------|
|
|
115
|
+
| **有头交互** | `yiyan-agent -i` | ✅ 显示 | 标准 | 首次登录、调试 |
|
|
116
|
+
| **无头交互** | `yiyan-agent -i --headless` | ❌ 隐藏 | ⚡ **最优** | 后台服务、生产环境 |
|
|
117
|
+
| **单任务** | `yiyan-agent "任务"` | ❌ 隐藏 | ⚡ 快 | 自动转发到服务器 |
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 🚀 生产环境部署
|
|
122
|
+
|
|
123
|
+
### Docker 部署
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
# 1. 创建 Dockerfile
|
|
127
|
+
FROM node:18
|
|
128
|
+
RUN npm install -g yiyan-browser-agent
|
|
129
|
+
RUN npx playwright install chromium
|
|
130
|
+
|
|
131
|
+
# 2. 启动容器(无头模式)
|
|
132
|
+
docker run -d \
|
|
133
|
+
-p 9527:9527 \
|
|
134
|
+
-v ~/.yiyan-agent:/root/.yiyan-agent \
|
|
135
|
+
--name yiyan-agent \
|
|
136
|
+
yiyan-agent -i --headless
|
|
137
|
+
|
|
138
|
+
# 3. 客户端提交任务
|
|
139
|
+
curl -X POST http://localhost:9527/task \
|
|
140
|
+
-d '{"task":"创建项目"}'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### systemd 服务部署
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# 1. 创建服务文件
|
|
147
|
+
sudo nano /etc/systemd/system/yiyan-agent.service
|
|
148
|
+
|
|
149
|
+
[Unit]
|
|
150
|
+
Description=Yiyan Browser Agent Service
|
|
151
|
+
After=network.target
|
|
152
|
+
|
|
153
|
+
[Service]
|
|
154
|
+
Type=simple
|
|
155
|
+
User=your-user
|
|
156
|
+
ExecStart=/usr/bin/yiyan-agent -i --headless
|
|
157
|
+
Restart=on-failure
|
|
158
|
+
RestartSec=10
|
|
159
|
+
|
|
160
|
+
[Install]
|
|
161
|
+
WantedBy=multi-user.target
|
|
162
|
+
|
|
163
|
+
# 2. 启动服务
|
|
164
|
+
sudo systemctl start yiyan-agent
|
|
165
|
+
sudo systemctl enable yiyan-agent
|
|
166
|
+
|
|
167
|
+
# 3. 查看日志
|
|
168
|
+
sudo journalctl -u yiyan-agent -f
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## ⚙️ 配置优化
|
|
174
|
+
|
|
175
|
+
### 性能配置(已默认启用)
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
// ~/.yiyan-agent/config.json
|
|
179
|
+
{
|
|
180
|
+
"STABLE_DELAY": 1500, // 快速响应检测
|
|
181
|
+
"SEND_DELAY": 100, // 快速发送
|
|
182
|
+
"RESPONSE_TIMEOUT": 120000, // 120s 超时
|
|
183
|
+
"HEADLESS": true // 无头模式(性能优先)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 安全配置(已默认启用)
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"COMMAND_SECURITY_ENABLED": true,
|
|
192
|
+
"PATH_TRAVERSAL_PROTECTION": true,
|
|
193
|
+
"FILE_OVERWRITE_PROTECTION": true,
|
|
194
|
+
"FILE_BACKUP_ENABLED": true
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 🔧 常见问题
|
|
201
|
+
|
|
202
|
+
### Q1: 如何重新登录?
|
|
203
|
+
|
|
204
|
+
```bash
|
|
205
|
+
# 1. 停止后台服务
|
|
206
|
+
yiyan-agent --stop # 或 Ctrl+C
|
|
207
|
+
|
|
208
|
+
# 2. 删除旧会话
|
|
209
|
+
rm -rf ~/.yiyan-agent/session/
|
|
210
|
+
|
|
211
|
+
# 3. 有头模式重新登录
|
|
212
|
+
yiyan-agent -i # 浏览器打开,手动登录
|
|
213
|
+
|
|
214
|
+
# 4. 登录后切换无头
|
|
215
|
+
yiyan-agent -i --headless
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Q2: 后台服务如何调试?
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
# 临时使用有头模式查看问题
|
|
222
|
+
yiyan-agent -i # 查看浏览器操作
|
|
223
|
+
|
|
224
|
+
# 或启用调试日志
|
|
225
|
+
yiyan-agent -i --headless --debug
|
|
226
|
+
# 查看 yiyan.log 日志文件
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Q3: 如何查看后台服务状态?
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
# 查看服务是否运行
|
|
233
|
+
ps aux | grep yiyan-agent
|
|
234
|
+
|
|
235
|
+
# 查看端口占用
|
|
236
|
+
netstat -tunlp | grep 9527
|
|
237
|
+
|
|
238
|
+
# 查看锁文件
|
|
239
|
+
ls ~/.yiyan-agent/server.lock
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Q4: 如何停止后台服务?
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
# 方式 1: 找到进程并杀死
|
|
246
|
+
ps aux | grep yiyan-agent
|
|
247
|
+
kill <PID>
|
|
248
|
+
|
|
249
|
+
# 方式 2: 删除锁文件(自动停止)
|
|
250
|
+
rm ~/.yiyan-agent/server.lock
|
|
251
|
+
|
|
252
|
+
# 方式 3: systemd 服务
|
|
253
|
+
sudo systemctl stop yiyan-agent
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 📁 文件位置
|
|
259
|
+
|
|
260
|
+
### 会话文件
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
~/.yiyan-agent/
|
|
264
|
+
├── session/ ← 登录会话(cookies)
|
|
265
|
+
│ └── Default/ ← Chromium 会话数据
|
|
266
|
+
├── logs/ ← 日志文件
|
|
267
|
+
│ ├── command-security.log ← 命令执行审计
|
|
268
|
+
│ ├── file-security.log ← 文件操作审计
|
|
269
|
+
│ └── yiyan.log ← 服务日志(自定义)
|
|
270
|
+
├── backups/ ← 文件备份
|
|
271
|
+
│ └── filename.timestamp.bak ← 被覆盖的文件备份
|
|
272
|
+
├── server.lock ← 后台服务器锁文件
|
|
273
|
+
└── config.json ← 全局配置(可选)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 💡 最佳实践
|
|
279
|
+
|
|
280
|
+
### 开发环境
|
|
281
|
+
|
|
282
|
+
```bash
|
|
283
|
+
# 调试模式(有头)
|
|
284
|
+
yiyan-agent -i --debug
|
|
285
|
+
|
|
286
|
+
# 可看到浏览器操作和详细日志
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 生产环境
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
# 后台服务(无头)
|
|
293
|
+
nohup yiyan-agent -i --headless > /var/log/yiyan.log 2>&1 &
|
|
294
|
+
|
|
295
|
+
# 客户端提交任务
|
|
296
|
+
yiyan-agent "自动化任务"
|
|
297
|
+
|
|
298
|
+
# 定期检查日志
|
|
299
|
+
tail -f /var/log/yiyan.log
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### 安全建议
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
# 1. 定期审查安全日志
|
|
306
|
+
tail -100 ~/.yiyan-agent/logs/command-security.log
|
|
307
|
+
|
|
308
|
+
# 2. 定期清理旧备份
|
|
309
|
+
find ~/.yiyan-agent/backups -mtime +30 -delete
|
|
310
|
+
|
|
311
|
+
# 3. 监控后台服务
|
|
312
|
+
watch -n 5 'ps aux | grep yiyan-agent'
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
---
|
|
316
|
+
|
|
317
|
+
## 🎯 快速参考
|
|
318
|
+
|
|
319
|
+
```bash
|
|
320
|
+
# 首次登录流程
|
|
321
|
+
yiyan-agent -i # 有头模式登录
|
|
322
|
+
Ctrl+C # 关闭
|
|
323
|
+
yiyan-agent -i --headless # 无头后台服务
|
|
324
|
+
|
|
325
|
+
# 日常使用
|
|
326
|
+
yiyan-agent -i --headless # 启动后台服务
|
|
327
|
+
yiyan-agent "任务" # 提交任务
|
|
328
|
+
|
|
329
|
+
# 调试模式
|
|
330
|
+
yiyan-agent -i # 有头可视化
|
|
331
|
+
yiyan-agent -i --debug # 详细日志
|
|
332
|
+
|
|
333
|
+
# 参数说明
|
|
334
|
+
--headless 无头模式(强制)
|
|
335
|
+
--show-browser 有头模式(强制)
|
|
336
|
+
--debug 调试模式
|
|
337
|
+
-i 交互模式
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
---
|
|
341
|
+
|
|
342
|
+
## 📚 相关文档
|
|
343
|
+
|
|
344
|
+
- [安全解决方案文档](SECURITY_SOLUTION.md) - 完整安全配置说明
|
|
345
|
+
- [CHANGELOG.md](../CHANGELOG.md) - 版本更新历史
|
|
346
|
+
- [README.md](../README.md) - 项目概述
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## 🆘 获取帮助
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# 命令行帮助
|
|
354
|
+
yiyan-agent --help
|
|
355
|
+
|
|
356
|
+
# GitHub Issues
|
|
357
|
+
https://github.com/readfor/yiyan-browser-agent/issues
|
|
358
|
+
|
|
359
|
+
# 日志位置
|
|
360
|
+
~/.yiyan-agent/logs/
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
**版本: 1.10.2**
|
|
366
|
+
**最后更新: 2026-06-02**
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yiyan-browser-agent",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "AI coding agent powered by Yiyan (文心一言) via browser automation — no API key needed. Performance-optimized
|
|
3
|
+
"version": "1.11.0",
|
|
4
|
+
"description": "AI coding agent powered by Yiyan (文心一言) via browser automation (chat.baidu.com) — no API key needed. Performance-optimized. Enhanced with comprehensive security.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"yiyan-agent": "src/index.js",
|
package/src/browser.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/browser.js — Playwright controller for Yiyan (
|
|
1
|
+
// src/browser.js — Playwright controller for Yiyan (chat.baidu.com)
|
|
2
2
|
// Performance optimized: smart waits, paste input, MutationObserver
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
@@ -14,12 +14,11 @@ const CrashHandler = require('./stability/crash-handler');
|
|
|
14
14
|
|
|
15
15
|
const SEL = {
|
|
16
16
|
chatInput: [
|
|
17
|
-
'
|
|
18
|
-
'
|
|
19
|
-
'.editable',
|
|
20
|
-
'#chat-input',
|
|
17
|
+
'#chat-textarea',
|
|
18
|
+
'textarea.ci-textarea',
|
|
21
19
|
'textarea[placeholder]',
|
|
22
20
|
'textarea',
|
|
21
|
+
'[role="textbox"]',
|
|
23
22
|
'[contenteditable="true"][role="textbox"]',
|
|
24
23
|
'[contenteditable="true"]',
|
|
25
24
|
'[class*="input-box"]',
|
|
@@ -46,12 +45,16 @@ const SEL = {
|
|
|
46
45
|
'button[aria-label*="停止"]',
|
|
47
46
|
'[aria-label*="stop generating" i]',
|
|
48
47
|
'[data-testid="stop-button"]',
|
|
48
|
+
'[class*="stop-gen"]',
|
|
49
|
+
'[class*="stopGen"]',
|
|
49
50
|
'[class*="stop-btn"]',
|
|
50
51
|
'[class*="stopBtn"]',
|
|
51
52
|
'[class*="abort"]',
|
|
52
53
|
],
|
|
53
54
|
|
|
54
55
|
newChat: [
|
|
56
|
+
'.new-dialog-container-button',
|
|
57
|
+
'[class*="new-dialog-container"]',
|
|
55
58
|
'button[aria-label*="New chat" i]',
|
|
56
59
|
'button[aria-label*="新对话"]',
|
|
57
60
|
'button[aria-label*="New conversation" i]',
|
|
@@ -61,15 +64,19 @@ const SEL = {
|
|
|
61
64
|
'[class*="newChat"]',
|
|
62
65
|
],
|
|
63
66
|
|
|
64
|
-
// Response ready indicators
|
|
67
|
+
// Response ready indicators (chat.baidu.com)
|
|
65
68
|
responseReady: [
|
|
69
|
+
'.ai-entry-block.ai-markdown',
|
|
70
|
+
'.answer-container',
|
|
71
|
+
'.cs-answer-container',
|
|
72
|
+
'.answer-box',
|
|
66
73
|
'[class*="answer"]',
|
|
67
|
-
'[class*="response"]',
|
|
68
74
|
'[class*="markdown"]',
|
|
69
|
-
'.ds-markdown',
|
|
70
75
|
],
|
|
71
76
|
|
|
72
77
|
messageContainer: [
|
|
78
|
+
'#chat-container-main',
|
|
79
|
+
'[class*="chat-container"]',
|
|
73
80
|
'[class*="chat-content"]',
|
|
74
81
|
'[class*="message-list"]',
|
|
75
82
|
'[class*="conversation"]',
|
|
@@ -178,17 +185,22 @@ class YiyanBrowser {
|
|
|
178
185
|
|
|
179
186
|
const needsLogin = await this.page.evaluate(() => {
|
|
180
187
|
const url = window.location.href;
|
|
188
|
+
// URL 中包含登录/认证路径 → 需要登录
|
|
189
|
+
if (url.includes('/auth') || url.includes('/login') || url.includes('/sign')) {
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
// 存在密码输入框 → 需要登录
|
|
193
|
+
if (document.querySelector('input[type="password"]')) {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
// 页面主体区域被登录界面遮挡(排除侧栏的"请登录"提示)
|
|
181
197
|
const bodyText = document.body?.innerText || '';
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
bodyText.includes('登录') ||
|
|
189
|
-
bodyText.includes('登 录') ||
|
|
190
|
-
!!document.querySelector('input[type="password"]')
|
|
191
|
-
);
|
|
198
|
+
const mainInput = document.querySelector('#chat-textarea, textarea.ci-textarea');
|
|
199
|
+
if (!mainInput) {
|
|
200
|
+
// 输入框不存在可能是需要登录
|
|
201
|
+
return bodyText.includes('Sign in') || bodyText.includes('Log in');
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
192
204
|
});
|
|
193
205
|
|
|
194
206
|
if (needsLogin) {
|
|
@@ -235,18 +247,24 @@ class YiyanBrowser {
|
|
|
235
247
|
// ── Sending Messages (stable: keyboard.type) ────────────────────────────────
|
|
236
248
|
|
|
237
249
|
async sendMessage(text) {
|
|
238
|
-
const { el } = await this._findInput();
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
+
const { el, isTextarea } = await this._findInput();
|
|
251
|
+
|
|
252
|
+
if (isTextarea) {
|
|
253
|
+
// textarea 元素:用 fill() 快速填充
|
|
254
|
+
await el.click({ force: true });
|
|
255
|
+
await this.page.waitForTimeout(100);
|
|
256
|
+
await el.fill('');
|
|
257
|
+
await this.page.waitForTimeout(50);
|
|
258
|
+
await el.fill(text);
|
|
259
|
+
await this.page.waitForTimeout(100);
|
|
260
|
+
} else {
|
|
261
|
+
// contenteditable 元素:用键盘输入
|
|
262
|
+
await el.click({ clickCount: 3, force: true });
|
|
263
|
+
await this.page.waitForTimeout(100);
|
|
264
|
+
await this.page.keyboard.press('Delete');
|
|
265
|
+
await this.page.waitForTimeout(50);
|
|
266
|
+
await this.page.keyboard.type(text, { delay: 10 });
|
|
267
|
+
}
|
|
250
268
|
|
|
251
269
|
// Press Enter to send
|
|
252
270
|
await this.page.keyboard.press('Enter');
|
|
@@ -308,9 +326,21 @@ class YiyanBrowser {
|
|
|
308
326
|
while (Date.now() - start < timeout) {
|
|
309
327
|
const text = await this._extractLastMessage();
|
|
310
328
|
|
|
311
|
-
// ── 思考区域内容检测 ──
|
|
329
|
+
// ── 思考区域内容检测 (chat.baidu.com) ──
|
|
312
330
|
const thinkingInfo = await this.page.evaluate(() => {
|
|
313
|
-
|
|
331
|
+
// chat.baidu.com 深度思考区域
|
|
332
|
+
const thinkingSelectors = [
|
|
333
|
+
'[class*="deep-think"]',
|
|
334
|
+
'[class*="deepThink"]',
|
|
335
|
+
'[class*="deep-search"]',
|
|
336
|
+
'[class*="thinking-container"]',
|
|
337
|
+
'[class*="container__SPpah"]', // 旧版兼容
|
|
338
|
+
];
|
|
339
|
+
let thinkingEl = null;
|
|
340
|
+
for (const sel of thinkingSelectors) {
|
|
341
|
+
thinkingEl = document.querySelector(sel);
|
|
342
|
+
if (thinkingEl) break;
|
|
343
|
+
}
|
|
314
344
|
if (!thinkingEl) return { text: '', exists: false };
|
|
315
345
|
|
|
316
346
|
const s = window.getComputedStyle(thinkingEl);
|
|
@@ -320,17 +350,22 @@ class YiyanBrowser {
|
|
|
320
350
|
return { text, exists: true };
|
|
321
351
|
});
|
|
322
352
|
|
|
323
|
-
// ──
|
|
353
|
+
// ── 统一稳定性检测:答案不变即算稳定 ──
|
|
324
354
|
const thinkingStable = thinkingInfo.text === lastThinkingText;
|
|
325
355
|
const answerStable = text === lastText;
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
356
|
+
// 如果有思考区域,需要两者都稳定;否则只看答案
|
|
357
|
+
const hasThinking = thinkingInfo.exists && thinkingInfo.text.length > 0;
|
|
358
|
+
const hasContent = text.length > 0;
|
|
359
|
+
const stable = hasThinking
|
|
360
|
+
? (thinkingStable && answerStable && hasContent)
|
|
361
|
+
: (answerStable && hasContent);
|
|
362
|
+
|
|
363
|
+
if (stable) {
|
|
364
|
+
// 稳定且有内容,检查稳定时间
|
|
330
365
|
if (Date.now() - lastStableTime >= stableDelay) {
|
|
331
366
|
stableCount++;
|
|
332
367
|
lastStableTime = Date.now();
|
|
333
|
-
logger.dim(`
|
|
368
|
+
logger.dim(`Stable: ${stableCount}/2 (answer: ${text.length} chars${hasThinking ? ', thinking: ' + thinkingInfo.text.length + ' chars' : ''})`);
|
|
334
369
|
}
|
|
335
370
|
} else {
|
|
336
371
|
// 任一变化,重置
|
|
@@ -340,13 +375,19 @@ class YiyanBrowser {
|
|
|
340
375
|
lastStableTime = Date.now();
|
|
341
376
|
}
|
|
342
377
|
|
|
343
|
-
// ── 完成标记检测 ──
|
|
378
|
+
// ── 完成标记检测 (chat.baidu.com) ──
|
|
344
379
|
const detected = await this.page.evaluate(() => {
|
|
345
380
|
const selectors = [
|
|
381
|
+
'.cos-icon.cos-icon-copy',
|
|
382
|
+
'.cos-icon-copy',
|
|
383
|
+
'.feedback-hover-show',
|
|
384
|
+
'[class*="feedback-wrapper"]',
|
|
385
|
+
'.cos-icon.cos-icon-share1',
|
|
386
|
+
'.cos-icon.cos-icon-feedback',
|
|
387
|
+
// 旧版兼容
|
|
346
388
|
'.dialogCardBottom__qoXjps3z',
|
|
347
389
|
'[class*="dialogCardBottom"]',
|
|
348
390
|
'[class*="dialog-bottom"]',
|
|
349
|
-
'[class*="response-footer"]',
|
|
350
391
|
];
|
|
351
392
|
for (const sel of selectors) {
|
|
352
393
|
const el = document.querySelector(sel);
|
|
@@ -400,16 +441,16 @@ class YiyanBrowser {
|
|
|
400
441
|
async _getMessageCount() {
|
|
401
442
|
return await this.page.evaluate(() => {
|
|
402
443
|
const candidates = [
|
|
403
|
-
'
|
|
404
|
-
'
|
|
405
|
-
'
|
|
406
|
-
'
|
|
407
|
-
'
|
|
408
|
-
'[
|
|
409
|
-
'[class*="
|
|
410
|
-
'.
|
|
411
|
-
'
|
|
412
|
-
'[class*="
|
|
444
|
+
'.ai-entry-block.ai-markdown',
|
|
445
|
+
'.answer-box',
|
|
446
|
+
'.cs-answer-container',
|
|
447
|
+
'.answer-container',
|
|
448
|
+
'.chat-search-answer-generate-item',
|
|
449
|
+
'[class*="answer-container"]',
|
|
450
|
+
'[class*="answer-box"]',
|
|
451
|
+
'.ai-markdown',
|
|
452
|
+
'.cs-question-bubble',
|
|
453
|
+
'[class*="question-bubble"]',
|
|
413
454
|
];
|
|
414
455
|
for (const sel of candidates) {
|
|
415
456
|
const els = document.querySelectorAll(sel);
|
|
@@ -435,10 +476,15 @@ class YiyanBrowser {
|
|
|
435
476
|
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
436
477
|
const tag = node.tagName.toLowerCase();
|
|
437
478
|
|
|
438
|
-
//
|
|
439
|
-
const cls = node.className
|
|
440
|
-
if (cls.includes('
|
|
441
|
-
|
|
479
|
+
// 排除思考过程区域和 UI 建议区域
|
|
480
|
+
const cls = (typeof node.className === 'string') ? node.className : '';
|
|
481
|
+
if (cls.includes('thinking') || cls.includes('Thinking') ||
|
|
482
|
+
cls.includes('deep-search') || cls.includes('deep_think') ||
|
|
483
|
+
cls.includes('suggestion') || cls.includes('follow-up') ||
|
|
484
|
+
cls.includes('question-container') || cls.includes('question-bubble') ||
|
|
485
|
+
cls.includes('quick-entrance') || cls.includes('feedback-wrapper') ||
|
|
486
|
+
cls.includes('hover-menu') || cls.includes('action-bar')) {
|
|
487
|
+
return; // 跳过思考区域和 UI 元素
|
|
442
488
|
}
|
|
443
489
|
|
|
444
490
|
if (tag === 'pre') {
|
|
@@ -474,62 +520,88 @@ class YiyanBrowser {
|
|
|
474
520
|
return result.trim();
|
|
475
521
|
}
|
|
476
522
|
|
|
477
|
-
// ──
|
|
523
|
+
// ── chat.baidu.com 新选择器 ──
|
|
524
|
+
|
|
525
|
+
// 优先: 获取最后一个回答块的内容
|
|
526
|
+
const answerBlocks = document.querySelectorAll(
|
|
527
|
+
'.ai-entry-block.ai-markdown, .answer-container.cs-enable-selection, .cs-answer-container'
|
|
528
|
+
);
|
|
529
|
+
if (answerBlocks.length > 0) {
|
|
530
|
+
const lastBlock = answerBlocks[answerBlocks.length - 1];
|
|
531
|
+
const text = getFullText(lastBlock);
|
|
532
|
+
if (text.length > 0) return text;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// 回退 1: 通过 answer-box 获取
|
|
536
|
+
const answerBoxes = document.querySelectorAll('.answer-box, .last-answer-box');
|
|
537
|
+
if (answerBoxes.length > 0) {
|
|
538
|
+
const lastBox = answerBoxes[answerBoxes.length - 1];
|
|
539
|
+
const text = getFullText(lastBox);
|
|
540
|
+
if (text.length > 0) return text;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// 回退 2: 旧版 #answer_text_id (兼容)
|
|
478
544
|
const answerEl = document.querySelector('#answer_text_id');
|
|
479
545
|
if (answerEl) {
|
|
480
546
|
return getFullText(answerEl);
|
|
481
547
|
}
|
|
482
548
|
|
|
483
|
-
//
|
|
549
|
+
// 回退 3: 通用 markdown 容器
|
|
550
|
+
const markdowns = document.querySelectorAll('.ai-markdown, [class*="markdown-content"]');
|
|
551
|
+
if (markdowns.length > 0) {
|
|
552
|
+
const lastMd = markdowns[markdowns.length - 1];
|
|
553
|
+
const text = getFullText(lastMd);
|
|
554
|
+
if (text.length > 0) return text;
|
|
555
|
+
}
|
|
556
|
+
|
|
484
557
|
return '';
|
|
485
558
|
});
|
|
486
559
|
}
|
|
487
560
|
|
|
488
561
|
async _isGenerating() {
|
|
489
562
|
return await this.page.evaluate(() => {
|
|
490
|
-
// ── 1.
|
|
491
|
-
const
|
|
492
|
-
'
|
|
493
|
-
'
|
|
494
|
-
'[class*="
|
|
495
|
-
'[class*="stopGen"]',
|
|
563
|
+
// ── 1. 检测 typing/generating 指示器 (chat.baidu.com) ──
|
|
564
|
+
const typingSelectors = [
|
|
565
|
+
'.cosd-markdown-content-typingall',
|
|
566
|
+
'.markdown-typing-all',
|
|
567
|
+
'[class*="typing"]',
|
|
496
568
|
'[class*="generating"]',
|
|
497
|
-
'[class*="
|
|
569
|
+
'[class*="loading-indicator"]',
|
|
570
|
+
'svg[class*="loading"]',
|
|
571
|
+
'svg[class*="spinner"]',
|
|
572
|
+
'[class*="blink"]',
|
|
573
|
+
'[class*="cursor-blink"]',
|
|
574
|
+
'[class*="pulsing"]',
|
|
498
575
|
];
|
|
499
|
-
for (const sel of
|
|
576
|
+
for (const sel of typingSelectors) {
|
|
500
577
|
const el = document.querySelector(sel);
|
|
501
578
|
if (el) {
|
|
502
579
|
const s = window.getComputedStyle(el);
|
|
503
|
-
if (s.display !== 'none' && s.visibility !== 'hidden'
|
|
580
|
+
if (s.display !== 'none' && s.visibility !== 'hidden') return true;
|
|
504
581
|
}
|
|
505
582
|
}
|
|
506
583
|
|
|
507
|
-
// ── 2.
|
|
508
|
-
const
|
|
509
|
-
'[
|
|
510
|
-
'[
|
|
511
|
-
'[class*="
|
|
512
|
-
'[class*="
|
|
513
|
-
'[class*="cursor"]',
|
|
514
|
-
'[class*="pulsing"]',
|
|
515
|
-
'[class*="thinking"]',
|
|
516
|
-
'svg[class*="loading"]',
|
|
517
|
-
'svg[class*="spinner"]',
|
|
518
|
-
'.loading-indicator',
|
|
519
|
-
'.generating-indicator',
|
|
584
|
+
// ── 2. 检测停止按钮 ──
|
|
585
|
+
const stopSelectors = [
|
|
586
|
+
'button[aria-label*="Stop" i]',
|
|
587
|
+
'button[aria-label*="停止"]',
|
|
588
|
+
'[class*="stop-gen"]',
|
|
589
|
+
'[class*="stopGen"]',
|
|
520
590
|
];
|
|
521
|
-
for (const sel of
|
|
591
|
+
for (const sel of stopSelectors) {
|
|
522
592
|
const el = document.querySelector(sel);
|
|
523
593
|
if (el) {
|
|
524
594
|
const s = window.getComputedStyle(el);
|
|
525
|
-
if (s.display !== 'none' && s.visibility !== 'hidden') return true;
|
|
595
|
+
if (s.display !== 'none' && s.visibility !== 'hidden' && s.opacity !== '0') return true;
|
|
526
596
|
}
|
|
527
597
|
}
|
|
528
598
|
|
|
529
|
-
// ── 3.
|
|
599
|
+
// ── 3. 检测完成标记 — 如果有回答但没有完成标记 → 还在生成 ──
|
|
530
600
|
const completionMarkers = [
|
|
531
|
-
'
|
|
532
|
-
'.
|
|
601
|
+
'.cos-icon.cos-icon-copy',
|
|
602
|
+
'.cos-icon-copy',
|
|
603
|
+
'.cos-icon.cos-icon-share1',
|
|
604
|
+
'.cos-icon-feedback',
|
|
533
605
|
'[class*="copy-btn"]',
|
|
534
606
|
'[class*="copyBtn"]',
|
|
535
607
|
'[aria-label*="Copy" i]',
|
|
@@ -537,6 +609,7 @@ class YiyanBrowser {
|
|
|
537
609
|
'[class*="regenerate"]',
|
|
538
610
|
'[class*="retry"]',
|
|
539
611
|
'[class*="action-btn"]',
|
|
612
|
+
'.feedback-hover-show',
|
|
540
613
|
];
|
|
541
614
|
let hasCompletionMarker = false;
|
|
542
615
|
for (const sel of completionMarkers) {
|
|
@@ -547,7 +620,9 @@ class YiyanBrowser {
|
|
|
547
620
|
}
|
|
548
621
|
|
|
549
622
|
// 如果有响应内容但没有完成标记 → 还在生成
|
|
550
|
-
const answerArea = document.querySelector(
|
|
623
|
+
const answerArea = document.querySelector(
|
|
624
|
+
'.ai-entry-block.ai-markdown, .answer-container, .cs-answer-container, .answer-box'
|
|
625
|
+
);
|
|
551
626
|
if (answerArea && answerArea.innerText && answerArea.innerText.length > 5) {
|
|
552
627
|
if (!hasCompletionMarker) {
|
|
553
628
|
return true;
|
|
@@ -568,29 +643,34 @@ class YiyanBrowser {
|
|
|
568
643
|
'正在思考中',
|
|
569
644
|
'正在思考',
|
|
570
645
|
'思考过程',
|
|
571
|
-
'
|
|
572
|
-
'我需要',
|
|
646
|
+
'深度思考',
|
|
573
647
|
'根据搜索结果',
|
|
574
648
|
'参考',
|
|
575
|
-
'picaole需要',
|
|
576
649
|
];
|
|
577
650
|
|
|
578
|
-
// Remove lines that
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
651
|
+
// Remove lines that are standalone thinking/UI markers
|
|
652
|
+
const lines = text.split('\n');
|
|
653
|
+
text = lines.filter(line => {
|
|
654
|
+
const trimmed = line.trim();
|
|
655
|
+
// 移除纯 UI 标记行
|
|
656
|
+
if (thinkingMarkers.some(m => trimmed === m)) return false;
|
|
657
|
+
// 移除建议追问行(新 UI 的 follow-up 按钮)
|
|
658
|
+
if (/^(能否|能再|能帮|可以|请用|用一句|用一段)/.test(trimmed) && trimmed.length < 50) return false;
|
|
659
|
+
return true;
|
|
660
|
+
}).join('\n');
|
|
661
|
+
|
|
662
|
+
// Remove everything before "准备输出结果" (thinking process end marker)
|
|
586
663
|
const outputMarker = '准备输出结果';
|
|
587
664
|
const markerIndex = text.indexOf(outputMarker);
|
|
588
665
|
if (markerIndex !== -1) {
|
|
589
666
|
text = text.slice(markerIndex + outputMarker.length).trim();
|
|
590
667
|
}
|
|
591
668
|
|
|
592
|
-
// Remove everything after regenerate/suggestion markers (
|
|
593
|
-
const cutMarkers = [
|
|
669
|
+
// Remove everything after regenerate/suggestion markers (UI elements)
|
|
670
|
+
const cutMarkers = [
|
|
671
|
+
'重新生成', '重新生成的', '换个回答', '输出更详细的', '再多提供',
|
|
672
|
+
'内容由AI生成', '查看使用规则',
|
|
673
|
+
];
|
|
594
674
|
for (const marker of cutMarkers) {
|
|
595
675
|
const cutIndex = text.indexOf(marker);
|
|
596
676
|
if (cutIndex !== -1) {
|
package/src/config.js
CHANGED
|
@@ -8,7 +8,7 @@ const os = require('os');
|
|
|
8
8
|
// ─────────────────────────────────────────────
|
|
9
9
|
const defaults = {
|
|
10
10
|
// Browser
|
|
11
|
-
YIYAN_URL : 'https://
|
|
11
|
+
YIYAN_URL : 'https://chat.baidu.com/',
|
|
12
12
|
SESSION_DIR : path.join(os.homedir(), '.yiyan-agent', 'session'),
|
|
13
13
|
HEADLESS : true, // Performance: 无头模式减少渲染开销
|
|
14
14
|
|
package/src/logger.js
CHANGED
|
@@ -44,7 +44,7 @@ const logger = {
|
|
|
44
44
|
${c('cyan','╔══════════════════════════════════════════════════╗')}
|
|
45
45
|
${c('cyan','║')} ${cb('lcyan','🤖 Yiyan Browser Agent (文心一言)')} ${c('cyan','║')}
|
|
46
46
|
${c('cyan','║')} ${c('gray','AI Coding Agent via Browser Automation')} ${c('cyan','║')}
|
|
47
|
-
${c('cyan','║')} ${c('gray','No API key needed — uses
|
|
47
|
+
${c('cyan','║')} ${c('gray','No API key needed — uses chat.baidu.com')} ${c('cyan','║')}
|
|
48
48
|
${c('cyan','╚══════════════════════════════════════════════════╝')}
|
|
49
49
|
`);
|
|
50
50
|
},
|