minimal-agent 0.8.2 → 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 (484) hide show
  1. package/README.md +14 -12
  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 +3 -1
  20. package/plugins/workflow-runner/src/agentCommon.js +249 -51
  21. package/plugins/workflow-runner/src/dslManifest.js +72 -0
  22. package/plugins/workflow-runner/src/expressions.js +198 -15
  23. package/plugins/workflow-runner/src/index.js +40 -8
  24. package/plugins/workflow-runner/src/jsonPayload.js +1 -1
  25. package/plugins/workflow-runner/src/loader.js +286 -72
  26. package/plugins/workflow-runner/src/multimodal.js +37 -0
  27. package/plugins/workflow-runner/src/recovery.js +49 -39
  28. package/plugins/workflow-runner/src/runner.js +802 -287
  29. package/plugins/workflow-runner/src/stepExecutors/agent.js +157 -39
  30. package/plugins/workflow-runner/src/stepExecutors/assert.js +16 -3
  31. package/plugins/workflow-runner/src/stepExecutors/llm.js +60 -111
  32. package/plugins/workflow-runner/src/stepExecutors/skill.js +47 -47
  33. package/plugins/workflow-runner/src/stepExecutors/tool.js +76 -10
  34. package/plugins/workflow-runner/src/stepExecutors/verify.js +27 -10
  35. package/plugins/workflow-runner/src/taskIdentity.js +296 -0
  36. package/plugins/workflow-runner/src/types.js +19 -9
  37. package/plugins/workflow-runner/src/workflowState.js +516 -33
  38. package/skills/adversarial-review/SKILL.md +0 -1
  39. package/skills/ai-image/SKILL.md +89 -0
  40. package/skills/ai-image/scripts/generate.mjs +497 -0
  41. package/skills/ai-image/scripts/generate_from_plan.mjs +469 -0
  42. package/skills/ai-image/scripts/generate_from_plan_test.mjs +223 -0
  43. package/skills/ai-image/scripts/generate_test.mjs +74 -0
  44. package/skills/browser-automation/SKILL.md +47 -0
  45. package/skills/docx/SKILL.md +25 -3
  46. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  47. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  48. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  49. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  50. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  51. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  52. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  53. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  54. package/skills/docx/scripts/opc_check.py +253 -0
  55. package/skills/image-gen-openrouter/SKILL.md +10 -2
  56. package/skills/image-stats/SKILL.md +65 -0
  57. package/skills/image-stats/scripts/__pycache__/image_stats.cpython-314.pyc +0 -0
  58. package/skills/image-stats/scripts/__pycache__/image_stats_test.cpython-314.pyc +0 -0
  59. package/skills/image-stats/scripts/image_stats.py +368 -0
  60. package/skills/image-stats/scripts/image_stats_test.py +181 -0
  61. package/skills/pptx/SKILL.md +123 -95
  62. package/skills/pptx/design.md +82 -0
  63. package/skills/pptx/examples/README.md +29 -0
  64. package/skills/pptx/examples/hybrid_example.mjs +107 -0
  65. package/skills/pptx/examples/hybrid_example.pptx +0 -0
  66. package/skills/pptx/pptxgenjs.md +41 -0
  67. package/skills/pptx/scripts/deck_helpers.mjs +224 -0
  68. package/skills/pptx/scripts/layout_lint.py +293 -0
  69. package/skills/pptx/scripts/layout_lint_test.py +160 -0
  70. package/skills/pptx/scripts/opc_check.py +339 -0
  71. package/skills/pptx/scripts/svg_fallback.mjs +145 -0
  72. package/skills/pptx/svg.md +67 -0
  73. package/skills/tts-zh/SKILL.md +19 -2
  74. package/skills/ui-design/SKILL.md +158 -0
  75. package/skills/ui-design/claude_design.md +309 -0
  76. package/skills/ui-design/evals/evals.json +254 -0
  77. package/skills/ui-design/references/aesthetic-direction.md +83 -0
  78. package/skills/ui-design/references/claude-design-protocol.md +36 -0
  79. package/skills/ui-design/references/design-context.md +53 -0
  80. package/skills/ui-design/references/design-execution-playbook.md +82 -0
  81. package/skills/ui-design/references/export-pptx.md +124 -0
  82. package/skills/ui-design/references/layout-composition.md +654 -0
  83. package/skills/ui-design/references/preview-feedback.md +37 -0
  84. package/skills/ui-design/references/source-image-layout.md +189 -0
  85. package/skills/ui-design/references/starter-components.md +78 -0
  86. package/skills/ui-design/references/vi-aegis-vector.md +144 -0
  87. package/skills/ui-design/references/vi-amber-prism.md +133 -0
  88. package/skills/ui-design/references/vi-aqua-glass.md +200 -0
  89. package/skills/ui-design/references/vi-comfort-vision.md +147 -0
  90. package/skills/ui-design/references/vi-ember-prism.md +140 -0
  91. package/skills/ui-design/references/vi-nebula-console.md +151 -0
  92. package/skills/ui-design/references/vi-obsidian-optics.md +139 -0
  93. package/skills/ui-design/references/vi-obsidian-xui.md +143 -0
  94. package/skills/ui-design/references/vi-precision-horizon-dark.md +117 -0
  95. package/skills/ui-design/references/vi-precision-horizon.md +215 -0
  96. package/skills/ui-design/references/vi-solar-relay.md +130 -0
  97. package/skills/ui-design/references/vi-template-workflow.md +48 -0
  98. package/skills/ui-design/references/vi-verdant-grid.md +141 -0
  99. package/skills/ui-design/references/vi-violet-meridian.md +172 -0
  100. package/skills/ui-design/scripts/__pycache__/assemble_material.cpython-314.pyc +0 -0
  101. package/skills/ui-design/scripts/__pycache__/assemble_material_test.cpython-314.pyc +0 -0
  102. package/skills/ui-design/scripts/__pycache__/chart_sanity_check.cpython-314.pyc +0 -0
  103. package/skills/ui-design/scripts/__pycache__/check_language_policy.cpython-314.pyc +0 -0
  104. package/skills/ui-design/scripts/__pycache__/check_ui_artifact.cpython-314.pyc +0 -0
  105. package/skills/ui-design/scripts/__pycache__/check_ui_artifact_pptx_test.cpython-314.pyc +0 -0
  106. package/skills/ui-design/scripts/__pycache__/deck_pages_check.cpython-314.pyc +0 -0
  107. package/skills/ui-design/scripts/__pycache__/deck_pages_check_test.cpython-314.pyc +0 -0
  108. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check.cpython-314.pyc +0 -0
  109. package/skills/ui-design/scripts/__pycache__/deck_visual_style_check_test.cpython-314.pyc +0 -0
  110. package/skills/ui-design/scripts/__pycache__/design_budget_check.cpython-314.pyc +0 -0
  111. package/skills/ui-design/scripts/__pycache__/design_budget_check_test.cpython-314.pyc +0 -0
  112. package/skills/ui-design/scripts/__pycache__/design_repair_hints.cpython-314.pyc +0 -0
  113. package/skills/ui-design/scripts/__pycache__/design_repair_hints_test.cpython-314.pyc +0 -0
  114. package/skills/ui-design/scripts/__pycache__/design_style_check.cpython-314.pyc +0 -0
  115. package/skills/ui-design/scripts/__pycache__/design_style_check_test.cpython-314.pyc +0 -0
  116. package/skills/ui-design/scripts/__pycache__/draft_coverage_check.cpython-314.pyc +0 -0
  117. package/skills/ui-design/scripts/__pycache__/extract_source_assets.cpython-314.pyc +0 -0
  118. package/skills/ui-design/scripts/__pycache__/extract_source_assets_test.cpython-314.pyc +0 -0
  119. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect.cpython-314.pyc +0 -0
  120. package/skills/ui-design/scripts/__pycache__/generated_image_review_collect_test.cpython-314.pyc +0 -0
  121. package/skills/ui-design/scripts/__pycache__/image_plan_lint.cpython-314.pyc +0 -0
  122. package/skills/ui-design/scripts/__pycache__/image_plan_lint_test.cpython-314.pyc +0 -0
  123. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite.cpython-314.pyc +0 -0
  124. package/skills/ui-design/scripts/__pycache__/image_prompt_rewrite_test.cpython-314.pyc +0 -0
  125. package/skills/ui-design/scripts/__pycache__/layout_balance.cpython-314.pyc +0 -0
  126. package/skills/ui-design/scripts/__pycache__/layout_balance_test.cpython-314.pyc +0 -0
  127. package/skills/ui-design/scripts/__pycache__/normalize_design_json.cpython-314.pyc +0 -0
  128. package/skills/ui-design/scripts/__pycache__/normalize_design_json_test.cpython-314.pyc +0 -0
  129. package/skills/ui-design/scripts/__pycache__/plan_source_layout.cpython-314.pyc +0 -0
  130. package/skills/ui-design/scripts/__pycache__/plan_source_layout_test.cpython-314.pyc +0 -0
  131. package/skills/ui-design/scripts/__pycache__/resolve_source_hint.cpython-314.pyc +0 -0
  132. package/skills/ui-design/scripts/__pycache__/resolve_source_hint_test.cpython-314.pyc +0 -0
  133. package/skills/ui-design/scripts/__pycache__/resolve_source_input.cpython-314.pyc +0 -0
  134. package/skills/ui-design/scripts/__pycache__/resolve_source_input_test.cpython-314.pyc +0 -0
  135. package/skills/ui-design/scripts/__pycache__/scaffold_ui_artifact.cpython-314.pyc +0 -0
  136. package/skills/ui-design/scripts/__pycache__/source_usage_check.cpython-314.pyc +0 -0
  137. package/skills/ui-design/scripts/__pycache__/source_usage_check_test.cpython-314.pyc +0 -0
  138. package/skills/ui-design/scripts/__pycache__/text_readability_pixel.cpython-314.pyc +0 -0
  139. package/skills/ui-design/scripts/__pycache__/visual_quality_gate.cpython-314.pyc +0 -0
  140. package/skills/ui-design/scripts/__pycache__/visual_quality_gate_test.cpython-314.pyc +0 -0
  141. package/skills/ui-design/scripts/assemble_material.py +217 -0
  142. package/skills/ui-design/scripts/assemble_material_test.py +149 -0
  143. package/skills/ui-design/scripts/browser_check_ui_artifact.mjs +221 -0
  144. package/skills/ui-design/scripts/chart_sanity_check.py +261 -0
  145. package/skills/ui-design/scripts/chart_sanity_check_test.py +165 -0
  146. package/skills/ui-design/scripts/check_language_policy.py +351 -0
  147. package/skills/ui-design/scripts/check_ui_artifact.py +435 -0
  148. package/skills/ui-design/scripts/check_ui_artifact_pptx_test.py +251 -0
  149. package/skills/ui-design/scripts/deck_pages_check.py +158 -0
  150. package/skills/ui-design/scripts/deck_pages_check_test.py +134 -0
  151. package/skills/ui-design/scripts/deck_visual_style_check.py +767 -0
  152. package/skills/ui-design/scripts/deck_visual_style_check_test.py +713 -0
  153. package/skills/ui-design/scripts/design_budget_check.py +271 -0
  154. package/skills/ui-design/scripts/design_budget_check_test.py +183 -0
  155. package/skills/ui-design/scripts/design_repair_hints.py +162 -0
  156. package/skills/ui-design/scripts/design_repair_hints_test.py +54 -0
  157. package/skills/ui-design/scripts/design_style_check.py +397 -0
  158. package/skills/ui-design/scripts/design_style_check_test.py +233 -0
  159. package/skills/ui-design/scripts/draft_coverage_check.py +184 -0
  160. package/skills/ui-design/scripts/draft_coverage_check_test.py +131 -0
  161. package/skills/ui-design/scripts/export_pptx_from_html.mjs +2367 -0
  162. package/skills/ui-design/scripts/extract_source_assets.py +607 -0
  163. package/skills/ui-design/scripts/extract_source_assets_test.py +256 -0
  164. package/skills/ui-design/scripts/generated_image_review_collect.py +206 -0
  165. package/skills/ui-design/scripts/generated_image_review_collect_test.py +91 -0
  166. package/skills/ui-design/scripts/image_plan_lint.py +571 -0
  167. package/skills/ui-design/scripts/image_plan_lint_test.py +515 -0
  168. package/skills/ui-design/scripts/image_prompt_rewrite.py +713 -0
  169. package/skills/ui-design/scripts/image_prompt_rewrite_test.py +966 -0
  170. package/skills/ui-design/scripts/layout_balance.py +881 -0
  171. package/skills/ui-design/scripts/layout_balance_test.py +927 -0
  172. package/skills/ui-design/scripts/normalize_design_json.py +272 -0
  173. package/skills/ui-design/scripts/normalize_design_json_test.py +119 -0
  174. package/skills/ui-design/scripts/plan_source_layout.py +1450 -0
  175. package/skills/ui-design/scripts/plan_source_layout_test.py +1136 -0
  176. package/skills/ui-design/scripts/resolve_source_input.py +270 -0
  177. package/skills/ui-design/scripts/resolve_source_input_test.py +98 -0
  178. package/skills/ui-design/scripts/scaffold_ui_artifact.py +377 -0
  179. package/skills/ui-design/scripts/source_usage_check.py +581 -0
  180. package/skills/ui-design/scripts/source_usage_check_test.py +427 -0
  181. package/skills/ui-design/scripts/text_readability_pixel.py +340 -0
  182. package/skills/ui-design/scripts/text_readability_pixel_test.py +219 -0
  183. package/skills/ui-design/scripts/visual_quality_gate.py +301 -0
  184. package/skills/ui-design/scripts/visual_quality_gate_test.py +233 -0
  185. package/skills/ui-design/tests/e2e/prompts/deck.md +19 -0
  186. package/skills/ui-design/tests/e2e/prompts/webpage.md +17 -0
  187. package/skills/ui-design/tests/e2e/render_check.mjs +101 -0
  188. package/skills/ui-design/tests/e2e/rubric.json +54 -0
  189. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/browser-check.json +49 -0
  190. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/language-check.txt +12 -0
  191. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/checks/static-check.txt +15 -0
  192. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/outputs/index.html +837 -0
  193. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/prompt.md +12 -0
  194. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/review.md +21 -0
  195. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/desktop.png +0 -0
  196. package/skills/ui-design/tests/e2e/runs/aegis-vector-preview-1/screenshots/mobile.png +0 -0
  197. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/homepage.html +373 -0
  198. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/outputs/platform.html +378 -0
  199. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/prompt.md +13 -0
  200. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/review.md +26 -0
  201. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  202. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  203. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  204. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  205. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  206. package/skills/ui-design/tests/e2e/runs/amber-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  207. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/checks/static-check.txt +2 -0
  208. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/outputs/index.html +656 -0
  209. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/prompt.md +12 -0
  210. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-1/render_check.mjs +67 -0
  211. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/checks/static-check.txt +2 -0
  212. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/outputs/index.html +587 -0
  213. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/prompt.md +12 -0
  214. package/skills/ui-design/tests/e2e/runs/aqua-glass-iteration-2/review.md +36 -0
  215. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/browser-check.json +49 -0
  216. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/language-check.txt +8 -0
  217. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/checks/static-check.txt +16 -0
  218. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/outputs/index.html +739 -0
  219. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/prompt.md +11 -0
  220. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/review.md +22 -0
  221. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/desktop.png +0 -0
  222. package/skills/ui-design/tests/e2e/runs/aqua-glass-volcano-refresh-1/screenshots/mobile.png +0 -0
  223. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/checks/browser-check.json +52 -0
  224. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/outputs/index.html +641 -0
  225. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/desktop.png +0 -0
  226. package/skills/ui-design/tests/e2e/runs/comfort-vision-preview-1/screenshots/mobile.png +0 -0
  227. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/outputs/index.html +467 -0
  228. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/browser-check.json +49 -0
  229. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/desktop.png +0 -0
  230. package/skills/ui-design/tests/e2e/runs/ember-prism-red-preview-1/screenshots/mobile.png +0 -0
  231. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/homepage.html +373 -0
  232. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/outputs/platform.html +378 -0
  233. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/prompt.md +13 -0
  234. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/review.md +26 -0
  235. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/browser-check.json +49 -0
  236. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/desktop.png +0 -0
  237. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/homepage/mobile.png +0 -0
  238. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/browser-check.json +49 -0
  239. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/desktop.png +0 -0
  240. package/skills/ui-design/tests/e2e/runs/ember-prism-screenshot-check-1/screenshots/platform/mobile.png +0 -0
  241. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/browser-check.json +49 -0
  242. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/language-check.txt +4 -0
  243. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/checks/static-check.txt +2 -0
  244. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/execution_notes.md +10 -0
  245. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/outputs/index.html +587 -0
  246. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/prompt.md +14 -0
  247. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/review.md +13 -0
  248. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/desktop.png +0 -0
  249. package/skills/ui-design/tests/e2e/runs/empty-agent-protocol-check/screenshots/mobile.png +0 -0
  250. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/browser-check.json +24 -0
  251. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/checks/static-check.txt +2 -0
  252. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/execution_notes.md +21 -0
  253. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/outputs/deck.html +451 -0
  254. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-01.png +0 -0
  255. package/skills/ui-design/tests/e2e/runs/iteration-1/deck/screenshots/slide-02.png +0 -0
  256. package/skills/ui-design/tests/e2e/runs/iteration-1/review.md +79 -0
  257. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/browser-check.json +26 -0
  258. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/checks/static-check.txt +2 -0
  259. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/execution_notes.md +20 -0
  260. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/outputs/index.html +534 -0
  261. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/desktop.png +0 -0
  262. package/skills/ui-design/tests/e2e/runs/iteration-1/webpage/screenshots/mobile.png +0 -0
  263. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/browser-check.json +64 -0
  264. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/language-check.txt +7 -0
  265. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/quick-validate.txt +1 -0
  266. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-console.txt +2 -0
  267. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/checks/static-homepage.txt +2 -0
  268. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/console.html +600 -0
  269. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/outputs/homepage.html +556 -0
  270. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/prompt.md +29 -0
  271. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/review.md +57 -0
  272. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-desktop.png +0 -0
  273. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/console-mobile.png +0 -0
  274. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-desktop.png +0 -0
  275. package/skills/ui-design/tests/e2e/runs/nebula-console-iteration-1/screenshots/homepage-mobile.png +0 -0
  276. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/browser-check.json +33 -0
  277. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/language-check.txt +6 -0
  278. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/quick-validate.txt +1 -0
  279. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/checks/static-check.txt +2 -0
  280. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/outputs/index.html +682 -0
  281. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/prompt.md +13 -0
  282. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/review.md +59 -0
  283. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/desktop.png +0 -0
  284. package/skills/ui-design/tests/e2e/runs/obsidian-optics-iteration-1/screenshots/mobile.png +0 -0
  285. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-01.html +466 -0
  286. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/outputs/page-02.html +433 -0
  287. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/prompt.md +13 -0
  288. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/browser-check.json +49 -0
  289. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/desktop.png +0 -0
  290. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-01/mobile.png +0 -0
  291. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/browser-check.json +49 -0
  292. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/desktop.png +0 -0
  293. package/skills/ui-design/tests/e2e/runs/obsidian-xui-screenshot-check-1/screenshots/page-02/mobile.png +0 -0
  294. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/browser-check.json +49 -0
  295. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/language-check.txt +3 -0
  296. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/checks/static-check.txt +2 -0
  297. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/execution_notes.md +24 -0
  298. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/outputs/index.html +723 -0
  299. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/prompt.md +15 -0
  300. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/review.md +26 -0
  301. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/desktop.png +0 -0
  302. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-1/screenshots/mobile.png +0 -0
  303. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-dark.json +49 -0
  304. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/browser-light.json +49 -0
  305. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/language-check.txt +11 -0
  306. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-dark.txt +2 -0
  307. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/checks/static-light.txt +3 -0
  308. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/execution_notes.md +23 -0
  309. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/dark.html +392 -0
  310. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/outputs/light.html +476 -0
  311. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/prompt.md +23 -0
  312. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/review.md +41 -0
  313. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/desktop.png +0 -0
  314. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/dark/mobile.png +0 -0
  315. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/desktop.png +0 -0
  316. package/skills/ui-design/tests/e2e/runs/precision-horizon-iteration-2/screenshots/light/mobile.png +0 -0
  317. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/browser-check.json +54 -0
  318. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/language-check.txt +5 -0
  319. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/quick-validate.txt +1 -0
  320. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/checks/static-check.txt +2 -0
  321. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/outputs/index.html +765 -0
  322. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/prompt.md +15 -0
  323. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/review.md +59 -0
  324. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/desktop.png +0 -0
  325. package/skills/ui-design/tests/e2e/runs/solar-relay-iteration-1/screenshots/mobile.png +0 -0
  326. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/outputs/index.html +508 -0
  327. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/browser-check.json +49 -0
  328. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/desktop.png +0 -0
  329. package/skills/ui-design/tests/e2e/runs/verdant-grid-preview-1/screenshots/mobile.png +0 -0
  330. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/browser-check.json +54 -0
  331. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/language-check.txt +6 -0
  332. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/quick-validate.txt +1 -0
  333. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/checks/static-check.txt +2 -0
  334. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/outputs/index.html +788 -0
  335. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/prompt.md +14 -0
  336. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/review.md +60 -0
  337. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/desktop.png +0 -0
  338. package/skills/ui-design/tests/e2e/runs/violet-meridian-iteration-1/screenshots/mobile.png +0 -0
  339. package/skills/video-compose/SKILL.md +8 -1
  340. package/skills/web-crawler/SKILL.md +70 -0
  341. package/skills/web-crawler/scripts/crawl.mjs +538 -0
  342. package/skills/web-crawler/scripts/crawl.test.mjs +204 -0
  343. package/skills/xlsx/SKILL.md +12 -0
  344. package/skills/xlsx/scripts/opc_check.py +253 -0
  345. package/src/advisorState.js +22 -0
  346. package/src/askBridge.js +97 -3
  347. package/src/bootstrap/workingDir.js +12 -1
  348. package/src/cli/args.js +17 -1
  349. package/src/cli/configMenu.js +236 -0
  350. package/src/cli/configWizard.js +7 -5
  351. package/src/cli/print.js +37 -4
  352. package/src/cli/streamJson.js +77 -24
  353. package/src/cli/wizards/advisorWizard.js +147 -0
  354. package/src/cli/wizards/fieldForm.js +149 -0
  355. package/src/cli/wizards/imageWizard.js +60 -0
  356. package/src/cli/wizards/openRouterWizard.js +23 -0
  357. package/src/cli/wizards/providerPresets.js +95 -0
  358. package/src/cli/wizards/providerWizard.js +219 -0
  359. package/src/cli/wizards/searchWizard.js +23 -0
  360. package/src/cli/wizards/ttsWizard.js +34 -0
  361. package/src/config/configFile.js +441 -83
  362. package/src/config/configService.js +446 -0
  363. package/src/config.js +24 -64
  364. package/src/context/agentHome.js +9 -0
  365. package/src/context/archive.js +7 -2
  366. package/src/context/compact.js +8 -5
  367. package/src/context/contextLock.js +81 -0
  368. package/src/context/persistContext.js +30 -11
  369. package/src/context/reactiveCompact.js +4 -2
  370. package/src/context/recentDirs.js +21 -61
  371. package/src/context/sessionPath.js +97 -23
  372. package/src/context/sessionRegistry.js +303 -24
  373. package/src/context/tokenCounter.js +18 -0
  374. package/src/context/workspaceIndex.js +731 -0
  375. package/src/infra/verification.js +28 -15
  376. package/src/llm/client.js +394 -159
  377. package/src/llm/providers/anthropicFormat.js +241 -0
  378. package/src/llm/providers/openaiFormat.js +190 -0
  379. package/src/loop.js +219 -32
  380. package/src/main.js +61 -11
  381. package/src/messageQueue.js +97 -0
  382. package/src/multimodal.js +203 -0
  383. package/src/plugin-sdk.js +15 -0
  384. package/src/plugins/commandRouter.js +3 -0
  385. package/src/plugins/hookEngine.js +5 -1
  386. package/src/plugins/pluginLifecycle.js +28 -0
  387. package/src/plugins/pluginLoader.js +2 -1
  388. package/src/plugins/pluginRunner.js +49 -21
  389. package/src/plugins/transcript.js +4 -3
  390. package/src/prompts/pluginList.js +5 -5
  391. package/src/prompts/projectInstructions.js +3 -3
  392. package/src/prompts/promptAssembly.js +133 -144
  393. package/src/prompts/skillList.js +12 -22
  394. package/src/prompts/system.js +0 -1
  395. package/src/session/runAgentTurn.js +70 -0
  396. package/src/session/runTurn.js +41 -6
  397. package/src/toolGate.js +17 -0
  398. package/src/tools/advisor/advisor.js +379 -0
  399. package/src/tools/agent/agent.js +555 -0
  400. package/src/tools/agent/agentTypes.js +59 -0
  401. package/src/tools/agent/coordinator.js +93 -0
  402. package/src/tools/agent/worktree.js +270 -0
  403. package/src/tools/ask/ask.js +7 -2
  404. package/src/tools/bash/bash.js +112 -18
  405. package/src/tools/download/download.js +144 -0
  406. package/src/tools/edit/edit.js +1 -0
  407. package/src/tools/edit/multi-edit.js +1 -0
  408. package/src/tools/genimage/genimage.js +242 -0
  409. package/src/tools/genspeech/genspeech.js +67 -0
  410. package/src/tools/glob/glob.js +1 -0
  411. package/src/tools/grep/grep.js +3 -0
  412. package/src/tools/grep/rgPath.js +2 -0
  413. package/src/tools/index.js +25 -2
  414. package/src/tools/read/read.js +1 -0
  415. package/src/tools/shared/killTree.js +52 -0
  416. package/src/tools/toolProgressBridge.js +48 -0
  417. package/src/tools/webbrowser/agentBrowserPath.js +258 -0
  418. package/src/tools/webbrowser/browser.js +59 -42
  419. package/src/tools/webbrowser/browserControl.js +118 -0
  420. package/src/tools/webbrowser/browserDaemon.js +97 -0
  421. package/src/tools/webbrowser/browserService.js +27 -0
  422. package/src/tools/webbrowser/browserStream.js +119 -0
  423. package/src/tools/webbrowser/webbrowser.js +699 -130
  424. package/src/tools/webfetch/webfetch.js +1 -0
  425. package/src/tools/websearch/websearch.js +134 -51
  426. package/src/tools/write/write.js +1 -0
  427. package/src/ui/App.js +249 -22
  428. package/src/ui/AskQuestion.js +29 -10
  429. package/src/ui/InputBox.js +104 -12
  430. package/src/ui/LiveArea.js +112 -19
  431. package/src/ui/MessageList.js +100 -14
  432. package/src/ui/ModelPicker.js +56 -0
  433. package/src/ui/QueuedMessagesBanner.js +14 -0
  434. package/src/ui/Root.js +18 -10
  435. package/src/ui/SessionPicker.js +102 -0
  436. package/src/ui/StatusLine.js +13 -4
  437. package/src/ui/WorkspacePicker.js +55 -0
  438. package/src/ui/figures.js +44 -0
  439. package/src/ui/hooks/askSlot.js +82 -0
  440. package/src/ui/hooks/useChat.js +358 -64
  441. package/src/ui/hooks/usePasteHandler.js +11 -1
  442. package/src/ui/hooks/useTextBuffer.js +1 -3
  443. package/src/ui/liveViewport.js +5 -1
  444. package/src/ui/markdown.js +206 -0
  445. package/src/ui/textBuffer.js +0 -7
  446. package/src/ui/theme.js +32 -0
  447. package/src/utils/debugLog.js +0 -4
  448. package/src/utils/resourcePaths.js +2 -2
  449. package/src/utils/stripThink.js +50 -0
  450. package/src/web/browserBridge.js +447 -0
  451. package/src/web/protocol.js +19 -0
  452. package/workflows/adaptive-research.yaml +13 -5
  453. package/workflows/book-review-short.yaml +36 -10
  454. package/workflows/e2e-write-greet.yaml +6 -2
  455. package/workflows/illustrate.yaml +227 -0
  456. package/workflows/plan.yaml +460 -179
  457. package/workflows/ppt.yaml +1659 -0
  458. package/workflows/ralph-loop.yaml +199 -0
  459. package/workflows/schema.json +81 -23
  460. package/workflows/webppt.yaml +496 -0
  461. package/workflows/word.yaml +710 -0
  462. package/workflows/youtube-shorts.yaml +46 -15
  463. package/MINIMAL-TRACE.md +0 -77
  464. package/plugins/HOW-TO-WRITE-A-PLUGIN.md +0 -186
  465. package/plugins/ralph-wiggum/.claude-plugin/plugin.json +0 -9
  466. package/plugins/ralph-wiggum/README.md +0 -197
  467. package/plugins/ralph-wiggum/commands/cancel-ralph.md +0 -18
  468. package/plugins/ralph-wiggum/commands/help.md +0 -137
  469. package/plugins/ralph-wiggum/commands/ralph-loop.md +0 -85
  470. package/plugins/ralph-wiggum/hooks/hooks.json +0 -15
  471. package/plugins/ralph-wiggum/hooks/stop-hook.sh +0 -191
  472. package/plugins/ralph-wiggum/plugin.js +0 -280
  473. package/plugins/ralph-wiggum/src/goalState.js +0 -258
  474. package/plugins/ralph-wiggum/src/sentinels.js +0 -76
  475. package/plugins/ralph-wiggum/src/stopHookRunner.js +0 -104
  476. package/plugins/ralph-wiggum/src/verificationGate.js +0 -202
  477. package/plugins/workflow-runner/src/miniReAct.js +0 -223
  478. package/plugins/workflow-runner/src/voteHelpers.js +0 -110
  479. package/skills/config/SKILL.md +0 -123
  480. package/skills/frontend-design/LICENSE.txt +0 -177
  481. package/skills/frontend-design/SKILL.md +0 -42
  482. package/src/ui/ProgressPanel.js +0 -54
  483. package/src/ui/StreamingBlock.js +0 -11
  484. package/src/ui/ToolStatus.js +0 -11
