diff --git a/example/a2a/README.md b/example/a2a/README.md new file mode 100644 index 0000000..3e8c826 --- /dev/null +++ b/example/a2a/README.md @@ -0,0 +1,47 @@ +# a2a · Agent-to-Agent 通信 + +让本 Agent 与其他 Agent **双向互调**:既能对外暴露自己的能力,也能去问别的 Agent。 + +## 两个方向 + +| 方向 | 怎么实现 | +|---|---| +| **入站**(别人问我) | 插件起一个 HTTP 服务端,暴露 `/agent-card`(能力描述)与 `/a2a`(JSON-RPC 入口) | +| **出站**(我问别人) | 提供 `a2a_query` / `a2a_discover` 工具,主动向远端 A2A Agent 发起请求 | + +## HTTP 端点 + +| 路径 | 作用 | +|---|---| +| `GET /agent-card` | 返回 Agent Card:本 Agent 的能力描述,供对方发现 | +| `POST /a2a` | JSON-RPC 2.0 入口,接收对方的任务请求 | + +## 工具 + +| 工具 | 说明 | +|---|---| +| `a2a_a2a_query` | 向另一个 A2A Agent 发查询并取回复 | +| `a2a_a2a_discover` | 取对方的 Agent Card(能力描述) | +| `a2a_a2a_status` | 看本插件运行状态(监听地址、当前配置) | +| `a2a_a2a_configure` | 改配置并自动重启服务(可动态改监听地址) | +| `a2a_a2a_restart` | 重启 HTTP 服务端(连接异常或改配置后用) | + +> 工具名前缀取自插件名(`tp`),按默认 `a2a_` 列出。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `listen` | `127.0.0.1:12000` | 服务端监听地址。**设为空可禁用 HTTP 服务**(只出站、不入站) | + +## 典型用法 + +1. **先发现再调用**:`a2a_discover` 拿对方能力 → 决定要不要发、发什么 → `a2a_query`。 + 跳过 discovery 直接问,容易问出对方不支持的东西。 +2. **只出站**:把 `listen` 设为空,本 Agent 不外露端口,但仍能主动联系别人。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/acp/README.md b/example/acp/README.md new file mode 100644 index 0000000..d8e44c4 --- /dev/null +++ b/example/acp/README.md @@ -0,0 +1,51 @@ +# acp · Agent Client Protocol 通信 + +[ACP](https://agentclientprotocol.com/) 桥接:本 Agent 既能**当服务端**接别人的任务,也能**当客户端**去调别的 ACP Agent。 + +## 两个方向 + +| 角色 | 行为 | +|---|---| +| **服务端** | 在本机起 HTTP 服务,处理 `session/new` / `session/update`,接受其他 Agent 的任务请求 | +| **客户端** | 通过 `acp_query` 向远程 ACP Agent 发 `session/new` 并读回复 | + +## 协议端点 + +- `POST /api/session` —— JSON-RPC,支持 `session/new` 与 `session/update` +- 客户端侧同时兼容**两种服务端**:SSE 型(流式 `session/reply`)与同步 JSON 型 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `acp_acp_query` | 向远程 ACP Agent 发起会话并等待回复,返回最终回答文本 | +| `acp_acp_status` | 查看运行状态与**当前活跃会话数** | +| `acp_acp_configure` | 改监听配置并重启 HTTP 服务 | + +> 工具名前缀取自插件名(`tp`),按默认 `acp_` 列出。 + +`acp_query` 可指向的远端举例(源码注释给的): + +- opencode:`http://127.0.0.1:13000` +- pi bridge:`http://127.0.0.1:12011` +- 回环到自身:`http://127.0.0.1:12001` + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `listen` | `127.0.0.1:12001` | 服务端监听地址。**设为空可禁用 HTTP 服务**(只出站) | + +## 与 a2a 的区别 + +| | a2a | acp | +|---|---|---| +| 面向 | Agent ↔ Agent 对等通信 | 客户端 → Agent 会话(每次一个 session) | +| 会话 | 一问一答 | 有 session 生命周期,可续 | +| 发现 | `/agent-card` | 无(需已知地址) | + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/ai_image/README.md b/example/ai_image/README.md index 181dd43..d458ff1 100644 --- a/example/ai_image/README.md +++ b/example/ai_image/README.md @@ -1,13 +1,34 @@ -# ai_image +# ai_image · 文生图 -ai_image plugin +按文字提示生成图片,下载到本地并返回**文件路径**。 -## Build +## 工具 + +| 工具 | 说明 | +|---|---| +| `ai_image_generate` | 按 prompt 生成图片 | + +返回值是**本地文件路径**(永久,不过期)。要把图给用户看,再用导出的通道 +以 `type=image`、`payload=<该路径>` 发送。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `api_key` | 空 | OpenAI / Stable Diffusion 的 API Key | +| `base_url` | 空 | 自定义 OpenAI 兼容网关(**不带 `/v1` 尾缀**,如 `http://127.0.0.1:8081`)。留空走官方 `https://api.openai.com` | +| `provider` | `openai` | 服务方:`openai` / `stability` | +| `model` | `dall-e-3` | 模型名(如 `dall-e-3`、`sd-xl`) | +| `size` | `1024x1024` | 默认尺寸,也可 `1024x1792` / `1792x1024` | + +## 实现要点 + +- **返回本地路径而不是远端 URL**:远端图床链接会过期,写进记忆就成了悬空指针。 + 下载到本地后路径稳定,可交给媒体存储做内容寻址。 +- 配了 `base_url` 就能指向自建/兼容网关,不必依赖官方接口。 + +## 构建 ```bash hmapdev build ``` - -## Install - -Upload the .hmap file through the Plugin Manager API. diff --git a/example/bili/README.md b/example/bili/README.md new file mode 100644 index 0000000..141b988 --- /dev/null +++ b/example/bili/README.md @@ -0,0 +1,43 @@ +# bili · B站视频下载 + +用 [yt-dlp](https://github.com/yt-dlp/yt-dlp) 把 B 站视频下载到本地。 + +## 前置依赖 + +需要系统里装有 `yt-dlp`: + +```bash +pip install -U yt-dlp # 或 apt install yt-dlp +``` + +## 工具 + +| 工具 | 说明 | +|---|---| +| `bili_video` | 下载 B 站视频;不指定 `format` 时先返回可用清晰度列表,指定后真正下载并返回文件路径 | + +参数: + +| 参数 | 说明 | +|---|---| +| `url` | 视频地址 | +| `format` | 格式 ID。常用:`30112`/`30080`=1080P、`30064`=720P、`30032`=480P、`30016`=360P。不指定则自动选最优 | + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `output_dir` | `/tmp/bili_videos` | 下载目录 | +| `proxy` | 空 | yt-dlp 使用的 HTTP 代理(如 `http://127.0.0.1:7890`)。留空则不设代理 | + +## 实现要点 + +- **`output_dir` 有安全校验**:它是配置项,但会拒绝被配成系统目录,避免 yt-dlp 往任意位置写文件。 +- 两阶段用法:先不传 `format` 拿到清晰度清单(`format_id` + `format_note`),再带上选定的 ID 下载。这样模型不会盲选一个不存在的格式。 +- B 站在部分网络环境下需要代理,见上面的 `proxy`。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/browser/README.md b/example/browser/README.md new file mode 100644 index 0000000..28baaa2 --- /dev/null +++ b/example/browser/README.md @@ -0,0 +1,66 @@ +# browser · 统一浏览器 + +一个插件覆盖三种"访问网页"的能力,从最轻到最重。**按需选层**是这个插件的重点 —— +绝大多数抓取用 HTTP 就够,不该为了一句话启动 Chromium。 + +## 三种能力层 + +| 层 | 工具 | 何时用 | +|---|---|---| +| **搜索** | `browser_search` | 要的是"找到哪些页面",不是页面本身 | +| **quick(纯 HTTP)** | `browser_fetch`(`mode=quick`) | 静态页、API、能直接拿到 HTML | +| **normal(无头渲染)** | `browser_render` / `browser_fetch`(`mode=render`) | JS 渲染的页面,HTTP 拿不到内容 | +| **interactive(CDP)** | `browser_start` + `navigate`/`click`/`type`/`scroll`/`html`/`screenshot` | 需要交互:登录、点按、翻页 | + +`browser_fetch` 的 `mode`: + +- `auto`(默认):先试 HTTP,**遇 403/429 才降级**用 Chromium 渲染 +- `render`:强制 Chromium +- `quick`:纯 HTTP,不降级 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `browser_search` | 网页搜索 | +| `browser_fetch` | 抓取 URL 内容,三种 mode 见上 | +| `browser_render` | 无头 Chromium 渲染并提取文本(normal) | +| `browser_start` | 启动交互式浏览器会话(CDP) | +| `browser_navigate` | 导航到指定 URL | +| `browser_click` | 点击元素 | +| `browser_type` | 输入文本 | +| `browser_scroll` | 滚动页面 | +| `browser_html` | 取当前页 HTML | +| `browser_screenshot` | 截图 | +| `browser_install` | 安装 systemd 托管的共享浏览器后端 | +| `browser_close` | 关闭会话 | + +## 共享浏览器后端 + +`browser_install` 安装 `homeagent-browser.service`(systemd 托管)。 +装上之后**所有 agent 共享同一个 Chromium 实例与登录态**,各自占独立标签页互不干扰 +(同 source 复用自己的标签页)。 + +前提:本机已有 chromium 二进制,没有会提示先装(`apt install chromium` 或等价)。 + +## 实现要点 + +- **搜索用 `cn.bing.com` 而不是 `www.bing.com`**:后者对程序化请求常回 302(同意/重定向页), + 根本拿不到结果块。 +- **标题取 `

