minimal-agent 0.18.6 → 0.18.8

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.
Files changed (84) hide show
  1. package/package.json +1 -1
  2. package/plugins/workflow-runner/src/runtime/context.js +5 -17
  3. package/plugins/workflow-runner/src/runtime/context.ts +4 -15
  4. package/plugins/workflow-runner/src/runtime/model.js +4 -2
  5. package/plugins/workflow-runner/src/runtime/model.ts +3 -2
  6. package/plugins/workflow-runner/test/api.test.ts +4 -2
  7. package/plugins/workflow-runner/test/embeddedExecution.test.ts +5 -6
  8. package/plugins/workflow-runner/test/modelOutcome.test.ts +36 -0
  9. package/skills/dynamic-ui/SKILL.md +38 -278
  10. package/skills/dynamic-ui/mermaid.md +32 -0
  11. package/skills/dynamic-ui/scenes/architecture-and-flow.md +4 -1
  12. package/skills/dynamic-ui/scenes/data-visualization.md +5 -2
  13. package/skills/dynamic-ui/svg-design.md +251 -0
  14. package/skills/dynamic-ui/templates/README.md +2 -1
  15. package/skills/dynamic-ui/tokens/visual-tokens.md +4 -5
  16. package/src/advisor/README.md +24 -0
  17. package/src/advisor/advisorCoordinator.js +101 -45
  18. package/src/advisor/advisorCoordinator.ts +90 -51
  19. package/src/advisor/deliveryReview.js +35 -0
  20. package/src/advisor/deliveryReview.ts +30 -0
  21. package/src/advisor/presentation.js +10 -0
  22. package/src/advisor/presentation.ts +12 -0
  23. package/src/advisor/reviewContext.js +60 -0
  24. package/src/advisor/reviewContext.ts +72 -0
  25. package/src/context/persistContext.js +5 -1
  26. package/src/context/persistContext.ts +5 -2
  27. package/src/context/tokenCounter.js +3 -0
  28. package/src/context/tokenCounter.ts +2 -0
  29. package/src/llm/messageProvenance.js +1 -1
  30. package/src/llm/messageProvenance.ts +1 -1
  31. package/src/loop.js +226 -31
  32. package/src/loop.ts +205 -31
  33. package/src/memory/fileStore.js +2 -34
  34. package/src/memory/fileStore.ts +2 -26
  35. package/src/prompts/policyRegistry.js +13 -9
  36. package/src/prompts/policyRegistry.ts +15 -9
  37. package/src/prompts/promptAssembly.js +16 -23
  38. package/src/prompts/promptAssembly.ts +22 -27
  39. package/src/prompts/visualPresentation.js +28 -0
  40. package/src/prompts/visualPresentation.ts +26 -0
  41. package/src/runtime/agentRuntimeContext.js +3 -0
  42. package/src/runtime/agentRuntimeContext.ts +4 -0
  43. package/src/runtime/answerPreparation.js +9 -0
  44. package/src/runtime/answerPreparation.ts +11 -0
  45. package/src/runtime/eventSemantics.js +2 -0
  46. package/src/runtime/eventSemantics.ts +2 -0
  47. package/src/runtime/fileLock.js +75 -0
  48. package/src/runtime/fileLock.ts +73 -0
  49. package/src/runtime/finalAnswer.js +9 -0
  50. package/src/runtime/finalAnswer.ts +13 -0
  51. package/src/runtime/lifecycleTrace.js +16 -0
  52. package/src/runtime/lifecycleTrace.ts +22 -0
  53. package/src/runtime/outputContract.js +17 -0
  54. package/src/runtime/outputContract.ts +34 -0
  55. package/src/session/runAgentTurn.js +7 -2
  56. package/src/session/runAgentTurn.ts +7 -2
  57. package/src/session/runTurn.js +15 -1
  58. package/src/session/runTurn.ts +13 -1
  59. package/src/session/runtimeStore.js +11 -28
  60. package/src/session/runtimeStore.ts +9 -23
  61. package/src/tools/advisor/advisor.js +56 -40
  62. package/src/tools/advisor/advisor.ts +57 -39
  63. package/src/tools/advisor/advisorDecision.js +6 -3
  64. package/src/tools/advisor/advisorDecision.ts +6 -3
  65. package/src/tools/agent/agent.js +14 -6
  66. package/src/tools/agent/agent.ts +14 -5
  67. package/src/tools/bash/semantics.js +91 -26
  68. package/src/tools/bash/semantics.ts +63 -33
  69. package/src/tools/index.js +4 -1
  70. package/src/tools/index.ts +4 -1
  71. package/src/tools/skill/searchSkills.js +2 -2
  72. package/src/tools/skill/searchSkills.ts +2 -2
  73. package/src/tools/skill/skill.js +4 -0
  74. package/src/tools/skill/skill.ts +3 -0
  75. package/src/types.ts +19 -6
  76. package/src/ui/LiveArea.js +2 -1
  77. package/src/ui/LiveArea.tsx +2 -1
  78. package/src/ui/MessageList.js +18 -5
  79. package/src/ui/MessageList.tsx +25 -3
  80. package/src/ui/draftNotice.js +1 -8
  81. package/src/ui/draftNotice.ts +1 -10
  82. package/src/ui/hooks/useChat.js +25 -1
  83. package/src/ui/hooks/useChat.ts +22 -1
  84. package/src/web/protocol.ts +3 -1
@@ -1,5 +1,8 @@
1
1
  # 数据可视化场景
2
2
 
3
+ 先判断表格是否已足够。选择 SVG 数据图时读取 `../svg-design.md` 并执行以下流程;
4
+ 已确认宿主支持且能清楚表达数据的 Mermaid 图可走 `../mermaid.md`,不套用 SVG 模板规则。
5
+
3
6
  共用契约:`SKILL.md`、本文件、`templates/manifest.json`、选中的 `templates/<id>/template.md`、
4
7
  `tokens/visual-tokens.md`。
5
8
 
@@ -37,7 +40,7 @@
37
40
  原因、离群点清单、阶段总量、优先级依据。
38
41
  3. 在 `templates/manifest.json` 里查 `scene` 为 `data-visualization` 且 `status` 为
