minimal-agent 0.9.0 → 0.10.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.
Files changed (467) hide show
  1. package/README.md +11 -8
  2. package/package.json +34 -1
  3. package/plugins/cron-runner/.claude-plugin/plugin.json +5 -0
  4. package/plugins/cron-runner/commands/cron.md +26 -0
  5. package/plugins/cron-runner/commands/crons.md +7 -0
  6. package/plugins/cron-runner/plugin.js +34 -0
  7. package/plugins/cron-runner/src/commands.js +348 -0
  8. package/plugins/cron-runner/src/cronTime.js +148 -0
  9. package/plugins/cron-runner/src/lock.js +68 -0
  10. package/plugins/cron-runner/src/runs.js +89 -0
  11. package/plugins/cron-runner/src/scheduleEngine.js +72 -0
  12. package/plugins/cron-runner/src/scheduler.js +355 -0
  13. package/plugins/cron-runner/src/schema.js +140 -0
  14. package/plugins/cron-runner/src/service.js +167 -0
  15. package/plugins/cron-runner/src/store.js +298 -0
  16. package/plugins/cron-runner/src/types.js +1 -0
  17. package/plugins/workflow-runner/commands/workflow.md +4 -4
  18. package/plugins/workflow-runner/commands/workflows.md +1 -1
  19. package/plugins/workflow-runner/plugin.js +2 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +172 -9
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +150 -13
  23. package/plugins/workflow-runner/src/index.js +39 -8
  24. package/plugins/workflow-runner/src/loader.js +216 -64
  25. package/plugins/workflow-runner/src/multimodal.js +37 -0
  26. package/plugins/workflow-runner/src/recovery.js +26 -24
  27. package/plugins/workflow-runner/src/runner.js +693 -287
  28. package/plugins/workflow-runner/src/stepExecutors/agent.js +126 -37
  29. package/plugins/workflow-runner/src/stepExecutors/assert.js +3 -2
  30. package/plugins/workflow-runner/src/stepExecutors/llm.js +38 -30
  31. package/plugins/workflow-runner/src/stepExecutors/skill.js +27 -38
  32. package/plugins/workflow-runner/src/stepExecutors/tool.js +56 -3
  33. package/plugins/workflow-runner/src/stepExecutors/verify.js +19 -10
  34. package/plugins/workflow-runner/src/taskIdentity.js +185 -30
  35. package/plugins/workflow-runner/src/types.js +18 -7
  36. package/plugins/workflow-runner/src/workflowState.js +355 -55
  37. package/skills/ai-image/SKILL.md +89 -0
  38. package/skills/ai-image/scripts/generate.mjs +497 -0
  39. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  40. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  41. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  42. package/skills/browser-automation/SKILL.md +47 -0
  43. package/skills/docx/SKILL.md +25 -3
  44. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  45. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  46. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/opc_check.py +253 -0
  53. package/skills/image-gen-openrouter/SKILL.md +10 -2
  54. package/skills/image-stats/SKILL.md +65 -0
  55. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  56. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  57. package/skills/image-stats/scripts/image_stats.py +368 -0
  58. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  59. package/skills/pptx/SKILL.md +123 -95
  60. package/skills/pptx/design.md +82 -0
  61. package/skills/pptx/examples/README.md +29 -0
  62. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  63. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  64. package/skills/pptx/pptxgenjs.md +41 -0
  65. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  66. package/skills/pptx/scripts/layout_lint.py +293 -0
  67. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  68. package/skills/pptx/scripts/opc_check.py +339 -0
  69. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  70. package/skills/pptx/svg.md +67 -0
  71. package/skills/tts-zh/SKILL.md +19 -2
  72. package/skills/ui-design/SKILL.md +158 -0
  73. package/skills/ui-design/claude_design.md +309 -0
  74. package/skills/ui-design/evals/evals.json +254 -0
  75. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  76. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  77. package/skills/ui-design/references/design-context.md +53 -0
  78. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  79. package/skills/ui-design/references/export-pptx.md +124 -0
  80. package/skills/ui-design/references/layout-composition.md +654 -0
  81. package/skills/ui-design/references/preview-feedback.md +37 -0
  82. package/skills/ui-design/references/source-image-layout.md +189 -0
  83. package/skills/ui-design/references/starter-components.md +78 -0
  84. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  85. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  86. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  87. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  88. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  89. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  90. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  91. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  92. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  93. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  94. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  95. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  96. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  97. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  98. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  99. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  100. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/assemble_material.py +217 -0
  140. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  141. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  142. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  143. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  144. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  145. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  146. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  147. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  148. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  149. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  150. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  151. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  152. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  153. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  154. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  155. package/skills/ui-design/scripts/design_style_check.py +397 -0
  156. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  157. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  158. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  159. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  160. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  161. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  162. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  163. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  164. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  165. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  166. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  167. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  168. package/skills/ui-design/scripts/layout_balance.py +881 -0
  169. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  170. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  171. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  172. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  173. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  174. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  175. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  176. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  177. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  178. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  179. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  180. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  181. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  182. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  183. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  184. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  185. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  186. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  187. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  188. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  195. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  196. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  205. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  206. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  221. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  222. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  225. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  226. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  239. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  240. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  248. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  249. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  261. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  262. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  275. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  292. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  293. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  316. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  324. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  325. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  328. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  329. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  337. package/skills/video-compose/SKILL.md +8 -1
  338. package/skills/web-crawler/SKILL.md +70 -0
  339. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  340. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  341. package/skills/xlsx/SKILL.md +12 -0
  342. package/skills/xlsx/scripts/opc_check.py +253 -0
  343. package/src/advisorState.js +22 -0
  344. package/src/askBridge.js +87 -3
  345. package/src/bootstrap/workingDir.js +12 -1
  346. package/src/cli/args.js +17 -1
  347. package/src/cli/configMenu.js +236 -0
  348. package/src/cli/configWizard.js +7 -5
  349. package/src/cli/print.js +37 -4
  350. package/src/cli/streamJson.js +77 -24
  351. package/src/cli/wizards/advisorWizard.js +147 -0
  352. package/src/cli/wizards/fieldForm.js +149 -0
  353. package/src/cli/wizards/imageWizard.js +60 -0
  354. package/src/cli/wizards/openRouterWizard.js +23 -0
  355. package/src/cli/wizards/providerPresets.js +95 -0
  356. package/src/cli/wizards/providerWizard.js +219 -0
  357. package/src/cli/wizards/searchWizard.js +23 -0
  358. package/src/cli/wizards/ttsWizard.js +34 -0
  359. package/src/config/configFile.js +441 -83
  360. package/src/config/configService.js +446 -0
  361. package/src/config.js +24 -64
  362. package/src/context/agentHome.js +9 -0
  363. package/src/context/archive.js +3 -2
  364. package/src/context/compact.js +8 -5
  365. package/src/context/contextLock.js +81 -0
  366. package/src/context/persistContext.js +30 -11
  367. package/src/context/reactiveCompact.js +4 -2
  368. package/src/context/recentDirs.js +21 -61
  369. package/src/context/sessionPath.js +97 -23
  370. package/src/context/sessionRegistry.js +303 -24
  371. package/src/context/tokenCounter.js +18 -0
  372. package/src/context/workspaceIndex.js +731 -0
  373. package/src/infra/verification.js +3 -2
  374. package/src/llm/client.js +293 -206
  375. package/src/llm/providers/anthropicFormat.js +241 -0
  376. package/src/llm/providers/openaiFormat.js +190 -0
  377. package/src/loop.js +211 -27
  378. package/src/main.js +61 -11
  379. package/src/messageQueue.js +97 -0
  380. package/src/multimodal.js +203 -0
  381. package/src/plugin-sdk.js +15 -0
  382. package/src/plugins/commandRouter.js +3 -0
  383. package/src/plugins/hookEngine.js +5 -1
  384. package/src/plugins/pluginLifecycle.js +28 -0
  385. package/src/plugins/pluginRunner.js +40 -0
  386. package/src/plugins/transcript.js +4 -3
  387. package/src/prompts/pluginList.js +2 -2
  388. package/src/prompts/projectInstructions.js +3 -3
  389. package/src/prompts/promptAssembly.js +133 -109
  390. package/src/prompts/skillList.js +12 -22
  391. package/src/session/runAgentTurn.js +70 -0
  392. package/src/session/runTurn.js +41 -6
  393. package/src/toolGate.js +17 -0
  394. package/src/tools/advisor/advisor.js +379 -0
  395. package/src/tools/agent/agent.js +555 -0
  396. package/src/tools/agent/agentTypes.js +59 -0
  397. package/src/tools/agent/coordinator.js +93 -0
  398. package/src/tools/agent/worktree.js +270 -0
  399. package/src/tools/ask/ask.js +7 -2
  400. package/src/tools/bash/bash.js +112 -18
  401. package/src/tools/download/download.js +144 -0
  402. package/src/tools/edit/edit.js +1 -0
  403. package/src/tools/edit/multi-edit.js +1 -0
  404. package/src/tools/genimage/genimage.js +242 -0
  405. package/src/tools/genspeech/genspeech.js +67 -0
  406. package/src/tools/glob/glob.js +1 -0
  407. package/src/tools/grep/grep.js +3 -0
  408. package/src/tools/grep/rgPath.js +2 -0
  409. package/src/tools/index.js +25 -2
  410. package/src/tools/read/read.js +1 -0
  411. package/src/tools/shared/killTree.js +52 -0
  412. package/src/tools/toolProgressBridge.js +48 -0
  413. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  414. package/src/tools/webbrowser/browser.js +59 -42
  415. package/src/tools/webbrowser/browserControl.js +118 -0
  416. package/src/tools/webbrowser/browserDaemon.js +97 -0
  417. package/src/tools/webbrowser/browserService.js +27 -0
  418. package/src/tools/webbrowser/browserStream.js +119 -0
  419. package/src/tools/webbrowser/webbrowser.js +699 -130
  420. package/src/tools/webfetch/webfetch.js +1 -0
  421. package/src/tools/websearch/websearch.js +134 -51
  422. package/src/tools/write/write.js +1 -0
  423. package/src/ui/App.js +249 -22
  424. package/src/ui/AskQuestion.js +19 -17
  425. package/src/ui/InputBox.js +34 -9
  426. package/src/ui/LiveArea.js +112 -19
  427. package/src/ui/MessageList.js +100 -14
  428. package/src/ui/ModelPicker.js +56 -0
  429. package/src/ui/QueuedMessagesBanner.js +14 -0
  430. package/src/ui/Root.js +18 -10
  431. package/src/ui/SessionPicker.js +102 -0
  432. package/src/ui/StatusLine.js +13 -4
  433. package/src/ui/WorkspacePicker.js +55 -0
  434. package/src/ui/figures.js +44 -0
  435. package/src/ui/hooks/askSlot.js +82 -0
  436. package/src/ui/hooks/useChat.js +345 -64
  437. package/src/ui/hooks/usePasteHandler.js +11 -1
  438. package/src/ui/hooks/useTextBuffer.js +1 -3
  439. package/src/ui/liveViewport.js +5 -1
  440. package/src/ui/markdown.js +206 -0
  441. package/src/ui/textBuffer.js +0 -7
  442. package/src/ui/theme.js +32 -0
  443. package/src/utils/debugLog.js +0 -4
  444. package/src/utils/resourcePaths.js +2 -2
  445. package/src/utils/stripThink.js +50 -0
  446. package/src/web/browserBridge.js +447 -0
  447. package/src/web/protocol.js +19 -0
  448. package/workflows/adaptive-research.yaml +9 -2
  449. package/workflows/book-review-short.yaml +31 -7
  450. package/workflows/e2e-write-greet.yaml +3 -0
  451. package/workflows/illustrate.yaml +227 -0
  452. package/workflows/plan.yaml +437 -207
  453. package/workflows/ppt.yaml +1659 -0
  454. package/workflows/ralph-loop.yaml +142 -112
  455. package/workflows/schema.json +81 -23
  456. package/workflows/webppt.yaml +496 -0
  457. package/workflows/word.yaml +710 -0
  458. package/workflows/youtube-shorts.yaml +38 -10
  459. package/MINIMAL-TRACE.md +0 -77
  460. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  461. package/skills/config/SKILL.md +0 -123
  462. package/skills/frontend-design/LICENSE.txt +0 -177
  463. package/skills/frontend-design/SKILL.md +0 -42
  464. package/skills/pptx/scripts/office/__pycache__/soffice.cpython-314.pyc +0 -0
  465. package/src/ui/ProgressPanel.js +0 -54
  466. package/src/ui/StreamingBlock.js +0 -11
  467. package/src/ui/ToolStatus.js +0 -11
