Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ The tool supports a `brief` boolean parameter (`default: false`). When `brief: t
### `group.get_avatar` — fetch user avatar
`group.get_avatar` accepts `user_id` (required) and optional `size` (40, 100, 140, 640, default 100). It downloads the QQ avatar and registers it as an attachment, returning an `<attachment uid="..."/>` tag that can be embedded in messages.

### OneBot local file transport
`[onebot].file_send_mode` selects `local` (default for compatibility, including older configs without this field or an environment override), `url`, or `stream`; URL and Stream require explicit selection. `file_send_host` defaults to `127.0.0.1` and is used only for URL delivery. Both hot reload per logical delivery snapshot. Keep local source paths in business tools, attachment registration and history; `OneBotClient` prepares a separate wire request, including nested forward media. URL mode uses the running Runtime port and per-file 16-minute tokens/copies; Stream requires the NapCat extension, uses 64 KiB chunks and a separate completion request with SHA-256 verification. Stream/URL preparation plus send/fallback share 8 minutes excluding the Stream queue. Preparation errors must not mark delivery or trigger file-segment fallback; uncertain delivery must not be retried. Never log chunk data/tokens, re-interpret completed NapCat paths on the Bot, or call global `clean_stream_temp_file`. The upstream merge and existing attachment registration may still buffer whole files. See [deployment](docs/deployment.md) and [configuration](docs/configuration.md).

### Unified attachment tag
Use `<attachment uid="..."/>` for both images and files. The legacy `<pic uid="..."/>` tag is still supported for backward compatibility but `attachment` is the recommended unified syntax. The system distinguishes image vs file based on the UID prefix (`pic_`/`file_`).
Remote attachments are cached only up to `[attachments].remote_download_max_size_mb`; larger items, or all remote items when the value is `0`, are registered as URL references with `source_ref` instead of downloaded file content.
Expand Down
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## v3.14.0 OneBot 本地文件三模式传输

本版本为 Bot 本地文件新增统一传输层,支持 `local`、`url`、`stream` 三种发送方式并按投递快照热更新;默认保持 `local` 兼容旧部署,跨文件系统发送可显式启用 Runtime 临时链接或 NapCat Stream 分块上传。

- 新增 `[onebot].file_send_mode`(`local` / `url` / `stream`)与 `[onebot].file_send_host`,环境变量为 `ONEBOT_FILE_SEND_MODE` / `ONEBOT_FILE_SEND_HOST`,WebUI 配置表单提供三种模式的下拉选择。两项均支持热更新,每次逻辑投递开始时取得独立快照,排队中与进行中的投递保持旧值。默认 `local`,旧配置及未显式指定模式的环境保持原有发送行为,`url` 和 `stream` 必须显式启用;`file_send_host` 仅用于 URL 模式,接受 IPv4、IPv6 或域名。
- QQ 本地图片、语音、视频、文件与嵌套合并转发中的媒体统一经过新传输层,覆盖 CQ 字符串与消息段数组,文件消息段缺少 `name` 时自动补全文件名;已有 HTTP/HTTPS URL、Base64 和协议端资源标识原样通过,展示文件名、附件 UID 与历史来源保持不变。
- `url` 模式复用 Runtime HTTP 监听,将本地文件复制为独立临时副本,新增 `GET` / `HEAD` `/api/v1/onebot/files/{file_id}?token=...` 路由:每个文件独立随机令牌、有效期 16 分钟、支持 Range 与重复读取;令牌只授权该文件,不能替代 `X-Undefined-API-Key` 调用其他 Runtime 接口;访问日志不记录查询串,正常停止清理本实例缓存,启动只回收本模块命名且已过期的遗留副本。删除源文件或切换发送模式不会提前使有效链接失效。
- `stream` 模式通过既有 OneBot WebSocket 按 64 KiB 分块上传并逐块等待确认,完成后独立请求并校验协议端路径、大小与 SHA-256;同一 Bot 的 Stream 文件投递串行,纯文本不等待上传锁;失败只重置未完成的 Stream,不调用清空临时目录的接口;不支持零字节文件。协议端不支持扩展时明确报错并提示改用 `local` 或 `url`,不会静默回退。
- 文件准备、实际发送与明确失败后的文件消息段回退共用 8 分钟预算,排队不计时;文件准备失败不计作已发送、不触发回退,也不会自动切换模式或重试上传。投递已发出但等待超时、被取消或连接中断时按结果未确认处理,禁止自动重发;`send_forward_msg` / `send_private_forward_msg` 纳入防重投递动作。
- 文件上传失败的回退收敛到统一调用入口:仅协议端明确拒绝时才改用文件消息段,且回退请求同样经过传输层准备;`FileTransferError` 不再被私聊与群临时会话回退、附件派发和 Bilibili 发送链路吞掉,消息类工具把包含模式与阶段的可展示说明直接反馈给模型。
- 加固日志与连接脱敏。WebSocket 客户端不再输出未经脱敏的 DEBUG 握手与原始帧,连接错误与 API 失败信息经脱敏后记录,新增 `chunk_data` 脱敏规则并修复脱敏替换未正确回填捕获组的问题;无法解析的消息只记录长度,接收循环停止时统一让挂起的请求失败退出,不再悬挂。

---

## v3.13.3 回复精简、群活跃度统计与图片预览修复

本版本减少对话中的重复解释,明确群成员活跃度统计的含义与数据范围,并修复 Chat 图片预览的键盘和焦点交互。
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-chat/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-chat",
"private": true,
"version": "3.13.3",
"version": "3.14.0",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_chat"
version = "3.13.3"
version = "3.14.0"
description = "Undefined native chat client"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-chat/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Chat",
"version": "3.13.3",
"version": "3.14.0",
"identifier": "com.undefined.chat",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
4 changes: 2 additions & 2 deletions apps/undefined-console/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "undefined-console",
"private": true,
"version": "3.13.3",
"version": "3.14.0",
"type": "module",
"scripts": {
"tauri": "tauri",
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "undefined_console"
version = "3.13.3"
version = "3.14.0"
description = "Undefined cross-platform management console"
authors = ["Undefined contributors"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion apps/undefined-console/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Undefined Console",
"version": "3.13.3",
"version": "3.14.0",
"identifier": "com.undefined.console",
"build": {
"beforeDevCommand": "npm run dev",
Expand Down
7 changes: 7 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ ws_url = "ws://127.0.0.1:3001"
# en: Access token (optional).
token = ""

# zh: 本地文件发送方式:local(共享路径,默认,兼容旧部署)/ url(Runtime 临时链接)/ stream(NapCat Stream API)。支持热更新。
# en: Local file transport: local (shared paths, default for compatibility), url (temporary Runtime links), or stream (NapCat Stream API). Hot reload supported.
file_send_mode = "local"
# zh: 仅 URL 模式使用:OneBot 可访问的 Runtime 主机地址,支持 IPv4、IPv6 或域名,不包含协议、端口或路径;端口使用 Runtime 实际监听端口。
# en: URL mode only: Runtime host reachable by OneBot, as IPv4, IPv6 or a domain without scheme, port or path. Uses the actual Runtime listening port.
file_send_host = "127.0.0.1"

[models]
# zh: 对话模型配置(主模型,处理每一条消息)。
# en: Chat model config (the main model, processing each message).
Expand Down
17 changes: 16 additions & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,20 @@ model_name = "gpt-4o-mini"
|---|---:|---|---|
| `ws_url` | `""` | OneBot WebSocket 地址 | 模板示例通常写 `ws://127.0.0.1:3001`;严格模式必填 |
| `token` | `""` | OneBot token | 同时用于 URL 参数与 `Authorization` 头 |
| `file_send_mode` | `"local"` | Bot 本地文件发送方式:`local` / `url` / `stream` | 缺省或空值用默认值,兼容旧部署;去除首尾空白并转小写;非法非空值报配置错误 |
| `file_send_host` | `"127.0.0.1"` | URL 模式传给 OneBot 的 Runtime 下载主机 | IPv4、IPv6 或域名,不包含协议、端口或路径;缺省或空值用默认值 |