@@ -0,0 +1,446 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { readMinimalAgentConfig, updateMinimalAgentConfig, defaultMaxTokens, DEFAULT_SUBAGENTS_CONFIG, DEFAULT_BROWSER_CONFIG, } from './configFile.js';
3
+ const DEFAULT_CONTEXT_WINDOW = 128_000;
4
+ function positiveInt(value, fallback) {
5
+ const parsed = Number.parseInt(value ?? '', 10);
6
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
7
+ }
8
+ /**
9
+ * 纯 env 自举路径(无 JSON 配置文件时的兜底)。只产出 OpenAI 格式的模型——Anthropic 格式
10
+ * 只能通过 JSON 配置(Desktop/CLI)新增,这是刻意的范围限制,不是遗漏。
11
+ *
12
+ * 顾问不再有独立的 env 自举路径(ADVISOR_AGENT_* 家族整体移除)——顾问配置以后只能通过
13
+ * JSON 配置的 `advisor.modelId` 引用模型列表条目,没有 JSON 时顾问就是未配置。
14
+ *
15
+ * ⚠️ 这里派生的模型 id/createdAt/updatedAt 必须是固定字面量,不能用 randomUUID()/Date.now()——
16
+ * configFromEnv() 在每次 reload() 都会重新构造,reload() 靠 JSON.stringify 差异判断 revision
17
+ * 是否变化;任何一次性值都会让 env 模式下每次 reload 的 revision 都在跳,进而每轮对话都触发
18
+ * 桌面端 resetProvider()。
19
+ */
20
+ function configFromEnv() {
21
+ const baseURL = process.env.MINIMAL_AGENT_BASE_URL;
22
+ const apiKey = process.env.MINIMAL_AGENT_API_KEY;
23
+ const model = process.env.MINIMAL_AGENT_MODEL;
24
+ if (!baseURL || !apiKey || !model)
25
+ return null;
26
+ const tools = {};
27
+ if (process.env.TAVILY_API_KEY)
28
+ tools.webSearch = { provider: 'tavily', apiKey: process.env.TAVILY_API_KEY };
29
+ if (process.env.OPENAI_IMAGE_BASE_URL && process.env.OPENAI_IMAGE_API_KEY && process.env.OPENAI_IMAGE_MODEL) {
30
+ tools.imageGeneration = {
31
+ provider: process.env.OPENAI_IMAGE_PROVIDER ?? 'openai',
32
+ baseURL: process.env.OPENAI_IMAGE_BASE_URL,
33
+ apiKey: process.env.OPENAI_IMAGE_API_KEY,
34
+ model: process.env.OPENAI_IMAGE_MODEL,
35
+ protocol: process.env.OPENAI_IMAGE_PROTOCOL === 'chat' || process.env.OPENAI_IMAGE_PROTOCOL === 'images'
36
+ ? process.env.OPENAI_IMAGE_PROTOCOL
37
+ : 'auto',
38
+ };
39
+ }
40
+ if (process.env.OPENROUTER_API_KEY)
41
+ tools.openRouter = { apiKey: process.env.OPENROUTER_API_KEY };
42
+ if (process.env.MINIMAX_API_KEY && process.env.MINIMAX_GROUP_ID) {
43
+ tools.textToSpeech = { provider: 'minimax', apiKey: process.env.MINIMAX_API_KEY, groupId: process.env.MINIMAX_GROUP_ID };
44
+ }
45
+ const compactRatio = Number(process.env.MINIMAL_AGENT_COMPACT_RATIO);
46
+ const name = process.env.MINIMAL_AGENT_PROVIDER ?? 'env';
47
+ const contextWindow = positiveInt(process.env.MINIMAL_AGENT_CONTEXT_WINDOW, DEFAULT_CONTEXT_WINDOW);
48
+ const envModel = {
49
+ id: 'env-base',
50
+ apiFormat: 'openai',
51
+ ...(name !== 'env' ? { displayName: name } : {}),
52
+ baseURL,
53
+ apiKey,
54
+ model,
55
+ contextWindow,
56
+ maxTokens: positiveInt(process.env.MINIMAL_AGENT_MAX_TOKENS, defaultMaxTokens(contextWindow)),
57
+ enabled: true,
58
+ createdAt: 0,
59
+ updatedAt: 0,
60
+ };
61
+ return {
62
+ version: 4,
63
+ models: [envModel],
64
+ baseModelId: envModel.id,
65
+ ...(compactRatio > 0 && compactRatio <= 1 ? { compactRatio } : {}),
66
+ subagents: {
67
+ ...DEFAULT_SUBAGENTS_CONFIG,
68
+ maxCallsPerTurn: positiveInt(process.env.AGENT_TOOL_MAX_CALLS_PER_TURN, 6),
69
+ maxConcurrent: positiveInt(process.env.AGENT_TOOL_MAX_CONCURRENT, 3),
70
+ timeoutMs: positiveInt(process.env.AGENT_TOOL_TIMEOUT_MS, 1_800_000),
71
+ exploreMaxTurns: positiveInt(process.env.AGENT_TOOL_EXPLORE_MAX_TURNS, 50),
72
+ generalMaxTurns: positiveInt(process.env.AGENT_TOOL_GENERAL_MAX_TURNS, 100),
73
+ },
74
+ browser: DEFAULT_BROWSER_CONFIG,
75
+ tools,
76
+ savedAt: 0,
77
+ };
78
+ }
79
+ function resolveProvider(entry, compactRatio) {
80
+ return {
81
+ name: entry.displayName ?? entry.model,
82
+ baseURL: entry.baseURL,
83
+ apiKey: entry.apiKey,
84
+ model: entry.model,
85
+ contextWindow: entry.contextWindow,
86
+ maxTokens: entry.maxTokens ?? defaultMaxTokens(entry.contextWindow),
87
+ apiFormat: entry.apiFormat,
88
+ ...(compactRatio !== undefined ? { compactRatio } : {}),
89
+ };
90
+ }
91
+ function mergeRecord(base, patch) {
92
+ const out = { ...(base ?? {}) };
93
+ for (const [key, value] of Object.entries(patch)) {
94
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
95
+ out[key] = mergeRecord(out[key] && typeof out[key] === 'object' && !Array.isArray(out[key]) ? out[key] : undefined, value);
96
+ }
97
+ else if (value !== undefined)
98
+ out[key] = value;
99
+ }
100
+ return out;
101
+ }
102
+ function hasEntries(value) {
103
+ return !!value && Object.keys(value).length > 0;
104
+ }
105
+ function applyModelsPatch(models, ops) {
106
+ let out = [...models];
107
+ const now = Date.now();
108
+ for (const item of ops) {
109
+ if (item.op === 'add') {
110
+ const m = item.model;
111
+ const apiFormat = m.apiFormat === 'anthropic' ? 'anthropic' : 'openai';
112
+ const baseURL = m.baseURL?.trim();
113
+ const modelId = m.model?.trim();
114
+ const apiKey = m.apiKey?.trim();
115
+ if (!baseURL || !modelId || !apiKey)
116
+ throw new Error('模型缺少 Base URL、模型 ID 或 API Key');
117
+ out.push({
118
+ id: randomUUID(),
119
+ apiFormat,
120
+ ...(m.displayName?.trim() ? { displayName: m.displayName.trim() } : {}),
121
+ baseURL,
122
+ apiKey,
123
+ model: modelId,
124
+ contextWindow: m.contextWindow && m.contextWindow > 0 ? m.contextWindow : DEFAULT_CONTEXT_WINDOW,
125
+ ...(m.maxTokens ? { maxTokens: m.maxTokens } : {}),
126
+ supportsVision: m.supportsVision !== false,
127
+ enabled: m.enabled ?? true,
128
+ createdAt: now,
129
+ updatedAt: now,
130
+ });
131
+ }
132
+ else if (item.op === 'update') {
133
+ const index = out.findIndex((entry) => entry.id === item.id);
134
+ if (index === -1)
135
+ throw new Error('要修改的模型不存在(可能已被删除)');
136
+ const existing = out[index];
137
+ const patch = item.patch;
138
+ const next = {
139
+ ...existing,
140
+ ...(patch.apiFormat ? { apiFormat: patch.apiFormat === 'anthropic' ? 'anthropic' : 'openai' } : {}),
141
+ ...(patch.displayName !== undefined ? { displayName: patch.displayName.trim() || undefined } : {}),
142
+ ...(patch.baseURL?.trim() ? { baseURL: patch.baseURL.trim() } : {}),
143
+ ...(patch.model?.trim() ? { model: patch.model.trim() } : {}),
144
+ ...(patch.contextWindow ? { contextWindow: patch.contextWindow } : {}),
145
+ ...(patch.maxTokens !== undefined ? { maxTokens: patch.maxTokens || undefined } : {}),
146
+ ...(patch.supportsVision !== undefined ? { supportsVision: patch.supportsVision !== false } : {}),
147
+ ...(patch.enabled !== undefined ? { enabled: patch.enabled } : {}),
148
+ updatedAt: now,
149
+ };
150
+ if (patch.apiKey?.trim())
151
+ next.apiKey = patch.apiKey.trim();
152
+ if (patch.clearApiKey)
153
+ next.apiKey = '';
154
+ out[index] = next;
155
+ }
156
+ else {
157
+ out = out.filter((entry) => entry.id !== item.id);
158
+ }
159
+ }
160
+ return out;
161
+ }
162
+ function applyConfigPatch(base, patch) {
163
+ const wasEmpty = (base?.models.length ?? 0) === 0 && (base?.baseModelId ?? null) === null;
164
+ let models = base?.models ?? [];
165
+ let baseModelId = base?.baseModelId ?? null;
166
+ if (patch.models?.length)
167
+ models = applyModelsPatch(models, patch.models);
168
+ // 全新安装加第一个模型时自动设为基座——否则用户加完模型还是没法聊天。
169
+ if (wasEmpty && baseModelId === null && models.length > 0)
170
+ baseModelId = models[0].id;
171
+ if (patch.baseModelId !== undefined) {
172
+ if (patch.baseModelId !== null && !models.some((entry) => entry.id === patch.baseModelId)) {
173
+ throw new Error('要设为基座的模型不存在');
174
+ }
175
+ baseModelId = patch.baseModelId;
176
+ }
177
+ // 不变量:禁止删除/禁用当前基座模型指向的条目(先设别的为基座)。
178
+ if (baseModelId !== null) {
179
+ const baseEntry = models.find((entry) => entry.id === baseModelId);
180
+ if (!baseEntry)
181
+ throw new Error('当前基座模型已被删除,请先设置其他模型为基座');
182
+ if (baseEntry.enabled === false)
183
+ throw new Error('不能禁用当前基座模型,请先设置其他模型为基座');
184
+ }
185
+ let advisor = base?.advisor;
186
+ if (patch.advisor) {
187
+ if (patch.advisor.clear) {
188
+ advisor = undefined;
189
+ }
190
+ else {
191
+ advisor = {
192
+ enabled: patch.advisor.enabled ?? advisor?.enabled ?? true,
193
+ mode: patch.advisor.mode ?? advisor?.mode ?? 'auto',
194
+ modelId: patch.advisor.modelId !== undefined ? patch.advisor.modelId : (advisor?.modelId ?? null),
195
+ contextPolicy: 'full-relevant',
196
+ shareSensitive: patch.advisor.shareSensitive ?? advisor?.shareSensitive ?? false,
197
+ inputTokenBudget: Math.min(128_000, Math.max(8_000, patch.advisor.inputTokenBudget ?? advisor?.inputTokenBudget ?? 128_000)),
198
+ timeoutMs: patch.advisor.timeoutMs ?? advisor?.timeoutMs ?? 180_000,
199
+ };
200
+ }
201
+ }
202
+ // 顾问引用的模型若已被删除或禁用,级联置空(软失联——解析层已把 null/悬空 id 当
203
+ // 「未配置」优雅处理,不留死链接;这与基座模型的硬阻止是刻意的不对称:顾问本来就是
204
+ // 可降级的第二意见,不像基座那样「没有就整个应用跑不起来」)。
205
+ if (advisor?.modelId) {
206
+ const referenced = models.find((entry) => entry.id === advisor.modelId);
207
+ if (!referenced || referenced.enabled === false)
208
+ advisor = { ...advisor, modelId: null };
209
+ }
210
+ const compactRatio = patch.compactRatio !== undefined
211
+ ? (patch.compactRatio > 0 && patch.compactRatio <= 1 ? patch.compactRatio : undefined)
212
+ : base?.compactRatio;
213
+ const tools = { ...(base?.tools ?? {}) };
214
+ const toolPatch = patch.tools;
215
+ if (toolPatch?.webSearch) {
216
+ if (toolPatch.webSearch.clear)
217
+ delete tools.webSearch;
218
+ else
219
+ tools.webSearch = mergeRecord(tools.webSearch, { provider: 'tavily', ...toolPatch.webSearch });
220
+ }
221
+ if (toolPatch?.imageGeneration) {
222
+ if (toolPatch.imageGeneration.clear)
223
+ delete tools.imageGeneration;
224
+ else {
225
+ const next = mergeRecord(tools.imageGeneration, toolPatch.imageGeneration);
226
+ if (toolPatch.imageGeneration.clearApiKey)
227
+ next.apiKey = '';
228
+ delete next.clear;
229
+ delete next.clearApiKey;
230
+ if (!next.baseURL || !next.apiKey || !next.model)
231
+ throw new Error('图片生成缺少 Base URL、API Key 或模型名称');
232
+ tools.imageGeneration = next;
233
+ }
234
+ }
235
+ if (toolPatch?.openRouter) {
236
+ if (toolPatch.openRouter.clear)
237
+ delete tools.openRouter;
238
+ else
239
+ tools.openRouter = mergeRecord(tools.openRouter, toolPatch.openRouter);
240
+ }
241
+ if (toolPatch?.textToSpeech) {
242
+ if (toolPatch.textToSpeech.clear)
243
+ delete tools.textToSpeech;
244
+ else {
245
+ const next = mergeRecord(tools.textToSpeech, { provider: 'minimax', ...toolPatch.textToSpeech });
246
+ if (toolPatch.textToSpeech.clearApiKey)
247
+ next.apiKey = '';
248
+ delete next.clear;
249
+ delete next.clearApiKey;
250
+ if (!next.apiKey || !next.groupId)
251
+ throw new Error('MiniMax 语音缺少 API Key 或 Group ID');
252
+ tools.textToSpeech = next;
253
+ }
254
+ }
255
+ const subagents = { ...(base?.subagents ?? DEFAULT_SUBAGENTS_CONFIG), ...(patch.subagents ?? {}) };
256
+ const browser = { ...(base?.browser ?? DEFAULT_BROWSER_CONFIG), ...(patch.browser ?? {}), profileId: 'default' };
257
+ return { models, baseModelId, ...(compactRatio !== undefined ? { compactRatio } : {}), advisor, subagents, browser, tools, drafts: base?.drafts };
258
+ }
259
+ export class ConfigService {
260
+ snapshot = { source: 'none', readOnly: false, config: null, revision: 0 };
261
+ get revision() { return this.snapshot.revision; }
262
+ async reload() {
263
+ const envOnly = process.env.MINIMAL_AGENT_CONFIG_MODE === 'env';
264
+ const json = envOnly ? null : await readMinimalAgentConfig();
265
+ const env = configFromEnv();
266
+ const next = {
267
+ source: json ? 'json' : env ? 'environment' : 'none',
268
+ readOnly: envOnly,
269
+ config: json ?? env,
270
+ };
271
+ const changed = this.snapshot.revision === 0
272
+ || this.snapshot.source !== next.source
273
+ || this.snapshot.readOnly !== next.readOnly
274
+ || JSON.stringify(this.snapshot.config) !== JSON.stringify(next.config);
275
+ this.snapshot = { ...next, revision: changed ? this.snapshot.revision + 1 : this.snapshot.revision };
276
+ return this.getSnapshot();
277
+ }
278
+ async getSnapshot() {
279
+ if (this.snapshot.revision === 0)
280
+ await this.reload();
281
+ return structuredClone(this.snapshot);
282
+ }
283
+ async provider() {
284
+ const { config } = await this.getSnapshot();
285
+ const entry = config?.models.find((m) => m.id === config.baseModelId);
286
+ if (!entry?.apiKey)
287
+ return null;
288
+ return resolveProvider(entry, config?.compactRatio);
289
+ }
290
+ async providerForModel(modelId) {
291
+ if (!modelId)
292
+ return this.provider();
293
+ const { config } = await this.getSnapshot();
294
+ const entry = config?.models.find((m) => m.id === modelId);
295
+ if (!entry?.apiKey)
296
+ return null;
297
+ return resolveProvider(entry, config?.compactRatio);
298
+ }
299
+ /** 带 apiKey 的原始列表——仅服务端内部消费(桌面端 store.ts 的 readConfig() 会脱敏成 ModelView)。 */
300
+ async models() {
301
+ return (await this.getSnapshot()).config?.models ?? [];
302
+ }
303
+ async model(id) {
304
+ return (await this.getSnapshot()).config?.models.find((entry) => entry.id === id) ?? null;
305
+ }
306
+ /**
307
+ * 保留「未启用但已配置」和「完全未配置」两种返回值的区分——advisor.ts 的两条不同报错
308
+ * 文案依赖这个区分,所以只按 modelId+对应条目 apiKey 是否非空判定可解析性,不掺 .enabled。
309
+ *
310
+ * `supportsVision` 现在是模型条目的属性(不再是顾问自己的行为字段),这里从所引用的
311
+ * ModelConfig 镜像到返回对象顶层,让 advisor.ts 的 `advisor.supportsVision` 读取方式
312
+ * 保持不变——它是解析后的便利视图,不是存储形状本身。
313
+ */
314
+ async advisor() {
315
+ // fresh reload(不用 getSnapshot 的缓存)——CLI/TUI/Desktop/Cron 共享同一份 config.json,
316
+ // 跨进程时缓存会读到过期状态(如 A 进程 /advisor off 后 B 进程仍判定已启用)。这条不变量
317
+ // 由 test/configService.test.ts 的 "advisor() always does a fresh reload" 用例锁定。
318
+ const { config } = await this.reload();
319
+ const advisor = config?.advisor;
320
+ if (!advisor?.modelId)
321
+ return null;
322
+ const entry = config.models.find((m) => m.id === advisor.modelId);
323
+ if (!entry?.apiKey)
324
+ return null;
325
+ return { ...advisor, provider: resolveProvider(entry), supportsVision: entry.supportsVision !== false };
326
+ }
327
+ async subagents() {
328
+ return (await this.reload()).config?.subagents ?? DEFAULT_SUBAGENTS_CONFIG;
329
+ }
330
+ async browser() {
331
+ return (await this.reload()).config?.browser ?? DEFAULT_BROWSER_CONFIG;
332
+ }
333
+ async tool(key) {
334
+ return (await this.reload()).config?.tools[key];
335
+ }
336
+ async toolStatus() {
337
+ const tools = (await this.reload()).config?.tools ?? {};
338
+ return {
339
+ webSearch: { state: tools.webSearch ? 'configured' : 'unconfigured', configured: !!tools.webSearch },
340
+ imageGeneration: { state: tools.imageGeneration ? 'configured' : 'unconfigured', configured: !!tools.imageGeneration },
341
+ openRouter: { state: tools.openRouter ? 'configured' : 'unconfigured', configured: !!tools.openRouter },
342
+ textToSpeech: { state: tools.textToSpeech ? 'configured' : 'unconfigured', configured: !!tools.textToSpeech },
343
+ };
344
+ }
345
+ async savePatch(patch) {
346
+ const current = await this.getSnapshot();
347
+ if (current.readOnly)
348
+ throw new Error('当前使用环境变量只读配置,不能从设置页修改');
349
+ await updateMinimalAgentConfig((latest) => applyConfigPatch(latest ?? current.config, patch));
350
+ return this.reload();
351
+ }
352
+ /** 现在只服务 tools 分区的实时录入表单(imageGeneration/textToSpeech 多字段、可能中途非法)。 */
353
+ async autosave(section, patch) {
354
+ void section;
355
+ const current = await this.getSnapshot();
356
+ if (current.readOnly)
357
+ throw new Error('当前使用环境变量只读配置,不能从设置页修改');
358
+ if (!current.config)
359
+ throw new Error('请先添加至少一个模型');
360
+ let state = 'applied';
361
+ const draftFields = [];
362
+ await updateMinimalAgentConfig((latest) => {
363
+ let active = latest ?? current.config;
364
+ if (patch.subagents)
365
+ active = { version: 4, ...applyConfigPatch(active, { subagents: patch.subagents }), savedAt: active.savedAt };
366
+ const drafts = structuredClone(active.drafts ?? {});
367
+ for (const key of Object.keys(patch.tools ?? {})) {
368
+ const rawPiece = patch.tools[key];
369
+ if (!rawPiece)
370
+ continue;
371
+ const previous = (drafts.tools ?? {})[key];
372
+ const merged = mergeRecord(previous, rawPiece);
373
+ try {
374
+ active = { version: 4, ...applyConfigPatch(active, { tools: { [key]: merged } }), savedAt: active.savedAt };
375
+ delete drafts.tools?.[key];
376
+ }
377
+ catch {
378
+ state = 'draft';
379
+ draftFields.push(key);
380
+ (drafts.tools ??= {})[key] = merged;
381
+ }
382
+ }
383
+ if (!hasEntries(drafts.tools))
384
+ delete drafts.tools;
385
+ return {
386
+ models: active.models, baseModelId: active.baseModelId,
387
+ ...(active.compactRatio !== undefined ? { compactRatio: active.compactRatio } : {}),
388
+ advisor: active.advisor, subagents: active.subagents, browser: active.browser, tools: active.tools,
389
+ ...(hasEntries(drafts) ? { drafts } : {}),
390
+ };
391
+ });
392
+ return { snapshot: await this.reload(), state, draftFields };
393
+ }
394
+ }
395
+ export const configService = new ConfigService();
396
+ export const SENSITIVE_ENV_KEYS = new Set([
397
+ 'MINIMAL_AGENT_API_KEY', 'TAVILY_API_KEY', 'OPENAI_IMAGE_API_KEY',
398
+ 'OPENROUTER_API_KEY', 'MINIMAX_API_KEY', 'MINIMAX_GROUP_ID',
399
+ ]);
400
+ /** Child processes inherit normal runtime variables, never model or tool credentials. */
401
+ export function sanitizeCredentialEnv(source = process.env) {
402
+ const env = { ...source };
403
+ for (const key of SENSITIVE_ENV_KEYS)
404
+ delete env[key];
405
+ return env;
406
+ }
407
+ export async function trustedToolEnv(kind, service = configService) {
408
+ const env = sanitizeCredentialEnv();
409
+ if (kind === 'imageGeneration') {
410
+ let cfg = await service.tool('imageGeneration');
411
+ if (!cfg) {
412
+ const openRouter = await service.tool('openRouter');
413
+ if (openRouter)
414
+ cfg = {
415
+ provider: 'openrouter',
416
+ baseURL: 'https://openrouter.ai/api/v1',
417
+ apiKey: openRouter.apiKey,
418
+ model: 'google/gemini-3-pro-image-preview',
419
+ protocol: 'chat',
420
+ };
421
+ }
422
+ if (cfg) {
423
+ env.OPENAI_IMAGE_PROVIDER = cfg.provider;
424
+ env.OPENAI_IMAGE_BASE_URL = cfg.baseURL;
425
+ env.OPENAI_IMAGE_API_KEY = cfg.apiKey;
426
+ env.OPENAI_IMAGE_MODEL = cfg.model;
427
+ if (cfg.protocol && cfg.protocol !== 'auto')
428
+ env.OPENAI_IMAGE_PROTOCOL = cfg.protocol;
429
+ else
430
+ delete env.OPENAI_IMAGE_PROTOCOL;
431
+ }
432
+ }
433
+ else if (kind === 'openRouter') {
434
+ const cfg = await service.tool('openRouter');
435
+ if (cfg)
436
+ env.OPENROUTER_API_KEY = cfg.apiKey;
437
+ }
438
+ else {
439
+ const cfg = await service.tool('textToSpeech');
440
+ if (cfg) {
441
+ env.MINIMAX_API_KEY = cfg.apiKey;
442
+ env.MINIMAX_GROUP_ID = cfg.groupId;
443
+ }
444
+ }
445
+ return env;
446
+ }
package/src/config.js CHANGED
@@ -5,17 +5,33 @@
5
5
  * 只从环境变量读取,集中管理所有配置。