@@ -13,12 +13,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  * 每题选项列表末尾自动追加一个「✏️ 其他(自由输入)」伪选项,命中即切到文本
14
14
  * 输入子模式,让用户填选项之外的答案(自由输入按单值处理,覆盖多选集合)。
15
15
  *
16
- * Ink 用法与风格对齐 cli/configWizard.tsx:useInput 手写键位、↑↓ 取模移动
16
+ * Ink 用法与风格对齐 cli/wizards/providerWizard.tsx:useInput 手写键位、↑↓ 取模移动
17
17
  * 高亮、`> ` 前缀 + cyan 高亮、gray 操作提示、文本输入用累积 draft + backspace。
18
18
  * ============================================================
19
19
  */
20
- import { useState } from 'react';
20
+ import { useEffect, useMemo, useState } from 'react';
21
21
  import { Box, Text, useInput } from 'ink';
22
+ import { theme } from './theme.js';
22
23
  /** description 过长截断到此长度,防止 live 区被超长文本撑爆。 */
23
24
  const DESC_MAX = 80;
24
25
  /**
@@ -31,17 +32,18 @@ const MAX_QUESTIONS = 4;
31
32
  function clampDesc(s) {
32
33
  return s.length > DESC_MAX ? `${s.slice(0, DESC_MAX - 1)}…` : s;
33
34
  }
34
- export function AskQuestion({ questions, onAnswer, onCancel, }) {
35
- // 硬限 4 题;超量截断并注记
36
- const effectiveQuestions = questions.length > MAX_QUESTIONS
37
- ? (() => {
38
- try {
39
- process.stderr.write(`[AskUserQuestion] 收到 ${questions.length} 题超过上限 ${MAX_QUESTIONS},已截断\n`);
40
- }
41
- catch { /* ignore */ }
42
- return questions.slice(0, MAX_QUESTIONS);
43
- })()
44
- : questions;
35
+ export function AskQuestion({ questions, source, queuedCount = 0, onAnswer, onCancel, }) {
36
+ // 硬限 4 题;超量截断。注记走 useEffect(只发一次):渲染期写 stderr 会随
37
+ // 每次按键重渲重复打行,且 Ink 渲染期间写 stdout/stderr 会撕裂终端画面。
38
+ const effectiveQuestions = useMemo(() => questions.slice(0, MAX_QUESTIONS), [questions]);
39
+ useEffect(() => {
40
+ if (questions.length <= MAX_QUESTIONS)
41
+ return;
42
+ try {
43
+ process.stderr.write(`[AskUserQuestion] 收到 ${questions.length} 题超过上限 ${MAX_QUESTIONS},已截断\n`);
44
+ }
45
+ catch { /* ignore */ }
46
+ }, [questions]);
45
47
  // 当前进行到第几题
46
48
  const [questionIndex, setQuestionIndex] = useState(0);
47
49
  // 当前题高亮的选项下标(含末尾「其他」伪选项)
@@ -181,14 +183,14 @@ export function AskQuestion({ questions, onAnswer, onCancel, }) {
181
183
  });
