dsh-activity-pane 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +5305 -0
- package/.dsh-plugin/index.mjs +5 -0
- package/LICENSE +38 -0
- package/README.md +41 -0
- package/cordis.patch.yml +7 -0
- package/package.json +68 -0
- package/scripts/acceptance.mjs +203 -0
- package/scripts/bench.mjs +151 -0
- package/scripts/build-client.mjs +46 -0
- package/scripts/check-staged-client.mjs +47 -0
- package/scripts/check.mjs +3536 -0
- package/scripts/watch.mjs +43 -0
- package/src/client.mjs +3269 -0
- package/src/core.mjs +1933 -0
- package/src/host.mjs +207 -0
- package/src/navigation.mjs +91 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-activity-pane contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
### Third-party notices
|
|
26
|
+
|
|
27
|
+
The multi-session card visual layout (`src/client.mjs` CSS, `core.mjs`
|
|
28
|
+
normalization ideas) is adapted from
|
|
29
|
+
[`dsh-answer-pet`](https://github.com/Nanki-nn/dsh-answer-pet) (MIT License,
|
|
30
|
+
Copyright (c) Nanki-nn). See README.md for details.
|
|
31
|
+
|
|
32
|
+
The agent-role robot icon (`createRobotIcon` in `src/client.mjs`) uses the
|
|
33
|
+
`bot` icon geometry from [Lucide](https://lucide.dev), ISC License:
|
|
34
|
+
Copyright (c) for portions of Lucide are held by Cole Bemis 2013-2022 as part
|
|
35
|
+
of Feather (MIT). All other copyright (c) for Lucide are held by Lucide
|
|
36
|
+
Contributors 2022. Permission to use, copy, modify, and/or distribute this
|
|
37
|
+
software for any purpose with or without fee is hereby granted, provided that
|
|
38
|
+
the above copyright notice and this permission notice appear in all copies.
|
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# dsh-activity-pane
|
|
2
|
+
|
|
3
|
+
DSH (DeepSeek Harness) 一大痛点是缺少活动会话与历史会话的管理,重度用户在同时运行跨越多个工作区的多个会话时,无法一目了然的掌控全局,尤其当 DSH 原生左边栏工作区的会话积累过多之后,活动会话的信息过于分散,无法解答以下问题:
|
|
4
|
+
- 现在有多少个会话在并行跑?
|
|
5
|
+
- 哪些会话启动了子代理甚至孙代理会话?它们有多少?
|
|
6
|
+
- 每个会话现在正在做什么?它们的进度如何?跑了多长时间?
|
|
7
|
+
- 每个会话使用什么模型?什么推理级别?输出速率、缓存命中率和 token 使用情况如何?
|
|
8
|
+
- 哪些会话的 agent 轮次最近刚结束,需要我行动?
|
|
9
|
+
- 过去一段时间我在哪些会话里交互过?最近的指令与结论是什么?
|
|
10
|
+
- ...
|
|
11
|
+
|
|
12
|
+
本插件试图解决这些问题,提供了一个**活动会话总览窗格**:将正在运行的会话、子会话、轮次完成后等待行动的会话、近期活跃过的历史会话,集中在一个窗格内进行整体展示。
|
|
13
|
+
|
|
14
|
+
<p align="center">
|
|
15
|
+
<picture>
|
|
16
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/screenshot-desktop-dark.png">
|
|
17
|
+
<img src="assets/screenshot-desktop-light.png" width="1000" alt="隔离演示环境中的活动会话总览窗格:展示运行统计、子代理层级、提问等待、完成提醒、错误提醒和最近历史">
|
|
18
|
+
</picture>
|
|
19
|
+
</p>
|
|
20
|
+
<p align="center"><sub>同一干净隔离环境中模拟编程任务的 <a href="assets/screenshot-mobile-dark.png">移动端深色抽屉</a> · <a href="assets/screenshot-mobile-light.png">移动端浅色抽屉</a></sub></p>
|
|
21
|
+
|
|
22
|
+
## 感谢与声明
|
|
23
|
+
|
|
24
|
+
- 本项目灵感来自 [`dsh-answer-pet`](https://github.com/Nanki-nn/dsh-answer-pet) 插件,借鉴了其中会话卡片的设计思路,并按自己的使用习惯与喜好做了调整与重新实现,感谢原作者的创意!
|
|
25
|
+
- 折叠时间线的分组语义改编自 MIT 许可的 [`dsh-auto-collapse`](https://github.com/a179-sanae/dsh-auto-collapse) 插件(数据层移植,无运行时依赖),感谢原作者。
|
|
26
|
+
- 时间线正文行与最近卡的 agent 角色机器人图标采用 ISC 许可的 [Lucide](https://lucide.dev) `bot` 图标几何,感谢 Lucide 贡献者。
|
|
27
|
+
- 本项目代码与文档 99.99% 由 AI 编写与审核,大概率存在 bug 与文档/代码不同步等问题,使用中如遇到问题请提交 issue。
|
|
28
|
+
|
|
29
|
+
## 相比 dsh-answer-pet 的调整
|
|
30
|
+
|
|
31
|
+
- [x] **从浮层改为固定窗格**:桌面端在左边栏工作区的右侧增加常驻贴边列;移动端使用默认隐藏的固定抽屉,通过会话头部的「活动」按钮展开,不挤压主会话布局。
|
|
32
|
+
- [x] **去除宠物图标功能**:不支持宠物相关功能,界面聚焦于会话活动本身。
|
|
33
|
+
- [x] **原生数据源订阅**:直接订阅 DSH 原生 `sessions` / `workspaces` 服务的推送式快照;时间线最多显示 4 个折叠工作项行,保留最近用户指令与真实执行中的工作项。
|
|
34
|
+
- [x] **增加历史会话列表**:窗格分为「活动会话」和「最近历史」两个区域,非活动主会话在最近 24 小时内仍可快速找回。
|
|
35
|
+
- [x] **强化等待行动提醒**:阻塞等待、完成提醒与错误提醒分别以金色、绿色和红色卡片标识;提问直接预览问题列表,完成提醒经卡片上的「移入历史」按钮显式确认;状态由宿主侧持久化并在所有客户端间同步,刷新页面或另开窗口不会丢失未确认的完成提醒和尚未被新回合覆盖的错误提醒。
|
|
36
|
+
- [x] **显示子/孙会话层级**:子代理以连接线和紧凑卡片嵌套在母会话下;母会话自身回合结束但仍有活动后代时继续按运行中呈现,子代理结束且没有活动后代后从活动区消失;历史区只保留主会话。
|
|
37
|
+
- [x] **显示工作区名称并参与排序**:会话卡片显示带稳定色彩的工作区徽标,会话排序与左侧边栏中的工作区顺序保持一致。
|
|
38
|
+
- [x] **展示当前工作与运行统计**:活动卡以最多 4 行折叠时间线展示最近指令、思考与工具调用;运行中卡片还显示回合进度、输出速率、缓存命中率、输入/输出 token 与运行时长。
|
|
39
|
+
- [x] **加入会话导航跳转**:点击或键盘激活会话卡片可跳转到对应会话页面,当前会话保持高亮。
|
|
40
|
+
- [x] **增加会话元信息**:会话卡片中显示当前使用的模型名称和推理级别。
|
|
41
|
+
- [x] **完善桌面与移动交互**:桌面窗格可折叠、拖拽调宽并记忆宽度;移动端使用不挤压主会话布局的固定抽屉;长列表提供独立滚动与回到顶部按钮。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Mount the activity pane as its own bundle. The plugin only augments the web
|
|
2
|
+
# client; the host half is a no-op. It does not depend on nor patch
|
|
3
|
+
# dsh-answer-pet — all session data comes from DSH's native sessions/workspaces
|
|
4
|
+
# client services.
|
|
5
|
+
- insert:
|
|
6
|
+
- id: dsh-activity-pane
|
|
7
|
+
name: dsh-activity-pane
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-activity-pane",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"packageManager": "pnpm@11.5.0",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": ">=20"
|
|
7
|
+
},
|
|
8
|
+
"description": "DeepSeek Harness Web 活动会话总览窗格:将正在运行与等待用户行动的会话集中到一个可贴边、可滚动的左侧窗格",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/ccll/dsh-activity-pane.git"
|
|
13
|
+
},
|
|
14
|
+
"main": "./.dsh-plugin/index.mjs",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": "./.dsh-plugin/index.mjs",
|
|
17
|
+
"./client": "./.dsh-plugin/client.js",
|
|
18
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
19
|
+
"./package.json": "./package.json"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
".dsh-plugin",
|
|
23
|
+
"src",
|
|
24
|
+
"scripts",
|
|
25
|
+
"cordis.patch.yml",
|
|
26
|
+
"README.md",
|
|
27
|
+
"LICENSE"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@deepseek-ai/dsh-storage-domain": "0.1.0-rc.6",
|
|
31
|
+
"zod": "^4.4.3"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build:client": "node scripts/build-client.mjs",
|
|
35
|
+
"check": "node scripts/check.mjs",
|
|
36
|
+
"test": "node scripts/check.mjs",
|
|
37
|
+
"test:e2e": "node e2e/run.mjs",
|
|
38
|
+
"dev:watch": "node scripts/watch.mjs",
|
|
39
|
+
"accept:manual": "node scripts/acceptance.mjs",
|
|
40
|
+
"lint:agentmap": "python3 tools/agentmap_lint.py --report",
|
|
41
|
+
"lint:test-impact": "python3 tools/test_impact_lint.py --self-test && python3 tools/test_impact_lint.py --report",
|
|
42
|
+
"lint": "pnpm lint:agentmap && pnpm lint:test-impact",
|
|
43
|
+
"check:staged-client": "node scripts/check-staged-client.mjs",
|
|
44
|
+
"verify:fast": "pnpm lint && pnpm check",
|
|
45
|
+
"verify": "pnpm verify:fast && pnpm test:e2e"
|
|
46
|
+
},
|
|
47
|
+
"dsh": {
|
|
48
|
+
"bundle": {
|
|
49
|
+
"patch": "./cordis.patch.yml"
|
|
50
|
+
},
|
|
51
|
+
"client": {
|
|
52
|
+
"inject": [],
|
|
53
|
+
"platform": "web"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"keywords": [
|
|
57
|
+
"dsh",
|
|
58
|
+
"dsh-plugin",
|
|
59
|
+
"deepseek-harness",
|
|
60
|
+
"activity-overview",
|
|
61
|
+
"session-pane",
|
|
62
|
+
"multi-session"
|
|
63
|
+
],
|
|
64
|
+
"license": "MIT",
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"playwright": "^1.62.1"
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// dsh-activity-pane 人工验收清单(GUI 交互类)。
|
|
2
|
+
//
|
|
3
|
+
// 本文件被 CONVENTIONS.md 的 `测试锚点路径` 登记为测试文件:为无法在 Node
|
|
4
|
+
// 纯函数中断言的浏览器交互类验收点提供稳定锚点。每个步骤可在一个真实
|
|
5
|
+
// dsh web 会话中按序人工执行并判定。
|
|
6
|
+
//
|
|
7
|
+
// 分工(T-083 起):可机器判定的交互验收点已迁移为 `e2e/specs/` 的 Playwright
|
|
8
|
+
// spec(pre-push 门禁自动执行,同样锚定 AC-ID);本清单只保留观感/视觉、
|
|
9
|
+
// 真实设备与复杂编排类条目。迁移映射:
|
|
10
|
+
// - R-01-001/AC-03~AC-06、R-01-002/AC-06~AC-07(空态/完成/阻塞及三处徽标)、R-01-017/AC-01、R-02-002/AC-01、AC-02 → e2e/specs/auto-update.mjs
|
|
11
|
+
// - R-01-004/AC-01、AC-02 → e2e/specs/long-list.mjs
|
|
12
|
+
// - R-01-007/AC-01、AC-02、R-01-011/AC-05 → e2e/specs/desktop-layout.mjs
|
|
13
|
+
// - R-01-008/AC-01、AC-02 点击/键盘、AC-03 鼠标、AC-04 文案/相对位置、AC-05、AC-06 点击/键盘 → e2e/specs/mobile-drawer.mjs(AC-03 真触摸、AC-04 精确位置与遮挡观感残留人工)
|
|
14
|
+
// - R-01-010/AC-04 → e2e/specs/auto-update.mjs 与 e2e/specs/session-lifecycle.mjs
|
|
15
|
+
// - R-01-001/AC-01、AC-02、R-01-002/AC-03、AC-10、R-01-010/AC-01、AC-02 → e2e/specs/session-lifecycle.mjs
|
|
16
|
+
// - R-01-002/AC-05、AC-10、AC-11、AC-12 完成提醒路径 → e2e/specs/completion-sync.mjs
|
|
17
|
+
// - R-01-002/AC-05、AC-06、AC-09、AC-10、AC-12、AC-13 错误提醒路径 → e2e/specs/error-reminder.mjs
|
|
18
|
+
// T-084 起第二批:
|
|
19
|
+
// - R-01-005/AC-01 点击/Enter/Space、R-01-006/AC-01 → e2e/specs/navigation.mjs
|
|
20
|
+
// - R-01-015/AC-01、AC-02、AC-04、AC-03 桌面折叠态 → e2e/specs/resize.mjs;AC-03 移动端无手柄 → e2e/specs/mobile-drawer.mjs
|
|
21
|
+
// - R-01-018/AC-01、AC-03、AC-04、AC-05、AC-02 点击/键盘/reduced-motion → e2e/specs/back-to-top.mjs(AC-02 平滑观感残留人工)
|
|
22
|
+
// - R-01-003/AC-03、AC-08~AC-12 浏览器着色、R-01-012/AC-01、AC-05、R-01-013/AC-01、AC-03..05、AC-07、AC-08、AC-10、AC-11 → e2e/specs/card-content.mjs(AC-02 结构断言已迁,多工作区感知区分与「标题随宿主更新」半句仍人工)
|
|
23
|
+
// T-089 起:
|
|
24
|
+
// - R-01-009/AC-02、AC-03 的 tool→等待→stream 实时更新 → e2e/specs/auto-update.mjs(AC-01 进行中 tool partial 仍由 unit/人工承接)。
|
|
25
|
+
// - R-01-014/AC-01、AC-03、AC-06 的列表 pending→ready、detail 渐进补齐与计数切换 → e2e/specs/loading-ready.mjs。
|
|
26
|
+
// - R-01-011/AC-03~AC-05 的键盘折叠/展开 → e2e/specs/desktop-layout.mjs。
|
|
27
|
+
//
|
|
28
|
+
// 用法:`node scripts/acceptance.mjs` 打印清单。
|
|
29
|
+
|
|
30
|
+
const steps = [
|
|
31
|
+
// R-01-003/AC-04 母子会话连接线
|
|
32
|
+
"让一个母会话同时运行两个以上子会话,确认每个子会话卡片在缩进槽中通过连接线与母会话层级相连;增加多级子会话后连接线仍与对应层级对齐,且卡片点击/键盘激活不受影响。",
|
|
33
|
+
// R-01-003/AC-05 活动子会话继承显示母会话
|
|
34
|
+
"让母会话自身先停止或进入非活动状态、但保持其子会话运行,确认母会话卡片仍留在活动区并位于子会话之前;子会话全部结束后确认母会话上下文卡片也消失且不进入最近历史。",
|
|
35
|
+
// R-01-003/AC-06、AC-07 工作区徽标图标与字号
|
|
36
|
+
"确认活动卡与最近卡上的工作区名称文字前都有文件夹图标,且与左边栏工作区条目的图标相同(一眼可辨为工作区归属);名称字号明显可读(不低于 10.5px),深浅主题下图标颜色与文字协调;把窗格拖窄使名称超长,确认仍以省略号截断而图标完整不被挤压。",
|
|
37
|
+
// R-01-003/AC-08、AC-09、AC-10、AC-11、AC-12 工作区徽标色彩区分与协调
|
|
38
|
+
"同时保持归属不同工作区的多个会话卡片可见,确认同一工作区的所有徽标(图标、文字、底色与描边)为同一色系、不同工作区的徽标颜色分布在橙/黄绿/绿/青/蓝/紫/洋红等感官明显不同的色区,且任何徽标都不是红色警戒色;重点确认 docsim 为橙色、dsh-activity-pane 为青色、ops 为蓝紫色,三者一眼可区分;每枚徽标的前景(图标+文字)与背景为 OKLCH 同色相单色系,文字鲜明不发灰、明度对比拉开;同一可见工作区集合下刷新页面后各工作区颜色保持不变;切换深浅主题后徽标仍协调且文字可辨,胶囊尺寸与字号不变。",
|
|
39
|
+
// R-01-005/AC-01 活跃期连续点击(回归:渲染不得移动卡片 DOM 吃掉 click/焦点/悬停)
|
|
40
|
+
"在某会话持续流式运行期间,连续点击当前会话活动卡与各历史卡:每次 click 都应生效(当前卡获得并保持焦点、其它卡完成跳转),悬停高亮不丢失;运行结束后行为保持一致。",
|
|
41
|
+
// R-01-005/AC-01 移动端切换不弹软键盘(回归:抑制原生 composer 自动聚焦)
|
|
42
|
+
"在移动视口(<=767px,真机或 DevTools 设备模拟)打开抽屉并点击一张非当前会话卡片,确认切换会话后软键盘不弹出、composer 输入框未获得焦点;随后手动点按 composer 确认仍可正常聚焦输入;回到桌面宽度切换会话,确认 composer 保持原生自动聚焦、可立即输入。",
|
|
43
|
+
// R-01-005/AC-02 列表未就绪重试
|
|
44
|
+
"在会话列表尚未就绪时立刻点击其卡片(可人为制造慢列表),确认窗格以脉冲反馈等待并在列表就绪后完成跳转;持续超时则本次交互结束且不影响其它卡片点击。",
|
|
45
|
+
// R-01-008/AC-03 真触摸与误触残留(鼠标外部点击已自动化)
|
|
46
|
+
"在真机或触摸模拟下轻点抽屉外部,确认抽屉收起且该次 tap 未误触主会话控件;外壳重挂载后遮罩仍只有一个。",
|
|
47
|
+
// R-01-008/AC-04 精确位置观感残留(文案与视口内几何已自动化)
|
|
48
|
+
"确认浮动开关位于左边栏切换按钮右侧,不与右侧按钮组叠盖,也不遮挡会话内容。",
|
|
49
|
+
// R-01-008/AC-06 真机软键盘残留(当前卡键盘与其它卡点击路径已自动化)
|
|
50
|
+
"移动抽屉内点击非当前卡切换后确认软键盘不弹出,手动点 composer 仍可聚焦。",
|
|
51
|
+
// R-02-002/AC-01 视图级祖先替换后窗格自愈(重挂载恢复已自动化,此处保留长跑空闲观察)
|
|
52
|
+
"触发外壳视图级重挂载(如整体布局切换/路由跳转导致中间列的祖先被替换)后,保持应用空闲(无会话活动)数分钟,确认窗格不丢失、控制台无插件错误。",
|
|
53
|
+
// R-02-003/AC-02 长跑资源不爬升
|
|
54
|
+
"让当前会话连续执行数十次工具调用,确认窗格渲染随调用数增长不明显变慢(原生图标缓存按当前会话修剪并限量);卸载插件后确认无残留元素与监听。",
|
|
55
|
+
// R-02-002/AC-02 静默等待(槽座延迟场景残留;加载早期无报错的常规路径已由 auto-update.mjs 覆盖)
|
|
56
|
+
"人为制造窗格宿主槽座延迟出现(如 DevTools 断点/网络节流延缓外壳布局渲染),确认等待期间控制台无插件错误,槽座出现后窗格自动挂载。",
|
|
57
|
+
// R-01-009/AC-02、R-01-012/AC-02..03 原生工作项实时刷新
|
|
58
|
+
"让某会话运行并先后出现工具调用/流式回复,确认卡片只显示主会话窗口实际顺序的最近 4 个工作项(不足 4 个全部显示),包含当前项且随原生推送实时更新;不出现独立当前动作文案行。",
|
|
59
|
+
// 回归:非当前活动会话也必须由 native session.open() hydrate 并实时更新
|
|
60
|
+
"让两个以上活动会话同时运行,保持当前会话不切换,确认每张非当前活动卡的工作项时间线也会随工具调用/流式回复实时更新;切换到该会话前后不应突然补跳。",
|
|
61
|
+
// R-01-009/AC-04 工具动作摘要(镜像主会话窗口,可含原始命令)
|
|
62
|
+
"让某会话进行中触发带参数的工具调用(如 bash/read/web_search),确认工作项详情与主会话窗口工作项行一致:bash 无 description 时显示原始命令首行,read/write/edit 显示相对工作区的路径,web_search 显示 query。",
|
|
63
|
+
// R-01-012/AC-03 选中/非选中态文字不漂移
|
|
64
|
+
"让包含 todo_write/ask_user_question/cordis_define/失败工具调用/上下文注入的会话在选中与非选中态间切换,确认时间线中每个动作的标题与摘要两态完全一致(todo 显示 x/y 进度、ask 显示等待/已答/已取消、失败动作显示错误首行、上下文注入显示「上下文注入 · 来源标识」而非注入内容原文)。",
|
|
65
|
+
// R-01-009/AC-05 统计行布局与缓存命中率
|
|
66
|
+
"运行中会话流式输出时,确认进度条下方统计行左侧依次显示「tok/s 速率 · 缓存命中率 · 输入 · 输出」(与会话主窗口同序),字段间以小圆点区隔且速率无约等于符号、命中率为百分比;本回合时长固定在该行最右侧;数据缺失的字段隐藏,两段皆空时整行不残留。",
|
|
67
|
+
// R-01-009/AC-06 回合进度条(时间驱动、半衰期每帧按最新实测速率校准,C-014、C-025、C-044)
|
|
68
|
+
"确认运行卡标题后不再显示进度百分比;百分比改为与进度条同行并紧跟其右侧,在固定占位内向右对齐,其文字右缘与下一行最右侧的本回合耗时文字右缘对齐,并相对进度条中心略微上移 1px、视觉上不再偏下;9% 到 100% 的位数变化不改变其占位宽度或右缘,窄卡宽下进度条可收缩而百分比保持完整可见。观察进度条随本回合已耗时平滑爬升(不再出现 10% 级跳变):新会话起步期(无实测速率)按 20 tok/s 保守基准慢爬(约 2 分钟到 18%、6 分钟到 40%);速率实测后进度增速随统计行显示的累计平均速率动态调整——平均速率上升时增速加快(如 ≈45 tok/s 时 4 分钟到 50%),平均速率回落时进度可随之小幅回退(实时估计语义,不承诺单调);回合切换后归零重爬;中途才接入的会话进度从该回合已耗时对应值起算;回退时条纹动画不中断。",
|
|
69
|
+
// R-01-009/AC-07 工作项时间线状态与摘要(无行级耗时)
|
|
70
|
+
"运行卡上确认工作项/轨迹含图标语义、文字、详情与状态,行尾无耗时显示(与主会话窗口工作项行一致);被中断的动作为已停止而非失败,失败动作摘要为错误输出首行。",
|
|
71
|
+
// R-01-009/AC-09 时间线外观(对齐/平滑正圆/竖线/外环/运行点闪烁)
|
|
72
|
+
"运行卡的工作项时间线节点应以 5px 实心圆核呈现,明显小于上方标题前的 7px 实心圆点,并在小圆核外围保留一圈跟随其轮廓的半透明光晕,隐藏的 7px 承载盒边界不得显现;时间线节点承载盒、竖线与标题圆点的圆心应当视觉竖直对齐(不左右偏移),竖线从所有圆点正中心穿过,时间线文字轨道与下方进度条共用左边界;竖线穿过最顶部节点圆点并向上有一段引出;正在执行(最末)节点为蓝色带半透明外围与光晕并持续闪烁,已定案节点为绿色/红色实心圆核带半透明外围,竖线终点没入最末圆点内部。分别以 DPR 1、1.25、1.5、2 截取节点并放大检查:5px 实心核边缘应有连续抗锯齿过渡、近看仍为平滑正圆,不得呈八边形阶梯轮廓;小圆核、外围环与竖线视觉同心,子代理卡节点左半不被裁切。",
|
|
73
|
+
// R-01-009/AC-10 运行中尾项蓝闪持续标志
|
|
74
|
+
"会话运行期间,即使处于工具定案到下一工作项出现的间隙(无流式输出、无进行中工具),时间线最末非用户工作项的圆点仍应保持蓝色闪烁,作为 agent 正在工作的标志;会话等待用户确认/回答时不适用该提升,尾部为报错/已停止/用户输入项时保持其原状态颜色。",
|
|
75
|
+
// R-01-009/AC-11 真实当前活动身份与末行状态
|
|
76
|
+
"制造当前 running 分组之后暂存三条 done 行的运行快照,确认时间线仍为最多四行:首行为用户指令、其后两条为最近已定案工作行、最末行为原 running 分组;末行文字、动作身份与主会话当前活动一致且蓝点闪烁,不得删除 running 后把旧尾消息改蓝冒充。",
|
|
77
|
+
// R-01-009/AC-08 进度条流式条纹动画
|
|
78
|
+
"会话处于流式回复阶段时,确认其卡片进度条内部有向右持续滚动的条纹/阴影动画;进入 tool 或思考阶段后条纹停止(恢复静态渐变填充)。即使页面开启 prefers-reduced-motion,仍应保留用于表达当前活动状态的条纹与运行点脉冲(行为对齐 answer-pet)。",
|
|
79
|
+
// R-01-016/AC-01 等待卡保留最近工作项时间线
|
|
80
|
+
"让一个会话完成一轮(进入完成提醒呈现)并保持未打开,确认其等待卡标题行下方仍显示该会话最近的工作项时间线(最多 4 项,图标/文字/状态与运行卡一致,尾项保持已定案颜色而非蓝色闪烁);再制造一个待确认或待回复的会话,确认其等待卡同样显示最近工作项时间线。",
|
|
81
|
+
// R-01-003/AC-05、R-01-009/AC-06、R-01-002/AC-03 委托周期保持运行呈现
|
|
82
|
+
"让一个启动了后台子代理的母会话自身结束回合(子代理仍在运行),确认活动区中该母会话卡片外观无任何变化:保持运行卡呈现、百分比进度条按原节奏继续推进(不归零、不打满)、不切入完成提醒卡面;子代理 settle 触发母会话新回合时进度仍不归零;处理回合完成后该会话再开新回合时进度归零重计。",
|
|
83
|
+
|
|
84
|
+
// R-01-016/AC-04 非运行卡时间线加载指示
|
|
85
|
+
"开启网络节流(Slow 3G)后刷新页面,观察等待卡:时间线数据在途时其时间线区域显示加载指示,数据返回后就地填充,卡片不重建。",
|
|
86
|
+
// T-007/AC-01 badge 自适应
|
|
87
|
+
"刷新或重挂载窗格,确认 workspace badge 只包住自身文字,不伸展到模型名称;模型保持在同一行右侧。",
|
|
88
|
+
// T-007/AC-02 模型批量加载(R-01-014 起为渐进呈现:逐卡就绪逐卡出现)
|
|
89
|
+
"刷新页面后观察活动卡,确认模型名称不会先在所有卡片上消失再批量闪现,而是逐卡就绪逐卡稳定出现;API 失败时对应区域保持空白。",
|
|
90
|
+
// R-01-012/AC-16 模型选择切换后卡片模型上下文跟随更新
|
|
91
|
+
"在某会话卡片模型已显示后,于该会话主窗口切换模型选择(/model 或输入框模型座位),确认该会话的活动卡/最近卡右上角模型上下文即刻更新为切换后的模型与 reasoning level,不停留在切换前的取值;切换后立即发消息,卡片显示与新回合实际所用模型一致。",
|
|
92
|
+
// R-01-014/AC-01、AC-02、AC-03、AC-04 加载过程可见与渐进呈现
|
|
93
|
+
"开启网络节流(Slow 3G)后刷新页面,确认:窗格活动区与最近历史区先显示加载中活动指示而不是「暂无活动会话」空态;卡片先出现,模型/工作项时间线/消息预览位置显示加载指示;数据就绪的卡片先填充,不等待其余卡片;加载期间窗格滚动、桌面折叠、移动端抽屉开关与卡片点击跳转均可用。",
|
|
94
|
+
// R-01-014/AC-06 数量标识在途显示加载指示
|
|
95
|
+
"开启网络节流(Slow 3G)后刷新页面,在会话列表就绪前确认:列头 n/m 胶囊显示加载转圈而非「0/0」;桌面折叠窄条与移动端浮动开关上的计数徽标同样显示转圈;列表就绪后三处恢复显示实际 n/m,有阻塞等待时照常进入脉冲。",
|
|
96
|
+
// R-01-014/AC-05 失败降级与重试
|
|
97
|
+
"在 DevTools 中阻断 models/history 请求后刷新,确认对应字段保持空白且控制台无未捕获错误;恢复网络并让会话离开可见区再回来,确认重新拉取并成功填充。",
|
|
98
|
+
// R-01-013/AC-03、AC-04、R-01-014/AC-02 深翻页与加载指示清理
|
|
99
|
+
"找一个最近用户消息在懒加载窗口之外的历史会话(主窗口顶部需点「加载更早」才能看到),刷新页面后确认其历史卡预览行经短暂加载后显示该消息物理首行;预览确实为空的卡片加载指示最终消失(不残留 spinner)。",
|
|
100
|
+
// T-007/AC-03 冷会话时间线
|
|
101
|
+
"刷新页面后不要切换当前会话,直接观察其它运行中活动卡,确认其工作项时间线通过 native history 出现;切换当前会话不应成为加载其它卡片时间线的前置条件。",
|
|
102
|
+
// T-009/AC-01 原生工作项呈现
|
|
103
|
+
"确认活动卡时间线行按主会话网页形式显示:原生 SVG 图标 + Bash/Edit 等动作标题(agent 正文行为机器人图标与「助手」标签、思考语义为「思考」标签)+ 后续摘要 + 状态;当主会话存在对应工作项时,图标和标题应与网页内容一致;同一回合有多个同名工具时,每行仍对应自己的 call id。",
|
|
104
|
+
// T-009/AC-02 非当前会话 fallback
|
|
105
|
+
"确认当前网页没有对应动作 DOM 时,非当前会话仍显示同语义的标题/摘要和网页同款 canonical fallback 图标;即使两个会话使用相同 turn/step 或同名工具,切换当前会话后各 card 仍保持自身时间线,不被当前会话内容覆盖。",
|
|
106
|
+
// R-01-012/AC-03 选中/非选中态一致
|
|
107
|
+
"切换当前会话,确认同一活动卡在选中与非选中两态下时间线图标与动作标题一致(glob 显示 Glob 与放大镜图标、web_fetch 显示 Fetch、grep 显示 Grep、cordis 插件动作显示完整动宾文案),不出现字符画图标。",
|
|
108
|
+
// T-011/AC-01 仅缩小图标
|
|
109
|
+
"确认 grep 工作项显示为 Grep 而不是 Search;文字和行高保持原有活动卡尺度,只有原生 SVG 图标缩小并不超过文字高度。",
|
|
110
|
+
// R-01-012/AC-05 用户图标
|
|
111
|
+
// R-01-012/AC-06 错误动作图标与文字
|
|
112
|
+
// R-01-012/AC-07 标题摘要分隔符
|
|
113
|
+
// R-01-012/AC-08 Bash 图标稳定
|
|
114
|
+
"确认用户输入工作项显示小人物图标与「用户」标签;Bash 始终显示网页同款终端/API 图标,不因展开或报错变成下拉箭头;工具报错时保留原动作图标并将图标、标题、摘要染红;「用户」/「思考」/Bash 等标题与后续摘要之间显示小圆点。",
|
|
115
|
+
// R-01-012/AC-09 agent 正文行机器人图标与「助手」标签
|
|
116
|
+
// R-01-012/AC-10 思考语义中文「思考」标签、无英文残留
|
|
117
|
+
// R-01-012/AC-11 机器人图标 12px 同盒、尺度一致且边缘清晰
|
|
118
|
+
"确认 agent 正文输出行显示机器人图标与「助手」标签(机器人图标与最近卡 agent reply 行一致);机器人图标为小电视几何(双斜短天线、无双耳),显示尺度与时间线相邻的工具/人物图标一致(同为 12px 字形盒),边缘清晰不发虚、不因留白显小;时间线各处不出现英文 Assistant/Think 标签,思考语义一律为「思考」。",
|
|
119
|
+
// R-01-012/AC-01 空值回落残留(模型名与 reasoning 显示断言已迁 e2e/specs/card-content.mjs)
|
|
120
|
+
"制造一个模型目录无值的会话,确认卡片右上角模型区域保持空白,不把 agentPreset 当成模型名。",
|
|
121
|
+
// R-01-013/AC-03、AC-04 空态与加载指示残留、R-01-013/AC-06 悬停观感、R-01-012/AC-11 预览行图标盒 12px(首行内容/角色标签/层序断言已迁 e2e/specs/card-content.mjs)
|
|
122
|
+
"确认最近历史卡两条预览行图标盒与时间线图标同为 12px、整体形式与活动卡时间线的用户/助手行一致;缺失内容时仅显示图标与标签、文本为空,加载中 spinner 出现在文本段;悬停时沿用现有卡片高亮反馈。",
|
|
123
|
+
// R-01-013/AC-02 标题随宿主更新(回归:单卡渲染异常不得冻结其余卡片)
|
|
124
|
+
"重命名一个历史会话后,确认窗格历史卡标题与左边栏同步变为新标题;对之后获得自动生成标题的会话,确认卡片不停留在首条消息形态的旧标题。",
|
|
125
|
+
// R-01-010/AC-01 归档会话不入最近历史
|
|
126
|
+
"在左边栏归档一个 24h 内有活动的会话后,确认其卡片从最近历史区消失(不再出现点了回落到新会话界面的死卡);其余历史卡不受影响。",
|
|
127
|
+
// R-01-010/AC-08、R-01-010/AC-09 历史区时间口径为最后活动时间
|
|
128
|
+
"找一个发指令后 agent 运行了较久才结束的会话,待其落入最近历史区后,确认卡片「最近 xx:xx」显示的是最后回复结束时刻(而非发指令时刻),且历史区按该时刻从新到旧排序;刷新页面(冷会话)后确认时间先按宿主列表时间显示、数据到达后精化为回复结束时刻。",
|
|
129
|
+
// R-01-013/AC-09 最近卡标题常规字重
|
|
130
|
+
"确认最近历史卡的会话标题为常规字重(不加粗),与活动卡的加粗标题形成明显区分。",
|
|
131
|
+
// R-01-013/AC-10 最近卡整体淡化
|
|
132
|
+
"确认最近历史卡整体比活动卡明显更淡(不透明度更低),悬停时仍有高亮反馈。",
|
|
133
|
+
// R-01-013/AC-11 最近卡轮廓可辨
|
|
134
|
+
"确认最近历史卡比活动卡略暗(不抢视线)且轮廓仍可辨:卡片底色与窗格底色可分辨、边缘有细描边,不融入背景;深浅主题各验一次。",
|
|
135
|
+
// R-01-002/AC-04 等待三类柔和配色与色彩语义(C-043)
|
|
136
|
+
"确认阻塞等待卡为金色卡面、完成提醒卡为绿色成功色卡面、错误提醒卡为红色错误色卡面(深浅主题各验一次:深色金/暗绿/暗红底+同色描边光晕,浅色淡金/淡绿/淡红底),三类均与主题协调且与非等待状态可区分,一瞥即可凭颜色分辨「金=需要答复」「绿=做完了不必急」「红=出错了要注意」。",
|
|
137
|
+
// R-01-002/AC-01、AC-02 阻塞等待类型胶囊
|
|
138
|
+
"分别制造待确认、待审查、待回复三种阻塞等待会话,确认其卡片末行首行类型胶囊分别前置对勾、文档、问号气泡圆底小图标(与卡片其它图标同尺度),且胶囊文字分别为「待确认」「待审查」「提问中」,无需读字即可从图标区分等待类型。",
|
|
139
|
+
// R-01-002/AC-08 末行胶囊+正文脉冲载体、状态点静止(C-040、C-043)
|
|
140
|
+
"让一个会话完成一轮进入完成提醒:确认卡片末行首行为绿色对勾「已完成」胶囊、其下正文「继续对话,或移入历史」与胶囊同频同相脉冲闪烁、「移入历史」按钮不闪;再制造一个待确认/待审查/待回复会话:确认其金色胶囊与正文文字同频同相脉冲闪烁(同亮同暗不漂移);三类等待卡的标题圆点均静止不闪、描边与光晕强度一致。",
|
|
141
|
+
// R-01-002/AC-09 胶囊+正文内容(错误胶囊/正文已自动化;人工保留三类并列观感与提问回落)
|
|
142
|
+
"观察三种阻塞等待卡的末行为「胶囊+正文」:待确认正文为「等待你确认授权后继续」、待审查为「等待你审查计划后继续」;待回复仅一个可展示问题时为带 bullet 的 HTML 无序列表,多个可展示问题时为 HTML 编号列表且编号对应原始问题位置,最多显示 3 个问题、超出后追加无编号省略项;逐项确认问题正文物理首行、尾冒号剥除与 header 回落,全部不可得时回落「等待你回答问题后继续」;完成提醒卡胶囊为「已完成」、正文为「继续对话,或移入历史」;错误提醒卡胶囊为「错误」、正文为错误信息原文。",
|
|
143
|
+
// R-01-002/AC-08 原地跨类转换相位对齐(C-040 复审)
|
|
144
|
+
"让同一张等待卡原地跨类翻转——完成提醒会话收到提问转为待回复(或阻塞会话被回答后完成,或错误回合后被新回合覆盖)——确认卡片不重建、颜色随类别切换,且胶囊与正文始终同亮同暗(不同相漂移、无一眼可见的错拍)。",
|
|
145
|
+
// R-01-012/AC-05 用户消息行实线下划线标识(C-022)、指令槽位已删除
|
|
146
|
+
"确认时间线中的用户消息行不再有任何背景底色,行下显示一条 1px 中性灰实线下划线,且下划线只从人物图标延伸到文字末尾、不贯穿整行(短消息右侧空白不带线,长消息截断时随文字延伸到行尾;柔和不抢眼,深浅主题各验一次);时间线上方不再出现常驻的用户指令槽位行;助手正文行无底色、无下划线。",
|
|
147
|
+
// R-01-012/AC-12、AC-13、AC-14、AC-15 指令锚行(C-039:末尾进入、触顶停留、第二行顶替)
|
|
148
|
+
"让一个会话运行:确认本轮用户指令作为普通显示行从时间线末尾进入、随新工作行到达向上滚动,滚动到第一行后停留(人物图标、「用户」标签、文字与实线下划线和普通用户行一致),其后至多三条工作显示行继续滚动、总行数不超过 4;真实当前活动始终保留为最末行并蓝闪。回合结束后再发送一条新指令:确认时间线不清空——新指令出现在时间线末尾,旧指令仍停留第一行,其间工作行随新行到达向上滚动;待新指令滚动到第二行时确认它取代旧指令占据第一行、其后各行上移(可暂少一行,随新行到达恢复),全程不出现两条用户消息相邻并列停留;空时间线的首条指令直接占据第一行并停留。深浅主题各验一次。",
|
|
149
|
+
// R-01-017/AC-03 思考组落定(闪烁语义修正)
|
|
150
|
+
"让会话经历思考后进入正文流式输出,确认正文行圆点闪烁时其上方思考组圆点已停止闪烁;会话等待用户行动时,其等待卡时间线内所有圆点均不闪烁。",
|
|
151
|
+
// R-01-009/AC-06、AC-11、R-01-012/AC-12 冷窗口兜底:进度、锚行与真实活动均不丢失
|
|
152
|
+
"让一个会话的单个回合运行足够久(回合内消息数超过 50,使回合起点与最近用户指令超出加载窗口),刷新页面后确认:该运行卡进度不从 0% 重爬,而是按本回合真实已耗时继续;时间线第一行仍为 history 兜底的最近用户指令,最末行仍是刷新前后的真实当前活动内容并蓝闪,中间为最近已定案工作行;history 锚行补入不得让当前活动消失或把旧尾内容改蓝。",
|
|
153
|
+
// R-01-001/AC-04 数量徽标紧跟标题
|
|
154
|
+
// R-01-001/AC-04、AC-05 数量徽标 n/m 化与口径
|
|
155
|
+
"确认列头活动会话数量徽标紧跟「活动会话」标题文字显示(不再顶到列头最右侧),配色与主题协调;徽标为「等待行动数/活动主会话总数」的分数形式:只开运行中的会话时分子为 0,让部分会话完成一轮或进入阻塞等待后分子增加;子代理卡片与层级上下文卡的出现/消失不改变分母。",
|
|
156
|
+
// R-01-001/AC-06 空态 0/0
|
|
157
|
+
"关闭所有会话或清空活动区,确认空态提示出现时数量徽标仍显示为 0/0 而非隐藏或空白。",
|
|
158
|
+
// R-01-002/AC-06~AC-08 三处数量胶囊固定同步脉冲与底色跟随(C-065)
|
|
159
|
+
"在存在等待行动会话时确认列头、折叠窄条与移动端「活动」按钮三处数量胶囊不使用红色渐变,无描边与外环,底色与透明度和等待卡片完全一致且全程可见;底色跟随等待构成且按 错误 > 阻塞 > 完成 优先级——存在错误提醒会话时为红底、否则存在阻塞等待会话时为金底、等待全部为完成提醒时为绿底;任一等待行动存在即以固定节奏亮度呼吸,不随等待占比变速,并与所有等待卡末行类型胶囊及正文同亮同暗;新增、解除或切换等待类别后仍保持同步;等待全部解除后脉冲停止。深浅两个主题各验一次三色底、固定同步脉冲与无描边呈现。",
|
|
160
|
+
// R-01-011/AC-04 折叠窄条镜像
|
|
161
|
+
"折叠窗格为窄条后重复上一步的观察:窄条计数同样为 n/m 且脉冲门控、底色跟随行为一致。",
|
|
162
|
+
// R-01-002/AC-05、R-01-010/AC-06 完成确认:打开/切走不解除
|
|
163
|
+
"让一个非当前会话完成一轮(活动卡转为完成提醒呈现),点击该卡片打开会话:确认卡片保持原位置与绿色完成提醒卡面不变,不立即移入历史区;再切换到其它会话,确认卡片仍以完成提醒呈现留在活动区(打开或切换当前会话均不解除完成提醒)。",
|
|
164
|
+
// R-01-002/AC-10 移入历史按钮:显式确认才解除
|
|
165
|
+
"继续上一步:不按「移入历史」按钮,直接在窗格内向下滚动并刷新页面——重新加载后确认该会话仍以完成提醒等待卡留在活动区(刷新与重连恢复提醒);点击其卡片上的「移入历史」按钮:确认卡片立即以移动动画迁入最近历史区,且点击不触发向该会话的跳转(主会话焦点不变)。",
|
|
166
|
+
// R-01-002/AC-10 按钮仅限完成提醒卡、R-01-002/AC-11 跨端同步
|
|
167
|
+
"分别制造待确认/待审查/待回复会话与一个错误回合会话,确认其等待卡上没有「移入历史」按钮(阻塞等待的解除是真正回答问题,错误提醒随新回合覆盖);再用两个浏览器窗口(或普通窗口+隐身窗口)同时打开同一 dsh 页面:在 A 窗口点击某完成提醒卡的「移入历史」,确认 B 窗口同一会话的完成提醒同帧解除、卡片迁入历史区;B 窗口若在确认后才刷新,确认刷新后仍不显示该提醒。",
|
|
168
|
+
// R-01-002/AC-13 错误提醒(C-043):红色卡面、错误信息、覆盖解除、刷新恢复
|
|
169
|
+
"让一个会话的回合以不可恢复错误结束(如配置一个必然 429/超时重试耗尽的模型路由或直接观察真实故障):确认该会话以红色错误提醒卡留在活动区——首行为红感叹号「错误」胶囊、其下为错误信息正文、胶囊与正文同频同相闪烁、无「移入历史」按钮;刷新页面后确认红色错误提醒恢复;随后在该会话继续对话并让新回合正常完成,确认红色提醒被新回合覆盖消失;若错误发生时该会话存在运行中的子代理,确认错误提醒推迟到后代全部结束后才显示;深浅主题各验一次红色卡面。",
|
|
170
|
+
// R-01-010/AC-06 当前焦点下结束一轮
|
|
171
|
+
"让当前会话直接完成一轮且不切换焦点:确认其卡片转为绿色完成提醒卡面并留在活动区,不移入最近历史区。",
|
|
172
|
+
// R-01-010/AC-06 保持解除、R-01-010/AC-07 迁移动画(双向)
|
|
173
|
+
"接上前一步,切换到另一个会话(窗格卡片或侧栏均可):确认原保持卡片以可见的移动动画从活动区精准飞入最近历史区对应卡片的新位置(位置与尺寸落到目标卡片后才淡出,非虚框、非中途消失),能感知卡片来源与去向;再确认其它活动区→历史区迁移(如当前会话运行结束落入历史区)同样播放移动动画;随后让一个历史区会话重新活动(向其发送指令重新运行):确认其卡片以移动动画从历史区精准飞入活动区对应卡片的新位置并同步淡入;在系统开启 prefers-reduced-motion 后重复一次迁移,确认两个方向均直接落位、无动画。",
|
|
174
|
+
// R-01-010/AC-10 受影响卡片平滑过渡
|
|
175
|
+
"重复上述两个方向的迁移并观察迁移卡以外的卡片:确认所有位置受影响的卡片(活动区剩余卡片、历史区其余卡片)与「最近历史」段头均以平滑滑动过渡到新位置,无任何卡片或段头瞬间跳变。",
|
|
176
|
+
// R-01-010/AC-05 分隔线留白
|
|
177
|
+
"确认活动区与最近历史区之间的分隔线上下各有约 10px 留白,两区呼吸感明确。",
|
|
178
|
+
// R-01-004/AC-03 滚动条仅滚动时显示
|
|
179
|
+
"在窗格内滚动时确认滚动条出现,停止滚动约 0.6s 后自动隐藏;不滚动时滚动条不可见,主会话区域滚动行为不受影响。",
|
|
180
|
+
// R-01-018/AC-02、AC-04、AC-05 观感残留(键盘/显隐/回顶/窄条/抽屉行为断言已迁 e2e/specs/back-to-top.mjs)
|
|
181
|
+
"确认平滑滚动动画流畅自然;切到 <=767px 移动视口打开抽屉,确认抽屉内按钮不遮挡标题行;深浅主题各验一次不透明底色与图标可辨。",
|
|
182
|
+
// R-01-011/AC-03 标题行整体收起
|
|
183
|
+
"桌面宽度下点击「活动会话」标题行(含标题与计数徽标的整行,行尾有 « 提示、无独立按钮),确认窗格折叠为不占用主会话宽度的窄条;键盘聚焦标题行按 Enter/Space 亦可收起。",
|
|
184
|
+
// R-01-011/AC-04 窄条竖排标题整体展开
|
|
185
|
+
"折叠态下确认窄条以竖排显示「活动会话」标题与活动计数徽标,悬停窄条出现与展开态标题行对等的高亮反馈,点击窄条任意位置展开并恢复活动区+历史区显示。",
|
|
186
|
+
// R-01-011/AC-06 移动端标题行即收起抽屉
|
|
187
|
+
"切到 <=767px 移动视口打开抽屉,确认点击抽屉内标题行与桌面为同一交互路径:抽屉收起、不出现折叠窄条;桌面宽度下点击标题行仍折叠为窄条,行为不回归。",
|
|
188
|
+
// R-01-017/AC-01 折叠时间线呈现质量(「无 dsh-auto-collapse 不降级」已由 e2e/specs/auto-update.mjs 自动化覆盖)
|
|
189
|
+
"运行一个多工具+思考回合的会话:确认活动卡时间线呈折叠分组(如「正在运行/运行了命令/编辑了文件/已思考」,组摘要为推理文本内容)。",
|
|
190
|
+
// R-02-004/AC-01 订阅断开
|
|
191
|
+
"在 DevTools 中观察:会话停止运行后,其轮内状态订阅被断开(无残留监听/继续更新);卸载插件后订阅归零、控制台干净。",
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const line = "=".repeat(64);
|
|
195
|
+
console.log(line);
|
|
196
|
+
console.log("dsh-activity-pane 人工验收清单(GUI)");
|
|
197
|
+
console.log("前置:已完成 `pnpm build:client && pnpm check`,并在本地 dsh web profile 挂载本插件。");
|
|
198
|
+
console.log(line);
|
|
199
|
+
steps.forEach((text, index) => {
|
|
200
|
+
console.log(`${index + 1}. ${text}`);
|
|
201
|
+
});
|
|
202
|
+
console.log(line);
|
|
203
|
+
console.log("判定:每个步骤可观察行为符合描述即通过;任一不通过请记录后反馈东家。");
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
// T-017 渲染归一化性能基线:合成快照驱动核心纯函数,对比改动前后单次渲染成本。
|
|
2
|
+
// 用法: node scripts/bench.mjs [iterations]
|
|
3
|
+
// 两种工况:steady(快照引用不变,memo 命中,对应 1s 时钟 tick);
|
|
4
|
+
// push(每次渲染替换一个运行会话快照,memo 未命中,对应流式推送)。
|
|
5
|
+
import {
|
|
6
|
+
buildEntries,
|
|
7
|
+
buildRecent,
|
|
8
|
+
cardSignature,
|
|
9
|
+
foldedConversationTimeline,
|
|
10
|
+
messagePreviews,
|
|
11
|
+
} from "../src/core.mjs";
|
|
12
|
+
|
|
13
|
+
const iterations = Number(process.argv[2]) || 200;
|
|
14
|
+
|
|
15
|
+
function makeChatNodes(count) {
|
|
16
|
+
const order = [];
|
|
17
|
+
const nodes = new Map();
|
|
18
|
+
for (let i = 0; i < count; i += 1) {
|
|
19
|
+
const key = `node:${i}`;
|
|
20
|
+
order.push(key);
|
|
21
|
+
const kind = i % 5 === 0 ? "user" : i % 5 === 1 ? "assistant-step" : i % 5 === 2 ? "tool-call" : i % 5 === 3 ? "assistant-step" : "context";
|
|
22
|
+
if (kind === "user") {
|
|
23
|
+
nodes.set(key, { key, kind: "user", visibility: "visible", data: { content: [{ type: "text", text: `用户消息 ${i}\n第二行` }] } });
|
|
24
|
+
} else if (kind === "assistant-step") {
|
|
25
|
+
nodes.set(key, { key, kind: "assistant-step", visibility: "visible", data: { turn: i, step: 0, status: "done", blocks: [{ kind: "text", text: `回复 ${i}\n详情` }] } });
|
|
26
|
+
} else if (kind === "tool-call") {
|
|
27
|
+
nodes.set(key, { key, kind: "tool-call", visibility: "visible", data: { root: { kind: "tool-result", callId: `call:${i}`, call: { name: "read", argsRaw: JSON.stringify({ file_path: `/tmp/f${i}` }) }, time: i * 10 + 5, callTime: i * 10, isError: false } } });
|
|
28
|
+
} else {
|
|
29
|
+
nodes.set(key, { key, kind: "context", visibility: "visible", data: { content: [{ type: "text", text: `上下文 ${i}` }] } });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return { order, nodes };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeSessionSnapshot(chatSize) {
|
|
36
|
+
const chat = makeChatNodes(chatSize);
|
|
37
|
+
return {
|
|
38
|
+
chat,
|
|
39
|
+
partial: null,
|
|
40
|
+
runningCalls: [],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const CHAT_SIZE = 2000;
|
|
45
|
+
const RUNNING = 3;
|
|
46
|
+
const RECENT = 20;
|
|
47
|
+
|
|
48
|
+
const byId = {};
|
|
49
|
+
const ids = [];
|
|
50
|
+
for (let i = 0; i < RUNNING; i += 1) {
|
|
51
|
+
byId[`run:${i}`] = { id: `run:${i}`, displayTitle: `运行${i}`, running: true, updatedAt: Date.now() };
|
|
52
|
+
ids.push(`run:${i}`);
|
|
53
|
+
}
|
|
54
|
+
for (let i = 0; i < RECENT; i += 1) {
|
|
55
|
+
byId[`cold:${i}`] = { id: `cold:${i}`, displayTitle: `历史${i}`, running: false, blank: false, updatedAt: Date.now() - (i + 1) * 3_600_000 };
|
|
56
|
+
ids.push(`cold:${i}`);
|
|
57
|
+
}
|
|
58
|
+
const listSnapshot = { ids, byId, current: "run:0" };
|
|
59
|
+
|
|
60
|
+
const detailsById = new Map();
|
|
61
|
+
for (let i = 0; i < RUNNING; i += 1) detailsById.set(`run:${i}`, { snapshot: makeSessionSnapshot(CHAT_SIZE) });
|
|
62
|
+
for (let i = 0; i < RECENT; i += 1) detailsById.set(`cold:${i}`, { snapshot: makeSessionSnapshot(CHAT_SIZE) });
|
|
63
|
+
|
|
64
|
+
// 与客户端 render() 同构的归一化流程(含快照引用 memo)。
|
|
65
|
+
function renderPass() {
|
|
66
|
+
const active = buildEntries(listSnapshot, [], detailsById);
|
|
67
|
+
for (const entry of active) {
|
|
68
|
+
const detail = detailsById.get(entry.id);
|
|
69
|
+
const snap = detail?.snapshot ?? null;
|
|
70
|
+
if (detail && snap) {
|
|
71
|
+
if (detail.memoTimelineOf !== snap) {
|
|
72
|
+
detail.memoTimelineOf = snap;
|
|
73
|
+
detail.memoTimeline = foldedConversationTimeline(snap);
|
|
74
|
+
}
|
|
75
|
+
entry.timeline = detail.memoTimeline;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const recent = buildRecent(listSnapshot, [], Date.now(), undefined, detailsById);
|
|
79
|
+
for (const entry of recent) {
|
|
80
|
+
const detail = detailsById.get(entry.id);
|
|
81
|
+
if (!detail) continue;
|
|
82
|
+
const key = detail.snapshot ?? detail.history ?? null;
|
|
83
|
+
if (detail.memoPreviewsOf !== key || !detail.memoPreviews) {
|
|
84
|
+
detail.memoPreviewsOf = key;
|
|
85
|
+
detail.memoPreviews = messagePreviews({ snapshot: detail.snapshot, history: detail.history });
|
|
86
|
+
}
|
|
87
|
+
entry.userPreview = detail.memoPreviews.userPreview;
|
|
88
|
+
entry.agentPreview = detail.memoPreviews.agentPreview;
|
|
89
|
+
}
|
|
90
|
+
return cardSignature([...active, ...recent]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ---- 改动前成本模型(before 证据下限)----
|
|
94
|
+
// 复刻删除前的调用模式与全序扫描:旧 buildEntries/buildRecent 对每个可见条目做
|
|
95
|
+
// conversationTimeline(逐项镜像,已随 T-049 移除)+ messagePreviews 全序推导(≈条目×2 遍全扫),旧渲染循环再对
|
|
96
|
+
// 活动条目重复一遍(≈活动条目×2 遍全扫);item 构建从简,只保证 O(order 全长) 同构。
|
|
97
|
+
function legacyScanAll(snapshot) {
|
|
98
|
+
const chat = snapshot?.chat;
|
|
99
|
+
const order = Array.isArray(chat?.order) ? chat.order : [];
|
|
100
|
+
const nodes = chat?.nodes;
|
|
101
|
+
const items = [];
|
|
102
|
+
for (const key of order) {
|
|
103
|
+
const node = nodes?.get?.(key) ?? nodes?.[key];
|
|
104
|
+
if (node) items.push({ id: String(node.key ?? key), kind: node.kind ?? "other" });
|
|
105
|
+
}
|
|
106
|
+
return items;
|
|
107
|
+
}
|
|
108
|
+
function legacyRenderPass() {
|
|
109
|
+
const active = buildEntries(listSnapshot, [], detailsById);
|
|
110
|
+
for (const entry of active) {
|
|
111
|
+
const detail = detailsById.get(entry.id);
|
|
112
|
+
if (!detail?.snapshot) continue;
|
|
113
|
+
entry.timeline = legacyScanAll(detail.snapshot).slice(-4); // 旧 buildEntries 推导
|
|
114
|
+
legacyScanAll(detail.snapshot); // 旧 buildEntries previews(MAX 全量)
|
|
115
|
+
legacyScanAll(detail.snapshot); // 旧渲染循环 timeline 重算
|
|
116
|
+
legacyScanAll(detail.snapshot); // 旧渲染循环 previews 重算
|
|
117
|
+
}
|
|
118
|
+
const recent = buildRecent(listSnapshot, [], Date.now(), undefined, detailsById);
|
|
119
|
+
for (const entry of recent) {
|
|
120
|
+
const detail = detailsById.get(entry.id);
|
|
121
|
+
if (!detail?.snapshot) continue;
|
|
122
|
+
legacyScanAll(detail.snapshot); // 旧 buildRecent previews(MAX 全量)
|
|
123
|
+
entry.userPreview = "";
|
|
124
|
+
entry.agentPreview = "";
|
|
125
|
+
}
|
|
126
|
+
return cardSignature([...active, ...recent]);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function measure(label, mutate, pass = renderPass) {
|
|
130
|
+
for (let i = 0; i < 10; i += 1) {
|
|
131
|
+
mutate?.(i);
|
|
132
|
+
pass();
|
|
133
|
+
}
|
|
134
|
+
const start = performance.now();
|
|
135
|
+
for (let i = 0; i < iterations; i += 1) {
|
|
136
|
+
mutate?.(i);
|
|
137
|
+
pass();
|
|
138
|
+
}
|
|
139
|
+
const elapsed = performance.now() - start;
|
|
140
|
+
console.log(`${label}: ${(elapsed / iterations).toFixed(3)} ms/pass (${RUNNING} running + ${RECENT} recent, chat=${CHAT_SIZE} nodes each)`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// steady:快照引用不变(1s 时钟 tick 工况)
|
|
144
|
+
measure("steady x" + iterations, null);
|
|
145
|
+
// push:每遍轮换 run:0 的预建快照(流式推送工况,单会话 memo 未命中;不含快照构建成本)
|
|
146
|
+
const pushSnapshots = [makeSessionSnapshot(CHAT_SIZE), makeSessionSnapshot(CHAT_SIZE)];
|
|
147
|
+
measure("push x" + iterations, (i) => {
|
|
148
|
+
detailsById.get("run:0").snapshot = pushSnapshots[i % 2];
|
|
149
|
+
});
|
|
150
|
+
// legacy:改动前成本模型(before 下限证据)
|
|
151
|
+
measure("legacy x" + iterations, null, legacyRenderPass);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { readFile, rename, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
|
|
5
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)))
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 生成 .dsh-plugin/client.js。采用"写临时文件 + 原子 rename",保证任何并发写入
|
|
9
|
+
* 或 HTTP 读取都只看到完整文件(杜绝就地截断写入的撕裂竞态);profile 侧经
|
|
10
|
+
* `link:` 符号链接指向本仓库,rename 后立即可见,HMR 的 stat-poll 照常触发。
|
|
11
|
+
*/
|
|
12
|
+
export async function build() {
|
|
13
|
+
const core = (await readFile(join(root, 'src/core.mjs'), 'utf8')).replace(
|
|
14
|
+
/^export\s+/gm,
|
|
15
|
+
'',
|
|
16
|
+
)
|
|
17
|
+
const navigation = (await readFile(join(root, 'src/navigation.mjs'), 'utf8')).replace(
|
|
18
|
+
/^export\s+/gm,
|
|
19
|
+
'',
|
|
20
|
+
)
|
|
21
|
+
const client = await readFile(join(root, 'src/client.mjs'), 'utf8')
|
|
22
|
+
const bundle = `/* Generated by scripts/build-client.mjs; edit src/ instead. */
|
|
23
|
+
window.__ModuleLoader__.load({
|
|
24
|
+
id: "dsh-activity-pane",
|
|
25
|
+
factory: (require) => {
|
|
26
|
+
var module = { exports: {} };
|
|
27
|
+
var exports = module.exports;
|
|
28
|
+
${core}
|
|
29
|
+
${navigation}
|
|
30
|
+
${client}
|
|
31
|
+
return module.exports;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
`
|
|
35
|
+
const tmp = join(root, '.dsh-plugin/client.js.tmp')
|
|
36
|
+
await writeFile(tmp, bundle)
|
|
37
|
+
await rename(tmp, join(root, '.dsh-plugin/client.js'))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// CLI 入口:node scripts/build-client.mjs
|
|
41
|
+
if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) {
|
|
42
|
+
build().catch((error) => {
|
|
43
|
+
console.error(error)
|
|
44
|
+
process.exit(1)
|
|
45
|
+
})
|
|
46
|
+
}
|