Initial TrueGrowth source import

This commit is contained in:
2026-07-07 09:36:36 +08:00
commit 3b6781d695
2283 changed files with 691996 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
# Design: postmessage-duplex 通信层重构
## Context
当前系统使用原生 `postMessage` API 实现 SW 与应用层通信:
- 应用层 → SW直接 `controller.postMessage()`
- SW → 应用层:`broadcastToClients()` 广播所有窗口
这种模式在多标签页场景下存在问题:
1. 多个页面同时提交相同任务SW 的去重检查存在时间窗口
2. 广播消息所有页面都会收到,无法区分请求来源
3. 请求-响应模式需要手动用 RxJS 实现
## Goals / Non-Goals
### Goals
- 使用 postmessage-duplex 实现双工通信
- 解决多标签页任务重复问题
- 简化请求-响应模式的实现
- 保持向后兼容(渐进式迁移)
### Non-Goals
- 不改变现有业务逻辑
- 不改变 IndexedDB 存储结构
- 不改变任务执行流程
## Decisions
### 1. 通信架构
```
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ Tab 1 (Page) │ │ Service Worker │ │ Tab 2 (Page) │
├─────────────────┤ ├──────────────────────┤ ├─────────────────┤
│ ServiceWorker │◄──────►│ Per-client channels │◄──────►│ ServiceWorker │
│ Channel │ RPC │ Map<clientId,Channel>│ RPC │ Channel │
└─────────────────┘ └──────────────────────┘ └─────────────────┘
```
- 每个页面创建独立的 `ServiceWorkerChannel`
- SW 端维护 `Map<clientId, channel>` 管理所有连接
- 请求-响应使用 `call` 方法,事件通知使用 `subscribe`
### 2. 消息类型划分
| 操作 | 当前方式 | 改造后方式 |
|------|---------|-----------|
| 任务创建 | postMessage + 监听 | `call('task:create', params)``{taskId, status}` |
| 任务取消 | postMessage | `call('task:cancel', {taskId})``{success}` |
| 任务重试 | postMessage | `call('task:retry', {taskId})``{success}` |
| 获取任务列表 | postMessage + timeout | `call('task:list')``{tasks}` |
| 任务状态变更 | broadcast | `subscribe('task:status')` |
| 任务完成 | broadcast | `subscribe('task:completed')` |
| Chat 流式 | broadcast | `subscribe('chat:chunk')` |
### 3. 任务创建流程改造
**当前流程**
```
1. 应用层生成 taskId
2. 本地创建任务(乐观更新)
3. postMessage 到 SW
4. SW 检查重复,可能拒绝
5. 广播 TASK_CREATED
```
**改造后流程**
```
1. 应用层调用 channel.call('task:create', params)
2. SW 检查重复
- 已存在:返回 {success: false, existingTaskId}
- 新建:创建任务,返回 {success: true, task}
3. 应用层根据响应更新本地状态
4. SW 广播 task:created 给其他客户端(排除发起者)
```
### 4. 多页面同步
- SW 维护所有通道引用
- 当任务状态变更时,广播给所有订阅该事件的通道
- 每个通道独立订阅,避免重复处理
### 5. Service Worker 端集成
由于 SW 不支持直接 import npm 模块,有两种方案:
**方案 A推荐**:打包时内联
- 使用 Vite 的 `?inline` 或自定义打包将库代码内联到 SW
**方案 B**:复制核心代码
- 将 postmessage-duplex 的 SW 相关代码复制到项目中
选择 **方案 A**,在 SW 构建配置中处理。
## Risks / Trade-offs
| Risk | Mitigation |
|------|------------|
| SW 端集成复杂 | 使用打包工具处理,文档中有示例 |
| 迁移风险 | 渐进式迁移,保留旧消息类型兼容 |
| 库稳定性 | 库较新v1.0.0),需关注 issues |
## Migration Plan
### Phase 1基础设施
1. 安装 postmessage-duplex 依赖
2. 配置 SW 打包支持
3. 创建新的通信服务(与现有并存)
### Phase 2核心功能迁移
1. 迁移任务创建为 call 模式
2. 迁移任务状态为 subscribe 模式
3. 迁移 Chat 流式为 subscribe 模式
### Phase 3清理
1. 移除旧的 RxJS 订阅逻辑
2. 移除兼容代码
3. 更新文档
## Open Questions
1. SW 打包方式的具体实现细节?
- 需要测试 Vite 对 SW 入口的打包行为
2. 是否需要支持消息压缩?
- 当前任务数据量不大,暂不需要

View File

