refactor: rewrite GETTING_STARTED, cleanup README, add hub.pyw, remove QUICK_START.pdf

This commit is contained in:
Jiaqing Liang
2026-03-27 12:58:13 +08:00
parent ff258dd0a9
commit f6c5011eb0
10 changed files with 536 additions and 528 deletions

6
.gitignore vendored
View File

@@ -73,7 +73,11 @@ memory/skill_search/**/__pycache__/
restore_commit.txt restore_commit.txt
sche_tasks/ sche_tasks/
QUICK_START.md
# CDP Bridge 密钥配置(首次运行自动生成) # CDP Bridge 密钥配置(首次运行自动生成)
assets/tmwd_cdp_bridge/config.js assets/tmwd_cdp_bridge/config.js
**log.* **log.*
# Reflect (ignore new files, whitelist existing)
reflect/*
!reflect/autonomous.py
!reflect/scheduler.py

View File

@@ -1,409 +1,258 @@
# GenericAgent 新手上手指南 # 🚀 新手上手指南
## 🎯 什么是 GenericAgent? > 完全没接触过编程也没关系跟着做就行。Mac / Windows 都适用。
>
GenericAgent 是一个极简、可自我进化的自主 Agent 框架。它的核心只有 **~3,300 行代码**,通过 **7 个原子工具 + 92 行 Agent Loop**,就能赋予任意 LLM 对你本地计算机的系统级控制能力 > 如果你已经有 Python 环境,直接跳到[第 2 步](#2-配置-api-key)
**它能做什么?**
- 控制浏览器(保留登录态)
- 执行终端命令
- 读写文件系统
- 模拟键盘鼠标
- 控制移动设备(ADB)
- 屏幕视觉识别
**最特别的是什么?**
GenericAgent 不预设技能,而是**靠进化获得能力**。每解决一个新任务,它就会自动将执行路径固化为 Skill,供后续直接调用。使用时间越长,沉淀的技能越多,最终形成一棵完全属于你的专属技能树。
> 🤖 **自举实证**: 本项目的所有 Git 操作,从 `git init` 到每一条 commit message,都是 GenericAgent 自主完成的。作者全程未打开过一次终端。
--- ---
## 🚀 5 分钟快速开始 ## 1. 安装 Python
### 第一步:安装部署 ### Mac
**方法一:标准安装(推荐开发者)** 打开「终端」(启动台搜索 "终端" 或 "Terminal"),粘贴这行命令然后回车:
```bash ```bash
# 1. 克隆仓库 brew install python
git clone https://github.com/lsdefine/GenericAgent.git
cd GenericAgent
# 2. 安装最小依赖
pip install streamlit pywebview
# 3. 配置 API Key
cp mykey_template.py mykey.py
``` ```
**方法二:Windows 便携版(推荐新手)** 如果提示 `brew: command not found`,说明还没装 Homebrew先粘贴这行
1. [下载便携版](http://kw.fudan.edu.cn/resources/PC-Agent-Portable.zip) (19MB)
2. 解压到任意目录
3. 双击运行即可
**方法三:Android(Termux)**
```bash ```bash
cd /sdcard/ga /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
python agentmain.py
``` ```
### 第二步:配置 API Key 装完后再执行 `brew install python`
编辑 `mykey.py` 文件,填入你的 LLM API Key: ### Windows
1. 打开 [python.org/downloads](https://www.python.org/downloads/),点黄色大按钮下载
2. 运行安装包,**底部的 "Add Python to PATH" 一定要勾上**
3. 点 "Install Now"
### 验证
终端 / 命令提示符里输入:
```bash
python3 --version
```
看到 `Python 3.x.x` 就 OK。Windows 上也可以试 `python --version`
> ⚠️ **版本提示**:推荐 **Python 3.11 或 3.12**。不要使用 3.14(与 pywebview 等依赖不兼容)。
---
## 2. 配置 API Key
### 下载项目
1. 打开 [GitHub 仓库页面](https://github.com/lsdefine/GenericAgent)
2. 点绿色 **Code** 按钮 → **Download ZIP**
3. 解压到你喜欢的位置
### 创建配置文件
进入项目文件夹,把 `mykey_template.py` 复制一份,重命名为 `mykey.py`
用任意文本编辑器打开 `mykey.py`,填入你的 API 信息。**选一种填就行**,不用的配置删掉或留着不管都行。
### 配置示例
**最常见的用法:**
```python ```python
# 示例:使用 Claude # 变量名含 'oai' → 走 OpenAI 兼容格式 (/chat/completions)
api_key = "sk-ant-xxx" # 你的 Anthropic API Key oai_config = {
model = "claude-3-5-sonnet-20241022" 'apikey': 'sk-你的密钥',
'apibase': 'http://你的API地址:端口',
# 或使用其他模型 'model': '模型名称',
# api_key = "your_openai_key" }
# model = "gpt-4"
``` ```
支持的模型: ```python
- Claude (Anthropic) # 变量名含 'claude'(不含 'native')→ 走 Claude 兼容格式 (/messages)
- GPT-4 / GPT-3.5 (OpenAI) claude_config = {
- Gemini (Google) 'apikey': 'sk-你的密钥',
- Kimi (Moonshot) 'apibase': 'http://你的API地址:端口',
- 其他兼容 OpenAI API 的模型 'model': 'claude-sonnet-4-20250514',
}
```
### 第三步:启动界面 **使用标准工具调用格式(适合较弱模型):**
```python
# 变量名同时含 'native' 和 'claude' → Claude 标准工具调用格式
native_claude_config = {
'apikey': 'sk-ant-你的密钥',
'apibase': 'https://api.anthropic.com',
'model': 'claude-sonnet-4-20250514',
}
```
> 💡 还支持 `native_oai_config`OpenAI 标准工具调用)、`xai_config`Grok、`sider_cookie`Sider详见 `mykey_template.py` 中的注释。
### 关键规则
**变量命名决定接口格式**(不是模型名决定的):
| 变量名包含 | 触发的 Session | 适用场景 |
|-----------|---------------|---------|
| `oai` | OpenAI 兼容 | 大多数 API 服务、OpenAI 官方 |
| `claude`(不含 `native` | Claude 兼容 | Claude API 服务 |
| `native` + `claude` | Claude 标准工具调用 | 较弱模型推荐,工具调用更规范 |
| `native` + `oai` | OpenAI 标准工具调用 | 较弱模型推荐,工具调用更规范 |
> 例:用 Claude 模型,但 API 服务提供的是 OpenAI 兼容接口 → 变量名用 `oai_xxx`。
**`apibase` 填写规则**(会自动拼接端点路径):
| 你填的内容 | 系统行为 |
|-----------|---------|
| `http://host:2001` | 自动补 `/v1/chat/completions` |
| `http://host:2001/v1` | 自动补 `/chat/completions` |
| `http://host:2001/v1/chat/completions` | 直接使用,不拼接 |
---
## 3. 初次启动
终端里进入项目文件夹,运行:
```bash ```bash
python launch.pyw cd 你的解压路径
python3 agentmain.py
``` ```
启动后会出现一个桌面悬浮窗,你可以直接在里面输入任务指令 这就是**命令行模式**,已经可以用了。你会看到一个输入提示符,直接打字发送任务即可
试试你的第一个任务:
```
帮我在桌面创建一个 hello.txt内容是 Hello World
```
> 💡 Windows 上如果 `python3` 不识别,换成 `python agentmain.py`。
--- ---
## 💡 基础使用 ## 4. 让 Agent 自己装依赖
### 你的第一个任务 Agent 启动后,只需要一句话,它就会自己搞定所有依赖:
启动后,试试这些简单的任务:
**文件操作:**
```
"帮我在桌面创建一个 hello.txt 文件,内容是 Hello World"
```
**网页浏览:**
```
"打开百度,搜索今天的天气"
```
**代码执行:**
```
"用 Python 计算 1 到 100 的和"
```
### 理解 Agent 的工作方式
GenericAgent 的工作流程:
``` ```
你的指令 → Agent 理解任务 → 调用工具执行 → 返回结果 → 沉淀经验 请查看你的代码,安装所有用得上的 python 依赖
``` ```
**核心循环(92 行代码):** Agent 会自己读代码、找出需要的包、全部装好。
1. **感知**: 读取当前环境状态
2. **推理**: 分析任务,制定执行计划
3. **执行**: 调用工具完成操作
4. **记忆**: 将成功的执行路径写入记忆层
5. **循环**: 继续下一步,直到任务完成
--- > ⚠️ 如果遇到网络问题导致 Agent 无法调用 API可能需要先手动装一个包
> ```bash
> pip install requests
> ```
## 🧬 进阶:Skill 系统 ### 升级到图形界面
### 什么是 Skill? 依赖装完后,就可以用 GUI 模式了:
Skill 是 GenericAgent 自动沉淀的任务执行模板。当你第一次完成某个任务时,Agent 会将整个执行流程固化为一个 Skill,下次遇到类似任务就能直接调用。
**举个例子:**
| 你说的话 | 第一次 Agent 做了什么 | 之后每次 |
|---------|---------------------|---------|
| "监控股票并提醒我" | 安装 mootdx → 构建选股流程 → 配置定时任务 → 保存 Skill | **一句话启动** |
| "用 Gmail 发这个文件" | 配置 OAuth → 编写发送脚本 → 保存 Skill | **直接可用** |
### 如何使用 Skill?
Skill 会自动保存在 `memory/` 目录下。你不需要手动管理,Agent 会在需要时自动调用。
**查看已有的 Skill:**
```bash ```bash
ls memory/*.md python3 launch.pyw
``` ```
**手动触发 Skill:** 启动后会出现一个桌面悬浮窗,直接在里面输入任务指令。
### 可选:让 Agent 帮你做的事
``` ```
"使用之前保存的股票监控 Skill" 请帮我建立 git 连接,方便以后更新代码
``` ```
### 培养你的专属 Agent Agent 会自动配好。如果你电脑上没有 Git它也会帮你下载 portable 版。
使用时间越长,你的 Agent 积累的 Skill 越多: ```
请帮我在桌面创建一个 launch.pyw 的快捷方式
```
- **第 1 周**: 基础文件操作、简单网页浏览 这样以后双击桌面图标就能启动,不用再开终端了。
- **第 1 个月**: 自动化工作流、数据处理脚本
- **第 3 个月**: 复杂的多步骤任务、跨平台操作
- **第 6 个月**: 一套任何人都没有的专属技能树
--- ---
## 🎮 实用玩法 ## 5. 能力解锁
环境跑起来之后,你可以逐步解锁更多能力。每一项都只需要**对 Agent 说一句话**
### 基础能力
| 能力 | 对 Agent 说 | 说明 |
|------|-----------|------|
| **PowerShell 脚本执行** | `帮我解锁当前用户的 PowerShell ps1 执行权限` | Windows 默认禁止运行 .ps1 脚本 |
| **全局文件搜索** | `安装并配置 Everything 命令行工具进 PATH` | 毫秒级全盘文件搜索 |
### 浏览器自动化 ### 浏览器自动化
GenericAgent 注入真实浏览器,保留你的登录态: | 能力 | 对 Agent 说 | 说明 |
|------|-----------|------|
| **Web 工具解锁** | `执行 web setup sop解锁 web 工具` | 注入浏览器插件,使 Agent 能直接操控网页 |
解锁后Agent 可以在**保留你登录态**的真实浏览器中操作:
``` ```
"打开淘宝,搜索 iPhone 15,按价格排序" 打开淘宝搜索 iPhone 16按价格排序
"登录我的 Gmail,查看未读邮件" 去 B 站,查看我最近看过的历史视频
"在美团上帮我点一杯奶茶"
``` ```
**可用工具:** ### 进阶能力
- `web_scan`: 读取网页内容
- `web_execute_js`: 执行 JavaScript 控制页面
### 文件和代码处理 | 能力 | 对 Agent 说 | 说明 |
|------|-----------|------|
| **OCR** | `用rapidocr配置你的ocr能力并存入记忆` | 让 Agent 能"看到"屏幕文字 |
| **屏幕视觉** | `仿造你的llmcore写个调用vision的能力并存入记忆` | 让 Agent 能"看到"屏幕内容 |
| **移动端控制** | `配置 ADB 环境,准备连接安卓设备` | 通过 USB/WiFi 控制 Android 手机 |
``` ### 聊天平台接入(可选)
"分析这个 Python 项目的代码结构"
"把这个 CSV 文件转换成 Excel"
"批量重命名这个文件夹里的图片"
```
**可用工具:** 接入后可以随时随地通过手机给电脑上的 Agent 发指令。
- `file_read`: 读取文件
- `file_write`: 写入文件
- `file_patch`: 精确修改文件
- `code_run`: 执行代码
### 移动设备控制(ADB) 对 Agent 说:`看你的代码,帮我配置 XX 平台的机器人接入`
通过 ADB 控制 Android 设备: 支持的平台:**微信个人Bot** / QQ / 飞书 / 企业微信 / 钉钉 / Telegram
``` > Agent 会自动读取代码、引导你完成配置。
"打开支付宝,查看我的账单"
"在微信上给张三发消息"
"截取手机屏幕并保存"
```
### 定时任务 ### 高级模式
``` 以下模式全部**自文档化**——不用查手册,直接问 Agent 即可:
"每天早上 8 点提醒我查看邮件"
"每小时检查一次股票价格" | 模式 | 对 Agent 说 |
"每周一生成上周的工作总结" |------|------------|
``` | **Reflect反射** | `查看你的代码,告诉我你的 reflect 模式怎么启用` |
| **计划任务** | `查看你的代码,告诉我你的计划任务模式怎么启用` |
| **Plan规划** | `查看你的代码,告诉我你的 plan 模式怎么启用` |
| **SubAgent子代理** | `查看你的代码,告诉我你的 subagent 模式怎么启用` |
| **自主探索** | `查看你的代码,告诉我你的自主探索模式怎么启用` |
> 💡 这就是 GenericAgent 的核心设计理念:**代码即文档**。Agent 能读懂自己的源码,所以任何功能你都可以直接问它。
--- ---
## 🤖 接入聊天平台(可选) ## 💡 使用越久越强
### 为什么要接入 Bot? GenericAgent 不预设技能,而是**靠使用进化**。每完成一个新任务,它会自动将执行路径固化为 Skill下次遇到类似任务直接调用。
将 GenericAgent 接入聊天平台后,你可以: 你不需要管理这些 SkillAgent 会自动处理。使用时间越长,积累的技能越多,最终形成一棵完全属于你的专属技能树。
- 随时随地通过手机控制你的电脑
- 多人协作使用同一个 Agent
- 接收 Agent 的主动通知和提醒
### 支持的平台 > 💡 如果你觉得某些重要信息 Agent 没有记住,可以直接告诉它:`把这个记到你的记忆里`,它会主动记忆。
- **QQ Bot**: WebSocket 长连接,无需公网 webhook **其他 Claw 的 Skill 也可以直接复用:**
- **飞书(Lark)**: 支持富文本、图片、文件、音频
- **企业微信(WeCom)**: 企业内部使用
- **钉钉(DingTalk)**: 企业协作
- **Telegram**: 国际用户
### 快速配置指南 - 让 Agent 搜索:`帮我找个做 XXX 的 skill` → 完成后 → `加入你的记忆中`
- 直接指定来源:`访问 XXX 文件夹/URL按照这个 skill 做 XXX`
**以 QQ Bot 为例:** **保持更新:**
1. 在 [QQ 开放平台](https://q.qq.com) 创建机器人 对 Agent 说:`git 更新你的代码,然后看看 commit 有什么新功能`
2. 获取 AppID 和 AppSecret
3.`mykey.py` 中配置:
```python > Agent 会自动 pull 最新代码并解读 commit log告诉你新增了什么能力。
qq_app_id = "YOUR_APP_ID"
qq_app_secret = "YOUR_APP_SECRET"
qq_allowed_users = ["YOUR_USER_OPENID"] # 或 ['*'] 公开访问
```
4. 启动 Bot: > 更多细节请参阅 [README.md](README.md) 或 [详细版图文教程](https://my.feishu.cn/wiki/CGrDw0T76iNFuskmwxdcWrpinPb)。
```bash
pip install qq-botpy
python frontends/qqapp.py
# 或与桌面窗口一起启动
python launch.pyw --qq
```
**其他平台配置详见:**
- 飞书: [assets/SETUP_FEISHU.md](assets/SETUP_FEISHU.md)
- 企业微信: `python launch.pyw --wecom`
- 钉钉: `python launch.pyw --dingtalk`
- Telegram: `python frontends/tgapp.py`
---
## 📚 记忆系统详解
### 三层记忆架构
GenericAgent 的记忆系统分为三层:
**L0 — 元规则(Meta Rules)**
- Agent 的基础行为规则
- 系统约束和安全边界
- 不可修改的核心逻辑
**L2 — 全局事实(Global Facts)**
- 长期运行中积累的稳定知识
- 环境配置、用户偏好
- 存储在 `memory/global_mem.txt`
**L3 — 任务 Skills(SOPs)**
- 完成特定任务的操作流程
- 自动沉淀的执行模板
- 存储在 `memory/*.md`
### 如何查看和管理记忆
**查看全局记忆:**
```bash
cat memory/global_mem.txt
```
**查看所有 Skills:**
```bash
ls memory/*.md
```
**手动编辑记忆:**
```bash
# 不建议新手操作,Agent 会自动管理
vim memory/global_mem.txt
```
---
## 🔧 常见问题
### 安装问题
**Q: pip install 失败怎么办?**
A: 尝试使用国内镜像源:
```bash
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple streamlit pywebview
```
**Q: Windows 上启动失败?**
A: 建议使用便携版,或检查 Python 版本(需要 3.8+)
### 使用问题
**Q: Agent 执行任务失败?**
A: 检查:
1. API Key 是否正确配置
2. 网络连接是否正常
3. 查看 `temp/` 目录下的日志文件
**Q: 如何让 Agent 停止当前任务?**
A: 在界面中输入 "停止" 或 "取消"
**Q: Agent 的回复太慢?**
A: 可以尝试:
1. 切换到更快的模型(如 GPT-3.5)
2. 减少任务的复杂度
3. 检查网络延迟
### 进阶问题
**Q: 如何自定义 Agent 的行为?**
A: 编辑 `memory/global_mem.txt`,添加你的偏好设置
**Q: 如何备份我的 Skills?**
A: 直接复制 `memory/` 目录即可
**Q: 可以同时运行多个 Agent 实例吗?**
A: 可以,但需要使用不同的工作目录
---
## 💪 最佳实践
### 新手建议
1. **从简单任务开始**: 先尝试文件操作、网页浏览等基础任务
2. **观察 Agent 的执行过程**: 理解它是如何调用工具的
3. **让 Agent 自己探索**: 不要过度干预,让它自主学习
4. **定期查看 Skills**: 了解 Agent 积累了哪些能力
### 进阶技巧
1. **组合使用多个工具**: 让 Agent 完成复杂的多步骤任务
2. **利用记忆系统**: 在 `global_mem.txt` 中记录常用的配置和偏好
3. **编写自定义脚本**: 通过 `code_run` 扩展 Agent 的能力
4. **接入多个聊天平台**: 实现跨平台的统一控制
### 安全注意事项
1. **不要在公共环境运行**: Agent 有系统级权限,注意安全
2. **谨慎授权**: 使用 Bot 时,设置 `allowed_users` 白名单
3. **定期备份**: 重要的 Skills 和配置要及时备份
4. **监控执行日志**: 定期检查 `temp/` 目录下的日志
---
## 🌟 社区和支持
### 获取帮助
- **GitHub Issues**: [提交问题](https://github.com/lsdefine/GenericAgent/issues)
- **微信交流群**: 扫描下方二维码加入
<div align="center">
<img src="assets/images/wechat_group.jpg" width="280"/>
</div>
### 贡献指南
欢迎贡献代码、文档或 Skills!
1. Fork 本仓库
2. 创建你的特性分支
3. 提交你的改动
4. 发起 Pull Request
---
## 📖 延伸阅读
- [完整 README](README.md) - 项目详细介绍
- [新用户欢迎文档](WELCOME_NEW_USER.md) - 详细的引导流程
- [快速开始指南](QUICK_START.pdf) - PDF 版本的快速入门
- [飞书配置指南](assets/SETUP_FEISHU.md) - 飞书 Bot 详细配置
---
**🎉 现在,开始你的 GenericAgent 之旅吧!**
记住: GenericAgent 不是一个预设好的工具,而是一个会随着你的使用不断进化的伙伴。使用时间越长,它就越懂你,越强大。

Binary file not shown.

132
README.md
View File

@@ -83,94 +83,11 @@ cp mykey_template.py mykey.py
python launch.pyw python launch.pyw
``` ```
#### Method 2: Windows Portable Version (Recommended for beginners) Full guide: [GETTING_STARTED.md](GETTING_STARTED.md)
[Download portable version](http://kw.fudan.edu.cn/resources/PC-Agent-Portable.zip) (19MB, unzip and run)
Full guide: [WELCOME_NEW_USER.md](WELCOME_NEW_USER.md)
#### Method 3: Android (Termux)
```bash
cd /sdcard/ga
python agentmain.py
```
--- ---
## 🤖 Bot Interfaces (Optional) ## 🤖 Bot Interface (Optional)
### QQ Bot
Uses `qq-botpy` WebSocket long connection — **no public webhook required**:
```bash
pip install qq-botpy
```
Add to `mykey.py`:
```python
qq_app_id = "YOUR_APP_ID"
qq_app_secret = "YOUR_APP_SECRET"
qq_allowed_users = ["YOUR_USER_OPENID"] # or ['*'] for public access
```
```bash
python frontends/qqapp.py
# or launch together with the desktop floating window
python launch.pyw --qq
```
> Create a bot at the [QQ Open Platform](https://q.qq.com) to get AppID / AppSecret. After the first message, user openid is logged in `temp/qqapp.log`.
### Lark (Feishu)
```bash
pip install lark-oapi
python frontends/fsapp.py # or python launch.pyw --feishu
```
```python
fs_app_id = "cli_xxx"
fs_app_secret = "xxx"
fs_allowed_users = ["ou_xxx"] # or ['*']
```
**Inbound support**: text, rich text post, images, files, audio, media, interactive cards / share cards
**Outbound support**: streaming progress cards, image replies, file / media replies
**Vision model**: Images are sent as true multimodal input to OpenAI Vision-compatible backends on the first turn
Full setup: [assets/SETUP_FEISHU.md](assets/SETUP_FEISHU.md)
### WeCom (Enterprise WeChat)
```bash
pip install wecom_aibot_sdk
python frontends/wecomapp.py # or python launch.pyw --wecom
```
```python
wecom_bot_id = "your_bot_id"
wecom_secret = "your_bot_secret"
wecom_allowed_users = ["your_user_id"]
wecom_welcome_message = "Hello, I'm online."
```
### DingTalk
```bash
pip install dingtalk-stream
python frontends/dingtalkapp.py # or python launch.pyw --dingtalk
```
```python
dingtalk_client_id = "your_app_key"
dingtalk_client_secret = "your_app_secret"
dingtalk_allowed_users = ["your_staff_id"] # or ['*']
```
### Telegram Bot ### Telegram Bot
@@ -335,23 +252,23 @@ cp mykey_template.py mykey.py
python launch.pyw python launch.pyw
``` ```
#### 方法二Windows 便携版(推荐新手) 完整引导流程见 [GETTING_STARTED.md](GETTING_STARTED.md)。
[下载便携版](http://kw.fudan.edu.cn/resources/PC-Agent-Portable.zip)19MB解压即用
完整引导流程见 [WELCOME_NEW_USER.md](WELCOME_NEW_USER.md)。
#### 方法三AndroidTermux
```bash
cd /sdcard/ga
python agentmain.py
```
--- ---
## 🤖 Bot 接口(可选) ## 🤖 Bot 接口(可选)
### 微信 Bot个人微信
无需额外配置,扫码登录即可:
```bash
pip install pycryptodome qrcode requests
python frontends/wechatapp.py
```
> 首次启动会弹出二维码,用微信扫码完成绑定。之后通过微信消息与 Agent 交互。
### QQ Bot ### QQ Bot
使用 `qq-botpy` WebSocket 长连接,**无需公网 webhook** 使用 `qq-botpy` WebSocket 长连接,**无需公网 webhook**
@@ -370,8 +287,6 @@ qq_allowed_users = ["YOUR_USER_OPENID"] # 或 ['*'] 公开访问
```bash ```bash
python frontends/qqapp.py python frontends/qqapp.py
# 或与桌面悬浮窗一起启动
python launch.pyw --qq
``` ```
> 在 [QQ 开放平台](https://q.qq.com) 创建机器人获取 AppID / AppSecret。首次消息后用户 openid 记录于 `temp/qqapp.log`。 > 在 [QQ 开放平台](https://q.qq.com) 创建机器人获取 AppID / AppSecret。首次消息后用户 openid 记录于 `temp/qqapp.log`。
@@ -380,7 +295,7 @@ python launch.pyw --qq
```bash ```bash
pip install lark-oapi pip install lark-oapi
python frontends/fsapp.py # 或 python launch.pyw --feishu python frontends/fsapp.py
``` ```
```python ```python
@@ -400,7 +315,7 @@ fs_allowed_users = ["ou_xxx"] # 或 ['*']
```bash ```bash
pip install wecom_aibot_sdk pip install wecom_aibot_sdk
python frontends/wecomapp.py # 或 python launch.pyw --wecom python frontends/wecomapp.py
``` ```
```python ```python
@@ -414,7 +329,7 @@ wecom_welcome_message = "你好,我在线上。"
```bash ```bash
pip install dingtalk-stream pip install dingtalk-stream
python frontends/dingtalkapp.py # 或 python launch.pyw --dingtalk python frontends/dingtalkapp.py
``` ```
```python ```python
@@ -424,19 +339,6 @@ dingtalk_allowed_users = ["your_staff_id"] # 或 ['*']
``` ```
### Telegram Bot
```python
# mykey.py
tg_bot_token = 'YOUR_BOT_TOKEN'
tg_allowed_users = [YOUR_USER_ID]
```
```bash
python frontends/tgapp.py
```
## 📊 与同类产品对比 ## 📊 与同类产品对比
| 特性 | GenericAgent | OpenClaw | Claude Code | | 特性 | GenericAgent | OpenClaw | Claude Code |

View File

@@ -1,66 +0,0 @@
# 🚀 欢迎使用物理级全能执行者
这是您的 Agent 初始化指引。请按以下阶段操作:
## 第一阶段:环境启动 (Initial Ignition)
1. **Python 环境检查**
- 确保安装了 Python 3.10+。几乎零额外依赖。
- `requests` 是唯一的第三方硬依赖,但多数 Python 发行版已预装。若缺失:`pip install requests`
2. **配置身份密钥 (Credentials)**
- 复制 `mykey_template.py``mykey.py` 并填入 API Key。
3. **唤醒 AgentCLI 最小启动)**
- 运行 `python agentmain.py`,进入 CLI 交互模式。此时 Agent 已可工作。
- 后续所有依赖(包括 GUI 模式所需的包)都可以叫 Agent 帮你安装。
4. **(可选)升级为 GUI 模式**
- **指令**`pip install streamlit pywebview`
- 安装后运行 `launch.pyw`,即可使用悬浮窗 GUI 界面。
## 第二阶段:能力激活 (Ability Activation)
在此阶段,您只需对我发送指令,所有物理操作由我完成:
1. **解锁 PowerShell 脚本执行权限**
- **指令**`请帮我当前用户解锁 powershell 的 ps1 执行权限。`
2. **配置全局文件搜索 (Everything CLI)**
- **指令**`安装并配置 everything 命令行工具进PATH。`
3. **Web 自动化环境配置 (Web Setup SOP)**
- **指令**`执行 web setup sop 解锁 web 工具`
- **物理影响**:我将引导您完成浏览器插件安装,并注入核心脚本,使我能够直接操控您的浏览器页面。
4. **补全常用工具库**
- **指令**`安装常用 Python 自动化包(如 requests, pandas, pyperclip。`
5. **配置网络代理 (Proxy Setup)**
- **指令**`告诉我能用的系统代理`
6. **激活视觉理解能力 (OCR & Vision)**
- **指令**`配置截图与 OCR 工具,解锁你的屏幕视觉。`
7. **移动端自动化准备 (Android/ADB)**
- **指令**`配置 ADB 环境,准备连接安卓设备。`
## 第三阶段:记忆与知识体系建造 (Knowledge Architecture)
当环境就绪后,您可以让我构建您的“数字大脑”:
1. **自动化 SOP 沉淀**
- **指令**`记录刚才的操作流程,生成一套自动化 SOP。`
2. **现有技能挂载 (SOP Retrieval)**
- **指令**`读取现有 SOP 目录,告诉我你现在掌握的所有技能。`
3. **物理资产审计 (Asset Audit)**
- **指令**`帮我建立物理资产清单,扫描并记录我常用的工具路径。`
4. **Web 调研实战 (Research & Report)**
- **指令**`搜索 [关键词],并根据网页内容整理一份简易 Markdown 报告保存到当前目录。`
- **物理影响**:我将自动打开浏览器,利用 Web 驱动采集多个页面信息,通过逻辑整合后在您的本地文件夹生成物理文件。
---
**💡 提示**:您可以直接复制上述 `指令` 发送给我,我将立刻执行对应的物理操作。

257
hub.pyw Normal file
View File

@@ -0,0 +1,257 @@
# launcher.pyw - GenericAgent 服务启动器
# 纯 tkinter + 标准库,零第三方依赖,跨平台
import os, sys, socket, subprocess, threading
import tkinter as tk
from tkinter import ttk
from collections import deque
LOCK_PORT = 19735
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def acquire_singleton():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind(('127.0.0.1', LOCK_PORT))
s.listen(1)
return s
except OSError:
return None
def discover_services():
services = []
reflect_dir = os.path.join(BASE_DIR, 'reflect')
if os.path.isdir(reflect_dir):
for f in sorted(os.listdir(reflect_dir)):
if f.endswith('.py') and not f.startswith('_'):
services.append({
'name': 'reflect/' + f,
'cmd': [sys.executable, 'agentmain.py', '--reflect', 'reflect/' + f],
})
frontends_dir = os.path.join(BASE_DIR, 'frontends')
if os.path.isdir(frontends_dir):
for f in sorted(os.listdir(frontends_dir)):
if f.endswith('app.py') and f != 'chatapp_common.py':
if f == 'stapp.py':
cmd = [sys.executable, '-m', 'streamlit', 'run',
'frontends/' + f, '--server.headless=true']
else:
cmd = [sys.executable, 'frontends/' + f]
services.append({'name': 'frontends/' + f, 'cmd': cmd})
return services
class ServiceManager:
def __init__(self):
self.procs = {}
self.buffers = {}
def start(self, name, cmd):
if name in self.procs and self.procs[name].poll() is None:
return
self.buffers[name] = deque(maxlen=500)
env = os.environ.copy()
env['PYTHONUNBUFFERED'] = '1'
kw = dict(cwd=BASE_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
text=True, bufsize=1, env=env)
if sys.platform == 'win32':
kw['creationflags'] = subprocess.CREATE_NO_WINDOW
proc = subprocess.Popen(cmd, **kw)
self.procs[name] = proc
threading.Thread(target=self._reader, args=(name, proc), daemon=True).start()
def _reader(self, name, proc):
try:
for line in proc.stdout:
self.buffers[name].append(line)
except Exception:
pass
def stop(self, name):
proc = self.procs.get(name)
if proc and proc.poll() is None:
proc.terminate()
try:
proc.wait(timeout=5)
except subprocess.TimeoutExpired:
proc.kill()
def is_running(self, name):
proc = self.procs.get(name)
return proc is not None and proc.poll() is None
def stop_all(self):
for name in list(self.procs):
self.stop(name)
def get_output(self, name):
buf = self.buffers.get(name)
return list(buf) if buf else []
class LauncherApp:
def __init__(self, root):
self.root = root
self.root.title('GenericAgent Launcher')
self.root.geometry('720x740')
self.root.protocol('WM_DELETE_WINDOW', self.on_close)
self.mgr = ServiceManager()
self.services = discover_services()
self.check_vars = {}
self.selected = None
self._build_ui()
self._poll()
def _build_ui(self):
# 标题行:左边标签,右边 Rescan 按钮
header = ttk.Frame(self.root)
header.pack(fill='x', padx=8, pady=(8, 0))
ttk.Label(header, text='Services', font=('', 10, 'bold')).pack(side='left')
ttk.Button(header, text='\u27f3 Rescan', width=10,
command=self._rescan).pack(side='right')
svc_frame = ttk.LabelFrame(self.root, padding=5)
svc_frame.pack(fill='x', padx=8, pady=(2, 4))
self.svc_container = ttk.Frame(svc_frame)
self.svc_container.pack(fill='x')
self.status_labels = {}
self.row_frames = {}
self.name_labels = {}
self._build_service_rows()
self.output_frame = ttk.LabelFrame(self.root, text='Output', padding=5)
self.output_frame.pack(fill='both', expand=True, padx=8, pady=(4, 8))
self.output_text = tk.Text(
self.output_frame, wrap='word', state='disabled',
bg='#1e1e1e', fg='#d4d4d4',
font=('Consolas', 9), insertbackground='white')
sb = ttk.Scrollbar(self.output_frame, command=self.output_text.yview)
self.output_text.configure(yscrollcommand=sb.set)
sb.pack(side='right', fill='y')
self.output_text.pack(fill='both', expand=True)
def _build_service_rows(self):
for svc in self.services:
name = svc['name']
row = tk.Frame(self.svc_container, cursor='hand2', padx=4, pady=2)
row.pack(fill='x', pady=1)
self.row_frames[name] = row
running = self.mgr.is_running(name)
var = self.check_vars.get(name, tk.BooleanVar(value=running))
if running:
var.set(True)
self.check_vars[name] = var
cb = ttk.Checkbutton(
row, variable=var,
command=lambda n=name, v=var, s=svc: self._toggle(n, v, s))
cb.pack(side='left')
name_lbl = tk.Label(row, text=name, anchor='w', cursor='hand2',
bg=row.cget('bg'))
name_lbl.pack(side='left', fill='x', expand=True)
self.name_labels[name] = name_lbl
st = 'running' if running else 'stopped'
fg = 'green' if running else 'gray'
lbl = ttk.Label(row, text=st, foreground=fg, width=10)
lbl.pack(side='right')
self.status_labels[name] = lbl
name_lbl.bind('<Button-1>', lambda e, n=name: self._select(n))
row.bind('<Button-1>', lambda e, n=name: self._select(n))
def _rescan(self):
# 记住正在运行的服务
running_names = {n for n in self.mgr.procs if self.mgr.is_running(n)}
# 清除旧行
for w in self.svc_container.winfo_children():
w.destroy()
self.status_labels.clear()
self.row_frames.clear()
self.name_labels.clear()
# 清除不再运行的 check_vars
old_vars = {k: v for k, v in self.check_vars.items() if k in running_names}
self.check_vars.clear()
self.check_vars.update(old_vars)
# 重新扫描
self.services = discover_services()
self._build_service_rows()
# 如果选中的服务不在新列表中,清除选中
svc_names = {s['name'] for s in self.services}
if self.selected and self.selected not in svc_names:
self.selected = None
self.output_frame.configure(text='Output')
def _toggle(self, name, var, svc):
if var.get():
self.mgr.start(name, svc['cmd'])
self._select(name)
else:
self.mgr.stop(name)
def _select(self, name):
self.selected = name
# 高亮选中行
for n, row in self.row_frames.items():
if n == name:
row.configure(bg='#cce5ff')
self.name_labels[n].configure(bg='#cce5ff')
else:
row.configure(bg='SystemButtonFace')
self.name_labels[n].configure(bg='SystemButtonFace')
self.output_frame.configure(text=f'Output - {name}')
self.root.after(50, self._refresh_output)
def _refresh_output(self):
if not self.selected:
return
lines = self.mgr.get_output(self.selected)
self.output_text.configure(state='normal')
self.output_text.delete('1.0', 'end')
self.output_text.insert('end', ''.join(lines[-200:]))
self.output_text.configure(state='disabled')
self.output_text.see('end')
def _poll(self):
for svc in self.services:
name = svc['name']
running = self.mgr.is_running(name)
lbl = self.status_labels[name]
if running:
lbl.configure(text='running', foreground='green')
else:
lbl.configure(text='stopped', foreground='gray')
if self.check_vars[name].get():
self.check_vars[name].set(False)
self._refresh_output()
self.root.after(1000, self._poll)
def on_close(self):
self.mgr.stop_all()
self.root.destroy()
if __name__ == '__main__':
lock = acquire_singleton()
if lock is None:
try:
import tkinter.messagebox as mb
r = tk.Tk()
r.withdraw()
mb.showinfo('Launcher', 'Already running.')
r.destroy()
except Exception:
pass
sys.exit(0)
root = tk.Tk()
app = LauncherApp(root)
root.mainloop()
lock.close()

View File

@@ -109,13 +109,9 @@ if __name__ == '__main__':
else: print('[Launch] DingTalk Bot not enabled (use --dingtalk to start)') else: print('[Launch] DingTalk Bot not enabled (use --dingtalk to start)')
if not args.no_sched: if not args.no_sched:
try: scheduler_proc = subprocess.Popen([sys.executable, os.path.join(script_dir, "agentmain.py"), "--reflect", os.path.join(script_dir, "reflect", "scheduler.py"), "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM); sock.bind(('127.0.0.1', 45762)); sock.listen(1) atexit.register(scheduler_proc.kill)
scheduler_proc = subprocess.Popen([sys.executable, os.path.join(script_dir, "agentmain.py"), "--reflect", os.path.join(script_dir, "reflect", "scheduler.py"), "--llm_no", str(args.llm_no)], creationflags=subprocess.CREATE_NO_WINDOW if os.name=='nt' else 0); print('[Launch] Task Scheduler started (duplicate prevented by scheduler port lock)')
atexit.register(lambda: (scheduler_proc.kill(), sock.close()))
print('[Launch] Task Scheduler started')
except OSError:
print('[Launch] Task Scheduler already running (port occupied)')
else: print('[Launch] Task Scheduler disabled (--no-sched)') else: print('[Launch] Task Scheduler disabled (--no-sched)')
monitor_thread = threading.Thread(target=idle_monitor, daemon=True) monitor_thread = threading.Thread(target=idle_monitor, daemon=True)

View File

@@ -19,7 +19,7 @@ def compress_history_tags(messages, keep_recent=10, max_len=800):
Supports both prompt-style (ClaudeSession/LLMSession) and content-style (NativeClaudeSession) messages.""" Supports both prompt-style (ClaudeSession/LLMSession) and content-style (NativeClaudeSession) messages."""
compress_history_tags._cd = getattr(compress_history_tags, '_cd', 0) + 1 compress_history_tags._cd = getattr(compress_history_tags, '_cd', 0) + 1
if compress_history_tags._cd % 5 != 0: return messages if compress_history_tags._cd % 5 != 0: return messages
_before = sum(len(json.dumps(m)) for m in messages) _before = sum(len(json.dumps(m, ensure_ascii=False)) for m in messages)
_pats = {tag: re.compile(rf'(<{tag}>)([\s\S]*?)(</{tag}>)') for tag in ('thinking', 'tool_use', 'tool_result')} _pats = {tag: re.compile(rf'(<{tag}>)([\s\S]*?)(</{tag}>)') for tag in ('thinking', 'tool_use', 'tool_result')}
def _trunc(text): def _trunc(text):
for pat in _pats.values(): text = pat.sub(lambda m: m.group(1) + m.group(2)[:max_len] + '...' + m.group(3) if len(m.group(2)) > max_len else m.group(0), text) for pat in _pats.values(): text = pat.sub(lambda m: m.group(1) + m.group(2)[:max_len] + '...' + m.group(3) if len(m.group(2)) > max_len else m.group(0), text)
@@ -34,7 +34,7 @@ def compress_history_tags(messages, keep_recent=10, max_len=800):
for block in c: for block in c:
if isinstance(block, dict) and block.get('type') == 'text' and isinstance(block.get('text'), str): if isinstance(block, dict) and block.get('type') == 'text' and isinstance(block.get('text'), str):
block['text'] = _trunc(block['text']) block['text'] = _trunc(block['text'])
print(f"[Cut] {_before} -> {sum(len(json.dumps(m)) for m in messages)}") print(f"[Cut] {_before} -> {sum(len(json.dumps(m, ensure_ascii=False)) for m in messages)}")
return messages return messages
def auto_make_url(base, path): def auto_make_url(base, path):

View File

@@ -5,6 +5,10 @@
- 非Selenium/Playwright不需调试浏览器或新数据目录 - 非Selenium/Playwright不需调试浏览器或新数据目录
- 支撑 `web_scan`(只读DOM) / `web_execute_js`(执行JS) 等高层工具 - 支撑 `web_scan`(只读DOM) / `web_execute_js`(执行JS) 等高层工具
## 通用限制
- ⚠web_execute_js中**禁止使用await**会报SyntaxError非async上下文BBS#34验证
- 需要异步操作时用`.then()`链式调用或回调
## 限制(isTrusted) ## 限制(isTrusted)
- JS dispatch的事件`isTrusted=false`,敏感操作(文件上传/部分按钮)会被浏览器拦截 - JS dispatch的事件`isTrusted=false`,敏感操作(文件上传/部分按钮)会被浏览器拦截
- ⭐**首选绕过CDP桥**——CDP派发的Input事件是浏览器原生级别(isTrusted=true)且无需前台见下方CDP章节 - ⭐**首选绕过CDP桥**——CDP派发的Input事件是浏览器原生级别(isTrusted=true)且无需前台见下方CDP章节
@@ -61,12 +65,67 @@ document.body.appendChild(el); // 响应写回el.textContent
- ⭐batch混合`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}` - ⭐batch混合`{cmd:'batch', commands:[{cmd:'cookies'},{cmd:'tabs'},{cmd:'cdp',...},...]}`
- 返回`{ok:true, results:[...]}`一次请求多命令CDP懒attach复用session - 返回`{ok:true, results:[...]}`一次请求多命令CDP懒attach复用session
- `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"` - `$N.path`引用第N个结果字段(0-indexed),如`"nodeId":"$2.root.nodeId"`
- 典型:文件上传三连 getDocument→querySelector(input[type=file])→setFileInputFiles - ⚠batch前序命令失败时后续`$N`引用拿到undefined整条链路**静默失败不报错**需检查返回results数组中每项的ok状态未验证BBS#46
- 典型:文件上传三连 getDocument(**depth:1**性能优化200ms+→个位数ms)→querySelector(input[type=file])→setFileInputFiles未验证BBS#38
- ⚠nodeId路径一致性getDocument+querySelector路径和performSearch+getSearchResults路径的nodeId**不互通**同一batch内不可混用未验证BBS#45
- ⚠文件上传后前端框架(React/Vue)可能不感知→JS补发**两个事件**Vue3需input事件而非仅change未验证BBS#35/#39
```js
el.dispatchEvent(new Event('input', {bubbles:true}));
el.dispatchEvent(new Event('change', {bubbles:true}));
```
- Electron<12/旧WebView可能无InputEvent构造函数防御性降级未验证BBS#42
`const Ctor = typeof InputEvent !== 'undefined' ? InputEvent : Event; el.dispatchEvent(new Ctor('input', {bubbles:true}));`
- 极端情况(框架仍不响应)Runtime.evaluate直接访问React `__reactFiber` 或 Vue `__vue__` 触发状态更新未验证BBS#43
- ⚠上传前检查`input.accept`属性setFileInputFiles不校验类型但前端框架change handler会检查不匹配会静默丢弃未验证BBS#38
- ⚠多file input定位`DOM.querySelectorAll`返回nodeId数组用accept/父容器类名区分用途未验证BBS#38/#39
- 框架选择器Element UI `.el-upload__input` | Ant Design `.ant-upload input[type=file]` | Naive UI `.n-upload-trigger input[type=file]` | Dropzone `.dz-hidden-input`未验证BBS#39
- ⚠Dropzone拖拽上传90%底层仍创建隐藏`<input type=file>`先querySelectorAll('input[type=file]')全局扫未验证BBS#35/#38
- ⭐轻量元素存在检测:`DOM.performSearch({query:'input[type=file]'})`返回resultCount不触发DOM树构建轮询等待元素时避免重复getDocument未验证BBS#39
- performSearch支持三种语法CSS选择器 / XPath(`//input[@type='file']`) / 纯文本自动识别未验证BBS#41
- ⭐瞬态file input处理Ant Design等框架点击上传按钮时动态创建input上传完立即销毁未验证BBS#42/#43
- 方案A(批处理)在同一batch内完成 performSearch→getSearchResults→setFileInputFiles→**discardSearchResults**缩小input被销毁的时间窗口discardSearchResults防searchId泄漏未验证BBS#46
- 方案B(事件监听)`DOM.enable`后监听`DOM.childNodeInserted`事件捕获input创建瞬间零延迟拿到nodeId
- 前提须先对document.body的nodeId调`DOM.requestChildNodes`否则CDP不推送子树变更
- ⚠`DOM.disable`会使所有已获取nodeId失效setFileInputFiles必须在disable之前。正确时序DOM.enable→requestChildNodes→[等事件]→setFileInputFiles→DOM.disable未验证BBS#45
- 方案C(猴子补丁兜底)Runtime.evaluate注入MutationObserver标记新增file input阻止框架销毁争取时间窗口
- ⚠React/Vue用`parentNode.removeChild(node)`而非`node.remove()`需patch `Element.prototype.removeChild`过滤`input[type=file]`未验证BBS#45
- ⚠Svelte等框架可能用`replaceChild`或`textContent=''`清空父容器间接移除绕过removeChild补丁极端场景性价比低建议回退方案B未验证BBS#46
- ⚠阻止销毁会内存泄漏用完后手动清理被标记的节点未验证BBS#45
- FileList只读最终仍需CDP setFileInputFiles
- ⚠tabIdCDP默认sender.tab.id(当前注入页)跨tab需显式tabId或先batch内tabs查 - ⚠tabIdCDP默认sender.tab.id(当前注入页)跨tab需显式tabId或先batch内tabs查
- CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等)单条每次attach→send→detach - CDP可用任意方法(Input/Network/DOM/Page/Runtime/Emulation等)单条每次attach→send→detach
- ⭐跨tab无需前台指定tabId即可操作后台标签页 - ⭐跨tab无需前台指定tabId即可操作后台标签页
- ⭐绕过isTrustedCDP派发的Input事件是浏览器原生级别 - ⭐绕过isTrustedCDP派发的Input事件是浏览器原生级别
## CDP点击完整生命周期未验证BBS#23
- 通用点击需**三事件序列**mouseMoved → mousePressed → mouseReleased间隔50-100ms
- 省略mouseMoved会导致MUI Tooltip/Ant Design Dropdown等hover依赖组件失效
- ⚠autofill释放是特例只需mousePressed即可见下方autofill章节
- 坐标修正页面有transform:scale/zoom时
```js
var scale = window.visualViewport ? window.visualViewport.scale : 1;
var zoom = parseFloat(getComputedStyle(document.documentElement).zoom) || 1;
var realX = x * zoom; var realY = y * zoom;
```
- iframe内元素CDP点击坐标需合成 `finalX = iframeRect.x + elRect.x`
- 跨域iframe拿不到contentDocument用CDP `Target.getTargets`找iframe targetId → `Target.attachToTarget`建独立会话
## CDP文本输入未验证BBS#23
- `Input.insertText({text:'...'})` — 直接插入不触发keydown/keyup
- `Input.dispatchKeyEvent` — 逐键派发,慢但完整模拟
- React/Vue受控组件先insertText再JS手动dispatch `input`事件input事件不检查isTrusted
- 简单输入框用insertText够用
## CDP DOM域穿透 closed Shadow DOM未验证BBS#24/#25
- `DOM.getDocument({depth:-1, pierce:true})` 穿透所有Shadow边界含closed
- `DOM.querySelector({nodeId, selector})` 定位 → `DOM.getBoxModel({nodeId})` 取坐标
- getBoxModel返回content八值[x1,y1,...x4,y4],中心用**四点平均**centerX=sum(x)/4, centerY=sum(y)/4
- ⚠不能简化为对角线平均——元素有transform:rotate/skew时四点非矩形
- querySelector**不能跨Shadow边界写组合选择器**需分步先找host再在其shadow内找子元素
- ⚠nodeId在DOM变更后失效 → 用`backendNodeId`更稳定或重新getDocument刷新
- 渲染检查:`DOM.resolveNode` → `Runtime.callFunctionOn` 检查offsetHeight>0
- 完整pipeline: getDocument(pierce) → querySelector → getBoxModel → 四点平均坐标 → Input三事件点击
## autofill获取 ## autofill获取
检测web_scan输出input带`data-autofilled="true"`value显示为受保护提示(非真实值Chrome安全保护需点击释放) 检测web_scan输出input带`data-autofilled="true"`value显示为受保护提示(非真实值Chrome安全保护需点击释放)
- ⭐首选CDP单次点击JS取任一autofill输入框坐标→CDP `Input.dispatchMouseEvent` mousePressed一次即可释放→JS读`.value` - ⭐首选CDP单次点击JS取任一autofill输入框坐标→CDP `Input.dispatchMouseEvent` mousePressed一次即可释放→JS读`.value`

View File

@@ -1,6 +1,13 @@
import os, json import os, json, socket as _socket
from datetime import datetime, timedelta from datetime import datetime, timedelta
# 端口锁防止重复启动bind失败时agentmain会直接崩溃退出
# reload时mod.__dict__保留_lock跳过重复绑定
try: _lock
except NameError:
_lock = _socket.socket(_socket.AF_INET, _socket.SOCK_STREAM)
_lock.bind(('127.0.0.1', 45762)); _lock.listen(1)
INTERVAL = 60 INTERVAL = 60
ONCE = False ONCE = False