182
184
  // 无题可问(空数组)→ 渲染占位(useInput 已处理取消,这里只防御渲染崩)
183
185
  if (!current) {
184
- return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: "gray", children: "\uFF08\u65E0\u95EE\u9898\uFF09" }) }));
186
+ return (_jsx(Box, { paddingX: 1, children: _jsx(Text, { color: theme.subtle, children: "\uFF08\u65E0\u95EE\u9898\uFF09" }) }));
185
187
  }
186
188
  const progress = `提问 (${questionIndex + 1}/${effectiveQuestions.length}) · ${current.header}`;
187
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "cyan", bold: true, children: progress }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: current.question }) }), textMode ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", children: "\u270F\uFE0F \u81EA\u7531\u8F93\u5165\uFF1A" }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: draft || ' ' }) }), _jsx(Text, { color: "gray", children: "\u8F93\u5165\u540E Enter \u63D0\u4EA4 \u00B7 ESC \u8FD4\u56DE" })] })) : (_jsxs(Box, { flexDirection: "column", children: [options.map((opt, i) => {
189
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [source?.kind === 'subagent' && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.subtle, dimColor: true, children: `⚡ 子任务:${source.description ?? '执行任务'}(其他子任务继续运行中)` }) })), queuedCount > 0 && (_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.subtle, dimColor: true, children: `另有 ${queuedCount} 个问题排队` }) })), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.user, bold: true, children: progress }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: current.question }) }), textMode ? (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.subtle, children: "\u270F\uFE0F \u81EA\u7531\u8F93\u5165\uFF1A" }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: _jsx(Text, { children: draft || ' ' }) }), _jsx(Text, { color: theme.subtle, children: "\u8F93\u5165\u540E Enter \u63D0\u4EA4 \u00B7 ESC \u8FD4\u56DE" })] })) : (_jsxs(Box, { flexDirection: "column", children: [options.map((opt, i) => {
188
190
  const active = i === idx;
189
191
  const box = multi ? (picked.has(i) ? '[x] ' : '[ ] ') : '';
190
- return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? 'cyan' : undefined, children: [active ? '> ' : ' ', box, opt.label] }), opt.description ? (_jsxs(Text, { color: "gray", children: [' ', clampDesc(opt.description)] })) : null] }, `${i}-${opt.label}`));
191
- }), _jsxs(Text, { color: idx === otherIndex ? 'cyan' : undefined, children: [idx === otherIndex ? '> ' : ' ', multi ? ' ' : '', "\u270F\uFE0F \u5176\u4ED6\uFF08\u81EA\u7531\u8F93\u5165\uFF09"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: multi
192
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? theme.user : undefined, children: [active ? '> ' : ' ', box, opt.label] }), opt.description ? (_jsxs(Text, { color: theme.subtle, children: [' ', clampDesc(opt.description)] })) : null] }, `${i}-${opt.label}`));
193
+ }), _jsxs(Text, { color: idx === otherIndex ? theme.user : undefined, children: [idx === otherIndex ? '> ' : ' ', multi ? ' ' : '', "\u270F\uFE0F \u5176\u4ED6\uFF08\u81EA\u7531\u8F93\u5165\uFF09"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.subtle, children: multi
192
194
  ? '↑↓ 移动 · 空格 多选 · Enter 确认本题 · ESC 取消'
193
195
  : '↑↓ 选择 · Enter 确认 · ESC 取消' }) })] }))] }));
194
196
  }
@@ -16,11 +16,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
16
  * - 保留强制刷新但精准控制时机
17
17
  * ============================================================
18
18
  */
19
- import React, { useRef, useState, useCallback } from 'react';
19
+ import React, { useRef, useState, useCallback, useEffect } from 'react';
20
20
  import { Box, Text, useApp, useInput } from 'ink';
21
21
  import { useTextBuffer } from './hooks/useTextBuffer.js';
22
22
  import { usePasteHandler } from './hooks/usePasteHandler.js';
23
23
  import { useInputHistory } from './hooks/useInputHistory.js';
24
+ import { theme } from './theme.js';
24
25
  // ============================================================
25
26
  // IME 启发式(中文输入法防护)
26
27
  // ------------------------------------------------------------
@@ -41,12 +42,19 @@ import { useInputHistory } from './hooks/useInputHistory.js';
41
42
  const IME_BURST_GAP_MS = 200; // 字符间隔 < 此值视作 IME 连续输入
42
43
  const IME_BURST_MIN_COUNT = 3; // 连续 N 个字符才触发 IME 锁定
43
44
  const IME_LOCKOUT_MS = 500; // 触发后禁 Enter 提交的窗口
44
- function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
45
+ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact, onWorkspaces }) {
45
46
  const buf = useTextBuffer();
46
47
  const hist = useInputHistory();
47
48
  const ctrlCCountRef = useRef(0);
48
49
  const ctrlCTimerRef = useRef(null);
49
50
  const { exit } = useApp();
51
+ // 卸载清理:双击退出计数窗口的定时器(组件被 AskQuestion/ConfigMenu 顶替时)
52
+ useEffect(() => {
53
+ return () => {
54
+ if (ctrlCTimerRef.current)
55
+ clearTimeout(ctrlCTimerRef.current);
56
+ };
57
+ }, []);
50
58
  // 刷新触发器:值本身不读,只用 setter 触发 InputBoxImpl 重渲(→ BufferView 随父重渲)。
51
59
  const [, setRefreshCounter] = useState(0);
52
60
  // ✅ Ctrl+R 反向搜索模式(null 表示不在搜索)
@@ -91,6 +99,10 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
91
99
  multiline: true,
92
100
  });
93
101
  useInput((input, key) => {
102
+ if (key.ctrl && input === 'p') {
103
+ onWorkspaces();
104
+ return;
105
+ }
94
106
  // ---- ✅ 粘贴检测(必须优先处理)----
95
107
  // 这会在粘贴期间忽略 Enter 键,防止多行内容被截断
96
108
  const pasteResult = handlePasteInput(input, key);
@@ -172,9 +184,9 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
172
184
  }
173
185
  return;
174
186
  }