@@ -0,0 +1,34 @@
# Change: 使用 postmessage-duplex 重构 Service Worker 通信层
## Why
当前 SW 与应用层通信采用原生 postMessage + 广播模式,存在以下问题:
1. **多标签页任务重复**:同时打开多个页面时,任务可能被重复添加
2. **无请求-响应模式**:需要手动用 RxJS filter + timeout 实现
3. **广播无差异化**:所有客户端都收到相同消息,无法区分请求来源
4. **无连接就绪保障**SW 未就绪时发送的消息可能丢失
## What Changes
- **BREAKING** 重构 `SWTaskQueueClient` 使用 `postmessage-duplex`
- **BREAKING** 重构 SW 端通信逻辑使用 `ServiceWorkerChannel`
- 任务创建改用 `call` 方法(请求-响应模式SW 返回创建结果
- 任务状态更新使用 `subscribe` 订阅模式
- 移除手动实现的 RxJS 超时/过滤逻辑
## Impact
- Affected specs: 无(新增通信层规范)
- Affected code:
- `packages/drawnix/src/services/sw-client/client.ts`
- `apps/web/src/sw/task-queue/queue.ts`
- `apps/web/src/sw/task-queue/types.ts`
- `apps/web/src/sw/index.ts`
## Benefits
1. **请求-响应模式**:任务创建等操作可等待 SW 确认结果
2. **消息队列**:连接就绪前消息自动缓存
3. **内置超时**:无需手动实现
4. **多页面隔离**:每个页面有独立通道,避免重复处理
5. **类型安全**:完整的 TypeScript 类型定义

View File

@@ -0,0 +1,81 @@
## ADDED Requirements
### Requirement: Duplex Communication Channel
应用层与 Service Worker 之间 SHALL 使用 postmessage-duplex 库建立双工通信通道。
#### Scenario: 页面初始化通道
- **WHEN** 页面加载完成
- **THEN** 自动创建 `ServiceWorkerChannel` 实例
- **AND** 等待 SW 就绪后建立连接
- **AND** 连接就绪前的消息 SHALL 自动缓存
#### Scenario: SW 接受新连接
- **WHEN** SW 收到新客户端连接消息
- **THEN** 为该客户端创建独立的通道实例
- **AND** 将通道存入 `Map<clientId, channel>`
### Requirement: Request-Response Task Creation
任务创建操作 SHALL 使用请求-响应模式,由 SW 返回创建结果。
#### Scenario: 成功创建新任务
- **GIVEN** 用户提交任务创建请求
- **WHEN** SW 验证任务不重复
- **THEN** SW 创建任务并返回 `{success: true, task: SWTask}`
- **AND** 应用层根据响应更新本地状态
- **AND** SW 广播 `task:created` 给其他客户端
#### Scenario: 拒绝重复任务
- **GIVEN** 用户提交任务创建请求
- **WHEN** SW 检测到相同参数的任务已存在
- **THEN** SW 返回 `{success: false, existingTaskId: string, reason: 'duplicate'}`
- **AND** 应用层 SHALL 不创建新任务
#### Scenario: 多页面同时提交相同任务
- **GIVEN** Tab A 和 Tab B 几乎同时提交相同任务
- **WHEN** SW 串行处理请求
- **THEN** 第一个请求成功创建任务
- **AND** 第二个请求收到重复拒绝响应
- **AND** 两个页面最终状态一致
### Requirement: Event-Based Status Updates
任务状态变更 SHALL 使用订阅模式通知所有客户端。
#### Scenario: 订阅任务状态变更
- **GIVEN** 页面订阅 `task:status` 事件
- **WHEN** 任何任务状态发生变更
- **THEN** 所有订阅客户端收到状态更新
- **AND** 事件包含 `{taskId, status, progress?, phase?}`
#### Scenario: 任务完成通知
- **GIVEN** 页面订阅 `task:completed` 事件
- **WHEN** 任务执行成功完成
- **THEN** 所有订阅客户端收到完成通知
- **AND** 事件包含 `{taskId, result}`
### Requirement: Chat Streaming via Subscribe
Chat 流式输出 SHALL 使用订阅模式。
#### Scenario: 订阅 Chat 流式输出
- **GIVEN** 页面发起 Chat 请求
- **WHEN** SW 开始生成响应
- **THEN** 订阅该 chatId 的客户端收到 `chat:chunk` 事件
- **AND** 响应完成时收到 `chat:done` 事件
### Requirement: Multi-Tab Isolation
多标签页 SHALL 独立管理各自的通道连接。
#### Scenario: 页面刷新重建连接
- **WHEN** 页面刷新
- **THEN** 旧通道自动销毁
- **AND** 新页面建立新通道
- **AND** SW 清理旧 clientId 的通道引用
#### Scenario: 页面关闭清理
- **WHEN** 页面关闭
- **THEN** SW 检测到客户端断开
- **AND** 清理对应的通道资源

View File

@@ -0,0 +1,36 @@
# Tasks: postmessage-duplex 通信层重构
## 1. 基础设施
- [ ] 1.1 安装 postmessage-duplex 依赖到 packages/drawnix
- [ ] 1.2 配置 SW 打包支持(内联 postmessage-duplex 到 SW bundle
- [ ] 1.3 创建类型定义文件 `sw-channel-types.ts`
## 2. 应用层改造
- [ ] 2.1 创建 `SWDuplexClient` 新服务(基于 ServiceWorkerChannel
- [ ] 2.2 实现任务创建 RPC 方法 `task:create`
- [ ] 2.3 实现任务查询 RPC 方法 `task:list`, `task:get`
- [ ] 2.4 实现任务操作 RPC 方法 `task:cancel`, `task:retry`, `task:delete`
- [ ] 2.5 实现事件订阅 `task:status`, `task:completed`, `task:failed`
- [ ] 2.6 实现 Chat 订阅 `chat:chunk`, `chat:done`, `chat:error`
## 3. Service Worker 改造
- [ ] 3.1 创建 `SWChannelManager` 管理多客户端通道
- [ ] 3.2 注册 RPC 处理器task:create, task:list 等)
- [ ] 3.3 实现事件广播(排除发起者)
- [ ] 3.4 改造任务创建逻辑(原子性检查 + 响应)
## 4. 集成测试
- [ ] 4.1 单页面任务创建测试
- [ ] 4.2 多页面同时创建相同任务测试
- [ ] 4.3 页面刷新后任务恢复测试
- [ ] 4.4 Chat 流式通信测试
## 5. 迁移与清理
- [ ] 5.1 迁移 `swTaskQueueService` 使用新客户端
- [ ] 5.2 移除旧的 RxJS 订阅逻辑
- [ ] 5.3 更新相关文档