`onebot.*` 变更需要重启进程才能生效。
`onebot.ws_url` / `onebot.token` 变更需要重启进程。`file_send_mode` / `file_send_host` 支持热更新:每次逻辑投递开始时取得独立快照,排队及进行中的投递保持旧值,后续投递使用新值。环境变量为 `ONEBOT_FILE_SEND_MODE` / `ONEBOT_FILE_SEND_HOST`,沿用 TOML 优先、环境变量补缺的规则。

- `local`(默认):保留原有路径或 `file://` 格式,协议端必须能读取该路径。
- `url`:复用 Runtime HTTP 监听,将本地文件复制为临时下载资源。URL 使用 `file_send_host` 与 **实际生效的监听端口**,不会使用尚未重启生效的新 `api.port`。需要 `[api].enabled = true` 且协议端能访问该监听;默认 `127.0.0.1` 指协议端自身的回环地址,跨容器时应填写其可达的 Bot 主机或域名,并配置可达的 `[api].host`。
- `stream`:通过 NapCat `upload_file_stream` 扩展按 64 KiB 分块上传,校验完成后使用协议端路径发送。协议端不支持时明确报错,需手动选择其他模式。零字节文件不支持此模式。

这些选项只影响 Bot 本地文件;已有 HTTP/HTTPS URL、Base64 和协议端资源标识保持原样,展示文件名、附件 UID 和历史来源不变。旧配置未包含新字段且未通过环境变量指定模式时继续采用 `local`,保持原有发送行为;`url` 和 `stream` 需要显式启用。不能假定所有 OneBot 实现或 Lagrange.Core 都支持 NapCat 扩展。