6
6
  *
7
7
  * 必需的环境变量:
8
- * MINIMAL_AGENT_BASE_URL - API 地址,如 https://api.minimax.chat/v1
8
+ * MINIMAL_AGENT_BASE_URL - API 完整端点地址,如 https://api.minimax.chat/v1/chat/completions
9
9
  * MINIMAL_AGENT_API_KEY - API key
10
10
  * MINIMAL_AGENT_MODEL - 模型名,如 MiniMax-M2.7
11
11
  *
12
12
  * 可选的环境变量:
13
13
  * MINIMAL_AGENT_PROVIDER - 友好名称,默认 "env"
14
14
  * MINIMAL_AGENT_CONTEXT_WINDOW - 上下文窗口,默认 128000
15
+ * MINIMAL_AGENT_MAX_TOKENS - 单次输出 token 上限,默认 16384(弱 provider 默认偏小会截断长输出)
15
16
  * ============================================================
16
17
  */
17
- import { readSavedConfig } from './config/configFile.js';
18
+ import { configService } from './config/configService.js';
18
19
  const DEFAULT_CONTEXT_WINDOW = 128_000;
20
+ /**
21
+ * 单次输出 token 上限的默认值。
22
+ *
23
+ * 16384:实测火山豆包接受、足够一次性写完整篇 PLAN.md(实测 ~6.5k token),又远低于
24
+ * 豆包 131072 的宽容上限。不发 max_tokens 的话豆包默认仅 4096,长输出会被硬截断。
25
+ * 个别 provider 若拒绝该值,client 会自动去掉 max_tokens 重发降级。
26
+ */
27
+ const DEFAULT_MAX_TOKENS = 16_384;
28
+ /** 解析正整数环境变量(NaN/≤0 → undefined,由调用方决定默认值)。 */
29
+ function parsePositiveInt(raw) {
30
+ if (!raw)
31
+ return undefined;
32
+ const n = parseInt(raw, 10);
33
+ return !Number.isNaN(n) && n > 0 ? n : undefined;
34
+ }
19
35
  /**
20
36
  * 解析压缩阈值比例(0~1 小数)。
21
37
  * 非法值(NaN、0、负数、>1)返回 undefined,由调用方决定是否用默认值 0.85。
@@ -60,78 +76,22 @@ export async function loadProvider() {
60
76
  }
61
77
  }
62
78
  const compactRatio = parseCompactRatio(process.env.MINIMAL_AGENT_COMPACT_RATIO);
79
+ const maxTokens = parsePositiveInt(process.env.MINIMAL_AGENT_MAX_TOKENS) ?? DEFAULT_MAX_TOKENS;
63
80
  return {
64
81
  name: process.env.MINIMAL_AGENT_PROVIDER ?? 'env',
65
82
  baseURL,
66
83
  apiKey,
67
84
  model,
68
85
  contextWindow,
86
+ maxTokens,
69
87
  ...(compactRatio !== undefined ? { compactRatio } : {}),
70
88
  };
71
89
  }
72
- /**
73
- * 层级加载:env 优先(模式 A),不全则回退到 ~/.minimal-agent/config.json(模式 B)。
74
- * 都没有时返回 null —— 调用方决定是触发向导还是直接退出。
75
- *
76
- * 与 loadProvider() 的区别:
77
- * - loadProvider() 只读 env,全缺则抛错(给 -p 模式用,明确告知"先做配置")
78
- * - 本函数静默 fallback 到 JSON,全缺则返回 null(给 TUI 模式用,可触发向导)
79
- *
80
- * env 字段优先级是逐项的:只要 env 里有 BASE_URL 就用 env 的 BASE_URL,
81
- * 其它字段从 JSON 补;这样用户可以临时用 env 覆盖单个字段做调试。
82
- */
90
+ /** Shared CLI/Desktop configuration: JSON first, environment only as bootstrap or env mode. */
83
91
  export async function loadProviderLayered() {
84
- const envBaseURL = process.env.MINIMAL_AGENT_BASE_URL;
85
- const envApiKey = process.env.MINIMAL_AGENT_API_KEY;
86
- const envModel = process.env.MINIMAL_AGENT_MODEL;
87
- const envName = process.env.MINIMAL_AGENT_PROVIDER;
88
- const envContextRaw = process.env.MINIMAL_AGENT_CONTEXT_WINDOW;
89
- let saved = null;
90
- if (!envBaseURL || !envApiKey || !envModel) {
91
- saved = await readSavedConfig();
92
- }
93
- const baseURL = envBaseURL ?? saved?.baseURL;
94
- const apiKey = envApiKey ?? saved?.apiKey;
95
- const model = envModel ?? saved?.model;
96
- if (!baseURL || !apiKey || !model)
97
- return null;
98
- let contextWindow = DEFAULT_CONTEXT_WINDOW;
99
- if (envContextRaw) {
100
- const n = parseInt(envContextRaw, 10);
101
- if (!Number.isNaN(n) && n > 0)
102
- contextWindow = n;
103
- }
104
- else if (saved?.contextWindow) {
105
- contextWindow = saved.contextWindow;
106
- }
107
- // compactRatio:env 优先(测试时临时 MINIMAL_AGENT_COMPACT_RATIO=0.2 即可),否则取 saved。
108
- const envRatio = parseCompactRatio(process.env.MINIMAL_AGENT_COMPACT_RATIO);
109
- const compactRatio = envRatio ??
110
- (typeof saved?.compactRatio === 'number' ? saved.compactRatio : undefined);
111
- return {
112
- name: envName ?? saved?.provider ?? 'env',
113
- baseURL,
114
- apiKey,
115
- model,
116
- contextWindow,
117
- ...(compactRatio !== undefined ? { compactRatio } : {}),
118
- };
92
+ return configService.provider();
119
93
  }
