docs: add brainstorm/plan decision artifacts + plan progress update

Add ce-brainstorm requirements doc and ce-plan plan doc for private board restrictions and scheme B bubbles (decision artifacts). Update 2026-07-02-002 plan with U6/U7 progress table. Add .compound-engineering/config.local.example.yaml from ce-setup. gitignore tmp_*.html and delete_old_cluster.sh.
This commit is contained in:
chiguyong 2026-07-02 21:27:20 +08:00
parent 8188e8861d
commit 96f459c27d
5 changed files with 610 additions and 5 deletions

View File

@ -0,0 +1,49 @@
# Compound Engineering -- local config
# Copy to .compound-engineering/config.local.yaml in your project root.
# All settings are optional. Invalid values fall through to defaults.
# --- Work delegation (Codex) ---
# work_delegate: codex # codex | false (default: false)
# work_delegate_consent: true # true | false (default: false)
# work_delegate_sandbox: yolo # yolo | full-auto (default: yolo)
# work_delegate_decision: auto # auto | ask (default: auto)
# work_delegate_model: gpt-5.4 # any valid codex model (omit to use ~/.codex/config.toml default)
# work_delegate_effort: high # minimal | low | medium | high | xhigh (omit to use ~/.codex/config.toml default)
# --- Product pulse ---
# Settings written by /ce-product-pulse first-run interview. Re-run the skill with
# argument `setup` or `reconfigure` to edit interactively.
# pulse_product_name: "Spiral" # used in report titles (no default)
# pulse_lookback_default: 24h # 1h | 24h | 7d | 30d (default: 24h)
# pulse_primary_event: "session_started" # the event that means "user showed up"
# pulse_value_event: "task_completed" # the event that means "user got value"
# pulse_completion_events: "onboarded,first_purchase" # comma-separated, 0-3 events
# pulse_quality_scoring: false # true | false (default: false; AI products only)
# pulse_quality_dimension: "answer accuracy" # dimension scored 1-5 when pulse_quality_scoring is true
# pulse_analytics_source: posthog # posthog | mixpanel | custom (no default)
# pulse_tracing_source: sentry # sentry | datadog | custom (no default)
# pulse_payments_source: stripe # stripe | custom (no default)
# pulse_db_enabled: false # true | false (default: false; read-only DB if true)
# pulse_metric_sources: "retention_d7=posthog,nps=delighted" # strategy-metric -> source overrides; comma-separated 'metric=source' pairs; unlisted metrics fall back to pulse_analytics_source
# pulse_pending_metrics: "retention_d7,nps" # comma-separated strategy metrics awaiting instrumentation; render as 'no data'
# pulse_excluded_metrics: "north_star" # comma-separated strategy metrics intentionally not in pulse
# --- Output format ---
# Per-skill output format default. Selects the exclusive format the artifact
# is written in: `md` produces a markdown file, `html` produces a single
# self-contained HTML file. The two are mutually exclusive -- there is no
# sibling artifact. See DESIGN.md or your agent instructions to influence
# HTML styling. CLI arguments override these defaults; pipeline contexts
# (e.g., LFG, disable-model-invocation) always force `md` regardless.
# plan_output: html # md | html (default: md)
# brainstorm_output: html # md | html (default: md)
# ideate_output: md # md | html (default: html -- ideation docs are human-facing, so HTML is the default; set md to opt out)
# --- ce-promote ---
# Written automatically when you decline the Spiral setup offer in /ce-promote.
# Suppresses that one-time setup nudge in this project. Remove the key to re-enable.
# ce_promote_spiral_optout: true # true | (absent) (default: absent -- offer once)

4
.gitignore vendored
View File

@ -57,3 +57,7 @@ data/
# Knowledge graph tooling (local-only, generated index) # Knowledge graph tooling (local-only, generated index)
.understand-anything/ .understand-anything/
# Local temp files
tmp_*.html
/delete_old_cluster.sh

View File