Stream 本地文件投递在同一 Bot 内串行,纯文本不等待上传锁。Stream/URL 文件准备、发送与明确失败后的文件消息段回退共用 8 分钟预算,排队不计时;临时资源保留 16 分钟。URL 副本在源文件删除或切换模式后仍可下载,到期拒绝新请求,已有下载允许完成。文件准备失败不会触发文件消息段回退或标记已发送;投递发出后无法确认结果时禁止自动重发。不会自动切换模式、自动重试上传或启动 Runtime。

传输过程使用分块 IO;现有附件登记与 NapCat 的分块合并仍可能读取完整文件,不保证整个链路固定内存占用。参见 [三模式部署要求](deployment.md#napcat--lagrangecore-部署要求) 与 [临时文件接口](openapi.md#onebot-临时文件下载)。

---

Expand Down Expand Up @@ -1332,6 +1344,7 @@ api_key = "replace-with-your-key"
- `naga.*`(`enabled/api_url/api_key/use_proxy/moderation_enabled/mode/allowed_group_ids/blocked_group_ids/allowed_private_ids/blocked_private_ids`)

### 5.3 明确“会执行热应用”的字段
- `onebot.file_send_mode` / `onebot.file_send_host`(新投递读取快照;进行中投递及旧 URL 生命周期不变)
- 模型发车间隔 / 模型名 / 模型池变更(队列间隔刷新)
- `models.grok.model_name` / `models.grok.queue_interval_seconds`(队列间隔刷新)
- `models.summary` / `models.historian` / `models.grok` 的非队列字段会刷新 AI 运行时配置,但不会重建聊天、视觉或 Agent 模型客户端;其中 `models.summary` 热更新会重建摘要服务,`/summary`/`/sum`、SummaryService(如 `/bugfix`)会立即使用专用 summary 模型配置;主 AI 调用的 `summary_agent` 始终走 `models.agent`(及 agent 模型池)。
Expand Down Expand Up @@ -1709,6 +1722,8 @@ api_key = "replace-with-your-key"
| TOML 路径 | 环境变量 |
|-----------|----------|
| `onebot.token` | `ONEBOT_TOKEN` |
| `onebot.file_send_mode` | `ONEBOT_FILE_SEND_MODE` |
| `onebot.file_send_host` | `ONEBOT_FILE_SEND_HOST` |
| `onebot.ws_url` | `ONEBOT_WS_URL` |

#### `render`
Expand Down
39 changes: 21 additions & 18 deletions docs/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,38 +241,41 @@ python -c "from Undefined.utils.resources import read_text_resource; print(len(r

## NapCat / Lagrange.Core 部署要求

**NapCat(或 Lagrange.Core)必须与 Bot 进程共享同一文件系统,不能将 NapCat 单独放在无法访问 Bot 数据目录的 Docker 容器内。**
Bot 本地文件支持三种发送方式,默认 `local`,保持旧部署的发送行为。**是否需要共享文件系统取决于模式**:

### 原因
| 模式 | 共享文件系统 | 协议端要求 | Runtime 文件监听 |
|---|---|---|---|
| `local`(默认) | 必须按发送路径可见 | 能读取 Bot 给出的路径/`file://` URI | 不需要 |
| `url` | 不需要 | 对相应消息/文件接口支持 HTTP URL,且能访问 Runtime | 需要 |
| `stream` | 不需要 | 支持 NapCat `upload_file_stream` 扩展 | 不需要 |

Bot 发送本地文件(图片、音频、压缩包等)时,统一使用 `file:///path/to/file` URI,例如:

```
[CQ:image,file=file:///home/pyl/Undefined/data/cache/render/stats_line_chart.png]
```toml
[onebot]
file_send_mode = "local"
file_send_host = "127.0.0.1" # 仅 URL 模式使用,不包含协议、端口或路径
```

NapCat 收到后会在**自身所在的文件系统**上按路径读取文件。若 NapCat 在独立容器中,宿主机路径不可见,会报:
`local` 适用于同一宿主机、同一容器,或共享 volume 且内部路径一致的不同容器。协议端会在**自己的文件系统**中读取 URI;路径未挂载仍会报 `ENOENT`。

```
ENOENT: no such file or directory, copyfile '/home/pyl/...' -> '/app/.config/QQ/NapCat/temp/...'
```
`url` 模式复用 `[api]` Runtime HTTP 服务,无需额外端口。`file_send_host` 填写协议端实际可达的 IPv4、IPv6 或域名;IPv6 会正确生成带方括号的 URL。端口取实际监听值,修改 `api.port` 而尚未重启时仍使用旧端口。默认 `127.0.0.1` 仅适用于协议端与 Bot 共用网络空间的情况,独立容器中的回环地址指向容器自身;需要同时保证 `[api].host` 的绑定允许协议端访问。Runtime 关闭或未就绪时准备阶段报错,不会自动启动服务。

URL 使用单文件独立令牌,有效期 16 分钟,支持 HEAD、Range 和重复读取。下载读取的是 Bot 保存的独立副本,业务删除源文件或切换模式不会影响有效链接。到期拒绝新请求,正在读取的请求可以完成,然后清理副本。不要在反向代理访问日志中记录文件 URL 查询串。

### 支持的部署方式
`stream` 通过已有 OneBot WebSocket 按 64 KiB 分块上传,每块单独等待确认,最后独立请求完成并校验路径、大小和 SHA-256,再发 QQ 消息。一个 Bot 的 Stream 文件投递串行,多文件顺序准备,文本消息不受上传锁影响。文件准备、发送和明确失败后的回退共用 8 分钟预算,排队等待不计时;协议端文件显式保留 16 分钟。未完成 Stream 失败时仅尝试重置该 Stream,已完成文件依靠保留期回收,不调用清空临时目录的接口。不支持零字节文件,不自动重试上传或跨重启续传。

| 场景 | 是否支持 |
|---|---|
| Bot 和 NapCat 都在宿主机 | ✅ |
| Bot 在宿主机,NapCat 在 Docker(路径未挂载) | ❌ |
| Bot 和 NapCat 在同一个 Docker 容器 | ✅ |
| Bot 和 NapCat 在不同容器,共享同一 volume 且路径一致 | ✅ |
**旧配置缺少新增字段且未通过环境变量指定模式时继续使用 `local`。** 需要跨文件系统发送时,可显式设置 `onebot.file_send_mode = "url"` 或 `"stream"`。选择 Stream 后,协议端明确不支持扩展时会提示切换配置,不会静默回退。NapCat 扩展不能视为所有 OneBot 实现的共同能力;使用 Lagrange.Core 等实现时应按其实际能力选 `local`,或核对所用消息与普通文件上传接口的 URL 支持后选择 `url`。

实现参考固定版本的 [NapCat 上传示例](https://github.com/NapNeko/NapCatQQ/blob/109d0c1dff755875f3b79795e99cee6115289fbb/packages/napcat-onebot/action/stream/test_upload_stream.py) 与 [UploadFileStream](https://github.com/NapNeko/NapCatQQ/blob/109d0c1dff755875f3b79795e99cee6115289fbb/packages/napcat-onebot/action/stream/UploadFileStream.ts)。Bot 新传输层使用分块 IO,但该上游在合并磁盘分块时仍构造完整内存缓冲区,现有附件登记也可能读取完整文件;**不承诺整个链路固定内存占用**。

### 受影响的功能

以下功能均依赖本地文件路径
以下功能的本地来源统一经过该传输层,保留原始附件 UID、展示文件名与历史语义

- `/stats` 统计图表
- `render.render_markdown` / `render.render_latex` 渲染图片
- 定时任务发送图片 / 音频
- `code_delivery_agent` 代码交付压缩包
- `messages.send_text_file` / `messages.send_url_file`
- Bilibili 视频下载发送

同时覆盖语音、视频缩略图和嵌套合并转发中的媒体,支持 CQ 字符串及消息段数组。已有 HTTP/HTTPS URL、Base64 或协议端资源标识原样通过。两项配置支持按投递快照热更新,见 [配置说明](configuration.md#43-onebot-协议端连接)。
15 changes: 14 additions & 1 deletion docs/openapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ tool_invoke_callback_timeout = 10

## 2. 鉴权规则

- 除 `/api/v1/naga/*` ,所有 `/api/*` 路由都要求请求头:
- 除 `/api/v1/naga/*` 及下述使用单文件令牌的 GET/HEAD 下载路由外,所有 `/api/*` 路由都要求请求头:

```http
X-Undefined-API-Key: <your_key>
Expand All @@ -80,6 +80,19 @@ curl http://127.0.0.1:8788/openapi.json

## 4. 主要接口

### OneBot 临时文件下载

```text
GET /api/v1/onebot/files/{file_id}?token=...
HEAD /api/v1/onebot/files/{file_id}?token=...
```

仅 `[onebot].file_send_mode = "url"` 在进程内部登记本地文件时产生下载链接,无公共上传或任意本地路径参数。每个 ID 对应独立随机令牌,权限仅限该文件;`X-Undefined-API-Key` 不能替代此令牌,该令牌也不能调用其他 Runtime API。此路由不借用或扩大 WebChat 附件作用域。

支持 GET、HEAD、Range(`206`)和有效期内重复读取。缺少或错误令牌为 `401`,文件不存在或过期为 `404`,无效 Range 为 `416`。链接与独立副本保留 16 分钟,过期拒绝新读取,已有下载结束后删除副本。业务清理源文件及切换发送模式不会提前撤销链接。正常停止清理本实例缓存;启动只回收 `data/cache/onebot_files` 中本模块命名且已过期的遗留缓存。

服务复用 Runtime 实际监听端口。`onebot.file_send_host` 仅用于生成协议端可达的 URL,不能改变绑定地址;绑定仍由 `[api].host` 控制。文件 URL 令牌和 Stream 分块内容不会写入 Bot 请求日志及 Runtime 访问日志,反向代理也应隐藏查询串。

### 健康检查

- `GET /health`
Expand Down
Loading
Loading