Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c8404c5e | ||
|
|
f5aa687cb1 | ||
|
|
3d2da4eb0b | ||
|
|
437af31118 | ||
|
|
4a69886952 | ||
|
|
67cf52bb77 | ||
|
|
cb700cf40b | ||
|
|
7e123e5688 | ||
|
|
4a55832e9d | ||
|
|
1f3e7ab441 | ||
|
|
07a6acfc21 | ||
|
|
4f3b22d601 | ||
|
|
f0361f3856 | ||
|
|
baf8f22aa7 | ||
|
|
ffb2aa7cc4 | ||
|
|
f179de8847 | ||
|
|
5882448237 | ||
|
|
8e2657c824 | ||
|
|
bf0988b2b2 |
@@ -0,0 +1 @@
|
||||
.mimosa/
|
||||
@@ -17,3 +17,20 @@
|
||||
- 模块边界、公开契约与隔离档位见 `policy/modules/`(如有)与 PROJECT.md。
|
||||
- 跨仓 gitlink 变更须经组合验证后在 Dev 仓推进;受保护分支经 PR 合入。
|
||||
- 验证命令见 PROJECT.md「验证」节;完成声明须附实际命令输出证据。
|
||||
|
||||
## Gitea 与 Git 访问事实(2026-09-30 实测)
|
||||
|
||||
历史文档与旧链接里的 `git-panel.u1680832.nyat.app:30319` 已失效(TLS 握手失败),
|
||||
见到一律按过期处理,不要再试。当前权威通道:
|
||||
|
||||
- **Web / API(HTTPS)**:`https://git-home.u1680832.nyat.app:30319`(Gitea 1.27.3,
|
||||
API 前缀 `/api/v1`;偶发 broken pipe / 超时,重试即可)。
|
||||
- **Git over SSH**:`ssh://git@git-ssh.u1680832.nyat.app:31411/weichen/<Repo>.git`
|
||||
(各仓 origin 与 gitlink 均为此格式)。
|
||||
- **API token**:`~/.secrets/gitea`,两行键值 `URL_PREFIX=…` 与
|
||||
`ACCESS_TOKEN=<40 位 hex>`;取值 `grep ACCESS_TOKEN ~/.secrets/gitea | cut -d= -f2-`,
|
||||
用作 `Authorization: token <值>` 请求头。**token 值不入仓、不回显、不写进产出物**
|
||||
(baseline 硬边界 2:只记录来源、字段与可用状态)。
|
||||
- **SSH key**:`~/.ssh/home_gitea`(ED25519,公钥指纹
|
||||
`SHA256:GYXaOXIMeloQ1fKSfdJ/ndgPQzs/Ix7Hk62ekfkfQ20`,Gitea 侧密钥名 GMKtec);
|
||||
`~/.ssh/config` 已有 `git-ssh.u1680832.nyat.app` 主机条目,克隆/推送直接可用。
|
||||
|
||||
@@ -96,6 +96,8 @@ panels/main.json:
|
||||
|
||||
cards/note.json:`{ "kind": "note", "data": [ …UINode ] }`。
|
||||
|
||||
**新卡片类型验收项(通道外溢约束,源自目标系统 06 §2.7)**:任何新卡片类型都必须能回答「这张卡在飞书/微信群里长什么样」——按钮回调走命令总线(与 Web 同源审计),无交互卡的通道降级为模板消息 + H5 确认页;深读类内容不做通道投影,深链回 Web 弹层。
|
||||
|
||||
**词汇边界**:section / list / kv / form / markdown / doc-ref / actions / chart-lite。
|
||||
表达不了的高级交互 → 向 Core 提案新词汇(契约扩展流程),**不要**期望注入 React 组件——前端不开放此口子,这是插件不破坏前端的机制保证。
|
||||
|
||||
|
||||
@@ -81,7 +81,9 @@ AGENT_SYSTEM_CONFIG=/etc/agent-system.json agent-system serve # 环境变量
|
||||
### 3.2 中栏对话
|
||||
|
||||
- **流式渲染**:正文逐字输出;思考过程(灰字竖线)与工具调用(芯片)实时展示;顶部状态点显示事件流连接。
|
||||
- **过程折叠**:任务完成后,思考与工具调用折叠为「⚙ 过程输出 · N 次工具调用」摘要行,点击展开回看(设置 → 界面 → 可改默认行为)。
|
||||
- **过程折叠(Cursor 式)**:流式期间思考/工具/中间轮解说照常显示;**最终回复一开始流式,过程立即折叠**为「过程输出 · N 次工具调用」摘要行,只显式显示回复(工具恢复时重新展开);任务终态后保持折叠,点击展开回看(设置 → 界面 → 可改默认行为)。
|
||||
- **长回复折叠**:终态后超过 800 字的回复自动收成「📄 长回复 · N 字(点击展开)」摘要行——流式期间全文可见,历史消息同样适用。
|
||||
- **长结果进右侧卡片(默认关)**:设置 → 界面 →「长结果进右侧卡片」开启后,报告/纪要/方案/对比/文档草稿等**产物型**长内容进右栏收件箱(result 卡,可置顶/收纳);长正文优先流式三段式——`card_begin` 建卡(收件箱即时出现 active 卡)→ `card_append` 逐段追加(卡片实时刷新,事件按 250ms 窗口节流)→ `card_commit` 收尾(转 done 并锚定到对话);一次性短产物走 `submit_card`。中栏保留结论摘要 + 锚点部件(`📄 标题 · N 字 ▸ 展开全文 · ↗ 收件箱`)——锚点可无跳转展开全文,右栏收起时阅读不残废;解释型长回答不进卡。下一轮对话上下文自动回填卡片全文(「🧩 上下文构成」可见 card(n) 条目);run 异常终止时未收尾的流式卡自动置 done/failed 并标注「未正常收尾」,内容不丢。
|
||||
- **上下文构成观测**:每条助手消息尾部「🧩 上下文构成」折叠芯片——该轮模型实际看到什么(系统提示/历史条数/附件/知识命中)与裁剪记录(历史超窗/工具结果超限),输入规模可解释。
|
||||
- **附件**:📎 上传 md/txt/docx/pdf/图片——文档经内置解析器(确定性代码解析:md/txt 直读、docx 解 OOXML、pdf 提取文本层)转为中间表示注入对话;解析结果与警告显式可见(不静默)。
|
||||
- **输入增强**:`/` 唤起命令菜单(/new /mock /model /profile /help);`@knowledge` 在消息中触发知识检索注入。
|
||||
@@ -132,6 +134,7 @@ AGENT_SYSTEM_CONFIG=/etc/agent-system.json agent-system serve # 环境变量
|
||||
- 上传与模型产出统一入 Artifacts;`write_artifact` 工具可被模型调用。
|
||||
- 查看:markdown 在线渲染;文本类可**编辑保存**;二进制(docx/pdf)下载;图片内联预览。
|
||||
- 文档工件与上传文本自动进**知识索引**(全文检索)。
|
||||
- **工件门面卡(与产物卡统一呈现)**:工具结果被截断超限时自动在收件箱留一张 `result` 门面卡(摘要 + 来源工具 + `doc-ref` 引用,点击直开查看器)——字节权威仍在工件(可编辑/下载),卡只做门面与锚;下一轮对话上下文会自动回填工件文本(上限与产物卡同为 12000 字,超限在「🧩 上下文构成」可见 `artifact(n)` 与截断记录),模型不再对工具大结果失忆。
|
||||
|
||||
### 4.6 知识检索
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
# 卡片流远期三项(内容流式 / artifact 统一 / 跨会话收件箱)—— 设计与实施提案
|
||||
|
||||
状态:**已批准(2026-09-30 用户批准;D49 聚合口径按提案建议拍板:项目视图=项目级卡∪各会话未决卡,done 不聚合)**;owner:weichen。
|
||||
前置:[卡流机制与中栏过程/回复分离](2026-09-30-cardflow-and-process-split.md)(D39–D43 已实施)、[界面交互底座](2026-09-30-ui-inbox-command-workspace.md)(D25–D33 收件箱/卡片/互定位)。
|
||||
输入:用户 2026-09-30 指示——将 cardflow-r1 遗留的三项远期(卡片内容流式、artifact 统一、跨会话收件箱 CF3)推进实施,先出方案与计划。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与现状盘点
|
||||
|
||||
cardflow-r1(PR #90)落地了显式路由(submit_card)、卡片权威载体(ctxasm 回填)与锚点互定位,同时把四项留作远期。本提案承接其中三项(飞书/微信投影仍留远期——通道依赖外部,单独立卡)。2026-09-30 代码盘点(core cafd9a5 / backend 076abfe / frontend 6752b78 / ui efd87f9 之后,R0=d64742c):
|
||||
|
||||
| 项 | 现有地基 | 缺口 |
|
||||
|---|---|---|
|
||||
| 内容流式 | `cardsvc.Update` 已支持整卡变更 + `card.updated` 事件(progress 卡流式区在用);前端 store 已消费 `card.updated` → upsertCard | `submit_card` 一次性出卡(llm.go 单次 JSON 参数 → CreateCard(done)),无流式产卡协议 |
|
||||
| artifact 统一 | 工具大结果已走 blobstore Artifact + `PartArtifactRef` + REST/查看器 + `artifact.created`;UINode 已有 `doc-ref` 词汇(UIRenderer 已渲染) | 双轨不互通:`card` 部件 ctxasm 回填全文(12000 字上限),`artifact_ref` 部件只渲染一行 `[产出工件 name]`——**模型下一轮对工具大结果失忆**;前端两套呈现入口(收件箱卡 vs 工件列表) |
|
||||
| 跨会话收件箱 | `core.ActivityCard.ConversationID` 注释已预留「空 = 项目级卡片」;`activity_cards` 表已有 `project_id` 列 | 列表/事件/前端三层全部按会话:`ListCards WHERE conversation_id=?`;SSE 严格按 `conversationId` 订阅(`/api/events` 必填参数);前端 `loadCards(convId)` 切会话即清空 |
|
||||
|
||||
## 2. 决策(D44 起)
|
||||
|
||||
| # | 决策 | 理由 |
|
||||
|---|---|---|
|
||||
| D44 | **流式产卡三段式**:内置工具 `card_begin(title, summary?)` / `card_append(text)` / `card_commit()`(同 update_todos 先例不经注册表,仅 `ui.cardflow=true` 时声明);`submit_card` 保留用于一次性短产物 | 与显式路由裁定同构(拆分发生在生成侧,可断言可审计);不依赖 LLM 网关流式工具参数能力;失败模式安全(不 begin = 全文留中栏) |
|
||||
| D45 | **流式卡生命周期**:begin 建卡 `kind=result, status=active`(收件箱可见、不计 pendingCount,与现状 active 语义一致);append 增量并入 body 尾部 markdown 节点,节流合并发布 `card.updated`(默认 250ms 窗口);commit 置 `done` + 消息追加 `Part.card` 引用部件 + finishRun 锚点回填(复用 submit_card 登记路径);run 终止(含失败/取消)仍有未 commit 的流式卡 → 兜底置 `done`(成功)或 `failed`(失败 run),tone 标注「未正常收尾」 | 不发明新状态机;断连/幻觉的恢复语义显式;pendingCount 不受流式过程影响 |
|
||||
| D46 | **artifact 上下文回填对称**:ctxasm 对 `PartArtifactRef` 增加 ArtifactResolver 回填正文(默认与卡同 12000 字上限,超限记 Truncations;无 resolver 留现占位行) | 先修「下一轮失忆」缺口——独立有价值、无 UI 依赖、可与 CF-S 并行 |
|
||||
| D47 | **artifact 门面卡(统一呈现层)**:工具结果 `Truncated=true` 时 runner 自动产 `kind=result` 门面卡:`body=[markdown(工具摘要), kv(来源/工件名), doc-ref(工件)]`,status=done,消息同时追加 `Part.card` 引用与既有 `PartArtifactRef`(兼容+审计);`PartArtifactRef` 前端渲染并入锚点样式(展开显 doc-ref、跳工件查看器) | 判据复用既有 Truncated 信号,不新造阈值;artifact 保持字节权威(可编辑、REST 查看器),卡是门面+锚——不在卡内复制字节;未截断的小结果不建卡(防收件箱噪音) |
|
||||
| D48 | **项目级卡数据语义**:`ConversationID` 空 = 项目级卡(core 既有注释转正);来源三个——① 项目级后台作业 producer;② 会话卡新增「留档到项目」动作(`to_project`,走命令总线,幂等);③ `card_begin`/`submit_card` 增加 `scope=project` 可选参数(默认 conversation);`activity_cards` 增加 project 维度索引 | 数据模型零迁移成本(列已在);生产端渐进开放 |
|
||||
| D49 | **项目收件箱聚合口径**:项目视图 = 项目级卡 ∪ 该项目全部会话 `action_required` 未决卡;`done` 卡不聚合(留在原会话,防噪音);会话视图 = 该会话全部卡 ∪ 项目级卡(项目卡带「项目」徽标,动作跨会话直达) | 跨会话的真正诉求是「待办不丢」;已完成产物按会话归档保持时间线清晰。已拍板(2026-09-30 用户批准本提案即确认) |
|
||||
| D50 | **项目事件频道**:`/api/events` 支持 `scope=project&projectId=…` 订阅(项目流独立 seq、持久化、断线重放——与会话流同 Stream 契约);项目级卡及跨会话聚合口径内的状态变更发布到项目流;会话内动作回执仍写会话流;`StreamEvent` 信封增加可选 `ProjectID` 字段(只增不删) | 不向全部会话流广播(事件放大且 seq 语义混乱);项目流自成单调序,重放语义与会话流一致 |
|
||||
| D51 | **前端双视图**:收件箱 tab 两级(会话/项目切换),会话 `pendingCount` = 会话未决 + 项目未决;项目视图复用 InboxList + 分组头(「项目级/其他会话待办」);切会话不清项目级卡缓存 | store.cards 拆两层(projectCards / convCards);openInbox 定位逻辑按卡归属路由 |
|
||||
| D52 | **完备性剧本增补**:零页面走查增加——流式卡双态(流式中/终态)、artifact 门面卡展开全文(doc-ref 打开查看器)、项目收件箱跨会话可见性(A 会话产待办 → B 会话/项目视图可见且可决) | 沿 D43 惯例:「内容被藏起来」与「待办跨会话丢失」是本批最高风险 |
|
||||
|
||||
## 3. 实施切分(三任务卡,串行)
|
||||
|
||||
| 期 | 内容 | 仓 | 依赖 |
|
||||
|---|---|---|---|
|
||||
| **CF-S 内容流式**(feature/card-stream-r1) | D44/D45:backend 三段式工具 + cardsvc Append(节流)+ run 终止兜底 + style-cardflow outputContract 更新 + mock `#cardflow` 流式演示;frontend/ui 仅可选视觉(流式中 shimmer);documents 提案转正 + 指南 | backend / documents(frontend、ui 可选) | 无 |
|
||||
| **CF-A artifact 统一**(feature/artifact-unify-r1) | D46/D47:backend ctxasm ArtifactResolver + golden 更新、runner Truncated→门面卡、mock `#upload` 演示门面;frontend artifact_ref 锚点样式统一;documents 指南 | backend / frontend / documents | 建议在 CF-S 合入后开工(llm.go/runner.go 文件面重叠,串行省冲突) |
|
||||
| **CF3 跨会话收件箱**(feature/project-inbox-r1) | D48–D51:core 信封 ProjectID + conformance;backend store 索引迁移/聚合查询、REST `GET /api/projects/{id}/cards` 与 to_project 动作、项目 SSE 频道与 emit 路由;frontend 双视图 + pendingCount;ui 徽标/分组;D52 走查 | core / backend / frontend / ui / documents | 体量最大,放最后;CF-A 的门面卡经项目视图获得跨会话可见性 |
|
||||
|
||||
跨仓顺序遵守 multi-repo 五步:core 先合入 → backend → frontend/ui → Dev gitlink PR → 组合验证。三卡各自走 task-start/task-sync/task-finish 全流程。
|
||||
|
||||
## 4. 契约与行为要点
|
||||
|
||||
- `card_begin` 返回 `cardId` 供后续调用绑定(工具轮间状态由 runner 持有,同轮多次 begin 以最后一卡为活跃卡——outputContract 约束单 run 单活跃流式卡)。
|
||||
- `card_append` 文本追加语义为 markdown 段落拼接;卡 body 总长不设硬限(权威载体),仅 ctxasm 回填时按上限截断留痕(与 D40 一致)。
|
||||
- 幻觉调用拒绝沿用现状模式:`ui.cardflow` 关闭时三工具不声明,幻觉调用返回「未启用」提示不执行。
|
||||
- 门面卡判据**只挂 `Truncated=true`**(既有 4.10 截断信号);同轮若已有显式 submit_card/card_commit 产物则不再自动建门面卡(防双卡)。
|
||||
- `to_project` 动作语义:卡复制为项目级卡(新 ID,`AnchorMessageID` 指向原卡),原会话卡置 `dismissed` 留痕——不移动(会话时间线不可变)。
|
||||
- 项目流事件持久化复用事件表机制,scope 列区分;重放从 `Last-Event-ID` 起(与会话流同构)。
|
||||
|
||||
## 5. 边界与不做
|
||||
|
||||
- 不动 `ResolveSystem` 签名与既有 SSE 会话流协议(项目频道是新增端点形态,非版本升级)。
|
||||
- 飞书/微信通道投影仍留远期(外部依赖,单独立卡)。
|
||||
- 不做项目级卡的细粒度权限(单用户体系内 owner 隔离已够;多用户项目共享收件箱随认证体系远期)。
|
||||
- `card_append` 不支持修改已提交内容(流式只追加;改稿 = 会话追问新卡)。
|
||||
- v1 不做「跨会话 done 卡聚合搜索」(产物检索归 RAG/工件库域)。
|
||||
|
||||
## 6. 验证计划
|
||||
|
||||
- CF-S:backend 单测(Append 节流合并/幂等、三段式 happy path、幻觉拒绝、run 失败/取消兜底置态)+ e2e `TestMockCardflowStreamLoop`(begin→append×N→commit → 卡 done + cardId 部件 + 锚点回填 + card.updated 序列断言)+ ctxasm golden 不变;组合 `make test`。
|
||||
- CF-A:ctxasm golden 更新(artifact 回填 + Truncations 记录);e2e(`#upload` 大结果 → 门面卡 + doc-ref + artifact_ref 并存 + 下一轮上下文含工件正文截断);前端锚点渲染 vitest。
|
||||
- CF3:store 迁移测试(project 索引、聚合查询、双方言);项目 SSE 频道 e2e(订阅/断线重放/seq 单调);前端双视图 vitest + pendingCount;D52 零页面走查双态 9+N 用例;组合 `make test` + `make build`。
|
||||
@@ -0,0 +1,52 @@
|
||||
# 卡流机制与中栏过程/回复分离 —— 设计与实施提案
|
||||
|
||||
状态:**已批准(2026-09-30 用户五项裁定)**;owner:weichen。
|
||||
前置:[界面交互底座](2026-09-30-ui-inbox-command-workspace.md)(D25–D33:收件箱/卡片/双现/互定位地基)、[界面重构与多用户认证](2026-09-30-ui-redesign-and-auth.md)(R1/R2/R4 已实施)。
|
||||
输入:目标系统设计全集 V2.2.1(`~/downloads/agent-system-design` 06 §3.3「助手卡片流」:AI 回复简短、长结果一律引导到右侧卡片);用户 2026-09-30 评估裁定。
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与裁定
|
||||
|
||||
评估报告结论:把目标系统「中栏短回复 + 右栏卡片承载产物」的交互泛化为基座通用能力。用户五项裁定:
|
||||
|
||||
1. **接受**模型显式路由为唯一切分机制(否决后处理自动拆分——拆分失败永远退化为现状,不丢内容);
|
||||
2. **认可**数据模型:产物全文权威在卡片(ActivityCard),消息只存引用部件 + 锚点渲染,上下文组装回填;
|
||||
3. **默认关**,用户手动打开(设置 → 界面 →「长结果进右侧卡片」);
|
||||
4. **CF0 先行**(中栏长回复折叠止痛,纯前端);
|
||||
5. **认可**对话完备性剧本修订:右栏不算「页面」,但卡内内容必须能从中栏锚点无跳转展开(验收项)。
|
||||
|
||||
同日新增需求:中栏过程/回复分离(Cursor 式)——流式期间思考/工具/中间轮解说照常显示,**一旦最终回复开始流式,过程即时折叠**,只显式显示回复,可手动展开回看。
|
||||
|
||||
## 2. 决策(D39 起:D34–D38 为认证提案 §5.1 预留段,虽未登记仍保持预留)
|
||||
|
||||
| # | 决策 | 理由 |
|
||||
|---|---|---|
|
||||
| D39 | **内容卡显式路由**:助手长产物经内置工具 `submit_card` 提交为收件箱 `kind=result` 卡;切分判据写进 prompt 模块 `style.cardflow` 的 outputContract(产物型进卡:报告/纪要/方案/对比/文档草稿;解释型长回答留中栏);不做后处理自动拆分 | 拆分发生在生成侧:可断言、可审计;失败模式安全(不出卡 = 全文留中栏) |
|
||||
| D40 | **卡片为产物权威载体**:全文存 ActivityCard(cardsvc 持久化),消息 parts 只存 `Part.card{cardId}` 引用 + 摘要锚点;`ctxasm` 组装时对 card 部件回填卡片正文(上限默认 12000 字,超限记 Truncations),上下文观测如实显示 | 与「registry 工具大结果→artifact+ref」模式同构;下一轮模型不失忆;时间线审计保留引用记录 |
|
||||
| D41 | **中栏过程/回复分离**:工具轮文本部件带 `process=true`(终轮文本无标记);live 流式期间 `phase=responding` 即时折叠过程(acting 重展开,交错轮不粘滞);终态 >800 字长回复折叠(CF0);LiveState 文本按 delta index 分桶(除末桶外均过程) | 用户裁定(Cursor 式);live 投影与落库消息同构,终态对账无跳变 |
|
||||
| D42 | **开关挂两层**:用户设置 `ui.cardflow`(boolean,默认 false,effect=new-run)控制工具注册与 prompt 注入;画像 PromptModules 可显式引用/排除 `style.cardflow` 覆盖 | 「用户可选」与画像机制严丝合缝;设置面 schema 驱动自动出现 |
|
||||
| D43 | **完备性剧本修订**(D31 增补):右栏收件箱不算「页面」;开启卡流时,零页面走查增加双态用例,卡内全文必须可从中栏锚点部件无跳转展开(窄屏/右栏收起时阅读不残废) | 「内容被藏起来」是分栏设计最高风险;中栏自足摘要是硬规则 |
|
||||
|
||||
## 3. 实施切分(三期)
|
||||
|
||||
| 期 | 内容 | 仓 |
|
||||
|---|---|---|
|
||||
| R1 过程分离 | D36 全部(backend 工具轮 process 标记 + Stream 注入缝;frontend 分桶/即时折叠/CF0) | backend / frontend |
|
||||
| R1 卡流 | D34/D35/D37:core CardPart.CardID;backend submit_card 工具+锚点回填+style-cardflow 模块+设置+ctxasm 回填+mock `#cardflow`;frontend 锚点部件(展开全文/跳收件箱)+result 卡体限高 | core / backend / frontend / ui / documents |
|
||||
| R2 收尾 | D32 另一半(卡→消息真定位,现误接 openInbox);result/待办轻分组;窄屏回退验证;双态零页面走查定稿 | frontend / documents |
|
||||
|
||||
跨仓顺序遵守 multi-repo 五步:core 先合入 → backend(同步 vendored core 副本)→ frontend/ui → Dev gitlink PR → 组合验证。
|
||||
|
||||
## 4. 契约与行为要点
|
||||
|
||||
- `CardPart{Kind, Data []UINode, CardID ID}`:CardID 非空 = 收件箱引用部件(submit_card);空 = 插件内嵌卡(现状不变)。
|
||||
- `submit_card(title, summary, body)`:runner 内置工具(不经注册表,同 update_todos 先例),仅 `ui.cardflow=true` 时声明;执行 = `CreateCard(kind=result, status=done, tone=ok, body=[markdown+摘要kv])` + 消息追加引用部件 + 工具返回「已发卡,对话中只留简短结论」;finishRun 持久化助手消息后把卡 `AnchorMessageID` 回填为该消息 ID。
|
||||
- 中栏锚点部件:`📄 {标题} · N 字 ▸ 展开全文 · ↗ 收件箱`——展开内联渲染卡片 body(数据从 store.cards 取),跳转走 openInbox;旧正则兜底保留(插件卡)。
|
||||
- 卡片内容流式(v1 一次性出卡)、飞书/微信通道投影(2.7 约束:新卡型必须能回答「这张卡在群里长什么样」——写入插件开发指南验收项)、与 artifact 统一、跨会话项目级收件箱:**均留远期**。
|
||||
|
||||
## 5. 边界与不做
|
||||
|
||||
- 不改 `ResolveSystem` 签名(注入 = 向 prof.PromptModules 副本追加模块 id);不动 SSE 协议版本。
|
||||
- `autoCollapseProcess` 偏好暂不加设置 UI(默认 true 即目标行为)。
|
||||
- 收件箱 pendingCount 语义不变(内容卡 status=done 不计入待办)。
|
||||
@@ -0,0 +1,113 @@
|
||||
# 文件预览与保留能力(工件删除 / fs 工具)方案
|
||||
|
||||
日期:2026-09-30;状态:已裁定(§5),实施任务 feature/file-preview-caps;作者:weichen + Agent
|
||||
关联:2026-09-30-ui-redesign-and-auth.md §9(上轮"下载为主"的非目标,本提案补齐)
|
||||
任务:feature/file-preview-proposal(本仓 PR 见任务卡)
|
||||
|
||||
## 1. 背景与诉求(用户原话归纳)
|
||||
|
||||
1. **文件预览补齐**:PDF、Word、PowerPoint、Excel、图像、markdown、代码文件等要有预览;可考虑做网页链接的预览;落位可以考虑右侧栏,或系统提供的弹出窗口。
|
||||
2. **工件删除**:接口和功能加入,但系统先不提供此类操作——保留能力,不暴露入口。
|
||||
3. **fs 类工具**:同样实现,但不提供(默认不可用)。
|
||||
|
||||
上轮交付后现状(chat-order-artifact-files,PR#71):
|
||||
- DocViewer(OverlayHost 单层):markdown 渲染、图片、pdf iframe、csv/json 等宽、二进制信息卡+下载;docx/xlsx/pptx 仅下载(当时定为非目标)。
|
||||
- `DELETE /api/artifacts/{id}` 后端端点已存在,前端工件面板有行内删除按钮(二次确认)。
|
||||
- 后端无任何 fs 类工具;文件面唯一入口是 artifacts(上传 + write_artifact)。
|
||||
|
||||
## 2. 合理性评估(逐条)
|
||||
|
||||
### 2.1 文件预览 —— 合理,方向正确;两点边界须裁定
|
||||
|
||||
**赞同**:
|
||||
- 预览是上轮"工作区文件管理"的自然完成项,闭环"浏览/显示/下载/回看"中的"显示"。
|
||||
- 落位思路与系统架构一致:预览应做成 @ui 能力组件(FilePreview),右栏轻预览与弹窗深预览复用同一组件——契合能力平台原则(系统基本功能做完善,插件直接复用)。
|
||||
|
||||
**边界与风险**:
|
||||
- docx/xlsx 的浏览器端解析**必须引入前端依赖**(mammoth / SheetJS),突破此前"无新依赖"约定——需 D1 裁定。缓解:全部 `dynamic import()` 按需加载,不进主 bundle,不预览不下载解析器。
|
||||
- pptx 没有轻量高保真方案(社区库重且还原度差)。推荐降级为**文本大纲级预览**(JSZip 解 slide XML,逐页标题+正文),全保真不做(D2)。
|
||||
- 网页链接预览:iframe 直嵌会被多数站点的 X-Frame-Options/CSP 拒绝,不可靠。推荐 **OG 元数据卡为主 + 可选"尝试内嵌"**(D3)。服务端抓取属 SSRF 高危面,硬边界:仅 http/https、解析后拒绝 localhost/环回/私有/保留地址、重定向逐跳同校验、超时与响应体上限。
|
||||
|
||||
### 2.2 工件删除:保留能力、不暴露入口 —— 合理,成本极低
|
||||
|
||||
- 后端端点**已存在**(无需新接口);动作是回退上轮加的行内删除按钮,保留 `api.deleteArtifact` 前端方法(插件可用)。
|
||||
- 与 API-first 原则完全一致:能力在公开 API,UI 只是第一个客户端;将来要暴露可走设置开关或管理端,不需要任何后端改动。
|
||||
- 文档与代码注释标注"保留能力,默认不暴露",防止后续误当死代码清理。
|
||||
|
||||
### 2.3 fs 工具:实现但不提供 —— 合理,契合能力预留;三点设计约束
|
||||
|
||||
**赞同**:实现合入(有测试、有沙箱、有门控),默认关闭=模型不可见、不进 `/api/tools`,插件/配置可开启。这正是"为插件系统预留能力"的形态。
|
||||
|
||||
**约束(缺一不可)**:
|
||||
1. **门控**:设置 `runtime.fsTools`(默认 false)。关闭时不注册工具——`/api/tools` 列表、模型可见工具集均不含;开启后仍受画像 tools.allow/deny/confirm 约束(现有机制)。
|
||||
2. **沙箱**:根目录 `<DataDir>/workspaces/<owner>/<conversationId>/`;路径规范化(filepath.Clean + Rel 校验)后必须仍在根内,拒绝 `..`、绝对路径与符号链接逃逸;单文件读写上限(如 2MB/1MB);list 深度与条数上限。
|
||||
3. **不变式**:write_file 落盘后**同步登记 artifact**(kind `file`,内容指针指向沙箱文件)——保住上轮确立的"工作区文件 = 工件面板可见可下载"不变式;read/list 仅限沙箱内。
|
||||
4. **测试**:路径穿越负例(`../`、绝对路径、URL 编码变体、符号链接)、上限、门控正负例。
|
||||
|
||||
## 3. 设计方案
|
||||
|
||||
### 3.1 FilePreview 能力组件(modules/ui)
|
||||
|
||||
```
|
||||
FilePreview({ source, compact? })
|
||||
source = { kind:'artifact', id, name, mime?, contentUrl, downloadUrl }
|
||||
| { kind:'url', href }
|
||||
```
|
||||
|
||||
- 按扩展名/mime 分派内部渲染器;渲染器注册表 `registerFilePreview(ext, renderer)` 预留插件自定义预览能力。
|
||||
- 格式矩阵(v1):
|
||||
|
||||
| 格式 | 方案 | 新依赖 |
|
||||
|---|---|---|
|
||||
| md/txt | marked 渲染(现成) | 无 |
|
||||
| 图像 | `<img>` + 点击放大 | 无 |
|
||||
| pdf | 同源 iframe(现成) | 无 |
|
||||
| csv/json/日志 | 等宽 pre(现成) | 无 |
|
||||
| **代码文件**(go/py/ts/js/rs/java/sh/sql/yaml/toml/html/css 等) | 等宽 + 行号(不做语法高亮;高亮若做需 highlight.js,另行裁定) | 无 |
|
||||
| **docx** | mammoth → HTML(样式收敛进 tokens) | mammoth(动态) |
|
||||
| **xlsx/xls** | SheetJS → 表格(sheet 切换,默认首 200 行分页) | xlsx(动态) |
|
||||
| **pptx** | JSZip 解 slide XML → 逐页文本大纲卡 | jszip(动态) |
|
||||
| **url** | OG 卡(标题/描述/站点/图标/首图)+「尝试内嵌」按钮(iframe 加载失败回退提示) | 服务端 `/api/url-preview` |
|
||||
|
||||
- **双落位**(D4):右栏新增"预览"页签(compact 模式:窄栏排版,边聊边看,可随消息工件 chip 一键换内容)+ OverlayHost 深预览(现 DocViewer 收敛为 FilePreview 的容器壳:工具栏=编辑/保存/下载/关闭)。两处同一组件、同一注册表。
|
||||
|
||||
### 3.2 后端(modules/backend)
|
||||
|
||||
- `GET /api/url-preview?url=`:SSRF 硬边界(见 §2.1);返回 `{title, description, siteName, favicon, image, finalUrl}`;解析 og:title/og:description/og:image/og:site_name,回退 `<title>`/meta description。5s 超时、256KB 截断、Content-Type 须 text/html。缓存(内存 LRU,15 分钟)。
|
||||
- fs 工具族 `internal/tools/fs.go`:`fs_read_file` / `fs_write_file` / `fs_list_dir`,参数与返回结构走既有工具契约;门控与沙箱见 §2.3。
|
||||
- 工件删除:**零后端改动**。
|
||||
|
||||
### 3.3 前端(modules/frontend)
|
||||
|
||||
- DocViewer 收敛为 FilePreview 容器(删自有格式分支);右栏 Workbench 增"预览"页签 + `store.previewSource`。
|
||||
- 消息工件 chip 与 ArtifactsPanel 行提供两个入口:默认弹窗深预览,"右栏"按钮轻预览;**移除删除按钮**(保留 api 方法与注释说明)。
|
||||
- URL 入口(P3):会话输入框粘贴纯 URL → 消息侧生成链接预览卡(点击进右栏/弹窗预览);插件卡 doc-ref 同样可指向 url 工件。
|
||||
|
||||
### 3.4 依赖与体积
|
||||
|
||||
mammoth + xlsx + jszip 全部动态 import,仅对应格式首次预览时加载;主 bundle 不变。版本随 frontend package-lock 锁定。
|
||||
|
||||
## 4. 分期与验收
|
||||
|
||||
| 期 | 内容 | 验收 |
|
||||
|---|---|---|
|
||||
| P1 预览基座 | FilePreview 组件+注册表;双落位;md/图片/pdf/csv/json/代码文件迁移收敛;删除按钮回退 | @ui vitest(组件矩阵);头链:右栏预览页签+弹窗各断言;面板无删除按钮、API 仍在 |
|
||||
| P2 Office | docx/xlsx/pptx 渲染器(动态加载) | 样例文件矩阵头链断言(docx 出正文、xlsx 出表格、pptx 出大纲);懒加载不进主 bundle(构建产物体积对比) |
|
||||
| P3 URL 预览 | /api/url-preview + LinkPreview + 输入框 URL 卡 | go 测试:OG 解析正例 + SSRF 负例(localhost/私网/非 http 协议/超时);头链:链接卡→右栏预览 |
|
||||
| P4 fs 工具 | 实现+门控+沙箱+测试(默认关) | go 测试:穿越负例全红、门控关时不注册;开启后 write_file 产物出现在工件面板 |
|
||||
|
||||
## 5. 决策点(2026-09-30 用户已裁定)
|
||||
|
||||
- **D1 Office 依赖**:✅ **引入** mammoth/SheetJS(动态加载,不进主包)。
|
||||
- **D2 pptx 深度**:✅ **全保真渲染**(用户选择;接受重型依赖与还原度折衷——实施时在 pptx-preview / pptx2html 候选中取与 Vite 兼容者,若均不可构建则带回证据退回文本大纲级再裁定)。
|
||||
- **D3 URL 预览形态**:✅ **OG 卡 + 可选尝试内嵌**。
|
||||
- **D4 预览落位**:✅ **右栏 + 弹窗双落位**。
|
||||
- **D5 fs 开关可见性**:按推荐默认执行——仅配置/环境可开(runtime.fsTools 默认 false),设置页不显示开关。
|
||||
- 实施任务:feature/file-preview-caps(P1–P4 一体交付)。
|
||||
|
||||
## 6. 边界与不做
|
||||
|
||||
- 不做 Office 文件的在线**编辑**(预览只读;md/txt/csv/json 编辑能力维持现状)。
|
||||
- 不做语法高亮(如需引入 highlight.js 另行裁定)。
|
||||
- 不做服务端格式转换(LibreOffice/onlyoffice 等违反单二进制非容器化部署约束)。
|
||||
- 删除能力不做确认流之外的新入口形态;fs 工具不做 shell/exec 类操作(只做文件读写列目录)。
|
||||
@@ -200,3 +200,29 @@ demo 为静态设计稿(无后端),数据为演示数据;视觉规格(
|
||||
- 本提案不含实施;实施前须用户裁定方案(默认 A)与认证阶段 2 范围。
|
||||
- 不做计费/套餐(Footer 用量区留位即可)、不做 SSO/OAuth、不做项目级 ACL(远期,见 5.2)。
|
||||
- 中栏与右栏结构不在本次重构范围(D25/D26 已定且未被推翻);底部终端面板不做(我们非终端工作台)。
|
||||
|
||||
## 9. 交付补记(2026-09-30 下午:消息顺序保真 + 工件文件管理)
|
||||
|
||||
用户验收 UI 能力平台后反馈四项,本轮修复(任务 feature/chat-order-artifact-files):
|
||||
|
||||
1. **消息顺序保真(新设计约定:渲染顺序 = 落库顺序)**。后端 llm.go 按时间交错落库 parts
|
||||
(text → tool_call → tool_result → artifact_ref → 下一轮 text…),前端曾按 `p.process`
|
||||
布尔二分重排(过程全前置),工具调用脱离真实位置。修复:`groupParts` 只把"连续的"过程
|
||||
部件就地聚成折叠块,内容部件原位保留;live 流式改为 `LiveState.parts` 按事件到达序维护
|
||||
(artifact.created 即时上 chip)。该约定写入 UI 层默认要求:任何 parts 分组展示不得改变原始顺序。
|
||||
2. **下载通道**。后端 `artifactContent` 始终回 `Content-Disposition`(默认 inline,
|
||||
`?download=1` → attachment,RFC 5987 filename* 支持中文名)+ Office/PDF 扩展名 MIME 表;
|
||||
前端 chip / 工件面板行 / DocViewer 三处都有下载动作。
|
||||
3. **工件面板 = 工作区文件管理**(浏览/显示/下载/回看/删除):搜索 + kind 过滤 + 上传 +
|
||||
行内 预览/下载/删除(二次确认);DocViewer 重构为单层 OverlayHost(pdf 内嵌 iframe、
|
||||
csv/json 等宽、二进制信息卡 + 下载 CTA);收件箱结果卡经 docBridge 可直接打开工件。
|
||||
4. **设置界面级联修复**。SettingsShell 根元素同挂宿主 `.modal`,被后加载的宿主样式以同
|
||||
特异性覆盖(column/860px)压坏左右布局——改为 `.modal.ui-settings-shell` 复合选择器
|
||||
(0,2,0)免疫加载顺序;设置行统一 @ui SettingRow,保存按钮进固定 actions 底栏。
|
||||
教训入库:**@ui 组件挂宿主通用类时,外壳样式必须用复合选择器高于宿主单类**。
|
||||
|
||||
验证:ui 37 + frontend 20 vitest 全绿;backend go test ./... 全绿(含 disposition/MIME 新测);
|
||||
Playwright 头链 12/12(mock 后端 + SQLite 直插交错 parts 探针消息):
|
||||
DOM 序 [text→process-collapse→text→artifact-chip]、设置外壳 computed flex-direction=row 且
|
||||
侧栏/主区并排、?download=1 → attachment+RFC5987 文件名、面板工具栏/过滤/上传/行内下载。
|
||||
docx 在线预览为非目标(下载为主);工作区文件即 artifacts(不新增 fs 工具/目录)。
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# 2026-09-30 · cardflow-r1 验证记录(CF1 机制 + CF2 收尾)
|
||||
|
||||
任务:Dev 仓 `feature/cardflow-r1`(提案 [2026-09-30-cardflow-and-process-split.md](../proposals/2026-09-30-cardflow-and-process-split.md),D39–D43)。
|
||||
|
||||
## 证据矩阵
|
||||
|
||||
| 层 | 验证 | 结果 |
|
||||
|---|---|---|
|
||||
| Core 契约 | `go test ./...`(golden message.json 锁定 `cardId` 线格式) | ok |
|
||||
| Backend 机制 | `internal/runtime`(submit_card 门控/产卡/引用部件)、`internal/ctxasm`(回填/截断/占位/card(n) 观测) | ok |
|
||||
| Backend 全链路 | `internal/server` e2e `TestMockCardflowFullLoop`:#cardflow → result 卡入收件箱 → 助手消息含 cardId 引用部件 → finishRun 回填 AnchorMessageID | ok |
|
||||
| UI 组件 | `npx tsc --noEmit` 0 错误;`npx vitest run` 39/39(card-anchor 6 用例:摘要行/展开/收起/locate 不触发展开/无卡兜底/无 locate 不渲染按钮) | ok |
|
||||
| Frontend | `npx tsc --noEmit` 0 错误;`npx vitest run` 27/27(live 分桶/过程标记 + 既有回归) | ok |
|
||||
| Dev 组合 | `make test` 退出码 0;`make build` 版本串 core=bcfacee+backend=ef17ad0+frontend=832a2ad+ui=31019ad | ok |
|
||||
|
||||
## 双态零页面走查(无头 Chromium 1680×1000,mock 服务 127.0.0.1:18923)
|
||||
|
||||
| # | 断言 | 结果 |
|
||||
|---|---|---|
|
||||
| 1 | 关闭态:普通消息出现过程折叠(行为不回退) | PASS |
|
||||
| 2 | 关闭态:无卡流锚点 | PASS |
|
||||
| 3 | 设置:`ui.cardflow` 写入 user scope 200 | PASS |
|
||||
| 4 | 设置:schema 含 ui.cardflow(ui 组,默认 false) | PASS |
|
||||
| 5 | 开启态:#cardflow 后中栏出现卡流锚点 | PASS |
|
||||
| 6 | 开启态:锚点内联展开全文(含决议正文——D38 中栏自足) | PASS |
|
||||
| 7 | 开启态:收件箱出现 result 卡(会议纪要 · 第 12 次周会) | PASS |
|
||||
| 8 | D32:收件箱卡「↙ 定位到对话」滚动中栏至产生消息(data-msg-id) | PASS |
|
||||
| 9 | 锚点「↗ 收件箱」定位收件箱卡 | PASS |
|
||||
|
||||
合计 9/9 通过;构建组合:bin/agent-system(0c4e404+core=bcfacee+backend=ef17ad0+frontend=832a2ad)。
|
||||
|
||||
## 说明与剩余项
|
||||
|
||||
- backend/frontend 分支已 rebase 至并行任务 `feature/chat-process-split-r1` 演进 tip(ae8e5d0 / 754ce83)之上,冲突双方语义均保留(fakeRunCtx 字段合并、两套测试并存)。
|
||||
- 任务 1(chat-process-split-r1)的 review-fix 与 PR 归并行会话所有者;本任务卡与 PR 链只覆盖 cardflow-r1。
|
||||
- PR 链(core→documents→ui→backend→frontend→Dev gitlink)依赖 Gitea 面板可用性:面板中断期间全部分支已推送(SSH),PR 创建与合入由 `/tmp/cardflow-pr-chain.sh` 面板恢复后自动执行(含 Dev 分支落后基线检查,落后即中止转人工)。
|
||||
- 远期(CF3):卡片内容流式、飞书/微信通道投影、与 artifact 统一、跨会话项目级收件箱。
|
||||
Reference in New Issue
Block a user