120
- /**
121
- * 把 saved config 里的工具 key 注入 process.env(env 已设则不覆盖)。
122
- *
123
- * 解决 npm 全局安装场景:`node dist/main.js` 不会自动加载 .env,
124
- * Tavily 之类的工具 key 只能从 ~/.minimal-agent/config.json 取出再写回 env,
125
- * websearch.ts 才能在 call() 里读到。
126
- *
127
- * 应在 main() 最早期调用(initWorkingDir 之后即可),保证 ALL_TOOLS
128
- * 任何 call() 触发前 env 已就位。
129
- */
94
+ /** @deprecated Compatibility hook. Trusted tools now read ConfigService directly. */
130
95
  export async function applyToolKeysToEnv() {
131
- const saved = await readSavedConfig();
132
- if (!saved)
133
- return;
134
- if (!process.env.TAVILY_API_KEY && saved.tavilyApiKey) {
135
- process.env.TAVILY_API_KEY = saved.tavilyApiKey;
136
- }
96
+ await configService.reload();
137
97
  }
@@ -0,0 +1,9 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ /** 测试和嵌入宿主可覆盖 home 根,生产默认使用系统主目录。 */
4
+ export function agentHomeBase() {
5
+ return process.env.MINIMAL_AGENT_HOME || homedir();
6
+ }
7
+ export function agentStateDir() {
8
+ return join(agentHomeBase(), '.minimal-agent');
9
+ }
@@ -26,6 +26,7 @@ import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
26
26
  import { homedir } from 'node:os';
