Files
goodbuddy/docs/features/desktop-pet/logic-design.md
T
mesalogo 374968d50b docs: add desktop pet feature design
Adds the full documentation set for the planned desktop pet feature under
docs/features/desktop-pet: README, PRD, user stories, logic design, UI
design, and technical design. The pet is scoped as a lightweight always-on
entry point for quick drafting, drag-and-drop staging, and task status at a
glance; it does not introduce a second Ask/Execute channel, task execution
path, or independent task-state store. Design only, no implementation yet.
2026-09-03 23:22:33 +08:00

204 lines
9.1 KiB
Markdown

# 桌面宠物功能逻辑设计
## 文档信息
| 项目 | 内容 |
| --- | --- |
| 状态 | 设计中 |
| 版本 | 0.1 |
| 日期 | 2026-09-03 |
| 关联 PRD | [桌面宠物 PRD](./prd.md) |
| User Stories | [桌面宠物 User Stories](./user-stories.md) |
| 任务模型 | [Task 与 Job 统一领域模型](../task-and-job/task-and-job-model.md) |
本文负责桌面宠物自身的状态、不变量和决策规则,不重新定义 Task、Conversation、Ask/
Execute 或附件的既有语义;这些语义以各自权威文档为准。
## 1. 核心不变量
### INV-1 宠物不是第二条执行通道
快速起草和拖放暂存只产生主窗口会话输入区的草稿数据(文本、附件引用),不产生消息、
不产生 Task、不触发模型调用。发送、模式选择和 Runtime 选择继续完全由主窗口既有流程
拥有。
### INV-2 状态镜面是既有状态的投影
宠物状态镜面的取值只能从当前 Task 集合的既有状态推导,不允许引入宠物专属的任务状态
字段或独立于 Task Center 的状态存储。宠物状态与 Task Center 展示的状态出现分歧即视为
缺陷。
### INV-3 总开关是唯一的存在性开关
“启用桌面宠物”关闭时,宠物窗口、气泡和托盘条目文案必须整体消失;除该开关外,任何
子设置(外观、位置、安静模式)的变化都不得单独创建或销毁宠物窗口。
### INV-4 暂时隐藏不影响启用状态
“暂时隐藏”只影响窗口可见性,不修改总开关持久化值,也不清除位置、外观或安静模式设置。
### INV-5 提示与原生通知互斥
同一个任务终态事件(完成或失败)只能产生宠物气泡或现有原生系统通知中的一种,不产生
两种同时可见的独立提示。
### INV-6 位置始终落在可用显示器内
宠物窗口的持久化位置在应用于当前显示器拓扑之前必须被校验;不能得出宠物窗口完全位于
任何当前可见显示器之外的最终坐标。
### INV-7 拖放与起草共享暂存规则
拖放产生的文本和文件必须满足与快速起草附件相同的类型、大小、数量限制;两者不允许存在
两套互不一致的限制值。
## 2. 状态维度
### 2.1 总开关与可见性
```text
disabled -> enabled_visible
enabled_visible <-> enabled_hidden (暂时隐藏 / 重新显示)
enabled_visible|enabled_hidden -> disabled (关闭总开关)
```
`disabled` 是唯一没有宠物窗口存在的状态;`enabled_hidden` 窗口不可见但设置全部保留。
### 2.2 快速起草弹出框
```text
closed -> drafting -> submitted | discarded
drafting -> closed (Escape / 点击外部 / 关闭按钮)
```
`submitted` 是终态转换动作,不是持续状态:提交后弹出框立即回到 `closed`,草稿数据已经
转交给主窗口。
### 2.3 拖放交互
```text
idle -> drag_over -> dropped_accepted | dropped_rejected | drag_cancelled
```
`drag_over` 只影响视觉反馈,不产生任何持久数据;只有 `dropped_accepted` 才产生暂存
数据。`dropped_rejected` 用于文件夹或全部文件超限的场景,不产生暂存也不打开主窗口。
### 2.4 状态镜面
```text
unavailable | idle | completed_recent | running | waiting_approval | failed_recent
```
取值由当前活跃与最近终态 Task 集合计算得出,属于派生状态,不单独持久化。
### 2.5 提示气泡
```text
none -> pending -> shown -> dismissed
```
`pending` 表示事件已发生但由于主窗口可见或安静模式而被抑制,不进入 `shown`。
## 3. 状态镜面优先级
宠物在任意时刻只呈现一个状态镜面取值。存在多个活跃 Task 时按以下优先级取最高项:
| 优先级 | 状态 | 条件 |
| --- | --- | --- |
| 1 | `unavailable` | 用于确认状态的 Runtime 或网络当前不可达 |
| 2 | `waiting_approval` | 至少一个 Task 处于等待批准 |
| 3 | `failed_recent` | 至少一个 Task 在最近的“最近失败”窗口内失败,且用户尚未查看 |
| 4 | `running` | 至少一个 Task 处于执行中 |
| 5 | `completed_recent` | 至少一个 Task 在最近的“最近完成”窗口内完成,且用户尚未查看 |
| 6 | `idle` | 以上条件都不满足 |
“最近失败”“最近完成”窗口的具体时长和“已查看”判定属于技术选择,由
[技术设计](./technical-design.md)基于既有 Task 已读/未读语义确定,不在本文固定具体
数值。
## 4. 提示归并决策表
| 事件 | 主窗口状态 | 安静模式 | 结果 |
| --- | --- | --- | --- |
| 任务完成 | 不可见或未聚焦 | 关闭 | 显示宠物气泡;不触发原生通知 |
| 任务完成 | 不可见或未聚焦 | 开启 | 不显示气泡;不触发原生通知;仅更新状态镜面 |
| 任务完成 | 可见并聚焦 | 任意 | 不显示气泡;反馈通过主窗口既有机制呈现 |
| 任务失败 | 不可见或未聚焦 | 关闭 | 显示宠物气泡并保持可见直到用户查看或关闭 |
| 任务失败 | 不可见或未聚焦 | 开启 | 不显示气泡;仅更新状态镜面为 `failed_recent` |
| 任务失败 | 可见并聚焦 | 任意 | 不显示气泡;反馈通过主窗口既有机制呈现 |
“主窗口可见并聚焦”与现有 [`desktop-notification.ts`](../../../src/main/desktop-notification.ts)
判定原生通知是否触发所使用的条件保持一致,避免宠物和原生通知各自使用不同的可见性
判定导致同一事件在两处呈现或同时缺失。
## 5. 快速起草与拖放的目标会话决策表
| 场景 | 目标会话 |
| --- | --- |
| 主窗口存在当前活跃会话,用户未在弹出框内更改目标 | 该活跃会话 |
| 用户在弹出框内选择“新建对话” | 提交时新建并激活一个会话 |
| 主窗口不存在任何会话 | 提交时新建并激活一个会话 |
| 目标会话在提交前被用户在主窗口中删除 | 视为“不存在任何会话”,回退到新建会话 |
拖放没有目标切换界面,始终采用与快速起草相同的默认目标会话规则。
## 6. 附件与文本限制
快速起草和拖放暂存复用现有聊天附件规则,不定义新的限制数值:
- 单条草稿的附件数量上限、单个附件大小上限、支持的文本与图片类型,与主窗口聊天附件
的现有限制一致。
- 拖放同时包含多个文件时,逐个按上述规则校验;不合规文件被拒绝,不影响合规文件继续
暂存。
- 拖放文件夹被整体拒绝,不做递归展开或部分接受。
## 7. 位置与显示器校验
1. 加载持久化位置后,检查该坐标是否位于当前任一可见显示器的可用区域内。
2. 位于范围内:直接使用。
3. 不在范围内(例如记忆的显示器已断开):改用主显示器上一个默认可见位置,并在下一次
用户拖动宠物后覆盖持久化位置。
4. 开启“贴边”时,拖动释放位置与最近边缘的距离小于吸附阈值即吸附到该边缘;阈值和默认
位置的具体数值属于技术选择。
## 8. 失败与恢复
| 场景 | 行为 |
| --- | --- |
| 主窗口进程尚未创建或已被销毁 | 提交快速起草或拖放时先创建/恢复主窗口,再写入草稿 |
| 写入草稿时主窗口窗口对象不可用 | 提交失败,弹出框保留草稿内容供用户重试,不静默丢弃 |
| 状态查询所需的 Runtime/网络不可达 | 状态镜面显示 `unavailable`,不回退显示 `idle` |
| 显示器拓扑变化导致位置越界 | 按第 7 节回退到默认可见位置,不隐藏窗口 |
| 用户在气泡展示期间关闭总开关 | 气泡与宠物窗口一并立即消失 |
| 应用启动时总开关为开启但窗口创建失败 | 记录失败原因,不阻塞主窗口正常启动,托盘菜单提供重试入口 |
## 9. 需求追踪
| 需求 | 主要逻辑 |
| --- | --- |
| `FR-1` | `INV-3`、总开关与可见性状态 |
| `FR-2` | 位置与显示器校验、`INV-6` |
| `FR-3` | `INV-1`、快速起草状态、目标会话决策表 |
| `FR-4` | `INV-1`、`INV-7`、拖放交互状态、附件与文本限制 |
| `FR-5` | `INV-2`、状态镜面优先级 |
| `FR-6` | `INV-5`、提示归并决策表 |
| `FR-7` | 提示归并决策表中安静模式列 |
| `FR-8` | 位置与显示器校验之外的技术选择,逻辑仅约束持久化立即生效 |
| `FR-9` | `INV-4`、总开关与可见性状态 |
| `FR-10` | 快速起草状态中的键盘转换,具体按键映射见 UI 设计 |
| `FR-11` | `INV-1`、`INV-2`、`INV-7` |
## 10. 逻辑完整性评估
本设计覆盖了启用/关闭、暂时隐藏、快速起草、拖放(含超限和文件夹)、状态镜面优先级、
提示与原生通知的归并、位置越界恢复、以及主窗口不可用时的失败路径。
未在本文固定、留给技术设计选择的实现细节:
- “最近完成/最近失败”窗口的具体时长和“已查看”判定的确切事件来源。
- 贴边吸附阈值和默认回退位置的具体像素值。
- 状态查询的具体轮询或事件驱动机制。
这些是技术选择而非未解决的产品决策:本文的优先级和决策表已经足以在任何取值下保持
行为一致。