deepspider 0.2.6 → 0.2.7

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.
@@ -0,0 +1,73 @@
1
+ # CI/CD Guidelines
2
+
3
+ > GitHub Actions 自动发布规范
4
+
5
+ ---
6
+
7
+ ## npm 自动发布
8
+
9
+ ### 触发条件
10
+
11
+ 推送 `v*` 标签时自动触发发布流程。
12
+
13
+ ```bash
14
+ npm version patch && git push && git push --tags
15
+ ```
16
+
17
+ ### Workflow 配置要点
18
+
19
+ #### 1. 原生模块处理
20
+
21
+ 项目包含 `isolated-vm` 等原生模块,在 CI 环境编译可能失败。
22
+
23
+ **解决方案**:使用 `--ignore-scripts` 跳过编译
24
+
25
+ ```yaml
26
+ - run: pnpm install --no-frozen-lockfile --ignore-scripts
27
+ ```
28
+
29
+ #### 2. NPM_TOKEN 认证
30
+
31
+ **正确配置**:
32
+
33
+ 1. 在 npmjs.com 生成 **Automation** 类型的 token
34
+ 2. 添加为 GitHub **Repository secret**(不是 Environment secret)
35
+ 3. 使用 `NODE_AUTH_TOKEN` 环境变量
36
+
37
+ ```yaml
38
+ - uses: actions/setup-node@v4
39
+ with:
40
+ node-version: '20'
41
+ registry-url: 'https://registry.npmjs.org'
42
+
43
+ - run: npm publish
44
+ env:
45
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
46
+ ```
47
+
48
+ ---
49
+
50
+ ## 常见问题
51
+
52
+ ### pnpm lockfile 不匹配
53
+
54
+ **错误**:`ERR_PNPM_LOCKFILE_CONFIG_MISMATCH`
55
+
56
+ **解决**:添加 `--no-frozen-lockfile`
57
+
58
+ ### isolated-vm 编译失败
59
+
60
+ **错误**:`v8::SourceLocation does not name a type`
61
+
62
+ **原因**:isolated-vm 与新版 Node.js V8 API 不兼容
63
+
64
+ **解决**:添加 `--ignore-scripts` 跳过原生模块编译
65
+
66
+ ### NPM_TOKEN 认证失败
67
+
68
+ **错误**:`ENEEDAUTH` 或 `Access token expired`
69
+
70
+ **检查**:
71
+ 1. Token 类型是否为 Automation
72
+ 2. Secret 是否为 Repository secret
73
+ 3. 使用 `NODE_AUTH_TOKEN` 而非直接写入 `.npmrc`
@@ -22,6 +22,7 @@ DeepSpider 是基于 DeepAgents + Patchright 的智能爬虫 Agent。
22
22
  | [State Management](./state-management.md) | Agent 状态与数据存储 | Done |
23
23
  | [Quality Guidelines](./quality-guidelines.md) | 代码质量规范 | Done |
24
24
  | [Type Safety](./type-safety.md) | Zod 类型验证规范 | Done |
25
+ | [CI/CD Guidelines](./ci-cd-guidelines.md) | GitHub Actions 自动发布规范 | Done |
25
26
 
26
27
  ---
27
28
 