27
27
  import { join } from 'node:path';
28
28
  import { getWorkingDir } from '../bootstrap/workingDir.js';
29
+ import { contentToPlainText } from '../multimodal.js';
29
30
  /** 解析 archive 根目录,支持 MINIMAL_AGENT_HOME 测试覆盖 */
30
31
  function agentHome() {
31
32
  return process.env.MINIMAL_AGENT_HOME ?? homedir();
@@ -51,6 +52,10 @@ export async function archiveCurrent(messages) {
51
52
  await mkdir(dir, { recursive: true });
52
53
  const id = new Date().toISOString().replace(/:/g, '-');
53
54
  const path = join(dir, `${id}.json`);
55
+ // 写入完整 messages(含 system)—— restoreArchive 期待原样还原以做 roundtrip
56
+ // 跳过判定单独用 nonSystem(空对话不归档)—— 这是有意分层的:
57
+ // skip 判定 = "有没有用户/助手消息可归档"
58
+ // write 内容 = "保留完整上下文供 /restore 还原"
54
59
  await writeFile(path, JSON.stringify({ updatedAt: Date.now(), messages }), 'utf8');
55
60
  return id;
56
61
  }
@@ -82,8 +87,8 @@ export async function listArchives() {
82
87
  if (!Array.isArray(data.messages))
83
88
  continue;
84
89
  const firstUser = data.messages.find((m) => m.role === 'user');
85
- const preview = firstUser && typeof firstUser.content === 'string'
86
- ? firstUser.content.replace(/\s+/g, ' ').slice(0, 80)
90
+ const preview = firstUser
91
+ ? contentToPlainText(firstUser.content).replace(/\s+/g, ' ').slice(0, 80)
87
92
  : '';
88
93
  entries.push({
89
94
  id,
@@ -299,7 +299,7 @@ export async function autoCompactIfNeeded(messages, provider, opts) {
299
299
  return { messages, compacted: false, before, after: before };
300
300
  }
301
301
  // 真正要压缩了
302
- const compactedMessages = await runCompaction(messages, provider);
302
+ const compactedMessages = await runCompaction(messages, provider, opts?.signal, opts?.chat ?? chat);
303
303
  const after = countMessagesTokens(compactedMessages);
304
304
  return { messages: compactedMessages, compacted: true, before, after };
305
305
  }
@@ -309,9 +309,11 @@ export async function autoCompactIfNeeded(messages, provider, opts) {
309
309
  * 与 autoCompactIfNeeded 共用同一段 runCompaction 逻辑;区别仅在于此函数
310
310
  * 永远跑压缩、不返回 `compacted` 字段(由调用方决定如何反馈)。
311
311
  */
312
- export async function forceCompact(messages, provider) {
312
+ export async function forceCompact(messages, provider, signal,
313
+ /** 注入 chat 实现(仅测试用;生产不传走默认实现)。 */
314
+ chatFn = chat) {
313
315
  const before = countMessagesTokens(messages);
314
- const compactedMessages = await runCompaction(messages, provider);
316
+ const compactedMessages = await runCompaction(messages, provider, signal, chatFn);
315
317
  const after = countMessagesTokens(compactedMessages);
316
318
  return { messages: compactedMessages, before, after };
317
319
  }
@@ -324,7 +326,7 @@ export async function forceCompact(messages, provider) {
324
326
  * ✅ 关键修复:使用 findTailWithCompleteToolChains() 替代简单的 slice(-N)
325
327
  * 保证压缩后的消息结构符合 OpenAI 协议要求(tool 消息必须有对应的 tool_call 父消息)。
326
328
  */
327
- async function runCompaction(messages, provider) {
329
+ async function runCompaction(messages, provider, signal, chatFn = chat) {
328
330
  // 1. 找出原 system(保留它,避免 agent 失忆)
329
331
  const systemMsg = messages.find((m) => m.role === 'system');
330
332
  const nonSystem = messages.filter((m) => m.role !== 'system');
@@ -342,10 +344,11 @@ async function runCompaction(messages, provider) {
342
344
  // 3. 调 LLM(用同一个 chat() 但不传工具,避免模型尝试调用工具)
343
345
  let summary = '';
344
346
  try {
345
- for await (const ev of chat({
347
+ for await (const ev of chatFn({
346
348
  provider,
347
349
  messages: compactRequest,
348
350
  tools: [],
351
+ signal,
349
352
  })) {
350
353
  if (ev.type === 'text_delta')
351
354
  summary += ev.delta;