175
- // ---- ✅ Ctrl+R 进入反向搜索(仅在空 buf + 非 disabled)----
187
+ // ---- ✅ Ctrl+R 进入反向搜索(仅在空 buf)----
176
188
  if (key.ctrl && input === 'r') {
177
- if (!disabled && buf.state.text.length === 0) {
189
+ if (buf.state.text.length === 0) {
178
190
  setSearchMode({ query: '', hit: null });
179
191
  }
180
192
  return;
@@ -194,6 +206,16 @@ function InputBoxImpl({ onSubmit, disabled, onAbort, onClear, onCompact }) {
194
206
  catch (e) {
195
207
  console.error('[InputBox] onAbort threw:', e);
196
208
  }
209
+ // 强制退出安全阀(2026-07-02「Ctrl+C 退不出来」事故):abort 收尾慢
210
+ // (长时子进程/网络挂起)时用户不能被困在程序里——1s 窗口内再按一次
211
+ // Ctrl+C 直接退出(复用下方双击退出的计数/窗口;工作流现场已由状态目录
212
+ // 持久化 halt 语义,强退不丢进度、可 resume)。ESC 保持只 abort 不退出。
213
+ ctrlCCountRef.current++;
214
+ if (ctrlCCountRef.current >= 2)
215
+ exit();
216
+ if (ctrlCTimerRef.current)
217
+ clearTimeout(ctrlCTimerRef.current);
218
+ ctrlCTimerRef.current = setTimeout(() => { ctrlCCountRef.current = 0; }, 1000);
197
219
  return;
198
220
  }
199
221
  ctrlCCountRef.current++;
@@ -393,19 +415,22 @@ function ReverseSearchView({ query, hit, }) {
393
415
  : hit !== null && hit.includes('\n')
394
416
  ? `${hitFirstLine} …`
395
417
  : hitFirstLine;
396
- return (_jsxs(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "magenta", bold: true, children: '(reverse-i-search)`' }), _jsx(Text, { color: "yellow", children: query }), _jsx(Text, { color: "magenta", bold: true, children: "': " }), _jsx(Text, { color: hitFirstLine === null ? 'gray' : 'white', children: hitDisplay })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", dimColor: true, children: "Enter \u63D0\u4EA4 \u00B7 ESC \u53D6\u6D88 \u00B7 Backspace \u5220\u5B57\u7B26" }) })] }));
418
+ return (_jsxs(Box, { borderStyle: "round", borderColor: "magenta", paddingX: 1, flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: theme.plugin, bold: true, children: '(reverse-i-search)`' }), _jsx(Text, { color: theme.warning, children: query }), _jsx(Text, { color: theme.plugin, bold: true, children: "': " }), _jsx(Text, { color: hitFirstLine === null ? theme.subtle : 'white', children: hitDisplay })] }), _jsx(Box, { children: _jsx(Text, { color: theme.subtle, dimColor: true, children: "Enter \u63D0\u4EA4 \u00B7 ESC \u53D6\u6D88 \u00B7 Backspace \u5220\u5B57\u7B26" }) })] }));
397
419
  }
398
420
  function BufferView({ buf, disabled }) {
399
421
  // buf.layout 由 useTextBuffer 的 useMemo([state]) 维护:父组件每次重渲传入的 buf
400
422
  // 都已带最新 layout,这里直接读即可(BufferView 故意不 memo,随父重渲)。
401
423
  const { lines, cursorRow, cursorCol } = buf.layout;
402
- const promptColor = disabled ? 'gray' : 'cyan';
403
- const prompt = disabled ? '⏳ ' : '> ';
424
+ const promptColor = disabled ? theme.warning : theme.user;
425
+ const prompt = disabled ? '📝 ' : '> ';
426
+ const hasContent = lines.some(l => l.length > 0);
404
427
  return (_jsx(Box, { borderStyle: "round", borderColor: promptColor, paddingX: 1, flexDirection: "column", children: lines.map((line, row) => {
405
428
  const isFirst = row === 0;
406
- const isCursorRow = row === cursorRow && !disabled;
429
+ const isCursorRow = row === cursorRow;
407
430
  // key 只用 row:保持稳定,避免逐行重建(layout 变化由父级重渲带下来)
408
- return (_jsxs(Box, { children: [_jsx(Text, { color: promptColor, bold: true, children: isFirst ? prompt : ' ' }), isCursorRow ? _jsx(CursorLine, { line: line, col: cursorCol }) : _jsx(Text, { children: line || ' ' })] }, row));
431
+ return (_jsxs(Box, { children: [_jsx(Text, { color: promptColor, bold: true, children: isFirst ? prompt : ' ' }), isCursorRow ? (_jsx(CursorLine, { line: line, col: cursorCol })) : disabled && isFirst && !hasContent ? (
432
+ /* 执行中占位提示:告知可继续输入插话 */
433
+ _jsx(Text, { color: theme.subtle, dimColor: true, children: "AI \u5DE5\u4F5C\u4E2D\uFF0C\u53EF\u7EE7\u7EED\u8F93\u5165\u8865\u5145\u6D88\u606F\uFF08Ctrl+C \u4E2D\u65AD\uFF0C\u53CC\u51FB Ctrl+C \u5F3A\u5236\u9000\u51FA\uFF09" })) : (_jsx(Text, { children: line || ' ' }))] }, row));
409
434
  }) }));
410
435
  }
411
436
  /** 光标行渲染(稳定,不会被重建) */
@@ -11,6 +11,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  * tool_executing → runningTools 逐行人话描述(gray dim)
12
12
  * compacting/idle→ 无内容行
13
13
  *
14
+ * ★ 暂态错误行(transientError):工作流步执行中 LLM 调用失败(会被该步重试)时,
15
+ * 不把它当永久红字提交进 scrollback,而是作为**状态行下方的一条黄色暂态行**显示。
16
+ * 它随状态行每帧刷新而被覆盖(不 strand),重试成功后由 useChat 丢弃 → 自然消失。
17
+ * 彻底失败(步 ok:false / 顶层 halt)才由 useChat 改走永久红字(chat.error)。
18
+ * 这条行同样走 INV-1 宽度钳制(clampWidthStart),不破坏防刷屏不变量。
19
+ *
14
20
  * ★ 为什么这样能既"多行打字机 + 流式答案"又**不刷屏**(详见 liveViewport.ts 头部):
15
21
  * - INV-1:每行都用 wrapToDisplayLines / clampWidthStart 按 CJK/emoji=2 钳到
16
22
  * ≤ cols - LIVE_RIGHT_MARGIN → 终端永不自动折行 → ink eraseLines 每帧精确。
@@ -26,6 +32,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
32
  import React, { useEffect, useRef, useState } from 'react';
27
33
  import { Box, Text } from 'ink';
28
34
  import { LIVE_RIGHT_MARGIN, clampWidthStart, wrapToDisplayLines, } from './liveViewport.js';
