Bläddra i källkod

发布 v0.4.17:优化 Bot 收发与历史恢复并支持十路并发

Samien 1 månad sedan
förälder
incheckning
29e60b5c90

+ 6 - 6
README.md

@@ -1,6 +1,6 @@
 # 夭夭 AI
 
-当前 Web 与 macOS 桌面发布版本:**v0.4.16**。桌面版提供 Apple Silicon(arm64)DMG,详见 [发布说明](docs/releases/v0.4.16.md)。
+当前 Web 与 macOS 桌面发布版本:**v0.4.17**。桌面版提供 Apple Silicon(arm64)DMG,详见 [发布说明](docs/releases/v0.4.17.md)。
 
 夭夭 AI 为网页、iOS 和安卓提供统一的 Agent、群聊、文件库及语音配置服务。Hermes 通过标准 9119 HTTP/WebSocket 接口执行任务。
 
@@ -38,7 +38,7 @@ Web 数据默认保存在 `~/.yaoyao`,可用 `YAOYAO_HOME` 指定(兼容 `HE
 
 ## macOS 桌面版
 
-在 [v0.4.16 发布页](https://github.com/Lsamien/yaoyao/releases/tag/v0.4.16) 下载 `Yaoyao-0.4.16-arm64.dmg`,将“夭夭”拖到应用程序后打开。首次启动会核对并同步配套本机 Web 服务;普通退出保留后台运行,“停止后台服务并退出”可完整停止本应用管理的后台。详细说明见 [macOS App](docs/macos-app.md)。
+在 [v0.4.17 发布页](https://github.com/Lsamien/yaoyao/releases/tag/v0.4.17) 下载 `Yaoyao-0.4.17-arm64.dmg`,将“夭夭”拖到应用程序后打开。首次启动会核对并同步配套本机 Web 服务;普通退出保留后台运行,“停止后台服务并退出”可完整停止本应用管理的后台。详细说明见 [macOS App](docs/macos-app.md)。
 
 此 DMG 使用 Apple Development 开发签名,未做 Apple 公证。
 
@@ -48,11 +48,11 @@ Web 数据默认保存在 `~/.yaoyao`,可用 `YAOYAO_HOME` 指定(兼容 `HE
 
 | 镜像 | 架构 |
 | --- | --- |
-| `samienluo/yaoyao:v0.4.16-amd` | AMD64 |
-| `samienluo/yaoyao:v0.4.16` | 自动匹配 AMD64 / ARM64 |
-| `samienluo/yaoyao:latest` | 最新稳定版通用镜像,当前与 `v0.4.16` 相同 |
+| `samienluo/yaoyao:v0.4.17-amd` | AMD64 |
+| `samienluo/yaoyao:v0.4.17` | 自动匹配 AMD64 / ARM64 |
+| `samienluo/yaoyao:latest` | 最新稳定版通用镜像,当前与 `v0.4.17` 相同 |
 
-准备好 Docker Engine/Compose 和容器可访问的 Hermes 9119 服务,在仓库目录将 `docker.env.example` 复制为 `docker.env`。配置上游地址,并设置 `HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.16`(或 `samienluo/yaoyao:latest`),即可使用远程镜像:
+准备好 Docker Engine/Compose 和容器可访问的 Hermes 9119 服务,在仓库目录将 `docker.env.example` 复制为 `docker.env`。配置上游地址,并设置 `HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.17`(或 `samienluo/yaoyao:latest`),即可使用远程镜像:
 
 ```sh
 docker compose --env-file docker.env pull web

+ 1 - 1
docker.env.example

@@ -3,7 +3,7 @@ HERMES_YAOYAO_BIND_ADDRESS=127.0.0.1
 HERMES_YAOYAO_PUBLISHED_PORT=15300
 # 远程通用镜像支持 AMD64 / ARM64;每次发布更新 latest 并保留旧版本标签。
 # HERMES_YAOYAO_IMAGE=samienluo/yaoyao:latest
-# 固定版本:samienluo/yaoyao:v0.4.16;仅 AMD64:samienluo/yaoyao:v0.4.16-amd。
+# 固定版本:samienluo/yaoyao:v0.4.17;仅 AMD64:samienluo/yaoyao:v0.4.17-amd。
 # 留空时使用本地构建名称 yaoyao:local。
 # YAOYAO_DESKTOP_IMAGE=hermes-yaoyao-desktop:0.4.0
 # Cursor Universal 兼容桌面的本地构建名称;基础镜像在 deploy/computer/Dockerfile.cursor 中固定。

+ 7 - 7
docs/docker-install.md

@@ -12,14 +12,14 @@
 
 | 标签 | 架构 |
 | --- | --- |
-| `v0.4.16-amd` | `linux/amd64` |
-| `v0.4.16` | `linux/amd64`、`linux/arm64`,拉取时自动匹配 |
-| `latest` | 最新稳定通用镜像,当前指向与 `v0.4.16` 相同的镜像 |
+| `v0.4.17-amd` | `linux/amd64` |
+| `v0.4.17` | `linux/amd64`、`linux/arm64`,拉取时自动匹配 |
+| `latest` | 最新稳定通用镜像,当前指向与 `v0.4.17` 相同的镜像 |
 
 ```sh
-docker pull samienluo/yaoyao:v0.4.16
+docker pull samienluo/yaoyao:v0.4.17
 # 需要固定 AMD64 时:
-docker pull samienluo/yaoyao:v0.4.16-amd
+docker pull samienluo/yaoyao:v0.4.17-amd
 ```
 
 每次新版本发布都会更新 `latest`,旧版本标签继续保留。需要固定部署版本时使用具体版本标签。
@@ -34,7 +34,7 @@ cp docker.env.example docker.env
 
 | 配置 | 用途 |
 | --- | --- |
-| `HERMES_YAOYAO_IMAGE` | 远程镜像可设为 `samienluo/yaoyao:v0.4.16` 或 `samienluo/yaoyao:latest`;未设置时使用本地构建名称 |
+| `HERMES_YAOYAO_IMAGE` | 远程镜像可设为 `samienluo/yaoyao:v0.4.17` 或 `samienluo/yaoyao:latest`;未设置时使用本地构建名称 |
 | `HERMES_YAOYAO_UPSTREAM` | Hermes 上游地址,默认 `http://host.docker.internal:9119` |
 | `HERMES_YAOYAO_BIND_ADDRESS` | Web 的宿主机发布地址,默认 `127.0.0.1`;局域网访问可设为 `0.0.0.0` |
 | `HERMES_YAOYAO_PUBLISHED_PORT` | 宿主机 Web 端口,默认 `15300` |
@@ -152,7 +152,7 @@ curl --fail http://127.0.0.1:15300/healthz
 使用 Docker Hub 发布镜像时,在 `docker.env` 设置:
 
 ```dotenv
-HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.16
+HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.17
 YAOYAO_CURSOR_DESKTOP_IMAGE=samienluo/yaoyao-desktop:v0.4.12-cursor-amd
 ```
 

+ 2 - 0
docs/release-process.md

@@ -27,6 +27,8 @@
 
 ## 发布说明与最终核验
 
+版本标题与更新说明只描述夭夭 AI 自身的功能、修复和兼容性,不出现其他参考产品的名称或对比。
+
 每次在 `docs/releases/vX.Y.Z.md` 和 GitHub Release 中同步说明:
 
 - 本次改动、Web/桌面/Android 版本、下载附件与签名状态。

+ 46 - 0
docs/releases/v0.4.17.md

@@ -0,0 +1,46 @@
+# 夭夭 AI v0.4.17
+
+本次发布 Web、macOS Apple Silicon 桌面版、Android 1.3(169)及 Docker Web 双架构镜像。
+
+## 更新内容
+
+- Bot 消息被服务器接收后立即恢复发送状态,后台同步不再阻塞输入。重试沿用原请求编号,旧回执不会清除新草稿或覆盖更新的会话状态。
+- 优化流式消息传输:首段及时显示,后续文本以更细的间隔合并更新,长回复使用持久化增量补丁,减少重复传输完整消息的流量。
+- 完善断线、刷新及历史恢复。补丁缺失或顺序不连续时重新同步,保留已加载历史;修复长回复跟随与异步排版后的定位,用户上翻阅读时保持当前位置。
+- 同一 Bot 可在不同会话或话题中同时回复,各自保留独立的上下文、消息、审批和停止操作。同一会话的追问继续排队并复用原会话。
+- Bot 全局最多同时执行 10 个轮次,超出的已接收工作继续排队。同一群聊最多接收 10 个未完成话题,超过时明确提示;单个话题仍最多并行 3 个成员轮次。
+- 并发额度与虚拟机数量独立,共享电脑和浏览器仍遵循各自的操作队列与独占规则。
+- 完善桌面升级验证,正式版使用高于上一正式版及本轮测试版的版本号,保留已有服务设置与数据。
+- Android 1.3(169)同步更新发送确认、流式补丁、长回复跟随与历史恢复。
+
+## 升级说明
+
+- 旧客户端继续兼容完整消息传输,新客户端按服务器能力启用增量消息。
+- 本版事件日志包含新的流式格式。升级前备份 `.yaoyao` 数据与密钥;需要关闭增量协议时使用本版后端的兼容模式,避免直接换回不能识别新日志格式的旧后端。
+- 桌面和执行节点建议同步更新至配套版本。固定桌面的数量与生命周期沿用已有部署配置。
+- 主项目采用 Apache-2.0,第三方组件保留各自许可与署名。
+
+## 下载
+
+- `Yaoyao-0.4.17-arm64.dmg`:macOS Apple Silicon,Apple Development 开发签名,未公证。
+- `Yaoyao-1.3-169-android.apk`:Android 12 及以上,沿用正式发布密钥签名。源码提交 `e915142`。
+- `Yaoyao-0.4.17-source.tar.gz`:Web/桌面源码及部署配置。
+- `SHA256SUMS.txt`:以上附件的 SHA-256 校验值。
+
+## Docker Hub
+
+| 镜像 | 架构 |
+| --- | --- |
+| `samienluo/yaoyao:v0.4.17-amd` | `linux/amd64` |
+| `samienluo/yaoyao:v0.4.17` | `linux/amd64`、`linux/arm64` |
+| `samienluo/yaoyao:latest` | 版本镜像验证后指向同一通用镜像 |
+
+```sh
+docker pull samienluo/yaoyao:v0.4.17
+docker compose --env-file docker.env pull web
+docker compose --env-file docker.env up -d --no-build web
+```
+
+Compose 中设置 `HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.17`。独立电脑镜像沿用现有配置,保留旧版本标签与附件。Docker 镜像发布到 Docker Hub,GitHub 不上传镜像归档。
+
+最终附件摘要、镜像摘要和运行验证记录发布在 [GitHub 发布页](https://github.com/Lsamien/yaoyao/releases/tag/v0.4.17)。

+ 2 - 2
package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "yaoyao",
-  "version": "0.4.17-bot-latency.1",
+  "version": "0.4.17",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "yaoyao",
-      "version": "0.4.17-bot-latency.1",
+      "version": "0.4.17",
       "license": "Apache-2.0",
       "dependencies": {
         "@koa/bodyparser": "^5.0.0",

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "yaoyao",
-  "version": "0.4.17-bot-latency.1",
+  "version": "0.4.17",
   "private": true,
   "license": "Apache-2.0",
   "description": "夭夭 AI:聊天、多 Agent 团队、文件库与电脑工作空间",

+ 3 - 3
release.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "releaseVersion": "0.4.17-bot-latency.1",
-  "webVersion": "0.4.17-bot-latency.1",
-  "gitTag": "v0.4.17-bot-latency.1"
+  "releaseVersion": "0.4.17",
+  "webVersion": "0.4.17",
+  "gitTag": "v0.4.17"
 }

+ 2 - 1
tests/client/conversationSearchDelete.test.ts

@@ -6,7 +6,7 @@ import ConversationsView from '../../src/client/views/ConversationsView.vue'
 import { apiRequest } from '../../src/client/api/client'
 import type { WorkspaceLifecyclePreview } from '../../src/shared/workspaceLifecycle'
 
-vi.mock('../../src/client/api/client', () => ({ apiRequest: vi.fn(), ApiError: class extends Error {} }))
+vi.mock('../../src/client/api/client', () => ({ apiRequest: vi.fn(), setApiCsrfToken: vi.fn(), ApiError: class extends Error {} }))
 vi.mock('../../src/client/stores/auth', () => ({ useAuthStore: () => ({ user: { id: 'owner' }, profiles: [], refreshProfileAvatars: async () => {} }) }))
 vi.mock('../../src/client/stores/theme', () => ({ useThemeStore: () => ({}) }))
 
@@ -19,6 +19,7 @@ async function setup(kind: 'direct' | 'group', remove: () => Promise<void> = asy
   await router.push('/conversations'); await router.isReady()
   vi.stubGlobal('EventSource', class { addEventListener() {} close() {} })
   vi.mocked(apiRequest).mockImplementation(async (path, options) => {
+    if (path === '/api/app/capabilities') return { features: [], csrfToken: 'fixture-csrf' } as never
     if (path === '/api/app/workspace/snapshot') return { agents: [{ id: 'bot', name: '旧聊天', archived: true }], conversations: archived, details: [], cursor: 1 } as never
     if (path.endsWith('/lifecycle')) {
       if (options?.method !== 'POST') return { name: '旧聊天', kind, groups, confirmationToken: 'a'.repeat(64) } as never

+ 2 - 1
tests/client/conversationSettings.test.ts

@@ -6,7 +6,7 @@ import ConversationsView from '@/views/ConversationsView.vue'
 import { apiRequest } from '@/api/client'
 import type { WorkspaceConversation } from '@shared/workspace'
 
-vi.mock('@/api/client', () => ({ apiRequest: vi.fn(), ApiError: class extends Error {} }))
+vi.mock('@/api/client', () => ({ apiRequest: vi.fn(), setApiCsrfToken: vi.fn(), ApiError: class extends Error {} }))
 vi.mock('@/stores/auth', () => ({ useAuthStore: () => ({ user: { id: 'owner' }, profiles: [], refreshProfileAvatars: async () => {} }) }))
 vi.mock('@/stores/theme', () => ({ useThemeStore: () => ({}) }))
 
@@ -22,6 +22,7 @@ it.each(['direct', 'group'] as const)('opens and saves the menu target %s while
   const agents = ['current-bot', 'target-bot'].map(id => ({ id, name: id === 'target-bot' ? '目标机器人' : '当前机器人', avatar: '', instructions: `${id}规则`, nodeId: 'local', profile: 'default', archived: false }))
   vi.stubGlobal('EventSource', class { addEventListener() {} close() {} })
   vi.mocked(apiRequest).mockImplementation(async path => {
+    if (path === '/api/app/capabilities') return { features: [], csrfToken: 'fixture-csrf' } as never
     if (path === '/api/app/workspace/snapshot') return { agents, conversations, cursor: 1, details: [] } as never
     if (path === '/api/app/agents') return { agents } as never
     if (path === '/api/app/agents/sources') return { sources: [{ nodeId: 'local', profile: 'default', name: '基础机器人' }] } as never