refactor: rewrite GETTING_STARTED, cleanup README, add hub.pyw, remove QUICK_START.pdf
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -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
|
||||||
|
|||||||
@@ -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 接入聊天平台后,你可以:
|
你不需要管理这些 Skill,Agent 会自动处理。使用时间越长,积累的技能越多,最终形成一棵完全属于你的专属技能树。
|
||||||
- 随时随地通过手机控制你的电脑
|
|
||||||
- 多人协作使用同一个 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 不是一个预设好的工具,而是一个会随着你的使用不断进化的伙伴。使用时间越长,它就越懂你,越强大。
|
|
||||||
BIN
QUICK_START.pdf
BIN
QUICK_START.pdf
Binary file not shown.
132
README.md
132
README.md
@@ -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)。
|
|
||||||
|
|
||||||
#### 方法三:Android(Termux)
|
|
||||||
|
|
||||||
```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 |
|
||||||
|
|||||||
@@ -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. **唤醒 Agent(CLI 最小启动)**
|
|
||||||
- 运行 `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
257
hub.pyw
Normal 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()
|
||||||
10
launch.pyw
10
launch.pyw
@@ -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)
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
- ⚠tabId:CDP默认sender.tab.id(当前注入页),跨tab需显式tabId或先batch内tabs查
|
- ⚠tabId:CDP默认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即可操作后台标签页
|
||||||
- ⭐绕过isTrusted:CDP派发的Input事件是浏览器原生级别
|
- ⭐绕过isTrusted:CDP派发的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`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user