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 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://yiyan.baidu.com), giving you a Claude Code / Cursor-style coding agent powered by Baidu's AI models at zero cost.
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 `yiyan.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.
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 yiyan.baidu.com
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 yiyan.baidu.com
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 yiyan.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.
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.10.2",
4
- "description": "AI coding agent powered by Yiyan (文心一言) via browser automation — no API key needed. Performance-optimized (30-40% faster). New --headless parameter for interactive mode. Enhanced with comprehensive security.",
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 (yiyan.baidu.com)
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
- '.editable__T7WAW4uW',
18
- '[role="textbox"]',
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
- return (
183
- url.includes('/auth') ||
184
- url.includes('/login') ||
185
- url.includes('/sign') ||
186
- bodyText.includes('Sign in') ||
187
- bodyText.includes('Log in') ||
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
- // Focus and select all existing content
241
- await el.click({ clickCount: 3, force: true });
242
- await this.page.waitForTimeout(100);
243
-
244
- // Clear by pressing Delete
245
- await this.page.keyboard.press('Delete');
246
- await this.page.waitForTimeout(50);
247
-
248
- // Type text character by character (stable, works reliably)
249
- await this.page.keyboard.type(text, { delay: 10 });
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
- const thinkingEl = document.querySelector('.container__SPpahQHm, [class*="container__SPpah"]');
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
- const hasContent = thinkingInfo.text.length > 5 && text.length > 0;
327
-
328
- if (thinkingStable && answerStable && hasContent) {
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(`Both stable: ${stableCount}/2 (thinking: ${thinkingInfo.text.length} chars, answer: ${text.length} chars)`);
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
- '[class*="answer"]',
404
- '[class*="response"]',
405
- '[class*="content"]',
406
- '[class*="markdown"]',
407
- '[class*="assistant"][class*="message"]',
408
- '[data-role="assistant"]',
409
- '[class*="markdown-content"]',
410
- '.ds-markdown',
411
- '[class*="chat-message"]',
412
- '[class*="message-bubble"]',
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('container__SPpahQHm') || cls.includes('thinking') || cls.includes('Thinking')) {
441
- return; // 跳过思考区域
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
- // ── answer_text_id 提取内容 ──
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
- // answer_text_id 不存在,返回空字符串
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. 检测停止按钮/生成状态 UI ──
491
- const stopSelectors = [
492
- 'button[aria-label*="Stop" i]',
493
- 'button[aria-label*="停止"]',
494
- '[class*="stop-gen"]',
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*=" Generating"]',
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 stopSelectors) {
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' && s.opacity !== '0') return true;
580
+ if (s.display !== 'none' && s.visibility !== 'hidden') return true;
504
581
  }
505
582
  }
506
583
 
507
- // ── 2. 检测加载动画/typing指示器 ──
508
- const loaderSelectors = [
509
- '[class*="typing"]',
510
- '[class*="loading"]',
511
- '[class*="spinner"]',
512
- '[class*="blink"]',
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 loaderSelectors) {
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
- '[class*="dialogCardBottom"]',
532
- '.dialogCardBottom__qoXjps3z',
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('[class*="answer"], [class*="response"], [class*="markdown"]');
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 contain thinking markers
579
- for (const marker of thinkingMarkers) {
580
- // 如果整行包含思考标记,移除该行
581
- const lines = text.split('\n');
582
- text = lines.filter(line => !line.includes(marker)).join('\n');
583
- }
584
-
585
- // Remove everything before "准备输出结果" (Yiyan's thinking process end marker)
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 (Yiyan's UI elements)
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://yiyan.baidu.com/',
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 yiyan.baidu.com')} ${c('cyan','║')}
47
+ ${c('cyan','║')} ${c('gray','No API key needed — uses chat.baidu.com')} ${c('cyan','║')}
48
48
  ${c('cyan','╚══════════════════════════════════════════════════╝')}
49
49
  `);
50
50
  },