39
42
  `ready` 的已实现素材(**不是** `needs-static-rewrite`——那些的 `widget-code.html` 还是
40
- 旧版 Chart.js/hover 实现,两个宿主都跑不了脚本,暂不可用,见 `SKILL.md` 的 File Map 一节)。
43
+ 旧版 Chart.js/hover 实现,两个宿主都跑不了脚本,暂不可用,见 `../svg-design.md` 的 File Map 一节)。
41
44
  4. 从下面的选型矩阵里挑最多三个候选。
42
45
  5. 只有具体理由才能否掉一个候选:avoid 规则命中、数据形状超出边界、素材未 `ready`、或者
43
46
  Markdown 明显更清楚。
@@ -73,7 +76,7 @@
73
76
 
74
77
  ## 静态 SVG 生成原则(替代原来的 Chart.js 方案)
75
78
 
76
- 两个宿主都不能执行脚本,因此所有图表**只能是手绘静态 SVG**,不能用 `<canvas>` + Chart.js:
79
+ 此 SVG 分支交付静态图,可手工编写或由工具生成;不能依赖 `<canvas>` + Chart.js 在聊天或文档内执行:
77
80
 
78
81
  1. **尺寸**:用 `viewBox="0 0 720 H"` + `width="100%"` + `height="auto"`,`H` 按内容实际
79
82
  高度算,不要用固定像素高度撑一个空画布。