@ -0,0 +1,78 @@
---
date: 2026-07-02
topic: private-board-restrictions-and-scheme-b-bubbles
---
## Summary
收尾私董会(@board模块的 UI 细节与单会话状态约束。三处改动:(1) 限制一个对话只能存在一个私董会,新建私董会必须从新会话发起;(2) 将 `BoardBannerCard`(私董会开始卡片)从带边框 / 紫条 / 进度条 / 专家 chip 的重样式简化为单行标题 + 副标题;(3) 给所有 `AssistantText` 渲染添加方案 B 风格的浅灰圆角矩形气泡,与方案 B 截图保持一致。
## Problem Frame
私董会功能自 2026-06 上线以来已能正确触发多轮讨论,但 UI 上仍残留三处粗糙点:
1. **单会话多私董会无约束**。`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`,对当前会话是否已经存在私董会无任何判断。后端在 `board_started` 事件中没有按会话做去重,连续两次 `SendMessage("@board:...")` 会在同一会话里创建第二个私董会,叠加在第一个未结束的私董会之上,`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
2. **`BoardBannerCard` 样式过重**。`BoardBannerCard.vue:55-137` 使用了 `background / border / border-radius / box-shadow` 四件套 + 4px 紫条 + 进度条 + 专家 chip pill与方案 B 整体"克制、不重样式"的取向冲突。方案 B 截图(参考 `docs/.../2026-06-18-chat-area-vi-redesign-requirements.md` 中的方案 B 示意)的"开始"标题区域是单行文本,不带装饰。
3. **方案 B 气泡未落地**。方案 B 截图中的"专家发言"区域是**有**浅灰圆角矩形气泡包裹内容(不是无气泡),与 ChatGPT / Notion AI 风格一致。当前 `AssistantText.vue:1-30` 的内容区 `.assistant-text` 没有背景 / 边框 / 圆角,气泡效果完全缺失。
## Key Decisions
- **私董会限制的"已存在"判断以 `boardState.status` 为准**`status === 'discussing' | 'concluding'` 时禁止在当前会话再次发起;`status === 'completed' | 'dissolved' | null` 时允许(已完成 / 已解散的旧私董会不阻塞新私董会)。判断点放在 `ChatInput.vue` "私董会"按钮 `@click` 处(最自然的 UX 拦截点),不放在 `BoardMeetingModal` 内部(避免用户填表后才发现不能发起)。
- **私董会限制的反馈方式是 a-modal 弹窗 + 快捷新建按钮**。点击"私董会"按钮时若检测到已有私董会,弹出 `a-modal`,标题"当前会话已存在私董会",副文"请新建会话来创建新的私董会",按钮"我知道了" + "新建会话"。"新建会话"按钮直接调用 `chatStore.createConversation()``chatStore.selectConversation(newId)`,让用户立即在新会话里继续操作。
- **BoardBannerCard 简化为单行标题 + 副标题**。完全去掉 `BankOutlined` 图标、专家 chip 列表、4px 紫条、进度条、卡片背景 / 边框 / 圆角 / 阴影。最终输出形如:
```
私董会 — 利用 agent 实现私董会的功能,应该用什么功能来打动客户
轮次:第 1 / 5 轮
```
标题字号 = `var(--font-base)` 加粗 + 主题文本;副标题字号 = `var(--font-xs)` + `var(--text-tertiary)`。`StickyModeHeader` 顶部的紫色"私董会"徽章 + 主题 + 专家头像组保持不变,承担需要"重样式"的展示职责。
- **方案 B 浅灰气泡应用到所有 assistant 消息**。具体范围:`role === 'assistant'` 的所有 `MessageShell` 内的内容(普通 chat、@team 阶段、@board 发言、Debate、Plan exec、Tool result 等)都加同款浅灰圆角矩形气泡;`role === 'user'` 的用户消息气泡保留现有 `UserBubble.vue` 的右对齐独立样式,不加 AssistantText 风格的浅灰块。气泡使用 token 颜色(`var(--bg-secondary)` 或 `var(--bg-elevated)` 系)+ 圆角 `var(--radius-md)` + 内边距 `var(--space-3) var(--space-4)`,确保与方案 B 截图视觉一致;颜色与边框用 CSS 变量绑定,**禁止硬编码** `#f3f4f6` / `#fbfbfa` / `#ededec` 等值。
- **气泡内的代码块、表格、行内代码样式保持不变**。`AssistantText.vue:257-401` 的 `pre / hljs / code / table` 样式已经在 dark-on-light 配色上做了适配(`--code-bg` / `--code-fg` / `--code-keyword` 等 token气泡背景换浅灰后这些 token 自动适配,不需要单独再改。
- **不触碰 StickyModeHeader 顶部条**。顶部条的紫色边框、徽章、4 个专家头像等不属于本次改动范围。
## Requirements
### 单会话私董会限制
- R1. `ChatInput.vue` 的"私董会"按钮 `@click` 处理函数在打开 `BoardMeetingModal` 前,先检查 `chatStore.boardState.value`:若非 null 且 `status === 'discussing' | 'concluding'`,触发 a-modal 弹窗,**不**打开 `BoardMeetingModal`
- R2. a-modal 弹窗内容:标题"当前会话已存在私董会",副文案"请新建会话来创建新的私董会",按钮"我知道了"(关闭弹窗)+ "新建会话"(主操作)。"新建会话"按钮点击后:(a) 关闭弹窗;(b) 调用 `chatStore.createConversation()`(c) `await chatStore.selectConversation(newId)`(d) 不自动打开 `BoardMeetingModal`,由用户在新会话中再次点击"私董会"按钮继续。
- R3. 若 `boardState.value === null``status === 'completed' | 'dissolved'`,保持当前行为:`showBoardModal = true` 直接打开 `BoardMeetingModal`,不弹提示。
- R4. 判定不依赖后端 / `is_board` 标记 / `conv.is_board`——以前端 `chatStore.boardState.value` 实时状态为权威源,避免 reload 后误判。
### 私董会开始卡片简化
- R5. `BoardBannerCard.vue` 重构为单行标题 + 副标题:保留 `topic / maxRounds / currentRound` props向后兼容调用方不再使用 `experts` props删除 prop
- R6. 模板输出仅两行:第一行 `私董会 — {topic}`(无 BankOutlined、无边框、无背景、无圆角、无 4px 紫条);第二行 `轮次:第 {currentRound} / {maxRounds} 轮`(小灰字)。
- R7. `<style scoped>` 中删除 `.board-banner-card` / `.board-banner-card__bar` / `.board-banner-card__chip` 等所有重样式相关类;保留 `.board-banner-card__title` / `.board-banner-card__meta` 两条最小样式。
- R8. `useMessageRenderer.ts``board_started` 的渲染路径不变(仍然渲染 `BoardBannerCard` 组件),确保 streaming 期间与 reload 后的视觉一致。
### AssistantText 浅灰气泡
- R9. `MessageShell.vue``.message-shell__content` 增加浅灰气泡样式:仅当 `role === 'assistant'` 时生效(`user` 角色不触发);`background: var(--bg-secondary)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`
- R10. 气泡样式**不**使用 `!important`,不覆盖组件内已有的代码块 / 表格 / 路由 tag 样式(这些样式已经在 R-9 之外独立维护)。
- R11. 浅灰气泡不影响 `BoardRoundCard``AssistantText` 的渲染(已统一走 `MessageShell` 槽位)。
- R12. 私董会专家发言(`board_speech` / `board_summary`)、普通 chat assistant 消息、@team 阶段消息、Debate 消息都通过 `MessageShell` + `AssistantText` 渲染,因此统一获得 R-9 浅灰气泡,无需逐个组件加样式。
- R13. 气泡宽度继承 `.message-shell__content` 的现有 `width: 100%; max-width: 100%`——气泡跟随消息列宽自适应,不强制固定最大宽度。
## Scope Boundaries
### Deferred for later
- **后端按会话去重 `board_started`**:本次只做前端拦截,**不**改后端逻辑。后端允许同一会话收到多次 `board_started` 是当前事实,本次不做接口变更;前端的"未在新会话发起"拦截在功能上等价于"阻止重复发起"。如果未来需要在多端 / 多浏览器同步场景下做强一致,再考虑后端校验。
- **StickyModeHeader 顶部条的视觉细化**(徽章大小、专家头像间距、紫色边框粗细)不在本次范围。
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**:本次只改 `BoardBannerCard`,其他卡片样式留待后续迭代。
### Outside this product's identity
- 不调整方案 B 调色板(`expertIdentity.ts` 的 12 色 PALETTE和专家头像首字符规则。
- 不调整私董会后端流程(`BoardOrchestrator` / `chatStream` 事件顺序)。
- 不调整 AssistantText 的 markdown 渲染逻辑、代码高亮、表格样式。
## Dependencies / Assumptions
- 假设:`chatStore.createConversation()` 存在并返回新会话 id`chatStore.ts:310` 已确认)。
- 假设:`chatStore.selectConversation(id)` 可异步调用(`chatStore.ts:218` 已确认)。
- 假设:`BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"``chatStream.ts:65` 已确认),"已存在私董会"判断取 `discussing | concluding`
- 假设:`--bg-secondary` / `--radius-md` / `--space-3` / `--space-4` / `--border-color` / `--text-primary` 已在 `tokens.css` 中定义并被前端使用(`styles/tokens.css` 已确认存在)。
- 不确定性:私董会可能存在的中间状态(`forming` / `executing` / `synthesizing` 等)目前不在 `BoardState.status` 联合中;如果未来增加新 status本次"已存在"判断需扩展。

View File

@ -0,0 +1,393 @@
---
date: 2026-07-02
type: feat
title: 私董会单会话限制 + 方案B 气泡 + 简化开始卡片
origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md
status: ready
---
## Summary
收尾私董会(@board模块的 UI 细节与单会话状态约束,四处协同改动:(1) 在 `ChatInput.vue` 的"私董会"按钮 click 处拦截"当前会话已存在进行中的私董会"场景,弹 a-modal 提示并提供快捷新建会话按钮;(2) 将 `BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为单行标题+副标题;(3) 给 `MessageShell.vue` 中所有 `role === 'assistant'` 的消息内容添加方案 B 风格的浅灰圆角矩形气泡F1-A 独立 token / F4-A 排除 conclusion / D4-方案1 `:empty` 隐藏);(4) 将 `UserBubble.vue` 普通文本消息改为 demo 中的深色右对齐气泡(`--color-primary` + `--text-inverse`@board/@team 命令卡片保持现有浅色背景。改动范围仅限前端 Vue 组件与少量 store/type/token 文件,不动后端、不动方案 B 调色板、不动 StickyModeHeader 顶部条。
## Problem Frame
私董会功能上线后存在三处 UI 粗糙点(详见 origin: docs/brainstorms/2026-07-02-private-board-restrictions-and-scheme-b-bubbles-requirements.md
1. **单会话多私董会无约束**`ChatInput.vue:75` 的"私董会"按钮 `@click` 直接 `showBoardModal = true`,对当前会话是否已存在私董会无任何判断。连续两次 `SendMessage("@board:...")` 会创建第二个私董会,叠加在第一个未结束的私董会之上,`boardState.experts` 被覆盖、轮次错乱、`StickyModeHeader` 头像数与实际不符。
2. **BoardBannerCard 样式过重**`BoardBannerCard.vue` 使用了 card+border+shadow+4px 紫条+进度条+专家 chip pill 等装饰,与方案 B 整体"克制、不重样式"取向冲突。方案 B 截图中的"开始"区域是单行文本,不带装饰。
3. **方案 B 气泡未落地**:方案 B 截图中专家发言区域**有**浅灰圆角矩形气泡包裹内容,与 ChatGPT / Notion AI 风格一致。当前 `MessageShell.vue:178-184``.message-shell__content` 没有背景/边框/圆角,气泡效果完全缺失。
## Requirements
完整继承 origin 文档的 13 条 R-IDsR1-R13并新增 R14-R19F4-A / D4-方案1 / U4 决策固化),共 19 条,分组如下:
**单会话私董会限制R1-R4**
- R1`ChatInput.vue` "私董会"按钮 `@click` 在打开 `BoardMeetingModal` 前检查 `chatStore.boardState``status === 'discussing' | 'concluding'` 时弹 a-modal**不**打开 modal
- R2a-modal 标题"当前会话已存在私董会",副文"请新建会话来创建新的私董会",按钮"我知道了"+ "新建会话"(主操作);"新建会话"流程:关 modal → `chatStore.createConversation()``chatStore.selectConversation(newId, true)` → 不自动打开 modal
- R3`boardState === null` 或 `status === 'completed' | 'dissolved'` 时保持当前行为
- R4以前端 `chatStore.boardState` 为权威源,不依赖后端 / `is_board` 标记
**BoardBannerCard 简化R5-R8**
- R5重构为单行标题+副标题,保留 `topic / maxRounds / currentRound` props 向后兼容,删除 `experts` prop
- R6模板输出两行`私董会 — {topic}` + `轮次:第 {currentRound} / {maxRounds} 轮`
- R7删除 `.board-banner-card` 的重样式background/border/border-radius/box-shadow`__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding+ `__title / __meta` 最小样式
- R8`useMessageRenderer.ts` 中 `board_started` 渲染路径不变
**AssistantText 浅灰气泡R9-R15**
- R9`MessageShell.vue` 的 `.message-shell__content``background: var(--bg-message-bubble)` + `border-radius: var(--radius-md)` + `padding: var(--space-3) var(--space-4)` + `border: 1px solid var(--border-color)` + `color: var(--text-primary)`,仅 `role === 'assistant'` 时生效。**F1-A 决策**:引入独立 token `--bg-message-bubble`light `#ffffff` / dark `#1f1f1f`),与 `--bg-secondary`inline code/table 背景)解耦,避免气泡背景与代码/表格背景视觉冲突
- R10不使用 `!important`,不覆盖代码块/表格/路由 tag 样式
- R11不影响 `BoardRoundCard``AssistantText` 渲染
- R12私董会专家发言、普通 chat、@team 阶段、Debate 等都通过 `MessageShell + AssistantText` 统一获得气泡
- R13气泡宽度继承现有 `width: 100%; max-width: 100%`,不强制固定最大宽度
- R14**F4-A 决策**:气泡选择器排除 `board_conclusion` 类型——`BoardConclusionCard` 自带 card chromebackground/border/border-radius/shadow保留其自身样式不再被气泡包裹。实现方式`MessageShell.vue` 通过 `messageType` prop 或 `:not(:has(.board-conclusion-card))` 选择器排除
- R15**D4-方案1 决策**:空 slot 内容pre-stream thinking / tool-call-only时气泡不渲染背景——通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。有内容流入后自动恢复气泡样式
**UserBubble 普通文本深色气泡R16-R19**
- R16`UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`background: var(--color-primary)` + `color: var(--text-inverse)` + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`
- R17@board/@team 命令卡片(`.user-bubble__command`)和文件附件保持现有 `--bg-tertiary` 浅色背景,不应用深色气泡样式
- R18dark mode 自动反转——`--color-primary` 在 dark mode 下为 `#fbfbfa`(浅),`--text-inverse` 为 `#1a1a1a`user 气泡自动变为浅色背景 + 深色文字
- R19通过 `isPlainText` computed`!fileAttachment && !commandBubble`+ `.user-bubble--text` modifier class 区分普通文本与命令卡片,不修改 `.user-bubble` 默认样式
## Key Technical Decisions
- **拦截点选择 ChatInput 按钮 click不放在 BoardMeetingModal 内部** — 最自然的 UX 拦截点,避免用户填表后才发现不能发起。判断依据 `chatStore.boardState` 实时状态,不依赖后端 / `is_board` 标记,避免 reload 后误判(见 origin R4
- **私董会状态判定取 `discussing | concluding`**`BoardState.status` 类型为 `"discussing" | "concluding" | "completed" | "dissolved"`chatStream.ts:65 确认)。`completed | dissolved` 的旧私董会不阻塞新私董会发起,符合 origin R3 要求。
- **"新建会话"按钮流程:`createConversation()` → `selectConversation(newId, true)`** — `createConversation()` 是同步函数chatStore.ts:333 确认),自动设置 `currentConversationId` 但不加载服务端会话;后续 `selectConversation(newId, true)` 强制 reload 确保状态干净。不自动打开 `BoardMeetingModal`,由用户在新会话中再次点击"私董会"按钮继续(见 origin R2
- **BoardBannerCard 简化为单行标题+副标题,不删除组件本身** — 保留 `BoardBannerCard.vue` 文件,仅重构 template + style。`useMessageRenderer.ts` 的 `board_started` 渲染路径不变origin R8保持 streaming 期间与 reload 后视觉一致。
- **气泡样式挂在 `MessageShell.vue` 的 `.message-shell__content` 上,不挂在 `AssistantText.vue`**`MessageShell` 是所有 assistant 消息的统一外壳,挂在这里可一次性覆盖 board_speech / board_summary / 普通聊天 / @team / Debate 等场景origin R12且不影响 `UserBubble.vue` 的 user 消息样式。**board_conclusion 例外**F4-A`BoardConclusionCard` 自带 card chrome不被气泡包裹。
- **气泡使用 CSS 变量,禁止硬编码** — 新增 `--bg-message-bubble: #ffffff` (light) / `#1f1f1f` (dark) 到 `tokens.css`,与 `--bg-secondary`inline code/table 背景 `#fbfbfa`解耦F1-A 决策。dark mode 自动切换。禁止硬编码 `#f3f4f6` / `#fbfbfa` / `#ffffff` 等值project rules 硬约束)。
- **`role === 'assistant'` 时启用气泡,`role === 'user'` 不启用** — 通过 `.message-shell--assistant .message-shell__content` 选择器限定,避免污染 user 消息的 `UserBubble` 样式。
- **F4-A 排除 conclusion 类型**`board_conclusion` 消息渲染的 `BoardConclusionCard` 自带 `background: var(--bg-primary)` + `border` + `border-radius: var(--radius-card)` + `box-shadow`,气泡选择器需排除该类型,避免"白卡片嵌套在白气泡里"的视觉冲突。实现方式:在 `MessageShell.vue` 增加 `messageType` prop`:not(:has(.board-conclusion-card))` 选择器conclusion 类型不加气泡样式。
- **D4-方案1 `:empty` 隐藏空气泡** — 空 slot 内容pre-stream thinking / tool-call-only时通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots 动画。有内容流入后自动恢复气泡样式,无需 JS 判断。
- **U4 仅普通文本消息深色气泡,命令卡片保持浅色**`UserBubble.vue` 同时渲染三种内容(普通文本 / @board|@team 命令卡片 / 文件附件命令卡片含结构化信息header + experts list深色背景会降低可读性。通过 `isPlainText` computed + `.user-bubble--text` modifier 精确限定深色样式仅作用于普通文本,命令卡片和文件附件继承 `.user-bubble` 默认 `--bg-tertiary` 浅色背景。
- **U4 用 `--color-primary` + `--text-inverse` 自动适配 dark mode**`--color-primary` 在 light mode 为 `#1a1a1a`dark mode 为 `#fbfbfa`(浅);`--text-inverse` 反之。user 气泡在 dark mode 下自动反转为浅色背景 + 深色文字,无需额外 dark mode 覆盖。
## Implementation Units
### U1. ChatInput 拦截 + a-modal 弹窗 + 快捷新建会话
**Goal:** 在 `ChatInput.vue` 的"私董会"按钮 click 处增加拦截逻辑:当前会话已存在进行中的私董会时弹 a-modal 提示,提供"我知道了"和"新建会话"两个按钮;"新建会话"按钮调用 `chatStore.createConversation()` + `selectConversation(newId, true)` 跳转到新空会话。
**Requirements:** R1, R2, R3, R4
**Dependencies:** 无
**Files:**
- src/agentkit/server/frontend/src/components/chat/ChatInput.vue修改
- src/agentkit/server/frontend/src/components/chat/\_\_tests\_\_/ChatInput.test.ts新建
**Approach:**
- 在 `<template>` 中新增一个 `<a-modal v-model:open="showBoardBlockModal">`,标题"当前会话已存在私董会",内容"请新建会话来创建新的私董会"footer 两个按钮:`<a-button @click="showBoardBlockModal = false">我知道了</a-button>` + `<a-button type="primary" @click="handleCreateNewConversationForBoard">新建会话</a-button>`
- 新增 `showBoardBlockModal = ref(false)` state
- 新增 `handleBoardClick()` 函数替代 inline `@click="showBoardModal = true"`
- 检查 `chatStore.boardState?.status`:若为 `'discussing' | 'concluding'``showBoardBlockModal.value = true`return
- 否则 `showBoardModal.value = true`
- 新增 `handleCreateNewConversationForBoard()` 函数:
- `showBoardBlockModal.value = false`
- `chatStore.createConversation()`
- `await chatStore.selectConversation(chatStore.currentConversationId!, true)`
- 不设置 `showBoardModal.value = true`(让用户在新会话中再次点击)
- "私董会"按钮 `@click` 改为 `handleBoardClick`
**Patterns to follow:**
- `ChatInput.vue:63-71``showTeamModal` 模式v-model:open + ref + handleXxxSubmit
- `chatStore.ts:333-345``createConversation` 同步签名
- `chatStore.ts:219``selectConversation(id, force = false)` 异步签名
**Test scenarios:**
- Happy path`chatStore.boardState === null` → 点击"私董会"按钮 → `showBoardModal === true``showBoardBlockModal === false`
- Happy path`chatStore.boardState?.status === 'completed'` → 点击"私董会"按钮 → `showBoardModal === true`(已完成私董会不阻塞)
- Happy path`chatStore.boardState?.status === 'dissolved'` → 同上
- 拦截 path`chatStore.boardState?.status === 'discussing'` → 点击"私董会"按钮 → `showBoardModal === false``showBoardBlockModal === true`
- 拦截 path`chatStore.boardState?.status === 'concluding'` → 同上
- "新建会话"按钮:点击后 `showBoardBlockModal === false``chatStore.currentConversationId` 已变化,`chatStore.boardState === null`(新会话清空 boardState
- "我知道了"按钮:点击后 `showBoardBlockModal === false``chatStore.currentConversationId` 不变,`showBoardModal === false`
> **注**Pinia setup store 的 ref 自动解包,`chatStore.boardState` 已是 `BoardState | null`,不能写 `chatStore.boardState.value`。如需 ref 访问用 `storeToRefs(chatStore).boardState.value`
**Verification:**
- 启动 Tauri 客户端,在已有私董会的会话中点击"私董会"按钮,应弹出提示而非 modal
- 点击"新建会话"后自动跳转到新会话boardState 清空
- 在新会话中点击"私董会"按钮可正常打开 BoardMeetingModal
---
### U2. BoardBannerCard 简化为单行标题+副标题
**Goal:** 将 `BoardBannerCard.vue` 从带边框/紫条/进度条/专家 chip 的重样式简化为纯文本两行:`私董会 — {topic}` + `轮次:第 N/M 轮`,删除 BankOutlined 图标、4px 紫条、专家 chip、卡片背景/边框/圆角/阴影。
**Requirements:** R5, R6, R7, R8
**Dependencies:** 无(与 U1/U3 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/components/chat/messages/BoardBannerCard.vue修改
- src/agentkit/server/frontend/src/components/chat/helpers/useMessageRenderer.ts修改**F3-A 决策**:删除 board_banner case 中 experts prop 传参,避免 Vue 3 attribute fallthrough 把 `[object Object]` 渲染到根 div
- src/agentkit/server/frontend/src/components/preview/scenes/Scene4BoardDiscussion.vue修改适配新 props
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/BoardBannerCard.test.ts新建
**Approach:**
- `<template>` 简化为:
```vue
<div class="board-banner-card">
<div class="board-banner-card__title">私董会 — {{ topic }}</div>
<div class="board-banner-card__meta">轮次:第 {{ currentRound }} / {{ maxRounds }} 轮</div>
</div>
```
- 删除 `<script setup>` 中的 `BankOutlined` import 和 `progressPercent` computed
- `interface Props` 删除 `experts` 字段,保留 `topic / maxRounds / currentRound`
- `<style scoped>` 删除 `.board-banner-card__bar / __body / __icon / __experts / __chip / __chip--moderator / __chip-avatar / __progress / __progress-fill` 共 9 个类,仅保留:
- `.board-banner-card` 容器(`margin-bottom: var(--space-3)` + `padding: var(--space-2) 0`,无 background/border/shadow
- `.board-banner-card__title``font-size: var(--font-md); font-weight: var(--font-weight-semibold); color: var(--text-primary); margin-bottom: var(--space-1)`
- `.board-banner-card__meta``font-size: var(--font-xs); color: var(--text-tertiary)`
- `Scene4BoardDiscussion.vue` 中调用 `<BoardBannerCard>` 的地方删除 `:experts` prop 传参
- `useMessageRenderer.ts``board_banner` case 删除 `experts` prop 传参(**F3-A 决策**
**Patterns to follow:**
- `MessageShell.vue:150-163``.message-shell__name / __meta` 简洁文本样式
**Test scenarios:**
- Happy path传入 `topic="测试主题" maxRounds=5 currentRound=2` → 渲染两行文本,第一行"私董会 — 测试主题",第二行"轮次:第 2 / 5 轮"
- 边界:`currentRound=0` → 第二行"轮次:第 0 / 5 轮"(不报错)
- 边界:`maxRounds=0` → 第二行"轮次:第 1 / 0 轮"(不报错,不做除零校验)
- 渲染契约:组件根 div **不**含 `background / border / border-radius / box-shadow` 任一 CSS 属性
- 渲染契约:组件**不**渲染 `<BankOutlined />` / `.board-banner-card__bar` / `.board-banner-card__chip` 任一元素
- Props 契约:传入 `experts` 数组时组件不报错向后兼容prop 已删除会被 Vue 忽略为 fallthrough attr但需确认不会作为 attribute 渲染到根 div
- **F3-A fallthrough 契约**`useMessageRenderer.ts` 的 `board_banner` case 不再传 `experts` prop已删除根 div **不**含 `experts="[object Object]"` 属性
**Verification:**
- 启动 Tauri 客户端,发起私董会,第一条消息应显示为纯文本两行,无卡片背景/图标/进度条
- 预览场景 `Scene4BoardDiscussion.vue` 仍然能正常渲染
---
### U3. MessageShell 浅灰气泡方案B
**Goal:** 在 `MessageShell.vue``.message-shell__content` 上增加方案 B 风格的浅灰圆角矩形气泡样式,仅 `role === 'assistant'` 时生效,使用 CSS 变量绑定dark mode 自动切换。**F1-A 决策**:气泡用独立 token `--bg-message-bubble`(与 inline code/table 的 `--bg-secondary` 解耦);**F4-A 决策**:排除 `board_conclusion` 类型;**D4-方案1 决策**:空内容通过 `:empty` 隐藏气泡。
**Requirements:** R9, R10, R11, R12, R13, R14, R15
**Dependencies:** 无(与 U1/U2 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/styles/tokens.css修改**F1-A 决策**:新增 `--bg-message-bubble` tokenlight `#ffffff` / dark `#1f1f1f`
- src/agentkit/server/frontend/src/components/chat/messages/MessageShell.vue修改
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/MessageShell.test.ts新建或扩展
**Approach:**
- **第一步F1-A**:在 `tokens.css``:root`light`[data-theme="dark"]`(或等效 dark 选择器)下新增 `--bg-message-bubble: #ffffff`light/ `#1f1f1f`dark。位置紧邻 `--bg-secondary` 定义,注释说明"消息气泡背景,与 inline code/table 背景解耦"
- **第二步**:在 `MessageShell.vue``<style scoped>` 中新增规则:
```css
.message-shell--assistant .message-shell__content {
background: var(--bg-message-bubble);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-3) var(--space-4);
color: var(--text-primary);
}
/* F4-A: board_conclusion 类型不加气泡,保留 BoardConclusionCard 自身 card chrome */
.message-shell--assistant.message-shell--conclusion .message-shell__content,
.message-shell--assistant:has(.board-conclusion-card) .message-shell__content {
background: transparent;
border: none;
border-radius: 0;
padding: 0;
}
/* D4-方案1: 空 slot 内容隐藏气泡背景,仅显示 thinking dots */
.message-shell--assistant .message-shell__content:empty {
background: transparent;
border: none;
padding: 0;
}
```
- **第三步F4-A 配套)**`MessageShell.vue` 的 `interface Props` 新增 `messageType?: string` prop可选template 根 div 根据 `messageType === 'board_conclusion'` 添加 `.message-shell--conclusion` class。或使用 `:has(.board-conclusion-card)` 选择器(更简洁但需确认浏览器支持——现代浏览器已支持 `:has()`Tauri WebView 基于 Chromium 111+ 支持)
- 选择器限定 `.message-shell--assistant` 前缀,确保 `role === 'user'``UserBubble` 不受影响
- 不使用 `!important`,让 `AssistantText.vue:257-401` 内部的 `pre / hljs / code / table` 样式自然继承(这些样式已用 `--code-bg` 等 token浅灰气泡背景下自动适配
- 不修改 `.message-shell__content``display: flex / flex-direction: column / gap: var(--space-2) / width: 100% / max-width: 100%`R13 要求)
- 不修改 `.message-shell--user .message-shell__content``align-items: flex-end`user 消息保持右对齐,不加气泡)
**Patterns to follow:**
- `tokens.css:65,223``--bg-secondary` 定义light `#fbfbfa`dark `#1f1f1f`)—— `--bg-message-bubble` 紧邻此定义
- `tokens.css:79,237``--border-color` 定义
- `tokens.css:97``--radius-md: 6px`
- `LoginView.vue:184-196` 的"显式绑定 token禁硬编码"模式
**Test scenarios:**
- Happy pathassistant渲染 `<MessageShell role="assistant">` + 默认 slot 内容 → `.message-shell__content` 计算样式含 `background-color: rgb(255, 255, 255)`light mode 下 `--bg-message-bubble: #ffffff`
- Happy pathuser渲染 `<MessageShell role="user">` + 默认 slot 内容 → `.message-shell__content` 计算样式 `background-color` 为空或 `rgba(0,0,0,0)`(不加气泡)
- **F1-A 区分性**slot 内含 `<code>async</code>` inline code → inline code 背景为 `--bg-secondary` (`#fbfbfa`),气泡背景为 `--bg-message-bubble` (`#ffffff`),二者视觉可区分
- 气泡内代码块slot 内含 `<pre><code>` → 代码块背景 `--code-bg` 不被气泡背景覆盖(视觉可区分)
- 气泡宽度:`.message-shell__content` 计算样式 `width: 100%``max-width: 100%`(跟随消息列宽)
- Dark mode切换到 dark mode → `.message-shell__content` 计算样式 `background-color: rgb(31, 31, 31)``--bg-message-bubble: #1f1f1f`
- **F4-A 排除 conclusion**:渲染 `<MessageShell role="assistant" messageType="board_conclusion">` + `BoardConclusionCard` slot → `.message-shell__content` 计算样式 `background-color: transparent``border: none`(不加气泡,`BoardConclusionCard` 自身 card chrome 保留)
- **D4-方案1 空内容隐藏**:渲染 `<MessageShell role="assistant">` + 空 slot → `.message-shell__content` 计算样式 `background-color: transparent``border: none``padding: 0``:empty` 选择器生效)
- **D4-方案1 有内容恢复**:渲染 `<MessageShell role="assistant">` + 非空 slot → `:empty` 不匹配,气泡样式正常应用
- 渲染契约:样式**不**使用 `!important`
- 渲染契约:样式**不**硬编码 `#f3f4f6 / #fbfbfa / #ededec / #ffffff` 等值(必须用 CSS 变量)
**Verification:**
- 启动 Tauri 客户端,发起普通 chatassistant 消息应有浅灰(实际为 `#ffffff` 纯白,与 `#fbfbfa` 的 inline code 区分)圆角矩形气泡
- 发起私董会专家发言board_speech应有同款气泡
- 发起私董会结论消息board_conclusion应保留 `BoardConclusionCard` 自身 card 样式,**不**被气泡包裹
- 切换 dark mode气泡背景应自动变为深色`#1f1f1f`
- user 消息(右侧)应显示为 demo 中的深色气泡(`--color-primary` 背景 + `--text-inverse` 白字),@board/@team 命令卡片保持现有浅色背景(见 U4
- assistant 消息在 thinking 阶段(空内容)不应显示空气泡矩形,仅显示三点动画
---
### U4. UserBubble 普通文本深色气泡(参考 demo
**Goal:** 将 `UserBubble.vue` 的普通文本消息(`<span class="user-bubble__text">`)样式改为 demo 中的深色右对齐气泡:`--color-primary` 背景 + `--text-inverse` 白字 + `padding: var(--space-3) var(--space-4)` + `max-width: 70%`。@board/@team 命令卡片和文件附件保持现有 `--bg-tertiary` 浅色背景,不变。
**Requirements:** R16, R17, R18, R19
**Dependencies:** 无(与 U1/U2/U3 互不依赖,可并行)
**Files:**
- src/agentkit/server/frontend/src/components/chat/messages/UserBubble.vue修改
- src/agentkit/server/frontend/src/components/chat/messages/\_\_tests\_\_/UserBubble.test.ts新建或扩展
**Approach:**
- `<script setup>` 新增 `isPlainText` computed`!fileAttachment.value && !commandBubble.value`
- `<template>` 根 div 的 `:class` 新增 `'user-bubble--text': isPlainText` modifier
```vue
<div
class="user-bubble"
:class="{
'user-bubble--focusable': msgId,
'user-bubble--text': isPlainText,
}"
...
>
```
- `<style scoped>` 新增 `.user-bubble--text` 规则(仅普通文本消息时覆盖 `.user-bubble` 默认背景):
```css
/* U4: 普通文本消息参考 demo 深色气泡command card / file attachment 保持浅色 */
.user-bubble--text {
background: var(--color-primary);
color: var(--text-inverse);
padding: var(--space-3) var(--space-4);
max-width: 70%;
}
```
- 不修改 `.user-bubble` 默认样式(`--bg-tertiary` 背景 + `--text-primary` 文字),让 command card 和 file attachment 继承现有浅色背景
- 不修改 `.user-bubble__command` / `.user-bubble__command-*` 内部样式(保持现有结构化卡片可读性)
- 不修改 `.user-bubble__actions` 操作按钮工具栏样式(当前 `--bg-secondary` 背景 + `--border-color` 边框,在深色气泡旁可见性需验证)
- `--color-primary``--text-inverse` 在 light/dark mode 下自动反转tokens.css:18,176 + 75,233 确认dark mode 下 user 气泡自动变为浅色背景 + 深色文字
**Patterns to follow:**
- demo `tmp_scheme_b_options_demo.html:230-240``.user-msg__bubble` 样式(`--color-primary` + `--text-inverse` + `--space-3 var(--space-4)` + `--radius-lg` + `max-width: 70%`
- `tokens.css:18,176``--color-primary` 定义light `#1a1a1a` / dark `#fbfbfa`
- `tokens.css:75,233``--text-inverse` 定义light `#ffffff` / dark `#1a1a1a`
**Test scenarios:**
- Happy path普通文本渲染 `<UserBubble content="Hello">` → 根 div 含 `.user-bubble--text` class计算样式 `background-color: rgb(26, 26, 26)`light mode 下 `--color-primary: #1a1a1a``color: rgb(255, 255, 255)``--text-inverse: #ffffff`
- Happy path@board 命令):渲染 `<UserBubble content="@board 测试主题">` → 根 div **不**含 `.user-bubble--text` class背景保持 `--bg-tertiary`浅色command card 内部样式不变
- Happy path@team 命令):渲染 `<UserBubble content="@team 测试">` → 同上,保持浅色背景
- Happy path文件附件渲染 `<UserBubble content="[文件][x.pdf](url)">` → 根 div **不**含 `.user-bubble--text` class背景保持 `--bg-tertiary`
- Dark mode切换到 dark mode → 普通文本消息 `.user-bubble--text` 计算样式 `background-color: rgb(251, 251, 250)``--color-primary: #fbfbfa``color: rgb(26, 26, 26)``--text-inverse: #1a1a1a`),自动反转
- 操作按钮工具栏:普通文本消息 hover → `.user-bubble__actions` 可见,在深色气泡左侧(`right: calc(100% + var(--space-2))`)显示,`--bg-secondary` 背景与深色气泡有视觉区分
- max-width 契约:普通文本消息 `max-width: 70%`command card 保持 `max-width: 100%``.user-bubble__command` 自身设置)
- 渲染契约:样式**不**使用 `!important`
- 渲染契约:样式**不**硬编码 `#1a1a1a / #ffffff` 等值(必须用 CSS 变量)
**Verification:**
- 启动 Tauri 客户端,发送普通文本消息 → 右侧显示深色圆角气泡,白字
- 发送 @board 命令 → 右侧显示浅色结构化命令卡片(保持现有样式)
- 发送 @team 命令 → 同上,保持浅色
- 切换 dark mode → 普通文本消息自动反转为浅色气泡 + 深色文字command card 不变
- 普通文本消息 hover → 操作按钮工具栏在气泡左侧可见
---
## Scope Boundaries
### Deferred to Follow-Up Work
- **后端按会话去重 `board_started`**:本次只做前端拦截,不改后端。如果未来需要在多端/多浏览器同步场景下做强一致再考虑后端校验origin 文档已声明 deferred
- **StickyModeHeader 顶部条的视觉细化**(徽章大小、专家头像间距、紫色边框粗细)不在本次范围。
- **BoardConclusionCard / DebateBannerCard / TeamPlanCard 等其他 board 模式组件的样式统一**:本次只改 `BoardBannerCard`,其他卡片样式留待后续迭代。
### Outside this product's identity
- 不调整方案 B 调色板(`expertIdentity.ts` 的 12 色 PALETTE和专家头像首字符规则。
- 不调整私董会后端流程(`BoardOrchestrator` / `chatStream` 事件顺序)。
- 不调整 AssistantText 的 markdown 渲染逻辑、代码高亮、表格样式。
## Risks & Dependencies
- **风险 1U3 气泡样式可能影响 `AssistantText` 内部代码块/表格的视觉对比度** — 缓解:测试场景明确要求代码块背景 `--code-bg` 不被覆盖;`AssistantText.vue:257-401` 的 `pre/hljs/code/table` 样式已用独立 token理论上不受影响。如出现对比度问题可在 `AssistantText.vue` 内微调 `--code-bg` 但**不**回滚 U3 的气泡。
- **风险 2U1 的 `selectConversation(newId, true)` 强制 reload 可能导致用户感知"卡顿"** — 缓解:`force=true` 仅在用户主动点击"新建会话"时触发,是用户预期行为;如确实卡顿,可考虑后续优化(不在本次范围)。
- **依赖 1`chatStore.createConversation()` 是同步函数(无返回值),但内部已自动设置 `currentConversationId`** — 实现时需注意:先 `createConversation()` 再读 `chatStore.currentConversationId` 拿到新 id不能直接 `const newId = await chatStore.createConversation()`
- **依赖 2`BoardState.status` 当前联合类型为 4 个值,未来若新增 `forming / executing / synthesizing` 等中间状态需扩展"已存在"判断** — 在 `handleBoardClick` 中用 `if (status === 'discussing' || status === 'concluding')` 显式判断,未来扩展时只需补 `||`
## Open Questions
以下问题在 ce-doc-review 中由 reviewer 提出。**Round 12026-07-02 demo 确认)** 已决策 F1/F3/F4/C2/D4**Round 22026-07-02 复审)** 新增 7 个 findingsG1-G5 + D2-R2/D3-R2/D4-R2全部 append 到本 section 留待实现期处理。其中 **G1 挑战 Round 1 D4-方案1 决策**P0 load-bearing3 reviewer 一致 confidence 100 认为不可实现),**G5 挑战 Round 1 F1-A 决策**`#ffffff` 与 `#fbfbfa` 视觉不可区分)—— 实现期需优先处理这两个挑战。
### 已决策Round 1 — 2026-07-02 demo 确认)
- **F1feasibilityP1— 已决策 F1-A**U3 气泡背景与 inline code/table 背景冲突。**决策**:引入独立 token `--bg-message-bubble`light `#ffffff` / dark `#1f1f1f`)区分气泡背景与 inline code/table 背景(保持 `--bg-secondary: #fbfbfa`)。已固化到 R9、U3 Approach、Key Technical Decisions。**⚠️ Round 2 G5 挑战**`#ffffff` 与 `#fbfbfa` 色差 <1% 低于 JND `#ffffff` `--bg-primary` 相同导致气泡仅靠边框可见——见下方 G5
- **F3feasibilityP2— 已决策 F3-A**U2 删除 `BoardBannerCard``experts` prop 后 `useMessageRenderer.ts:145` 的 fallthrough 风险。**决策**:在 U2 Files 列表中加 `useMessageRenderer.ts`,删除 `board_banner` case 中 `experts` prop 传参。已固化到 U2 Files、U2 Approach、U2 Test scenarios。
- **F4feasibilityP2— 已决策 F4-A**U3 气泡包裹 `BoardConclusionCard` 的嵌套冲突。**决策**:气泡选择器排除 `board_conclusion` 类型,`BoardConclusionCard` 保留自身 card chrome 不被气泡包裹。已固化到 R14、U3 Approach、U3 Test scenarios、Key Technical Decisions。**⚠️ Round 2 G2/G3 挑战**`:has()` 事实错误 + `messageType` prop 选项需 ChatMessage.vue不在 Files——见下方 G2/G3。
- **C2coherenceP2— 已应用 safe_auto**R7 表述已修订为"删除 `.board-banner-card` 的重样式background/border/border-radius/box-shadow`__bar / __chip` 等重样式类,保留 `.board-banner-card` 容器(仅 margin/padding+ `__title / __meta` 最小样式"。
- **D4design-lensP2— 已决策 D4-方案1**U3 空内容气泡表现。**决策**:通过 `:empty` 选择器隐藏 `background / border / padding`,仅显示 thinking dots。已固化到 R15、U3 Approach、U3 Test scenarios、Key Technical Decisions。**⚠️ Round 2 G1 推翻**`:empty` 永不匹配AssistantText 总渲染根 div——见下方 G1实现期必须替换方案。
### 仍待实现期决定
#### Round 1 遗留
- **D1design-lensP1**U1 `selectConversation(newId, true)` 失败时无错误处理——modal 已关闭、currentConversationId 已变更、用户无反馈。待选方案try/catch + a-message error toast + 回滚 currentConversationId。
- **D2design-lensP2**U1 modal 关闭与 selectConversation resolve 之间的过渡期无 loading 反馈。待选方案modal 内"新建会话"按钮 loading spinner / 全局 loading overlay。
- **D3design-lensP2**U2 长 topic 文本无 overflow/wrap/truncation 策略。待选方案:`text-overflow: ellipsis` + `white-space: nowrap` / `word-break: break-word` / 多行 line-clamp。
#### Round 2 复审新增2026-07-02
**P0/P1 load-bearing挑战 Round 1 决策,实现期优先处理):**
- **G1feasibility + design-lens + adversarial 三方共识P0conf 100×3— 推翻 Round 1 D4-方案1**`:empty` 选择器永不匹配。`AssistantText.vue:1-6` 总是渲染根 `<div class="assistant-text">`(含 loading spinner / tool cards / routing div`useMessageRenderer.ts:361-362` 总是将 AssistantText 作为 slot 子组件挂载到 `.message-shell__content`,所以 `.message-shell__content` 在生产环境永远非空。D4-方案1 的 `:empty` CSS 规则不会生效thinking 阶段会显示空气泡矩形(正是 D4-方案1 要防止的 UX 缺陷。plan 的测试场景"空 slot → `:empty` 生效"用真空 slot 不反映生产。**待选方案**
- (a) MessageShell 加 `isEmpty` computed`!message.content && !message.thinking && toolCalls.length === 0`+ `.message-shell--empty` class 覆盖气泡样式 —— 推荐,最简单
- (b) 用 `:has(.assistant-text__loading:only-child)` 检测纯 loading 状态 —— 脆弱,不推荐
- (c) AssistantText 真空时不渲染根 div —— 高风险,可能破坏 ThinkingBlock / ToolCallCard
- 实现期需选择 (a)/(b)/(c) 并更新 R15、U3 Approach 第二步 CSS、U3 Test scenarios、Key Technical Decisions。
- **G5adversarial + feasibility residualP2conf 75— 挑战 Round 1 F1-A**`#ffffff`纯白vs `#fbfbfa`off-white色差 <1% 低于人眼 JND~2.3 `#ffffff` `--bg-primary: #ffffff`页面背景相同导致气泡仅靠 1px `--border-color: #ededec` 边框可见AE6"视觉可区分"断言可能不成立origin 文档描述为"浅灰圆角矩形气泡" `#ffffff` 实际是白色。**待选方案**
- (a) `--bg-message-bubble` light 改为 `#f7f7f5`(实际浅灰,与 `--bg-tertiary` 一致)—— 视觉符合 origin 描述
- (b) 保持 `#ffffff` 但修订 AE6 + U3 Test scenarios "视觉可区分" 为"token 级解耦(未来兼容),视觉差异 sub-JND"
- (c) 改用 `#f3f4f6` 等更明显灰值
- 实现期需选择 (a)/(b)/(c) 并更新 R9、U3 Approach、AE6、tokens.css 值。
**P2 gated_auto事实修正 + 决策落地):**
- **G2adversarial + feasibility residualP2conf 75— Tauri `:has()` 事实错误**plan U3 Approach 第 202 行称"Tauri WebView 基于 Chromium 111+ 支持",实际 Tauri 2.x macOS 用 WKWebViewSafari 引擎Linux 用 WebKitGTK仅 Windows 用 WebView2Chromium。`:has()` 在 Safari 15.4+2022-03/ WebKitGTK 2.35+ / Chromium 105+ 支持,实际可用但 plan 推理错误。**Fix**:修正 U3 Approach 第 202 行 + Key Technical Decisions F4-A 段为"Tauri 2.x macOS 用 WKWebViewSafari 15.4+ 支持 `:has()`Linux WebKitGTK 2.35+Windows WebView2 Chromium 105+。若需支持更老 macOS优先用 `messageType` prop 方案"。
- **G3coherence + adversarialP2conf 75— F4-A `:has()` vs `messageType` prop 决策悬而未决 + ChatMessage.vue 缺失**plan 对 F4-A 结论排除提供两个选项(`:has(.board-conclusion-card)` 选择器 OR `messageType` prop + `.message-shell--conclusion` class但不决策且三处描述不一致Key Technical Decisions 用 `:not(:has())`U3 Approach 用"或"U3 CSS 用两者 comma-joined。若选 `messageType` prop需修改 ChatMessage.vue 传递 `:message-type="spec.type"`,但 ChatMessage.vue 不在 U3 Files 列表。**Fix**:实现期决策用 `messageType` prop更稳健无 CSS 兼容风险),添加 `src/agentkit/server/frontend/src/components/chat/ChatMessage.vue` 到 U3 Files更新 Key Technical Decisions + U3 Approach + U3 CSS 三处统一为 `messageType` prop 方案。
**P2 manual设计决策**
- **D2-R2design-lensP2conf 75— U4 focus ring 在深色气泡上不可见**`UserBubble.vue:304` 用 `var(--accent-primary, #1a1a1a)`,但 `--accent-primary` 在 tokens.css 未定义fallback `#1a1a1a``--color-primary` 相同。U4 深色气泡(`--color-primary: #1a1a1a` 背景)上 focus ring 不可见WCAG 1.4.11 违规。**待选方案**(a) 定义 `--accent-primary` token`#3b82f6` light / `#60a5fa` dark(b) 改用 `--color-info` / `--accent-team`(c) 不在本次处理pre-existing bugU4 加剧但不引入)。
- **D3-R2design-lensP2conf 75— U3 dark mode 代码块对比度不足**dark mode 下 `--code-bg: #11111b`tokens.css:255vs `--bg-message-bubble: #1f1f1f` 仅差 ~14 亮度级代码块可能融入气泡。light mode 对比度足够(`#1e1e2e` on `#ffffff`)。**待选方案**(a) 加 dark mode 专属测试场景验证对比度;(b) 调整 `--bg-message-bubble` dark 值为 `#252525`(接近 `--bg-elevated`(c) 给 `pre``border: 1px solid var(--border-color)`
- **D4-R2design-lensP2conf 75— U2 简化 banner 缺乏视觉分隔**:删除所有 chrome 后,两行纯文本 bannerfont-md semibold + font-xs tertiary可能融入消息流失去"section divider"作用。BankOutlined 图标删除后无视觉锚点。**待选方案**(a) 保留 2px 左边框 `--accent-board`(匹配私董会身份,克制);(b) 加小色点前缀;(c) 纯排版足够("私董会 —" 文本前缀即锚点)。
## System-Wide Impact
- **前端用户**:所有 Tauri 客户端和 Web GUI 用户将看到 (a) 私董会按钮在已有私董会的会话中弹提示而非 modal(b) 私董会开始消息显示为简洁两行文本;(c) 所有 assistant 消息有浅灰圆角气泡(`--bg-message-bubble: #ffffff`board_conclusion 例外(保留自身 card(d) thinking 阶段不显示空气泡。视觉变化明显但符合方案 B 整体取向。
- **后端/运维**:无影响(不动后端、不动部署、不动配置)。
- **其他团队**:无影响(仅前端 Vue 组件)。
## Acceptance Examples
- AE1用户在已存在 `status='discussing'` 私董会的会话中点击"私董会"按钮 → 弹出 a-modal 提示"当前会话已存在私董会",点击"新建会话"后自动跳转到新空会话boardState 清空。
- AE2用户在新会话中发起私董会 → 第一条消息显示为两行纯文本"私董会 — {topic}" + "轮次:第 1/5 轮",无卡片背景/图标/进度条。
- AE3用户发起私董会专家依次发言 → 每条专家发言board_speech显示为彩色圆形头像 + 粗体名字 + "第 N 轮 · 专家" + 浅灰圆角矩形气泡包裹内容。
- AE4用户发起普通 chat不涉及私董会→ assistant 消息有浅灰圆角气泡user 普通文本消息有深色右对齐气泡U4
- AE5用户切换到 dark mode → 浅灰气泡自动变为深色(`--bg-message-bubble: #1f1f1f`),代码块/表格对比度保持可读。
- AE6F1-Aassistant 消息气泡内含 inline code → inline code 背景 `--bg-secondary: #fbfbfa` 与气泡背景 `--bg-message-bubble: #ffffff` 视觉可区分。
- AE7F4-A私董会结论消息board_conclusion`BoardConclusionCard` 保留自身 card chromebackground/border/shadow**不**被气泡包裹,无嵌套冲突。
- AE8D4-方案1assistant 消息在 thinking 阶段(空内容)→ 不显示空气泡矩形,仅显示三点动画;内容流入后自动出现气泡。
- AE9U4用户发送普通文本消息 → 右侧显示深色圆角气泡(`--color-primary` 背景 + 白字max-width 70%
- AE10U4用户发送 @board 命令 → 右侧显示浅色结构化命令卡片(保持现有 `--bg-tertiary` 背景),不受深色气泡影响
- AE11U4切换 dark mode → 普通文本消息自动反转为浅色气泡 + 深色文字,@board 命令卡片样式不变

View File

@ -2,13 +2,29 @@
date: 2026-07-02 date: 2026-07-02
type: fix type: fix
title: 修复私董会 transient state 残留 + ReAct 工具调用引导不足 title: 修复私董会 transient state 残留 + ReAct 工具调用引导不足
status: ready status: in-progress
--- ---
## Summary ## Summary
收尾两个独立 bug(1) 前端 store-level transient state`boardState` / `debateState` / `collaborationState`)在 `createConversation` / `selectConversation` / `deleteConversation` 三个动作下的重置口径不一致,导致新建对话后私董会顶部标题残留、跨会话状态泄漏;(2) ReAct 引擎 `_build_tool_use_prompt` 规则 3 "如果不需要工具就能回答,直接回答即可" 给 LLM 留出偷懒窗口,且工具调用提示被后置的 tool section 覆盖,导致复杂需求(涉及外部数据 / 多步分析)下 LLM 倾向于直接回答而非调用 `web_search` / `baidu_search`。Bug 1 覆盖前端 3 个 action 路径对称重置Bug 2 仅做 L0提示规则调整L1工具描述扩展与 L2PLAN_EXEC 启用)按用户决策拆为独立 plan。 收尾两个独立 bug(1) 前端 store-level transient state`boardState` / `debateState` / `collaborationState`)在 `createConversation` / `selectConversation` / `deleteConversation` 三个动作下的重置口径不一致,导致新建对话后私董会顶部标题残留、跨会话状态泄漏;(2) ReAct 引擎 `_build_tool_use_prompt` 规则 3 "如果不需要工具就能回答,直接回答即可" 给 LLM 留出偷懒窗口,且工具调用提示被后置的 tool section 覆盖,导致复杂需求(涉及外部数据 / 多步分析)下 LLM 倾向于直接回答而非调用 `web_search` / `baidu_search`。Bug 1 覆盖前端 3 个 action 路径对称重置Bug 2 仅做 L0提示规则调整L1工具描述扩展与 L2PLAN_EXEC 启用)按用户决策拆为独立 plan。
## Progress
| Unit | 状态 | 验证 | 提交 |
|------|------|------|------|
| U1 createConversation 重置 | done | 5 前端单测 pass | 7376005 |
| U2 selectConversation 条件重置 | done | 5 前端单测 pass | 7376005 |
| U3 deleteConversation 补全 | done | 5 前端单测 pass | 7376005 |
| U4 ReAct prompt 规则重排 | done | 6 后端单测 pass | 7376005 |
| U5 端到端验证测试 | done | 11 单测全 pass | 7376005 |
| U6 Bug 2 L4 真实 LLM smoke test | pending | — | — |
| U7 工作树未提交变更清理 | pending | — | — |
PR: http://8.153.107.96/gitea/fischer/fischer-agentkit/pulls/17
ce-code-review: mode:agent, 无 actionable findings
ce-test-browser: agent-browser 已安装,待 U6 阶段执行
## Problem Frame ## Problem Frame
**Bug 1私董会顶部标题在新对话后残留** **Bug 1私董会顶部标题在新对话后残留**
@ -240,6 +256,69 @@ status: ready
**Verification:** `cd src/agentkit/server/frontend && npm run test:unit` + `pytest tests/unit/test_react_engine.py` 全部通过 **Verification:** `cd src/agentkit/server/frontend && npm run test:unit` + `pytest tests/unit/test_react_engine.py` 全部通过
### U6. Bug 2 L4 真实 LLM smoke test 验证
**Goal:** 验证 U4 的 L0 规则重排在真实 LLM 调用中是否生效 — Agent 面对复杂需求(外部信息 / 实时数据 / 多步分析)时是否调用 `web_search` 而非直接回答。将 Bug 2 状态从 "hypothesis applied, pending L4 verification" 升级为 "verified" 或回退并触发 L1/L2。
**Requirements:** R4 验证闭环
**Dependencies:** U4已 donePR #17 合入后执行
**Files:**
- tests/manual/test_react_l4_smoke.py新建手动 smoke test 脚本,不进 CI
**Approach:**
- 准备 5 个 probe query覆盖外部信息 / 实时数据 / 多步分析 / 不确定事实 / 混合类型:
1. "收集 GitHub Trending 前 10 个项目信息并分析商业价值"(原 bug 复现 query
2. "最新 AI 领域有什么重要新闻?"(实时数据)
3. "对比 React 和 Vue 3 在大型项目中的性能差异"(多步分析 + 外部信息)
4. "今天上海天气怎么样?"(实时数据,简单)
5. "请帮我总结这段文字:..."(无需工具,验证 escape hatch 规则 4 仍有效)
- 对每个 query 跑 `ReActEngine.execute()`,记录是否触发 `web_search` tool call
- query 1-4 期望触发工具调用≥3/4 pass 算通过query 5 期望不触发
- 如通过率 < 3/4触发 L1工具描述扩展并创建独立 plan
- 使用 `agent-browser` 打开 http://localhost:15173 进行前端层验证(可选):在 chat 中输入 probe query观察是否出现 tool_call step
**Test scenarios:**
- probe query 1-4ReAct 循环中至少出现一次 `web_search` tool call
- probe query 5ReAct 循环中无 tool call直接回答
- 回归断言U4 单测仍 passL0 文本未变)
**Verification:** `python3 tests/manual/test_react_l4_smoke.py` 输出报告5 个 query 的 tool call 统计 + pass/fail 判定。通过后更新本 plan Progress 表 U6 状态为 doneBug 2 状态升级为 "L4 verified"
### U7. 工作树未提交变更清理
**Goal:** 清理工作树中 32 个来自前序 session 的未提交变更,按 concern 分组提交,使工作树恢复干净状态。避免后续开发时 diff 噪声干扰 review。
**Requirements:** 无(工程治理)
**Dependencies:** 无(可与 U6 并行)
**Files (按 concern 分组):**
| 分组 | 文件 | 来源 session | 建议提交方式 |
|------|------|-------------|-------------|
| A. expert avatar emoji 移除 | configs/experts/*.yaml (15 个) | emoji 移除 plan (2026-07-02-001) | 单独 commit `refactor: expert avatar 改为首字符` |
| B. dev 环境配置 | docker-compose.yaml, scripts/dev-start.sh, docker-compose.dev.yml, .env.dev, src/agentkit/server/config.py | dev 环境修复 session | 单独 commit `fix: dev 环境配置 + 端口隔离` |
| C. board 元数据持久化 | src/agentkit/experts/board_orchestrator.py, src/agentkit/server/routes/chat.py | 私董会持久化修复 session | 单独 commit `fix: board_speech/round_summary 持久化 avatar/color 元数据` |
| D. 前端方案B + board UI | StickyModeHeader.vue, expertIdentity.ts, useMessageRenderer.ts, BoardRoundCard.vue, MessageShell.vue, Scene4BoardDiscussion.vue, chatStream.ts, types.ts, LoginView.vue | 方案B + 私董会限制 session | 需 review 后 commit部分可能已通过 PR #15 合入 |
| E. .understand-anything | .understand-anything/* (3 tracked + untracked) | knowledge graph 工具 | 加入 .gitignore 或单独 commit |
| F. 未跟踪 plan/brainstorm 文档 | docs/brainstorms/2026-07-02-*.md, docs/plans/2026-07-02-001-*.md | ce-plan/ce-brainstorm 产物 | commit 为决策记录 |
**Approach:**
- 对每组 `git diff HEAD -- <files>` 检查变更内容,确认无冲突
- 分组 commit每组 commit message 遵循 conventional commits 格式
- D 组需特别注意:检查是否与已合入的 PR #15 内容重叠,避免重复提交
- E 组(.understand-anything建议加入 .gitignore 而非 commit是工具生成的本地索引
- 如某组变更属于未完成的 feature如方案B则 stash 而非 commit待对应 plan 完成后再提交
**Test scenarios:**
- 每组 commit 后 `git status --short` 该组文件不再出现
- 全部完成后 `git status --short` 仅显示 untracked.understand-anything 等 gitignored 项)
- `npm run typecheck` + `ruff check src/` 仍通过(确认无回归)
**Verification:** `git status --short` 输出为空(或仅 gitignored 项);`npm run test:unit` + `pytest tests/unit/ -x -q` 全套通过
## Out of Scope ## Out of Scope
- **L1工具描述扩展**web_search / baidu_search / web_crawl 工具 description 添加"何时使用"触发关键词(如"需要最新互联网信息、新闻、Trending、股价时使用 web_search")。按用户决策推迟为独立 plan - **L1工具描述扩展**web_search / baidu_search / web_crawl 工具 description 添加"何时使用"触发关键词(如"需要最新互联网信息、新闻、Trending、股价时使用 web_search")。按用户决策推迟为独立 plan
@ -256,15 +335,17 @@ status: ready
## Deferred to Follow-Up Work ## Deferred to Follow-Up Work
- L1web_search / baidu_search 工具 description 扩展(独立 plan - L1web_search / baidu_search 工具 description 扩展(独立 plan— 仅在 U6 L4 smoke test 未通过时触发
- L2启用 PLAN_EXEC phase policy 处理复杂需求(独立 plan - L2启用 PLAN_EXEC phase policy 处理复杂需求(独立 plan— 仅在 U6 通过但工具调用率仍不理想时触发
- 重构 stream-owned state 为按 conversationId 拆分(架构性,独立 plan - 重构 stream-owned state 为按 conversationId 拆分(架构性,独立 plan
## Verification (per unit, summary) ## Verification (per unit, summary)
- U1/U2/U3`cd src/agentkit/server/frontend && npm run test:unit` 全套通过;新增 3 个 `describe` 块共 8+ test cases - U1/U2/U3`cd src/agentkit/server/frontend && npm run test:unit` 全套通过;新增 3 个 `describe` 块共 8+ test cases
- U4`pytest tests/unit/test_react_engine.py -k ToolUsePromptRules` 通过;新增 1 个 test class 4-5 个 test cases。**Bug 2 状态声明**L0 文本断言通过后 Bug 2 标记为 "hypothesis applied, pending L4 verification"(非 "fixed"),真实 LLM smoke test 在 L1/L2 独立 plan 中执行 - U4`pytest tests/unit/test_react_engine.py -k ToolUsePromptRules` 通过;新增 1 个 test class 4-5 个 test cases已 done。**Bug 2 状态声明**L0 文本断言通过后 Bug 2 标记为 "hypothesis applied, pending L4 verification"(非 "fixed"),真实 LLM smoke test 在 U6 中执行
- U5完整套件通过端到端 4-5 个联动测试 - U5完整套件通过端到端 4-5 个联动测试(已 done11 单测全 pass
- U6`python3 tests/manual/test_react_l4_smoke.py` 输出 5 个 probe query 的 tool call 统计≥3/4 外部信息 query 触发 web_search + query 5 不触发 → Bug 2 状态升级为 "L4 verified"
- U7`git status --short` 为空(或仅 gitignored 项);`npm run test:unit` + `pytest tests/unit/ -x -q` 全套通过
- 集成:`python3 -m pytest tests/unit/ -x -q`AGENTS.md 硬约束) + `cd src/agentkit/server/frontend && npm run test:unit` 通过 - 集成:`python3 -m pytest tests/unit/ -x -q`AGENTS.md 硬约束) + `cd src/agentkit/server/frontend && npm run test:unit` 通过
- Lint`ruff check src/ && ruff format src/`AGENTS.md 硬约束)通过 - Lint`ruff check src/ && ruff format src/`AGENTS.md 硬约束)通过
- TypeScript`cd src/agentkit/server/frontend && npm run typecheck` 通过 - TypeScript`cd src/agentkit/server/frontend && npm run typecheck` 通过