feat(design): integrate agent activity timeline

This commit is contained in:
2026-09-07 12:21:40 +08:00
parent 81b524a02c
commit 7bdb42f26c
12 changed files with 243 additions and 133 deletions

View File

@@ -4,6 +4,9 @@
Accepted / implemented
Amended on 2026-09-07 to define operation-scoped public activity as transient
presentation state beneath the matching optimistic user message.
## Date
2026-08-30
@@ -27,6 +30,12 @@ versioned Design Specification and a persistent form throughout the design flow.
- Every mutation carries stable command and semantic operation identities. An
unknown transport result keeps the same command for replay; revision conflicts
refresh canonical state without discarding local drafts.
- While a chat operation is pending, Electron Main may normalize the server's closed
`design.assistant.progress` lifecycle into fixed youth-safe stages, and Renderer
may show one transient activity panel beneath the matching optimistic user message.
Activity is not a canonical Turn, Current Specification state, assistant content,
or evidence that a change committed. Model chain-of-thought, prompts, Provider
responses, tool arguments, and unvalidated model text are never rendered.
- Generation is a compile-and-confirm boundary. The server returns an immutable
Quote for one exact Specification revision; the client displays the public output
plan, warnings, expiry, and Token Point amount and confirms only the Quote ID.
@@ -51,6 +60,8 @@ and uncertain retries auditable without exposing provider internals.
old clients and V1 semantic writers receive no compatibility window.
- Production database cutover, paid Provider activation, and real-account installed
client smoke remain separate operator gates.
- Replayed activity and generic terminal Gateway events reconcile by the original
operation identity. They do not create another message, form, or retry intent.
## Supersedes

View File