@@ -0,0 +1,251 @@
1
+ # SVG 设计细则
2
+
3
+ 仅在入口已选择 SVG 时读取。Mermaid 使用 mermaid.md,不套用这里的坐标规划和模板流程。路径均相对于本技能目录。
4
+
5
+ ## 与 ui-design 的分工——别重复造轮子
6
+
7
+ [ui-design](../ui-design/SKILL.md) 是这个项目里"样式与视觉设计"决策的统一入口:任何涉及整份
8
+ 文档/整套 deck 的配色、VI、字体字阶、版式布局,都应该先去那里定下来。本技能的职责窄得多:
9
+ **这段内容值不值得画一张图、该画哪种图、怎么保证画出来的东西不重叠不脏**——是排版执行细节,
10
+ 不是设计系统决策。
11
+
12
+ 具体到颜色:下面「视觉 Token」表里的十六进制都是**兜底色**,不是权威色板。写文档、报告、幻灯片
13
+ 时,如果 ui-design 已经为这份产出物定出了品牌色,直接把兜底色整体换成那份色值;只有在 Desktop
14
+ 聊天场景(没有独立文档、没有 ui-design 介入的必要)才直接用表里给的兜底色。
15
+
16
+ ## 输出契约
17
+
18
+ **两个宿主的根节点要求不一样,别弄混**:
19
+
20
+ - **Desktop 内联围栏**(```svg 围栏块):内容可以是一段纯 `<svg>...</svg>`,也可以是一段
21
+ 静态的 HTML 卡片片段(比如 `comparison-cards` 那种带分隔线的卡片、徽标)——渲染层
22
+ (`SvgWidget.tsx`)只要求内容"看起来是标记"(以 `<` 开头),不强制根标签必须是
23
+ `<svg>`。两种情况都会被塞进一个独立的 Shadow DOM 宿主,类名不会跟同一会话里的其它围栏块
24
+ 冲突。
25
+ - **写进文档**(docx/pptx/独立 html 报告,当图片资源嵌入):**必须是单一根节点的
26
+ `<svg>...</svg>`**——Word/PowerPoint 的图片嵌入管线需要一段真正的矢量图形,塞一段
27
+ `<div>` 卡片布局进去没有任何渲染器认得。如果一份文档里想要"卡片对比"这种排版,直接用
28
+ 文档格式自己的表格/形状/文本框(参考 [ui-design](../ui-design/SKILL.md)/
29
+ [pptx](../pptx/SKILL.md)/[docx](../docx/SKILL.md)),不要试图把 HTML 卡片硬塞成"SVG"。
30
+ 素材库里 `tree-flow`/`sequence-diagram` 这类图表类模板内部本来就是一个独立的
31
+ `<svg>...</svg>` 子树(外层 `<div class="widget">` 只是 Desktop 卡片外壳),写文档时把
32
+ 这段内部 `<svg>` 单独抽出来即可;`comparison-cards`/`architecture-elements` 这类以 HTML
33
+ 卡片为主的模板目前只适合 Desktop 内联场景,不要抽取硬塞进文档。
34
+
35
+ 不管落到哪种根节点,都不要 `<!DOCTYPE>`、`<html>`、`<head>`、`<body>`、注释、说明性文字混进
36
+ 这段标记内部。
37
+
38
+ **没有任何一个宿主能执行 `<script>`**:Desktop 的 CSP 是 `script-src 'self'`,内联脚本和
39
+ `onXxx` 事件属性本来就跑不起来;静态文档更不必说。因此:
40
+ - 不产出 `<script>` 标签、不产出 `onclick`/`onload` 这类事件属性。
41
+ - 不依赖 hover/click 才能看到的信息——数值、标签、图例这些关键信息必须直接画在图上常驻可见,
42
+ 不能只在假想的交互态里出现。原技能里"局部交互/tooltip/toggle"的设想在这两个宿主下都不成立,
43
+ 见 `scenes/micro-interaction.md` 里的替代做法。
44
+
45
+ ## 视觉 Token
46
+
47
+ Desktop 内联 SVG 使用 `var(--dui-<角色>, <兜底值>)` 跟随主题。导出独立 SVG、嵌入文档或交给图片转换器时,将变量解析为实际色值与字体;不能假定转换器支持 CSS 变量。按目标阅读器验证,必要时提供 PNG 回退。
48
+
49
+ | 角色 | 写法 | 兜底值(= App 深色主题) | 用途 |
50
+ |---|---|---|---|
51
+ | 正文文字 | `var(--dui-text, #f4f1e7)` | #f4f1e7 | 图上主要文字 |
52
+ | 次要文字 | `var(--dui-text-muted, #9fb0ae)` | #9fb0ae | 元数据、图例、说明、坐标轴 |
53
+ | 弱化文字 | `var(--dui-text-faint, #6f8280)` | #6f8280 | 占位、禁用态、次要连线 |
54
+ | 边框 | `var(--dui-border, rgba(238,241,235,.14))` | — | 卡片/面板描边 |
55
+ | 表面 | `var(--dui-surface, #172321)` | — | 卡片/面板填充 |
56
+ | 次表面 | `var(--dui-surface-muted, #1e2c29)` | — | 嵌套区域 |
57
+ | 品牌色 | `var(--dui-brand, #c85f3d)` | — | 主焦点、主路径、推荐项 |
58
+ | 强调色 | `var(--dui-accent, #2bb8c4)` | — | 次焦点、第二类目 |
59
+ | 成功 | `var(--dui-success, #2bb8c4)` | — | 仅状态/风险语义时用 |
60
+ | 警示 | `var(--dui-warning, #d9b354)` | — | 同上 |
61
+ | 危险 | `var(--dui-danger, #d9645a)` | — | 同上 |
62
+ | 系列 1 | `var(--dui-chart-series-1, #c85f3d)` | = 品牌色 | 同类并列数据源第 1 项 |
63
+ | 系列 2 | `var(--dui-chart-series-2, #2bb8c4)` | | 第 2 项 |
64
+ | 系列 3 | `var(--dui-chart-series-3, #d9b354)` | | 第 3 项 |
65
+ | 系列 4 | `var(--dui-chart-series-4, #6fa87c)` | | 第 4 项 |
66
+ | 系列其它 | `var(--dui-chart-other, #6f8280)` | | Top N + Other 的 Other |
67
+
68
+ **品牌基线**:不默认紫色——那是原技能抄来的,不是这个项目的品牌。Desktop 环境下品牌色自动是
69
+ App 自己的铁锈橙(`--accent`);写文档时如果 ui-design 给了别的品牌色,把上表兜底值整体替换,
70
+ 不要还留着铁锈橙或紫色。
71
+
72
+ 字体:`font-family: var(--font, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif)`
73
+ (正文),`var(--mono, "Cascadia Code", "JetBrains Mono", Consolas, monospace)`(数值、代码风格
74
+ 标识)。
75
+
76
+ 间距/圆角/字号是纯字面量,不必变量化跨主题(结构性数值本来就不该跟着主题换):
77
+ - 间距:4 / 8 / 12 / 16 / 20 / 24px,用于 gap、padding、图例行距。
78
+ - 圆角:8px 默认;12px 卡片;999px 胶囊/圆点。
79
+ - 字号:12/18(说明文字,最小不低于 11px)、14/20(正文基线)、16/24(标题,不能再大)。
80
+
81
+ ## Scope
82
+
83
+ 用于:数值趋势、量级对比、构成/占比、分布、流程强度;模块关系、依赖图、流程图、状态转移、
84
+ 调用链、时间线/排期;方案对比、技术选型、风险摘要、决策矩阵;原理讲解、因果链;需要展示"切换前
85
+ 后/开关两态"这类状态对比但做不到真交互的场景(见 `scenes/micro-interaction.md`)。
86
+
87
+ 不用于:独立可运行的网站/App/交互原型(用 [ui-design](../ui-design/SKILL.md) 或
88
+ [web-artifacts-builder](../web-artifacts-builder/SKILL.md));一次性事实、一步命令、Markdown
89
+ 表格已经足够清楚的内容;没有明确焦点的装饰性配图;CLI/TUI 直接显示的纯文本聊天场景。
90
+
91
+ ## File Map
92
+
93
+ `{{SKILL_DIR}}` 是本 `SKILL.md` 所在目录。
94
+
95
+ ```
96
+ {{SKILL_DIR}}/
97
+ ├── SKILL.md ← 唯一入口
98
+ ├── scenes/ ← 场景指南(每个场景一个文件)
99
+ │ ├── data-visualization.md
100
+ │ ├── architecture-and-flow.md
101
+ │ ├── comparison-and-decision.md
102
+ │ ├── mechanism-explanation.md
103
+ │ └── micro-interaction.md
104
+ ├── tokens/
105
+ │ └── visual-tokens.md ← 完整 token 定义(按需读取)
106
+ └── templates/ ← 素材库(可复用原型示例)
107
+ ├── manifest.json
108
+ └── <template-id>/
109
+ ├── template.md ← 素材说明(20-30 行)
110
+ ├── widget-code.html ← 完整可用示例(纯 <svg>,无 <script>)
111
+ └── fixture.json ← 示例数据
112
+ ```
113
+
114
+ `templates/manifest.json` 里 `status` 字段如实标注:`ready` 表示已按本文档的静态 SVG 契约验证
115
+ 可用;`needs-static-rewrite` 表示 `widget-code.html` 还是旧版 Chart.js/`<script>` 悬浮提示交互
116
+ 实现,两个宿主都执行不了脚本,**暂不可用**——按 `template.md` 记录的设计意图手工画静态 SVG,
117
+ 或改用下面的回退基元,不要因为目录存在就当它能直接抄。
118
+
119
+ ## Visual Design 核心原则
120
+
121
+ - **无缝** —— 用户不该看出宿主界面在哪结束、你画的图从哪开始。
122
+ - **扁平** —— 不用渐变、噪点纹理、装饰性阴影,干净的平面。
123
+ - **紧凑** —— 内联展示最关键的信息,其余留给正文文字。
124
+ - **分离** —— 解释性文字放正文,图上只放视觉证据。
125
+ - **图表就是图表** —— 图表类 widget 只画图表本身、可选的简短标题、以及坐标轴/图例/标签这些必要
126
+ 的读图辅助;不要在图表 widget 里塞洞见、结论、建议或页脚文案。
127
+
128
+ 素材源:先读 `templates/manifest.json`,再读选中的 `templates/<id>/template.md`,再决定是否
129
+ 借用 `widget-code.html`/`fixture.json`。素材是参考范例,不是死板的固定布局。
130
+
131
+ 兜底规则:没有现成素材匹配时,自定义输出也必须选定一种紧凑原型(`chart-card`、
132
+ `metric-strip-chart`、`node-flow`、`decision-cards`、`compact-table-visual`、
133
+ `explanation-panel` 之一),并遵守与素材相同的 token、表面、密度、颜色优先级、字体、间距、
134
+ 圆角、焦点规则。自定义输出不能为了凑内容而发明新色板、新字号阶梯、新间距刻度、新圆角值。
135
+ 对于 SVG 流程图、架构图、框架图、关系图,只有在先做过一次简单的坐标规划、确认节点/标签/
136
+ 连线轨道/图例/徽标都能不重叠地放下之后,才允许走这条兜底路线。
137
+
138
+ **焦点色**:颜色契约的优先级高于语义色猜测。先用中性表面/连线加 `--dui-brand`/
139
+ `--dui-chart-series-*`;只有用户明确要求状态/风险/健康度编码,或数据字段本身就是状态/风险
140
+ 变量时,才用 `--dui-accent` 或语义色。"success/failure/warning/blocked/accepted/rejected"
141
+ 这类词本身不能自动带出绿/黄/红。
142
+
143
+ **图表系列色**:同类数据源统一用 `--dui-chart-series-1` 到 `-4`;溢出/其它用
144
+ `--dui-chart-other`。不要给普通数据源用彩虹色、语义色。5 个以上同级数据源应归组为 Top N +
145
+ Other、拆成小倍图、或改用表格/列表。
146
+
147
+ **图示分类**:结构用中性色,焦点/推荐用品牌色,次要类目用强调色。普通流程图/架构图/框架图/
148
+ 依赖图优先用中性+品牌色或图表系列色,而不是语义色。一张紧凑图最多 1-2 个有含义的颜色。连线
149
+ 本身不应用图示分类色。
150
+
151
+ **连线**:默认中性 `--dui-text-faint` 或 `--dui-border`;主路径用 `--dui-brand`;只有明确标注
152
+ 的并列路径才用 `--dui-chart-series-*`;只有状态/风险是主要编码变量时才用语义连线色。流程图连线
153
+ 用平滑曲线/圆角折线,`stroke-linecap="round"`,`stroke-linejoin="round"`。一张图最多 2 种连线
154
+ 样式。方向箭头用统一的 marker:`viewBox="0 0 8 8"`、`refX="7"`、`refY="4"`、
155
+ `markerWidth="8"`、`markerHeight="8"`、`markerUnits="userSpaceOnUse"`、`orient="auto"`,箭头
156
+ 路径 `M1 1 L7 4 L1 7 Z`,配合 `marker-end` 挂在代表连线本身的那条 path 上——不要用花括号、
157
+ 双曲线、独立三角形拼箭头。
158
+
159
+ ## Content Rules
160
+
161
+ 每张图只要一个清晰可见的焦点(推荐项、关键路径、瓶颈、最高值、阶段边界、风险标记),用位置、
162
+ 标签或一个强调色让它显眼,不要只靠颜色区分。
163
+
164
+ 文案密度:节点标签 2-5 个字/词;卡片标题 6-10 个字/词;副标题 ≤5 个字/词;连线标签 1-3 个字/
165
+ 词,能省则省。
166
+
167
+ 复杂度预算:1 个焦点,2-5 个主节点,2-4 个选项/KPI/系列,最多 1 个交互概念(在两个宿主下等价于
168
+ "最多一种状态对比",见 micro-interaction)。横向一排最多 4 个方框,5 个以上要归组/换行/拆分。
169
+ 自定义 SVG 图示保持 ≤6 个未分组节点、≤8 条连线,更大的结构要归组、拆面板、改表格,或直接用
170
+ Markdown 回答。
171
+
172
+ 数据诚实:估算值要标注"约/估计";数值保留一致的小数位;数字旁标单位;来源本身是粗略估计时不要
173
+ 显得像精确值。绝不画地图——按区域用条形图/表格/矩阵代替。
174
+
175
+ ## SVG Fundamentals
176
+
177
+ 出现 SVG、连线、流程图、架构图、结构图、机制示意图时都适用下面的几何规则。
178
+
179
+ **ViewBox**:优先 `viewBox="0 0 720 H"`,`width="100%"`,`height="auto"`。安全边距 ≥40 设计
180
+ 单位或 viewBox 宽度的 6%(取更大值)。`H` 由最低可见图形/文字基线/连线加底部留白决定。不要用
181
+ 负坐标、不要为了塞下过长标签而收缩 viewBox、不要在底部留大片无法解释的空白、不要靠裁剪/背景块
182
+ 掩盖不合法的几何。
183
+
184
+ **文字尺寸**:标签 14px(拉丁字符约 8px/字宽),元数据 12px(约 7px/字宽)。节点宽度 ≥
185
+ max(标题字数×8, 副标题字数×7) + 24px 内边距。中文标签要比可用宽度短。代码风格字符串、数学
186
+ 符号、长标识符、混合文字要按 30-50% 放大宽度估算。
187
+
188
+ **硬性拟合门槛**:在给每个可见文字标签、节点、连线标签、图例项、徽标、容器都估算出包围盒之前,
189
+ 不要画自定义 SVG。任何一个盒子放不下就先削减内容:缩短标签、归组节点、拆分图示、改成表格/列表,
190
+ 或直接用 Markdown 回答。不要用更小的字号、透明重叠、`overflow: hidden`、裁剪蒙版或背景矩形来
191
+ 替代一次真正合法的布局。
192
+
193
+ **连线**:每条 path/polyline 都要 `fill="none"`,绕开无关节点。同一行相邻节点检查
194
+ `left.x + left.width + gap <= right.x`,无关节点间距 ≥32,流程步骤间距 ≥60;同一列相邻节点
195
+ 检查 `top.y + top.height + gap <= bottom.y`,堆叠节点间距 ≥28,带连线标签时 ≥56。结构容器要
196
+ 先于内部节点放置,且必须留出可见内边距、不与标题/图例/外部输入输出碰撞。
197
+
198
+ **画之前的静默自检**:viewBox 高度按最低元素+留白算出;所有内容落在 x=0..720 内;每个文字
199
+ 标签都留了内边距;任何带文字的 `<rect>`/面板/卡片/图例都按内容尺寸撑开容器并留出内边距;没有
200
+ 两个节点/标签/徽标/图例/连线标签的包围盒重叠;没有连线穿过无关节点或文字;流程图节点用圆角
201
+ 矩形,判断/特殊形状用软化路径。以上任意一条不满足,就不要输出这段 SVG——简化视觉,或改用文字/
202
+ 表格兜底。
203
+
204
+ ## Scene Router
205
+
206
+ 先判断用户意图,路由到**一个**场景文件:
207
+
208
+ | 用户意图 | 场景文件 |
209
+ |---|---|
210
+ | 数值趋势、量级对比、构成/占比、分布、流程强度、密度热力 | `scenes/data-visualization.md` |
211
+ | 模块关系、依赖图、流程图、状态转移、调用链、排期 | `scenes/architecture-and-flow.md` |
212
+ | 方案对比、技术选型、风险摘要、决策矩阵 | `scenes/comparison-and-decision.md` |
213
+ | 原理讲解、物理/抽象机制、因果链 | `scenes/mechanism-explanation.md` |
214
+ | "切换前后"这类状态对比(不是真交互,见该场景文件) | `scenes/micro-interaction.md` |
215
+
216
+ 流程(内部执行步骤,不要讲给用户听):
217
+
218
+ 1. 判断紧凑可视化是否比 Markdown 更清楚;不是就跳过。
219
+ 2. 路由到匹配的场景文件。
220
+ 3. 结合素材:读 `templates/manifest.json`,只在 `status: "ready"` 的候选里挑,读对应
221
+ `templates/<id>/template.md` 再决定是否借用 `widget-code.html`/`fixture.json`。
222
+ 4. 优先选用能以最小改动表达焦点的素材;场景组合优于生搬硬套一个不太合适的素材;没有清晰焦点
223
+ 时优先选择纯文字。
224
+ 5. 按选中场景文件的强制工作流执行,再落笔画 SVG。
225
+ 6. 素材是可复用范例不是死板模板——按需借用布局、CSS、token 用法,混用多个素材时每一块必须回答
226
+ 一个不同的问题。
227
+ 7. 改造图表类素材时保留 `tokens/visual-tokens.md` 的颜色契约,不要把某个模板局部的调色例外
228
+ 搬到别的图表类型上,也不要让语义标签名反过来决定图表系列色的顺序。
229
+ 8. 只有在没有 `ready` 素材匹配、每个候选都有明确的 avoid 规则冲突、场景本质是需要独特图示的
230
+ 机制讲解、组合多个原型比硬套一个完整素材更清楚,或者 Markdown 本来就比图更清楚时,才生成
231
+ 自定义输出。"手写更快""这很简单""自己画更轻量""我已经会画这个了"都不是有效理由。
232
+ 9. 没有 `ready` 素材匹配时不要从零自由发挥,选一个兜底原型(见上面 Visual Design 一节)并保持
233
+ 同一套 token/颜色/字体/间距/圆角/布局契约。
234
+ 10. 兜底优先用中性结构,再用品牌色/图表系列色表达焦点或并列类目;语义色只用于明确的状态/风险
235
+ 编码;卡片/面板背景保持中性,颜色放在标记/边框/标签/焦点路径/图标上;不要在同一张图里堆叠
236
+ 多个兜底原型,除非每一块回答的是不同问题。
237
+ 11. 如果兜底方案放不下(字太小、出现嵌套滚动、标签被裁、连线重叠、连线穿过文字、隐藏溢出、
238
+ 超过 4 个同级图表色加 Other、超过 2 个互不相关的视觉块),改用 Markdown 回答或要求用户
239
+ 收窄范围,而不是硬塞。
240
+
241
+ 素材意图速查:`line-trend` 趋势;`bar-chart-multiple` 分组对比;`scatter-chart` 相关性/离群点;
242
+ `gantt-chart` 排期/关键路径;`funnel-bar-chart` 转化流失;`sankey-chart` 带权重的分阶段流动;
243
+ `heatmap-chart` 密度;`pie-donut-text` 带总计的构成;`bar-stacked-legend` 堆叠贡献;
244
+ `pie-chart-label-list` 带标签的部分-整体;`radar-chart-legend`/`radar-chart-lines-only` 画像
245
+ 对比;`comparison-cards` 方案权衡;`sequence-diagram` 有序调用;`tree-flow` 层级;
246
+ `architecture-elements` 架构原语。
247
+
248
+ 新增、改名、删除或调整某个素材的路由意图时,`templates/<template-id>/template.md`、
249
+ `widget-code.html`、`fixture.json`、`templates/manifest.json`、相关 `scenes/*.md`、
250
+ `templates/README.md` 要一起改。目录或必需文件缺失就不能标 `ready`。兜底原型不是素材 id,
251
+ 不应出现在 `templates/manifest.json` 里。
@@ -5,7 +5,8 @@ This directory stores reusable dynamic-ui generation materials.
5
5
  Templates are not frontend components and do not belong to any particular rendering tool. They
6
6
  are source examples and behavior contracts for a static HTML/SVG fragment that gets either (a)
7
7
  dropped inline into a minimal-desktop chat reply as a ` ```svg ` fenced code block, or (b)
8
- embedded into a document (docx/pptx/html report) that's already being written — see `SKILL.md`
8
+ embedded into a document (docx/pptx/html report) that's already being written — see `../SKILL.md`
9
+ for Mermaid/SVG routing and `../svg-design.md`
9
10
  for the host-routing rules and the two-tier root-element contract (Desktop tolerates an HTML
10
11
  `<div>` fragment; document embedding needs a real `<svg>` root).
11
12
 
@@ -1,14 +1,13 @@
1
1
  # Visual Tokens —— 完整定义
2
2
 
3
- `SKILL.md` 已经给了这套 token 的精简版;这份文件补充完整的角色表、一个可以直接照抄的最小
3
+ `../svg-design.md` 已经给了这套 token 的精简版;这份文件补充完整的角色表、一个可以直接照抄的最小
4
4
  可用示例,以及"写文档时怎么换掉兜底色"的细节。按需读取,不需要每次都通读。
5
5
 
6
6
  ## 写法统一约定
7
7
 
8
- 每处颜色/字体都写成 `var(--dui-<角色>, <字面量兜底值>)`,绝不单独写 `var(--dui-x)`(没有
9
- 兜底值)——脱离 Desktop 环境时变量不存在,缺兜底值会让浏览器/文档渲染器把属性值当无效处理,
10
- 图形直接失色或失踪。也绝不为了图省事直接写字面量颜色跳过变量——那样在 Desktop 里就不会跟随
11
- 用户切换的亮暗主题。
8
+ Desktop 内联图颜色使用 `var(--dui-<角色>, <字面量兜底值>)`,不省略兜底值,
9
+ 以跟随亮暗主题。独立 SVG 和文档导出则先解析为实际色值与字体,不能假定图片转换器
10
+ 支持 CSS 变量(即便提供了兜底)。按目标阅读器检查,必要时用 PNG 回退。
12
11
 
13
12
  ## 完整角色表
14
13
 
@@ -0,0 +1,24 @@
1
+ # 自动顾问与文字交付契约
2
+
3
+ TUI 和 Desktop 复用 `runQuery` 的检查点与工具事件,不在前端自行发起第二模型请求。
4
+ 仅已配置、启用且为自动模式的 Advisor 才进入有效工具集;仅手动模式保持手动调用。
5
+
6
+ 实质交付不限定为文件。调研结论、计算、方案比较、推荐也需要第二意见。
7
+ 提示词要求主模型主动识别;运行时补充确定性的文本信号(分析请求对应的
8
+ 非简短回答、含数字的成本/风险/推荐等),并保留变更、验证、正式成果与调查证据触发器。
9
+ 这些信号是可测试的启发式,不是完整语义分类器;短小含蓄的结论仍可能漏判,
10
+ 没有任何长度单独触发规则。不要将“自信”或“没有文件”当作豁免条件。
11
+
12
+ 执行前、连续失败、文字与文件交付共享待复核检查点。若模型仍试图继续解决或交付,
13
+ 宿主补一次对应 reason 的 Advisor 调用;用户明确不满、证据冲突、无法判断的表达
14
+ 也可建立检查点。只读诊断和人类澄清仍可进行。
15
+ 通过同一工具权限、取消、历史和 UI 事件通道执行,并将最新待交付草稿交给顾问。
16
+ 主模型读取意见后重新生成操作或回复,不执行咨询前预生成的后续操作。
17
+ 已完成同阶段咨询不重复调用;交付复核后成果又发生写入,重新开启交付复核;
18
+ 新增证据后的新冲突可以再问,普通读取本身不重开交付检查。
19
+ 明确仅修正拼写/文案的小修改不机械进行执行前或变更交付检查。
20
+ 顾问失败、被权限拒绝或达到执行上限时允许有界降级,并明确提示未复核;不无限重试,
21
+ 不将未调用、调用失败或顾问意见冒充验证证据。忽略提醒不再算尝试,
22
+ 实际失败/拒绝作为咨询结果留痕,未执行的检查点跨中断保留。
23
+
24
+ 回归入口:`bun test test/deliveryReview.test.ts test/loop.advisorGate.test.ts`。
@@ -1,8 +1,7 @@
1
1
  import crypto from 'node:crypto';
2
+ import { needsTextDeliveryReview, reviewSignal, isCosmeticRequest } from './deliveryReview.js';
2
3
  import { expandAdvisorHistory } from '../context/advisorHistory.js';
3
4
  import { provenanceLabel } from '../llm/messageProvenance.js';
4
- // A trigger can defer once. Changing command spelling never grants another deferral.
5
- const MAX_CONSECUTIVE_DEFERRALS = 1;
6
5
  /**
7
6
  * contextPacket() 证据条目的数量与单条截断上限。
8
7
  * 优先分配给近期、相关证据,同时保留早期依据。最终请求预算仍可裁剪本包。
@@ -34,6 +33,14 @@ function textContent(message) {
34
33
  }
35
34
  return '';
36
35
  }
36
+ function manifestFingerprint(raw) {
37
+ try {
38
+ return JSON.stringify(JSON.parse(raw));
39
+ }
40
+ catch {
41
+ return raw;
42
+ }
43
+ }
37
44
  function toolNamesByCallId(history) {
38
45
  const names = new Map();
39
46
  for (const message of history) {
@@ -60,7 +67,7 @@ function versionsOf(metadata) {
60
67
  }
61
68
  function checkpointKey(checkpoint, versions, signature) {
62
69
  // pre-execution/pre-delivery 的意图是"本任务版本内只问一次"(见 beforeTool()
63
- // 的 reason 文案"即将进行本任务版本的首次实质写入")。旧实现把 workVersion/
70
+ // 的执行方案复核)。旧实现把 workVersion/
64
71
  // evidenceVersion 也编进 key,而这两个计数器在几乎每次成功的工具调用(包括
65
72
  // 纯 Read/Grep,见 observeToolResult)后都会变化——"首次"因此从未真正生效,
66
73
  // 任务里的每一次不同写入都会重新触发一轮完整 Advisor+AdvisorDecision,同时
@@ -84,10 +91,8 @@ export class AdvisorCoordinator {
84
91
  #objective;
85
92
  #advisorAvailable;
86
93
  #completed = new Set();
87
- #deferredOnce = new Set();
88
- #deferCounts = new Map();
89
94
  #observedConsultations = new Set();
90
- #degradations = [];
95
+ #unavailableTransitions = [];
91
96
  #softAlerts = new Set();
92
97
  #taskVersion;
93
98
  #taskCommitted = false;
@@ -98,6 +103,9 @@ export class AdvisorCoordinator {
98
103
  #verifications = 0;
99
104
  #deliveryProduced = false;
100
105
  #deliveryDraft;
106
+ #deliveryReviewedWork = -1;
107
+ #deliveryManifest;
108
+ #reviewedManifest;
101
109
  #required;
102
110
  #softCheckpoint;
103
111
  #pending;
@@ -141,6 +149,11 @@ export class AdvisorCoordinator {
141
149
  : -1;
142
150
  for (let index = 0; index < input.history.length; index += 1) {
143
151
  const message = input.history[index];
152
+ if (message.role === 'assistant') {
153
+ const submission = message.tool_calls?.find(call => call.function.name === 'PresentResult');
154
+ if (submission)
155
+ this.#deliveryManifest = manifestFingerprint(submission.function.arguments);
156
+ }
144
157
  // Text-only work has a delivery boundary too. Only answers preceding the
145
158
  // current request start a new review cycle; replay inside a request does not.
146
159
  if (message.role === 'assistant' && message.advisorTaskVersion !== undefined
@@ -173,6 +186,10 @@ export class AdvisorCoordinator {
173
186
  const mutationCount = display.files.filter((file) => ['created', 'modified', 'generated', 'deleted'].includes(file.activity)).length;
174
187
  this.#workVersion += mutationCount;
175
188
  }
189
+ else if (message.advisorMutatesWorkspace && message.toolResultDisposition !== 'deferred'
190
+ && !message.content.startsWith('Error: ')) {
191
+ this.#workVersion += 1;
192
+ }
176
193
  const failed = message.content.startsWith('Error: ');
177
194
  if (toolName &&
178
195
  toolName !== 'Advisor' &&
@@ -228,8 +245,31 @@ export class AdvisorCoordinator {
228
245
  metadata: message.display_metadata,
229
246
  }, { mutatesWorkspace: message.advisorMutatesWorkspace === true, highRisk: false });
230
247
  }
248
+ if (this.#required && (!this.#required.key.startsWith(`${this.#taskVersion}:`)
249
+ || this.#completed.has(this.#required.key)))
250
+ this.#required = undefined;
251
+ this.#requireSignal(input.currentRequest);
252
+ }
253
+ #requireSignal(text, delivery = false) {
254
+ const reason = reviewSignal(text);
255
+ if (!reason || !this.#advisorAvailable || this.#required)
256
+ return;
257
+ const key = `${this.#taskVersion}:signal:${reason}:${this.#evidenceVersion}`;
258
+ if (this.#completed.has(key))
259
+ return;
260
+ this.#required = { checkpoint: delivery ? 'pre-delivery' : 'pre-execution', key, reviewReason: reason,
261
+ reason: `针对 ${reason} 复核用户反馈或证据;先核查已有材料,不把顾问意见当作事实。` };
262
+ this.#reminders.push(this.#required.reason);
231
263
  }
232
264
  #applyReviewEvent(metadata) {
265
+ if (!(metadata.kind === 'advisor_checkpoint' && metadata.status === 'required')
266
+ && this.#required?.key === metadata.checkpointKey)
267
+ this.#required = undefined;
268
+ if (metadata.checkpoint === 'pre-delivery' && metadata.kind === 'advisor_consultation') {
269
+ this.#deliveryReviewedWork = metadata.workVersion;
270
+ this.#reviewedManifest = this.#deliveryManifest;
271
+ this.#completed.add(`${metadata.taskVersion}:pre-delivery`);
272
+ }
233
273
  if (metadata.kind === 'advisor_consultation' && metadata.status === 'completed') {
234
274
  if (this.#observedConsultations.has(metadata.consultationId))
235
275
  return;
@@ -241,8 +281,8 @@ export class AdvisorCoordinator {
241
281
  return;
242
282
  }
243
283
  if (metadata.kind === 'advisor_checkpoint' && metadata.status === 'required') {
244
- this.#deferredOnce.add(metadata.checkpointKey);
245
- this.#deferCounts.set(metadata.checkpointKey, 1);
284
+ const signal = metadata.checkpointKey.match(/:signal:(user_dissatisfaction|evidence_conflict|uncertainty):/)?.[1];
285
+ this.#required = { checkpoint: metadata.checkpoint, key: metadata.checkpointKey, reviewReason: signal, reason: '恢复尚未完成的顾问检查' };
246
286
  return;
247
287
  }
248
288
  this.#completed.add(metadata.checkpointKey);
@@ -261,12 +301,11 @@ export class AdvisorCoordinator {
261
301
  ? `顾问检查 ${metadata.checkpoint} 咨询未成功,已降级继续;该次咨询不构成验证证据。`
262
302
  : `顾问检查 ${metadata.checkpoint} 未完成,已降级继续;该次检查不构成验证证据。`);
263
303
  }
264
- drainDegradations() { return this.#degradations.splice(0); }
265
- #recordDegradation(checkpoint, key) {
266
- const event = { kind: 'advisor_checkpoint', checkpoint,
267
- checkpointKey: key, status: 'attempted-unavailable', ...this.versions };
268
- this.#applyReviewEvent(event);
269
- this.#degradations.push(event);
304
+ // Retain the public method name for existing callers. Pending checks also need
305
+ // persistence when a turn ends at its limit before consultation can run.
306
+ drainDegradations() {
307
+ return [...this.#unavailableTransitions.splice(0), ...(this.#required ? [{ kind: 'advisor_checkpoint', checkpoint: this.#required.checkpoint,
308
+ checkpointKey: this.#required.key, status: 'required', ...this.versions }] : [])];
270
309
  }
271
310
  get versions() {
272
311
  return {
@@ -279,6 +318,14 @@ export class AdvisorCoordinator {
279
318
  return this.#pending?.consultationId;
280
319
  }
281
320
  setAvailable(available) {
321
+ const pending = this.#required ?? this.#softCheckpoint;
322
+ if (!available && this.#advisorAvailable && pending && !this.#completed.has(pending.key)) {
323
+ const event = { kind: 'advisor_checkpoint', checkpoint: pending.checkpoint,
324
+ checkpointKey: pending.key, status: 'attempted-unavailable', ...this.versions };
325
+ this.#applyReviewEvent(event);
326
+ this.#unavailableTransitions.push(event);
327
+ this.#softCheckpoint = undefined;
328
+ }
282
329
  this.#advisorAvailable = available;
283
330
  }
284
331
  usageLine(used) {
@@ -311,6 +358,13 @@ export class AdvisorCoordinator {
311
358
  return { allow: true };
312
359
  }
313
360
  if (call.function.name === 'PresentResult') {
361
+ // Review the actual submission, not an earlier manifest with different
362
+ // files/claims. Whitespace-only JSON changes are not a new revision.
363
+ const manifest = manifestFingerprint(call.function.arguments);
364
+ this.#deliveryManifest = manifest;
365
+ if (this.#deliveryReviewedWork >= 0 && this.#reviewedManifest !== manifest) {
366
+ this.#completed.delete(checkpointKey('pre-delivery', this.versions));
367
+ }
314
368
  this.#deliveryDraft = `待提交成果(尚未发布;文件内容必须以读取/渲染证据核实):\n${call.function.arguments}`;
315
369
  return this.beforeDelivery(undefined, true);
316
370
  }
@@ -320,7 +374,11 @@ export class AdvisorCoordinator {
320
374
  return failure;
321
375
  }
322
376
  if (semantics.mutatesWorkspace) {
323
- return this.requestCheckpoint('pre-execution', '即将进行本任务版本的首次实质写入');
377
+ if (this.#required?.reviewReason)
378
+ return this.#defer(this.#required.checkpoint, this.#required.reason, this.#required.key);
379
+ if (isCosmeticRequest(this.#currentRequest))
380
+ return { allow: true };
381
+ return this.requestCheckpoint('pre-execution', '即将执行可能修改工作区的操作,先复核执行方案');
324
382
  }
325
383
  return { allow: true };
326
384
  }
@@ -331,31 +389,9 @@ export class AdvisorCoordinator {
331
389
  const key = checkpointKey(checkpoint, this.versions, options.signature);
332
390
  if (this.#completed.has(key))
333
391
  return { allow: true };
334
- if (options.oneShot && this.#deferredOnce.has(key)) {
335
- this.#recordDegradation(checkpoint, key);
336
- this.#required = undefined;
337
- this.#completed.add(key);
338
- const notice = '交付前顾问检查已提示一次但未完成咨询,本次有界降级;不得声称结果已获顾问验证。';
339
- this.#reminders.push(notice);
340
- this.#degradationWarnings.add(notice);
341
- return { allow: true };
342
- }
343
- // A missed consultation must not keep the task suspended.
344
- const seen = (this.#deferCounts.get(key) ?? 0) + 1;
345
- this.#deferCounts.set(key, seen);
346
- if (seen > MAX_CONSECUTIVE_DEFERRALS) {
347
- this.#recordDegradation(checkpoint, key);
348
- this.#required = undefined;
349
- this.#completed.add(key);
350
- // 绕过了本该经过的检查点就必须留痕:静默放行会让模型以为自己合规了。
351
- const notice = `Advisor 检查点 ${checkpoint} 已连续暂缓 ${MAX_CONSECUTIVE_DEFERRALS} 次仍未完成咨询,本次降级放行。这一步没有第二意见背书,请自行加倍核实,并在汇报时如实说明。`;
352
- this.#degradationWarnings.add(notice);
353
- if (!this.#reminders.includes(notice))
354
- this.#reminders.push(notice);
355
- return { allow: true };
356
- }
392
+ // A reminder is not an attempt. The host must execute the pending review,
393
+ // or record its actual failure/unavailability before releasing this boundary.
357
394
  this.#required = { checkpoint, key, reason };
358
- this.#deferredOnce.add(key);
359
395
  return this.#defer(checkpoint, reason, key);
360
396
  }
361
397
  notifyStalled(signature) {
@@ -373,16 +409,37 @@ export class AdvisorCoordinator {
373
409
  this.#reminders.push('检测到连续同类失败或执行没有进展。继续重试前请优先调用 Advisor 获取第二意见;这是提醒,不阻止只读诊断或切换路径。');
374
410
  }
375
411
  shouldCheckDelivery() {
376
- return (this.#mutations > 0 ||
412
+ return ((this.#mutations > 0 && !isCosmeticRequest(this.#currentRequest)) ||
377
413
  this.#verifications > 0 ||
378
414
  this.#deliveryProduced ||
415
+ needsTextDeliveryReview(this.#currentRequest, this.#deliveryDraft ?? '') ||
379
416
  this.#investigationEvidence >= 3);
380
417
  }
418
+ /** After one ignored reminder, the host executes the consultation through its
419
+ * normal tool pipeline instead of treating a missing attempt as a failed one. */
420
+ pendingReview(draft) {
421
+ if (draft !== undefined)
422
+ this.#deliveryDraft = draft;
423
+ const active = this.#required ?? this.#softCheckpoint;
424
+ if (!this.#advisorAvailable || !active || this.#completed.has(active.key))
425
+ return;
426
+ return { reason: active.reviewReason ?? (active.checkpoint === 'pre-delivery' ? 'delivery_review'
427
+ : active.checkpoint === 'stalled' ? 'stalled' : 'plan_review'),
428
+ note: active.reason ?? '核实当前问题、待执行方案或待交付草稿,主 Agent 读取第二意见后自行判断继续。' };
429
+ }
381
430
  beforeDelivery(draft, force = false) {
382
431
  if (draft !== undefined)
383
432
  this.#deliveryDraft = draft;
384
433
  if (!this.#advisorAvailable)
385
434
  return { allow: true };
435
+ this.#requireSignal(draft ?? '', true);
436
+ if (this.#required?.reviewReason)
437
+ return this.#defer(this.#required.checkpoint, this.#required.reason, this.#required.key);
438
+ // A later successful mutation changes the reviewed artifact. Ordinary reads
439
+ // and wording changes do not reopen the checkpoint.
440
+ if (this.#deliveryReviewedWork >= 0 && this.#workVersion > this.#deliveryReviewedWork) {
441
+ this.#completed.delete(checkpointKey('pre-delivery', this.versions));
442
+ }
386
443
  const failure = this.#checkFailureReview();
387
444
  if (failure && !failure.allow) {
388
445
  if (failure.deferred && !failure.deferred.ok)
@@ -391,7 +448,7 @@ export class AdvisorCoordinator {
391
448
  }
392
449
  if (!force && !this.shouldCheckDelivery())
393
450
  return { allow: true };
394
- const decision = this.requestCheckpoint('pre-delivery', '当前回合已产生实质成果或证据,提交最终结论前进行一次第二意见复核', { oneShot: true });
451
+ const decision = this.requestCheckpoint('pre-delivery', '当前回合已产生实质成果或证据,提交最终结论前进行一次第二意见复核');
395
452
  if (decision.deferred &&
396
453
  !decision.deferred.ok &&
397
454
  !this.#reminders.includes(decision.deferred.error)) {
@@ -405,13 +462,12 @@ export class AdvisorCoordinator {
405
462
  const explicit = reason === 'plan_review' ? 'pre-execution' : reason === 'delivery_review' ? 'pre-delivery' : undefined;
406
463
  const active = explicit
407
464
  ? { checkpoint: explicit, key: checkpointKey(explicit, this.versions) }
408
- : (reason === 'evidence_conflict' && this.#pending)
465
+ : this.#required ?? ((reason === 'evidence_conflict' && this.#pending)
409
466
  ? { checkpoint: this.#pending.checkpoint, key: this.#pending.checkpointKey }
410
- : this.#required ??
411
- this.#softCheckpoint ?? {
467
+ : this.#softCheckpoint ?? {
412
468
  checkpoint: 'pre-execution',
413
469
  key: `${this.versions.taskVersion}:voluntary`,
414
- };
470
+ });
415
471
  this.#softCheckpoint = { checkpoint: active.checkpoint, key: active.key };
416
472
  return {
417
473
  kind: 'advisor_consultation',