Explorar el Código

发布 v0.4.20:新增 Bot 异步协作、项目与三层文件记忆

Samien hace 1 mes
padre
commit
21146b805e

+ 6 - 6
README.md

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

+ 1 - 1
desktop/service-sync.test.mjs

@@ -124,7 +124,7 @@ test('App first launch installs an independent Web; Web remains up after quit an
     // unload launchd (not just kill its PID), and exit with one menu action.
     await reopened.evaluate(async()=>{
       const capabilities=await (await fetch('/api/realtime/capabilities')).json()
-      const created=await fetch('/api/realtime/channels',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':capabilities.csrfToken},body:JSON.stringify({channel:'chat'})})
+      const created=await fetch('/api/realtime/channels',{method:'POST',headers:{'Content-Type':'application/json','X-CSRF-Token':capabilities.csrfToken},body:JSON.stringify({channel:'chat',chatProtocol:'ordinary-chat-transcript-v2'})})
       if(!created.ok)throw new Error('fixture realtime channel unavailable')
       const channel=await created.json()
       const response=await fetch(`/api/realtime/channels/${channel.id}/events`)

+ 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.19;仅 AMD64:samienluo/yaoyao:v0.4.19-amd。
+# 固定版本:samienluo/yaoyao:v0.4.20;仅 AMD64:samienluo/yaoyao:v0.4.20-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.19-amd` | `linux/amd64` |
-| `v0.4.19` | `linux/amd64`、`linux/arm64`,拉取时自动匹配 |
-| `latest` | 最新稳定通用镜像,当前指向与 `v0.4.19` 相同的镜像 |
+| `v0.4.20-amd` | `linux/amd64` |
+| `v0.4.20` | `linux/amd64`、`linux/arm64`,拉取时自动匹配 |
+| `latest` | 最新稳定通用镜像,当前指向与 `v0.4.20` 相同的镜像 |
 
 ```sh
-docker pull samienluo/yaoyao:v0.4.19
+docker pull samienluo/yaoyao:v0.4.20
 # 需要固定 AMD64 时:
-docker pull samienluo/yaoyao:v0.4.19-amd
+docker pull samienluo/yaoyao:v0.4.20-amd
 ```
 
 每次新版本发布都会更新 `latest`,旧版本标签继续保留。需要固定部署版本时使用具体版本标签。
@@ -34,7 +34,7 @@ cp docker.env.example docker.env
 
 | 配置 | 用途 |
 | --- | --- |
-| `HERMES_YAOYAO_IMAGE` | 远程镜像可设为 `samienluo/yaoyao:v0.4.19` 或 `samienluo/yaoyao:latest`;未设置时使用本地构建名称 |
+| `HERMES_YAOYAO_IMAGE` | 远程镜像可设为 `samienluo/yaoyao:v0.4.20` 或 `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.19
+HERMES_YAOYAO_IMAGE=samienluo/yaoyao:v0.4.20
 YAOYAO_CURSOR_DESKTOP_IMAGE=samienluo/yaoyao-desktop:v0.4.12-cursor-amd
 ```
 

+ 45 - 0
docs/releases/v0.4.20.md

@@ -0,0 +1,45 @@
+# 夭夭 AI v0.4.20
+
+本次发布 Web、macOS Apple Silicon 桌面版及 Docker Web 双架构镜像。Android APK 按要求不重新打包,附带上一版已验证的 1.3(171)安装包。
+
+## 更新内容
+
+- Bot 支持异步联系同伴:请求立即返回投递凭据,同伴完成后继续原会话或话题。界面可查看协作状态、请求与结果,并停止整条协作链;迟到结果不会重新启动已停止的工作。
+- 新建群默认平等讨论,无需先选负责人;未点名时全员参与,逐轮轮换发言顺序。支持 1–12 轮、最多 8 位成员,单个成员失败不阻止其他成员。已有群保留原协作模式,需要交付时可继续使用目标与验收流程。
+- 新增独立项目,可关联多个群,单聊也可选择当前项目。项目成员关系单独管理,群内成员需先加入项目才能使用项目记忆。
+- 新增 Bot、用户、项目三层文件记忆。共享事实按贡献 Bot 分开保存,保留来源、更新时间与修订记录,支持搜索、人工编辑、遗忘和 Markdown 导出。
+- Bot 资料提供协作和自动记录开关,默认开启。暂停自动记录保留已有记忆;用户编辑受保护,遗忘与修订保留屏障,防止旧提炼任务重新写回旧事实。
+- 记忆提炼在后台执行,失败可查看和重试,不阻塞聊天。协作链有投递次数、深度和重复内容限制,异步结果返回原话题后再合并提炼。
+
+## 升级与兼容
+
+- 本次新增项目、记忆和协作管理界面交付于 Web/macOS。复用的 Android 1.3(171)未包含 Android 新源码中的对应管理界面。
+- 完整记忆能力需要更新配套执行节点,并由节点声明隔离记忆和无工具提炼能力;不支持时界面提示升级。共享基础配置不会被改写。
+- 文件记忆和项目保存在 `.yaoyao/bot-workspace/<账号 ID>/`。升级前使用一致性快照,或停服后备份整个数据目录,包含数据库、WAL、附件及 `bot-workspace`。
+- 普通聊天继续使用 v2 协议,服务端和客户端仍需配套升级。独立电脑的数量、数据卷和生命周期沿用已有部署配置。
+- 主项目采用 Apache-2.0,第三方组件保留各自许可与署名。
+
+## 下载
+
+- `Yaoyao-0.4.20-arm64.dmg`:macOS Apple Silicon,Apple Development 开发签名,未公证。
+- `Yaoyao-1.3-171-android.apk`:Android 12 及以上,正式签名,直接复用 v0.4.19 已发布附件,文件摘要保持一致。
+- `Yaoyao-0.4.20-source.tar.gz`:Web/桌面源码及部署配置。
+- `SHA256SUMS.txt`:以上附件的 SHA-256 校验值。
+
+## Docker Hub
+
+| 镜像 | 架构 |
+| --- | --- |
+| `samienluo/yaoyao:v0.4.20-amd` | `linux/amd64` |
+| `samienluo/yaoyao:v0.4.20` | `linux/amd64`、`linux/arm64` |
+| `samienluo/yaoyao:latest` | 版本镜像验证后指向同一通用镜像 |
+
+```sh
+docker pull samienluo/yaoyao:v0.4.20
+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.20`。旧版本标签与附件保留。Docker 镜像发布到 Docker Hub,GitHub 不上传镜像归档。
+
+最终附件摘要、镜像摘要和运行验证记录发布在 [GitHub 发布页](https://github.com/Lsamien/yaoyao/releases/tag/v0.4.20)。

+ 2 - 2
package-lock.json

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

+ 1 - 1
package.json

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

+ 3 - 3
release.json

@@ -1,6 +1,6 @@
 {
   "schemaVersion": 1,
-  "releaseVersion": "0.4.19",
-  "webVersion": "0.4.19",
-  "gitTag": "v0.4.19"
+  "releaseVersion": "0.4.20",
+  "webVersion": "0.4.20",
+  "gitTag": "v0.4.20"
 }

+ 12 - 0
tests/e2e/workspace-chat.spec.ts

@@ -380,6 +380,8 @@ test('created roles and editable teams share a durable chat list without plugin
   await dialog.getByRole('textbox', { name: '名称', exact: true }).fill('产品开发团队')
   await dialog.getByRole('checkbox', { name: '产品经理', exact: true }).check()
   await dialog.getByRole('checkbox', { name: '开发工程师', exact: true }).check()
+  await dialog.getByText('高级协作设置', { exact: true }).click()
+  await dialog.getByRole('combobox', { name: '协作方式', exact: true }).selectOption('host')
   await dialog
     .getByRole('combobox', { name: '负责人', exact: true })
     .selectOption({ label: '产品经理' })
@@ -481,6 +483,8 @@ test('created roles and editable teams share a durable chat list without plugin
   await page.screenshot({ path: testInfo.outputPath('restored-team-presets.png'), fullPage: true })
   await dialog.getByRole('button', { name: '产品设计团队', exact: true }).click()
   await expect(dialog.getByRole('textbox', { name: '名称', exact: true })).toHaveValue('产品设计团队')
+  await dialog.getByText('高级协作设置', { exact: true }).click()
+  await dialog.getByRole('combobox', { name: '协作方式', exact: true }).selectOption('host')
   await dialog.getByRole('combobox', { name: '产品负责人对应的机器人', exact: true }).selectOption({ label: '产品经理' })
   await dialog.getByRole('combobox', { name: '交互设计对应的机器人', exact: true }).selectOption({ label: '开发工程师' })
   const [submitted] = await Promise.all([
@@ -499,10 +503,16 @@ test('created roles and editable teams share a durable chat list without plugin
   await page.getByPlaceholder('输入消息,Enter 发送,Shift + Enter 换行').fill('请汇总设计方案')
   await page.getByRole('button', { name: '发送消息', exact: true }).click()
   await expect(page.locator('.message--assistant')).toContainText('我是产品经理')
+  const conversationPath = new URL(page.url()).pathname.replace('/conversations/', '/api/app/conversations/')
+  await expect.poll(async () => {
+    const c = (await (await page.request.get(conversationPath)).json()).conversation
+    return { active: Boolean(c.activeRunId), queued: c.queuedMessageCount }
+  }).toEqual({ active: false, queued: 0 })
   const composer = page.getByPlaceholder('输入消息,Enter 发送,Shift + Enter 换行')
   await composer.fill('[hold-workspace] 正在执行的任务')
   await page.getByRole('button', { name: '发送消息', exact: true }).click()
   await expect(page.getByRole('button', { name: '停止生成', exact: true })).toBeVisible()
+  await expect.poll(async () => (await (await page.request.get(conversationPath)).json()).conversation.queuedMessageCount).toBe(0)
   await composer.fill('后续排队任务')
   await expect(page.getByRole('button', { name: '发送消息', exact: true })).toBeEnabled()
   await page.getByRole('button', { name: '发送消息', exact: true }).click()
@@ -600,6 +610,8 @@ for (const mode of ['普通聊天', 'Bot 单聊', '群聊', '普通长回复'] a
         await dialog.getByRole('textbox', { name: '名称', exact: true }).fill('流式验证群')
         await dialog.getByRole('checkbox', { name: `${mode}流式验证助手`, exact: true }).check()
         await dialog.getByRole('checkbox', { name: '群聊流式协作成员', exact: true }).check()
+        await dialog.getByText('高级协作设置', { exact: true }).click()
+        await dialog.getByRole('combobox', { name: '协作方式', exact: true }).selectOption('host')
         await dialog.getByRole('combobox', { name: '负责人', exact: true }).selectOption({ label: `${mode}流式验证助手` })
         await dialog.getByRole('button', { name: '保存', exact: true }).click()
         await expect(page.getByRole('heading', { name: '流式验证群', exact: true })).toBeVisible()