` 里的 ``**:直接抓结果块里第一个 `` 会拿到来源行而非标题。 +- **摘要认 `b_lineclamp`**:旧版 Bing 用 `b_caption`,新版已迁走,两套都匹配。 +- **有 SSRF 防护**:见源码 `SSRF` 段,抓取前校验目标地址,避免被诱导访问内网。 + +## 测试 + +```bash +go test -count=1 ./... +``` + +`testdata/bing_cn.html` 是搜索解析的固定样本,用它做离线断言,避免测试依赖真实网络。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/calendar/README.md b/example/calendar/README.md index faeaf45..70dc10c 100644 --- a/example/calendar/README.md +++ b/example/calendar/README.md @@ -1,13 +1,57 @@ -# calendar +# calendar · 日历事件 -calendar plugin +事件管理:支持**重复事件**与**多档提醒**。 -## Build +## 工具 + +| 工具 | 说明 | +|---|---| +| `calendar_event_add` | 添加事件 | +| `calendar_event_list` | 列出即将到来的事件(含日期、时间、重复规则) | +| `calendar_event_update` | 更新事件(**只改传入的字段**;会重置提醒状态) | +| `calendar_event_delete` | 删除事件(连带**该事件及之后的所有重复实例**) | +| `calendar_today` | 今日事件 + 倒计时 | +| `calendar_week` | 本周事件,按天分组 | +| `calendar_month` | 月历网格,带事件标记点 | +| `calendar_search` | 按关键词搜标题 / 地点 / 备注 | + +`calendar_event_add` 的时间格式:`YYYY-MM-DD HH:MM`;只给 `YYYY-MM-DD` 表示全天事件。 + +## 重复规则 + +`repeat` 取值: + +| 值 | 含义 | +|---|---| +| `none` | 不重复 | +| `daily` | 每天 | +| `weekday` | 每个工作日 | +| `weekly` | 每周 | +| `biweekly` | 每两周 | +| `monthly` | 每月 | +| `yearly` | 每年 | +| `lunar_yearly` | **按农历年**(生日、传统节日用) | + +`lunar_yearly` 是刻意加的:农历节日按公历写死会逐年偏移。 + +## 提醒 + +`remind_before` 单位是**分钟**,可给多个、逗号分隔: + +``` +15,60,1440 # 提前 15 分钟 + 1 小时 + 1 天 +0 或留空 # 不提醒 +``` + +到点通过 `InjectInterruptText` 注入提醒,带 `NoMemory: true` —— +提醒是瞬时信号,不是记忆内容。通道 `calendar` 同样声明为 NoMemory。 + +## 存储 + +事件存为 JSON,插件重启后保留。 + +## 构建 ```bash hmapdev build ``` - -## Install - -Upload the .hmap file through the Plugin Manager API. diff --git a/example/editdoc/README.md b/example/editdoc/README.md new file mode 100644 index 0000000..9e4c8d7 --- /dev/null +++ b/example/editdoc/README.md @@ -0,0 +1,49 @@ +# editdoc · Office 文档编辑 + +编辑 `.docx` / `.xlsx` / `.pptx` 内容:查找替换、改单元格、插行。 + +> ⚠️ **版本说明**:本目录是 **v1.0.0**,只有 `edit_document` 一个工具。 +> 线上部署的 v2.0.0(全能办公版,支持新建/读取/转换 docx·xlsx·pptx·md·csv·txt) +> **源码尚未公开**,本文档不描述那些能力。参见 `plugin.json` 的 `version`。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `edit_document` | 编辑文档内容,**编辑后原文件被覆盖** | + +参数: + +| 参数 | 说明 | +|---|---| +| `file` | 文档路径(必填) | +| `operation` | `replace_text`(查找替换)/ `set_cell`(设置单元格)/ `insert_row`(插入行)(必填) | +| `target` | 要查找的文本(`replace_text` 用) | +| `replacement` | 替换为的文本(`replace_text` 用) | +| `sheet` | 工作表名(xlsx 可选) | +| `row` | 行号(`set_cell` / `insert_row` 用) | +| `col` | 列号(`set_cell` 用) | +| `value` | 单元格值(`set_cell` 用) | + +编辑前建议先读一遍内容确认目标文本 —— 查找替换是**全文件覆盖写**,没有撤销。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `script_path` | 空 | `edit_doc.py` 的绝对路径。留空则用插件可执行文件同目录下的 `edit_doc.py` | +| `venv_python` | 空 | 执行 `edit_doc.py` 的 Python 解释器(建议用 venv 里的)。**必须配置,留空会报错** | + +## 工作原理 + +本插件是 Go 写的薄壳:把参数序列化成 JSON,交给 Python 脚本 `edit_doc.py` 执行实际文档操作。 +文档解析依赖 Python 侧的库(python-docx / openpyxl / python-pptx 之类),所以: + +- **需要自备 `edit_doc.py`**:它不在本目录里。 +- 用 `venv_python` 指向装了这些库的解释器,避免污染系统 Python。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/files/README.md b/example/files/README.md new file mode 100644 index 0000000..96c00ab --- /dev/null +++ b/example/files/README.md @@ -0,0 +1,44 @@ +# files · 沙箱文件操作 + +读写与编辑文件,**全部操作限制在沙箱目录内**。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `files_read` | 读文件内容,支持 `offset` / `limit` 读大文件 | +| `files_write` | 写文件,**自动创建父目录** | +| `files_edit` | 按精确字符串替换改文件 | +| `files_ls` | 列目录(目录名带 `/` 后缀) | + +`files_edit` 用 `edits[]` 传多组替换,每组 `{old, new}`: + +- 每个 `old` 必须在**原文件**中**恰好出现一次** —— 不唯一会报错,避免改错地方。 +- 所有替换都针对**原内容**匹配,不要在同一个 `edits` 里写相互重叠的改动。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `dir` | 空 | 允许访问的根目录。留空用默认沙箱(主数据目录下的 `files_sandbox`)。**不建议设为 `/`** | + +## 沙箱实现 + +路径校验不止一次,是两道: + +1. **规范化后判断**:`filepath.Abs` + `filepath.Clean`,再用 `withinSandbox` + 检查结果是否在根目录之下(`/` 作为特例放行)。 +2. **解析符号链接后再判断**:`filepath.EvalSymlinks` 求出真实路径,**再查一次**沙箱。 + +第 2 步是关键:只做第 1 步的话,沙箱内一个指向外部的软链接就能绕过限制 +(`.../sandbox/link -> /etc`)。报错文案也区分了这两种情况 +(`path outside sandbox` vs `path escapes sandbox via symlink`)。 + +对不存在的路径(`write` 会用到),求真实路径时只对已存在的部分做 `EvalSymlinks`, +其余保留为未创建的尾部。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/memo/README.md b/example/memo/README.md new file mode 100644 index 0000000..507ff74 --- /dev/null +++ b/example/memo/README.md @@ -0,0 +1,43 @@ +# memo · 待办与备忘录 + +两类条目,行为**刻意不同**: + +| 类型 | 用途 | 是否主动提醒 | +|---|---|---| +| **待办**(todo) | 有截止概念、需要被催的事 | ✅ 会 | +| **备忘录**(memo) | 纯记事,供以后查阅 | ❌ 不会 | + +分开的理由:把"提醒我"和"记一下"混成一类,要么备忘录天天弹、要么待办被忘掉。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `memo_todo_add` | 添加待办(会被主动提醒) | +| `memo_todo_complete` | 标记待办完成(不再提醒) | +| `memo_todo_list` | 列出未完成待办(含 ID、内容、创建时间) | +| `memo_todo_delete` | 删除待办(含已完成的) | +| `memo_memo_create` | 创建备忘录(纯记事,不提醒) | +| `memo_memo_list` | 列出全部备忘录 | +| `memo_memo_delete` | 删除备忘录 | + +> 工具名前缀取自插件名(`p.tp`),上面按默认的 `memo_` 写法列出。 + +## 提醒机制 + +- 后台 **每 5 分钟**检查一次未完成待办数;有则通过 `InjectInterruptText` 注入一条 + 「注意,你还有 N 条待办未完成,请检查」。 +- 注入带 **`NoMemory: true`** —— 这是定时提醒,不是记忆内容,不该进向量化。 +- 通道声明为 **`NoMemory`**(`RegisterInputChannel(p.name, ChannelDef{NoMemory:true})`), + 理由同上:提醒是瞬时信号。 +- 另有 `StagePreAction` 钩子,在每轮动作前参与。 + +## 存储 + +条目落在数据目录的 `todos.json`,插件重启后仍在。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/music/README.md b/example/music/README.md new file mode 100644 index 0000000..83e9904 --- /dev/null +++ b/example/music/README.md @@ -0,0 +1,30 @@ +# music · 音乐搜索 + +按关键词搜歌、按 ID 查歌词(数据来自网易云音乐公开接口)。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `music_search` | 按关键词搜歌,返回歌曲列表(含歌曲 ID) | +| `music_lyrics` | 按歌曲 ID 取歌词 | + +典型两段式用法:先 `music_search` 拿 ID,再 `music_lyrics` 取词。 + +## 实现要点 + +- 请求打的是 `https://music.163.com/api/...`,并固定带上 `Referer: https://music.163.com/` —— + 该接口对缺少来源头的请求会拒绝。 +- 是**只读**插件:不下载音频、不写本地文件,因此没有需要清理的副作用。 + +## 已知边界 + +- 依赖第三方(网易云)公开接口,其可用性与返回结构不受本插件控制; + 接口变动时可能返回空列表,而不是报错。 +- 仅覆盖"搜索 + 歌词",不含播放地址解析。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/ocr/README.md b/example/ocr/README.md new file mode 100644 index 0000000..a1d444d --- /dev/null +++ b/example/ocr/README.md @@ -0,0 +1,40 @@ +# ocr · 图片文字识别 + +从图片里提取文字(中英文),基于 [Tesseract](https://github.com/tesseract-ocr/tesseract) OCR 引擎。 + +## 前置依赖 + +需要系统里装有 `tesseract` 可执行文件: + +```bash +# Debian/Ubuntu +apt install tesseract-ocr tesseract-ocr-chi-sim +``` + +中文识别需要 `chi_sim` 语言包;缺它时中文会识别成乱码而非报错。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `ocr_ocr_image` | 对图片做 OCR,返回识别文本 | + +参数: + +| 参数 | 说明 | +|---|---| +| `image_url` | 图片的 HTTP/HTTPS 地址(与 `image_data` 二选一) | +| `image_data` | 图片的 base64 数据,**不含** `data:image/...` 前缀(与 `image_url` 二选一) | +| `language` | 识别语言,默认 `chi_sim+eng`;可选 `chi_sim` / `eng` / `chi_sim+eng` | + +## 实现要点 + +- 传入的图先落到临时目录,OCR 完 `defer os.RemoveAll` 清掉,不残留。 +- 调用参数固定 `--psm 3`(全自动页面分割),适合截图与常规排版图片;对单行小图或竖排文本效果会下降。 +- **`Cleaner`**:工具返回的是 JSON(含 `text`、`language` 等字段),进记忆计算前只取 `text` 正文 —— 否则 JSON 结构本身会参与向量化。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/qq/README.md b/example/qq/README.md new file mode 100644 index 0000000..4d99024 --- /dev/null +++ b/example/qq/README.md @@ -0,0 +1,167 @@ +# qq · QQ 消息桥接 + +通过 [NapCat](https://github.com/NapNeko/NapCatQQ) 把 QQ 接成 HomeAgent 的一个 IO 通道: +让 agent 收发 QQ 消息、读群/好友信息、传文件。 + +> ⚠️ 这是**安全敏感**插件:它让外部 QQ 用户能触达 agent 的工具。 +> 本文档的「权限模型」一节请务必读完。 + +## 通道与钩子 + +| 类型 | 名称 | 说明 | +|---|---|---| +| 出站 | `qq` | `CapText` + `CapFile` + `CapImage` + `CapAudio`;发消息/文件给 QQ | +| 入站 | `qq` | `NoMemory: true` + `Cleaner` + `RecallPolicy: None` | + +四个阶段钩子(全部 `StageScopeGlobal`): + +| 钩子 | 作用 | +|---|---| +| `on_input` | 把本轮 QQ 身份**绑到帧上** | +| `before_toolcall` | 权限门:逐个工具判断是否放行 | +| `post_action` | 清掉被拒绝时模型已经吐出的废话 | +| `after_output` | 收尾时清理插件全局身份 | + +## 工具(20 个) + +| 工具 | 说明 | +|---|---| +| `qq_get_message` | 按 `message_id` 取消息正文、发送者、附件 | +| `qq_get_history` | 取群/私聊最近历史消息 | +| `qq_list_chats` | 会话列表(按最新消息排序,带未读数与摘要) | +| `qq_mark_read` | 把某会话未读数清零 | +| `qq_send_file` | 发文件/图片(私聊或群聊) | +| `qq_get_groups` | 群列表,可按关键词搜 | +| `qq_get_friends` | 好友列表,可按昵称/备注搜 | +| `qq_get_recent_contacts` | 最近有消息的联系人与群 | +| `qq_resolve_name` / `qq_resolve_nickname` | 名字 ↔ QQ 号互查 | +| `qq_get_group_member_info` | 群成员信息 | +| `qq_group_manage` | 群综合管理(见下) | +| `qq_friend_action` | 好友操作 | +| `qq_get_group_files` | 群文件列表 | +| `qq_download_file` / `qq_upload_group_file` / `qq_get_download_tasks` | 文件传输与任务 | +| `qq_read_document` | 读 QQ 传来的文档 | +| `qq_video_download` | 下载视频 | +| `qq_send_like` | 点赞 | + +`qq_group_manage` 一个工具承载多种操作(`command` 参数): +`leave` 退群、`kick` 踢人、`ban`/`unban` 禁言解禁、`rename` 改名、`mute-all` 全员禁言、 +`set-card` 设名片、`set-admin` 设管理、`set-title` 设头衔、`member-list`、`group-info`、 +`msg-history`、`recall` 撤回、`pin-msg` 精华、`list-files`、`pending-requests`、`folder-create` 等。 + +**破坏性操作**(`leave`/`kick`/`ban`/`unban`/`rename`/`mute-all`/`set-card`/`set-admin`/ +`set-title`/`recall`/`pin-msg`/`folder-create`)**必须显式传 `confirm: true`**。 + +## 权限模型 + +这是本插件最重要的部分。 + +### 身份分级 + +| 身份 | 权限 | +|---|---| +| **owner**(Bot 所有者) | 私聊或群聊均**完整放行** | +| **普通 QQ 用户** | 只放行白名单内的工具 | + +### 身份必须「绑帧」,不能只存插件全局 + +源码注释记录了两个真实故障,这就是绑帧的原因: + +1. **中断抢占后身份丢失**:中断会抢占当前轮、把现场压栈。中断轮收尾时 + `after_output` 会清空插件**全局**身份;随后外层被恢复(`resumeTask` 复用同一帧、 + **不重跑 `on_input`**)。若身份只存全局,恢复后的外层就是"无身份", + `before_toolcall` 在 `!auth.active` 处直接返回 —— **整个权限门失效**。 +2. **运行中到达的消息改写身份**:新消息会调 `activateAuthContext` 改写全局身份, + 把**正在跑的那一轮**换成另一方的身份(换高=越权,换低=误拒)。 + +帧上的 `Extra` 随帧一起压栈/恢复,正好是"这一轮的身份"。 + +### 合并取最小权限 + +多来源被内核合并到同一推理时,权限**取交集**而非并集: + +```go +p.auth.owner = p.auth.owner && next.owner +``` + +防的是"非所有者请求 + 随后所有者消息"意外把前一个请求提权。 + +### 硬私有工具 + +非所有者**一律拒绝**(不看白名单),按前缀拦截: +`calendar_`、`email_`、`mail_`、`agentmail_`、`memory_`、`knowledge_`、`device_`、 +`devicectl_`、`terminal_`、`shell_`、`command_`、`exec_`、`filesystem_`、`agentfs_`、 +`config_`、`settings_`、`plugin_`、`plugins_`, +外加 `read_file`、`write_file`、`edit_file`、`delete_file`、`list_files`、`run_command`、 +`homeagent_config`、`homeagent_restart`、`output_send__email`、`output_send__mail`。 + +### 参数与会话一致性校验 + +光看工具名不够,还要检查**参数指向的会话与当前身份一致**,否则可以拿别人的 +`message_id` 去读别处内容: + +- 带 `message_id` 的工具:该 ID 必须属于当前 QQ 会话(`lookupMsgRef` 校验 peer 与群/私聊类型)。 +- `get_group_member_info` / `get_group_files`:`group_id` 必须是**当前群**。 + +### 频率与重复控制 + +| 键 | 作用 | +|---|---| +| `max_qq_tool_calls` | 单轮工具调用上限 | +| `max_qq_output_calls` | 单轮输出调用上限 | +| `max_duplicate_qq_send` | 重复发送上限,防刷屏 | +| `batch_window_ms` / `batch_max_ms` | 消息合批窗口 | + +被拒时只允许**发一次权鉴说明**,之后锁止本轮剩余工具调用 +(`clearDeniedResponse` 再把模型已写出的内容清掉,避免输出里带一堆"我不能…")。 + +## 配置项 + +### 连接 + +| 键 | 默认 | 说明 | +|---|---|---| +| `napcat_url` | — | NapCat 服务地址 | +| `listen` | — | 本插件 HTTP 监听地址 | +| `webhook_token` | — | webhook 校验令牌 | + +### 身份与准入 + +| 键 | 默认 | 说明 | +|---|---|---| +| `owner` | 空 | Bot 所有者 QQ 列表(逗号分隔),拥有完整权限 | +| `admin` | 空 | **旧配置名**,`owner` 为空时作为所有者列表(兼容用) | +| `dm_policy` | `open` | 私聊策略:`open` / `allowlist` / `disabled` | +| `allow_from` | 空 | 私聊白名单(QQ 号,逗号分隔) | +| `group_policy` | `open` | 群聊策略:`open` / `allowlist` / `disabled` | +| `group_allow_from` | 空 | 群白名单 | +| `private_tool_allowlist` | 空 | 私聊下非所有者可用的工具 | +| `group_tool_allowlists` | 空 | 按群配置的工具白名单 | + +### 文件与转发 + +| 键 | 说明 | +|---|---| +| `files_dir` | 本地文件目录 | +| `remote_dir` | 供 NapCat 容器访问的目录(发文件前先复制到这里) | +| `agentfs_dir` | agent 文件系统目录 | +| `forward_rules` | JSON 数组,每项 `{group_id,host,port,password,template}`:匹配的群消息经 **RCON** 转发到 Minecraft;`template` 支持 `{nickname}` / `{message}` 占位 | + +## 部署前提 + +需要**自行部署 NapCat**(本插件不含 QQ 协议实现,只是 NapCat 的客户端)。 +发文件前会先把文件复制到 `remote_dir`,因为 NapCat 通常在容器里,看不到宿主任意路径。 + +## 测试 + +```bash +go test -count=1 -race ./... +``` + +含权限门与绑帧的回归测试。改动权限相关代码后务必跑 `-race`。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/recoverydiag/README.md b/example/recoverydiag/README.md new file mode 100644 index 0000000..bbf41b0 --- /dev/null +++ b/example/recoverydiag/README.md @@ -0,0 +1,53 @@ +# recoverydiag · 快速检查 / 崩溃取证 + +给 guard 与 failback 用的**确定性诊断工具集**。 + +设计基调(源码原话):**返回结论而非原文,确定性检出,不消耗 LLM token。** +崩溃后最忌讳的是把几万行日志塞进模型上下文让它"看看",那既慢又不可靠 —— +这里每个工具都在本地算出结论再返回。 + +## 工具 + +| 工具 | 说明 | +|---|---| +| `recoverydiag_diag_triage` | 快速分诊:按退出码 / 信号 / 存活状态粗分类别(进程死亡 vs 配置类不可达 vs 正常) | +| `recoverydiag_diag_db` | config.db 完整性(`PRAGMA integrity_check`)+ LLM 源解析校验(`core.llm.sources.*` 必备字段),逐项 ok/fail | +| `recoverydiag_diag_log_scan` | 在日志目录的时间窗内统计已知错误签名(panic / OOM / 网络不可达 / provider 失败 / sql / 致命)出现次数,给出主导结论 | +| `recoverydiag_diag_delta` | 对比 baseline(上次 good 快照/目录)与现状,列出 created / modified / deleted 清单与摘要,判定"改了什么" | +| `recoverydiag_diag_loc` | 综合前四项结论,按**因果强度正交排序**定位根因并给出推荐恢复动作 | + +## 用法顺序 + +``` +diag_triage → diag_db → diag_log_scan → diag_delta → diag_loc + (各自独立,可只跑需要的) (要传前四项的结论) +``` + +`diag_loc` 需要你把它余下的结论**作为参数传进去**(`triage` / `db` / `log` / `delta` 四个对象), +它不自己去调 —— 这样它只做归因,不重复执行。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `db_check_cmd` | `auto` | `diag_db` 用的 `sqlite3` 命令。留空=auto:可用时用 sqlite3,缺失则回退读内核 Settings | +| `recovery_kb_dir` | 空 | `diag_loc` 结论 JSON 的落盘目录。缺省 `/recovery_kb` | + +## 不注册通道与钩子 + +本插件**只提供工具**,不订阅输入、不挂阶段钩子 —— 它是被 guard 或 agent 主动调用的, +不做后台干预。 + +## 测试 + +```bash +go test -count=1 ./... +``` + +`diag_test.go` 覆盖各诊断项的判定逻辑。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/rss/README.md b/example/rss/README.md index 3ba022c..5241c1e 100644 --- a/example/rss/README.md +++ b/example/rss/README.md @@ -1,13 +1,44 @@ -# rss +# rss · RSS/Atom 订阅监控 -rss plugin +订阅 RSS/Atom 源,**有新文章时主动通知** agent(不必每轮去问)。 -## Build +## 工具 + +| 工具 | 说明 | +|---|---| +| `rss_subscribe` | 订阅一个 RSS/Atom 源 | +| `rss_unsubscribe` | 取消订阅 | +| `rss_list` | 列出全部订阅 | +| `rss_check_now` | 立即检查所有源(不等轮询周期) | + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `poll_interval` | `30` | 默认轮询间隔(**分钟**) | + +订阅时可对单个源覆盖间隔。 + +## 通知机制 + +- 后台按各自间隔轮询(默认 30 分钟)。 +- 发现新条目时通过 `InjectInterruptText` 注入,格式形如 + `📡 <源标题> () — N 篇新文章:` 后跟条目。 +- 注入带 **`NoMemory: true`**,通道 `rss` 也声明为 `NoMemory` —— + 订阅推送是信号不是知识,不该进向量化挤掉别的记忆。 + +## 实现要点 + +- **订阅时就记下全部已有 GUID**:`handleSubscribe` 会把抓取到的历史条目 + 一次性标为 `seenGUIDs`,所以**订阅一个源不会把它的历史文章全部推送一遍**。 + 只有订阅之后新出现的条目才通知。这是避免刷屏的关键。 +- **去重按「源 URL + GUID」**:不同源可能用相同 GUID,只用 GUID 会互相误判。 + GUID 缺失时回退用 `link`;两者都缺则跳过该条。 +- `seenGUIDs` 有清理逻辑,不会无限增长。 +- 解析用 [gofeed](https://github.com/mmcdole/gofeed)(`v1.4.0`)。 + +## 构建 ```bash hmapdev build ``` - -## Install - -Upload the .hmap file through the Plugin Manager API. diff --git a/example/sanitizer/README.md b/example/sanitizer/README.md new file mode 100644 index 0000000..1f4282a --- /dev/null +++ b/example/sanitizer/README.md @@ -0,0 +1,48 @@ +# sanitizer · 文本清洗 + +**不注册任何工具**,只挂三个阶段钩子,在 Agent 全链路上洗掉两类污染: + +1. **坏字节**:坏 UTF-8、`U+FFFD`(替换符)、ANSI 转义序列 +2. **思维泄漏**:LLM 输出里残留的工具调用标记 + +## 为什么需要它 + +坏字节会**被 LLM 复读**。一次工具返回乱码(比如源码里带 ANSI 颜色码、或二进制片段被当文本读出来), +这些字节会进上下文,之后模型每次生成都可能把它抄一遍 —— 越滚越脏。 +在每个入口洗掉,比事后清理便宜得多。 + +思维泄漏则是另一种:模型有时把 `...` 这类内部标记直接写进正文, +用户就看到一堆不该出现的 XML。 + +## 挂载的三个阶段 + +| 阶段 | 处理对象 | 作用 | +|---|---|---| +| `on_input` | `ctx.RawMessage` | 洗用户输入,脏字节不进后续链路 | +| `after_toolcall` | `ctx.ToolResults` | 洗工具结果,**坏字节不进 LLM 上下文** | +| `post_action` | `ctx.LLMText` | 洗模型输出:先清思维泄漏,再清乱码 | + +每次有改动都打一行日志(`cleaned N bytes`),便于确认它真的在工作而不是静默失败。 + +## 识别哪些泄漏形态 + +按正则匹配多种标记写法,覆盖不同模型家族的习惯: + +- ``、``、`` +- `` +- 上述标记包在 ```xml / ```json 代码块里的形态 +- 中文括号变体:`【tool_call】…【/tool_call】` + +## 实现要点 + +- 依赖 **ABI v2 的 stage 写回能力**:插件对 `StageContext` 的修改会同步回内核。 + 在 v1 上改了不生效。 +- 读写 `StageContext` 时按约定加 `ctx.Lock()`。 + +## 构建 + +```bash +go build -buildmode=plugin -o sanitizer.so . +``` + +或经 `hmapdev build` 打包为 `.hmap`。 diff --git a/example/vanblog/README.md b/example/vanblog/README.md new file mode 100644 index 0000000..03e7a59 --- /dev/null +++ b/example/vanblog/README.md @@ -0,0 +1,77 @@ +# vanblog · VanBlog 博客管理 + +用管理 API 操作 [VanBlog](https://vanblog.mereith.com/) 开源博客系统: +文章增删改查、分类标签、草稿发布、备份导出等。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `url` | `https://blog.jianfgit.xyz` | VanBlog 站点基地址 | +| `token` | 空 | 管理员 API Token(长期令牌,从后台「Token 管理」创建) | +| `reset_token` | 空 | 用于 `auth/restore` 重置管理员密码的**特殊** Token | + +`token` 与 `reset_token` 都是 `password` 类型(界面遮蔽)。 + +## 工具(28 个) + +### 文章 + +| 工具 | 说明 | +|---|---| +| `vanblog_list_articles` | 列文章,支持分页与搜索 | +| `vanblog_get_article` | 取单篇完整内容 | +| `vanblog_create_article` | 新建(`title` 与 `category` 必填) | +| `vanblog_update_article` | 更新(**只传要改的字段**) | +| `vanblog_delete_article` | 删除(**软删除**) | +| `vanblog_search_articles` | 按链接搜索文章 | + +### 草稿 + +`vanblog_manage_drafts`:`list` / `get` / `create` / `update` / `delete` / **`publish`** + +### 内容组织 + +| 工具 | 命令 | +|---|---| +| `vanblog_manage_categories` | `list` / `get` / `create` / `update` / `delete` | +| `vanblog_manage_tags` | `list` / `get` / `rename` / `delete` | + +### 站点与运维 + +| 工具 | 说明 | +|---|---| +| `vanblog_manage_site` / `_settings` / `_menu` / `_social` / `_links` | 站点配置类 | +| `vanblog_manage_about` / `_pages` | 关于页与自定义页面 | +| `vanblog_manage_images` | 图床管理 | +| `vanblog_manage_rewards` | 赞赏配置 | +| `vanblog_manage_backup` | 备份 | +| `vanblog_manage_caddy` | Caddy 配置 | +| `vanblog_manage_isr` | ISR 增量静态渲染 | +| `vanblog_manage_pipelines` | 流水线 | +| `vanblog_manage_collaborators` | 协作者 | +| `vanblog_manage_tokens` | Token 管理 | +| `vanblog_get_analysis` / `_logs` / `_meta` | 统计、日志、元信息 | +| `vanblog_auth` | 认证相关(含 `restore` 重置密码) | + +> 工具名前缀取自插件名(`tp`),上面按默认 `vanblog_` 列出。 + +## 实现要点 + +- 走的是 VanBlog 的管理 API(`/api/admin/...`),所以必须配 **admin token**, + 不是前台只读接口。 +- `update_article` 是**部分更新**:只传想改的字段,没传的保持不变。 + (不要为了改标题而把正文一起传一遍。) +- `delete_article` 是**软删除**,内容仍在,可在后台恢复。 +- 早期版本把 token 放在内核配置(`plugin.vanblog.token`)里, + 现在会**自动迁移**到插件配置,迁移后清空内核侧取值。 + +## 前置 + +需要一个可访问的 VanBlog 实例,并在后台创建一个长期 Token。 + +## 构建 + +```bash +hmapdev build +``` diff --git a/example/weather/README.md b/example/weather/README.md index 2d6ffe2..6745a40 100644 --- a/example/weather/README.md +++ b/example/weather/README.md @@ -1,13 +1,33 @@ -# weather +# weather · 天气查询 -weather plugin +给 agent 补上天气查询能力(基于 [wttr.in](https://wttr.in),无需 API Key)。 -## Build +## 工具 + +| 工具 | 说明 | +|---|---| +| `weather_current` | 查询某城市当前天气 | +| `weather_forecast` | 查询未来几天预报 | +| `weather_set_location` | 设置默认城市 | + +`weather_current` / `weather_forecast` 都接受 `location`(城市名,如 `Beijing`、`Shanghai`); +省略时用配置里的默认城市。`weather_current` 另有 `units`:`metric`(摄氏,默认)或 `imperial`(华氏)。 + +## 配置项 + +| 键 | 默认 | 说明 | +|---|---|---| +| `default_location` | 空 | 默认城市名。留空则每次调用都必须传 `location` | + +## 实现要点 + +- **`NoMemory: true`**:天气是外部实时数据,对记忆计算无长期价值,跳过向量化与关键词提取(原文仍保留在对话里)。 +- **`Cleaner`**:输出参与记忆计算前先过滤,只保留摘要行 —— 天气查询会反复出现,全文进记忆会挤占上下文预算,而"上周三北京多少度"通常并不需要召回。 + +## 构建 ```bash hmapdev build ``` -## Install - -Upload the .hmap file through the Plugin Manager API. +产出 `.hmap` 后经 Plugin Manager API 安装。