35
+ import { theme } from './theme.js';
36
+ import { figures } from './figures.js';
29
37
  const STAGE_LABELS = {
30
38
  thinking: '思考中',
31
39
  streaming: '回答中',
@@ -37,7 +45,7 @@ const SPINNER_FRAMES = ['✶', '✸', '✹', '✺', '✹', '✷'];
37
45
  /** spinner 推进 + 强制 re-render 间隔(ms)。~7Hz,远低于 ink 31Hz throttle,Windows 安全。 */
38
46
  const SPINNER_INTERVAL_MS = 150;
39
47
  /** 内容区行数硬顶(大终端也不铺满,保持清爽)。 */
40
- const CONTENT_HARD_CAP = 8;
48
+ const CONTENT_HARD_CAP = 16;
41
49
  /** 终端高度里预留给 status(1) + 输入框(3) + StatusLine(1) + error/interrupt + 安全余量。 */
42
50
  const RESERVED_ROWS = 9;
43
51
  /** reasoning 行 💭 前缀 / 续行缩进的预留宽度(💭=2 + 空格;上界 4 更稳)。 */
@@ -53,28 +61,108 @@ function lastN(arr, n) {
53
61
  return arr.length <= n ? arr : arr.slice(arr.length - n);
54
62
  }
55
63
  function toolDesc(t) {
56
- if (t.argsFriendly && t.argsFriendly.length > 0)
57
- return t.argsFriendly;
58
- const a = t.argsPreview.length > TOOL_ARGS_MAX
59
- ? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
60
- : t.argsPreview;
61
- return `${t.toolName}(${a})`;
64
+ // 长时工具的步内实时进度(LoopEvent.tool_progress)追加在描述后——
65
+ // 比静态 args 信息量大得多(如生图 "[ai-image-plan] round 1/1 15/31 t2i → ai_p11.png")。
66
+ // 宽度钳制由调用方 clampWidthStart 统一负责(INV-1),这里只拼内容。
67
+ const base = t.argsFriendly && t.argsFriendly.length > 0
68
+ ? t.argsFriendly
69
+ : `${t.toolName}(${t.argsPreview.length > TOOL_ARGS_MAX
70
+ ? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
71
+ : t.argsPreview})`;
72
+ return t.progress && t.progress.length > 0 ? `${base} · ${t.progress}` : base;
73
+ }
74
+ /** 不带 progress 的静态描述(蜂群树的描述行用;进度另起 ⎿ 行)。 */
75
+ function toolDescStatic(t) {
76
+ return t.argsFriendly && t.argsFriendly.length > 0
77
+ ? t.argsFriendly
78
+ : `${t.toolName}(${t.argsPreview.length > TOOL_ARGS_MAX
79
+ ? `${t.argsPreview.slice(0, TOOL_ARGS_MAX - 1)}…`
80
+ : t.argsPreview})`;
81
+ }
82
+ /**
83
+ * 蜂群树:runningTools 里有 ≥2 个并行的 Agent 子任务时,把 tool_executing
84
+ * 内容区渲染成树形(借鉴 Claude Code 的 AgentProgressLine 语汇):
85
+ *
86
+ * ⚡ 3 个子任务并行中(另有 1 个工具)
87
+ * ├─ 子任务[explore] 检索路由实现
88
+ * │ ⎿ 第 2 轮 · Reading src/router.ts
89
+ * └─ 子任务[explore] 调研配置系统
90
+ * ⎿ 正在撰写报告…(已输出 850 字)
91
+ *
92
+ * - <2 个 Agent → 返回 null,调用方走既有扁平路径(单 Agent 行为不变)。
93
+ * - 行预算:两行模式(描述行 + ⎿ 进度行)放不下 maxLines 时降级单行模式
94
+ * (进度拼在描述后),仍超则 lastN 截尾——INV-2 恒成立。
95
+ * - 每行经 clampWidthStart 钳宽(INV-1;树形字形 ├└│⎿ 已入 charWidth=2 上界)。
96
+ * - Agent 的 progress 行(agent.ts publish 的「label」第 N 轮 · …)剥掉与
97
+ * 描述行重复的「label」前缀,树里不重复念名字。
98
+ * 导出供单测断言(liveArea.test.ts 蜂群树 describe)。
99
+ */
100
+ export function buildSwarmLines(runningTools, maxWidth, maxLines) {
101
+ const agents = runningTools.filter((t) => t.toolName === 'Agent');
102
+ if (agents.length < 2)
103
+ return null;
104
+ const others = runningTools.filter((t) => t.toolName !== 'Agent');
105
+ const queued = agents.filter((agent) => agent.progressDetail?.kind === 'subagent' && agent.progressDetail.phase === 'queued').length;
106
+ const activeCount = agents.length - queued;
107
+ const header = `${figures.swarm} ${activeCount} 运行${queued > 0 ? ` · ${queued} 排队` : ''}${others.length > 0 ? `(另有 ${others.length} 个工具)` : ''}`;
108
+ // 进度文案剥冗余 label 前缀(描述行已含「子任务[type] label」)
109
+ const stripLabel = (p) => p.replace(/^「[^」]*」\s*/, '');
110
+ const twoLine = 1 + agents.length * 2 + others.length <= maxLines;
111
+ const lines = [header];
112
+ agents.forEach((t, i) => {
113
+ const isLast = i === agents.length - 1 && others.length === 0;
114
+ const branch = isLast ? figures.corner : figures.branch;
115
+ const progress = t.progress ? stripLabel(t.progress) : '';
116
+ if (twoLine) {
117
+ lines.push(`${branch} ${toolDescStatic(t)}`);
118
+ lines.push(`${isLast ? ' ' : `${figures.pipe} `}${figures.elbow} ${progress || '启动中…'}`);
119
+ }
120
+ else {
121
+ lines.push(`${branch} ${toolDescStatic(t)}${progress ? ` · ${progress}` : ''}`);
122
+ }
123
+ });
124
+ for (const t of others)
125
+ lines.push(` ${toolDesc(t)}`);
126
+ return lastN(lines, maxLines).map((l) => clampWidthStart(l, maxWidth));
62
127
  }
63
128
  /**
64
129
  * 纯函数:把一帧的 props(+ 动画 frame / 已耗时秒数)算成 LiveView。
65
- * idle 返回 null。导出供单测断言 INV-1(每行宽)/ INV-2(行数)/ 颜色层次。
130
+ * idle 且无暂态错误时返回 null(live 区隐藏);idle 但有暂态错误时仅渲染该黄色行
131
+ * (让"重试中/已恢复"提示在步间隙 stage 短暂回 idle 时也不闪没)。
132
+ * 导出供单测断言 INV-1(每行宽)/ INV-2(行数)/ 颜色层次。
66
133
  */
67
134
  export function buildLiveView(props) {
68
- const { progressStage, reasoningPreview, streamingText, runningTools, pluginProgress, terminalCols, terminalRows, frame, elapsedSec, } = props;
69
- if (progressStage === 'idle')
70
- return null;
71
- const stage = progressStage;
135
+ const { progressStage, reasoningPreview, streamingText, runningTools, pluginProgress, transientError, terminalCols, terminalRows, frame, elapsedSec, } = props;
72
136
  const maxWidth = Math.max(8, terminalCols - LIVE_RIGHT_MARGIN);
73
137
  const maxLines = contentMaxLines(terminalRows);
138
+ // 暂态错误行:单行、钳宽(满足 INV-1)。空串视为无。
139
+ const transientErrorLine = transientError && transientError.length > 0
140
+ ? clampWidthStart(transientError, maxWidth)
141
+ : null;
142
+ // idle 且无暂态错误 → 整个 live 区隐藏(保持既有行为)。
143
+ // idle 但有暂态错误 → 只渲染那一行黄字,无状态行 / 无 spinner(避免 idle 还转圈)。
144
+ if (progressStage === 'idle') {
145
+ if (transientErrorLine === null)
146
+ return null;
147
+ return {
148
+ statusLine: '',
149
+ statusColor: theme.user,
150
+ contentLines: [],
151
+ contentDim: true,
152
+ transientErrorLine,
153
+ };
154
+ }
155
+ const stage = progressStage;
74
156
  // ---- 状态行 ----
75
157
  let status = `${frame} ${STAGE_LABELS[stage]} (${elapsedSec}s)`;
76
158
  if (pluginProgress) {
77
- status += ` · [plugin:${pluginProgress.pluginId}] ${pluginProgress.current}/${pluginProgress.max ?? '?'}${pluginProgress.message ? ` ${pluginProgress.message}` : ''}`;
159
+ // current 缺省 = 纯文案进度(工作流步骤 title),不渲染数字
160
+ const num = pluginProgress.current !== undefined
161
+ ? `${pluginProgress.current}/${pluginProgress.max ?? '?'}`
162
+ : '';
163
+ const msg = pluginProgress.message ?? '';
164
+ const body = [num, msg].filter(Boolean).join(' ');
165
+ status += ` · [plugin:${pluginProgress.pluginId}]${body ? ` ${body}` : ''}`;
78
166
  }
79
167
  status = clampWidthStart(status, maxWidth);
80
168
  // ---- 内容区 ----
@@ -82,7 +170,7 @@ export function buildLiveView(props) {
82
170
  let contentDim = true;
83
171
  if (stage === 'thinking' && reasoningPreview.length > 0) {
84
172
  const wrapped = wrapToDisplayLines(reasoningPreview, maxWidth - REASONING_PREFIX_RESERVE);
85
- contentLines = lastN(wrapped, maxLines).map((line, i) => i === 0 ? `💭 ${line}` : ` ${line}`);
173
+ contentLines = lastN(wrapped, maxLines).map((line, i) => i === 0 ? `${figures.thought} ${line}` : ` ${line}`);
86
174
  contentDim = true;
87
175
  }
88
176
  else if (stage === 'streaming' && streamingText.length > 0) {
@@ -90,14 +178,19 @@ export function buildLiveView(props) {
90
178
  contentDim = false; // 答案 = 主角,默认亮色
91
179
  }
92
180
  else if (stage === 'tool_executing' && runningTools.length > 0) {
93
- contentLines = lastN(runningTools, maxLines).map((t) => clampWidthStart(` ${toolDesc(t)}`, maxWidth));
181
+ // ≥2 个并行 Agent 子任务 → 蜂群树;否则既有扁平路径(单工具/单 Agent 不变)
182
+ const swarm = buildSwarmLines(runningTools, maxWidth, maxLines);
183
+ contentLines =
184
+ swarm ??
185
+ lastN(runningTools, maxLines).map((t) => clampWidthStart(` ${toolDesc(t)}`, maxWidth));
94
186
  contentDim = true;
95
187
  }
96
188
  return {
97
189
  statusLine: status,
98
- statusColor: stage === 'compacting' ? 'magenta' : 'cyan',
190
+ statusColor: stage === 'compacting' ? theme.plugin : theme.user,
99
191
  contentLines,
100
192
  contentDim,
193
+ transientErrorLine,
101
194
  };
102
195
  }
103
196
  export const LiveArea = React.memo(function LiveArea(props) {
@@ -118,8 +211,8 @@ export const LiveArea = React.memo(function LiveArea(props) {
118
211
  const timer = setInterval(() => setTick((t) => (t + 1) % 1_000_000), SPINNER_INTERVAL_MS);
119
212
  return () => clearInterval(timer);
120
213
  }, [progressStage]);
121
- if (progressStage === 'idle')
122
- return null;
214
+ // 不在 idle 直接 return:idle 但存在暂态错误时 buildLiveView 仍会返回一个
215
+ // 只含 transientErrorLine 的 view(让"已恢复/重试中"提示在步间隙也不闪没)。
123
216
  const now = Date.now();
124
217
  const elapsedSec = startRef.current
125
218
  ? Math.max(0, Math.floor((now - startRef.current) / 1000))
@@ -128,5 +221,5 @@ export const LiveArea = React.memo(function LiveArea(props) {
128
221
  const view = buildLiveView({ ...props, frame, elapsedSec });
129
222
  if (!view)
130
223
  return null;
131
- return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: view.statusColor, bold: true, wrap: "truncate", children: view.statusLine }), view.contentLines.map((line, i) => (_jsx(Text, { color: view.contentDim ? 'gray' : undefined, dimColor: view.contentDim, wrap: "truncate", children: line.length > 0 ? line : ' ' }, `live-${i}`)))] }));
224
+ return (_jsxs(Box, { flexDirection: "column", children: [view.statusLine.length > 0 && (_jsx(Text, { color: view.statusColor, bold: true, wrap: "truncate", children: view.statusLine })), view.contentLines.map((line, i) => (_jsx(Text, { color: view.contentDim ? theme.subtle : theme.answer, dimColor: view.contentDim, wrap: "truncate", children: line.length > 0 ? line : ' ' }, `live-${i}`))), view.transientErrorLine !== null && (_jsx(Text, { color: theme.warning, wrap: "truncate", children: view.transientErrorLine }))] }));
132
225
  });