@@ -113,6 +113,42 @@ proc.on('close', () => {
113
113
 
114
114
  **原因**: `spawn` 的 options 不包含 `timeout`,这是 `execSync` 的选项。使用 spawn 时必须手动实现超时逻辑。
115
115
 
116
+ ### 6. 用正则替换 HTML 字符串
117
+
118
+ ```javascript
119
+ // ❌ 禁止:正则替换 HTML 字符串会破坏结构
120
+ function linkifyPaths(html) {
121
+ return html.replace(/(\/[\w.\-\/]+)/g, '<a href="$1">$1</a>');
122
+ }
123
+ // 会把 </strong> 中的 /strong 也匹配成路径!
124
+
125
+ // ✅ 使用 DOM TreeWalker 遍历文本节点
126
+ function linkifyPaths(container) {
127
+ const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT);
128
+ const textNodes = [];
129
+ while (walker.nextNode()) textNodes.push(walker.currentNode);
130
+
131
+ textNodes.forEach(node => {
132
+ // 只处理纯文本,不会影响 HTML 标签
133
+ });
134
+ }
135
+ ```
136
+
137
+ **原因**: 正则无法区分 HTML 标签和文本内容,容易误匹配导致结构破坏。
138
+
139
+ ### 7. LLM 工具参数传递大段代码
140
+
141
+ ```javascript
142
+ // ❌ 禁止:直接传递大段代码内容,可能被 LLM 截断
143
+ await saveReport({ pythonCode: longCodeString });
144
+
145
+ // ✅ 先保存到文件,再传递文件路径
146
+ await artifactSave({ path: 'domain/decrypt.py', content: code });
147
+ await saveReport({ pythonCodeFile: 'domain/decrypt.py' });
148
+ ```
149
+
150
+ **原因**: LLM 输出有长度限制,大段代码作为参数传递时可能被截断。分步保存确保代码完整性。
151
+
116
152
  ---
117
153
 
118
154
  ## Required Patterns
@@ -8,7 +8,7 @@
8
8
 
9
9
  <!-- @@@auto:current-status -->
10
10
  - **Active File**: `journal-1.md`
11
- - **Total Sessions**: 1
11
+ - **Total Sessions**: 2
12
12
  - **Last Active**: 2026-02-03
13
13
  <!-- @@@/auto:current-status -->
14
14
 
@@ -19,7 +19,7 @@
19
19
  <!-- @@@auto:active-documents -->
20
20
  | File | Lines | Status |
21
21
  |------|-------|--------|
22
- | `journal-1.md` | ~61 | Active |
22
+ | `journal-1.md` | ~125 | Active |
23
23
  <!-- @@@/auto:active-documents -->
24
24
 
25
25
  ---
@@ -29,6 +29,7 @@
29
29
  <!-- @@@auto:session-history -->
30
30
  | # | Date | Title | Commits |
31
31
  |---|------|-------|---------|
32
+ | 2 | 2026-02-03 | GitHub Actions 自动发布 npm | `4ff9a25`, `debdc4e`, `ab56fe2`, `67f9c55`, `b13b03d`, `63a6304`, `327ca39`, `78de837`, `46ce73e` |
32
33
  | 1 | 2026-02-03 | 环境变量重命名与配置检测 | `4aa6cad` |
33
34
  <!-- @@@/auto:session-history -->
34
35
 
@@ -59,3 +59,67 @@
59
59
  ### Next Steps
60
60
 
61
61
  - None - task complete
62
+
63
+ ## Session 2: GitHub Actions 自动发布 npm
64
+
65
+ **Date**: 2026-02-03
66
+ **Task**: GitHub Actions 自动发布 npm
67
+
68
+ ### Summary
69
+
70
+ (Add summary)
71
+
72
+ ### Main Changes
73
+
74
+ ## 完成内容
75
+
76
+ 实现 GitHub Actions 自动发布到 npm。
77
+
78
+ | 变更 | 说明 |
79
+ |------|------|
80
+ | GitHub Actions | 添加 .github/workflows/publish.yml |
81
+ | 触发条件 | 推送 v* 标签时自动发布 |
82
+ | CI 流程 | lint → publish |
83
+ | Node.js | 使用 v20 + --ignore-scripts 跳过原生模块编译 |
84
+
85
+ ## 遇到的问题与解决
86
+
87
+ 1. **pnpm lockfile 不匹配** → 添加 --no-frozen-lockfile
88
+ 2. **isolated-vm 编译失败** → 添加 --ignore-scripts
89
+ 3. **NPM_TOKEN 认证失败** → 使用 NODE_AUTH_TOKEN 环境变量
90
+
91
+ ## 发布流程
92
+
93
+ ```bash
94
+ npm version patch && git push && git push --tags
95
+ ```
96
+
97
+ ## 变更文件
98
+
99
+ - `.github/workflows/publish.yml` - GitHub Actions 配置
100
+
101
+ ### Git Commits
102
+
103
+ | Hash | Message |
104
+ |------|---------|
105
+ | `4ff9a25` | (see git log) |
106
+ | `debdc4e` | (see git log) |
107
+ | `ab56fe2` | (see git log) |
108
+ | `67f9c55` | (see git log) |
109
+ | `b13b03d` | (see git log) |
110
+ | `63a6304` | (see git log) |
111
+ | `327ca39` | (see git log) |
112
+ | `78de837` | (see git log) |
113
+ | `46ce73e` | (see git log) |
114
+
115
+ ### Testing
116
+
117
+ - [OK] (Add test results)
118
+
119
+ ### Status
120
+
121
+ [OK] **Completed**
122
+
123
+ ### Next Steps
124
+
125
+ - None - task complete
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepspider",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "智能爬虫工程平台 - 基于 DeepAgents + Patchright 的 AI 爬虫 Agent",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -127,19 +127,36 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
127
127
 
128
128
  **禁止**:未经验证就直接保存报告或输出代码
129
129
 
130
- ### 输出与保存(验证通过后才能执行)
131
- - \`save_analysis_report\` - 保存分析报告,生成 Markdown、HTML 和代码文件
130
+ ### 输出与保存(分步保存,避免代码截断)
131
+
132
+ **推荐流程**(分步保存):
133
+ 1. 先用 \`artifact_save\` 保存 Python 代码到文件(如 \`{domain}/decrypt.py\`)
134
+ 2. 再调用 \`save_analysis_report\`,传入 \`pythonCodeFile\` 文件路径
135
+ 3. **必须在最终输出中告知用户文件保存路径**
136
+
137
+ **为什么要分步保存**:
138
+ - 直接传代码内容可能被 LLM 截断
139
+ - 分步保存确保代码完整性
132
140
 
133
141
  **调用 save_analysis_report 的前提条件**(必须全部满足):
134
142
  1. 已使用 \`execute_python\` 或 \`verify_with_python\` 验证代码能正确运行
135
- 2. 验证结果与预期一致(能解密出目标数据,或能生成正确的签名)
136
- 3. 如果验证失败,必须先修复代码再次验证,直到成功
143
+ 2. 验证结果与预期一致
144
+ 3. 已用 \`artifact_save\` 保存代码文件
137
145
 
138
146
  **参数要求**:
139
147
  - domain: 网站域名
140
- - markdown: 简洁的分析摘要(不要太长)
141
- - pythonCode: **经过验证的、完整可运行的 Python 代码**(必须)
142
- - jsCode: JavaScript 代码(可选)
148
+ - markdown: 简洁的分析摘要
149
+ - pythonCodeFile: Python 代码文件路径(推荐)
150
+ - pythonCode: Python 代码内容(不推荐,可能被截断)
151
+
152
+ **完成后必须输出文件路径**:
153
+ 分析完成后,必须明确告知用户生成的文件路径,格式如:
154
+ \`\`\`
155
+ 📁 生成的文件:
156
+ - Python 代码: ~/.deepspider/output/{domain}/decrypt.py
157
+ - 分析报告: ~/.deepspider/output/{domain}/report.html
158
+ \`\`\`
159
+ 用户可以点击路径直接打开文件。
143
160
 
144
161
  ## 输出要求
145
162
 
@@ -293,11 +310,15 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
293
310
  2. ✅ 分析加密/解密算法
294
311
  3. ✅ 生成可运行的代码
295
312
  4. ✅ **端到端验证:发送请求能获取到目标数据**
313
+ 5. ✅ **保存报告:调用 save_analysis_report 保存分析结果**
314
+
315
+ **第5步是强制的**:验证成功后必须调用 \`save_analysis_report\`,否则用户无法查看报告和代码文件。
296
316
 
297
317
  **以下情况不算完成**:
298
318
  - ❌ 只验证了加密算法正确,但请求返回错误
299
319
  - ❌ 请求返回"参数错误"、"签名无效"、"数据标识不符合要求"等
300
320
  - ❌ 没有实际获取到用户要求的目标数据
321
+ - ❌ **验证成功但没有调用 save_analysis_report**
301
322
 
302
323
  ### 遇到问题时的正确做法
303
324
  如果端到端验证失败:
package/src/agent/run.js CHANGED
@@ -517,6 +517,25 @@ ${JSON.stringify(config.fields, null, 2)}
517
517
  请先用 query_store 查询已有的加密代码,然后整合生成配置和脚本。`;
518
518
  } else if (data.type === 'chat') {
519
519
  userPrompt = `${browserReadyPrefix}${data.text}`;
520
+ } else if (data.type === 'open-file') {
521
+ // 打开文件 - 使用系统默认程序
522
+ let filePath = data.path;
523
+ if (filePath && typeof filePath === 'string') {
524
+ // 展开 ~ 为 home 目录
525
+ if (filePath.startsWith('~/')) {
526
+ filePath = filePath.replace('~', process.env.HOME || process.env.USERPROFILE);
527
+ }
528
+ const { exec } = await import('child_process');
529
+ const platform = process.platform;
530
+ const cmd = platform === 'darwin' ? `open "${filePath}"` :
531
+ platform === 'win32' ? `start "" "${filePath}"` :
532
+ `xdg-open "${filePath}"`;
533
+ exec(cmd, (err) => {
534
+ if (err) console.error('[open-file] 打开失败:', err.message);
535
+ else console.log('[open-file] 已打开:', filePath);
536
+ });
537
+ }
538
+ return;
520
539
  } else {
521
540
  return;
522
541
  }
@@ -91,3 +91,123 @@ description: |
91
91
  2. 解码字符串
92
92
  3. 简化控制流
93
93
  4. 重命名变量
94
+
95
+ ## 密文特征识别(重要)
96
+
97
+ **根据密文数据的格式特征,可以直接推断加密方式,无需分析代码。**
98
+
99
+ ### Base64 编码
100
+
101
+ | 特征 | 说明 |
102
+ |------|------|
103
+ | 字符集 | `A-Za-z0-9+/=` |
104
+ | 结尾 | 可能有 `=` 或 `==` 填充 |
105
+ | 长度 | 4的倍数 |
106
+
107
+ ```
108
+ 示例: SGVsbG8gV29ybGQ=
109
+ URL安全变体: SGVsbG8gV29ybGQ (用 - _ 替代 + /)
110
+ ```
111
+
112
+ ### Hex 编码
113
+
114
+ | 特征 | 说明 |
115
+ |------|------|
116
+ | 字符集 | `0-9a-fA-F` |
117
+ | 长度 | 偶数位 |
118
+
119
+ ```
120
+ 示例: 48656c6c6f20576f726c64
121
+ ```
122
+
123
+ ### 哈希值特征
124
+
125
+ | 算法 | 长度(hex) | 长度(bytes) | 示例 |
126
+ |------|-----------|-------------|------|
127
+ | MD5 | 32 | 16 | `d41d8cd98f00b204e9800998ecf8427e` |
128
+ | SHA1 | 40 | 20 | `da39a3ee5e6b4b0d3255bfef95601890afd80709` |
129
+ | SHA256 | 64 | 32 | `e3b0c44298fc1c149afbf4c8996fb924...` |
130
+ | SM3 | 64 | 32 | 与SHA256同长度 |
131
+
132
+ **判断技巧**: 固定长度 + 纯hex字符 = 大概率是哈希
133
+
134
+ ### AES 密文特征
135
+
136
+ | 模式 | 特征 |
137
+ |------|------|
138
+ | ECB | 长度是16的倍数,相同明文产生相同密文 |
139
+ | CBC | 长度是16的倍数,通常前16字节是IV |
140
+ | GCM | 末尾有16字节认证标签 |
141
+
142
+ ```
143
+ Base64编码的AES密文: 长度是4的倍数,解码后是16的倍数
144
+ 常见格式: IV(16B) + Ciphertext + Tag(GCM)
145
+ ```
146
+
147
+ ### RSA 密文特征
148
+
149
+ | 密钥长度 | 密文长度(bytes) | Base64长度 |
150
+ |----------|-----------------|------------|
151
+ | 1024位 | 128 | ~172 |
152
+ | 2048位 | 256 | ~344 |
153
+ | 4096位 | 512 | ~684 |
154
+
155
+ **判断技巧**: 固定长度 + 与RSA密钥长度匹配 = RSA加密
156
+
157
+ ### 国密 SM2 密文特征
158
+
159
+ ```
160
+ C1C3C2 格式 (推荐):
161
+ - C1: 65字节 (04开头的未压缩公钥点)
162
+ - C3: 32字节 (SM3哈希)
163
+ - C2: 与明文等长
164
+
165
+ C1C2C3 格式 (旧):
166
+ - C1: 65字节
167
+ - C2: 与明文等长
168
+ - C3: 32字节
169
+
170
+ 总长度: 97 + 明文长度
171
+ ```
172
+
173
+ **判断技巧**: 以 `04` 开头的hex + 长度约97+N = SM2
174
+
175
+ ### JWT Token 特征
176
+
177
+ ```
178
+ 格式: header.payload.signature
179
+ 示例: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
180
+
181
+ 特征:
182
+ - 三段用 . 分隔
183
+ - 每段都是 Base64URL 编码
184
+ - 第一段解码后是 JSON,包含 alg 字段
185
+ ```
186
+
187
+ ### 常见组合模式
188
+
189
+ | 场景 | 典型组合 |
190
+ |------|----------|
191
+ | 接口签名 | `MD5(params + timestamp + secret)` 或 `HMAC-SHA256` |
192
+ | 密码传输 | `RSA(password)` 或 `AES(password)` |
193
+ | 数据加密 | `AES-CBC(data)` + `RSA(aes_key)` |
194
+ | Token | `JWT` 或 `AES(user_id + timestamp)` |
195
+
196
+ ### 快速判断流程
197
+
198
+ ```
199
+ 1. 看字符集
200
+ - 纯hex → 可能是哈希或hex编码的密文
201
+ - Base64字符 → 解码后再分析
202
+ - 有 . 分隔 → 可能是JWT
203
+
204
+ 2. 看长度
205
+ - 32/40/64 hex → 哈希 (MD5/SHA1/SHA256)
206
+ - 128/256/512 bytes → RSA
207
+ - 16的倍数 → AES
208
+
209
+ 3. 看格式
210
+ - 04开头 → SM2 或 ECDSA
211
+ - ey开头 → JWT
212
+ - -----BEGIN → PEM格式密钥
213
+ ```
@@ -6,9 +6,9 @@
6
6
 
7
7
  import { z } from 'zod';
8
8
  import { tool } from '@langchain/core/tools';
9
- import { writeFileSync } from 'fs';
10
- import { join } from 'path';
11
- import { PATHS, ensureDir, getReportDir } from '../../config/paths.js';
9
+ import { writeFileSync, readFileSync, existsSync } from 'fs';
10
+ import { join, basename } from 'path';
11
+ import { PATHS, ensureDir, DEEPSPIDER_HOME } from '../../config/paths.js';
12
12
 
13
13
  const OUTPUT_DIR = PATHS.REPORTS_DIR;
14
14
 
@@ -25,6 +25,31 @@ function escapeHtml(str) {
25
25
  return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
26
26
  }
27
27
 
28
+ /**
29
+ * 从文件路径读取代码内容
30
+ * 支持相对路径和绝对路径
31
+ */
32
+ function readCodeFromFile(filePath) {
33
+ if (!filePath) return null;
34
+
35
+ let fullPath = filePath;
36
+ if (!fullPath.startsWith('/')) {
37
+ fullPath = join(PATHS.OUTPUT_DIR, filePath);
38
+ }
39
+
40
+ if (!existsSync(fullPath)) {
41
+ console.warn('[report] 代码文件不存在:', fullPath);
42
+ return null;
43
+ }
44
+
45
+ try {
46
+ return readFileSync(fullPath, 'utf-8');
47
+ } catch (e) {
48
+ console.warn('[report] 读取代码文件失败:', e.message);
49
+ return null;
50
+ }
51
+ }
52
+
28
53
  /**
29
54
  * 生成 HTML 报告页面
30
55
  */
@@ -72,34 +97,57 @@ function generateHtmlPage(title, markdown, pythonCode, jsCode) {
72
97
 
73
98
  /**
74
99
  * 保存分析报告
100
+ * 支持两种模式:
101
+ * 1. 传入代码文件路径(推荐)- pythonCodeFile/jsCodeFile
102
+ * 2. 传入代码内容(兼容)- pythonCode/jsCode
75
103
  */
76
104
  export const saveAnalysisReport = tool(
77
- async ({ domain, title, markdown, pythonCode, jsCode }) => {
105
+ async ({ domain, title, markdown, pythonCode, pythonCodeFile, jsCode, jsCodeFile }) => {
78
106
  try {
79
107
  const domainDir = join(OUTPUT_DIR, extractDomain(domain));
80
108
  ensureDir(domainDir);
81
109
 
82
110
  const paths = {};
83
111
 
112
+ // 优先从文件读取代码
113
+ let finalPythonCode = pythonCode;
114
+ let finalJsCode = jsCode;
115
+
116
+ if (pythonCodeFile) {
117
+ const code = readCodeFromFile(pythonCodeFile);
118
+ if (code) {
119
+ finalPythonCode = code;
120
+ console.log('[report] 从文件读取 Python 代码:', pythonCodeFile);
121
+ }
122
+ }
123
+
124
+ if (jsCodeFile) {
125
+ const code = readCodeFromFile(jsCodeFile);
126
+ if (code) {
127
+ finalJsCode = code;
128
+ console.log('[report] 从文件读取 JS 代码:', jsCodeFile);
129
+ }
130
+ }
131
+
84
132
  // 保存 Markdown
85
133
  paths.markdown = join(domainDir, 'analysis.md');
86
134
  writeFileSync(paths.markdown, markdown, 'utf-8');
87
135
 
88
136
  // 保存 Python 代码
89
- if (pythonCode) {
137
+ if (finalPythonCode) {
90
138
  paths.python = join(domainDir, 'decrypt.py');
91
- writeFileSync(paths.python, pythonCode, 'utf-8');
139
+ writeFileSync(paths.python, finalPythonCode, 'utf-8');
92
140
  }
93
141
 
94
142
  // 保存 JS 代码
95
- if (jsCode) {
143
+ if (finalJsCode) {
96
144
  paths.javascript = join(domainDir, 'decrypt.js');
97
- writeFileSync(paths.javascript, jsCode, 'utf-8');
145
+ writeFileSync(paths.javascript, finalJsCode, 'utf-8');
98
146
  }
99
147
 
100
148
  // 生成 HTML
101
149
  paths.html = join(domainDir, 'report.html');
102
- const html = generateHtmlPage(title || domain, markdown, pythonCode, jsCode);
150
+ const html = generateHtmlPage(title || domain, markdown, finalPythonCode, finalJsCode);
103
151
  writeFileSync(paths.html, html, 'utf-8');
104
152
 
105
153
  console.log('[report] 已保存:', domainDir);
@@ -110,13 +158,15 @@ export const saveAnalysisReport = tool(
110
158
  },
111
159
  {
112
160
  name: 'save_analysis_report',
113
- description: '保存加密分析报告。分析完成后必须调用,保存 Markdown、HTML 和代码文件。',
161
+ description: `保存分析报告。推荐先用 artifact_save 保存代码文件,再传入文件路径。`,
114
162
  schema: z.object({
115
- domain: z.string().describe('网站域名或 URL'),
163
+ domain: z.string().describe('网站域名'),
116
164
  title: z.string().optional().describe('报告标题'),
117
- markdown: z.string().describe('Markdown 分析报告'),
118
- pythonCode: z.string().describe('Python 解密代码(必须提供完整可运行代码)'),
119
- jsCode: z.string().optional().describe('JavaScript 解密代码'),
165
+ markdown: z.string().describe('Markdown 摘要'),
166
+ pythonCodeFile: z.string().optional().describe('Python 代码文件路径(推荐)'),
167
+ pythonCode: z.string().optional().describe('Python 代码内容(不推荐)'),
168
+ jsCodeFile: z.string().optional().describe('JS 代码文件路径'),
169
+ jsCode: z.string().optional().describe('JS 代码内容'),
120
170
  }),
121
171
  }
122
172
  );
@@ -12,10 +12,12 @@ let hookManager = null;
12
12
 
13
13
  /**
14
14
  * 标记 Hook 已注入(供外部调用)
15
+ * 注意:Hook 脚本由 browser/client.js 自动注入,此处仅标记状态
15
16
  */
16
17
  export function markHookInjected() {
17
18
  if (!hookManager) {
18
19
  hookManager = new HookManager();
20
+ hookManager.markInjected();
19
21
  }
20
22
  }
21
23
 
@@ -25,13 +27,13 @@ export function markHookInjected() {
25
27
  export const launchBrowser = tool(
26
28
  async ({ headless }) => {
27
29
  const browser = await getBrowser({ headless });
28
- // 检查是否已经注入过 Hook
30
+ // Hook 已由 browser/client.js 自动注入,此处仅标记状态
29
31
  if (!hookManager) {
30
32
  hookManager = new HookManager();
31
- await hookManager.inject(browser.getPage());
32
- return JSON.stringify({ success: true, message: '浏览器已启动,Hook 已注入' });
33
+ hookManager.markInjected();
34
+ hookManager.bindConsole(browser.getPage());
33
35
  }
34
- return JSON.stringify({ success: true, message: '浏览器已就绪' });
36
+ return JSON.stringify({ success: true, message: '浏览器已就绪,Hook 已注入' });
35
37
  },
36
38
  {
37
39
  name: 'launch_browser',