@@ -30,6 +30,7 @@
| 客户端更新检查 | 设置页 | Renderer update store → IPC → Main AppUpdater → 目标 feed | Main 记录并重抛原始错误Renderer 只显示去重、脱敏的单条提示,稳定源缺包不伪装为最新版 |
| Design Workspace load | Canvas/Sidebar | Renderer Host API → Main-owned Works Square V2 adapter → current Workspace/Direction/Living Form projection | 一个 Workspace 公开一个 current Direction 与 persistent Agent Sessionconversation timeline 是交互历史,不是独立 authority object |
| Design input and reconciliation | Chat/direct edit/decision/proposal/lock/Asset binding/restore | Renderer draft → Main → `design.input.apply` → canonical Direction projection | 全部进入同一 reduceraccepted 前 draft 保持本地revision conflict 刷新权威投影unknown result 复用原 command/operation identity |
| Design activity and reply streaming | Direction Gateway journal | Main 固定活动阶段归一化 → Renderer operation-scoped activitycommitted assistant delta → matching provisional assistant bubble → canonical Turn | `design.assistant.progress` 只显示在对应 optimistic user message 下方,不成为消息或右侧摘要;`design.assistant.delta` 只投影已提交回复。两者都按 Workspace、Direction、operation identity 和 replay cursor 收敛,均不改写 Specification |
| 设计有限请求生命周期 | Renderer Workspace JSON 调用 / shared Works token refresh | Main deadline → Electron transport → safe-read-only Node fallback → Host API timeout envelope | 包括取凭据、请求头和响应 body 的完整生命周期最多 30 秒;超时 abort transport 并稳定返回 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT`;透明 fallback 只允许 `GET`/`HEAD`/`OPTIONS`mutation 不隐式重放 |
| Design Asset binding | 当前 Workspace 已完成图片 / 本地上传 | canonical Asset → typed `asset_binding` input → Current Specification revision | 图片/视频用途由 Specification medium 和 binding role 决定;只提交真实 Workspace Asset identity不使用本地路径或自然语言占位 |
| Design Quote and confirmation | 当前 exact Specification revision | `design.quote.request` → immutable public Quote projection → `design.generation.confirm` with Quote ID | Renderer 只展示 output summary、warnings、expiry、Token PointsProvider Prompt/model/route/storage/internal billing 不下发或编辑 |
@@ -81,4 +82,4 @@
## Last Updated
2026-09-06
2026-09-07

View File

@@ -35,11 +35,11 @@
| `electron/coding-runtime/pi/{extension-host,subagent,write-lease}.ts` / `electron/main/background-lifecycle.ts` | 必需 Makelore extension、显式安装且启用的 Device Package extensions、交互、子 Agent、同项目写租约与后台运行租约 | parent generation 加载完整 Main-selected extension 集child 并发 4、单次最多 8、禁止递归active/uncertain run 不因页面隐藏或确认超时被回收,所有 stop/replacement 必须有原因 |
| `electron/main/updater.ts` | 目标 feed 解析、electron-updater 生命周期与原始诊断 | 缺少稳定 manifest 保持错误;事件/Promise 重复失败按检查实例去重 |
| `src/components/settings/UpdateSettings.tsx` | 更新状态、重试与用户可读错误展示 | 只显示一条简洁中文提示;技术诊断统一回退到本地化通用文案 |
| `shared/image-workspace.ts` | AI Design V2 Workspace、Direction、Living Form、immutable Quote、Task、Asset 与事件共享契约 | Current Specification 是唯一语义权威Task/Asset 属于独立 Workspace resource projection |
| `shared/image-workspace.ts` | AI Design V2 Workspace、Direction、Living Form、immutable Quote、Task、Asset 与事件共享契约 | Current Specification 是唯一语义权威Task/Asset 属于独立 Workspace resource projectionpublic activity 是固定、operation-scoped presentation contract |
| `electron/api/routes/image-workspace.ts` | AI 绘画 Host API 与本地事件流投影 | Renderer 只通过该路由访问 Main-owned workspace adapter |
| `electron/image-workspace/works-square-workspace.ts` | Works Square V2 adapter、stable command/operation IDs 与 Direction event mapping | unknown transport result 保留精确命令Main 持有 token、ticket、WebSocket、retry classificationsafe errors |
| `src/stores/image-workspace.ts` | Current Workspace/Direction/Living Form、local drafts、pending operations 与 Workspace resources | revision conflict 刷新 canonical stateevent order/Task progress 不成为 Specification authority |
| `src/pages/ImageCanvas/{index,DesignConversationPane,DesignFormPane,DesignProductionPanel}.tsx` | 持久双栏 Canvas、conversation、Living Form、immutable Quote confirmation、Tasks 与 Assets | chat/direct edits/decisions/locks/bindings 使用同一 V2 reducer确认只提交 Quote ID |
| `electron/image-workspace/works-square-workspace.ts` | Works Square V2 adapter、stable command/operation IDs 与 Direction event mapping | unknown transport result 保留精确命令Main 持有 token、ticket、WebSocket、retry classificationsafe errors,并把闭集 progress stage 归一为固定 youth-safe copy |
| `src/stores/image-workspace.ts` | Current Workspace/Direction/Living Form、local drafts、pending operations、operation activity 与 Workspace resources | revision conflict 刷新 canonical stateactivity/delta/terminal event 按 Workspace、Direction 和 operation identity 收敛;event order/Task progress 不成为 Specification authority |
| `src/pages/ImageCanvas/{index,DesignConversationPane,DesignFormPane,DesignProductionPanel}.tsx` | 持久双栏 Canvas、conversation、operation-scoped AI activity、Living Form、immutable Quote confirmation、Tasks 与 Assets | chat/direct edits/decisions/locks/bindings 使用同一 V2 reducer固定活动附着于对应 user bubble 且不成为对话或右侧摘要;确认只提交 Quote ID |
| `src/components/layout/ImageWorkspaceSidebar.tsx` | Workspace 选择、创建与 Prompt Museum 入口 | 不再创建或选择嵌套 Conversation |
| `shared/image-prompt-museum.ts` | Prompt Museum 列表、分类、详情、署名与分页共享 DTO | 客户端不包含内容数据集,只定义服务端字段契约 |
| `electron/api/routes/image-prompt-museum.ts` | Main-owned Museum 列表/详情代理与 Works 登录态 | 仅 GET 固定路径和白名单查询Renderer 不获得 Bearer Token |
@@ -93,4 +93,4 @@
## Last Updated
2026-09-06
2026-09-07

View File

@@ -27,8 +27,8 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展
| Code-owned Official Project Plugins | Existing Account acquisition or system-included delivery → project enablement → effective parent snapshot | Data Service、Game Resource 与 Project Scaffold 都不要求 Agent assignment项目启用后自动进入每个父 Agentchild 不继承 Plugin。三者不经过设备下载、更新、Beta 或 artifact 签名Game Resource 的一次确认由 Main 提交一次、内部轮询并把全部终态输出自动写入冻结的原项目恢复本地交付不得重新生成或计费Project Scaffold 的 `.mjs` 仍只来自签名客户端固定资源。需要分配的 Marketplace 下载包保持原规则。 |
| Device Packages | Conversation install tools → Main-owned inspect/preview/confirm/commit → immutable local generation → parent Skill/Pi-extension resources | 支持 npm、Git、绝对本地 Plugin 目录与 loose `SKILL.md`没有可见安装入口、Account Library、Release、Admission 或 Marketplace Package Store。可执行 extension 与非空 Skill `scripts/` 拥有桌面用户权限,必须披露并独立确认;生命周期脚本禁用。每个 generation 包含所有显式安装且当前启用的 Skill/extension新/idle parent 自动刷新active parent 在 turn settled 后刷新child 始终为空。 |
| AI Design Workspace & Living Form | 一个 Workspace 的当前 Direction、Current Specification、持久 Agent Session、conversation timeline、Tasks 与 Assets | 自然对话是主创作面Living Form 仅以“AI 已理解”的紧凑辅助摘要与可选手动调整投影服务端 Current SpecificationRenderer 只持有草稿和已接受投影 |
| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer稳定 command/operation ID 支持 unknown-result 重放revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影assistant delta 只能在匹配 operation 的一个未完成助手气泡中临时绘制且不生成独立整理进度栏 |
| AI Design Gateway Routing | Main-owned Works Square V2 adapter 与 Direction event stream | Main 持有 Works Token、stream ticket、WebSocket、重试分类和错误脱敏事件顺序与 Task progress 不构成 Specification 真值 |
| AI Design Input & Reconciliation | Chat、字段/集合编辑、decision、proposal、lock、Asset binding 与 restore | 全部进入同一 `design.input.apply` reducer稳定 command/operation ID 支持 unknown-result 重放revision conflict 刷新权威状态;待提交 chat 从同一 pending operation 临时投影,固定 public activity 附着于该用户消息,assistant delta 只进入匹配 operation 的一个未完成助手气泡 |
| AI Design Gateway Routing | Main-owned Works Square V2 adapter 与 Direction event stream | Main 持有 Works Token、stream ticket、WebSocket、重试分类和错误脱敏Main 将闭集 `design.assistant.progress` 阶段归一为固定中文,不透传模型推理或任意服务端文案;事件顺序与 Task progress 不构成 Specification 真值 |
| AI Design Quote & Task Controls | 精确 Specification revision 编译出的不可变 Quote 与 Workspace Tasks | 客户端只展示 public output summary、warnings、expiry 与 Token Points并以 Quote ID 确认Provider Prompt/model/route/storage/billing atoms 不下发 |
| AI Design Assets | Workspace 已完成作品或本地上传的真实 Asset | Asset 通过 typed binding 写入 Specification生成结果经 Main-owned asset download 保存 |
| Prompt Museum | Canvas “获取灵感”页面 → Main Host API → Works Square | 服务端驱动的审核内容、筛选和分页Renderer 不持有 Works Token 或内置数据集,“使用此 Prompt”只回填输入框 |
@@ -94,6 +94,7 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展
- Renderer 的异步与流式结果必须核对 Workspace、Direction、revision 和 operation identityDirection snapshot 是 Specification 真值Task/Asset 事件只更新 Workspace resource projection。
- 待提交 chat 可以从现有 pending operation 立即投影为明确标注“发送中/正在确认”的用户气泡,但只有服务端 canonical turn 能进入对话历史;确定失败必须恢复原草稿,不能另建一套消息状态或新业务意图。
- `design.assistant.delta` 是未完成的传输状态,不是 canonical assistant turn。Renderer 只能把它临时绘制为与同一 pending chat identity 绑定的单个未完成助手气泡,并在该 operation 确定收敛后由 canonical turn 替换不得持久化为第二条消息、伪装为已完成回复或创建独立整理进度栏。unknown outcome 保留原 identity 与已有片段,其他 operation 的更新不得全局清除它;右侧 Current Specification 继续呈现 AI 当前整理出的设计理解。重叠连接与重放 chunk 必须按连接 generation 和 `chunkIndex` 收敛。
- `design.assistant.progress` 是与同一 pending chat identity 绑定的瞬时活动投影,可在 optimistic user bubble 下更新和折叠。它只能使用 Main 归一化的固定阶段,不得成为 conversation Turn、右侧 Current Specification 的副本、通用全局整理栏或 model chain-of-thought终态事件按原 operation identity 收敛它。
- 确认生成只提交不可变 Quote ID。Task 事务已提交但事件迟到时可刷新 Workspace projectionTask progress 不得改写 Living Form 或授权新的生成操作。
- Updater feed 选择、原始错误日志、下载和安装生命周期只属于 Electron Main。Renderer 不得把缺失稳定 manifest 投影为“已是最新版”也不得显示原始堆栈、URL、路径或错误码并发检查共享同一错误事件时只发送一次错误状态后续独立重试仍可重新报告。
- 图片与视频复用同一个单图来源选择器。图片 Brief 可从当前 Workspace 的已完成作品或本地上传中选择一张参考图继续生成;视频 Brief 使用同一入口绑定首帧。两条路径都必须通过现有 Workspace Asset 上传/选择契约提交一个真实 `attachmentAssetIds`,不得用本地路径或自然语言描述代替资产身份。
@@ -107,4 +108,4 @@ Makelore 是 Electron 桌面客户端。Renderer 负责项目操作与状态展
## Last Updated
2026-09-06
2026-09-07

View File

@@ -4,6 +4,23 @@ This file is the integrated default-branch snapshot. Feature tasks record progre
## Integrated Through
- AI Design operation activity source
`81b524a02c2cf51304090afa8fb89d27c72c5d2b` from task
`20260907-design-agent-activity-client-b6d913e4` is integrated by task
`20260907-integrate-design-agent-activity-b7d3f1a8`. Electron Main now
normalizes the server's four closed public stages into fixed youth-safe copy;
Renderer attaches one transient, update-in-place `AI 处理过程` panel to the exact
optimistic user message and collapses completed work without creating a chat Turn
or duplicating the right-side Current Specification. Workspace, Direction,
operation, pre-command turn sequence, connection generation, cursor, and terminal
events keep replay and stale results scoped; unknown outcomes retain the original
identity. Integration review additionally preserves prior completed/unknown activity
when a later chat starts and keeps each pending user's activity and provisional
reply together in visual order. Final committed assistant text continues through
the existing provisional assistant bubble and canonical Turn replacement. Focused verification passed 69
tests, TypeScript, changed-file ESLint, and all Vite production targets. No server,
database, billing, Quote/Task, navigation, package, deployment, publication, or paid
Provider call changed.
- Coding project setup UX source `8042d2be265e40288e628fcdc755e9f29151306b`
from task `20260906-project-setup-ux-90fe6cf2` is integrated onto local `main`
by task `20260906-integrate-project-setup-ux-4b7d2e91`. A Coding Conversation is
@@ -719,7 +736,7 @@ This file is the integrated default-branch snapshot. Feature tasks record progre
AI Design Canvas 现在默认以对话和一张持续可见的“我的创作”卡服务 8-16 岁创作者;专业字段矩阵收进按需打开的“精细调整”,移动端保持对话优先并只挂载一个卡片/底部面板。Creation Card、精细调整、Quote、Task 与结果提示都只投影权威状态,已知问题按 code 转成通俗中文,未知服务端或 Provider 文本不会直接显示。一个 Workspace 仍只公开一个 current Direction、一个 persistent Agent Session 和一个 Current Specificationconversation timeline 只记录交互历史。Chat、direct edits、decision responses、proposal acceptance、locks、Asset binding 与 restore 都通过 `design.input.apply` 进入同一服务端 reducerRenderer drafts 在 accepted 前保持本地。Main 持有 Works Token、stream ticket、WebSocket、request deadline、stable command/operation IDs 与错误脱敏unknown result 只能复用原 identity结构化业务错误不得重放。Generation 由服务端对 exact Specification revision 编译 immutable Quote客户端只展示 public output plan、warnings、expiry 与 Token Points并以 Quote ID 调用 `design.generation.confirm`Provider Prompt、model、route、storage 和 billing atoms 不进入 Renderer。Task/Asset events 独立收敛 Workspace resources不改写 Living Form。Development 与 packaged builds 均使用 Works Square V2V1 DTO、local semantic adapter、mutable Quote PATCH 与 editable provider Prompt 已移除。
新提交的 Design chat 会立即从现有 pending operation 投影为带“发送中”或“正在确认”的临时用户气泡,服务端确认的 canonical turn 到达后再替换它;确定失败时原草稿重新出现在输入框。`design.assistant.delta` 只作为内部传输与收敛状态,不显示原始片段,也不在对话区生成独立整理进度栏;右侧 Current Specification 摘要继续承担“AI 当前听懂的内容”。重复连接和事件按 connection generation 与 `chunkIndex` 收敛。
新提交的 Design chat 会立即从现有 pending operation 投影为带“发送中”或“正在确认”的临时用户气泡,服务端确认的 canonical turn 到达后再替换它;确定失败时原草稿重新出现在输入框。`design.assistant.progress` 经 Main 归一为固定通俗阶段只在对应用户气泡下形成可更新、可折叠的“AI 处理过程”,不成为对话消息、右侧摘要或模型思考过程。`design.assistant.delta` 仅把已验证并提交的回复临时绘制为同一 operation 的未完成助手气泡,随后由 canonical turn 替换;不生成独立全局整理栏。右侧 Current Specification 摘要继续承担“AI 当前听懂的内容”。重复连接和事件按 connection generation、operation identity`chunkIndex` 收敛。
Canvas 侧栏提供“获取灵感”进入 Prompt Museum。列表、筛选、分页、详情、作者/来源/许可证和图片地址全部由服务端经 Main-owned Host API 提供,客户端不打包静态数据集;服务端相对媒体只允许固定 `/api/image-prompt-museum/{entry}/media/{thumbnail|number}` 形状,并由 Main 注入 Works Bearer、执行一次 401 刷新、可信 raster MIME 与 10 MiB 上限后转为 Renderer data URLcredential-free HTTPS CDN 图片保持直连。图片失败只显示卡片内占位,不阻断卡片或详情;缺少来源 URL 时显示纯文本。“使用此 Prompt”只把原文带回当前 Canvas 输入框,不自动发送。该模块不是投稿、点赞、评论或排行榜社区。客户端契约已就绪,但不据此宣称 Works Square 内容后台和生产数据已经部署。`pnpm run dev` 现在默认使用云端 Canvas 适配器,本地适配器只能通过显式开发命令启用;产品 UI 只保留中文。
@@ -872,4 +889,4 @@ Robot 绑定设备默认先显示“引导配网 / 已有激活码”路径选
## Last Updated
2026-09-06
2026-09-07

View File

@@ -1,103 +0,0 @@
# Task: Render AI Design agent activity progress
## Identity
- Task ID: 20260907-design-agent-activity-client-b6d913e4
- Mode: Feature
- Branch: codex/20260907-design-agent-activity-client-b6d913e4-design-agent-activity-client
- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260907-design-agent-activity-client-b6d913e4
- Base commit: da29294979da4ce041edf992b590ee6ea170a44f
- Owner: codex-root
- Status: Ready for Integration
## Scope
- Consume operation-scoped AI Design activity events through the existing Main-owned
Works Square transport.
- Render one transient activity area attached to the optimistic user message, update
meaningful steps in place, and settle it correctly on final reply or terminal error.
- Cover reconnect/replay, transport-unknown, stale-operation, and compact responsive
presentation without duplicating the right-side Design summary.
## Intent And Constraints
- User messages remain immediately visible and final assistant replies remain normal
conversation content.
- Activity is presentation state, not canonical Design state or a durable chat bubble.
- Show at most a few youth-safe phase/action/outcome lines; never render raw reasoning,
tool arguments, prompts, provider output, or technical error details.
- Preserve stable operation identity across uncertain transport outcomes and ignore
progress that belongs to another Workspace/direction/operation.
## Project Context Loaded
- Project goal: a youth-friendly desktop creation flow with Electron Main as the only
network authority and server-owned Design Specification semantics.
- Current integrated focus: Living Form V2 and immediate optimistic user messages are
integrated; the experimental `050d78d` progress UI is not assumed present.
- Decisions: ADR-007 keeps Renderer drafts temporary and Gateway operations stable;
the activity view may observe events but cannot become another authority.
- Evidence/commitments: real paid Design production smoke remains separate and is not
claimed by this client presentation task.
- Relevant peers: the Enter-key E2E task is test-only; older generic-error tasks have
undefined scope. No semantic conflict was found.
- Likely modules: Main Workspace adapter/DTO mapping, Renderer store event reduction,
Design conversation pane, and focused unit/Electron E2E tests.
- Unknowns to resolve in source: current stream event DTO shape and the best existing
operation-to-optimistic-message association seam.
- Gate result: Passed.
## Outcome
- Added a dedicated `AI 处理过程` panel beneath the optimistic user message. Active
steps update in place; completed processing collapses into a compact, user-toggleable
summary while the final assistant reply continues to stream as normal conversation.
- Added Main-owned normalization for the four public progress stages. Main maps stages
to fixed youth-safe copy instead of trusting arbitrary server/provider text.
- Preserved exact operation association across optimistic, streaming, canonical, and
transport-unknown states by recording the pre-command turn sequence and matching the
earliest exact canonical user turn after that boundary.
- Added Gateway command terminal-event handling so accepted commands converge after a
polling disconnect. WebSocket-terminal-first races remain authoritative, stale
connections cannot mutate current state, duplicate chunks advance the replay cursor,
and late results from another Workspace cannot replace the active Workspace.
- Activity remains transient presentation state: it is filtered by Workspace and
direction, never becomes a chat message or right-side Design authority, and contains
no raw model reasoning, prompts, tool arguments, or technical provider output.
## Verification
- `pnpm exec vitest run tests/unit/image-workspace-store.test.ts
tests/unit/image-canvas-page.test.tsx
tests/unit/works-square-design-workspace.test.ts
tests/unit/image-workspace-route.test.ts` — 67 passed.
- `pnpm exec tsc --noEmit` — passed.
- Focused ESLint across all eight changed TypeScript/TSX files — passed.
- `pnpm run build:vite` — passed; only existing chunk-size, dynamic-import, and stale
Browserslist-data warnings were reported.
- `git diff --check` — passed.
- Independent client and lifecycle reviews were repeated after the terminal-race,
exact-turn-association, and Workspace A→B→A fixes — PASS; no reachable P1/P2
remained.
## Follow-ups
- Add an explicit snapshot/cursor reseed contract for a Gateway `cursor expired`
response in a separate transport-resilience task. That pre-existing recovery gap is
broader than the AI activity presentation slice.
## Promotion Candidates
- Target canonical documents: ADR-007 and the client/Main transport architecture.
- Proposal: document operation-scoped AI activity as transient Renderer state fed by
Main-normalized public Gateway events; generic Gateway terminal events must settle
uncertain writes without changing operation identity.
- Evidence: fixed public stage projection, exact turn association, terminal/poll race
tests, stale-source and cross-Workspace tests, 67 passing focused tests, typecheck,
lint, and production build.
- Future impact: future Agents SDK lifecycle hooks can reuse the same UI seam, while
canonical Design state continues to come only from Workspace snapshots/events.
- Semantic conflicts: none identified; this preserves ADR-007's Main ownership and
temporary Renderer-state boundaries.
- Human confirmation required: yes, during a future integration-mode promotion to
canonical architecture documentation.

View File

@@ -0,0 +1,64 @@
# Task: Integrate AI Design agent activity UI
## Identity
- Task ID: 20260907-integrate-design-agent-activity-b7d3f1a8
- Mode: Integration
- Branch: codex/20260907-integrate-design-agent-activity-b7d3f1a8-integrate-design-agent-activity
- Worktree: D:\Datas\OthersProjects\.codex-worktrees\makelore\20260907-integrate-design-agent-activity-b7d3f1a8
- Base commit: da29294979da4ce041edf992b590ee6ea170a44f
- Owner: codex-root
- Status: Ready for Integration
## Scope
- Integrate client source commit `81b524a02c2cf51304090afa8fb89d27c72c5d2b` from exact base `da29294979da4ce041edf992b590ee6ea170a44f`.
- Promote the operation-scoped AI Design activity projection into canonical client memory.
- Verify Main event normalization, Renderer reconciliation, conversation rendering, and existing Design streaming behavior.
## Intent And Constraints
- Render only the server's fixed public lifecycle stages beneath the matching optimistic user message.
- Keep `design.assistant.delta` as the single provisional assistant-text stream and the right-side Living Form as the Current Specification projection.
- Never render raw chain-of-thought, prompt/provider/tool payloads, or a duplicate global “整理进度” banner.
- Preserve stable operation identity and existing unknown-result reconciliation.
- Do not touch or adopt unrelated changes in the root worktree.
## Outcome
- Fast-forwarded the exact source commit from the recorded base without conflict.
- Integrated Main-normalized public Design activity, operation-scoped Renderer
reconciliation, terminal-event convergence, and the conversation-attached activity
panel while preserving optimistic user messages and canonical assistant replacement.
- Fixed two integration-review findings: a later chat no longer erases earlier
completed/unknown activity, and each pending operation now renders its user message,
activity, and provisional assistant stream as one ordered group.
- Promoted the accepted transient-activity boundary into ADR-007, architecture maps,
data flow, domain rules, and current integrated state.
- No server contract, database, billing, Quote/Task, navigation, package, deployment,
publication, or Provider behavior changed.
## Verification
- `pnpm exec vitest run tests/unit/image-workspace-store.test.ts
tests/unit/image-canvas-page.test.tsx
tests/unit/works-square-design-workspace.test.ts
tests/unit/image-workspace-route.test.ts` — 69 passed.
- `pnpm exec tsc --noEmit` — passed.
- Focused ESLint across all eight changed TypeScript/TSX files — passed.
- `pnpm run build:vite` — passed; only existing chunk-size, dynamic-import, and
stale Browserslist-data warnings were emitted.
- `git diff --check` — passed.
- Independent integration review — PASS after the two multi-operation findings were
fixed and covered.
## Follow-ups
- Add an explicit snapshot/cursor reseed contract for a Gateway `cursor expired`
response in a separate transport-resilience task.
## Promotion Candidates
- Applied: ADR-007, system overview, module map, data flow, domain rules, and current
integrated state now define operation-scoped public activity as transient
presentation rather than Design authority or model reasoning.

View File

@@ -125,6 +125,7 @@
- Direction projection 是 Specification 真值。event cursor、assistant delta、Task progress 与 Asset event 只用于传输/资源收敛,不得推进或覆盖 canonical specification revision。
- 用户提交 chat 后,客户端可以立即把同一个 pending operation 投影为临时用户气泡,并明确显示“发送中”或“正在确认”;只有服务端返回的 canonical turn 才进入 conversation timeline。确定失败时必须让原草稿重新可编辑不得把临时投影持久化为第二条消息。
- `design.assistant.delta` 只用于未完成回复的传输和结果收敛,不得进入 canonical conversation timeline。客户端可以把它临时显示为与同一 pending chat identity 绑定的单个未完成助手气泡;确定收敛后必须由 canonical turn 替换unknown outcome 则保留原 identity 与已有片段。不得把片段伪装为完成回复、持久化为第二条消息、因其他 operation 更新而全局清除或另加独立的整理进度栏。AI 当前整理出的设计理解仍由右侧 Current Specification 投影承载;连接重叠或事件重放按 connection generation 与 `chunkIndex` 去重收敛。
- `design.assistant.progress` 只能由 Main 把服务端闭集 stage 归一为固定、通俗的活动文案,并附着在同一 pending chat identity 的 optimistic user bubble 下。它是可更新、可折叠的瞬时 presentation state不是消息、Current Specification、任务授权或模型思考过程不得显示 chain-of-thought、prompt、Provider response、tool argument、任意服务端文案或未验证模型文本。终态、重连和重放必须按原 operation identity 收敛。
- Main 只向 Renderer 投影已知错误码的固定中文提示未知上游错误文本必须脱敏为通用提示Works Token、stream ticket、provider internals 留在 Main/服务端。
- AI 绘画 Workspace JSON 请求与 shared Works token refresh 必须覆盖取凭据、发请求和读取响应 body 的完整 30 秒 deadline即使底层 transport 忽略 abort调用方也必须确定性结束为 `504 DESIGN_WORKSPACE_REQUEST_TIMEOUT` 并释放共同等待者。Electron-to-Node 透明 fallback 仅允许 `GET`/`HEAD`/`OPTIONS`mutation 不得因 transport failure 被隐式重放,任何重试必须由上层显式幂等合同授权。
- `design.quote.request` 必须绑定 exact current Specification revision 并返回 immutable Quote任何 production-meaning edit 都需要新 revision 与新 Quote。对话或 Current Specification 就绪态可以引导用户请求并查看 Quote桌面端可定位到报价区、移动端可打开报价面板但该动作不得创建 Task。
@@ -169,4 +170,4 @@
## Last Reviewed
2026-09-06
2026-09-07

View File

@@ -172,6 +172,9 @@ export function DesignConversationPane({
const text = assistantStreams[message.operationId]?.trim();
return text ? [{ ...message, text }] : [];
});
const streamingReplyByOperationId = new Map(
streamingReplies.map((reply) => [reply.operationId, reply.text]),
);
const projectedDraft = chatDraft.trim() !== ''
&& pendingChatMessages.some((message) => message.message === chatDraft.trim());
const composerDraft = projectedDraft ? '' : chatDraft;
@@ -290,6 +293,7 @@ export function DesignConversationPane({
{pendingChatMessages.map((message) => {
const linkedActivity = activityByOperationId.get(message.operationId);
const streamingReply = streamingReplyByOperationId.get(message.operationId);
return (
<div
key={message.operationId}
@@ -318,27 +322,25 @@ export function DesignConversationPane({
operationId={message.operationId}
/>
)}
{streamingReply && (
<div
data-testid={`streaming-design-assistant-${message.operationId}`}
role="status"
aria-live="polite"
aria-atomic="true"
className="mr-auto max-w-[92%] rounded-2xl rounded-bl-md border border-brand/20 bg-background px-3.5 py-3 text-sm leading-6 text-foreground shadow-sm"
>
{streamingReply}
<span
aria-hidden="true"
className="ml-1 inline-block h-3.5 w-1 animate-pulse rounded-full bg-brand align-middle"
/>
</div>
)}
</div>
);
})}
{streamingReplies.map((reply) => (
<div
key={reply.operationId}
data-testid={`streaming-design-assistant-${reply.operationId}`}
role="status"
aria-live="polite"
aria-atomic="true"
className="mr-auto max-w-[92%] rounded-2xl rounded-bl-md border border-brand/20 bg-background px-3.5 py-3 text-sm leading-6 text-foreground shadow-sm"
>
{reply.text}
<span
aria-hidden="true"
className="ml-1 inline-block h-3.5 w-1 animate-pulse rounded-full bg-brand align-middle"
/>
</div>
))}
{canRequestQuote && pendingChatMessages.length === 0 && (
<div
data-testid="design-conversation-quote-action"

View File

@@ -550,12 +550,13 @@ export const useImageWorkspaceStore = create<ImageWorkspaceState>((set, get) =>
const assistantActivities = isChatCommand(command)
? existingActivity
? {
...state.assistantActivities,
[operation.id]: {
...existingActivity,
status: 'active' as const,
},
}
: {}
: state.assistantActivities
: state.assistantActivities;
return {
pendingOperations: { ...state.pendingOperations, [operation.id]: operation },

View File

@@ -198,6 +198,63 @@ describe('youth AI Design Canvas page', () => {
.toBeInTheDocument();
});
it('keeps each pending reply beside the user message that started it', () => {
prepareWorkspace({ turns: [] });
useImageWorkspaceStore.setState({
pendingOperations: {
'operation-chat-1': {
id: 'operation-chat-1',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-1',
input: { kind: 'chat', message: '先画一只小熊' },
},
status: 'unknown',
error: '结果尚未确认',
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
'operation-chat-2': {
id: 'operation-chat-2',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-2',
input: { kind: 'chat', message: '再补充一只小兔子' },
},
status: 'submitting',
error: null,
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
assistantStreams: {
'operation-chat-1': '小熊会站在月亮旁边。',
},
});
render(<ImageCanvas />);
const conversation = screen.getByTestId('image-workspace-conversation');
const firstMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-1');
const firstReply = within(conversation)
.getByTestId('streaming-design-assistant-operation-chat-1');
const secondMessage = within(conversation).getByTestId('pending-design-chat-operation-chat-2');
expect(firstMessage.compareDocumentPosition(firstReply) & Node.DOCUMENT_POSITION_FOLLOWING)
.toBeTruthy();
expect(firstReply.compareDocumentPosition(secondMessage) & Node.DOCUMENT_POSITION_FOLLOWING)
.toBeTruthy();
});
it('shows public AI activity under the submitted message and collapses it beside the final turn', async () => {
const message = '画一只在月球踢球的熊猫';
const { workspace } = prepareWorkspace({ turns: [] });

View File

@@ -1206,6 +1206,64 @@ describe('V2 Living Form store', () => {
.toMatchObject({ status: 'unknown', steps: [{ stage: 'reviewing_context' }] });
});
it('preserves an earlier unknown activity when another chat starts', async () => {
await loadedStore();
const submission = deferred<Awaited<ReturnType<typeof submitImageWorkspaceCommand>>>();
operationIdMock.mockReturnValueOnce('operation-chat-2');
submitCommandMock.mockReturnValueOnce(submission.promise);
useImageWorkspaceStore.setState({
chatDraft: '再补充一只小兔子',
pendingOperations: {
'operation-chat-1': {
id: 'operation-chat-1',
label: '发送创作想法',
command: {
kind: 'apply_input',
workspaceId: 'workspace-1',
sessionId: 'session-1',
expectedDirectionRevision: 4,
clientOperationId: 'operation-chat-1',
input: { kind: 'chat', message: '先画一只小熊' },
},
status: 'unknown',
error: '结果尚未确认',
clearDraftPaths: [],
clearChatDraft: true,
baseRawTurnSequence: 1,
},
},
assistantActivities: {
'operation-chat-1': {
workspaceId: 'workspace-1',
directionId: 'direction-1',
status: 'unknown',
turnId: null,
steps: [{ stage: 'reviewing_context', message: '正在整理上下文' }],
},
},
});
const sendPromise = useImageWorkspaceStore.getState().sendChat();
expect(useImageWorkspaceStore.getState().assistantActivities).toMatchObject({
'operation-chat-1': {
status: 'unknown',
steps: [{ stage: 'reviewing_context' }],
},
});
expect(useImageWorkspaceStore.getState().pendingOperations).toMatchObject({
'operation-chat-1': { status: 'unknown' },
'operation-chat-2': { status: 'submitting' },
});
submission.resolve({
clientOperationId: 'operation-chat-2',
runId: 'run-2',
workspace: designWorkspaceFixture(),
});
await sendPromise;
});
it('expires quote blockers only after the canonical Specification changes', async () => {
await loadedStore();
const blocker = {