@@ -34,6 +34,12 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
34
34
  */
35
35
  import React from 'react';
36
36
  import { Box, Static, Text } from 'ink';
37
+ import { friendlyToolDescription } from '../loop.js';
38
+ import { contentToDisplayText, contentToPlainText } from '../multimodal.js';
39
+ import { splitThinkBlocks } from '../utils/stripThink.js';
40
+ import { figures } from './figures.js';
41
+ import { renderMarkdownLines } from './markdown.js';
42
+ import { theme } from './theme.js';
37
43
  /** 中断标记 user message 的固定内容(loop.ts 写入) */
38
44
  const INTERRUPT_USER_CONTENT = '(用户按下了 ESC/Ctrl+C 中断了任务)';
39
45
  const MAX_TOOL_PREVIEW_LINES = 3;
@@ -41,6 +47,58 @@ const MAX_TOOL_PREVIEW_LINES = 3;
41
47
  const NARROW_TERMINAL_ROWS = 28;
42
48
  /** reasoning 超过此字符数截尾 —— 防撑爆历史 */
43
49
  const REASONING_MAX_CHARS = 2000;
50
+ /**
51
+ * 把 assistant 最终正文拆成展示行:markdown 管线(src/ui/markdown.ts)产出
52
+ * prose/code/fence 三类行,⏺ 锚点落在**第一条非空 prose 行**——答案以代码块
53
+ * 开头时代码顶格在前、⏺ 顺延;全代码答案无 ⏺(代码纯净优先)。
54
+ * 纯文本无 markdown 特征时走快路径,与旧「按 \n 切 + wrap='end'」逐行一致。
55
+ */
56
+ export function buildAssistantDisplayLines(text) {
57
+ if (text.length === 0)
58
+ return [];
59
+ let anchorPlaced = false;
60
+ return renderMarkdownLines(text).map((line) => {
61
+ if (line.kind !== 'prose') {
62
+ return { content: line.text, prefix: '', kind: line.kind, wrap: 'end' };
63
+ }
64
+ if (!anchorPlaced && line.text.trim() !== '') {
65
+ anchorPlaced = true;
66
+ return { content: line.text, prefix: '⏺ ', kind: line.kind, wrap: 'end' };
67
+ }
68
+ return { content: line.text, prefix: ' ', kind: line.kind, wrap: 'end' };
69
+ });
70
+ }
71
+ /**
72
+ * 扫 history 里全部 assistant.tool_calls,建 tool_call_id → 人话描述 索引。
73
+ * tool 消息本身只有 content + tool_call_id(不动 history 字节的前提下,
74
+ * 头行要显示「哪个工具在干什么」只能反查发起方)。<Static> 提交时序保证
75
+ * assistant(tool_calls) 恒先于对应 tool 消息入 history,索引命中无竞态。
76
+ */
77
+ export function buildToolCallIndex(history) {
78
+ const map = new Map();
79
+ for (const m of history) {
80
+ if (m.role !== 'assistant' || !m.tool_calls)
81
+ continue;
82
+ for (const tc of m.tool_calls) {
83
+ map.set(tc.id, {
84
+ toolName: tc.function.name,
85
+ friendly: friendlyToolDescription(tc.function.name, tc.function.arguments),
86
+ });
87
+ }
88
+ }
89
+ return map;
90
+ }
91
+ /**
92
+ * 从 Agent 子任务报告的统计尾巴(agent.ts 固定格式
93
+ * 「(子任务「x」[type]:N 轮 · M 次工具调用,K 次失败)」)解析统计数。
94
+ * 失败结果(错误文案)不匹配 → null,头行只显示描述不显示 ✓ 统计。
95
+ */
96
+ export function agentResultStats(content) {
97
+ const m = /(子任务「[^」]*」\[[^\]]+\]:(\d+) 轮 · (\d+) 次工具调用(?:,(\d+) 次失败)?)/.exec(content);
98
+ if (!m)
99
+ return null;
100
+ return { turns: Number(m[1]), tools: Number(m[2]), failed: m[3] ? Number(m[3]) : 0 };
101
+ }
44
102
  /**
45
103
  * 把 assistant message 的两个字符串型 reasoning 字段拼成单字符串。
46
104
  * `reasoning_details` 是结构化数组,本期不展开。两个字段任一非空即返回拼接结果;
@@ -80,7 +138,8 @@ export function summarizeTurn(history) {
80
138
  const turnStartIndices = [];
81
139
  for (let i = 0; i < history.length; i++) {
82
140
  const m = history[i];
83
- if (m.role === 'user' && m.content !== INTERRUPT_USER_CONTENT) {
141
+ if (m.role === 'user' &&
142
+ contentToPlainText(m.content) !== INTERRUPT_USER_CONTENT) {
84
143
  turnStartIndices.push(i);
85
144
  }
86
145
  }
@@ -144,6 +203,7 @@ function formatDuration(ms) {
144
203
  export const MessageList = React.memo(function MessageList({ history, compactGeneration, bannerText, terminalRows, }) {
145
204
  const items = React.useMemo(() => {
146
205
  const turnMetas = summarizeTurn(history);
206
+ const toolIndex = buildToolCallIndex(history);
147
207
  return [
148
208
  ...(bannerText
149
209
  ? [
@@ -158,6 +218,7 @@ export const MessageList = React.memo(function MessageList({ history, compactGen
158
218
  kind: 'message',
159
219
  msg: m,
160
220
  turnMeta: turnMetas[idx],
221
+ toolMeta: m.role === 'tool' ? (toolIndex.get(m.tool_call_id) ?? null) : null,
161
222
  // 纯派生 key:有 id 用 id,没有就用 idx 兜底。绝不在 render 期 mutate 消息对象
162
223
  // (<Static> 已 commit 的行永不重绘,key 仅在 append 对账时用,idx 已足够稳定)。
163
224
  key: m.id ?? `message-${idx}`,
@@ -170,7 +231,7 @@ export const MessageList = React.memo(function MessageList({ history, compactGen
170
231
  if (item.kind === 'banner') {
171
232
  return _jsx(BannerRow, { text: item.text }, item.key);
172
233
  }
173
- return (_jsx(MessageRow, { message: item.msg, turnMeta: item.turnMeta, foldReasoning: foldReasoning }, item.key));
234
+ return (_jsx(MessageRow, { message: item.msg, turnMeta: item.turnMeta, toolMeta: item.toolMeta, foldReasoning: foldReasoning }, item.key));
174
235
  } }, compactGeneration));
175
236
  });
176
237
  function BannerRow({ text }) {
@@ -179,9 +240,9 @@ function BannerRow({ text }) {
179
240
  const splitAt = text.indexOf(' ·');
180
241
  const head = splitAt >= 0 ? text.slice(0, splitAt) : text;
181
242
  const tail = splitAt >= 0 ? text.slice(splitAt) : '';
182
- return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: "cyan", bold: true, children: head }), tail.length > 0 && _jsx(Text, { color: "gray", children: tail })] }));
243
+ return (_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { color: theme.user, bold: true, children: head }), tail.length > 0 && _jsx(Text, { color: theme.subtle, children: tail })] }));
183
244
  }
184
- function MessageRow({ message, turnMeta, foldReasoning, }) {
245
+ function MessageRow({ message, turnMeta, toolMeta, foldReasoning, }) {
185
246
  switch (message.role) {
186
247
  case 'system': {
187
248
  // v2.x:ephemeral system message 是 UI 临时提示(slash 命令输出等),
@@ -190,20 +251,27 @@ function MessageRow({ message, turnMeta, foldReasoning, }) {
190
251
  if (!ephemeral)
191
252
  return null;
192
253
  const lines = message.content.split('\n');
193
- return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: "gray", dimColor: true, children: line }, `eph-${i}`))) }) }));
254
+ return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, i) => (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: line }, `eph-${i}`))) }) }));
194
255
  }
195
256
  case 'user': {
257
+ const rule = figures.hr.repeat(6);
196
258
  const headerLine = turnMeta !== null
197
- ? `────── Turn ${turnMeta.turnN} · ${formatTime(turnMeta.timestamp)} · ${formatDuration(turnMeta.durationMs)} · ${turnMeta.toolsCount} tools ──────`
259
+ ? `${rule} Turn ${turnMeta.turnN} · ${formatTime(turnMeta.timestamp)} · ${formatDuration(turnMeta.durationMs)} · ${turnMeta.toolsCount} tools ${rule}`
198
260
  : null;
199
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [headerLine !== null && (_jsx(Text, { color: "gray", dimColor: true, children: headerLine })), _jsxs(Box, { children: [_jsx(Text, { color: "cyan", bold: true, children: '> ' }), _jsx(Text, { color: "cyan", children: message.content })] })] }));
261
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [headerLine !== null && (_jsx(Text, { color: theme.subtle, dimColor: true, children: headerLine })), _jsxs(Box, { children: [_jsx(Text, { color: theme.user, bold: true, children: '> ' }), _jsx(Text, { color: theme.user, wrap: "end", children: contentToDisplayText(message.content) })] })] }));
200
262
  }
201
263
  case 'assistant': {
202
264
  // assistant 可能只有 tool_calls 没有 content(content 为 null)
203
- const text = message.content ?? '';
265
+ const raw = message.content ?? '';
266
+ // 展示层切分内联 <think>(MiniMax 等把思维链写进 content,内核有意不剥):
267
+ // thought 并入 💭 块(灰 dim + 折叠,与结构化 reasoning 同款待遇),
268
+ // 正文只显示净化后的部分。纯展示层——history 字节零改动(KV 缓存约束)。
269
+ const { thought, answer } = splitThinkBlocks(raw);
204
270
  const tcCount = message.tool_calls?.length ?? 0;
205
- // reasoning 拼接
206
- let reasoningText = combineReasoning(message);
271
+ // reasoning 拼接:结构化字段在前,内联 <think> 在后
272
+ let reasoningText = [combineReasoning(message), thought]
273
+ .filter(Boolean)
274
+ .join('\n');
207
275
  let truncatedCharsHint = '';
208
276
  if (reasoningText.length > REASONING_MAX_CHARS) {
209
277
  const omitted = reasoningText.length - REASONING_MAX_CHARS;
@@ -215,17 +283,35 @@ function MessageRow({ message, turnMeta, foldReasoning, }) {
215
283
  ? reasoningLines.slice(0, 2)
216
284
  : reasoningLines;
217
285
  const hiddenReasoningCount = reasoningLines.length - visibleReasoningLines.length;
218
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [visibleReasoningLines.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [visibleReasoningLines.map((line, i) => (_jsx(Text, { color: "gray", dimColor: true, children: i === 0 ? `💭 ${line}` : ` ${line}` }, `r-${i}`))), hiddenReasoningCount > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: ` ▾ ${hiddenReasoningCount} 行 reasoning 折叠(终端高度 < ${NARROW_TERMINAL_ROWS})` })), truncatedCharsHint.length > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: ` ${truncatedCharsHint}` }))] })), text.length > 0 &&
219
- text.split('\n').map((line, i) => i === 0 ? (_jsxs(Box, { children: [_jsx(Text, { color: "green", bold: true, children: '⏺ ' }), _jsx(Text, { children: line.length > 0 ? line : ' ' })] }, `a-${i}`)) : (_jsx(Text, { children: ` ${line}` }, `a-${i}`))), tcCount > 0 && (_jsx(Text, { color: "yellow", dimColor: true, children: ` └─ 调用了 ${tcCount} 个工具:${(message.tool_calls ?? [])
286
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [visibleReasoningLines.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [visibleReasoningLines.map((line, i) => (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: i === 0 ? `${figures.thought} ${line}` : ` ${line}` }, `r-${i}`))), hiddenReasoningCount > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: ` ${figures.fold} ${hiddenReasoningCount} 行 reasoning 折叠(终端高度 < ${NARROW_TERMINAL_ROWS})` })), truncatedCharsHint.length > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: ` ${truncatedCharsHint}` }))] })), buildAssistantDisplayLines(answer).map((line, i) => {
287
+ if (line.kind === 'fence') {
288
+ return (_jsx(Text, { color: theme.subtle, dimColor: true, wrap: line.wrap, children: line.content }, `a-${i}`));
289
+ }
290
+ if (line.kind === 'code') {
291
+ return (_jsx(Text, { wrap: line.wrap, children: line.content.length > 0 ? line.content : ' ' }, `a-${i}`));
292
+ }
293
+ return line.prefix === '⏺ ' ? (_jsxs(Box, { children: [_jsx(Text, { color: theme.anchor, bold: true, wrap: line.wrap, children: line.prefix }), _jsx(Text, { wrap: line.wrap, children: line.content.length > 0 ? line.content : ' ' })] }, `a-${i}`)) : (_jsx(Text, { wrap: line.wrap, children: `${line.prefix}${line.content}`.length > 0
294
+ ? `${line.prefix}${line.content}`
295
+ : ' ' }, `a-${i}`));
296
+ }), tcCount > 0 && (_jsx(Text, { color: theme.warning, dimColor: true, wrap: "end", children: ` ${figures.corner} 调用了 ${tcCount} 个工具:${(message.tool_calls ?? [])
220
297
  .map((tc) => tc.function.name)
221
298
  .join(', ')}` }))] }));
222
299
  }
223
300
  case 'tool': {
224
301
  const lines = message.content.split('\n');
225
- const preview = lines.slice(0, MAX_TOOL_PREVIEW_LINES).join('\n');
302
+ const previewLines = lines.slice(0, MAX_TOOL_PREVIEW_LINES);
226
303
  const hidden = Math.max(0, lines.length - MAX_TOOL_PREVIEW_LINES);
227
304
  const shortId = message.tool_call_id.slice(-8);
228
- return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "gray", dimColor: true, children: `[tool result · #${shortId}]` }), _jsx(Text, { color: "gray", children: preview }), hidden > 0 && (_jsx(Text, { color: "gray", dimColor: true, children: `... ▾ 完整 ${lines.length} 行(输入 /show ${shortId} 查看)` }))] }) }));
305
+ // 头行升级:有 toolMeta(发起方反查命中)显示人话描述;Agent 子任务
306
+ // 再解析统计尾巴显示 ✓ N 轮 · M 次工具。无 meta(历史重载等)兜底旧格式。
307
+ const stats = toolMeta?.toolName === 'Agent' ? agentResultStats(message.content) : null;
308
+ const statsTail = stats
309
+ ? ` ${figures.ok} ${stats.turns} 轮 · ${stats.tools} 次工具${stats.failed > 0 ? `(${stats.failed} 次失败)` : ''}`
310
+ : '';
311
+ const headText = toolMeta
312
+ ? `[${toolMeta.friendly} · #${shortId}]${statsTail}`
313
+ : `[tool result · #${shortId}]`;
314
+ return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: theme.subtle, dimColor: true, wrap: "end", children: headText }), previewLines.map((line, i) => (_jsx(Text, { color: theme.subtle, wrap: "end", children: line.length > 0 ? line : ' ' }, `p-${i}`))), hidden > 0 && (_jsx(Text, { color: theme.subtle, dimColor: true, children: `... ${figures.fold} 完整 ${lines.length} 行(输入 /show ${shortId} 查看)` }))] }) }));
229
315
  }
230
316
  }
231
317
  }
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import { theme } from './theme.js';
5
+ export function ModelPicker({ models, currentModelId, title, nullOptionLabel, onSelect, onCancel, }) {
6
+ const enabledModels = models.filter((m) => m.enabled && m.apiKey);
7
+ const hasNullOption = Boolean(nullOptionLabel);
8
+ const nullIndex = hasNullOption ? enabledModels.length : -1;
9
+ const listLength = enabledModels.length + (hasNullOption ? 1 : 0);
10
+ const initialIdx = (() => {
11
+ const idx = enabledModels.findIndex((m) => m.id === currentModelId);
12
+ if (idx >= 0)
13
+ return idx;
14
+ if (hasNullOption && !currentModelId)
15
+ return nullIndex;
16
+ return 0;
17
+ })();
18
+ const [idx, setIdx] = useState(Math.max(0, Math.min(initialIdx, listLength - 1)));
19
+ useInput((input, key) => {
20
+ if (key.escape || (key.ctrl && input === 'c')) {
21
+ onCancel();
22
+ return;
23
+ }
24
+ if (key.upArrow) {
25
+ setIdx((i) => (i - 1 + listLength) % listLength);
26
+ return;
27
+ }
28
+ if (key.downArrow) {
29
+ setIdx((i) => (i + 1) % listLength);
30
+ return;
31
+ }
32
+ if (key.return) {
33
+ if (hasNullOption && idx === nullIndex) {
34
+ onSelect(null);
35
+ }
36
+ else {
37
+ const target = enabledModels[idx];
38
+ if (target)
39
+ onSelect(target.id);
40
+ }
41
+ }
42
+ });
43
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: theme.user, bold: true, children: title }) }), _jsx(Text, { color: theme.subtle, children: "\u5728\u5DF2\u914D\u7F6E\u7684\u6A21\u578B\u4E2D\u9009\u62E9\uFF1B\u9009\u62E9\u540E\u7ACB\u5373\u751F\u6548\uFF0C\u540E\u7EED\u8BF7\u6C42\u4F7F\u7528\u65B0\u6A21\u578B\u3002" }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [enabledModels.map((m, i) => {
44
+ const active = i === idx;
45
+ const isCurrent = m.id === currentModelId;
46
+ const displayName = m.displayName || m.model;
47
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: active ? theme.user : undefined, children: [active ? '> ' : ' ', isCurrent ? '✓ ' : ' ', _jsx(Text, { color: active ? theme.user : undefined, bold: isCurrent, children: displayName }), isCurrent ? _jsx(Text, { color: theme.subtle, children: " \uFF08\u5F53\u524D\uFF09" }) : null] }), _jsxs(Text, { color: theme.subtle, children: [' ', m.model, " \u00B7 ", formatContextWindow(m.contextWindow)] })] }, m.id));
48
+ }), hasNullOption && (_jsxs(Text, { color: idx === nullIndex ? theme.user : theme.subtle, children: [idx === nullIndex ? '> ' : ' ', !currentModelId ? '✓ ' : ' ', nullOptionLabel] }))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.subtle, children: "\u2191\u2193 \u9009\u62E9 \u00B7 Enter \u786E\u8BA4 \u00B7 ESC \u53D6\u6D88" }) })] }));
49
+ }
50
+ function formatContextWindow(tokens) {
51
+ if (tokens >= 1_000_000)
52
+ return `${(tokens / 1_000_000).toFixed(1)}M context`;
53
+ if (tokens >= 1_000)
54
+ return `${Math.round(tokens / 1_000)}K context`;
55
+ return `${tokens} context`;
56
+ }