# Pi Agent Web 迭代任务
目标:在保留 Java v1 + React + SSE 主链路的前提下,把
web/演进为生产级 Agent 控制台。 原则:所有新增能力直接围绕 Java v1 + React + SSE 主链路演进,不引入独立的 Node/WebSocket 实验运行时。 状态说明(2026-08-14,按当前源码核对):[x]已实现;[ ]尚未完成。部分实现项拆为子项,避免将未完成的验收能力标为完成。
# 架构优先级(后续迭代遵循)
在引入多智能体、分布式状态、RAG/向量库、Docker/Kubernetes 沙箱或控制面之前, 优先把 单 Agent Core 契约与可观测性 做扎实。当前 pi-agent-v1 的目标是一个 可独立运行、可审计、可扩展的 ReAct runtime,而不是提前复刻完整 Harness/Service 平台。
近期的 Core 收敛边界:
RuntimeContext显式贯穿每次运行,承载runId、sessionId、userId、workspace 与请求级属性;- 所有核心流式事件经事件信封携带稳定
eventId、时间戳与runId,便于 SSE 去重、回放和 Trace 对齐; AgentLoop只保留模型—工具主循环;日志、观测、system prompt 变换等通过轻量 middleware 扩展;- 权限继续采用工具自身不可绕过的安全检查 + 运行前决策;会话长期状态随后再收敛为
AgentState; - 不将 Harness 级能力作为本阶段依赖,避免为单机 v1 引入不必要的分布式复杂度。
# 迭代记录约定
每次有代码、协议或行为改动的迭代,都在 docs/iterations/ 新增一份独立 Markdown,命名为 YYYY-MM-DD-<slug>.md。记录必须包含:目标、变更文件、契约变化、验证证据、已知边界与下一步; 不以滚动更新本计划替代具体变更记录。纯讨论但未落地的方案不创建迭代记录。
# 当前基线
- [x] 独立前后端:Java v1 API(8787)+ React 主 UI(5173)。
- [x] 多会话、历史分页、模型切换。
- [x] SSE:消息、回合、工具开始/结束、压缩、错误、Token usage。
- [x] 工具参数/结果折叠卡、运行链路与会话 Token 累计。
- [x] 已建立
docs/ARCHITECTURE.md:明确 HTTP 接入、Agent 编排、工具、交互、存储与前端后续拆分边界。
# P0:主对话富渲染
- [x] Markdown 渲染:标题、列表、引用、表格、任务清单。
- [x] 代码块:
- [x] 语言高亮、复制。
- [x] 折叠、超长代码展开。
- [x] 流式文本:
- [x] 生成光标、消息淡入。
- [x] 仅在用户位于底部时自动滚动。
- [x] 回答内工具引用:点击后定位并展开对应工具卡。
验收:已在本地浏览器的 Mock 工具调用中验证 Markdown 表格、长代码块展开,以及工具引用定位并展开;流式自动滚动仅在距底部 72px 内触发。
# P1:执行链路与工具体验
- [x] 单次对话可视化链路:可从运行卡打开持久化回放,展示 Agent、轮次、模型调用、工具、耗时、状态与完整交互记录。
- [x] Agent 运行活动:
- [x] 执行中、结束后的轻量活动行与错误提示;详细链路不占用对话卡片。
- [x] 等待、明确成功/失败、已取消状态。
- [x] 运行中可请求暂停:当前模型或工具步骤结束后停止后续执行,并持久化为已取消运行。
- [x] 回合时间轴:
- [x] 轮次、工具数、运行累计 Token 使用量。
- [x] 耗时。
- [x] 工具卡:
- [x] 参数、输出、错误、折叠展开、JSON 高亮。
- [x] 耗时、输出行数、复制与下载。
- [x] 长日志:按需渲染、搜索、错误行高亮、虚拟列表。
- [ ] 事件摘要:
- [x] 上下文压缩、权限拒绝与错误提示。
- [x] 超时、重试的后端事件与单独展示。
- [x] 运行结束回执:
- [x] 步骤数、工具数、Token。
- [x] 总耗时、轮次汇总、失败数。
后端依赖:SSE 已透出 toolCallId、工具耗时 durationMs、输出行数 outputLines、超时标记 timedOut;agentend 携带 status/turnCount/failureCount/durationMs,turnend 携带 durationMs;历史 JSONL 已持久化 durationMs/lineCount/timedOut,刷新后可恢复展示(含超时状态)。模型失败自动重试 1 次并发出 model_retry 事件。
暂停契约:POST /api/chat/pause 接收 { sessionId } 并返回 202。它设置当前运行的协作式取消信号;AgentLoop 会在下一轮开始、模型返回后以及每个工具调用前检查该信号,追加 ABORTED 消息并以 agent_end.status=cancelled 收束运行。不会为“立即停止”强杀正在执行的模型 HTTP 请求或子进程。
# P1:Token 与可观测性
- [x] 每轮输入/输出/总 Token(SSE 逐 assistant 返回 usage,前端按轮次归属展示明细)。
- [x] 会话累计 Token 与运行累计 Token。
- [ ] 数字滚动动画;接近预算阈值时颜色提示。
- [x] 接近预算阈值时颜色提示:>80% 黄色、≥100% 红色,前端常量
TOKENBUDGET=100000。 - [ ] 数字滚动动画。
- [ ] 运行期间的模型、上下文、压缩次数、错误次数摘要。
- [ ] 导出本次运行记录为 Markdown / JSON。(已确认本期不做)
验收:Token usage 已随 assistant 历史消息持久化并在刷新后返回;仍需以真实模型 API usage 对账验证。
# 待排期:模型推理流展示
目标:在回答正文之前展示模型实际返回的推理增量,默认折叠;它是模型流式输出的独立通道,不与最终回答文本混合。
- [ ]
Model/OpenAIModel解析 provider 的reasoning_content(或等价字段)。 - [ ] Core 新增
reasoningstart、reasoningupdate、reasoning_end事件,并由 Web SSE 透传。 - [ ] 前端在 assistant 消息开头渲染默认折叠的“思考过程”块,实时追加 reasoning delta;最终回答继续使用现有
message_*事件。 - [ ] 在
models.json增加推理模型配置(例如deepseek-reasoner),并以真实 API 验证文本、工具调用、usage 与异常重试。
边界:当前 deepseek-chat 通常不返回推理字段,不能把模型自行生成的 <thinking> 文本当作真实推理链;仅展示 provider 明确返回的 reasoning 数据。默认关闭展开,且不把 reasoning 纳入用户可见的最终答案或长期摘要。
# P2:文件工件与结果预览
- [x] 工件卡片:名称、类型、大小、产生时间、来源工具。
- [x] 图片缩略图与大图预览。
- [x] Markdown、文本、代码、JSON、CSV 预览。
- [x] 本轮多个工件聚合显示。
- [x] 详情抽屉:预览、下载、复制路径。
- [x] 工具结束即通过 SSE 追加工件元数据;图片缩略图直接渲染在对应工具卡中。
后端契约:GET /api/artifacts?sessionId=... 返回本会话成功 write/edit 或显式声明 bash.artifacts 的 artifacts[],包含 id(即 toolCallId)、path、name、mimeType、size、createdAt、modifiedAt、sourceTool 与 previewable;GET /api/artifacts/{id}/content?sessionId=... 按需返回不超过 1 MB 的内容。toolexecutionend 也会携带首个工件元数据供实时 UI 渲染。路径即使在关闭工具沙箱时也必须重新校验在 workspaceRoot 内。
# P2:模型追问与选项卡片
模型需要用户补充信息时,暂停执行并发送结构化交互事件;用户选择或输入后,恢复同一次 Agent 任务。
- [x] 单选、多选、推荐项、风险确认卡。
- [x] “其他”自定义输入框。
- [x] 表单字段:文本、日期、目录、数字、开关。
- [x] 快捷动作:预览、返回修改。
- [x] 提交后显示用户的选择,且卡片变为只读回执。
- [x] 支持取消、超时和页面刷新后的状态恢复。
建议 SSE 事件:
{
"id": "ask_001",
"type": "user_input_required",
"title": "请选择生成方式",
"description": "不同方式会影响输出格式。",
"mode": "single",
"options": [
{ "id": "web", "label": "网页报告", "description": "适合浏览和分享" },
{ "id": "markdown", "label": "Markdown 文件", "description": "适合纳入项目" },
{ "id": "both", "label": "两者都要", "recommended": true }
],
"allowCustom": true,
"submitLabel": "继续"
}
已实现接口:POST /api/chat/continue,提交 { sessionId, requestId, selections, customInput };待答表单持久化到 AgentState.pendingInteraction,提交后写入原 toolCallId 的 ToolResult,再启动下一段 AgentLoop。
后端边界:AgentLoop 通过 Suspend 结束当前 HTTP/SSE 段,/api/chat/continue 基于持久化会话启动新的执行段;因此服务重启后仍可提交待答表单,但并非恢复原 JVM 栈或同一 SSE 流。页面刷新可重新读取并展示 pending 卡片。
# P3:响应式与生产化体验
- [x] 会话工作页:左侧会话栏固定;对话、轨迹、工件以全宽页签切换,不使用详情侧栏。
- [x] 移动端抽屉:会话、运行链路和工件预览不挤压对话区。
- [ ] 长任务迷你地图:按轮次/工具跳转。
- [x] 命令面板:新建会话、链路、工件、清空与减少动效(
⌘/Ctrl + K)。 - [x] 无障碍:键盘可进入会话、焦点态、减少动态效果开关。
- [ ] 性能:消息虚拟列表。
- [x] 历史分页、工件懒加载、日志窗口化与截断。
- [ ] 运行恢复:
- [x] 会话历史与 Token usage 可在刷新后加载。
- [x] 新产生的 Agent 运行会持久化
traceId,刷新后仍可从对话活动行进入轨迹页。 - [x] 当前会话选择、侧栏折叠与已选工件恢复;未完成 ask_user 任务由
AgentState恢复。 - [ ] 运行卡及单工具展开状态恢复。
# P2:前端体验精修(参考 deepseek-harness)
来源:2026-08-14 对
deepseek-harness前端代码的完整走读。以下各项均标注了参考路径和落地难度。
# 1. 运行统计条(StatsLine)
在输入框下方固定展示一行紧凑的运行统计,让用户随时了解 Agent 效率。
- [x] 前端聚合 SSE 事件中的
usage、durationMs、turnCount等数据,渲染为管道符分隔的统计条。 - [x] 紧凑数字格式:Token 517 / 12.2K / 517K / 1.2M;时间 45.2s / 2m42s。
- [x] 超长时 ellipsis 截断 + hover tooltip 显示完整内容。
- [x] 分组规则:轮次/步骤数 | LLM 耗时 · 工具耗时 | TTFT · tokens/s | Input · Output。
参考:packages/client/ui-conversation/src/client/chat/StatsLine.tsx 难度:小(纯前端聚合,SSE 数据已齐全)
# 2. 工具行 Shimmer 扫光动画
工具执行时,工具行出现一道从左到右的扫光效果,比 spinner 更优雅。
- [x] 纯 CSS 实现:
data-state='running'时::after伪元素做 300px 光带平移,2.6s ease-out 循环。 - [x] 工具结束(success/failed/timeout)后扫光立即停止。
- [x] 与
prefers-reduced-motion联动:减少动效开启时禁用扫光。
参考:packages/client/ui-tool/src/client/tool/components/ToolRow.module.css @keyframes dsh-tool-row-sweep 难度:极小(纯 CSS,零依赖)
# 3. 审批面板 Composer Takeover
高风险命令审批从弹窗升级为沉浸式——审批出现时替换输入框区域,用户注意力更集中。
- [x] 审批 pending 时,输入框区域替换为审批卡片:琥珀色顶部条带 + 脉动圆点。
- [x] 卡片内容:模型给出的审批理由作为标题,命令以 monospace 灰色显示。
- [x] 长命令在卡片内滚动(
max-height限制),但拒绝/允许按钮始终固定在视口内。 - [x] 一次性 latch:点击后按钮立即 disable,请求失败时自动恢复可点击。
- [x] 审批完成后(允许/拒绝/超时),输入框区域自动恢复。
参考:packages/client/ui-conversation/src/client/skeleton/ApprovalPanel.tsx + .module.css 难度:中(需要调整审批交互从弹窗到 composer 区域的布局切换)
# 4. 追问表单分页与 IME 兼容
多步追问支持分页浏览,并兼容中文输入法。
- [x] 多步分页:
1 / N进度 + 左右翻页按钮,每步独立校验。 - [x] 选项支持
recommended标记,自动解析“(推荐)”后缀并显示徽章。 - [x] 跳过按钮:未完成的问题可跳过,提交前校验未回答项并自动跳转。
- [x] IME 兼容:Enter 键检测
nativeEvent.isComposing || keyCode === 229,避免中文输入过程中误触发提交。 - [x] 自定义输入框:有选项时在末尾追加“其他”输入行;无选项时展示 textarea。
参考:packages/client/ui-user-questions/src/client/QuestionComposer.tsx 难度:中(前端交互逻辑较多,但后端 ask_user 协议无需改动)
# 5. 工具 IN/OUT 展开卡片
工具展开后用结构化卡片分别展示输入参数和输出结果。
- [x] IN/OUT 双 section 卡片,用
grid-template-columns: max-content 1fr让标签左对齐。 - [x] IN/OUT 标签
position: sticky滚动时始终可见。 - [x] 输入和输出各自独立滚动(
max-height: 150px),长输出不会淹没短输入。 - [x] 错误输出使用 error 颜色标记。
参考:packages/client/ui-tool/src/client/tool/components/ToolRow.module.css .ioCard / .ioSection / .ioLabel 难度:小(纯 CSS + 少量 JSX 结构调整)
# 6. 模型重试实时倒计时
model_retry 事件渲染为可折叠的倒计时组件,而非静态文本。
- [x]
<details>折叠元素,默认收起,展开后显示延迟、失败原因。 - [x] 实时倒计时:以浏览器
Date.now()为锚点,250ms 间隔更新,避免服务端/客户端时钟偏差。 - [x] 倒计时归零后自动停止 interval。
- [x] 区分 scheduled / active / cancelled 三种重试状态。
参考:packages/client/ui-conversation/src/client/chat/MessageItem.tsx ModelRetryItem 难度:小(SSE model_retry 事件已携带 delayMs)
# 7. 三栏布局自动折叠
窄屏自动折叠侧栏,宽屏恢复,拖拽手柄更流畅。
- [x]
ResizeObserver+ rAF 节流监听容器宽度(非 window),窄于阈值时自动折叠侧栏。 - [x] 拖拽手柄使用
setPointerCapture+ rAF 节流,避免动画与手指脱节。 - [x] 拖拽期间通过
data-dragging属性禁用 CSS transition。
参考:packages/client/ui-layout/src/client/AppFrame.tsx 难度:中(需调整现有 ColumnResizeHandle 的实现)
# 推荐实施顺序
- P0 Markdown/代码渲染 + 工具卡增强。
- P1 执行时间轴 + 运行回执 + Token 面板。
- P2 模型追问选项卡片与 Agent 暂停/继续协议。
- P2 工件预览与详情抽屉。
- P3 长日志性能、迷你地图、移动端与状态恢复。
- P2 前端精修:统计条 → Shimmer 动画 → IN/OUT 卡片 → 重试倒计时 → 审批 Takeover → 追问分页 → 布局自动折叠。
# 每项任务完成标准
- 有明确 SSE/API 数据契约,且后端日志可定位异常。
- 新功能在流式、工具、多轮、历史恢复四种场景下验证。
web/的 TypeScript 检查与生产构建通过。- 不引入与 Java v1 主链路重复的独立 Agent 运行时。
- 本次核对:
web/已通过npm run build(TypeScript 检查 + Vite 生产构建);仍有单个压缩包大于 500 kB 的构建告警,尚未做拆包优化。