# DeepSeek Agent 文件操作工具

## 项目简介

用 Tampermonkey 用户脚本把 **chat.deepseek.com 的网页 AI** 和 **本地小模型 + 本地工具** 接起来：
网页 AI 负责思考、拆解任务，本地小模型只负责按它的指令调用工具。

- 网页 AI = **小马**（提示词见 `prompt-web.md`）
- 本地小模型 = **小牛**（提示词见 `prompt_local_llm.md`），只做“敲一条命令的手”
- 通信全部走 WebSocket（`ws://localhost:8765`），没有 HTTP/Flask 接口

## 工作流程

```text
用户 → 网页 AI(小马) → 回复里带 "@小牛 ..." 的任务描述（HTML）
                          ↓
             Tampermonkey 脚本监听最后一条 AI 消息
                          ↓  正文静默 2~4 秒（按长度自适应），判定回复完毕
                   WebSocket 发给 agent_executor.py
                          ↓  按 "@小牛" / "@用户" 分段，只把 "@小牛" 的段落转成纯文本
                转发给本地 LLM（OpenAI 兼容接口）
                          ↓
                    返回 tool_calls → 逐个执行工具
                          ↓  单轮：结果直接拼成文本返回，不再回传本地模型
             "小牛：<工具结果>" 显示在右下角卡片里（不碰输入框）
                          ↓  用户点「直接发送」→ 自动写入输入框并点发送
                             也可点「填充」先改再自己发，或「复制」后粘贴
                             （勾选了「自动发送」时跳过这一步，结果直接发回）
                 网页 AI 读结果 → 决定下一步 / 回复 "@用户"
```

把鼠标移到「停止」按钮上会弹出一个小面板，勾选「自动发送」后，后端结果一返回就自动发回网页 AI；
不勾选时仍然由人点卡片上的按钮。该开关只在当前页面会话内有效，刷新后默认关闭。

## 组件

| 文件 | 作用 |
| --- | --- |
| `Tampermonkey.js` | 悬浮按钮、悬停「停止」的自动发送开关、WebSocket 客户端、DOM 监听、结果卡片（直接发送 / 填充 / 复制 / 关闭） |
| `agent_executor.py` | WebSocket 服务（:8765），消息分发与 Agent 单轮循环 |
| `toolUtil.py` | 工具实现、工具 schema、审计日志 |
| `prompt.md` | 网页 AI（小马）的 system prompt |
| `prompt_local_llm.md` | 本地小模型（小牛）的 system prompt |

## 工具清单

文件：`read_file`、`write_file`（可追加，自动去重）、`list_directory`、`delete_file`、
`delete_directory`、`search_files`（glob）、`grep`（正则）
执行：`execute_shell`、`python_exec`
网络：`http_request`
其它：`calculate`、`get_current_time`、`memory_save`、`memory_recall`、`git_info`

所有调用都会往 `<工作目录>/audit.log` 追加一行 JSON（超过 10MB 轮转一次）。
记忆落在 `<工作目录>/agent_memory.txt`。

## 运行

先按自己的 llama-server 填 `.env`（已被 `.gitignore` 忽略，不会进仓库）：

```dotenv
AGENT_LLAMA_HOST=http://192.168.1.46:9931
AGENT_LLAMA_API_KEY=sk-...                                # 与 llama-server 的 --api-key 一致
AGENT_LLAMA_MODEL=Huihui-NeoHorse-1-4B-abliterated-Q5_K   # 必须与 /v1/models 的 id 完全一致
```

```bash
docker compose up -d
```

容器内安装 `requests`、`websockets` 后启动服务；`./work` 挂载为 `/work`（工具默认工作目录），
仓库本身挂载为 `/app`。`docker compose` 会自动读取同目录的 `.env`。

可用环境变量（compose 里都有默认值，`.env` 可覆盖）：

| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `AGENT_WS_HOST` | `0.0.0.0` | WebSocket 监听地址 |
| `AGENT_WS_PORT` | `8765` | WebSocket 端口 |
| `AGENT_WORK_DIR` | `/work` | 工具默认工作目录 |
| `AGENT_LLAMA_HOST` | `http://192.168.1.46:9931` | llama-server 地址 |
| `AGENT_LLAMA_API_KEY` | `sk-no-key-required` | 与 `--api-key` 一致，否则 401 |
| `AGENT_LLAMA_MODEL` | `Huihui-NeoHorse-1-4B-abliterated-Q5_K` | 用 `--models-dir` 起服务时，必须和 `/v1/models` 的 id 一致，否则 400 |
| `AGENT_LLAMA_TIMEOUT` | `600` | 单次模型请求超时（秒） |

## 发布与更新

脚本用 Tampermonkey 的 `@updateURL` 自动检查新版本：

1. 把 `Tampermonkey.js` 放到能直接访问的地址（GitHub raw、自己的静态站、gist 都行）；
2. 把文件头部的 `@updateURL`、`@downloadURL` 换成这个地址（现在是 `example.com` 占位）；
3. 每次改完脚本把 `@version` 加一（当前 `11.3`），Tampermonkey 下次检查时会提示更新，
   也可以在扩展面板里手动「检查用户脚本更新」。

更新地址必须返回**带完整元数据头**的 `.user.js`（也就是这个文件原样），不能是 HTML 页面或跳转链接。

## 排错

| 现象 | 原因 |
| --- | --- |
| `LLM 401: Invalid API Key` | `AGENT_LLAMA_API_KEY` 和 llama-server 的 `--api-key` 不一致 |
| `LLM 400: model 'xxx' not found` | `AGENT_LLAMA_MODEL` 和 `/v1/models` 的 id 不一致（路由模式按名字选模型） |
| 一直是 `skipped 非小牛的消息` | 网页 AI 的回复里没有 `@小牛`，检查 `prompt.md` 是否成功发送 |
| 小牛乱调工具（比如让它建文件却跑了 `pwd`） | 模型能力问题，把指令写得更死板、一次只给一个动作 |

## 注意

- **没有沙箱**：路径、命令、网络都不做限制。工具能读写宿主机上进程有权限的任何文件，
  `execute_shell` 等价于直接拿到 shell。只在本机、只接自己信任的模型使用。
- 容器的 `/app` 就是本仓库（可写），也就是说模型可以改这个项目自身的代码。
- `docker-compose.yaml` 把 8765 映射到 `0.0.0.0` 且服务无鉴权。同网段任何人都能连上来执行命令，
  只想本机用的话改成 `127.0.0.1:8765:8765`。
- 单轮工具模式是刻意设计：本地小模型没有记忆、不做重试和决策，多轮 loop 只会放大它的不确定性，
  判断和重试都由网页端小马负责。
- 工具结果默认**不会自动写进输入框**：只在右下角卡片展示。点「直接发送」写入输入框并点发送，
  点「填充」只写入不发送，点「复制」只进剪贴板。
- 「自动发送」是显式开关：必须把鼠标移到「停止」按钮上勾选才生效，刷新页面后重置为关闭；
  开启后结果会立刻走一遍「直接发送」，网页 AI 会持续往下推进，注意它可能自己连跑多步。
- 整条链路里只有“新会话发送提示词”和用户点「直接发送」/「填充」/勾选「自动发送」会动输入框，
  其余每一步都由人按键推进。
