minimal-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (317) hide show
  1. package/LICENSE +148 -0
  2. package/README.md +287 -0
  3. package/dist/main.js +4140 -0
  4. package/package.json +66 -0
  5. package/skills/algorithmic-art/LICENSE.txt +202 -0
  6. package/skills/algorithmic-art/SKILL.md +405 -0
  7. package/skills/algorithmic-art/templates/generator_template.js +223 -0
  8. package/skills/algorithmic-art/templates/viewer.html +599 -0
  9. package/skills/batch/SKILL.md +39 -0
  10. package/skills/canvas-design/LICENSE.txt +202 -0
  11. package/skills/canvas-design/SKILL.md +130 -0
  12. package/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  13. package/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  14. package/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  15. package/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  16. package/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  17. package/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  18. package/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  19. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  20. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  21. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  22. package/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  23. package/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  24. package/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  25. package/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  26. package/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  27. package/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  28. package/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  29. package/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  30. package/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  31. package/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  32. package/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  33. package/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  34. package/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  35. package/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  36. package/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  37. package/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  38. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  39. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  40. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  41. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  42. package/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  43. package/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  44. package/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  45. package/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  46. package/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  47. package/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  48. package/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  49. package/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  50. package/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  51. package/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  52. package/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  53. package/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  54. package/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  55. package/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  56. package/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  57. package/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  58. package/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  59. package/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  60. package/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  61. package/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  62. package/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  63. package/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  64. package/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  65. package/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  66. package/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  67. package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  68. package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  69. package/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  70. package/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  71. package/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  72. package/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  73. package/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  74. package/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  75. package/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  76. package/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  77. package/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  78. package/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  79. package/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  80. package/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  81. package/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  82. package/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  83. package/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  84. package/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  85. package/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  86. package/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  87. package/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  88. package/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  89. package/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  90. package/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  91. package/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  92. package/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  93. package/skills/commit/SKILL.md +46 -0
  94. package/skills/compact/SKILL.md +27 -0
  95. package/skills/config/SKILL.md +94 -0
  96. package/skills/diff/SKILL.md +30 -0
  97. package/skills/docx/LICENSE.txt +30 -0
  98. package/skills/docx/SKILL.md +660 -0
  99. package/skills/docx/scripts/__init__.py +1 -0
  100. package/skills/docx/scripts/accept_changes.py +135 -0
  101. package/skills/docx/scripts/comment.py +318 -0
  102. package/skills/docx/scripts/convert_to_docx.py +140 -0
  103. package/skills/docx/scripts/office/__init__.py +1 -0
  104. package/skills/docx/scripts/office/helpers/__init__.py +0 -0
  105. package/skills/docx/scripts/office/helpers/__pycache__/__init__.cpython-314.pyc +0 -0
  106. package/skills/docx/scripts/office/helpers/__pycache__/merge_runs.cpython-314.pyc +0 -0
  107. package/skills/docx/scripts/office/helpers/__pycache__/simplify_redlines.cpython-314.pyc +0 -0
  108. package/skills/docx/scripts/office/helpers/merge_runs.py +199 -0
  109. package/skills/docx/scripts/office/helpers/simplify_redlines.py +197 -0
  110. package/skills/docx/scripts/office/pack.py +159 -0
  111. package/skills/docx/scripts/office/schemas/__init__.py +1 -0
  112. package/skills/docx/scripts/office/soffice.py +183 -0
  113. package/skills/docx/scripts/office/unpack.py +132 -0
  114. package/skills/docx/scripts/office/validate.py +111 -0
  115. package/skills/docx/scripts/office/validators/__init__.py +15 -0
  116. package/skills/docx/scripts/office/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  117. package/skills/docx/scripts/office/validators/__pycache__/base.cpython-314.pyc +0 -0
  118. package/skills/docx/scripts/office/validators/__pycache__/docx.cpython-314.pyc +0 -0
  119. package/skills/docx/scripts/office/validators/__pycache__/pptx.cpython-314.pyc +0 -0
  120. package/skills/docx/scripts/office/validators/__pycache__/redlining.cpython-314.pyc +0 -0
  121. package/skills/docx/scripts/office/validators/base.py +847 -0
  122. package/skills/docx/scripts/office/validators/docx.py +446 -0
  123. package/skills/docx/scripts/office/validators/pptx.py +275 -0
  124. package/skills/docx/scripts/office/validators/redlining.py +247 -0
  125. package/skills/docx/scripts/templates/comments.xml +3 -0
  126. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  127. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  128. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  129. package/skills/docx/scripts/templates/people.xml +3 -0
  130. package/skills/frontend-design/LICENSE.txt +177 -0
  131. package/skills/frontend-design/SKILL.md +42 -0
  132. package/skills/init/SKILL.md +238 -0
  133. package/skills/init/evals/evals.json +28 -0
  134. package/skills/mcp-builder/LICENSE.txt +202 -0
  135. package/skills/mcp-builder/SKILL.md +236 -0
  136. package/skills/mcp-builder/reference/evaluation.md +602 -0
  137. package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  138. package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  139. package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  140. package/skills/mcp-builder/scripts/connections.py +151 -0
  141. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  142. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  143. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  144. package/skills/pdf/LICENSE.txt +30 -0
  145. package/skills/pdf/SKILL.md +314 -0
  146. package/skills/pdf/forms.md +294 -0
  147. package/skills/pdf/reference.md +612 -0
  148. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  149. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  150. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  151. package/skills/pdf/scripts/create_validation_image.py +37 -0
  152. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  153. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  154. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  155. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  156. package/skills/pptx/LICENSE.txt +30 -0
  157. package/skills/pptx/SKILL.md +232 -0
  158. package/skills/pptx/editing.md +205 -0
  159. package/skills/pptx/pptxgenjs.md +420 -0
  160. package/skills/pptx/scripts/__init__.py +0 -0
  161. package/skills/pptx/scripts/add_slide.py +195 -0
  162. package/skills/pptx/scripts/clean.py +286 -0
  163. package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
  164. package/skills/pptx/scripts/office/helpers/merge_runs.py +199 -0
  165. package/skills/pptx/scripts/office/helpers/simplify_redlines.py +197 -0
  166. package/skills/pptx/scripts/office/pack.py +159 -0
  167. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  168. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  169. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  170. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  171. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  172. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  173. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  174. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  175. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  176. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  177. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  178. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  179. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  180. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  181. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  182. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  183. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  184. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  185. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  186. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  187. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  188. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  189. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  190. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  191. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  192. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  193. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  194. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/ECMA-376_3rd_edition.zip +1 -0
  195. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/README.md +1 -0
  196. package/skills/pptx/scripts/office/schemas/mce/mce-core.xsd +1 -0
  197. package/skills/pptx/scripts/office/schemas/mce/mce-override.xsd +1 -0
  198. package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  199. package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  200. package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  201. package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  202. package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  203. package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  204. package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  205. package/skills/pptx/scripts/office/soffice.py +183 -0
  206. package/skills/pptx/scripts/office/unpack.py +132 -0
  207. package/skills/pptx/scripts/office/validate.py +111 -0
  208. package/skills/pptx/scripts/office/validators/__init__.py +15 -0
  209. package/skills/pptx/scripts/office/validators/base.py +847 -0
  210. package/skills/pptx/scripts/office/validators/docx.py +446 -0
  211. package/skills/pptx/scripts/office/validators/pptx.py +275 -0
  212. package/skills/pptx/scripts/office/validators/redlining.py +247 -0
  213. package/skills/pptx/scripts/thumbnail.py +289 -0
  214. package/skills/simplify/SKILL.md +52 -0
  215. package/skills/skill-creator/LICENSE.txt +202 -0
  216. package/skills/skill-creator/SKILL.md +485 -0
  217. package/skills/skill-creator/agents/analyzer.md +274 -0
  218. package/skills/skill-creator/agents/comparator.md +202 -0
  219. package/skills/skill-creator/agents/grader.md +223 -0
  220. package/skills/skill-creator/assets/eval_review.html +146 -0
  221. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  222. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  223. package/skills/skill-creator/references/schemas.md +430 -0
  224. package/skills/skill-creator/scripts/__init__.py +0 -0
  225. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  226. package/skills/skill-creator/scripts/generate_report.py +326 -0
  227. package/skills/skill-creator/scripts/improve_description.py +247 -0
  228. package/skills/skill-creator/scripts/package_skill.py +136 -0
  229. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  230. package/skills/skill-creator/scripts/run_eval.py +310 -0
  231. package/skills/skill-creator/scripts/run_loop.py +328 -0
  232. package/skills/skill-creator/scripts/utils.py +47 -0
  233. package/skills/theme-factory/LICENSE.txt +202 -0
  234. package/skills/theme-factory/SKILL.md +59 -0
  235. package/skills/theme-factory/theme-showcase.pdf +0 -0
  236. package/skills/theme-factory/themes/arctic-frost.md +19 -0
  237. package/skills/theme-factory/themes/botanical-garden.md +19 -0
  238. package/skills/theme-factory/themes/desert-rose.md +19 -0
  239. package/skills/theme-factory/themes/forest-canopy.md +19 -0
  240. package/skills/theme-factory/themes/golden-hour.md +19 -0
  241. package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  242. package/skills/theme-factory/themes/modern-minimalist.md +19 -0
  243. package/skills/theme-factory/themes/ocean-depths.md +19 -0
  244. package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  245. package/skills/theme-factory/themes/tech-innovation.md +19 -0
  246. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  247. package/skills/web-artifacts-builder/SKILL.md +79 -0
  248. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +53 -0
  249. package/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  250. package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  251. package/skills/webapp-testing/LICENSE.txt +202 -0
  252. package/skills/webapp-testing/SKILL.md +96 -0
  253. package/skills/webapp-testing/examples/console_logging.py +35 -0
  254. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  255. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  256. package/skills/webapp-testing/scripts/with_server.py +106 -0
  257. package/skills/xlsx/LICENSE.txt +30 -0
  258. package/skills/xlsx/SKILL.md +292 -0
  259. package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
  260. package/skills/xlsx/scripts/office/helpers/merge_runs.py +199 -0
  261. package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  262. package/skills/xlsx/scripts/office/pack.py +159 -0
  263. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  264. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  265. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  266. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  267. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  268. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  269. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  270. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  271. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  272. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  273. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  274. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  275. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  276. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  277. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  278. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  279. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  280. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  281. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  282. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  283. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  284. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  285. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  286. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  287. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  288. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  289. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  290. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/ECMA-376_3rd_edition.zip +1 -0
  291. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/README.md +1 -0
  292. package/skills/xlsx/scripts/office/schemas/mce/mce-core.xsd +1 -0
  293. package/skills/xlsx/scripts/office/schemas/mce/mce-override.xsd +1 -0
  294. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  295. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  296. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  297. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  298. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  299. package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  300. package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  301. package/skills/xlsx/scripts/office/soffice.py +183 -0
  302. package/skills/xlsx/scripts/office/unpack.py +132 -0
  303. package/skills/xlsx/scripts/office/validate.py +111 -0
  304. package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
  305. package/skills/xlsx/scripts/office/validators/base.py +847 -0
  306. package/skills/xlsx/scripts/office/validators/docx.py +446 -0
  307. package/skills/xlsx/scripts/office/validators/pptx.py +275 -0
  308. package/skills/xlsx/scripts/office/validators/redlining.py +247 -0
  309. package/skills/xlsx/scripts/recalc.py +184 -0
  310. package/vendor/ripgrep/COPYING +3 -0
  311. package/vendor/ripgrep/README.md +34 -0
  312. package/vendor/ripgrep/arm64-darwin/rg +0 -0
  313. package/vendor/ripgrep/arm64-linux/rg +0 -0
  314. package/vendor/ripgrep/arm64-win32/rg.exe +0 -0
  315. package/vendor/ripgrep/x64-darwin/rg +0 -0
  316. package/vendor/ripgrep/x64-linux/rg +0 -0
  317. package/vendor/ripgrep/x64-win32/rg.exe +0 -0
package/dist/main.js ADDED
@@ -0,0 +1,4140 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.tsx
4
+ import { render } from "ink";
5
+ import { createRequire } from "module";
6
+
7
+ // src/config/configFile.ts
8
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
9
+ import { homedir } from "os";
10
+ import { dirname, join } from "path";
11
+ function getConfigFilePath() {
12
+ return process.env.MINIMAL_AGENT_CONFIG_FILE ?? join(homedir(), ".minimal-agent", "config.json");
13
+ }
14
+ async function readSavedConfig() {
15
+ const file = getConfigFilePath();
16
+ try {
17
+ const raw = await readFile(file, "utf8");
18
+ const data = JSON.parse(raw);
19
+ if (typeof data.baseURL !== "string" || typeof data.apiKey !== "string" || typeof data.model !== "string" || data.baseURL.length === 0 || data.apiKey.length === 0 || data.model.length === 0) {
20
+ return null;
21
+ }
22
+ return {
23
+ baseURL: data.baseURL,
24
+ apiKey: data.apiKey,
25
+ model: data.model,
26
+ provider: typeof data.provider === "string" ? data.provider : void 0,
27
+ contextWindow: typeof data.contextWindow === "number" && data.contextWindow > 0 ? data.contextWindow : void 0,
28
+ savedAt: typeof data.savedAt === "number" ? data.savedAt : 0
29
+ };
30
+ } catch {
31
+ return null;
32
+ }
33
+ }
34
+ async function saveConfig(cfg) {
35
+ const file = getConfigFilePath();
36
+ await mkdir(dirname(file), { recursive: true });
37
+ const data = { ...cfg, savedAt: Date.now() };
38
+ await writeFile(file, JSON.stringify(data, null, 2), "utf8");
39
+ try {
40
+ await chmod(file, 384);
41
+ } catch {
42
+ }
43
+ }
44
+
45
+ // src/config.ts
46
+ var DEFAULT_CONTEXT_WINDOW = 128e3;
47
+ async function loadProvider() {
48
+ const baseURL = process.env.MINIMAL_AGENT_BASE_URL;
49
+ const apiKey = process.env.MINIMAL_AGENT_API_KEY;
50
+ const model = process.env.MINIMAL_AGENT_MODEL;
51
+ if (!baseURL || !apiKey || !model) {
52
+ const missing = [];
53
+ if (!baseURL) missing.push("MINIMAL_AGENT_BASE_URL");
54
+ if (!apiKey) missing.push("MINIMAL_AGENT_API_KEY");
55
+ if (!model) missing.push("MINIMAL_AGENT_MODEL");
56
+ throw new Error(
57
+ `\u7F3A\u5C11\u5FC5\u9700\u7684\u73AF\u5883\u53D8\u91CF\uFF1A${missing.join(", ")}
58
+
59
+ \u8BF7\u5728 .env \u4E2D\u914D\u7F6E\uFF1A
60
+ MINIMAL_AGENT_BASE_URL=https://api.example.com/v1
61
+ MINIMAL_AGENT_API_KEY=your-api-key
62
+ MINIMAL_AGENT_MODEL=your-model
63
+
64
+ \u53C2\u8003 .env.example`
65
+ );
66
+ }
67
+ const contextWindowRaw = process.env.MINIMAL_AGENT_CONTEXT_WINDOW;
68
+ let contextWindow = DEFAULT_CONTEXT_WINDOW;
69
+ if (contextWindowRaw) {
70
+ const n = parseInt(contextWindowRaw, 10);
71
+ if (!Number.isNaN(n) && n > 0) {
72
+ contextWindow = n;
73
+ }
74
+ }
75
+ return {
76
+ name: process.env.MINIMAL_AGENT_PROVIDER ?? "env",
77
+ baseURL,
78
+ apiKey,
79
+ model,
80
+ contextWindow
81
+ };
82
+ }
83
+ 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) return null;
97
+ let contextWindow = DEFAULT_CONTEXT_WINDOW;
98
+ if (envContextRaw) {
99
+ const n = parseInt(envContextRaw, 10);
100
+ if (!Number.isNaN(n) && n > 0) contextWindow = n;
101
+ } else if (saved?.contextWindow) {
102
+ contextWindow = saved.contextWindow;
103
+ }
104
+ return {
105
+ name: envName ?? saved?.provider ?? "env",
106
+ baseURL,
107
+ apiKey,
108
+ model,
109
+ contextWindow
110
+ };
111
+ }
112
+
113
+ // src/context/persistContext.ts
114
+ import { mkdir as mkdir2, readFile as readFile2, unlink, writeFile as writeFile2 } from "fs/promises";
115
+ import { homedir as homedir2 } from "os";
116
+ import { dirname as dirname2, join as join2 } from "path";
117
+ function getContextPath() {
118
+ return process.env.MINIMAL_AGENT_CONTEXT_FILE ?? join2(homedir2(), ".minimal-agent", "last-context.json");
119
+ }
120
+ async function loadContext() {
121
+ const file = getContextPath();
122
+ try {
123
+ const raw = await readFile2(file, "utf8");
124
+ const data = JSON.parse(raw);
125
+ if (!Array.isArray(data.messages)) return null;
126
+ return data.messages;
127
+ } catch {
128
+ return null;
129
+ }
130
+ }
131
+ async function saveContext(messages) {
132
+ const file = getContextPath();
133
+ try {
134
+ await mkdir2(dirname2(file), { recursive: true });
135
+ const data = { updatedAt: Date.now(), messages };
136
+ await writeFile2(file, JSON.stringify(data), "utf8");
137
+ } catch {
138
+ }
139
+ }
140
+ async function clearContext() {
141
+ const file = getContextPath();
142
+ try {
143
+ await unlink(file);
144
+ } catch {
145
+ }
146
+ }
147
+
148
+ // src/prompts/system.ts
149
+ import { homedir as homedir3 } from "os";
150
+
151
+ // src/prompts/projectInstructions.ts
152
+ import { readFile as readFile3 } from "fs/promises";
153
+ import { join as join3 } from "path";
154
+ var FILENAME = "minimal-agent.md";
155
+ async function loadProjectInstructions(cwd) {
156
+ const filePath = join3(cwd, FILENAME);
157
+ try {
158
+ const content = await readFile3(filePath, "utf-8");
159
+ const trimmed = content.trim();
160
+ if (trimmed.length === 0) return null;
161
+ return trimmed;
162
+ } catch (e) {
163
+ const code = e.code;
164
+ if (code !== "ENOENT") {
165
+ process.stderr.write(
166
+ `[minimal-agent] \u8DF3\u8FC7\u9879\u76EE\u6307\u4EE4 ${filePath}\uFF1A${code ?? e.message}
167
+ `
168
+ );
169
+ }
170
+ return null;
171
+ }
172
+ }
173
+
174
+ // src/prompts/skillList.ts
175
+ import { readFile as readFile4, readdir } from "fs/promises";
176
+ import { join as join4, dirname as dirname3 } from "path";
177
+ import { fileURLToPath } from "url";
178
+ var __dirname = dirname3(fileURLToPath(import.meta.url));
179
+ var PROJECT_ROOT = join4(__dirname, "..", "..");
180
+ var SKILLS_DIR = join4(PROJECT_ROOT, "skills");
181
+ function stripQuotes(s) {
182
+ const trimmed = s.trim();
183
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
184
+ return trimmed.slice(1, -1);
185
+ }
186
+ return trimmed;
187
+ }
188
+ function parseFrontmatter(content) {
189
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
190
+ if (!match) return null;
191
+ const frontmatter = match[1];
192
+ const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
193
+ const descMatch = frontmatter.match(/^description:\s*(.+)$/m);
194
+ const typeMatch = frontmatter.match(/^type:\s*(.+)$/m);
195
+ const triggersMatch = frontmatter.match(/^triggers:\s*(.+)$/m);
196
+ if (!nameMatch || !descMatch) return null;
197
+ return {
198
+ name: stripQuotes(nameMatch[1]),
199
+ description: stripQuotes(descMatch[1]),
200
+ type: typeMatch ? stripQuotes(typeMatch[1]) : void 0,
201
+ triggers: triggersMatch ? stripQuotes(triggersMatch[1]) : void 0
202
+ };
203
+ }
204
+ async function getSkillList() {
205
+ const skills = [];
206
+ try {
207
+ const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
208
+ for (const entry of entries) {
209
+ if (!entry.isDirectory()) continue;
210
+ const skillPath = join4(SKILLS_DIR, entry.name, "SKILL.md");
211
+ try {
212
+ const content = await readFile4(skillPath, "utf8");
213
+ const meta = parseFrontmatter(content);
214
+ if (meta) {
215
+ skills.push(meta);
216
+ }
217
+ } catch {
218
+ }
219
+ }
220
+ } catch {
221
+ }
222
+ return skills;
223
+ }
224
+ function formatSkillHint(skills) {
225
+ if (skills.length === 0) {
226
+ return "";
227
+ }
228
+ const lines = [];
229
+ lines.push("# \u53EF\u7528\u6280\u80FD\uFF08/\u547D\u4EE4\uFF09");
230
+ lines.push("");
231
+ lines.push(
232
+ "\u4EE5\u4E0B\u662F\u5F53\u524D\u53EF\u7528\u7684\u6280\u80FD\u3002**\u5F53\u7528\u6237\u610F\u56FE\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A\u65F6\uFF0C\u4F60\u5E94\u8BE5\u4E3B\u52A8\u4F7F\u7528\u5B83**\u3002"
233
+ );
234
+ lines.push("\u5728\u4F7F\u7528\u524D\uFF0C\u5148\u7528 Read \u5DE5\u5177\u8BFB\u53D6 `skills/{name}/SKILL.md` \u4E86\u89E3\u5B8C\u6574\u6D41\u7A0B\u3002");
235
+ lines.push("");
236
+ for (const skill of skills) {
237
+ const type = skill.type ?? "prompt\uFF08\u5C55\u5F00\u4E3A\u7CFB\u7EDF\u63D0\u793A\u8BCD\uFF0C\u6307\u5BFC\u6A21\u578B\u6267\u884C\u590D\u6742\u4EFB\u52A1\uFF09";
238
+ const triggers = skill.triggers ?? "\uFF08\u8BE6\u89C1 SKILL.md\uFF09";
239
+ lines.push(`## ${skill.name}`);
240
+ lines.push(`- **\u89E6\u53D1\u65F6\u673A**: ${triggers}`);
241
+ lines.push(`- **\u529F\u80FD**: ${skill.description}`);
242
+ lines.push(`- **\u7C7B\u578B**: ${type}`);
243
+ lines.push("");
244
+ }
245
+ lines.push("## \u6280\u80FD\u8C03\u7528\u6D41\u7A0B");
246
+ lines.push("");
247
+ lines.push("```");
248
+ lines.push("1. \u5728 T \u9636\u6BB5\u5224\u65AD\u7528\u6237\u610F\u56FE\u662F\u5426\u5339\u914D\u67D0\u4E2A\u6280\u80FD\u7684\u89E6\u53D1\u65F6\u673A");
249
+ lines.push("2. \u5339\u914D \u2192 \u5728 A \u9636\u6BB5\u7528 Read \u5DE5\u5177\u8BFB\u53D6 skills/{name}/SKILL.md");
250
+ lines.push("3. \u6309\u7167 SKILL.md \u7684 Quick Reference \u548C\u6D41\u7A0B\u6267\u884C");
251
+ lines.push("4. \u5982\u679C\u6280\u80FD\u7C7B\u578B\u662F prompt\uFF0C\u5C06\u5176\u5185\u5BB9\u4F5C\u4E3A\u989D\u5916\u4E0A\u4E0B\u6587\u6307\u5BFC\u540E\u7EED\u64CD\u4F5C");
252
+ lines.push("```");
253
+ lines.push("");
254
+ lines.push("> \u{1F4A1} \u6280\u80FD\u662F\u5DE5\u5177\u7684\u7EC4\u5408\u62F3\u3002\u5355\u4E2A\u5DE5\u5177\u662F\u57FA\u7840\u64CD\u4F5C\uFF1B\u6280\u80FD\u662F\u9488\u5BF9\u7279\u5B9A\u573A\u666F\u7684\u6807\u51C6\u5316\u5DE5\u4F5C\u6D41\u3002");
255
+ return lines.join("\n");
256
+ }
257
+
258
+ // src/prompts/system.ts
259
+ async function getSystemPrompt(cwd, tools) {
260
+ const toolList = tools.map((t) => ` - ${t.name}`).join("\n");
261
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
262
+ const skills = await getSkillList();
263
+ const skillHint = formatSkillHint(skills);
264
+ return `\u4F60\u662F minimal-agent\uFF0C\u4E00\u4E2A\u6700\u5C0F\u5316\u7684 AI \u7F16\u7A0B\u52A9\u624B\u3002
265
+ \u4F60\u8FD0\u884C\u5728\u7528\u6237\u7684\u672C\u5730\u7EC8\u7AEF\u91CC\uFF0C\u53EF\u4EE5\u8BFB\u53D6\u3001\u4FEE\u6539\u3001\u521B\u5EFA\u6587\u4EF6\uFF0C\u53EF\u4EE5\u641C\u7D22\u6587\u4EF6\u540D\u548C\u6587\u4EF6\u5185\u5BB9\uFF0C\u53EF\u4EE5\u8054\u7F51\u641C\u7D22\u6700\u65B0\u4FE1\u606F\u3002
266
+
267
+ # \u5DE5\u4F5C\u73AF\u5883
268
+ - \u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55: ${cwd}
269
+ - \u7528\u6237\u4E3B\u76EE\u5F55: ${homedir3()}
270
+ - \u5E73\u53F0: ${process.platform}
271
+ - \u5F53\u524D\u65E5\u671F: ${today}
272
+
273
+ # \u65F6\u95F4\u611F\u77E5\u89C4\u5219\uFF08\u91CD\u8981\uFF01\u641C\u7D22\u524D\u5FC5\u8BFB\uFF09
274
+ - **\u6BCF\u6B21\u6267\u884C WebSearch \u524D\uFF0C\u5FC5\u987B\u5148\u786E\u8BA4\u5F53\u524D\u65E5\u671F**
275
+ - \u5F53\u524D\u65E5\u671F\uFF1A${today}
276
+ - \u641C\u7D22\u65F6\u5FC5\u987B\u5728 query \u672B\u5C3E\u9644\u5E26\u65E5\u671F\u9650\u5236\uFF0C\u5982 "... as of [\u65E5\u671F]" \u6216 "... ${today}"
277
+ - \u4F8B\u5982\u641C\u7D22\u65E5\u672C\u653F\u7B56\u65F6\u5199\uFF1A"\u65E5\u672C\u5165\u5883\u653F\u7B56 ${today}"
278
+ - \u7981\u6B62\u4F7F\u7528 "\u73B0\u5728"\u3001"\u6700\u8FD1"\u3001"\u6700\u65B0" \u7B49\u6A21\u7CCA\u65F6\u95F4\u8BCD\u2014\u2014\u5FC5\u987B\u7528\u5177\u4F53\u65E5\u671F
279
+
280
+ # \u53EF\u7528\u5DE5\u5177
281
+ ${toolList}
282
+
283
+ # \u5DE5\u5177\u4F7F\u7528\u89C4\u8303
284
+ - \u4FEE\u6539\u65E2\u6709\u6587\u4EF6\u4E4B\u524D\uFF0C\u5FC5\u987B\u7528 Read \u5DE5\u5177\u8BFB\u53D6\u76F8\u5173\u4EE3\u7801\u7684\u6700\u65B0\u5185\u5BB9\u3002
285
+ "\u6211\u8BB0\u5F97"\u3001"\u6211\u4E4B\u524D\u770B\u8FC7"\u3001"\u5E94\u8BE5\u5DEE\u4E0D\u591A"\u90FD\u4E0D\u7B97\u8BFB\u8FC7\u2014\u2014\u5FC5\u987B\u5728\u672C\u8F6E\u4EFB\u52A1\u4E2D\u5B9E\u9645\u8C03\u7528 Read\u3002
286
+ \u5C24\u5176\u5F53\u4F60\u8981\u53C2\u8003\u67D0\u4E2A\u6587\u4EF6\u7684\u5199\u6CD5\u6765\u5B9E\u73B0\u7C7B\u4F3C\u529F\u80FD\u65F6\uFF0C\u5FC5\u987B\u5148\u91CD\u8BFB\u8BE5\u6587\u4EF6\u7684\u5173\u952E\u90E8\u5206\uFF08\u51FD\u6570\u5B9E\u73B0\u3001\u5224\u65AD\u903B\u8F91\u3001\u6570\u636E\u6D41\uFF09\uFF0C\u7406\u89E3\u6E05\u695A\u540E\u518D\u52A8\u624B\u3002
287
+ - Edit \u5DE5\u5177\u7684 old_string \u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF1B\u4E0D\u552F\u4E00\u65F6\u8BF7\u6269\u5927\u4E0A\u4E0B\u6587\u6216\u663E\u5F0F replace_all=true\u3002
288
+ - \u521B\u5EFA\u65B0\u6587\u4EF6\u7528 Write\uFF0C\u6216 Edit \u65F6 old_string \u4F20\u7A7A\u5B57\u7B26\u4E32\u3002
289
+ - \u627E\u6587\u4EF6\u7528 Glob\uFF08"**/*.ts"\uFF09\uFF0C\u627E\u6587\u4EF6\u5185\u5BB9\u7528 Grep\uFF08\u57FA\u4E8E ripgrep\uFF09\u3002
290
+ - \u5F53\u7528\u6237\u95EE\u5230\u8BAD\u7EC3\u622A\u6B62\u540E\u624D\u51FA\u73B0\u7684\u4FE1\u606F\uFF08\u6700\u65B0\u7248\u672C\u53F7\u3001\u8FD1\u671F\u65B0\u95FB\u3001\u7B2C\u4E09\u65B9 API \u6587\u6863\uFF09\u65F6\u7528 WebSearch\uFF1B\u4F18\u5148\u7CBE\u786E\u7684\u81EA\u7136\u8BED\u8A00\u67E5\u8BE2\u3002
291
+ - \u5F53\u9700\u8981\u6267\u884C\u7EC8\u7AEF\u547D\u4EE4\uFF08\u5B89\u88C5\u4F9D\u8D56\u3001\u8FD0\u884C\u6D4B\u8BD5/\u6784\u5EFA/lint\u3001git \u64CD\u4F5C\u3001\u6587\u4EF6\u7CFB\u7EDF\u64CD\u4F5C\u7B49\uFF09\u65F6\u7528 Bash\u3002
292
+ \u26A0\uFE0F **Bash \u662F\u4E07\u80FD\u5DE5\u5177\uFF0C\u4F46\u5FC5\u987B\u4F5C\u4E3A\u6700\u540E\u624B\u6BB5**\uFF1A\u5F53 Read/Edit/Write/Glob/Grep \u7B49\u4E13\u7528\u5DE5\u5177\u65E0\u6CD5\u5B8C\u6210\u4EFB\u52A1\u65F6\uFF0C\u624D\u8003\u8651\u7528 Bash\u3002
293
+ \u4E0D\u8981\u7528 Bash \u6267\u884C find/grep/cat/head/tail/sed/awk/echo \u7B49\u547D\u4EE4\u6765\u66FF\u4EE3\u4E13\u7528\u5DE5\u5177\u2014\u2014\u4E13\u7528\u5DE5\u5177\u63D0\u4F9B\u66F4\u597D\u7684\u7528\u6237\u4F53\u9A8C\u548C\u53EF\u5BA1\u8BA1\u6027\u3002
294
+ Bash \u6709\u5B89\u5168\u9ED1\u540D\u5355\uFF08rm -rf /\u3001mkfs\u3001shutdown \u7B49\u5371\u9669\u547D\u4EE4\u4F1A\u88AB\u62E6\u622A\uFF09\uFF0C\u4F46\u4ECD\u9700\u8C28\u614E\uFF1A
295
+ \u5148\u786E\u8BA4\u547D\u4EE4\u65E0\u5BB3\u518D\u6267\u884C\uFF0C\u907F\u514D\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF08\u5982 git push --force\u3001git reset --hard\uFF09\u3002
296
+ \u957F\u65F6\u95F4\u8FD0\u884C\u7684\u547D\u4EE4\uFF08npm install\u3001bun test\uFF09\u6CE8\u610F\u8D85\u65F6\u8BBE\u7F6E\uFF1B\u9700\u8981\u4EA4\u4E92\u8F93\u5165\u7684\u547D\u4EE4\u4E0D\u8981\u7528 Bash\uFF08\u7528 Write \u5199\u811A\u672C\u4EE3\u66FF\uFF09\u3002
297
+ - \u5F53\u9700\u8981\u83B7\u53D6\u7F51\u9875\u9759\u6001\u6587\u672C\u5185\u5BB9\uFF08\u6293\u53D6\u6587\u6863\u3001\u8BFB\u53D6\u6587\u7AE0\uFF09\u65F6\u7528 WebFetch\u3002
298
+ WebBrowser \u4F9D\u8D56\u53EF\u9009\u5305\uFF08playwright-core + chromium\uFF09\u2014\u2014 **\u9ED8\u8BA4\u5047\u5B9A\u672A\u5B89\u88C5**\uFF0C
299
+ \u4EC5\u5728 WebFetch \u660E\u786E\u65E0\u6CD5\u6EE1\u8DB3\uFF08\u5982\u9700\u8981 JS \u6E32\u67D3\u540E\u7684\u5185\u5BB9\u3001\u70B9\u51FB\u6309\u94AE\u3001\u586B\u8868\u5355\u3001\u622A\u56FE\uFF09\u65F6\u518D\u5C1D\u8BD5\u3002
300
+ \u5982\u679C WebBrowser \u62A5"\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668/\u7F3A\u5C11\u4F9D\u8D56"\uFF0C\u4E0D\u8981\u91CD\u8BD5\uFF0C\u6539\u56DE WebFetch \u6216\u544A\u77E5\u7528\u6237\u5B89\u88C5\u4F9D\u8D56\u3002
301
+ - \u4E00\u6B21\u56DE\u7B54\u91CC\u53EF\u4EE5\u5E76\u884C\u8C03\u7528\u591A\u4E2A\u53EA\u8BFB\u5DE5\u5177\uFF08Read/Glob/Grep/WebSearch/WebFetch\uFF09\uFF0C\u5199\u5DE5\u5177\uFF08Edit/Write/Bash\uFF09\u8BF7\u9010\u4E2A\u6267\u884C\u3002
302
+
303
+ # \u6280\u80FD\u7CFB\u7EDF\uFF08\u79EF\u6781\u4F7F\u7528\uFF09
304
+ ${skillHint}
305
+
306
+ # \u6587\u4EF6\u8BFB\u53D6\u89C4\u8303\uFF08\u91CD\u8981\uFF01\u9632\u6B62\u4E0A\u4E0B\u6587\u6C61\u67D3\uFF09
307
+ ## \u8BFB\u53D6\u7B56\u7565\uFF08\u6309\u573A\u666F\u9009\u62E9\uFF09
308
+ ### \u573A\u666F 1\uFF1A\u4E0D\u786E\u5B9A\u6587\u4EF6\u5185\u5BB9
309
+ \u2192 \u5148 Grep("\u5173\u952E\u5B57", path="\u6587\u4EF6\u8DEF\u5F84") \u5B9A\u4F4D
310
+ \u2192 \u518D Read(file_path, offset=X, limit=Y) \u8BFB\u6307\u5B9A\u533A\u57DF
311
+
312
+ ### \u573A\u666F 2\uFF1A\u77E5\u9053\u8981\u6539\u54EA\u4E2A\u51FD\u6570
313
+ \u2192 \u76F4\u63A5 Read(file_path, offset=1, limit=400) \u5148\u8BFB\u524D 400 \u884C
314
+ \u2192 \u6216\u8005\u7528 Grep \u627E\u5230\u884C\u53F7\u540E Read(file_path, offset=\u884C\u53F7, limit=200)
315
+
316
+ ### \u573A\u666F 3\uFF1A\u5927\u6587\u4EF6\uFF08>256KB\uFF09
317
+ \u2192 \u4E0D\u8981\u4E00\u6B21\u6027\u8BFB\uFF0C\u7528 Grep \u5B9A\u4F4D\u5173\u952E\u90E8\u5206
318
+ \u2192 \u5206\u591A\u6B21\u5C0F\u8303\u56F4 Read\uFF0C\u6BCF\u6B21\u4E0D\u8D85\u8FC7 200 \u884C
319
+ \u2192 \u4F8B\u5982\uFF1ARead(file, offset=1, limit=100) \u2192 Read(file, offset=101, limit=100)
320
+
321
+ ### \u573A\u666F 4\uFF1A\u9700\u8981\u770B imports/exports
322
+ \u2192 Read(file, offset=1, limit=50) \u53EA\u8BFB\u6587\u4EF6\u5934\u90E8
323
+
324
+ ## \u8B66\u60D5\u4FE1\u53F7
325
+ - \u9047\u5230 "\u6587\u4EF6\u8FC7\u5927" \u9519\u8BEF \u2192 \u8BF4\u660E\u4F60\u8BFB\u4E86\u4E0D\u8BE5\u8BFB\u7684\u5927\u6587\u4EF6\uFF0C\u6539\u7528\u5206\u6BB5\u8BFB
326
+ - \u9047\u5230 "\u8F93\u51FA\u8D85\u8FC7 30000 \u5B57\u7B26" \u2192 \u8BF4\u660E\u5355\u6B21\u8BFB\u592A\u591A\uFF0C\u6539\u5C0F limit
327
+
328
+ # \u8F93\u51FA\u98CE\u683C
329
+ - \u7528\u4E2D\u6587\u56DE\u7B54\u7528\u6237\u3002
330
+ - \u7B80\u660E\u627C\u8981\uFF0C\u907F\u514D\u91CD\u590D\u89E3\u91CA\u4EE3\u7801\u505A\u4E86\u4EC0\u4E48 \u2014\u2014 \u8BA9\u4EE3\u7801\u81EA\u8EAB\u8BF4\u8BDD\u3002
331
+ - \u62A5\u9519\u65F6\u7ED9\u51FA\u6839\u56E0\uFF0C\u4E0D\u8981\u53EA\u590D\u8FF0\u9519\u8BEF\u6D88\u606F\u3002
332
+ - \u4E0D\u4E3B\u52A8\u8DD1\u6D4B\u8BD5\u6216 typecheck\uFF0C\u9664\u975E\u7528\u6237\u8981\u6C42\u3002
333
+
334
+ # \u884C\u4E3A\u7EA6\u675F
335
+ - \u6CA1\u6709\u7528\u6237\u8BB8\u53EF\u4E0D\u8981\u6267\u884C\u7834\u574F\u6027\u64CD\u4F5C\uFF08rm -rf / git reset --hard / git push --force \u7B49\uFF09\u3002
336
+ - \u5982\u679C\u7528\u6237\u7684\u8BF7\u6C42\u4E0D\u6E05\u6670\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u6F84\u6E05\u518D\u52A8\u624B\u3002
337
+ - \u4EFB\u52A1\u5B8C\u6210\u540E\u7528\u4E00\u4E24\u53E5\u8BDD\u603B\u7ED3\u6539\u4E86\u4EC0\u4E48\uFF0C\u4E0D\u8981\u957F\u7BC7\u5927\u8BBA\u3002
338
+
339
+ # \u4E2D\u65AD\u652F\u6301
340
+ - \u7528\u6237\u53EF\u4EE5\u968F\u65F6\u6309 ESC \u6216 Ctrl+C \u4E2D\u65AD\u4F60\u7684\u5DE5\u4F5C\u3002
341
+ - \u4E2D\u65AD\u540E\u7B49\u5F85\u7528\u6237\u8F93\u5165\u65B0\u4EFB\u52A1\uFF0C\u4E0D\u8981\u81EA\u884C\u7EE7\u7EED\u6267\u884C\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002
342
+ - \u5982\u679C\u7528\u6237\u8F93\u5165\u4E86\u65B0\u4EFB\u52A1\uFF0C\u76F4\u63A5\u54CD\u5E94\u65B0\u4EFB\u52A1\uFF0C\u4E0D\u5FC5\u63D0\u53CA\u4E4B\u524D\u88AB\u4E2D\u65AD\u7684\u64CD\u4F5C\u3002
343
+
344
+ # \u4EFB\u52A1\u8BC6\u522B
345
+ - \u7528\u6237\u5F00\u59CB\u65B0\u4EFB\u52A1\u65F6\uFF08"\u5E2E\u6211\u505A X"\u3001"\u8FD9\u6B21\u505A YYY"\uFF09\uFF0C\u4E0D\u8981\u7EE7\u7EED\u4E4B\u524D\u672A\u5B8C\u6210\u7684\u65E7\u4EFB\u52A1
346
+ - \u7528\u6237\u8BF4"\u7EE7\u7EED"\u3001"\u63A5\u7740\u505A"\u3001"\u518D\u52A0\u4E00\u4E2A"\u65F6\uFF0C\u4FDD\u6301\u5F53\u524D\u4EFB\u52A1\u4E0A\u4E0B\u6587
347
+ - \u7528\u6237\u8BF4"\u91CD\u65B0\u5F00\u59CB"\u3001"\u4E0D\u7B97\u4E86\uFF0C\u505A X"\u65F6\uFF0C\u7ACB\u5373\u653E\u4E0B\u65E7\u4EFB\u52A1
348
+ - \u610F\u56FE\u6A21\u7CCA\u65F6\uFF0C\u5148\u7528\u4E00\u53E5\u8BDD\u6F84\u6E05\uFF1A"\u4F60\u662F\u7EE7\u7EED\u521A\u624D\u7684\uFF0C\u8FD8\u662F\u5F00\u59CB\u65B0\u7684\uFF1F"`;
349
+ }
350
+ async function buildFullSystemPrompt(cwd, tools) {
351
+ let content = await getSystemPrompt(cwd, tools);
352
+ const projectInstructions = await loadProjectInstructions(cwd);
353
+ if (projectInstructions) {
354
+ content += [
355
+ "",
356
+ "--- \u9879\u76EE\u6307\u4EE4\uFF08minimal-agent.md\uFF09 ---",
357
+ "",
358
+ "\u26A0\uFE0F \u65F6\u6548\u6027\u58F0\u660E\uFF1A",
359
+ "- \u4EE5\u4E0B\u5185\u5BB9\u6765\u6E90\u4E8E\u9879\u76EE\u6839\u76EE\u5F55\u7684 `minimal-agent.md` \u6587\u4EF6\uFF0C\u5177\u6709\u65F6\u6548\u6027\uFF0C\u53EF\u80FD\u5DF2\u8FC7\u65F6\u3002",
360
+ "- \u5B83\u662F\u53C2\u8003\u7EBF\u7D22\uFF0C\u4E0D\u662F\u7EDD\u5BF9\u771F\u7406\u3002\u5F53\u5B83\u4E0E\u5B9E\u9645\u4EE3\u7801\u6216\u6587\u4EF6\u5185\u5BB9\u51B2\u7A81\u65F6\uFF0C\u4EE5\u4EE3\u7801\u4E3A\u51C6\u3002",
361
+ "- \u6D89\u53CA\u4EE3\u7801\u548C\u9879\u76EE\u6587\u4EF6\u7684\u4EFB\u4F55\u64CD\u4F5C\u524D\uFF0C\u5FC5\u987B\u5148\u7528 Read / Grep \u5DE5\u5177\u786E\u8BA4\u5F53\u524D\u4EE3\u7801\u7684\u5B9E\u9645\u72B6\u6001\uFF0C\u518D\u57FA\u4E8E\u6700\u65B0\u4FE1\u606F\u505A\u51B3\u7B56\uFF0C\u4E0D\u8981\u4EC5\u51ED\u6B64\u6587\u6863\u7684\u63CF\u8FF0\u64CD\u4F5C\u3002",
362
+ "- \u5982\u679C\u4F60\u53D1\u73B0 `minimal-agent.md` \u7684\u63CF\u8FF0\u4E0E\u5F53\u524D\u4EE3\u7801\u4E0D\u4E00\u81F4\uFF0C\u5E94\u4E3B\u52A8\u7528 Write \u5DE5\u5177\u66F4\u65B0 `minimal-agent.md` \u4F7F\u5176\u4E0E\u4EE3\u7801\u4FDD\u6301\u540C\u6B65\u3002",
363
+ "",
364
+ projectInstructions
365
+ ].join("\n");
366
+ }
367
+ return content;
368
+ }
369
+
370
+ // src/tools/bash/bash.ts
371
+ import { spawn } from "child_process";
372
+ import { z } from "zod";
373
+
374
+ // src/tools/types.ts
375
+ var DEFAULT_MAX_RESULT_SIZE_CHARS = 3e4;
376
+ var MAX_LINES_TO_READ = 2e3;
377
+ var MAX_FILE_SIZE_BYTES = 256 * 1024;
378
+
379
+ // src/utils/zodToJson.ts
380
+ import { zodToJsonSchema } from "zod-to-json-schema";
381
+ function toToolParameters(schema) {
382
+ const json = zodToJsonSchema(schema, {
383
+ target: "openApi3",
384
+ $refStrategy: "none"
385
+ });
386
+ const { $schema, ...rest } = json;
387
+ return rest;
388
+ }
389
+
390
+ // src/tools/bash/bash.ts
391
+ var DEFAULT_TIMEOUT_MS = 12e4;
392
+ var MAX_TIMEOUT_MS = 6e5;
393
+ var FORBIDDEN_PATTERNS = [
394
+ // ---- rm -rf / 及变体 ----
395
+ // 经典:"rm -rf /"、"rm -rf /*"、"rm -fr /usr"、"rm -rf $HOME"、"rm -rf ~"
396
+ // 也覆盖 sudo rm -rf /
397
+ {
398
+ pattern: /\brm\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*\s+)+(?:\/+\*?|\$HOME|~)(?:\s|$)/,
399
+ reason: "\u7981\u6B62\u9012\u5F52\u5220\u9664\u6839\u76EE\u5F55\u6216\u5BB6\u76EE\u5F55\uFF08rm -rf / \u7C7B\uFF09"
400
+ },
401
+ // 拦截 "rm -rf <绝对路径>" 中目标是关键系统目录的情况(防误伤其它正常 rm -rf ./tmp)
402
+ {
403
+ pattern: /\brm\s+(?:-[a-zA-Z]*[rRf][a-zA-Z]*\s+)+(?:\/etc|\/usr|\/bin|\/sbin|\/var|\/boot|\/lib|\/System|\/Windows|\/Users|\/home)(?:\/|\s|$)/,
404
+ reason: "\u7981\u6B62\u9012\u5F52\u5220\u9664\u7CFB\u7EDF\u5173\u952E\u76EE\u5F55"
405
+ },
406
+ // ---- 格盘 / 改写裸设备 ----
407
+ {
408
+ pattern: /\bmkfs(?:\.[a-z0-9]+)?\b/i,
409
+ reason: "\u7981\u6B62\u683C\u5F0F\u5316\u6587\u4EF6\u7CFB\u7EDF\uFF08mkfs\uFF09"
410
+ },
411
+ {
412
+ // 末尾不能用 \b:盘符 "C:" 后是非 word char,\b 匹配不到。
413
+ // 改成"盘符后跟空白/反斜杠/结尾"。
414
+ pattern: /\bformat\s+[A-Za-z]:(?=\s|\\|$)/i,
415
+ reason: "\u7981\u6B62 Windows \u683C\u76D8\u547D\u4EE4\uFF08format X:\uFF09"
416
+ },
417
+ {
418
+ pattern: /\bdd\s+[^|;&]*\bof=\/dev\/(?:sd[a-z]|nvme|disk|hd[a-z]|mmcblk)/i,
419
+ reason: "\u7981\u6B62 dd \u5199\u5165\u88F8\u78C1\u76D8\u8BBE\u5907"
420
+ },
421
+ {
422
+ pattern: />\s*\/dev\/(?:sd[a-z]|nvme|disk|hd[a-z]|mmcblk)/i,
423
+ reason: "\u7981\u6B62\u91CD\u5B9A\u5411\u5230\u88F8\u78C1\u76D8\u8BBE\u5907"
424
+ },
425
+ // ---- 关机 / 重启 ----
426
+ {
427
+ pattern: /\b(?:shutdown|reboot|halt|poweroff)\b/i,
428
+ reason: "\u7981\u6B62\u5173\u673A/\u91CD\u542F\u547D\u4EE4"
429
+ },
430
+ {
431
+ pattern: /\binit\s+[06]\b/,
432
+ reason: "\u7981\u6B62 init 0/6\uFF08\u5173\u673A/\u91CD\u542F\uFF09"
433
+ },
434
+ // ---- Fork bomb ----
435
+ {
436
+ pattern: /:\s*\(\s*\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/,
437
+ reason: "\u7981\u6B62 fork bomb"
438
+ },
439
+ // ---- 管道到 shell(典型恶意安装脚本模式) ----
440
+ // curl ... | sh / wget ... | bash / curl ... | python
441
+ {
442
+ pattern: /\b(?:curl|wget|fetch)\b[^|;&]*\|\s*(?:sh|bash|zsh|dash|python|perl|ruby|node)\b/i,
443
+ reason: "\u7981\u6B62\u4ECE\u7F51\u7EDC\u7BA1\u9053\u5230 shell \u89E3\u91CA\u5668\uFF08curl ... | sh \u6A21\u5F0F\uFF09"
444
+ },
445
+ // ---- chmod 整盘 777 ----
446
+ {
447
+ pattern: /\bchmod\s+(?:-R\s+)?[0-7]*7[0-7]*\s+\/(?:\s|$)/,
448
+ reason: "\u7981\u6B62\u5BF9\u6839\u76EE\u5F55 chmod 777"
449
+ },
450
+ // ---- Windows 系统级删除 ----
451
+ {
452
+ pattern: /\b(?:del|erase)\s+\/[sS][^|;&]*[A-Za-z]:\\?\s*$/,
453
+ reason: "\u7981\u6B62 Windows \u5168\u76D8\u5220\u9664\uFF08del /s \u6839\u76EE\u5F55\uFF09"
454
+ },
455
+ {
456
+ pattern: /\brmdir\s+\/[sS][^|;&]*[A-Za-z]:\\?\s*$/,
457
+ reason: "\u7981\u6B62 Windows \u5168\u76D8 rmdir"
458
+ },
459
+ {
460
+ pattern: /\bdiskpart\b/i,
461
+ reason: "\u7981\u6B62 diskpart"
462
+ }
463
+ ];
464
+ function checkForbidden(command) {
465
+ for (const { pattern, reason } of FORBIDDEN_PATTERNS) {
466
+ if (pattern.test(command)) return reason;
467
+ }
468
+ return null;
469
+ }
470
+ var inputSchema = z.object({
471
+ command: z.string().min(1, "\u5FC5\u987B\u63D0\u4F9B command").describe("\u8981\u6267\u884C\u7684 shell \u547D\u4EE4\uFF08\u5047\u5B9A bash \u8BED\u6CD5\uFF09"),
472
+ timeout: z.number().int().positive().max(MAX_TIMEOUT_MS).optional().describe(`\u8D85\u65F6\uFF08\u6BEB\u79D2\uFF09\uFF0C\u6700\u591A ${MAX_TIMEOUT_MS}\uFF08${MAX_TIMEOUT_MS / 6e4} \u5206\u949F\uFF09\uFF1B\u4E0D\u586B\u9ED8\u8BA4 ${DEFAULT_TIMEOUT_MS}`),
473
+ description: z.string().optional().describe('\u7528\u4E00\u53E5\u8BDD\u4E3B\u52A8\u8BED\u6001\u63CF\u8FF0\u547D\u4EE4\u505A\u4EC0\u4E48\uFF085-10 \u8BCD\uFF09\uFF0C\u5982 "List files in current directory"')
474
+ });
475
+ var parameters = toToolParameters(inputSchema);
476
+ var description = `Executes a given bash command and returns its output.
477
+
478
+ The working directory persists between commands, but shell state does not. The shell environment is initialized from the user's profile.
479
+
480
+ IMPORTANT: Avoid using this tool to run \`find\`, \`grep\`, \`cat\`, \`head\`, \`tail\`, \`sed\`, \`awk\`, or \`echo\` commands, unless explicitly instructed or after you have verified that a dedicated tool cannot accomplish your task. Instead, use the appropriate dedicated tool as this will provide a much better experience for the user:
481
+
482
+ - File search: Use Glob (NOT find or ls)
483
+ - Content search: Use Grep (NOT grep or rg)
484
+ - Read files: Use Read (NOT cat/head/tail)
485
+ - Edit files: Use Edit (NOT sed/awk)
486
+ - Write files: Use Write (NOT echo >/cat <<EOF)
487
+ - Communication: Output text directly (NOT echo/printf)
488
+ While the Bash tool can do similar things, it's better to use the built-in tools as they provide a better user experience and make it easier to review tool calls and give permission.
489
+
490
+ # Instructions
491
+ - If your command will create new directories or files, first use this tool to run \`ls\` to verify the parent directory exists and is the correct location.
492
+ - Always quote file paths that contain spaces with double quotes in your command (e.g., cd "path with spaces/file.txt").
493
+ - Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of \`cd\`. You may use \`cd\` if the User explicitly requests it.
494
+ - You may specify an optional timeout in milliseconds (up to ${MAX_TIMEOUT_MS}ms / ${MAX_TIMEOUT_MS / 6e4} minutes). By default, your command will timeout after ${DEFAULT_TIMEOUT_MS}ms (${DEFAULT_TIMEOUT_MS / 6e4} minutes).
495
+ - When issuing multiple commands:
496
+ - If the commands are independent and can run in parallel, make multiple Bash tool calls in a single message.
497
+ - If the commands depend on each other and must run sequentially, use a single Bash call with '&&' to chain them.
498
+ - Use ';' only when you need to run commands sequentially but don't care if earlier commands fail.
499
+ - DO NOT use newlines to separate commands (newlines are ok in quoted strings and here-strings).
500
+ - For git commands:
501
+ - Prefer to create a new commit rather than amending an existing commit.
502
+ - Before running destructive operations (e.g., git reset --hard, git push --force, git checkout --), consider whether there is a safer alternative that achieves the same goal. Only use destructive operations when they are truly the best approach.
503
+ - Never skip hooks (--no-verify) or bypass signing (--no-gpg-sign, -c commit.gpgsign=false) unless the user has explicitly asked for it. If a hook fails, investigate and fix the underlying issue.
504
+ - Avoid unnecessary \`sleep\` commands; do not retry failing commands in a sleep loop \u2014 diagnose the root cause.
505
+
506
+ # Safety
507
+ The following command patterns are blocked at the tool level and will fail before execution (no need to try them):
508
+ - \`rm -rf /\` and variants targeting root, $HOME, ~, or system directories (/etc, /usr, /bin, /Windows, /Users, /home, ...)
509
+ - Filesystem destruction: \`mkfs\`, \`format X:\`, \`dd of=/dev/sdX\`, redirects to raw block devices
510
+ - Power management: \`shutdown\`, \`reboot\`, \`halt\`, \`poweroff\`, \`init 0/6\`
511
+ - Fork bombs
512
+ - Pipe-to-shell from network: \`curl ... | sh\`, \`wget ... | bash\`, etc.
513
+ - \`chmod 777 /\`, Windows full-disk \`del /s\` / \`rmdir /s\`, \`diskpart\`
514
+
515
+ If you have a legitimate use case that requires one of the above patterns, ask the user to run the command themselves in their terminal \u2014 do not try to bypass the check.`;
516
+ async function call(input, signal) {
517
+ const command = input.command;
518
+ const timeoutMs = Math.min(input.timeout ?? DEFAULT_TIMEOUT_MS, MAX_TIMEOUT_MS);
519
+ const forbiddenReason = checkForbidden(command);
520
+ if (forbiddenReason) {
521
+ return {
522
+ ok: false,
523
+ error: `\u5B89\u5168\u68C0\u67E5\u62D2\u7EDD\u6267\u884C\uFF1A${forbiddenReason}
524
+ \u547D\u4EE4\uFF1A${command}`
525
+ };
526
+ }
527
+ let stdout = "";
528
+ let stderr = "";
529
+ let exitCode = null;
530
+ let timedOut = false;
531
+ let killedBySignal = false;
532
+ const ac = new AbortController();
533
+ const onAbort = () => ac.abort();
534
+ signal?.addEventListener("abort", onAbort, { once: true });
535
+ const timer = setTimeout(() => {
536
+ timedOut = true;
537
+ ac.abort();
538
+ }, timeoutMs);
539
+ try {
540
+ await new Promise((resolveP, rejectP) => {
541
+ const child = spawn(command, {
542
+ shell: true,
543
+ cwd: process.cwd(),
544
+ signal: ac.signal,
545
+ env: process.env,
546
+ windowsHide: true
547
+ });
548
+ child.stdout?.setEncoding("utf8");
549
+ child.stderr?.setEncoding("utf8");
550
+ child.stdout?.on("data", (chunk) => {
551
+ stdout += chunk;
552
+ });
553
+ child.stderr?.on("data", (chunk) => {
554
+ stderr += chunk;
555
+ });
556
+ child.on("error", (e) => {
557
+ if (e.code === "ABORT_ERR") {
558
+ killedBySignal = true;
559
+ resolveP();
560
+ } else {
561
+ rejectP(e);
562
+ }
563
+ });
564
+ child.on("close", (code, sig) => {
565
+ exitCode = code;
566
+ if (sig) killedBySignal = true;
567
+ resolveP();
568
+ });
569
+ });
570
+ } catch (e) {
571
+ return {
572
+ ok: false,
573
+ error: `\u6267\u884C\u547D\u4EE4\u5931\u8D25\uFF1A${e.message}
574
+ \u547D\u4EE4\uFF1A${command}`
575
+ };
576
+ } finally {
577
+ clearTimeout(timer);
578
+ signal?.removeEventListener("abort", onAbort);
579
+ }
580
+ const parts = [];
581
+ if (stdout) parts.push(`<stdout>
582
+ ${stdout.replace(/\s+$/, "")}
583
+ </stdout>`);
584
+ if (stderr) parts.push(`<stderr>
585
+ ${stderr.replace(/\s+$/, "")}
586
+ </stderr>`);
587
+ if (parts.length === 0) parts.push("<no output>");
588
+ if (timedOut) {
589
+ parts.push(`
590
+ [\u8D85\u65F6 ${timeoutMs}ms \u540E\u88AB\u4E2D\u65AD]`);
591
+ } else if (killedBySignal && !timedOut) {
592
+ parts.push(`
593
+ [\u88AB\u4FE1\u53F7\u4E2D\u65AD]`);
594
+ }
595
+ parts.push(`
596
+ [exit code: ${exitCode === null ? "killed" : exitCode}]`);
597
+ let content = parts.join("\n");
598
+ if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
599
+ content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
600
+
601
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
602
+ }
603
+ if (timedOut || exitCode !== null && exitCode !== 0 || killedBySignal) {
604
+ return { ok: false, error: content };
605
+ }
606
+ return { ok: true, content };
607
+ }
608
+ var bashTool = {
609
+ name: "Bash",
610
+ description,
611
+ inputSchema,
612
+ parameters,
613
+ isReadOnly: false,
614
+ isConcurrencySafe: false,
615
+ // 默认按"会改状态"算;保守不并发
616
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
617
+ call
618
+ };
619
+
620
+ // src/tools/edit/edit.ts
621
+ import { readFile as readFile5, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
622
+ import { existsSync } from "fs";
623
+ import { dirname as dirname4, resolve } from "path";
624
+ import { z as z2 } from "zod";
625
+ var MAX_EDIT_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
626
+ var inputSchema2 = z2.object({
627
+ file_path: z2.string().min(1).describe("\u8981\u7F16\u8F91\u7684\u6587\u4EF6\u8DEF\u5F84"),
628
+ old_string: z2.string().describe(
629
+ "\u8981\u66FF\u6362\u7684\u539F\u6587\u672C\uFF08\u5FC5\u987B\u5728\u6587\u4EF6\u4E2D\u552F\u4E00\uFF0C\u9664\u975E replace_all=true\uFF09\uFF1B\u4E3A\u7A7A\u5B57\u7B26\u4E32\u4E14\u6587\u4EF6\u4E0D\u5B58\u5728\u65F6\u521B\u5EFA\u65B0\u6587\u4EF6"
630
+ ),
631
+ new_string: z2.string().describe("\u66FF\u6362\u4E3A\u7684\u65B0\u6587\u672C\uFF08\u4E0E old_string \u5FC5\u987B\u4E0D\u540C\uFF09"),
632
+ replace_all: z2.boolean().optional().describe("\u662F\u5426\u66FF\u6362\u6240\u6709\u51FA\u73B0\u4F4D\u7F6E\uFF08\u9ED8\u8BA4 false\uFF09")
633
+ });
634
+ var parameters2 = toToolParameters(inputSchema2);
635
+ var description2 = `Performs exact string replacements in files.
636
+
637
+ Usage:
638
+ - You MUST use your \`Read\` tool to read the current content of the file BEFORE calling Edit.
639
+ Memory is unreliable \u2014 if you think you "remember" how the code looks, you are probably wrong.
640
+ Re-read the relevant sections (function, module, or area you plan to change) every time.
641
+ - To create a new file, pass an empty \`old_string\` and the desired contents as \`new_string\`.
642
+ - ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
643
+ - The edit will FAIL if \`old_string\` is not unique in the file. Either provide a larger string with more surrounding context to make it unique or use \`replace_all\` to change every instance of \`old_string\`.
644
+ - Use \`replace_all\` for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
645
+ - Preserve exact indentation (tabs/spaces).`;
646
+ function validatePath(filePath) {
647
+ if (filePath.includes("\0")) {
648
+ return { ok: false, error: "\u8DEF\u5F84\u5305\u542B\u975E\u6CD5\u5B57\u7B26\uFF08null byte\uFF09" };
649
+ }
650
+ if (process.platform === "win32" && filePath.includes("\\\\")) {
651
+ return { ok: false, error: "\u4E0D\u652F\u6301 UNC \u8DEF\u5F84\uFF08\\\\server\\share \u683C\u5F0F\uFF09\uFF0C\u8BF7\u4F7F\u7528\u672C\u5730\u8DEF\u5F84" };
652
+ }
653
+ return { ok: true };
654
+ }
655
+ async function call2(input) {
656
+ const filePath = resolve(input.file_path);
657
+ const { old_string, new_string } = input;
658
+ const replaceAll = input.replace_all ?? false;
659
+ const pathCheck = validatePath(filePath);
660
+ if (!pathCheck.ok) return pathCheck;
661
+ if (old_string === new_string) {
662
+ return { ok: false, error: "old_string \u4E0E new_string \u5B8C\u5168\u76F8\u540C\uFF0C\u6CA1\u6709\u53EF\u6539\u7684\u5185\u5BB9\u3002" };
663
+ }
664
+ if (old_string === "" && !existsSync(filePath)) {
665
+ try {
666
+ await mkdir3(dirname4(filePath), { recursive: true });
667
+ await writeFile3(filePath, new_string, "utf8");
668
+ return {
669
+ ok: true,
670
+ content: `\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6\uFF1A${filePath}\uFF08${new_string.length} \u5B57\u7B26\uFF09`
671
+ };
672
+ } catch (e) {
673
+ return { ok: false, error: `\u521B\u5EFA\u6587\u4EF6\u5931\u8D25\uFF1A${e.message}` };
674
+ }
675
+ }
676
+ if (!existsSync(filePath)) {
677
+ return {
678
+ ok: false,
679
+ error: `\u6587\u4EF6\u4E0D\u5B58\u5728\uFF1A${filePath}
680
+ \uFF08\u8981\u521B\u5EFA\u65B0\u6587\u4EF6\uFF0C\u8BF7\u628A old_string \u8BBE\u4E3A\u7A7A\u5B57\u7B26\u4E32\uFF09`
681
+ };
682
+ }
683
+ let original;
684
+ try {
685
+ original = await readFile5(filePath, "utf8");
686
+ } catch (e) {
687
+ return { ok: false, error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
688
+ }
689
+ const fileSize = Buffer.byteLength(original, "utf8");
690
+ if (fileSize > MAX_EDIT_FILE_SIZE_BYTES) {
691
+ return {
692
+ ok: false,
693
+ error: `\u6587\u4EF6\u8FC7\u5927\uFF08${fileSize} \u5B57\u8282 > ${MAX_EDIT_FILE_SIZE_BYTES} \u5B57\u8282 \u2248 1GB\uFF09\u3002Edit \u4E0D\u9002\u5408\u64CD\u4F5C\u8D85\u5927\u6587\u4EF6\uFF0C\u8BF7\u7528 Write \u5DE5\u5177\u3002`
694
+ };
695
+ }
696
+ if (old_string === "") {
697
+ return {
698
+ ok: false,
699
+ error: "old_string \u4E3A\u7A7A\u4F46\u6587\u4EF6\u5DF2\u5B58\u5728 \u2014\u2014 \u8FD9\u901A\u5E38\u662F\u9519\u8BEF\u7684\u3002\u8981\u66FF\u6362\u5168\u6587\u8BF7\u7528 Write \u5DE5\u5177\u3002"
700
+ };
701
+ }
702
+ const occurrences = countOccurrences(original, old_string);
703
+ if (occurrences === 0) {
704
+ const hint = findFuzzyMatchHint(original, old_string);
705
+ const extraMsg = hint ? `
706
+
707
+ \u{1F4A1} \u63D0\u793A\uFF1A${hint}` : "";
708
+ return {
709
+ ok: false,
710
+ error: `\u5728 ${filePath} \u4E2D\u627E\u4E0D\u5230 old_string\u3002\u8BF7\u5148\u7528 Read \u5DE5\u5177\u6838\u5BF9\u5185\u5BB9\uFF08\u6CE8\u610F\u7A7A\u683C/\u7F29\u8FDB/\u6362\u884C\uFF09\u3002${extraMsg}`
711
+ };
712
+ }
713
+ if (occurrences > 1 && !replaceAll) {
714
+ return {
715
+ ok: false,
716
+ error: `old_string \u5728\u6587\u4EF6\u4E2D\u51FA\u73B0\u4E86 ${occurrences} \u6B21\uFF0C\u4E0D\u552F\u4E00\u3002
717
+ \u8BF7\u6269\u5927 old_string \u5305\u542B\u66F4\u591A\u4E0A\u4E0B\u6587\uFF0C\u6216\u663E\u5F0F\u4F20 replace_all=true\u3002`
718
+ };
719
+ }
720
+ const replaced = replaceAll ? splitReplaceAll(original, old_string, new_string) : original.replace(old_string, new_string);
721
+ try {
722
+ await writeFile3(filePath, replaced, "utf8");
723
+ } catch (e) {
724
+ return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
725
+ }
726
+ const linesBefore = original.split("\n").length;
727
+ const linesAfter = replaced.split("\n").length;
728
+ return {
729
+ ok: true,
730
+ content: `\u5DF2\u7F16\u8F91 ${filePath}
731
+ \u66FF\u6362\u6B21\u6570\uFF1A${replaceAll ? occurrences : 1}
732
+ \u884C\u6570\uFF1A${linesBefore} \u2192 ${linesAfter}`
733
+ };
734
+ }
735
+ function countOccurrences(haystack, needle) {
736
+ if (needle.length === 0) return 0;
737
+ let count = 0;
738
+ let pos = 0;
739
+ while ((pos = haystack.indexOf(needle, pos)) !== -1) {
740
+ count++;
741
+ pos += needle.length;
742
+ }
743
+ return count;
744
+ }
745
+ function splitReplaceAll(haystack, needle, replacement) {
746
+ return haystack.split(needle).join(replacement);
747
+ }
748
+ function findFuzzyMatchHint(fileContent, target) {
749
+ const MIN_OVERLAP_RATIO = 0.5;
750
+ const MAX_HINTS = 3;
751
+ const targetTrimmed = target.trim();
752
+ if (targetTrimmed.length < 5) return null;
753
+ const lines = fileContent.split("\n");
754
+ const candidates = [];
755
+ for (let i = 0; i < lines.length; i++) {
756
+ const line = lines[i];
757
+ const trimmedLine = line.trim();
758
+ if (trimmedLine.length < 5) continue;
759
+ const ratio = lcsRatio(targetTrimmed, trimmedLine);
760
+ if (ratio >= MIN_OVERLAP_RATIO) {
761
+ candidates.push({ lineNum: i + 1, text: trimmedLine, ratio });
762
+ }
763
+ }
764
+ if (target.includes("\n") && candidates.length < MAX_HINTS) {
765
+ const targetLines = target.split("\n").filter((l) => l.trim().length > 0);
766
+ if (targetLines.length >= 2) {
767
+ for (let start = 0; start <= lines.length - targetLines.length; start++) {
768
+ const window = lines.slice(start, start + targetLines.length).map((l) => l.trim()).join("\n");
769
+ const ratio = lcsRatio(target.trim(), window);
770
+ if (ratio >= MIN_OVERLAP_RATIO) {
771
+ candidates.push({ lineNum: start + 1, text: window.slice(0, 80) + (window.length > 80 ? "..." : ""), ratio });
772
+ }
773
+ }
774
+ }
775
+ }
776
+ if (candidates.length === 0) return null;
777
+ candidates.sort((a, b) => b.ratio - a.ratio);
778
+ const top = candidates.slice(0, MAX_HINTS);
779
+ const hints = top.map(
780
+ (c) => `\u7B2C ${c.lineNum} \u884C\u9644\u8FD1\u6709\u76F8\u4F3C\u5185\u5BB9\uFF08\u76F8\u4F3C\u5EA6 ${(c.ratio * 100).toFixed(0)}%\uFF09:
781
+ "${c.text.slice(0, 100)}${c.text.length > 100 ? "\u2026" : ""}"`
782
+ );
783
+ return `\u53EF\u80FD\u4F60\u60F3\u8981\u4FEE\u6539\u4EE5\u4E0B\u4F4D\u7F6E\u4E4B\u4E00\uFF1F
784
+ ${hints.join("\n ")}`;
785
+ }
786
+ function lcsRatio(a, b) {
787
+ const m = a.length;
788
+ const n = b.length;
789
+ const MAX_LEN = 500;
790
+ const aa = m > MAX_LEN ? a.slice(0, MAX_LEN) : a;
791
+ const bb = n > MAX_LEN ? b.slice(0, MAX_LEN) : b;
792
+ const mm = aa.length;
793
+ const nn = bb.length;
794
+ const dp = new Array(nn + 1).fill(0);
795
+ for (let i = 1; i <= mm; i++) {
796
+ let prev = 0;
797
+ for (let j = 1; j <= nn; j++) {
798
+ const temp = dp[j];
799
+ if (aa[i - 1] === bb[j - 1]) {
800
+ dp[j] = prev + 1;
801
+ } else {
802
+ dp[j] = Math.max(dp[j], dp[j - 1]);
803
+ }
804
+ prev = temp;
805
+ }
806
+ }
807
+ return dp[nn] / Math.max(mm, nn);
808
+ }
809
+ var editTool = {
810
+ name: "Edit",
811
+ description: description2,
812
+ inputSchema: inputSchema2,
813
+ parameters: parameters2,
814
+ isReadOnly: false,
815
+ isConcurrencySafe: false,
816
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
817
+ call: call2
818
+ };
819
+
820
+ // src/tools/glob/glob.ts
821
+ import { stat } from "fs/promises";
822
+ import { isAbsolute, resolve as resolve2 } from "path";
823
+ import fg from "fast-glob";
824
+ import { z as z3 } from "zod";
825
+ var inputSchema3 = z3.object({
826
+ pattern: z3.string().min(1).describe('glob \u6A21\u5F0F\uFF0C\u4F8B\u5982 "**/*.ts" \u6216 "src/components/**/*.tsx"'),
827
+ path: z3.string().optional().describe('\u641C\u7D22\u7684\u6839\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09\uFF1B\u7701\u7565\u65F6\u4E0D\u8981\u4F20 "undefined" \u5B57\u7B26\u4E32')
828
+ });
829
+ var parameters3 = toToolParameters(inputSchema3);
830
+ var description3 = `- Fast file pattern matching tool that works with any codebase size
831
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
832
+ - Returns matching file paths sorted by modification time (oldest first)
833
+ - Use this tool when you need to find files by name patterns
834
+ - When you need to do an open ended search that may require multiple rounds, prefer the Grep tool for content search`;
835
+ async function call3(input) {
836
+ const cwd = input.path ? resolve2(input.path) : process.cwd();
837
+ const pattern = input.pattern.replace(/\\/g, "/");
838
+ let matches;
839
+ try {
840
+ matches = await fg(pattern, {
841
+ cwd,
842
+ dot: true,
843
+ onlyFiles: true,
844
+ followSymbolicLinks: false,
845
+ // 默认排除一些噪音目录;如果用户的 pattern 显式指了它们,这里也不会盖
846
+ ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
847
+ absolute: false
848
+ });
849
+ } catch (e) {
850
+ return { ok: false, error: `glob \u5931\u8D25\uFF1A${e.message}` };
851
+ }
852
+ if (matches.length === 0) {
853
+ return { ok: true, content: `(no files matched pattern "${pattern}" in ${cwd})` };
854
+ }
855
+ const withMtime = await Promise.all(
856
+ matches.map(async (rel) => {
857
+ const abs = isAbsolute(rel) ? rel : resolve2(cwd, rel);
858
+ try {
859
+ const st = await stat(abs);
860
+ return { path: rel, mtime: st.mtimeMs };
861
+ } catch {
862
+ return { path: rel, mtime: 0 };
863
+ }
864
+ })
865
+ );
866
+ withMtime.sort((a, b) => a.mtime - b.mtime);
867
+ let content = withMtime.map((m) => m.path).join("\n");
868
+ if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
869
+ content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
870
+
871
+ ... (\u5171 ${withMtime.length} \u4E2A\u6587\u4EF6\uFF0C\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD\u5C3E\u90E8\u65B0\u6587\u4EF6)`;
872
+ }
873
+ return {
874
+ ok: true,
875
+ content: `${content}
876
+
877
+ (\u5171 ${withMtime.length} \u4E2A\u6587\u4EF6\uFF0C\u6309\u4FEE\u6539\u65F6\u95F4\u5347\u5E8F/\u65E7\u5728\u524D)`
878
+ };
879
+ }
880
+ var globTool = {
881
+ name: "Glob",
882
+ description: description3,
883
+ inputSchema: inputSchema3,
884
+ parameters: parameters3,
885
+ isReadOnly: true,
886
+ isConcurrencySafe: true,
887
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
888
+ call: call3
889
+ };
890
+
891
+ // src/tools/grep/grep.ts
892
+ import { spawn as spawn3 } from "child_process";
893
+ import { resolve as resolve4 } from "path";
894
+ import { z as z4 } from "zod";
895
+
896
+ // src/tools/grep/rgPath.ts
897
+ import { spawn as spawn2 } from "child_process";
898
+ import { chmodSync, existsSync as existsSync2 } from "fs";
899
+ import { dirname as dirname5, resolve as resolve3 } from "path";
900
+ import { fileURLToPath as fileURLToPath2 } from "url";
901
+ var cached;
902
+ async function resolveRgPath() {
903
+ if (cached !== void 0) return cached;
904
+ cached = await detect();
905
+ return cached;
906
+ }
907
+ async function detect() {
908
+ const fromEnv = process.env.MINIMAL_AGENT_RIPGREP_PATH;
909
+ if (fromEnv && existsSync2(fromEnv)) return fromEnv;
910
+ const vendored = vendoredRgPath();
911
+ if (vendored && existsSync2(vendored)) {
912
+ ensureExecutable(vendored);
913
+ return vendored;
914
+ }
915
+ if (await trySpawn("rg")) return "rg";
916
+ for (const candidate of claudeCodeCandidates()) {
917
+ if (existsSync2(candidate)) {
918
+ ensureExecutable(candidate);
919
+ return candidate;
920
+ }
921
+ }
922
+ return null;
923
+ }
924
+ function vendoredRgPath() {
925
+ try {
926
+ const here = dirname5(fileURLToPath2(import.meta.url));
927
+ const projectRoot = resolve3(here, "..", "..");
928
+ return resolve3(projectRoot, "vendor", "ripgrep", subdir(), exeName());
929
+ } catch {
930
+ return null;
931
+ }
932
+ }
933
+ function subdir() {
934
+ return `${process.arch}-${process.platform}`;
935
+ }
936
+ function exeName() {
937
+ return process.platform === "win32" ? "rg.exe" : "rg";
938
+ }
939
+ function ensureExecutable(path2) {
940
+ if (process.platform === "win32") return;
941
+ try {
942
+ chmodSync(path2, 493);
943
+ } catch {
944
+ }
945
+ }
946
+ function trySpawn(cmd) {
947
+ return new Promise((resolveP) => {
948
+ let settled = false;
949
+ const done = (ok) => {
950
+ if (settled) return;
951
+ settled = true;
952
+ resolveP(ok);
953
+ };
954
+ try {
955
+ const child = spawn2(cmd, ["--version"], {
956
+ stdio: ["ignore", "pipe", "ignore"],
957
+ windowsHide: true
958
+ });
959
+ let stdout = "";
960
+ child.stdout.setEncoding("utf8");
961
+ child.stdout.on("data", (c) => stdout += c);
962
+ child.on("error", () => done(false));
963
+ child.on(
964
+ "close",
965
+ (code) => done(code === 0 && stdout.startsWith("ripgrep "))
966
+ );
967
+ setTimeout(() => {
968
+ try {
969
+ child.kill();
970
+ } catch {
971
+ }
972
+ done(false);
973
+ }, 5e3);
974
+ } catch {
975
+ done(false);
976
+ }
977
+ });
978
+ }
979
+ function claudeCodeCandidates() {
980
+ const arch = process.arch;
981
+ const platform = process.platform;
982
+ const subdir2 = `${arch}-${platform}`;
983
+ const exe = platform === "win32" ? "rg.exe" : "rg";
984
+ const npmRoots = [];
985
+ if (platform === "win32") {
986
+ if (process.env.APPDATA) {
987
+ npmRoots.push(resolve3(process.env.APPDATA, "npm", "node_modules"));
988
+ }
989
+ if (process.env.USERPROFILE) {
990
+ npmRoots.push(
991
+ resolve3(process.env.USERPROFILE, "AppData", "Roaming", "npm", "node_modules")
992
+ );
993
+ }
994
+ } else {
995
+ const home = process.env.HOME ?? "";
996
+ if (home) {
997
+ npmRoots.push(resolve3(home, ".npm-global", "lib", "node_modules"));
998
+ npmRoots.push(resolve3(home, ".npm", "lib", "node_modules"));
999
+ npmRoots.push(resolve3(home, "node_modules"));
1000
+ }
1001
+ npmRoots.push("/usr/local/lib/node_modules");
1002
+ npmRoots.push("/usr/lib/node_modules");
1003
+ npmRoots.push("/opt/homebrew/lib/node_modules");
1004
+ }
1005
+ return npmRoots.map(
1006
+ (root) => resolve3(root, "@anthropic-ai", "claude-code", "vendor", "ripgrep", subdir2, exe)
1007
+ );
1008
+ }
1009
+
1010
+ // src/tools/grep/grep.ts
1011
+ var inputSchema4 = z4.object({
1012
+ pattern: z4.string().min(1).describe("\u6B63\u5219\u8868\u8FBE\u5F0F\uFF08ripgrep \u517C\u5BB9\u8BED\u6CD5\uFF09"),
1013
+ path: z4.string().optional().describe("\u641C\u7D22\u7684\u6839\u76EE\u5F55\u6216\u6587\u4EF6\uFF08\u9ED8\u8BA4\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\uFF09"),
1014
+ glob: z4.string().optional().describe('\u6587\u4EF6\u540D glob \u8FC7\u6EE4\uFF0C\u5982 "*.ts"'),
1015
+ type: z4.string().optional().describe('rg \u7684\u6587\u4EF6\u7C7B\u578B\u5FEB\u6377\u540D\uFF0C\u5982 "py"\u3001"rust"\u3001"js"'),
1016
+ output_mode: z4.enum(["content", "files_with_matches", "count"]).optional().describe("\u8F93\u51FA\u6A21\u5F0F\uFF1Acontent=\u5339\u914D\u884C\uFF1Bfiles_with_matches=\u53EA\u5217\u6587\u4EF6\uFF1Bcount=\u6BCF\u6587\u4EF6\u8BA1\u6570"),
1017
+ "-i": z4.boolean().optional().describe("\u5FFD\u7565\u5927\u5C0F\u5199"),
1018
+ "-n": z4.boolean().optional().describe("\u663E\u793A\u884C\u53F7\uFF08\u4EC5 content \u6A21\u5F0F\uFF09"),
1019
+ "-A": z4.number().int().min(0).optional().describe("\u5339\u914D\u540E\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1020
+ "-B": z4.number().int().min(0).optional().describe("\u5339\u914D\u524D\u5C55\u793A\u51E0\u884C\u4E0A\u4E0B\u6587"),
1021
+ "-C": z4.number().int().min(0).optional().describe("\u5339\u914D\u524D\u540E\u5404\u5C55\u793A\u51E0\u884C\uFF08\u8986\u76D6 -A/-B\uFF09"),
1022
+ head_limit: z4.number().int().positive().optional().describe("\u8F93\u51FA\u6700\u591A\u4FDD\u7559\u524D N \u884C\uFF08\u9632\u6B62\u7ED3\u679C\u8FC7\u5927\uFF09")
1023
+ });
1024
+ var parameters4 = toToolParameters(inputSchema4);
1025
+ var description4 = `A powerful search tool built on ripgrep.
1026
+
1027
+ Usage:
1028
+ - ALWAYS use Grep for content search tasks. Do NOT invoke \`grep\` or \`rg\` directly via Bash.
1029
+ - Supports full regex syntax (e.g., "log.*Error", "function\\s+\\w+")
1030
+ - Filter files with glob parameter (e.g., "*.js", "**/*.tsx") or type parameter (e.g., "js", "py", "rust")
1031
+ - Output modes: "content" shows matching lines, "files_with_matches" shows only file paths (default), "count" shows match counts
1032
+ - Pattern syntax: Uses ripgrep (not classic grep)`;
1033
+ async function call4(input, signal) {
1034
+ const args = [];
1035
+ const mode = input.output_mode ?? "files_with_matches";
1036
+ if (mode === "files_with_matches") args.push("-l");
1037
+ else if (mode === "count") args.push("--count-matches");
1038
+ if (input["-i"]) args.push("-i");
1039
+ if (mode === "content" && input["-n"]) args.push("-n");
1040
+ if (typeof input["-C"] === "number") {
1041
+ args.push("-C", String(input["-C"]));
1042
+ } else {
1043
+ if (typeof input["-A"] === "number") args.push("-A", String(input["-A"]));
1044
+ if (typeof input["-B"] === "number") args.push("-B", String(input["-B"]));
1045
+ }
1046
+ if (input.glob) args.push("--glob", input.glob);
1047
+ if (input.type) args.push("--type", input.type);
1048
+ args.push("--max-columns", "500");
1049
+ args.push("--max-columns-preview");
1050
+ args.push("--sort", "modified");
1051
+ args.push("-e", input.pattern);
1052
+ args.push(input.path ? resolve4(input.path) : ".");
1053
+ const rgPath = await resolveRgPath();
1054
+ if (!rgPath) {
1055
+ return {
1056
+ ok: false,
1057
+ error: "\u627E\u4E0D\u5230 ripgrep\uFF08rg\uFF09\u3002\u53EF\u4EE5\u4E09\u9009\u4E00\uFF1A\n \u2460 \u88C5 Claude Code\uFF08\u81EA\u5E26 rg\uFF09\uFF1Anpm i -g @anthropic-ai/claude-code\n \u2461 \u7CFB\u7EDF\u88C5 ripgrep\uFF1A\n macOS: brew install ripgrep\n Windows: scoop install ripgrep\n Ubuntu: sudo apt install ripgrep\n \u2462 \u5728 .env \u8BBE\u7F6E MINIMAL_AGENT_RIPGREP_PATH=/\u7EDD\u5BF9/\u8DEF\u5F84/\u5230/rg"
1058
+ };
1059
+ }
1060
+ let stdout = "";
1061
+ let stderr = "";
1062
+ let exitCode = null;
1063
+ try {
1064
+ await new Promise((resolveP, rejectP) => {
1065
+ const child = spawn3(rgPath, args, {
1066
+ cwd: process.cwd(),
1067
+ signal,
1068
+ windowsHide: true
1069
+ });
1070
+ child.stdout.setEncoding("utf8");
1071
+ child.stderr.setEncoding("utf8");
1072
+ child.stdout.on("data", (chunk) => stdout += chunk);
1073
+ child.stderr.on("data", (chunk) => stderr += chunk);
1074
+ child.on("error", (e) => rejectP(e));
1075
+ child.on("close", (code) => {
1076
+ exitCode = code;
1077
+ resolveP();
1078
+ });
1079
+ });
1080
+ } catch (e) {
1081
+ const err = e;
1082
+ return { ok: false, error: `\u6267\u884C rg \u5931\u8D25\uFF08${rgPath}\uFF09\uFF1A${err.message}` };
1083
+ }
1084
+ if (exitCode === 1) {
1085
+ return { ok: true, content: `(no matches for pattern: ${input.pattern})` };
1086
+ }
1087
+ if (exitCode !== 0) {
1088
+ return {
1089
+ ok: false,
1090
+ error: `rg \u9000\u51FA\u7801 ${exitCode}\uFF1A${stderr.slice(0, 500) || "(\u65E0 stderr)"}`
1091
+ };
1092
+ }
1093
+ let content = stdout;
1094
+ if (input.head_limit && input.head_limit > 0) {
1095
+ const lines = content.split("\n");
1096
+ if (lines.length > input.head_limit) {
1097
+ content = lines.slice(0, input.head_limit).join("\n") + `
1098
+ ... (\u5DF2\u622A\u65AD\u5230 ${input.head_limit} \u884C / \u5171 ${lines.length} \u884C\uFF0C\u4FDD\u7559\u6700\u65B0\u5339\u914D)`;
1099
+ }
1100
+ }
1101
+ if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
1102
+ content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
1103
+
1104
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD\u5C3E\u90E8\u65E7\u7ED3\u679C)`;
1105
+ }
1106
+ return { ok: true, content };
1107
+ }
1108
+ var grepTool = {
1109
+ name: "Grep",
1110
+ description: description4,
1111
+ inputSchema: inputSchema4,
1112
+ parameters: parameters4,
1113
+ isReadOnly: true,
1114
+ isConcurrencySafe: true,
1115
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
1116
+ call: call4
1117
+ };
1118
+
1119
+ // src/tools/read/read.ts
1120
+ import { readFile as readFile6, stat as stat2 } from "fs/promises";
1121
+ import { resolve as resolve5 } from "path";
1122
+ import { z as z5 } from "zod";
1123
+ var inputSchema5 = z5.object({
1124
+ file_path: z5.string().min(1, "\u5FC5\u987B\u63D0\u4F9B file_path").describe("\u8981\u8BFB\u53D6\u7684\u6587\u4EF6\u8DEF\u5F84\uFF0C\u7EDD\u5BF9\u8DEF\u5F84\u4F18\u5148"),
1125
+ offset: z5.number().int().positive().optional().describe("\u8D77\u59CB\u884C\u53F7\uFF081-indexed\uFF09\uFF1B\u4E0D\u586B\u5219\u4ECE\u6587\u4EF6\u5F00\u5934\u8BFB"),
1126
+ limit: z5.number().int().positive().optional().describe(`\u6700\u591A\u8BFB\u591A\u5C11\u884C\uFF1B\u4E0D\u586B\u5219\u7528\u9ED8\u8BA4\u503C ${MAX_LINES_TO_READ}`)
1127
+ });
1128
+ var parameters5 = toToolParameters(inputSchema5);
1129
+ var description5 = `Reads a file from the local filesystem. You can access any file directly by using this tool.
1130
+ Assume this tool is able to read all files on the machine. If the User provides a path to a file assume that path is valid. It is okay to read a file that does not exist; an error will be returned.
1131
+
1132
+ Usage:
1133
+ - The file_path parameter must be an absolute path, not a relative path
1134
+ - By default, it reads up to ${MAX_LINES_TO_READ} lines starting from the beginning of the file
1135
+ - You can optionally specify a line offset and limit (especially handy for long files)
1136
+ - Results are returned using cat -n format, with line numbers starting at 1
1137
+ - This tool can only read text files, not directories. To read a directory, use the Glob tool.
1138
+ - If you read a file that exists but has empty contents you will receive a warning in place of file contents.`;
1139
+ async function call5(input) {
1140
+ const filePath = resolve5(input.file_path);
1141
+ const offset = input.offset ?? 1;
1142
+ const limit = input.limit ?? MAX_LINES_TO_READ;
1143
+ let st;
1144
+ try {
1145
+ st = await stat2(filePath);
1146
+ } catch (e) {
1147
+ return {
1148
+ ok: false,
1149
+ error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${filePath} \u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE\uFF08${e.message}\uFF09`
1150
+ };
1151
+ }
1152
+ if (!st.isFile()) {
1153
+ return { ok: false, error: `${filePath} \u4E0D\u662F\u6587\u4EF6\uFF08\u53EF\u80FD\u662F\u76EE\u5F55\uFF09\u3002\u8981\u5217\u76EE\u5F55\u7528 Glob \u5DE5\u5177\u3002` };
1154
+ }
1155
+ if (st.size > MAX_FILE_SIZE_BYTES) {
1156
+ return {
1157
+ ok: false,
1158
+ error: `\u6587\u4EF6\u8FC7\u5927\uFF08${st.size} \u5B57\u8282 > ${MAX_FILE_SIZE_BYTES}\uFF09\u3002\u8BF7\u7528 offset/limit \u5206\u6BB5\u8BFB\u3002`
1159
+ };
1160
+ }
1161
+ let raw;
1162
+ try {
1163
+ raw = await readFile6(filePath, "utf8");
1164
+ } catch (e) {
1165
+ return { ok: false, error: `\u8BFB\u53D6\u5931\u8D25\uFF1A${e.message}` };
1166
+ }
1167
+ if (raw.length === 0) {
1168
+ return { ok: true, content: "<file is empty>" };
1169
+ }
1170
+ const allLines = raw.split("\n");
1171
+ const totalLines = allLines.length;
1172
+ const startIdx = Math.max(0, offset - 1);
1173
+ const endIdx = Math.min(totalLines, startIdx + limit);
1174
+ const slice = allLines.slice(startIdx, endIdx);
1175
+ const numbered = slice.map((line, i) => `${(startIdx + i + 1).toString()} ${line}`).join("\n");
1176
+ let content = numbered;
1177
+ const contentLength = content.length;
1178
+ if (contentLength > DEFAULT_MAX_RESULT_SIZE_CHARS) {
1179
+ content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
1180
+
1181
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
1182
+ }
1183
+ if (endIdx < totalLines) {
1184
+ const nextOffset = endIdx + 1;
1185
+ content += `
1186
+
1187
+ ... (\u672C\u6B21\u8FD4\u56DE ${slice.length} \u884C / \u6587\u4EF6\u5171 ${totalLines} \u884C\uFF1B\u7528 offset=${nextOffset} \u7EE7\u7EED\u8BFB)`;
1188
+ }
1189
+ if (st.size > 100 * 1024 && offset === 1) {
1190
+ content += `
1191
+
1192
+ \u26A0\uFE0F \u6CE8\u610F\uFF1A\u8FD9\u662F\u4E00\u4E2A\u5927\u6587\u4EF6\uFF08${(st.size / 1024).toFixed(1)} KB\uFF09\u3002\u5EFA\u8BAE\u7528 offset/limit \u5206\u6BB5\u8BFB\u53D6\uFF0C\u4F8B\u5982\u5148\u8BFB\u5173\u952E\u90E8\u5206\uFF08imports\u3001exports\u3001\u51FD\u6570\u7B7E\u540D\uFF09\u3002`;
1193
+ }
1194
+ return { ok: true, content };
1195
+ }
1196
+ var readTool = {
1197
+ name: "Read",
1198
+ description: description5,
1199
+ inputSchema: inputSchema5,
1200
+ parameters: parameters5,
1201
+ isReadOnly: true,
1202
+ isConcurrencySafe: true,
1203
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
1204
+ call: call5
1205
+ };
1206
+
1207
+ // src/tools/webfetch/webfetch.ts
1208
+ import { z as z6 } from "zod";
1209
+
1210
+ // src/tools/webfetch/preapproved.ts
1211
+ var PREAPPROVED_HOSTS = /* @__PURE__ */ new Set([
1212
+ // GitHub
1213
+ "github.com",
1214
+ "gist.github.com",
1215
+ // 开发文档
1216
+ "docs.github.com",
1217
+ "developer.github.com",
1218
+ "help.github.com",
1219
+ // NPM
1220
+ "npmjs.com",
1221
+ "www.npmjs.com",
1222
+ // 包注册表
1223
+ "pypi.org",
1224
+ "www.pypi.org",
1225
+ "crates.io",
1226
+ "pub.dev",
1227
+ "packagist.org",
1228
+ "rubygems.org",
1229
+ // 开发平台
1230
+ "stackoverflow.com",
1231
+ "www.stackoverflow.com",
1232
+ "serverfault.com",
1233
+ "superuser.com",
1234
+ "askubuntu.com",
1235
+ // 文档与 wiki
1236
+ "readthedocs.io",
1237
+ "www.readthedocs.io",
1238
+ "readthedocs.org",
1239
+ "wiki.python.org",
1240
+ "en.wikipedia.org",
1241
+ "zh.wikipedia.org",
1242
+ // 官方文档
1243
+ "nodejs.org",
1244
+ "www.nodejs.org",
1245
+ "deno.land",
1246
+ "www.deno.land",
1247
+ "bun.sh",
1248
+ "www.bun.sh",
1249
+ // Rust
1250
+ "doc.rust-lang.org",
1251
+ "www.rust-lang.org",
1252
+ "rust-lang.org",
1253
+ // 云平台
1254
+ "aws.amazon.com",
1255
+ "docs.aws.amazon.com",
1256
+ "cloud.google.com",
1257
+ "docs.microsoft.com",
1258
+ "azure.microsoft.com",
1259
+ "developer.microsoft.com",
1260
+ // AI/LLM
1261
+ "openai.com",
1262
+ "platform.openai.com",
1263
+ "docs.anthropic.com",
1264
+ "anthropic.com",
1265
+ "claude.ai",
1266
+ "docs.cohere.com",
1267
+ "cohere.com",
1268
+ // AI 模型文档
1269
+ "ai.google.dev",
1270
+ "ai.google.com",
1271
+ "developers.google.com",
1272
+ "learn.deepmind.com",
1273
+ // AI 开发框架
1274
+ "python.langchain.com",
1275
+ "js.langchain.com",
1276
+ "docs.litellm.ai",
1277
+ "litellm.ai",
1278
+ // 前端框架
1279
+ "react.dev",
1280
+ "reactjs.org",
1281
+ "www.reactjs.org",
1282
+ "nextjs.org",
1283
+ "www.nextjs.org",
1284
+ "vuejs.org",
1285
+ "www.vuejs.org",
1286
+ "svelte.dev",
1287
+ "svelte.org",
1288
+ "angular.io",
1289
+ "www.angular.io",
1290
+ // 构建工具
1291
+ "vitejs.dev",
1292
+ "vite.dev",
1293
+ "webpack.js.org",
1294
+ "esbuild.github.io",
1295
+ "rollupjs.org",
1296
+ "esbuild.github.io",
1297
+ // CSS
1298
+ "tailwindcss.com",
1299
+ "www.tailwindcss.com",
1300
+ "postcss.org",
1301
+ // 数据库
1302
+ "redis.io",
1303
+ "www.redis.io",
1304
+ "postgresql.org",
1305
+ "www.postgresql.org",
1306
+ "www.mysql.com",
1307
+ "dev.mysql.com",
1308
+ "docs.mongodb.com",
1309
+ "www.mongodb.com",
1310
+ "sqlite.org",
1311
+ "www.sqlite.org",
1312
+ // 工具类
1313
+ "regex101.com",
1314
+ "ihateregex.io",
1315
+ "explainshell.com",
1316
+ "tldr.sh",
1317
+ // 代码分享
1318
+ "replit.com",
1319
+ "www.replit.com",
1320
+ "codesandbox.io",
1321
+ "www.codesandbox.io",
1322
+ "codepen.io",
1323
+ "www.codepen.io",
1324
+ "jsfiddle.net",
1325
+ "www.jsfiddle.net",
1326
+ // CI/CD
1327
+ "docs.github.com/en/actions",
1328
+ "circleci.com",
1329
+ "docs.circleci.com",
1330
+ "travis-ci.org",
1331
+ "www.travis-ci.com",
1332
+ "jenkins.io",
1333
+ "www.jenkins.io",
1334
+ // 容器/云原生
1335
+ "kubernetes.io",
1336
+ "www.kubernetes.io",
1337
+ "docker.com",
1338
+ "www.docker.com",
1339
+ "docs.docker.com",
1340
+ // 测试
1341
+ "jestjs.io",
1342
+ "www.jestjs.io",
1343
+ "vitest.dev",
1344
+ "testing-library.com",
1345
+ "www.testing-library.com",
1346
+ "playwright.dev",
1347
+ "www.playwright.dev",
1348
+ "webdriver.io",
1349
+ "webdriver.io",
1350
+ // API 文档
1351
+ "httpbin.org",
1352
+ "restfulapi.net",
1353
+ "swagger.io",
1354
+ "www.swagger.io",
1355
+ "openapi.net",
1356
+ // 安全
1357
+ "owasp.org",
1358
+ "www.owasp.org",
1359
+ "cve.mitre.org",
1360
+ // 证书
1361
+ "letsencrypt.org",
1362
+ "www.letsencrypt.org",
1363
+ "acmev2.pki.duckdns.org",
1364
+ // 博客与技术文章
1365
+ "medium.com",
1366
+ "www.medium.com",
1367
+ "dev.to",
1368
+ "www.dev.to",
1369
+ "hashnode.com",
1370
+ "www.hashnode.com",
1371
+ "devblogs.microsoft.com",
1372
+ // 浏览器
1373
+ "caniuse.com",
1374
+ "developer.mozilla.org",
1375
+ "web.dev",
1376
+ "www.w3.org",
1377
+ // 开源项目
1378
+ "apache.org",
1379
+ "www.apache.org",
1380
+ "gnu.org",
1381
+ "www.gnu.org",
1382
+ "fsf.org",
1383
+ "www.fsf.org",
1384
+ "opensource.org",
1385
+ "www.opensource.org",
1386
+ // 技术社区
1387
+ "reddit.com",
1388
+ "www.reddit.com",
1389
+ "news.ycombinator.com",
1390
+ "lobste.rs",
1391
+ // 文件格式
1392
+ "json.org",
1393
+ "yaml.org",
1394
+ "www.yaml.org",
1395
+ "toml.io",
1396
+ "www.toml.io",
1397
+ // 版本控制
1398
+ "git-scm.com",
1399
+ "www.git-scm.com",
1400
+ "github.blog",
1401
+ "githubstatus.com",
1402
+ // AI 搜索
1403
+ "tavily.com",
1404
+ "www.tavily.com",
1405
+ "perplexity.ai",
1406
+ "www.perplexity.ai",
1407
+ // AI 图片
1408
+ "midjourney.com",
1409
+ "www.midjourney.com",
1410
+ "stability.ai",
1411
+ "www.stability.ai",
1412
+ // Embeddings / 向量
1413
+ "qdrant.tech",
1414
+ "www.qdrant.tech",
1415
+ "weaviate.io",
1416
+ "www.weaviate.io",
1417
+ "pinecone.io",
1418
+ "www.pinecone.io",
1419
+ // API 平台
1420
+ "ngrok.com",
1421
+ "www.ngrok.com",
1422
+ "requestly.io",
1423
+ "www.requestly.io",
1424
+ // MCP
1425
+ "modelcontextprotocol.io",
1426
+ "www.modelcontextprotocol.io",
1427
+ "github.com/modelcontextprotocol"
1428
+ ]);
1429
+ var PREAPPROVED_SUFFIXES = [
1430
+ ".github.io",
1431
+ ".readthedocs.io",
1432
+ ".vercel.app",
1433
+ ".vercel.dev",
1434
+ ".netlify.app",
1435
+ ".netlify.com",
1436
+ ".cloudflare-pages.com",
1437
+ ".pages.dev",
1438
+ ".surge.sh",
1439
+ ".herokuapp.com",
1440
+ ".railway.app",
1441
+ ".fly.dev",
1442
+ ".repl.co",
1443
+ ".workers.dev",
1444
+ ".pages.plus",
1445
+ ".coded.app",
1446
+ ".preview.app",
1447
+ ".staging.app"
1448
+ ];
1449
+ function isPreapprovedHost(hostname) {
1450
+ const normalized = hostname.toLowerCase();
1451
+ if (PREAPPROVED_HOSTS.has(normalized)) {
1452
+ return true;
1453
+ }
1454
+ for (const suffix of PREAPPROVED_SUFFIXES) {
1455
+ if (normalized.endsWith(suffix)) {
1456
+ return true;
1457
+ }
1458
+ }
1459
+ return false;
1460
+ }
1461
+ var isPreapprovedDomain = isPreapprovedHost;
1462
+
1463
+ // src/tools/webfetch/webfetch.ts
1464
+ var MAX_URL_LENGTH = 2e3;
1465
+ var FETCH_TIMEOUT_MS = 6e4;
1466
+ var MAX_HTTP_CONTENT_LENGTH = 10 * 1024 * 1024;
1467
+ var MAX_REDIRECTS = 10;
1468
+ var URL_CACHE = /* @__PURE__ */ new Map();
1469
+ var CACHE_TTL_MS = 15 * 60 * 1e3;
1470
+ var MAX_CACHE_SIZE_BYTES = 50 * 1024 * 1024;
1471
+ function cleanCache() {
1472
+ const now = Date.now();
1473
+ let totalSize = 0;
1474
+ const entries = [];
1475
+ for (const [url, entry] of URL_CACHE) {
1476
+ if (now - entry.fetchedAt > CACHE_TTL_MS) {
1477
+ URL_CACHE.delete(url);
1478
+ continue;
1479
+ }
1480
+ const size = entry.bytes;
1481
+ totalSize += size;
1482
+ entries.push({ url, entry, size });
1483
+ }
1484
+ if (totalSize > MAX_CACHE_SIZE_BYTES) {
1485
+ entries.sort((a, b) => a.entry.fetchedAt - b.entry.fetchedAt);
1486
+ for (const { url, size } of entries) {
1487
+ URL_CACHE.delete(url);
1488
+ totalSize -= size;
1489
+ if (totalSize <= MAX_CACHE_SIZE_BYTES * 0.8) break;
1490
+ }
1491
+ }
1492
+ }
1493
+ var inputSchema6 = z6.object({
1494
+ url: z6.string().describe("\u8981\u83B7\u53D6\u5185\u5BB9\u7684 URL"),
1495
+ prompt: z6.string().describe("\u5BF9\u5185\u5BB9\u8FDB\u884C\u5904\u7406\u7684\u6307\u4EE4\uFF0C\u63CF\u8FF0\u4F60\u60F3\u4ECE\u9875\u9762\u63D0\u53D6\u4EC0\u4E48\u4FE1\u606F")
1496
+ });
1497
+ var parameters6 = toToolParameters(inputSchema6);
1498
+ var description6 = `- Fetches content from a specified URL and processes it using an AI model.
1499
+ - Takes a URL and a prompt as input.
1500
+ - Fetches the URL content, converts HTML to markdown.
1501
+ - Processes the content with the prompt (e.g., extract summary, find specific info).
1502
+ - Returns the processed result.
1503
+ - HTTP URLs are automatically upgraded to HTTPS.
1504
+ - When a URL redirects to a different host, returns a warning with the redirect URL.
1505
+ - This tool is read-only and does not modify any files.
1506
+ - Results may be summarized if the content is very large.
1507
+ - \u26A0\uFE0F IMPORTANT: This tool WILL FAIL for authenticated or private URLs.
1508
+ Before using this tool, check if the URL points to an authenticated service
1509
+ (e.g. Google Docs, Confluence, Jira, GitHub private repos). If so, look for
1510
+ a specialized MCP tool that provides authenticated access.
1511
+ - \u{1F4A1} For GitHub URLs (repos, issues, PRs), prefer using the \`gh\` CLI via Bash
1512
+ instead (e.g. \`gh pr view <pr-number>\`, \`gh issue view <number>\`, \`gh api <endpoint>\`).
1513
+ - \u26A1 If an MCP-provided web fetch tool is available, prefer using that tool instead,
1514
+ as it may have fewer restrictions and better performance.
1515
+ - \u{1F512} Domain preapproved list includes common documentation sites (MDN, TypeScript,
1516
+ React, Vue, Angular, Node.js, Bun, Rust, Go, Python, etc.). Other domains
1517
+ will work but results may be less reliable.
1518
+ - \u{1F4DD} This tool includes a self-cleaning 15-minute cache for faster repeated access
1519
+ to the same URL.
1520
+ - \u26A0\uFE0F For PDF files, the tool will attempt to extract readable text but results may
1521
+ be limited. Binary images cannot be processed.`;
1522
+ function validateURL(url) {
1523
+ if (url.length > MAX_URL_LENGTH) {
1524
+ return { ok: false, error: `URL \u592A\u957F\uFF08\u6700\u5927 ${MAX_URL_LENGTH} \u5B57\u7B26\uFF09` };
1525
+ }
1526
+ let parsed;
1527
+ try {
1528
+ parsed = new URL(url);
1529
+ } catch {
1530
+ return { ok: false, error: `\u65E0\u6548\u7684 URL \u683C\u5F0F` };
1531
+ }
1532
+ if (parsed.username || parsed.password) {
1533
+ return { ok: false, error: `URL \u4E0D\u80FD\u5305\u542B\u7528\u6237\u540D\u6216\u5BC6\u7801` };
1534
+ }
1535
+ return { ok: true, parsed };
1536
+ }
1537
+ async function fetchURL(url, signal, depth = 0) {
1538
+ const validated = validateURL(url);
1539
+ if (!validated.ok) return { type: "error", error: validated.error };
1540
+ let targetUrl = url;
1541
+ const parsed = validated.parsed;
1542
+ if (parsed.protocol === "http:") {
1543
+ parsed.protocol = "https:";
1544
+ targetUrl = parsed.toString();
1545
+ }
1546
+ let response;
1547
+ try {
1548
+ const timeoutSignal = AbortSignal.timeout(FETCH_TIMEOUT_MS);
1549
+ const controller = new AbortController();
1550
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
1551
+ response = await fetch(targetUrl, {
1552
+ signal: combinedSignal,
1553
+ headers: {
1554
+ Accept: "text/markdown, text/html, */*",
1555
+ "User-Agent": "minimal-agent/1.0"
1556
+ }
1557
+ });
1558
+ } catch (e) {
1559
+ if (signal?.aborted) return { type: "error", error: "\u8BF7\u6C42\u88AB\u4E2D\u65AD" };
1560
+ return { type: "error", error: `\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25\uFF1A${e.message}` };
1561
+ }
1562
+ const code = response.status;
1563
+ const codeText = response.statusText;
1564
+ const contentType = response.headers.get("content-type") ?? "";
1565
+ const location = response.headers.get("location");
1566
+ if (location && [301, 302, 307, 308].includes(code)) {
1567
+ const redirectUrl = new URL(location, targetUrl).toString();
1568
+ const originalHost = new URL(url).hostname;
1569
+ const redirectHost = new URL(redirectUrl).hostname;
1570
+ const stripWww = (h) => h.replace(/^www\./, "");
1571
+ if (stripWww(originalHost) !== stripWww(redirectHost)) {
1572
+ return {
1573
+ type: "redirect",
1574
+ originalUrl: url,
1575
+ redirectUrl,
1576
+ statusCode: code
1577
+ };
1578
+ }
1579
+ if (depth >= MAX_REDIRECTS) {
1580
+ return { type: "error", error: `\u91CD\u5B9A\u5411\u5FAA\u73AF\u8D85\u8FC7\u9650\u5236\uFF08\u6700\u591A ${MAX_REDIRECTS} \u6B21\uFF09` };
1581
+ }
1582
+ return fetchURL(redirectUrl, signal, depth + 1);
1583
+ }
1584
+ let rawBuffer;
1585
+ try {
1586
+ rawBuffer = await response.arrayBuffer();
1587
+ } catch (e) {
1588
+ return { type: "error", error: `\u8BFB\u53D6\u54CD\u5E94\u4F53\u5931\u8D25\uFF1A${e.message}` };
1589
+ }
1590
+ if (rawBuffer.byteLength > MAX_HTTP_CONTENT_LENGTH) {
1591
+ return { type: "error", error: `\u5185\u5BB9\u592A\u5927\uFF08${rawBuffer.byteLength} bytes\uFF0C\u8D85\u8FC7 ${MAX_HTTP_CONTENT_LENGTH}\uFF09` };
1592
+ }
1593
+ let content;
1594
+ try {
1595
+ const decoder = new TextDecoder("utf-8", { fatal: false });
1596
+ content = decoder.decode(rawBuffer);
1597
+ } catch (e) {
1598
+ return { type: "error", error: `\u89E3\u7801\u5931\u8D25\uFF1A${e.message}` };
1599
+ }
1600
+ return { type: "success", content, bytes: rawBuffer.byteLength, code, codeText, contentType };
1601
+ }
1602
+ async function htmlToMarkdown(html) {
1603
+ const TurndownService = (await import("turndown")).default;
1604
+ const td = new TurndownService();
1605
+ return td.turndown(html);
1606
+ }
1607
+ async function call6(input, signal) {
1608
+ const { url } = input;
1609
+ const start = Date.now();
1610
+ const cacheKey = url;
1611
+ let targetHostname;
1612
+ try {
1613
+ targetHostname = new URL(url).hostname;
1614
+ } catch {
1615
+ targetHostname = "";
1616
+ }
1617
+ const preapproved = isPreapprovedDomain(targetHostname);
1618
+ const cached2 = URL_CACHE.get(cacheKey);
1619
+ if (cached2 && Date.now() - cached2.fetchedAt <= CACHE_TTL_MS) {
1620
+ let content2 = cached2.content;
1621
+ const contentType2 = cached2.contentType;
1622
+ if (contentType2.includes("text/html")) {
1623
+ try {
1624
+ content2 = await htmlToMarkdown(content2);
1625
+ } catch (e) {
1626
+ console.warn(`turndown \u5931\u8D25: ${e.message}`);
1627
+ }
1628
+ }
1629
+ const formattedSize2 = cached2.bytes < 1024 ? `${cached2.bytes} B` : cached2.bytes < 1024 * 1024 ? `${(cached2.bytes / 1024).toFixed(1)} KB` : `${(cached2.bytes / 1024 / 1024).toFixed(1)} MB`;
1630
+ const domainNote2 = preapproved ? "[\u{1F512} \u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u4FE1]" : "[\u26A0\uFE0F \u975E\u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u80FD\u4E0D\u51C6\u786E]";
1631
+ const output2 = `\u3010WebFetch \u7ED3\u679C\u3011
1632
+ URL: ${url}
1633
+ \u72B6\u6001: ${cached2.code} ${cached2.codeText}
1634
+ \u5927\u5C0F: ${formattedSize2}
1635
+ \u8017\u65F6: 0.00s (\u7F13\u5B58\u547D\u4E2D)
1636
+ ${domainNote2}
1637
+ [\u{1F4E6} \u6765\u81EA\u7F13\u5B58\uFF0815\u5206\u949F TTL\uFF09]
1638
+
1639
+ --- \u5185\u5BB9 ---
1640
+ ${content2}`;
1641
+ let final2 = output2;
1642
+ if (final2.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
1643
+ final2 = final2.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
1644
+
1645
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
1646
+ }
1647
+ return { ok: true, content: final2 };
1648
+ }
1649
+ const fetched = await fetchURL(url, signal ?? new AbortController().signal);
1650
+ const durationMs = Date.now() - start;
1651
+ if (fetched.type === "redirect") {
1652
+ const statusText = fetched.statusCode === 301 ? "Moved Permanently" : fetched.statusCode === 308 ? "Permanent Redirect" : fetched.statusCode === 307 ? "Temporary Redirect" : "Found";
1653
+ return {
1654
+ ok: true,
1655
+ content: `\u3010\u91CD\u5B9A\u5411\u68C0\u6D4B\u3011
1656
+
1657
+ \u539F\u59CB URL: ${fetched.originalUrl}
1658
+ \u91CD\u5B9A\u5411\u5230: ${fetched.redirectUrl}
1659
+ \u72B6\u6001: ${fetched.statusCode} ${statusText}
1660
+
1661
+ \u8BF7\u4F7F\u7528\u91CD\u5B9A\u5411\u540E\u7684 URL \u518D\u6B21\u8C03\u7528 WebFetch \u5DE5\u5177\u3002`
1662
+ };
1663
+ }
1664
+ if (fetched.type === "error") {
1665
+ return { ok: false, error: fetched.error };
1666
+ }
1667
+ let { content, bytes, code, codeText, contentType } = fetched;
1668
+ URL_CACHE.set(cacheKey, {
1669
+ bytes,
1670
+ code,
1671
+ codeText,
1672
+ content,
1673
+ contentType,
1674
+ fetchedAt: Date.now()
1675
+ });
1676
+ cleanCache();
1677
+ if (contentType.includes("text/html")) {
1678
+ try {
1679
+ content = await htmlToMarkdown(content);
1680
+ } catch (e) {
1681
+ console.warn(`turndown \u5931\u8D25: ${e.message}`);
1682
+ }
1683
+ }
1684
+ const formattedSize = bytes < 1024 ? `${bytes} B` : bytes < 1024 * 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1024 / 1024).toFixed(1)} MB`;
1685
+ const domainNote = preapproved ? "[\u{1F512} \u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u4FE1]" : "[\u26A0\uFE0F \u975E\u9884\u6279\u51C6\u57DF\u540D\uFF0C\u5185\u5BB9\u53EF\u80FD\u4E0D\u51C6\u786E]";
1686
+ const output = `\u3010WebFetch \u7ED3\u679C\u3011
1687
+ URL: ${url}
1688
+ \u72B6\u6001: ${code} ${codeText}
1689
+ \u5927\u5C0F: ${formattedSize}
1690
+ \u8017\u65F6: ${(durationMs / 1e3).toFixed(2)}s
1691
+ ${domainNote}
1692
+
1693
+ --- \u5185\u5BB9 ---
1694
+ ${content}`;
1695
+ let final = output;
1696
+ if (final.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
1697
+ final = final.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
1698
+
1699
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
1700
+ }
1701
+ return { ok: true, content: final };
1702
+ }
1703
+ var webfetchTool = {
1704
+ name: "WebFetch",
1705
+ description: description6,
1706
+ inputSchema: inputSchema6,
1707
+ parameters: parameters6,
1708
+ isReadOnly: true,
1709
+ isConcurrencySafe: true,
1710
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
1711
+ call: call6
1712
+ };
1713
+
1714
+ // src/tools/webbrowser/webbrowser.ts
1715
+ import { z as z7 } from "zod";
1716
+
1717
+ // src/tools/webbrowser/browser.ts
1718
+ import os from "os";
1719
+ import path from "path";
1720
+ var browserInstance = null;
1721
+ var pageInstance = null;
1722
+ async function getBrowserPage() {
1723
+ if (!browserInstance) {
1724
+ const { chromium } = await import("playwright-core");
1725
+ try {
1726
+ browserInstance = await chromium.launch({
1727
+ headless: true,
1728
+ args: ["--no-sandbox", "--disable-setuid-sandbox"]
1729
+ });
1730
+ } catch (e) {
1731
+ const message = e instanceof Error ? e.message : String(e);
1732
+ throw new Error(
1733
+ `Failed to launch browser: ${message}
1734
+ \u8BF7\u5148\u5B89\u88C5 playwright \u53CA\u6D4F\u89C8\u5668\uFF1A
1735
+ npm install playwright-core
1736
+ npx playwright install chromium`
1737
+ );
1738
+ }
1739
+ }
1740
+ if (!pageInstance) {
1741
+ pageInstance = await browserInstance.newPage();
1742
+ await pageInstance.setViewportSize({ width: 1280, height: 720 });
1743
+ }
1744
+ return pageInstance;
1745
+ }
1746
+ function screenshotPath(prefix = "browser") {
1747
+ return path.join(os.tmpdir(), `${prefix}-${Date.now()}.png`);
1748
+ }
1749
+
1750
+ // src/tools/webbrowser/webbrowser.ts
1751
+ var inputSchema7 = z7.object({
1752
+ action: z7.enum(["navigate", "screenshot", "getContent", "click", "fill", "submit"]).describe("Browser action to perform"),
1753
+ url: z7.string().url().optional().describe("URL to navigate to (required for navigate action)"),
1754
+ selector: z7.string().optional().describe("CSS selector for click/fill/submit actions"),
1755
+ value: z7.string().optional().describe("Value to fill in input fields"),
1756
+ timeout: z7.number().int().positive().optional().describe("Timeout in milliseconds (default: 30000)")
1757
+ });
1758
+ var parameters7 = toToolParameters(inputSchema7);
1759
+ var description7 = `Control a headless web browser. Navigate to URLs, take screenshots, and interact with web pages.
1760
+
1761
+ When to use WebBrowser vs WebSearch:
1762
+ - WebSearch: When you need to find information or discover URLs through search
1763
+ - WebBrowser: When you need to interact with a specific web page (navigate, click, fill forms, get content, take screenshots)
1764
+
1765
+ Input parameters:
1766
+ - action (required): The browser action. One of:
1767
+ - navigate: Go to a URL (requires url parameter)
1768
+ - screenshot: Take a screenshot of the current page
1769
+ - getContent: Extract text content from the current page
1770
+ - click: Click an element (requires selector parameter)
1771
+ - fill: Fill an input field (requires selector and value parameters)
1772
+ - submit: Submit a form (requires selector parameter)
1773
+ - url (optional): URL to navigate to (required for navigate action, must be a valid URL)
1774
+ - selector (optional): CSS selector for click/fill/submit actions
1775
+ - value (optional): Value to fill in input fields
1776
+ - timeout (optional): Timeout in milliseconds for actions (default: 30000)
1777
+
1778
+ Usage notes:
1779
+ - The browser runs headless (no visible window)
1780
+ - Screenshots are saved to temporary files (/tmp/browser-*.png)
1781
+ - Content is truncated to 50000 characters max
1782
+ - Only one browser instance is maintained per session
1783
+ - Requires: npm install playwright-core && npx playwright install chromium
1784
+
1785
+ Example actions:
1786
+ - Navigate and get content: { action: "navigate", url: "https://example.com" }
1787
+ - Take screenshot: { action: "screenshot" }
1788
+ - Click element: { action: "click", selector: "#submit-btn" }
1789
+ - Fill form field: { action: "fill", selector: "input[name='email']", value: "user@example.com" }`;
1790
+ async function call7(input, signal) {
1791
+ const { action, url, selector, value, timeout = 3e4 } = input;
1792
+ let page;
1793
+ try {
1794
+ page = await getBrowserPage();
1795
+ } catch (e) {
1796
+ return {
1797
+ ok: false,
1798
+ error: `\u65E0\u6CD5\u542F\u52A8\u6D4F\u89C8\u5668\uFF1A${e instanceof Error ? e.message : String(e)}`
1799
+ };
1800
+ }
1801
+ try {
1802
+ switch (action) {
1803
+ case "navigate": {
1804
+ if (!url) {
1805
+ return { ok: false, error: "navigate action requires url parameter" };
1806
+ }
1807
+ await page.goto(url, { timeout, waitUntil: "domcontentloaded" });
1808
+ const content = await page.evaluate(() => document.body.innerText);
1809
+ const screenshot = screenshotPath("browser-nav");
1810
+ await page.screenshot({ path: screenshot });
1811
+ const truncatedContent = content.substring(0, 5e4);
1812
+ return {
1813
+ ok: true,
1814
+ content: [
1815
+ `[Navigate] ${page.url()}`,
1816
+ `Screenshot: ${screenshot}`,
1817
+ "",
1818
+ "--- Page Content (first 50000 chars) ---",
1819
+ truncatedContent,
1820
+ content.length > 5e4 ? "\n... (truncated)" : ""
1821
+ ].join("\n")
1822
+ };
1823
+ }
1824
+ case "screenshot": {
1825
+ const screenshot = screenshotPath("browser-screenshot");
1826
+ await page.screenshot({ path: screenshot });
1827
+ return {
1828
+ ok: true,
1829
+ content: `[Screenshot] ${page.url()}
1830
+ Saved: ${screenshot}`
1831
+ };
1832
+ }
1833
+ case "getContent": {
1834
+ const content = await page.evaluate(() => document.body.innerText);
1835
+ const truncated = content.substring(0, 5e4);
1836
+ return {
1837
+ ok: true,
1838
+ content: [
1839
+ `[Page Content] ${page.url()}`,
1840
+ "",
1841
+ truncated,
1842
+ content.length > 5e4 ? "\n... (truncated at 50000 chars)" : ""
1843
+ ].join("\n")
1844
+ };
1845
+ }
1846
+ case "click": {
1847
+ if (!selector) {
1848
+ return { ok: false, error: "click action requires selector parameter" };
1849
+ }
1850
+ await page.click(selector, { timeout });
1851
+ return {
1852
+ ok: true,
1853
+ content: `[Click] ${selector}
1854
+ Current URL: ${page.url()}`
1855
+ };
1856
+ }
1857
+ case "fill": {
1858
+ if (!selector || value === void 0) {
1859
+ return { ok: false, error: "fill action requires selector and value parameters" };
1860
+ }
1861
+ await page.fill(selector, value);
1862
+ return {
1863
+ ok: true,
1864
+ content: `[Fill] ${selector} = "${value}"
1865
+ Current URL: ${page.url()}`
1866
+ };
1867
+ }
1868
+ case "submit": {
1869
+ if (!selector) {
1870
+ return { ok: false, error: "submit action requires selector parameter" };
1871
+ }
1872
+ await Promise.all([
1873
+ page.waitForNavigation({ timeout }).catch(() => {
1874
+ }),
1875
+ page.click(selector, { timeout })
1876
+ ]);
1877
+ return {
1878
+ ok: true,
1879
+ content: `[Submit] ${selector}
1880
+ Current URL: ${page.url()}`
1881
+ };
1882
+ }
1883
+ default:
1884
+ return { ok: false, error: `Unknown action: ${action}` };
1885
+ }
1886
+ } catch (e) {
1887
+ const message = e instanceof Error ? e.message : String(e);
1888
+ const truncated = message.length > DEFAULT_MAX_RESULT_SIZE_CHARS ? message.substring(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + "\n... (truncated)" : message;
1889
+ return { ok: false, error: `[${action}] Error: ${truncated}` };
1890
+ }
1891
+ }
1892
+ var webbrowserTool = {
1893
+ name: "WebBrowser",
1894
+ description: description7,
1895
+ inputSchema: inputSchema7,
1896
+ parameters: parameters7,
1897
+ isReadOnly: false,
1898
+ isConcurrencySafe: false,
1899
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
1900
+ call: call7
1901
+ };
1902
+
1903
+ // src/tools/websearch/websearch.ts
1904
+ import { z as z8 } from "zod";
1905
+ var inputSchema8 = z8.object({
1906
+ query: z8.string().min(1, "\u5FC5\u987B\u63D0\u4F9B\u641C\u7D22\u5173\u952E\u8BCD").max(400, "\u641C\u7D22\u5173\u952E\u8BCD\u592A\u957F\uFF08>400 \u5B57\uFF09").describe("\u641C\u7D22\u5173\u952E\u8BCD\uFF0C\u5EFA\u8BAE\u81EA\u7136\u8BED\u8A00\u63CF\u8FF0\u9700\u8981\u67E5\u7684\u4FE1\u606F"),
1907
+ max_results: z8.number().int().min(1).max(20).optional().describe("\u8FD4\u56DE\u7ED3\u679C\u6570\u91CF\uFF0C1-20\uFF0C\u9ED8\u8BA4 5"),
1908
+ search_depth: z8.enum(["basic", "advanced"]).optional().describe("basic \u5FEB\u4F46\u6D45\uFF1Badvanced \u6162\u4F46\u6DF1\uFF08\u542B answer \u6458\u8981\uFF09\uFF0C\u9ED8\u8BA4 basic"),
1909
+ topic: z8.enum(["general", "news"]).optional().describe("general=\u901A\u7528\u7F51\u9875\uFF1Bnews=\u504F\u65B0\u95FB\u6E90\uFF1B\u9ED8\u8BA4 general")
1910
+ });
1911
+ var parameters8 = toToolParameters(inputSchema8);
1912
+ var description8 = `- Searches the public web via the Tavily Search API and returns structured results.
1913
+ - Use this when you need up-to-date information that is not in your training data, or when the user asks for recent news / docs / API references.
1914
+ - Returns the top N results, each with a title, URL, and content snippet. With \`search_depth: "advanced"\` Tavily also returns a synthesized answer at the top.
1915
+ - Prefer specific natural-language queries over keyword soup (e.g. "how does Bun handle .env files in version 1.1").
1916
+ - Requires the TAVILY_API_KEY environment variable to be set; if missing the tool returns a friendly error.`;
1917
+ async function call8(input, signal) {
1918
+ const apiKey = process.env.TAVILY_API_KEY;
1919
+ if (!apiKey) {
1920
+ return {
1921
+ ok: false,
1922
+ error: "WebSearch \u4E0D\u53EF\u7528\uFF1A\u73AF\u5883\u53D8\u91CF TAVILY_API_KEY \u672A\u8BBE\u7F6E\u3002\n\u8BF7\u5728\u9879\u76EE\u6839 .env \u91CC\u52A0\uFF1A\n TAVILY_API_KEY=tvly-\u4F60\u7684key\n\u514D\u8D39 key \u7533\u8BF7\uFF1Ahttps://tavily.com/"
1923
+ };
1924
+ }
1925
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1926
+ const queryWithDate = input.query.includes(today) ? input.query : `${input.query} (as of ${today})`;
1927
+ const body = {
1928
+ api_key: apiKey,
1929
+ query: queryWithDate,
1930
+ max_results: input.max_results ?? 5,
1931
+ search_depth: input.search_depth ?? "basic",
1932
+ topic: input.topic ?? "general",
1933
+ // 让 advanced 模式自动给 answer;basic 不会有
1934
+ include_answer: input.search_depth === "advanced"
1935
+ };
1936
+ let res;
1937
+ try {
1938
+ res = await fetch("https://api.tavily.com/search", {
1939
+ method: "POST",
1940
+ headers: { "content-type": "application/json" },
1941
+ body: JSON.stringify(body),
1942
+ signal
1943
+ });
1944
+ } catch (e) {
1945
+ return {
1946
+ ok: false,
1947
+ error: `\u8C03\u7528 Tavily \u5931\u8D25\uFF08\u7F51\u7EDC\uFF09\uFF1A${e.message}`
1948
+ };
1949
+ }
1950
+ if (!res.ok) {
1951
+ const text = await res.text().catch(() => "");
1952
+ return {
1953
+ ok: false,
1954
+ error: `Tavily HTTP ${res.status}\uFF1A${text.slice(0, 500)}`
1955
+ };
1956
+ }
1957
+ let data;
1958
+ try {
1959
+ data = await res.json();
1960
+ } catch (e) {
1961
+ return {
1962
+ ok: false,
1963
+ error: `Tavily \u54CD\u5E94\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${e.message}`
1964
+ };
1965
+ }
1966
+ const results = data.results ?? [];
1967
+ if (results.length === 0) {
1968
+ return {
1969
+ ok: true,
1970
+ content: `(no results for "${input.query}")`
1971
+ };
1972
+ }
1973
+ const parts = [];
1974
+ if (data.answer) {
1975
+ parts.push(`\u3010\u7EFC\u5408\u56DE\u7B54\u3011
1976
+ ${data.answer}
1977
+ `);
1978
+ }
1979
+ parts.push(`\u3010\u5171 ${results.length} \u6761\u7ED3\u679C\u3011`);
1980
+ results.forEach((r, i) => {
1981
+ const date = r.published_date ? `\uFF08${r.published_date}\uFF09` : "";
1982
+ parts.push(
1983
+ `
1984
+ [${i + 1}] ${r.title}${date}
1985
+ URL: ${r.url}
1986
+ ${(r.content ?? "").trim()}`
1987
+ );
1988
+ });
1989
+ let content = parts.join("\n");
1990
+ if (content.length > DEFAULT_MAX_RESULT_SIZE_CHARS) {
1991
+ content = content.slice(0, DEFAULT_MAX_RESULT_SIZE_CHARS) + `
1992
+
1993
+ ... (\u8F93\u51FA\u8D85\u8FC7 ${DEFAULT_MAX_RESULT_SIZE_CHARS} \u5B57\u7B26\uFF0C\u5DF2\u622A\u65AD)`;
1994
+ }
1995
+ return { ok: true, content };
1996
+ }
1997
+ var webSearchTool = {
1998
+ name: "WebSearch",
1999
+ description: description8,
2000
+ inputSchema: inputSchema8,
2001
+ parameters: parameters8,
2002
+ isReadOnly: true,
2003
+ isConcurrencySafe: true,
2004
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
2005
+ call: call8
2006
+ };
2007
+
2008
+ // src/tools/write/write.ts
2009
+ import { existsSync as existsSync3 } from "fs";
2010
+ import { mkdir as mkdir4, stat as stat3, writeFile as writeFile4 } from "fs/promises";
2011
+ import { dirname as dirname6, resolve as resolve6 } from "path";
2012
+ import { z as z9 } from "zod";
2013
+ var MAX_WRITE_SIZE_BYTES = 1024 * 1024 * 1024;
2014
+ var inputSchema9 = z9.object({
2015
+ file_path: z9.string().min(1).describe("\u8981\u5199\u5165\u7684\u6587\u4EF6\u8DEF\u5F84"),
2016
+ content: z9.string().describe("\u6587\u4EF6\u5B8C\u6574\u5185\u5BB9\uFF08\u4F1A\u8986\u76D6\u65E2\u6709\u5185\u5BB9\uFF09")
2017
+ });
2018
+ var parameters9 = toToolParameters(inputSchema9);
2019
+ var description9 = `Writes a file to the local filesystem.
2020
+
2021
+ Usage:
2022
+ - This tool will overwrite the existing file if there is one at the provided path.
2023
+ - If the parent directory does not exist, it will be created recursively.
2024
+ - ALWAYS prefer editing existing files in the codebase via the Edit tool. NEVER write new files unless explicitly required.
2025
+ - NEVER create documentation files (*.md) or README files unless explicitly requested by the User.`;
2026
+ function validatePath2(filePath) {
2027
+ if (filePath.includes("\0")) {
2028
+ return { ok: false, error: "\u8DEF\u5F84\u5305\u542B\u975E\u6CD5\u5B57\u7B26\uFF08null byte\uFF09" };
2029
+ }
2030
+ if (process.platform === "win32" && filePath.includes("\\\\")) {
2031
+ return { ok: false, error: "\u4E0D\u652F\u6301 UNC \u8DEF\u5F84\uFF08\\\\server\\share \u683C\u5F0F\uFF09\uFF0C\u8BF7\u4F7F\u7528\u672C\u5730\u8DEF\u5F84" };
2032
+ }
2033
+ return { ok: true };
2034
+ }
2035
+ async function call9(input) {
2036
+ const filePath = resolve6(input.file_path);
2037
+ const pathCheck = validatePath2(filePath);
2038
+ if (!pathCheck.ok) return pathCheck;
2039
+ const contentSize = Buffer.byteLength(input.content, "utf8");
2040
+ if (contentSize > MAX_WRITE_SIZE_BYTES) {
2041
+ return {
2042
+ ok: false,
2043
+ error: `\u5199\u5165\u5185\u5BB9\u8FC7\u5927\uFF08${contentSize} \u5B57\u8282 > ${MAX_WRITE_SIZE_BYTES} \u5B57\u8282 \u2248 1GB\uFF09\u3002\u8BF7\u62C6\u5206\u5185\u5BB9\u6216\u4F7F\u7528 Edit \u5DE5\u5177\u5206\u5757\u4FEE\u6539\u3002`
2044
+ };
2045
+ }
2046
+ try {
2047
+ await mkdir4(dirname6(filePath), { recursive: true });
2048
+ let originalSize = 0;
2049
+ const fileExisted = existsSync3(filePath);
2050
+ if (fileExisted) {
2051
+ try {
2052
+ const st = await stat3(filePath);
2053
+ originalSize = st.size;
2054
+ } catch {
2055
+ }
2056
+ }
2057
+ await writeFile4(filePath, input.content, "utf8");
2058
+ const action = fileExisted ? "\u5DF2\u8986\u76D6" : "\u5DF2\u521B\u5EFA\u65B0\u6587\u4EF6";
2059
+ const sizeInfo = fileExisted ? `\uFF08\u539F\u6587\u4EF6 ${originalSize} \u5B57\u7B26 \u2192 \u65B0\u5185\u5BB9 ${input.content.length} \u5B57\u7B26\uFF09` : `\uFF08${input.content.length} \u5B57\u7B26\uFF09`;
2060
+ return {
2061
+ ok: true,
2062
+ content: `${action} ${filePath}${sizeInfo}`
2063
+ };
2064
+ } catch (e) {
2065
+ return { ok: false, error: `\u5199\u5165\u5931\u8D25\uFF1A${e.message}` };
2066
+ }
2067
+ }
2068
+ var writeTool = {
2069
+ name: "Write",
2070
+ description: description9,
2071
+ inputSchema: inputSchema9,
2072
+ parameters: parameters9,
2073
+ isReadOnly: false,
2074
+ isConcurrencySafe: false,
2075
+ maxResultSizeChars: DEFAULT_MAX_RESULT_SIZE_CHARS,
2076
+ call: call9
2077
+ };
2078
+
2079
+ // src/tools/index.ts
2080
+ var ALL_TOOLS = [
2081
+ readTool,
2082
+ editTool,
2083
+ writeTool,
2084
+ globTool,
2085
+ grepTool,
2086
+ webSearchTool,
2087
+ webfetchTool,
2088
+ webbrowserTool,
2089
+ bashTool
2090
+ ];
2091
+ function getToolByName(name) {
2092
+ return ALL_TOOLS.find((t) => t.name === name);
2093
+ }
2094
+ async function executeTool(name, rawArguments, signal) {
2095
+ const tool = getToolByName(name);
2096
+ if (!tool) {
2097
+ const available = ALL_TOOLS.map((t) => t.name).join(", ");
2098
+ return { ok: false, error: `\u672A\u77E5\u5DE5\u5177 "${name}"\u3002\u53EF\u7528\u5DE5\u5177\uFF1A${available}` };
2099
+ }
2100
+ let parsedJson;
2101
+ try {
2102
+ parsedJson = rawArguments.length === 0 ? {} : JSON.parse(rawArguments);
2103
+ } catch (e) {
2104
+ return {
2105
+ ok: false,
2106
+ error: `\u5DE5\u5177\u53C2\u6570\u4E0D\u662F\u5408\u6CD5 JSON\uFF1A${e.message}
2107
+ \u6536\u5230\u7684\u5B57\u7B26\u4E32\uFF1A${rawArguments.slice(0, 200)}`
2108
+ };
2109
+ }
2110
+ const checked = tool.inputSchema.safeParse(parsedJson);
2111
+ if (!checked.success) {
2112
+ const issues = checked.error.issues.map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`).join("\n");
2113
+ return {
2114
+ ok: false,
2115
+ error: `\u53C2\u6570\u6821\u9A8C\u5931\u8D25\uFF08\u8BF7\u68C0\u67E5\u53C2\u6570\u540D/\u7C7B\u578B\uFF09\uFF1A
2116
+ ${issues}`
2117
+ };
2118
+ }
2119
+ try {
2120
+ return await tool.call(checked.data, signal);
2121
+ } catch (e) {
2122
+ return {
2123
+ ok: false,
2124
+ error: `\u5DE5\u5177\u6267\u884C\u629B\u51FA\u5F02\u5E38\uFF1A${e.message}`
2125
+ };
2126
+ }
2127
+ }
2128
+
2129
+ // src/ui/Root.tsx
2130
+ import { useEffect as useEffect2, useState as useState6 } from "react";
2131
+ import { Box as Box7, Text as Text7 } from "ink";
2132
+
2133
+ // src/cli/configWizard.tsx
2134
+ import { useCallback, useMemo, useState } from "react";
2135
+ import { Box, Text, useApp, useInput } from "ink";
2136
+ import { jsx, jsxs } from "react/jsx-runtime";
2137
+ var DEFAULT_CONTEXT_WINDOW2 = 128e3;
2138
+ var PRESETS = [
2139
+ {
2140
+ name: "minimax",
2141
+ label: "MiniMax (\u6D77\u87BA)",
2142
+ baseURL: "https://api.minimax.chat/v1",
2143
+ models: ["MiniMax-M2.7", "MiniMax-M1", "abab6.5s-chat"]
2144
+ },
2145
+ {
2146
+ name: "deepseek",
2147
+ label: "DeepSeek",
2148
+ baseURL: "https://api.deepseek.com/v1",
2149
+ models: ["deepseek-chat", "deepseek-reasoner"]
2150
+ },
2151
+ {
2152
+ name: "openai",
2153
+ label: "OpenAI",
2154
+ baseURL: "https://api.openai.com/v1",
2155
+ models: ["gpt-4o", "gpt-4o-mini"]
2156
+ },
2157
+ {
2158
+ name: "moonshot",
2159
+ label: "Moonshot (Kimi)",
2160
+ baseURL: "https://api.moonshot.cn/v1",
2161
+ models: ["moonshot-v1-8k", "moonshot-v1-32k", "moonshot-v1-128k"]
2162
+ },
2163
+ {
2164
+ name: "custom",
2165
+ label: "\u81EA\u5B9A\u4E49\uFF08\u624B\u52A8\u586B baseURL\uFF09",
2166
+ baseURL: "",
2167
+ models: []
2168
+ }
2169
+ ];
2170
+ function ConfigWizard({ onSuccess }) {
2171
+ const { exit } = useApp();
2172
+ const [step, setStep] = useState("preset");
2173
+ const [presetIndex, setPresetIndex] = useState(0);
2174
+ const [baseURL, setBaseURL] = useState("");
2175
+ const [apiKey, setApiKey] = useState("");
2176
+ const [model, setModel] = useState("");
2177
+ const [modelIndex, setModelIndex] = useState(0);
2178
+ const [draft, setDraft] = useState("");
2179
+ const [errorMsg, setErrorMsg] = useState(null);
2180
+ const preset = PRESETS[presetIndex];
2181
+ const enterStep = useCallback(
2182
+ (next) => {
2183
+ setErrorMsg(null);
2184
+ if (next === "baseURL") setDraft(baseURL || preset.baseURL);
2185
+ if (next === "apiKey") setDraft("");
2186
+ if (next === "model") {
2187
+ if (preset.models.length > 0) {
2188
+ setDraft(model || preset.models[0]);
2189
+ setModelIndex(0);
2190
+ } else {
2191
+ setDraft(model);
2192
+ }
2193
+ }
2194
+ setStep(next);
2195
+ },
2196
+ [baseURL, model, preset]
2197
+ );
2198
+ const handleTest = useCallback(async () => {
2199
+ setStep("testing");
2200
+ setErrorMsg(null);
2201
+ try {
2202
+ const url = `${baseURL.replace(/\/$/, "")}/chat/completions`;
2203
+ const resp = await fetch(url, {
2204
+ method: "POST",
2205
+ headers: {
2206
+ "Content-Type": "application/json",
2207
+ Authorization: `Bearer ${apiKey}`
2208
+ },
2209
+ body: JSON.stringify({
2210
+ model,
2211
+ messages: [{ role: "user", content: "ping" }],
2212
+ max_tokens: 5,
2213
+ stream: false
2214
+ })
2215
+ });
2216
+ if (!resp.ok) {
2217
+ const body = await resp.text().catch(() => "");
2218
+ throw new Error(`HTTP ${resp.status}: ${body.slice(0, 200) || resp.statusText}`);
2219
+ }
2220
+ const data = await resp.json();
2221
+ if (!Array.isArray(data.choices)) {
2222
+ throw new Error("\u54CD\u5E94\u4E2D\u6CA1\u6709 choices \u5B57\u6BB5\uFF0C\u53EF\u80FD\u4E0D\u662F OpenAI \u517C\u5BB9\u534F\u8BAE");
2223
+ }
2224
+ await saveConfig({
2225
+ baseURL,
2226
+ apiKey,
2227
+ model,
2228
+ provider: preset.name,
2229
+ contextWindow: DEFAULT_CONTEXT_WINDOW2
2230
+ });
2231
+ onSuccess({
2232
+ name: preset.name,
2233
+ baseURL,
2234
+ apiKey,
2235
+ model,
2236
+ contextWindow: DEFAULT_CONTEXT_WINDOW2
2237
+ });
2238
+ } catch (e) {
2239
+ setErrorMsg(e.message || "\u8FDE\u63A5\u5931\u8D25");
2240
+ setStep("error");
2241
+ }
2242
+ }, [apiKey, baseURL, model, onSuccess, preset.name]);
2243
+ useInput((input, key) => {
2244
+ if (key.escape || key.ctrl && input === "c") {
2245
+ exit();
2246
+ return;
2247
+ }
2248
+ if (step === "preset") {
2249
+ if (key.upArrow) {
2250
+ setPresetIndex((i) => (i - 1 + PRESETS.length) % PRESETS.length);
2251
+ } else if (key.downArrow) {
2252
+ setPresetIndex((i) => (i + 1) % PRESETS.length);
2253
+ } else if (key.return) {
2254
+ enterStep("baseURL");
2255
+ }
2256
+ return;
2257
+ }
2258
+ if (step === "baseURL") {
2259
+ if (key.return) {
2260
+ if (draft.trim().length === 0) return;
2261
+ setBaseURL(draft.trim());
2262
+ enterStep("apiKey");
2263
+ } else if (key.backspace || key.delete) {
2264
+ setDraft((s) => s.slice(0, -1));
2265
+ } else if (input && !key.meta && !key.ctrl) {
2266
+ setDraft((s) => s + input);
2267
+ }
2268
+ return;
2269
+ }
2270
+ if (step === "apiKey") {
2271
+ if (key.return) {
2272
+ if (draft.length === 0) return;
2273
+ setApiKey(draft);
2274
+ enterStep("model");
2275
+ } else if (key.backspace || key.delete) {
2276
+ setDraft((s) => s.slice(0, -1));
2277
+ } else if (input && !key.meta && !key.ctrl) {
2278
+ setDraft((s) => s + input);
2279
+ }
2280
+ return;
2281
+ }
2282
+ if (step === "model") {
2283
+ if (preset.models.length > 0) {
2284
+ if (key.upArrow) {
2285
+ const ni = (modelIndex - 1 + preset.models.length) % preset.models.length;
2286
+ setModelIndex(ni);
2287
+ setDraft(preset.models[ni]);
2288
+ } else if (key.downArrow) {
2289
+ const ni = (modelIndex + 1) % preset.models.length;
2290
+ setModelIndex(ni);
2291
+ setDraft(preset.models[ni]);
2292
+ } else if (key.tab) {
2293
+ setDraft((s) => s + "");
2294
+ }
2295
+ }
2296
+ if (key.return) {
2297
+ if (draft.trim().length === 0) return;
2298
+ setModel(draft.trim());
2299
+ void handleTest();
2300
+ } else if (key.backspace || key.delete) {
2301
+ setDraft((s) => s.slice(0, -1));
2302
+ } else if (input && !key.meta && !key.ctrl && !key.upArrow && !key.downArrow) {
2303
+ setDraft((s) => s + input);
2304
+ }
2305
+ return;
2306
+ }
2307
+ if (step === "error") {
2308
+ if (key.return) {
2309
+ enterStep("preset");
2310
+ }
2311
+ return;
2312
+ }
2313
+ });
2314
+ const maskedApiKey = useMemo(() => "*".repeat(Math.min(draft.length, 32)), [draft]);
2315
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingX: 1, children: [
2316
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "minimal-agent \xB7 \u9996\u6B21\u914D\u7F6E\u5411\u5BFC" }) }),
2317
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u6309 ESC \u6216 Ctrl+C \u9000\u51FA\uFF08\u4E0D\u5199\u914D\u7F6E\uFF0C\u4E0B\u6B21\u542F\u52A8\u7EE7\u7EED\u5411\u5BFC\uFF09" }) }),
2318
+ step === "preset" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2319
+ /* @__PURE__ */ jsx(Text, { children: "Step 1/4 \xB7 \u9009\u62E9 provider \u9884\u8BBE\uFF08\u2191\u2193 \u79FB\u52A8\uFF0CEnter \u786E\u8BA4\uFF09" }),
2320
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: PRESETS.map((p, i) => /* @__PURE__ */ jsxs(Text, { color: i === presetIndex ? "cyan" : void 0, children: [
2321
+ i === presetIndex ? "> " : " ",
2322
+ p.label,
2323
+ p.baseURL ? ` (${p.baseURL})` : ""
2324
+ ] }, p.name)) })
2325
+ ] }),
2326
+ step === "baseURL" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2327
+ /* @__PURE__ */ jsx(Text, { children: "Step 2/4 \xB7 \u8F93\u5165 API base URL\uFF08\u9ED8\u8BA4\u4E3A\u9884\u8BBE\u503C\uFF0C\u53EF\u6539\uFF09" }),
2328
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
2329
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664" })
2330
+ ] }),
2331
+ step === "apiKey" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2332
+ /* @__PURE__ */ jsx(Text, { children: "Step 3/4 \xB7 \u8F93\u5165 API key\uFF08\u8F93\u5165\u5185\u5BB9\u4E0D\u4F1A\u663E\u793A\uFF09" }),
2333
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: maskedApiKey || " " }) }),
2334
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2335
+ "Enter \u786E\u8BA4 \xB7 Backspace \u5220\u9664\uFF08\u5DF2\u8F93\u5165 ",
2336
+ draft.length,
2337
+ " \u5B57\u7B26\uFF09"
2338
+ ] })
2339
+ ] }),
2340
+ step === "model" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2341
+ /* @__PURE__ */ jsxs(Text, { children: [
2342
+ "Step 4/4 \xB7 \u8F93\u5165\u6216\u9009\u62E9 model",
2343
+ preset.models.length > 0 ? "\uFF08\u2191\u2193 \u5207\u6362\u9884\u8BBE\uFF0C\u6216\u76F4\u63A5\u7F16\u8F91\uFF09" : ""
2344
+ ] }),
2345
+ preset.models.length > 0 && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginTop: 1, children: preset.models.map((m, i) => /* @__PURE__ */ jsxs(Text, { color: i === modelIndex ? "cyan" : "gray", children: [
2346
+ i === modelIndex ? "> " : " ",
2347
+ m
2348
+ ] }, m)) }),
2349
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "cyan", paddingX: 1, children: /* @__PURE__ */ jsx(Text, { children: draft || " " }) }),
2350
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "Enter \u63D0\u4EA4\u5E76\u6D4B\u8BD5\u8FDE\u63A5" })
2351
+ ] }),
2352
+ step === "testing" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2353
+ /* @__PURE__ */ jsxs(Text, { color: "yellow", children: [
2354
+ "\u23F3 \u6B63\u5728\u6D4B\u8BD5\u8FDE\u63A5 ",
2355
+ baseURL,
2356
+ " ..."
2357
+ ] }),
2358
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2359
+ "model = ",
2360
+ model
2361
+ ] })
2362
+ ] }),
2363
+ step === "error" && /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
2364
+ /* @__PURE__ */ jsx(Text, { color: "red", children: "\u2717 \u8FDE\u63A5\u6D4B\u8BD5\u5931\u8D25" }),
2365
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "red", children: errorMsg ?? "\u672A\u77E5\u9519\u8BEF" }) }),
2366
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "\u5DF2\u8F93\u5165\uFF1A" }) }),
2367
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2368
+ " provider = ",
2369
+ preset.name
2370
+ ] }),
2371
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2372
+ " baseURL = ",
2373
+ baseURL
2374
+ ] }),
2375
+ /* @__PURE__ */ jsxs(Text, { color: "gray", children: [
2376
+ " model = ",
2377
+ model
2378
+ ] }),
2379
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "yellow", children: "\u6309 Enter \u56DE\u5230 Step 1 \u91CD\u8BD5 \xB7 ESC \u9000\u51FA" }) })
2380
+ ] })
2381
+ ] });
2382
+ }
2383
+
2384
+ // src/ui/App.tsx
2385
+ import React3 from "react";
2386
+ import { Box as Box6, Text as Text6 } from "ink";
2387
+
2388
+ // src/ui/InputBox.tsx
2389
+ import { useRef as useRef2, useState as useState4, useCallback as useCallback4 } from "react";
2390
+ import { Box as Box2, Text as Text2, useApp as useApp2, useInput as useInput2 } from "ink";
2391
+
2392
+ // src/ui/hooks/useTextBuffer.ts
2393
+ import { useCallback as useCallback2, useMemo as useMemo2, useState as useState2 } from "react";
2394
+
2395
+ // src/ui/textBuffer.ts
2396
+ var EMPTY_BUFFER = { text: "", cursor: 0 };
2397
+ function toCps(s) {
2398
+ return Array.from(s);
2399
+ }
2400
+ function fromCps(cps) {
2401
+ return cps.join("");
2402
+ }
2403
+ function isWordChar(c) {
2404
+ if (!c) return false;
2405
+ return /[A-Za-z0-9_]/.test(c);
2406
+ }
2407
+ function findLineStart(cps, idx) {
2408
+ let i = idx;
2409
+ while (i > 0 && cps[i - 1] !== "\n") i--;
2410
+ return i;
2411
+ }
2412
+ function findLineEnd(cps, idx) {
2413
+ let i = idx;
2414
+ while (i < cps.length && cps[i] !== "\n") i++;
2415
+ return i;
2416
+ }
2417
+ function clamp(cps, idx) {
2418
+ return Math.max(0, Math.min(cps.length, idx));
2419
+ }
2420
+ function bufInsert(s, content) {
2421
+ if (content.length === 0) return s;
2422
+ const cps = toCps(s.text);
2423
+ const ins = toCps(content);
2424
+ cps.splice(s.cursor, 0, ...ins);
2425
+ return { text: fromCps(cps), cursor: s.cursor + ins.length };
2426
+ }
2427
+ function bufSetText(text) {
2428
+ return { text, cursor: toCps(text).length };
2429
+ }
2430
+ function bufClear() {
2431
+ return EMPTY_BUFFER;
2432
+ }
2433
+ function bufDeleteBefore(s) {
2434
+ if (s.cursor === 0) return s;
2435
+ const cps = toCps(s.text);
2436
+ cps.splice(s.cursor - 1, 1);
2437
+ return { text: fromCps(cps), cursor: s.cursor - 1 };
2438
+ }
2439
+ function bufDeleteAt(s) {
2440
+ const cps = toCps(s.text);
2441
+ if (s.cursor >= cps.length) return s;
2442
+ cps.splice(s.cursor, 1);
2443
+ return { text: fromCps(cps), cursor: s.cursor };
2444
+ }
2445
+ function bufDeleteWordBefore(s) {
2446
+ if (s.cursor === 0) return s;
2447
+ const cps = toCps(s.text);
2448
+ let i = s.cursor;
2449
+ while (i > 0 && !isWordChar(cps[i - 1])) i--;
2450
+ while (i > 0 && isWordChar(cps[i - 1])) i--;
2451
+ cps.splice(i, s.cursor - i);
2452
+ return { text: fromCps(cps), cursor: i };
2453
+ }
2454
+ function bufKillToLineStart(s) {
2455
+ const cps = toCps(s.text);
2456
+ const lineStart = findLineStart(cps, s.cursor);
2457
+ if (lineStart === s.cursor) return s;
2458
+ cps.splice(lineStart, s.cursor - lineStart);
2459
+ return { text: fromCps(cps), cursor: lineStart };
2460
+ }
2461
+ function bufKillToLineEnd(s) {
2462
+ const cps = toCps(s.text);
2463
+ const lineEnd = findLineEnd(cps, s.cursor);
2464
+ if (lineEnd === s.cursor) return s;
2465
+ cps.splice(s.cursor, lineEnd - s.cursor);
2466
+ return { text: fromCps(cps), cursor: s.cursor };
2467
+ }
2468
+ function bufMoveLeft(s) {
2469
+ return { text: s.text, cursor: Math.max(0, s.cursor - 1) };
2470
+ }
2471
+ function bufMoveRight(s) {
2472
+ const len = toCps(s.text).length;
2473
+ return { text: s.text, cursor: Math.min(len, s.cursor + 1) };
2474
+ }
2475
+ function bufMoveWordLeft(s) {
2476
+ const cps = toCps(s.text);
2477
+ let i = s.cursor;
2478
+ while (i > 0 && !isWordChar(cps[i - 1])) i--;
2479
+ while (i > 0 && isWordChar(cps[i - 1])) i--;
2480
+ return { text: s.text, cursor: i };
2481
+ }
2482
+ function bufMoveWordRight(s) {
2483
+ const cps = toCps(s.text);
2484
+ let i = s.cursor;
2485
+ while (i < cps.length && !isWordChar(cps[i])) i++;
2486
+ while (i < cps.length && isWordChar(cps[i])) i++;
2487
+ return { text: s.text, cursor: i };
2488
+ }
2489
+ function bufMoveLineStart(s) {
2490
+ const cps = toCps(s.text);
2491
+ return { text: s.text, cursor: findLineStart(cps, s.cursor) };
2492
+ }
2493
+ function bufMoveLineEnd(s) {
2494
+ const cps = toCps(s.text);
2495
+ return { text: s.text, cursor: findLineEnd(cps, s.cursor) };
2496
+ }
2497
+ function bufMoveBufferStart(s) {
2498
+ return { text: s.text, cursor: 0 };
2499
+ }
2500
+ function bufMoveBufferEnd(s) {
2501
+ return { text: s.text, cursor: toCps(s.text).length };
2502
+ }
2503
+ function bufMoveUp(s) {
2504
+ const cps = toCps(s.text);
2505
+ const lineStart = findLineStart(cps, s.cursor);
2506
+ if (lineStart === 0) return s;
2507
+ const prevLineEnd = lineStart - 1;
2508
+ const prevLineStart = findLineStart(cps, prevLineEnd);
2509
+ const col = s.cursor - lineStart;
2510
+ const prevLineLen = prevLineEnd - prevLineStart;
2511
+ return { text: s.text, cursor: prevLineStart + Math.min(col, prevLineLen) };
2512
+ }
2513
+ function bufMoveDown(s) {
2514
+ const cps = toCps(s.text);
2515
+ const lineStart = findLineStart(cps, s.cursor);
2516
+ const lineEnd = findLineEnd(cps, s.cursor);
2517
+ if (lineEnd === cps.length) return s;
2518
+ const nextLineStart = lineEnd + 1;
2519
+ const nextLineEnd = findLineEnd(cps, nextLineStart);
2520
+ const col = s.cursor - lineStart;
2521
+ const nextLineLen = nextLineEnd - nextLineStart;
2522
+ return { text: s.text, cursor: nextLineStart + Math.min(col, nextLineLen) };
2523
+ }
2524
+ function layoutBuffer(s) {
2525
+ const cps = toCps(s.text);
2526
+ const cursor = clamp(cps, s.cursor);
2527
+ const lineStart = findLineStart(cps, cursor);
2528
+ const lines = [];
2529
+ let buf = [];
2530
+ let cursorRow = 0;
2531
+ let charCount = 0;
2532
+ for (const ch of cps) {
2533
+ if (ch === "\n") {
2534
+ lines.push(fromCps(buf));
2535
+ buf = [];
2536
+ if (charCount < lineStart) cursorRow++;
2537
+ } else {
2538
+ buf.push(ch);
2539
+ }
2540
+ charCount++;
2541
+ }
2542
+ lines.push(fromCps(buf));
2543
+ const cursorCol = cursor - lineStart;
2544
+ return { lines, cursorRow, cursorCol };
2545
+ }
2546
+
2547
+ // src/ui/hooks/useTextBuffer.ts
2548
+ function useTextBuffer() {
2549
+ const [state, setState] = useState2(EMPTY_BUFFER);
2550
+ const insert = useCallback2((s) => setState((p) => bufInsert(p, s)), []);
2551
+ const setText = useCallback2((s) => setState(bufSetText(s)), []);
2552
+ const clear = useCallback2(() => setState(bufClear()), []);
2553
+ const deleteBefore = useCallback2(() => setState(bufDeleteBefore), []);
2554
+ const deleteAt = useCallback2(() => setState(bufDeleteAt), []);
2555
+ const deleteWordBefore = useCallback2(() => setState(bufDeleteWordBefore), []);
2556
+ const killToLineStart = useCallback2(() => setState(bufKillToLineStart), []);
2557
+ const killToLineEnd = useCallback2(() => setState(bufKillToLineEnd), []);
2558
+ const moveLeft = useCallback2(() => setState(bufMoveLeft), []);
2559
+ const moveRight = useCallback2(() => setState(bufMoveRight), []);
2560
+ const moveWordLeft = useCallback2(() => setState(bufMoveWordLeft), []);
2561
+ const moveWordRight = useCallback2(() => setState(bufMoveWordRight), []);
2562
+ const moveLineStart = useCallback2(() => setState(bufMoveLineStart), []);
2563
+ const moveLineEnd = useCallback2(() => setState(bufMoveLineEnd), []);
2564
+ const moveBufferStart = useCallback2(() => setState(bufMoveBufferStart), []);
2565
+ const moveBufferEnd = useCallback2(() => setState(bufMoveBufferEnd), []);
2566
+ const moveUp = useCallback2(() => setState(bufMoveUp), []);
2567
+ const moveDown = useCallback2(() => setState(bufMoveDown), []);
2568
+ const layout = useMemo2(() => layoutBuffer(state), [state]);
2569
+ return {
2570
+ state,
2571
+ layout,
2572
+ insert,
2573
+ setText,
2574
+ clear,
2575
+ deleteBefore,
2576
+ deleteAt,
2577
+ deleteWordBefore,
2578
+ killToLineStart,
2579
+ killToLineEnd,
2580
+ moveLeft,
2581
+ moveRight,
2582
+ moveWordLeft,
2583
+ moveWordRight,
2584
+ moveLineStart,
2585
+ moveLineEnd,
2586
+ moveBufferStart,
2587
+ moveBufferEnd,
2588
+ moveUp,
2589
+ moveDown
2590
+ };
2591
+ }
2592
+
2593
+ // src/ui/hooks/usePasteHandler.ts
2594
+ import { useCallback as useCallback3, useRef, useState as useState3 } from "react";
2595
+ var PASTE_THRESHOLD = 20;
2596
+ var PASTE_TIMEOUT_MS = 100;
2597
+ function usePasteHandler({
2598
+ onPasteComplete,
2599
+ multiline = false
2600
+ }) {
2601
+ const [isPasting, setIsPasting] = useState3(false);
2602
+ const pasteStateRef = useRef({
2603
+ chunks: [],
2604
+ timeoutId: null
2605
+ });
2606
+ const onPasteCompleteRef = useRef(onPasteComplete);
2607
+ onPasteCompleteRef.current = onPasteComplete;
2608
+ const setIsPastingRef = useRef(setIsPasting);
2609
+ setIsPastingRef.current = setIsPasting;
2610
+ const flushPaste = useCallback3(() => {
2611
+ const { chunks } = pasteStateRef.current;
2612
+ if (chunks.length === 0) return;
2613
+ const rawText = chunks.join("");
2614
+ const cleanedText = rawText.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n{3,}/g, "\n\n");
2615
+ pasteStateRef.current = { chunks: [], timeoutId: null };
2616
+ setIsPastingRef.current(false);
2617
+ onPasteCompleteRef.current(cleanedText);
2618
+ }, []);
2619
+ const handleInput = useCallback3(
2620
+ (input, key) => {
2621
+ if (isPasting && key.return) {
2622
+ return "enter-in-paste";
2623
+ }
2624
+ if (multiline && key.return && key.meta && !key.ctrl) {
2625
+ return "enter-newline";
2626
+ }
2627
+ if (key.return) {
2628
+ return "continue";
2629
+ }
2630
+ const isLongInput = input.length > PASTE_THRESHOLD;
2631
+ if (isLongInput || isPasting) {
2632
+ setIsPasting(true);
2633
+ if (pasteStateRef.current.timeoutId) {
2634
+ clearTimeout(pasteStateRef.current.timeoutId);
2635
+ }
2636
+ pasteStateRef.current.chunks.push(input);
2637
+ pasteStateRef.current.timeoutId = setTimeout(() => {
2638
+ flushPaste();
2639
+ }, PASTE_TIMEOUT_MS);
2640
+ return "paste";
2641
+ }
2642
+ return "continue";
2643
+ },
2644
+ [isPasting, multiline, flushPaste]
2645
+ );
2646
+ return {
2647
+ handleInput,
2648
+ isPasting
2649
+ };
2650
+ }
2651
+
2652
+ // src/ui/InputBox.tsx
2653
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
2654
+ function InputBox({ onSubmit, disabled, onAbort, onClear, onCompact }) {
2655
+ const buf = useTextBuffer();
2656
+ const ctrlCCountRef = useRef2(0);
2657
+ const ctrlCTimerRef = useRef2(null);
2658
+ const { exit } = useApp2();
2659
+ const [refreshCounter, setRefreshCounter] = useState4(0);
2660
+ const lastDeleteTime = useRef2(0);
2661
+ const DELETE_DEBOUNCE_MS = 20;
2662
+ const forceRefresh = useCallback4(() => {
2663
+ setRefreshCounter((c) => c + 1);
2664
+ }, []);
2665
+ const { handleInput: handlePasteInput } = usePasteHandler({
2666
+ onPasteComplete: (text) => {
2667
+ buf.insert(text);
2668
+ forceRefresh();
2669
+ },
2670
+ multiline: true
2671
+ });
2672
+ useInput2((input, key) => {
2673
+ const pasteResult = handlePasteInput(input, key);
2674
+ if (pasteResult === "paste") {
2675
+ return;
2676
+ }
2677
+ if (pasteResult === "enter-in-paste") {
2678
+ return;
2679
+ }
2680
+ if (pasteResult === "enter-newline") {
2681
+ buf.insert("\n");
2682
+ forceRefresh();
2683
+ return;
2684
+ }
2685
+ if (key.escape) {
2686
+ if (disabled) onAbort();
2687
+ return;
2688
+ }
2689
+ if (key.ctrl && input === "c") {
2690
+ if (buf.state.text.length > 0) {
2691
+ buf.clear();
2692
+ forceRefresh();
2693
+ return;
2694
+ }
2695
+ if (disabled) {
2696
+ onAbort();
2697
+ return;
2698
+ }
2699
+ ctrlCCountRef.current++;
2700
+ if (ctrlCCountRef.current >= 2) exit();
2701
+ if (ctrlCTimerRef.current) clearTimeout(ctrlCTimerRef.current);
2702
+ ctrlCTimerRef.current = setTimeout(() => {
2703
+ ctrlCCountRef.current = 0;
2704
+ }, 1e3);
2705
+ return;
2706
+ }
2707
+ if (key.return) {
2708
+ const text = buf.state.text.trim();
2709
+ if (text.length === 0) return;
2710
+ if (text === "/exit" || text === "/quit") exit();
2711
+ else if (text === "/new" || text === "/clear") {
2712
+ buf.clear();
2713
+ forceRefresh();
2714
+ onClear();
2715
+ } else if (text === "/compact") {
2716
+ buf.clear();
2717
+ forceRefresh();
2718
+ onCompact();
2719
+ } else {
2720
+ buf.clear();
2721
+ forceRefresh();
2722
+ onSubmit(text);
2723
+ }
2724
+ return;
2725
+ }
2726
+ if (key.leftArrow) {
2727
+ if (key.ctrl || key.meta) buf.moveWordLeft();
2728
+ else buf.moveLeft();
2729
+ forceRefresh();
2730
+ return;
2731
+ }
2732
+ if (key.rightArrow) {
2733
+ if (key.ctrl || key.meta) buf.moveWordRight();
2734
+ else buf.moveRight();
2735
+ forceRefresh();
2736
+ return;
2737
+ }
2738
+ if (key.upArrow) {
2739
+ buf.moveUp();
2740
+ forceRefresh();
2741
+ return;
2742
+ }
2743
+ if (key.downArrow) {
2744
+ buf.moveDown();
2745
+ forceRefresh();
2746
+ return;
2747
+ }
2748
+ if (key.backspace) {
2749
+ buf.deleteBefore();
2750
+ forceRefresh();
2751
+ return;
2752
+ }
2753
+ if (key.delete) {
2754
+ const now = Date.now();
2755
+ if (now - lastDeleteTime.current < DELETE_DEBOUNCE_MS) {
2756
+ return;
2757
+ }
2758
+ lastDeleteTime.current = now;
2759
+ buf.deleteBefore();
2760
+ forceRefresh();
2761
+ return;
2762
+ }
2763
+ if (key.ctrl && !key.meta) {
2764
+ switch (input) {
2765
+ case "a":
2766
+ buf.moveLineStart();
2767
+ break;
2768
+ case "e":
2769
+ buf.moveLineEnd();
2770
+ break;
2771
+ case "b":
2772
+ buf.moveLeft();
2773
+ break;
2774
+ case "f":
2775
+ buf.moveRight();
2776
+ break;
2777
+ case "u":
2778
+ buf.killToLineStart();
2779
+ break;
2780
+ case "k":
2781
+ buf.killToLineEnd();
2782
+ break;
2783
+ case "w":
2784
+ buf.deleteWordBefore();
2785
+ break;
2786
+ case "h":
2787
+ buf.deleteBefore();
2788
+ break;
2789
+ // Ctrl+H = Backspace
2790
+ default:
2791
+ return;
2792
+ }
2793
+ forceRefresh();
2794
+ return;
2795
+ }
2796
+ if (key.meta && input === "<") {
2797
+ buf.moveBufferStart();
2798
+ forceRefresh();
2799
+ return;
2800
+ }
2801
+ if (key.meta && input === ">") {
2802
+ buf.moveBufferEnd();
2803
+ forceRefresh();
2804
+ return;
2805
+ }
2806
+ if (input && !key.meta) {
2807
+ const normalized = input.replace(/\r\n?/g, "\n");
2808
+ buf.insert(normalized);
2809
+ forceRefresh();
2810
+ }
2811
+ });
2812
+ return /* @__PURE__ */ jsx2(
2813
+ BufferView,
2814
+ {
2815
+ buf,
2816
+ disabled,
2817
+ refreshCounter
2818
+ }
2819
+ );
2820
+ }
2821
+ function BufferView({ buf, disabled, refreshCounter }) {
2822
+ const { lines, cursorRow, cursorCol } = buf.layout;
2823
+ const promptColor = disabled ? "gray" : "cyan";
2824
+ const prompt = disabled ? "\u23F3 " : "> ";
2825
+ return /* @__PURE__ */ jsx2(Box2, { borderStyle: "round", borderColor: promptColor, paddingX: 1, flexDirection: "column", children: lines.map((line, row) => {
2826
+ const isFirst = row === 0;
2827
+ const isCursorRow = row === cursorRow && !disabled;
2828
+ return /* @__PURE__ */ jsxs2(Box2, { children: [
2829
+ /* @__PURE__ */ jsx2(Text2, { color: promptColor, bold: true, children: isFirst ? prompt : " " }),
2830
+ isCursorRow ? /* @__PURE__ */ jsx2(CursorLine, { line, col: cursorCol }) : /* @__PURE__ */ jsx2(Text2, { children: line || " " })
2831
+ ] }, row);
2832
+ }) });
2833
+ }
2834
+ function CursorLine({ line, col }) {
2835
+ const cps = Array.from(line);
2836
+ const before = cps.slice(0, col).join("");
2837
+ const cursorChar = cps[col] ?? " ";
2838
+ const after = cps.slice(col + 1).join("");
2839
+ return /* @__PURE__ */ jsxs2(Text2, { children: [
2840
+ before,
2841
+ /* @__PURE__ */ jsx2(Text2, { inverse: true, children: cursorChar }),
2842
+ after
2843
+ ] });
2844
+ }
2845
+
2846
+ // src/ui/MessageList.tsx
2847
+ import { Box as Box3, Text as Text3 } from "ink";
2848
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
2849
+ var MAX_TOOL_PREVIEW_LINES = 3;
2850
+ function MessageList({ history, streamingText }) {
2851
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2852
+ history.map((m, i) => /* @__PURE__ */ jsx3(MessageRow, { message: m }, i)),
2853
+ streamingText.length > 0 && /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
2854
+ /* @__PURE__ */ jsx3(Text3, { children: streamingText }),
2855
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: " \u258D" })
2856
+ ] })
2857
+ ] });
2858
+ }
2859
+ function MessageRow({ message }) {
2860
+ switch (message.role) {
2861
+ case "system":
2862
+ return null;
2863
+ // 不展示系统提示词
2864
+ case "user":
2865
+ return /* @__PURE__ */ jsxs3(Box3, { marginBottom: 1, children: [
2866
+ /* @__PURE__ */ jsx3(Text3, { color: "cyan", bold: true, children: "> " }),
2867
+ /* @__PURE__ */ jsx3(Text3, { children: message.content })
2868
+ ] });
2869
+ case "assistant": {
2870
+ const text = message.content ?? "";
2871
+ const tcCount = message.tool_calls?.length ?? 0;
2872
+ return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginBottom: 1, children: [
2873
+ text.length > 0 && /* @__PURE__ */ jsx3(Text3, { children: text }),
2874
+ tcCount > 0 && /* @__PURE__ */ jsx3(Text3, { color: "yellow", dimColor: true, children: ` \u2514\u2500 \u8C03\u7528\u4E86 ${tcCount} \u4E2A\u5DE5\u5177\uFF1A${(message.tool_calls ?? []).map((tc) => tc.function.name).join(", ")}` })
2875
+ ] });
2876
+ }
2877
+ case "tool": {
2878
+ const lines = message.content.split("\n");
2879
+ const preview = lines.slice(0, MAX_TOOL_PREVIEW_LINES).join("\n");
2880
+ const hidden = Math.max(0, lines.length - MAX_TOOL_PREVIEW_LINES);
2881
+ return /* @__PURE__ */ jsx3(Box3, { marginBottom: 1, paddingLeft: 2, children: /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", children: [
2882
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: "[tool result]" }),
2883
+ /* @__PURE__ */ jsx3(Text3, { color: "gray", children: preview }),
2884
+ hidden > 0 && /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: `... (\u7701\u7565 ${hidden} \u884C)` })
2885
+ ] }) });
2886
+ }
2887
+ }
2888
+ }
2889
+
2890
+ // src/ui/StatusLine.tsx
2891
+ import { Box as Box4, Text as Text4 } from "ink";
2892
+
2893
+ // src/llm/client.ts
2894
+ async function* chat(args) {
2895
+ const { provider, messages, tools, signal } = args;
2896
+ const openaiTools = tools.map((t) => ({
2897
+ type: "function",
2898
+ function: {
2899
+ name: t.name,
2900
+ description: typeof t.description === "function" ? t.description() : t.description,
2901
+ parameters: t.parameters
2902
+ }
2903
+ }));
2904
+ const body = {
2905
+ model: provider.model,
2906
+ messages,
2907
+ tools: openaiTools.length > 0 ? openaiTools : void 0,
2908
+ stream: true,
2909
+ // tool_choice: 'auto' 是默认值,可不写;某些 provider 必须显式
2910
+ tool_choice: openaiTools.length > 0 ? "auto" : void 0
2911
+ };
2912
+ const url = `${provider.baseURL.replace(/\/$/, "")}/chat/completions`;
2913
+ const resp = await fetch(url, {
2914
+ method: "POST",
2915
+ headers: {
2916
+ "Content-Type": "application/json",
2917
+ Authorization: `Bearer ${provider.apiKey}`
2918
+ },
2919
+ body: JSON.stringify(body),
2920
+ signal
2921
+ });
2922
+ if (!resp.ok) {
2923
+ const errText = await resp.text().catch(() => "");
2924
+ throw new Error(
2925
+ `LLM \u8BF7\u6C42\u5931\u8D25 [${resp.status} ${resp.statusText}]\uFF1A${errText.slice(0, 500)}`
2926
+ );
2927
+ }
2928
+ if (!resp.body) {
2929
+ throw new Error("LLM \u54CD\u5E94\u6CA1\u6709 body\uFF08provider \u53EF\u80FD\u4E0D\u652F\u6301\u6D41\u5F0F\uFF1F\uFF09");
2930
+ }
2931
+ const reader = resp.body.getReader();
2932
+ const decoder = new TextDecoder();
2933
+ let buffer = "";
2934
+ let stopReason = "unknown";
2935
+ try {
2936
+ while (true) {
2937
+ if (signal?.aborted) {
2938
+ yield { type: "done", stopReason: "aborted" };
2939
+ return;
2940
+ }
2941
+ const { value, done } = await reader.read();
2942
+ if (done) break;
2943
+ if (signal?.aborted) {
2944
+ yield { type: "done", stopReason: "aborted" };
2945
+ return;
2946
+ }
2947
+ buffer += decoder.decode(value, { stream: true });
2948
+ let lineEnd;
2949
+ while ((lineEnd = buffer.indexOf("\n")) !== -1) {
2950
+ const line = buffer.slice(0, lineEnd).trim();
2951
+ buffer = buffer.slice(lineEnd + 1);
2952
+ if (!line || !line.startsWith("data:")) continue;
2953
+ const dataStr = line.slice(5).trim();
2954
+ if (dataStr === "[DONE]") {
2955
+ yield { type: "done", stopReason };
2956
+ return;
2957
+ }
2958
+ let chunk;
2959
+ try {
2960
+ chunk = JSON.parse(dataStr);
2961
+ } catch {
2962
+ continue;
2963
+ }
2964
+ const delta = chunk.choices?.[0]?.delta;
2965
+ const finish = chunk.choices?.[0]?.finish_reason;
2966
+ if (finish) {
2967
+ stopReason = finish === "stop" || finish === "tool_calls" || finish === "length" ? finish : "unknown";
2968
+ }
2969
+ if (!delta) continue;
2970
+ if (typeof delta.content === "string" && delta.content.length > 0) {
2971
+ yield { type: "text_delta", delta: delta.content };
2972
+ }
2973
+ if (Array.isArray(delta.tool_calls)) {
2974
+ for (const tc of delta.tool_calls) {
2975
+ yield {
2976
+ type: "tool_call_delta",
2977
+ index: tc.index ?? 0,
2978
+ id: tc.id,
2979
+ name: tc.function?.name,
2980
+ argumentsDelta: tc.function?.arguments
2981
+ };
2982
+ }
2983
+ }
2984
+ if (typeof delta.reasoning_content === "string" && delta.reasoning_content.length > 0) {
2985
+ yield {
2986
+ type: "reasoning_delta",
2987
+ field: "reasoning_content",
2988
+ delta: delta.reasoning_content
2989
+ };
2990
+ }
2991
+ if (typeof delta.reasoning === "string" && delta.reasoning.length > 0) {
2992
+ yield {
2993
+ type: "reasoning_delta",
2994
+ field: "reasoning",
2995
+ delta: delta.reasoning
2996
+ };
2997
+ }
2998
+ if (Array.isArray(delta.reasoning_details) && delta.reasoning_details.length > 0) {
2999
+ yield {
3000
+ type: "reasoning_delta",
3001
+ field: "reasoning_details",
3002
+ items: delta.reasoning_details
3003
+ };
3004
+ }
3005
+ }
3006
+ }
3007
+ yield { type: "done", stopReason };
3008
+ } finally {
3009
+ reader.releaseLock?.();
3010
+ }
3011
+ }
3012
+
3013
+ // src/context/tokenCounter.ts
3014
+ function countTextTokens(text) {
3015
+ if (!text) return 0;
3016
+ let asciiChars = 0;
3017
+ let nonAsciiChars = 0;
3018
+ for (let i = 0; i < text.length; i++) {
3019
+ const code = text.charCodeAt(i);
3020
+ if (code < 128) asciiChars++;
3021
+ else nonAsciiChars++;
3022
+ }
3023
+ return Math.ceil(asciiChars / 4) + nonAsciiChars;
3024
+ }
3025
+ function countMessagesTokens(messages) {
3026
+ let total = 0;
3027
+ for (const m of messages) {
3028
+ total += 4;
3029
+ if (typeof m.content === "string") {
3030
+ total += countTextTokens(m.content);
3031
+ } else if (m.content === null) {
3032
+ }
3033
+ if (m.role === "assistant" && m.tool_calls) {
3034
+ for (const tc of m.tool_calls) {
3035
+ total += 8;
3036
+ total += countTextTokens(tc.function.name);
3037
+ total += countTextTokens(tc.function.arguments);
3038
+ }
3039
+ }
3040
+ if (m.role === "tool") {
3041
+ total += countTextTokens(m.tool_call_id);
3042
+ }
3043
+ }
3044
+ return total;
3045
+ }
3046
+
3047
+ // src/context/compact.ts
3048
+ var NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
3049
+
3050
+ - Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
3051
+ - You already have all the context you need in the conversation above.
3052
+ - Tool calls will be REJECTED and will waste your only turn \u2014 you will fail the task.
3053
+ - Your entire response must be plain text: an <analysis> block followed by a <summary> block.
3054
+
3055
+ `;
3056
+ var NO_TOOLS_TRAILER = "\n\nREMINDER: Do NOT call any tools. Respond with plain text only \u2014 an <analysis> block followed by a <summary> block. Tool calls will be rejected and you will fail the task.";
3057
+ var COMPACT_PROMPT_BODY = `Your task is to create a detailed summary of this conversation. This summary will be placed at the start of a continuing session; newer messages that build on this context will follow after your summary. Summarize thoroughly so that someone reading only your summary and then the newer messages can fully understand what happened and continue the work.
3058
+
3059
+ Before providing your final summary, wrap your analysis in <analysis> tags to organize your thoughts and ensure you've covered all necessary points. In your analysis process:
3060
+
3061
+ 1. Chronologically analyze each message and section of the conversation. For each section thoroughly identify:
3062
+ - The user's explicit requests and intents
3063
+ - Your approach to addressing the user's requests
3064
+ - Key decisions, technical concepts and code patterns
3065
+ - Specific details like file names, full code snippets, function signatures, file edits
3066
+ - Errors that you ran into and how you fixed them
3067
+ - Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
3068
+ 2. Double-check for technical accuracy and completeness, addressing each required element thoroughly.
3069
+
3070
+ Your summary should include the following sections:
3071
+
3072
+ 1. Primary Request and Intent: Capture all of the user's explicit requests and intents in detail
3073
+ 2. Key Technical Concepts: List all important technical concepts, technologies, and frameworks discussed.
3074
+ 3. Files and Code Sections: Enumerate specific files and code sections examined, modified, or created. Include FULL code snippets where applicable and include a summary of why this file read or edit is important. (Code snippets are NOT compressed \u2014 preserve them verbatim.)
3075
+ 4. Errors and fixes: List all errors that you ran into, and how you fixed them. Pay special attention to specific user feedback that you received, especially if the user told you to do something differently.
3076
+ 5. Problem Solving: Document problems solved and any ongoing troubleshooting efforts.
3077
+ 6. All user messages: List ALL user messages that are not tool results. These are critical for understanding the users' feedback and changing intent. (User messages are NOT compressed \u2014 preserve them verbatim.)
3078
+ 7. Pending Tasks: Outline any pending tasks that you have explicitly been asked to work on.
3079
+ 8. Work Completed: Describe what was accomplished by the end of this portion.
3080
+ 9. Context for Continuing Work: Summarize any context, decisions, or state that would be needed to understand and continue the work in subsequent messages. Include direct quotes from the most recent conversation showing exactly where work was left off \u2014 this should be verbatim to ensure there's no drift.
3081
+
3082
+ Here's an example of how your output should be structured:
3083
+
3084
+ <example>
3085
+ <analysis>
3086
+ [Your thought process, ensuring all points are covered thoroughly and accurately]
3087
+ </analysis>
3088
+
3089
+ <summary>
3090
+ 1. Primary Request and Intent:
3091
+ [Detailed description]
3092
+
3093
+ 2. Key Technical Concepts:
3094
+ - [Concept 1]
3095
+ - [...]
3096
+
3097
+ 3. Files and Code Sections:
3098
+ - [File Name 1]
3099
+ - [Summary of why this file is important]
3100
+ - [Important Code Snippet]
3101
+ - [...]
3102
+
3103
+ 4. Errors and fixes:
3104
+ - [Detailed description of error 1]:
3105
+ - [How you fixed the error]
3106
+ - [User feedback on the error if any]
3107
+ - [...]
3108
+
3109
+ 5. Problem Solving:
3110
+ [Description of solved problems and ongoing troubleshooting]
3111
+
3112
+ 6. All user messages:
3113
+ - [Detailed non tool use user message]
3114
+ - [...]
3115
+
3116
+ 7. Pending Tasks:
3117
+ - [Task 1]
3118
+ - [...]
3119
+
3120
+ 8. Work Completed:
3121
+ [What was accomplished]
3122
+
3123
+ 9. Context for Continuing Work:
3124
+ [Key context, decisions, or state needed to continue]
3125
+
3126
+ </summary>
3127
+ </example>
3128
+
3129
+ Please provide your summary following this structure, ensuring precision and thoroughness in your response.`;
3130
+ function buildCompactPrompt(customInstructions) {
3131
+ let prompt = NO_TOOLS_PREAMBLE + COMPACT_PROMPT_BODY;
3132
+ if (customInstructions && customInstructions.trim() !== "") {
3133
+ prompt += `
3134
+
3135
+ Additional Instructions:
3136
+ ${customInstructions.trim()}`;
3137
+ }
3138
+ prompt += NO_TOOLS_TRAILER;
3139
+ return prompt;
3140
+ }
3141
+ var BASE_COMPACT_PROMPT = buildCompactPrompt();
3142
+ function buildContinuationUserMessage(args) {
3143
+ const { summary, recentMessagesPreserved } = args;
3144
+ let msg = `This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.
3145
+
3146
+ ${summary}`;
3147
+ if (recentMessagesPreserved) {
3148
+ msg += `
3149
+
3150
+ Recent messages are preserved verbatim.`;
3151
+ }
3152
+ msg += `
3153
+
3154
+ Continue the conversation from where it left off without asking the user any further questions. Resume directly \u2014 do not acknowledge the summary, do not recap what was happening, do not preface with "I'll continue" or similar. Pick up the last task as if the break never happened.`;
3155
+ return msg;
3156
+ }
3157
+ function formatCompactSummary(rawResponse) {
3158
+ const match = rawResponse.match(/<summary>([\s\S]*?)<\/summary>/);
3159
+ if (match) return match[1].trim();
3160
+ const stripped = rawResponse.replace(/<analysis>[\s\S]*?<\/analysis>/g, "").trim();
3161
+ return stripped || rawResponse.trim();
3162
+ }
3163
+ var AUTOCOMPACT_BUFFER_TOKENS = 25e3;
3164
+ var MIN_KEEP_RECENT_MESSAGES = 4;
3165
+ function getCompactThreshold(provider) {
3166
+ return Math.max(1e3, provider.contextWindow - AUTOCOMPACT_BUFFER_TOKENS);
3167
+ }
3168
+ function findTailWithCompleteToolChains(messages, minKeep = MIN_KEEP_RECENT_MESSAGES) {
3169
+ if (messages.length <= minKeep) return [...messages];
3170
+ const tailEnd = messages.length;
3171
+ let tailStart = tailEnd - minKeep;
3172
+ const knownToolCallIds = /* @__PURE__ */ new Set();
3173
+ for (let i = tailStart; i < tailEnd; i++) {
3174
+ const msg = messages[i];
3175
+ if (msg.role === "assistant" && msg.tool_calls) {
3176
+ for (const tc of msg.tool_calls) {
3177
+ knownToolCallIds.add(tc.id);
3178
+ }
3179
+ }
3180
+ }
3181
+ let needsExpansion = true;
3182
+ while (needsExpansion && tailStart > 0) {
3183
+ needsExpansion = false;
3184
+ for (let i = tailStart; i < tailEnd; i++) {
3185
+ const msg = messages[i];
3186
+ if (msg.role === "tool" && msg.tool_call_id) {
3187
+ if (!knownToolCallIds.has(msg.tool_call_id)) {
3188
+ needsExpansion = true;
3189
+ tailStart--;
3190
+ const newMsg = messages[tailStart];
3191
+ if (newMsg.role === "assistant" && newMsg.tool_calls) {
3192
+ for (const tc of newMsg.tool_calls) {
3193
+ knownToolCallIds.add(tc.id);
3194
+ }
3195
+ }
3196
+ break;
3197
+ }
3198
+ }
3199
+ }
3200
+ }
3201
+ return messages.slice(tailStart);
3202
+ }
3203
+ async function autoCompactIfNeeded(messages, provider) {
3204
+ const before = countMessagesTokens(messages);
3205
+ const threshold = getCompactThreshold(provider);
3206
+ if (before < threshold) {
3207
+ return { messages, compacted: false, before, after: before };
3208
+ }
3209
+ const compactedMessages = await runCompaction(messages, provider);
3210
+ const after = countMessagesTokens(compactedMessages);
3211
+ return { messages: compactedMessages, compacted: true, before, after };
3212
+ }
3213
+ async function forceCompact(messages, provider) {
3214
+ const before = countMessagesTokens(messages);
3215
+ const compactedMessages = await runCompaction(messages, provider);
3216
+ const after = countMessagesTokens(compactedMessages);
3217
+ return { messages: compactedMessages, before, after };
3218
+ }
3219
+ async function runCompaction(messages, provider) {
3220
+ const systemMsg = messages.find((m) => m.role === "system");
3221
+ const nonSystem = messages.filter((m) => m.role !== "system");
3222
+ const compactRequest = [
3223
+ ...systemMsg ? [systemMsg] : [],
3224
+ ...nonSystem,
3225
+ {
3226
+ role: "user",
3227
+ content: buildCompactPrompt()
3228
+ }
3229
+ ];
3230
+ let summary = "";
3231
+ try {
3232
+ for await (const ev of chat({
3233
+ provider,
3234
+ messages: compactRequest,
3235
+ tools: []
3236
+ })) {
3237
+ if (ev.type === "text_delta") summary += ev.delta;
3238
+ }
3239
+ } catch (e) {
3240
+ return [
3241
+ ...systemMsg ? [systemMsg] : [],
3242
+ {
3243
+ role: "user",
3244
+ content: `(\u81EA\u52A8\u538B\u7F29\u5931\u8D25\uFF1A${e.message}\uFF0C\u5DF2\u76F4\u63A5\u622A\u65AD\u65E7\u5386\u53F2)`
3245
+ },
3246
+ ...findTailWithCompleteToolChains(nonSystem)
3247
+ ];
3248
+ }
3249
+ const cleanSummary = formatCompactSummary(summary);
3250
+ const recentTail = findTailWithCompleteToolChains(nonSystem);
3251
+ return [
3252
+ ...systemMsg ? [systemMsg] : [],
3253
+ {
3254
+ role: "user",
3255
+ content: buildContinuationUserMessage({
3256
+ summary: cleanSummary,
3257
+ recentMessagesPreserved: recentTail.length > 0
3258
+ })
3259
+ },
3260
+ ...recentTail
3261
+ ];
3262
+ }
3263
+
3264
+ // src/ui/hooks/useTokenUsage.ts
3265
+ function useTokenUsage(messages, provider) {
3266
+ const tokens = countMessagesTokens(messages);
3267
+ const threshold = Math.max(1e3, provider.contextWindow - AUTOCOMPACT_BUFFER_TOKENS);
3268
+ return {
3269
+ tokens,
3270
+ threshold,
3271
+ contextWindow: provider.contextWindow,
3272
+ percentageOfWindow: tokens / provider.contextWindow,
3273
+ toThreshold: threshold - tokens
3274
+ };
3275
+ }
3276
+
3277
+ // src/ui/StatusLine.tsx
3278
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
3279
+ function StatusLine({ provider, history }) {
3280
+ const usage = useTokenUsage(history, provider);
3281
+ const ratio = usage.tokens / usage.threshold;
3282
+ const color = ratio >= 1 ? "red" : ratio >= 0.7 ? "yellow" : "green";
3283
+ return /* @__PURE__ */ jsxs4(Box4, { children: [
3284
+ /* @__PURE__ */ jsxs4(Text4, { color: "gray", children: [
3285
+ provider.name,
3286
+ "/",
3287
+ provider.model
3288
+ ] }),
3289
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " }),
3290
+ /* @__PURE__ */ jsxs4(Text4, { color, children: [
3291
+ "tokens ",
3292
+ fmt(usage.tokens),
3293
+ " / ",
3294
+ fmt(usage.contextWindow)
3295
+ ] }),
3296
+ /* @__PURE__ */ jsx4(Text4, { color: "gray", children: " \xB7 " }),
3297
+ /* @__PURE__ */ jsx4(Text4, { color, children: usage.toThreshold > 0 ? `\u8DDD\u538B\u7F29 ${fmt(usage.toThreshold)}` : `\u5DF2\u8D85\u9608\u503C\uFF08\u4E0B\u8F6E\u538B\u7F29\uFF09` })
3298
+ ] });
3299
+ }
3300
+ function fmt(n) {
3301
+ if (n < 1e3) return `${n}`;
3302
+ if (n < 1e6) return `${(n / 1e3).toFixed(1)}K`;
3303
+ return `${(n / 1e6).toFixed(2)}M`;
3304
+ }
3305
+
3306
+ // src/ui/ToolStatus.tsx
3307
+ import { Box as Box5, Text as Text5 } from "ink";
3308
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
3309
+ function ToolStatus({ status, compacting }) {
3310
+ if (compacting) {
3311
+ return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsx5(Text5, { color: "magenta", bold: true, children: "\u{1F5DC} \u6B63\u5728\u538B\u7F29\u5BF9\u8BDD\u5386\u53F2..." }) });
3312
+ }
3313
+ if (status) {
3314
+ return /* @__PURE__ */ jsx5(Box5, { children: /* @__PURE__ */ jsxs5(Text5, { color: "yellow", children: [
3315
+ "\u23F3 ",
3316
+ /* @__PURE__ */ jsx5(Text5, { bold: true, children: status.toolName }),
3317
+ `(${status.argsPreview})`
3318
+ ] }) });
3319
+ }
3320
+ return null;
3321
+ }
3322
+
3323
+ // src/ui/hooks/useChat.ts
3324
+ import { useCallback as useCallback5, useEffect, useMemo as useMemo3, useRef as useRef3, useState as useState5 } from "react";
3325
+
3326
+ // src/context/snipCompact.ts
3327
+ var DEFAULT_SNIP_PERCENT = 0.2;
3328
+ var DEFAULT_MIN_KEEP = 5;
3329
+ var DEFAULT_MAX_SNIP = 50;
3330
+ var DEFAULT_THRESHOLD = 10;
3331
+ function snipCompactIfNeeded(messages, options = {}) {
3332
+ const force = options.force ?? false;
3333
+ const snipPercent = options.snipPercent ?? DEFAULT_SNIP_PERCENT;
3334
+ const minKeep = options.minMessagesToKeep ?? DEFAULT_MIN_KEEP;
3335
+ const maxSnip = options.maxMessagesToSnip ?? DEFAULT_MAX_SNIP;
3336
+ if (messages.length === 0) {
3337
+ return { messages: [], messagesRemoved: 0, tokensFreed: 0 };
3338
+ }
3339
+ const systemMsg = messages[0].role === "system" ? messages[0] : null;
3340
+ const rest = systemMsg ? messages.slice(1) : messages.slice();
3341
+ if (!force && rest.length < DEFAULT_THRESHOLD) {
3342
+ return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
3343
+ }
3344
+ const byPercent = Math.floor(rest.length * snipPercent);
3345
+ const proposedSnipCount = Math.min(
3346
+ byPercent,
3347
+ maxSnip,
3348
+ Math.max(0, rest.length - minKeep)
3349
+ );
3350
+ if (proposedSnipCount <= 0) {
3351
+ return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
3352
+ }
3353
+ const actualCut = findHeadCutpoint(rest, proposedSnipCount);
3354
+ if (actualCut === 0 || actualCut >= rest.length) {
3355
+ return { messages: messages.slice(), messagesRemoved: 0, tokensFreed: 0 };
3356
+ }
3357
+ const snipped = rest.slice(0, actualCut);
3358
+ const remaining = rest.slice(actualCut);
3359
+ const tokensFreed = countMessagesTokens(snipped);
3360
+ const marker = {
3361
+ role: "user",
3362
+ content: `[\u5DF2\u81EA\u52A8\u5220\u9664\u6700\u65E9\u7684 ${actualCut} \u6761\u5BF9\u8BDD\u4EE5\u8282\u7701 token\uFF08\u7EA6\u91CA\u653E ${tokensFreed.toLocaleString()} tokens\uFF09\u3002\u5982\u9700\u5B8C\u6574\u5386\u53F2\u8BF7\u7528 /new \u91CD\u542F\u4F1A\u8BDD\u6216 /compact \u89E6\u53D1 LLM \u6458\u8981\u538B\u7F29\u3002]`
3363
+ };
3364
+ return {
3365
+ messages: [...systemMsg ? [systemMsg] : [], marker, ...remaining],
3366
+ messagesRemoved: actualCut,
3367
+ tokensFreed
3368
+ };
3369
+ }
3370
+ function findHeadCutpoint(messages, proposedCut) {
3371
+ let cut = Math.max(0, Math.min(proposedCut, messages.length));
3372
+ while (cut < messages.length) {
3373
+ const knownIds = /* @__PURE__ */ new Set();
3374
+ let foundOrphan = false;
3375
+ for (let i = cut; i < messages.length; i++) {
3376
+ const msg = messages[i];
3377
+ if (msg.role === "assistant" && msg.tool_calls) {
3378
+ for (const tc of msg.tool_calls) knownIds.add(tc.id);
3379
+ } else if (msg.role === "tool" && !knownIds.has(msg.tool_call_id)) {
3380
+ foundOrphan = true;
3381
+ break;
3382
+ }
3383
+ }
3384
+ if (!foundOrphan) return cut;
3385
+ cut++;
3386
+ }
3387
+ return cut;
3388
+ }
3389
+
3390
+ // src/context/reactiveCompact.ts
3391
+ var attemptedThisSession = false;
3392
+ function resetReactiveCompactState() {
3393
+ attemptedThisSession = false;
3394
+ }
3395
+ function isPromptTooLongError(error) {
3396
+ const msg = errorMessage(error).toLowerCase();
3397
+ if (!msg) return false;
3398
+ const hasSubject = /prompt|context|token|input length/.test(msg);
3399
+ const hasIssue = /too long|exceed|limit|maximum|over/.test(msg);
3400
+ return hasSubject && hasIssue;
3401
+ }
3402
+ function errorMessage(error) {
3403
+ if (typeof error === "string") return error;
3404
+ if (error instanceof Error) return error.message;
3405
+ if (error && typeof error === "object" && "message" in error) {
3406
+ return String(error.message ?? "");
3407
+ }
3408
+ return String(error ?? "");
3409
+ }
3410
+ async function reactiveCompactIfApplicable(messages, provider, error) {
3411
+ if (!isPromptTooLongError(error)) {
3412
+ return { recovered: false, messages, reason: "not a prompt-too-long error" };
3413
+ }
3414
+ if (attemptedThisSession) {
3415
+ return {
3416
+ recovered: false,
3417
+ messages,
3418
+ reason: "already attempted this session \u2014 use /new or /compact manually"
3419
+ };
3420
+ }
3421
+ attemptedThisSession = true;
3422
+ try {
3423
+ const r = await forceCompact(messages, provider);
3424
+ return {
3425
+ recovered: true,
3426
+ messages: r.messages,
3427
+ reason: `LLM \u538B\u7F29\u6210\u529F\uFF08${r.before} \u2192 ${r.after} tokens\uFF09`,
3428
+ before: r.before,
3429
+ after: r.after
3430
+ };
3431
+ } catch (compactErr) {
3432
+ }
3433
+ const beforeSnip = countMessagesTokens(messages);
3434
+ const snipped = snipCompactIfNeeded(messages, {
3435
+ force: true,
3436
+ snipPercent: 0.4
3437
+ });
3438
+ if (snipped.messagesRemoved > 0) {
3439
+ const afterSnip = countMessagesTokens(snipped.messages);
3440
+ return {
3441
+ recovered: true,
3442
+ messages: snipped.messages,
3443
+ reason: `snip \u515C\u5E95\u6210\u529F\uFF08\u5220\u9664 ${snipped.messagesRemoved} \u6761\u6700\u8001\u6D88\u606F\uFF0C\u91CA\u653E ~${snipped.tokensFreed} tokens\uFF09`,
3444
+ before: beforeSnip,
3445
+ after: afterSnip
3446
+ };
3447
+ }
3448
+ return {
3449
+ recovered: false,
3450
+ messages,
3451
+ reason: "\u53CD\u5E94\u5F0F\u538B\u7F29\u5931\u8D25\uFF1ALLM \u538B\u7F29\u629B\u9519\u4E14 snip \u4E5F\u6CA1\u4E1C\u897F\u53EF\u780D"
3452
+ };
3453
+ }
3454
+
3455
+ // src/context/microCompactLite.ts
3456
+ import { createHash } from "crypto";
3457
+ var MAX_REPEAT_COUNT = 3;
3458
+ var MAX_RESULT_SIZE = 4e3;
3459
+ var HEAD_KEEP_CHARS = 2e3;
3460
+ var TAIL_KEEP_CHARS = 1e3;
3461
+ var MAX_KEEP_ROUNDS = 10;
3462
+ var SHORT_CONTENT_THRESHOLD = 200;
3463
+ var currentTurn = 0;
3464
+ var contentCache = /* @__PURE__ */ new Map();
3465
+ function incrementTurn() {
3466
+ currentTurn++;
3467
+ }
3468
+ var COMPRESSIBLE_TOOLS = /* @__PURE__ */ new Set([
3469
+ "Grep",
3470
+ "Bash",
3471
+ "WebFetch",
3472
+ "Glob"
3473
+ ]);
3474
+ function microCompact(toolName, content) {
3475
+ if (!content) return content;
3476
+ if (content.startsWith("Error:") || content.startsWith("\u9519\u8BEF")) {
3477
+ return content;
3478
+ }
3479
+ if (content.length <= SHORT_CONTENT_THRESHOLD) {
3480
+ return content;
3481
+ }
3482
+ const hash = sha1(content);
3483
+ const existing = contentCache.get(hash);
3484
+ if (existing) {
3485
+ existing.count++;
3486
+ if (existing.count > MAX_REPEAT_COUNT) {
3487
+ return `[\u2191 ${existing.firstToolName} \u7ED3\u679C\u5DF2\u91CD\u590D\u51FA\u73B0 ${existing.count} \u6B21\uFF08\u76F8\u540C\u5185\u5BB9\u5DF2\u7701\u7565\uFF09]`;
3488
+ }
3489
+ return content;
3490
+ }
3491
+ contentCache.set(hash, { count: 1, firstToolName: toolName, firstSeenTurn: currentTurn });
3492
+ if (content.length > MAX_RESULT_SIZE && COMPRESSIBLE_TOOLS.has(toolName)) {
3493
+ return truncateContent(content);
3494
+ }
3495
+ return content;
3496
+ }
3497
+ function expireOldEntries() {
3498
+ let expired = 0;
3499
+ for (const [hash, entry] of contentCache) {
3500
+ if (currentTurn - entry.firstSeenTurn > MAX_KEEP_ROUNDS) {
3501
+ contentCache.delete(hash);
3502
+ expired++;
3503
+ }
3504
+ }
3505
+ return expired;
3506
+ }
3507
+ function sha1(str) {
3508
+ return createHash("sha1").update(str).digest("hex");
3509
+ }
3510
+ function truncateContent(content) {
3511
+ const omitted = content.length - HEAD_KEEP_CHARS - TAIL_KEEP_CHARS;
3512
+ return content.slice(0, HEAD_KEEP_CHARS) + `
3513
+
3514
+ [... \u7701\u7565\u4E86 ${omitted.toLocaleString()} \u5B57\u7B26 ...]
3515
+
3516
+ ` + content.slice(-TAIL_KEEP_CHARS);
3517
+ }
3518
+
3519
+ // src/loop.ts
3520
+ async function* runQuery(userInput, options) {
3521
+ const { provider, history, signal } = options;
3522
+ const maxTurns = options.maxTurns ?? 50;
3523
+ history.push({ role: "user", content: userInput });
3524
+ let reactiveAttempted = false;
3525
+ let turn = 0;
3526
+ while (turn < maxTurns) {
3527
+ turn++;
3528
+ incrementTurn();
3529
+ expireOldEntries();
3530
+ if (signal?.aborted) {
3531
+ history.push({
3532
+ role: "user",
3533
+ content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
3534
+ });
3535
+ yield { type: "error", error: "\u5DF2\u88AB\u7528\u6237\u4E2D\u65AD" };
3536
+ return;
3537
+ }
3538
+ try {
3539
+ const compact = await autoCompactIfNeeded(history, provider);
3540
+ if (compact.compacted) {
3541
+ yield { type: "compact_start" };
3542
+ history.length = 0;
3543
+ history.push(...compact.messages);
3544
+ yield { type: "compact_done", before: compact.before, after: compact.after };
3545
+ }
3546
+ } catch (e) {
3547
+ yield {
3548
+ type: "error",
3549
+ error: `\u81EA\u52A8\u538B\u7F29\u5931\u8D25\uFF08\u7EE7\u7EED\u4E0D\u538B\u7F29\uFF09\uFF1A${e.message}`
3550
+ };
3551
+ }
3552
+ let assistantText = "";
3553
+ const toolCallsByIndex = [];
3554
+ let reasoningContent = "";
3555
+ let reasoningString = "";
3556
+ const reasoningDetails = [];
3557
+ try {
3558
+ for await (const ev of chat({
3559
+ provider,
3560
+ messages: history,
3561
+ tools: ALL_TOOLS,
3562
+ signal
3563
+ })) {
3564
+ if (ev.type === "text_delta") {
3565
+ assistantText += ev.delta;
3566
+ yield { type: "text", delta: ev.delta };
3567
+ } else if (ev.type === "tool_call_delta") {
3568
+ mergeToolCallDelta(toolCallsByIndex, ev);
3569
+ } else if (ev.type === "reasoning_delta") {
3570
+ if (ev.field === "reasoning_content" && ev.delta) {
3571
+ reasoningContent += ev.delta;
3572
+ } else if (ev.field === "reasoning" && ev.delta) {
3573
+ reasoningString += ev.delta;
3574
+ } else if (ev.field === "reasoning_details" && ev.items) {
3575
+ reasoningDetails.push(...ev.items);
3576
+ }
3577
+ }
3578
+ }
3579
+ } catch (e) {
3580
+ if (e.name === "AbortError") {
3581
+ history.push({
3582
+ role: "user",
3583
+ content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
3584
+ });
3585
+ yield { type: "interrupted" };
3586
+ return;
3587
+ }
3588
+ if (isPromptTooLongError(e) && !reactiveAttempted) {
3589
+ reactiveAttempted = true;
3590
+ yield { type: "compact_start" };
3591
+ const result = await reactiveCompactIfApplicable(history, provider, e);
3592
+ if (result.recovered) {
3593
+ history.length = 0;
3594
+ history.push(...result.messages);
3595
+ yield {
3596
+ type: "compact_done",
3597
+ before: result.before ?? 0,
3598
+ after: result.after ?? 0
3599
+ };
3600
+ turn--;
3601
+ continue;
3602
+ }
3603
+ }
3604
+ yield { type: "error", error: `LLM \u8C03\u7528\u5931\u8D25\uFF1A${e.message}` };
3605
+ return;
3606
+ }
3607
+ const assistantMsg = {
3608
+ role: "assistant",
3609
+ content: assistantText.length > 0 ? assistantText : null,
3610
+ ...toolCallsByIndex.length > 0 ? { tool_calls: toolCallsByIndex } : {},
3611
+ ...reasoningContent ? { reasoning_content: reasoningContent } : {},
3612
+ ...reasoningString ? { reasoning: reasoningString } : {},
3613
+ ...reasoningDetails.length > 0 ? { reasoning_details: reasoningDetails } : {}
3614
+ };
3615
+ history.push(assistantMsg);
3616
+ yield { type: "assistant_message", message: assistantMsg };
3617
+ if (toolCallsByIndex.length === 0) {
3618
+ yield { type: "turn_done" };
3619
+ return;
3620
+ }
3621
+ for (const tc of toolCallsByIndex) {
3622
+ if (signal?.aborted) {
3623
+ history.push({
3624
+ role: "user",
3625
+ content: "(\u7528\u6237\u6309\u4E0B\u4E86 ESC/Ctrl+C \u4E2D\u65AD\u4E86\u4EFB\u52A1)"
3626
+ });
3627
+ yield { type: "error", error: "\u5DF2\u88AB\u7528\u6237\u4E2D\u65AD" };
3628
+ return;
3629
+ }
3630
+ const argsPreview = previewArgs(tc.function.arguments);
3631
+ yield {
3632
+ type: "tool_start",
3633
+ toolName: tc.function.name,
3634
+ toolCallId: tc.id,
3635
+ argsPreview
3636
+ };
3637
+ const result = await executeTool(tc.function.name, tc.function.arguments, signal);
3638
+ const rawContent = result.ok ? result.content : `Error: ${result.error}`;
3639
+ const content = microCompact(tc.function.name, rawContent);
3640
+ history.push({
3641
+ role: "tool",
3642
+ content,
3643
+ tool_call_id: tc.id
3644
+ });
3645
+ yield {
3646
+ type: "tool_end",
3647
+ toolName: tc.function.name,
3648
+ toolCallId: tc.id,
3649
+ ok: result.ok,
3650
+ content
3651
+ };
3652
+ }
3653
+ }
3654
+ yield {
3655
+ type: "error",
3656
+ error: `\u8FBE\u5230\u6700\u5927\u8F6E\u6570 ${maxTurns}\uFF0C\u63D0\u524D\u7ED3\u675F\uFF08\u9632\u6B62\u5931\u63A7\uFF09\u3002\u5982\u679C\u5408\u7406\u53EF\u4EE5\u63D0\u9AD8 maxTurns\u3002`
3657
+ };
3658
+ }
3659
+ function mergeToolCallDelta(acc, ev) {
3660
+ let slot = acc[ev.index];
3661
+ if (!slot) {
3662
+ slot = {
3663
+ id: "",
3664
+ type: "function",
3665
+ function: { name: "", arguments: "" }
3666
+ };
3667
+ acc[ev.index] = slot;
3668
+ }
3669
+ if (ev.id) slot.id = ev.id;
3670
+ if (ev.name) slot.function.name += ev.name;
3671
+ if (ev.argumentsDelta) slot.function.arguments += ev.argumentsDelta;
3672
+ }
3673
+ function previewArgs(rawJson) {
3674
+ const oneLine = rawJson.replace(/\s+/g, " ").trim();
3675
+ if (oneLine.length <= 60) return oneLine;
3676
+ return oneLine.slice(0, 60) + "...";
3677
+ }
3678
+
3679
+ // src/ui/hooks/useChat.ts
3680
+ function useChat(args) {
3681
+ const historyRef = useRef3(args.initialHistory.slice());
3682
+ const [version, setVersion] = useState5(0);
3683
+ const bump = useCallback5(() => setVersion((v) => v + 1), []);
3684
+ const [streamingText, setStreamingText] = useState5("");
3685
+ const [toolStatus, setToolStatus] = useState5(null);
3686
+ const [isLoading, setIsLoading] = useState5(false);
3687
+ const [error, setError] = useState5(null);
3688
+ const [interrupted, setInterrupted] = useState5(false);
3689
+ const [compacting, setCompacting] = useState5(false);
3690
+ const abortRef = useRef3(null);
3691
+ useEffect(() => {
3692
+ return () => {
3693
+ abortRef.current?.abort();
3694
+ };
3695
+ }, []);
3696
+ const submit = useCallback5(
3697
+ async (input) => {
3698
+ if (isLoading) return;
3699
+ const trimmed = input.trim();
3700
+ if (trimmed.length === 0) return;
3701
+ setIsLoading(true);
3702
+ setError(null);
3703
+ setInterrupted(false);
3704
+ setStreamingText("");
3705
+ const ac = new AbortController();
3706
+ abortRef.current = ac;
3707
+ try {
3708
+ for await (const ev of runQuery(trimmed, {
3709
+ provider: args.provider,
3710
+ history: historyRef.current,
3711
+ signal: ac.signal
3712
+ })) {
3713
+ handleEvent(ev, {
3714
+ setStreamingText,
3715
+ setToolStatus,
3716
+ setCompacting,
3717
+ setError,
3718
+ setInterrupted,
3719
+ bump
3720
+ });
3721
+ }
3722
+ } catch (e) {
3723
+ setError(`\u672A\u6355\u83B7\u5F02\u5E38\uFF1A${e.message}`);
3724
+ } finally {
3725
+ setIsLoading(false);
3726
+ setStreamingText("");
3727
+ setToolStatus(null);
3728
+ setCompacting(false);
3729
+ abortRef.current = null;
3730
+ args.onPersist?.(historyRef.current);
3731
+ }
3732
+ },
3733
+ [args.provider, args.onPersist, bump, isLoading]
3734
+ );
3735
+ const abort = useCallback5(() => {
3736
+ abortRef.current?.abort();
3737
+ }, []);
3738
+ const clearHistory = useCallback5(async () => {
3739
+ if (isLoading) return;
3740
+ const newSystemPrompt = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
3741
+ historyRef.current.length = 0;
3742
+ historyRef.current.push({ role: "system", content: newSystemPrompt });
3743
+ resetReactiveCompactState();
3744
+ setStreamingText("");
3745
+ setToolStatus(null);
3746
+ setError(null);
3747
+ setInterrupted(false);
3748
+ setCompacting(false);
3749
+ bump();
3750
+ await clearContext();
3751
+ }, [bump, isLoading]);
3752
+ const compactNow = useCallback5(async () => {
3753
+ if (isLoading) return;
3754
+ setIsLoading(true);
3755
+ setCompacting(true);
3756
+ setError(null);
3757
+ let success = false;
3758
+ try {
3759
+ const r = await forceCompact(historyRef.current, args.provider);
3760
+ historyRef.current.length = 0;
3761
+ historyRef.current.push(...r.messages);
3762
+ bump();
3763
+ success = true;
3764
+ } catch (e) {
3765
+ setError(`\u624B\u52A8\u538B\u7F29\u5931\u8D25\uFF1A${e.message}`);
3766
+ } finally {
3767
+ setCompacting(false);
3768
+ setIsLoading(false);
3769
+ if (success) {
3770
+ args.onPersist?.(historyRef.current);
3771
+ args.onCompactDone?.();
3772
+ }
3773
+ }
3774
+ }, [args.provider, args.onPersist, args.onCompactDone, bump, isLoading]);
3775
+ const history = useMemo3(() => historyRef.current, [version]);
3776
+ return {
3777
+ history,
3778
+ streamingText,
3779
+ toolStatus,
3780
+ isLoading,
3781
+ error,
3782
+ interrupted,
3783
+ compacting,
3784
+ submit,
3785
+ abort,
3786
+ clearHistory,
3787
+ compactNow
3788
+ };
3789
+ }
3790
+ function handleEvent(ev, setters) {
3791
+ switch (ev.type) {
3792
+ case "text":
3793
+ setters.setStreamingText((prev) => prev + ev.delta);
3794
+ break;
3795
+ case "assistant_message":
3796
+ setters.setStreamingText(() => "");
3797
+ setters.bump();
3798
+ break;
3799
+ case "tool_start":
3800
+ setters.setToolStatus({
3801
+ toolName: ev.toolName,
3802
+ argsPreview: ev.argsPreview,
3803
+ status: "running"
3804
+ });
3805
+ setters.bump();
3806
+ break;
3807
+ case "tool_end":
3808
+ setters.setToolStatus(null);
3809
+ setters.bump();
3810
+ break;
3811
+ case "compact_start":
3812
+ setters.setStreamingText(() => "");
3813
+ setters.setCompacting(true);
3814
+ setters.bump();
3815
+ break;
3816
+ case "compact_done":
3817
+ setters.setCompacting(false);
3818
+ setters.bump();
3819
+ break;
3820
+ case "turn_done":
3821
+ setters.bump();
3822
+ break;
3823
+ case "interrupted":
3824
+ setters.setInterrupted(true);
3825
+ setters.bump();
3826
+ break;
3827
+ case "error":
3828
+ setters.setError(ev.error);
3829
+ setters.bump();
3830
+ break;
3831
+ }
3832
+ }
3833
+
3834
+ // src/ui/App.tsx
3835
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
3836
+ var CLEAR_SCREEN = "\x1B[2J\x1B[H";
3837
+ function App({ provider, initialHistory }) {
3838
+ const onPersist = React3.useCallback((messages) => {
3839
+ void saveContext(messages);
3840
+ }, []);
3841
+ const chat2 = useChat({
3842
+ provider,
3843
+ initialHistory,
3844
+ onPersist,
3845
+ onCompactDone: () => process.stdout.write(CLEAR_SCREEN)
3846
+ });
3847
+ const handleClear = React3.useCallback(async () => {
3848
+ await chat2.clearHistory();
3849
+ process.stdout.write(CLEAR_SCREEN);
3850
+ }, [chat2]);
3851
+ return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
3852
+ /* @__PURE__ */ jsxs6(Box6, { marginBottom: 1, children: [
3853
+ /* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "minimal-agent" }),
3854
+ /* @__PURE__ */ jsx6(Text6, { color: "gray", children: ` \xB7 ${provider.name}/${provider.model} \xB7 /new \u6E05\u7A7A \xB7 /compact \u538B\u7F29 \xB7 /exit \u9000\u51FA \xB7 Ctrl+C \u4E2D\u65AD` })
3855
+ ] }),
3856
+ /* @__PURE__ */ jsx6(MessageList, { history: chat2.history, streamingText: chat2.streamingText }),
3857
+ /* @__PURE__ */ jsx6(
3858
+ ToolStatus,
3859
+ {
3860
+ status: chat2.toolStatus,
3861
+ compacting: chat2.compacting
3862
+ }
3863
+ ),
3864
+ chat2.error && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsxs6(Text6, { color: "red", children: [
3865
+ "\u26A0 ",
3866
+ chat2.error
3867
+ ] }) }),
3868
+ chat2.interrupted && /* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "yellow", children: "\u26A1 \u64CD\u4F5C\u88AB\u7528\u6237\u4E2D\u65AD\uFF0C\u7B49\u5F85\u65B0\u7684\u4EFB\u52A1\u8F93\u5165..." }) }),
3869
+ /* @__PURE__ */ jsx6(
3870
+ InputBox,
3871
+ {
3872
+ onSubmit: chat2.submit,
3873
+ disabled: chat2.isLoading,
3874
+ onAbort: chat2.abort,
3875
+ onClear: handleClear,
3876
+ onCompact: chat2.compactNow
3877
+ }
3878
+ ),
3879
+ /* @__PURE__ */ jsx6(StatusLine, { provider, history: chat2.history })
3880
+ ] });
3881
+ }
3882
+
3883
+ // src/ui/Root.tsx
3884
+ import { jsx as jsx7 } from "react/jsx-runtime";
3885
+ function Root({ initialProvider }) {
3886
+ const [phase, setPhase] = useState6(
3887
+ initialProvider ? { kind: "loading-history", provider: initialProvider } : { kind: "wizard" }
3888
+ );
3889
+ useEffect2(() => {
3890
+ if (phase.kind !== "loading-history") return;
3891
+ let cancelled = false;
3892
+ void (async () => {
3893
+ const history = await buildInitialHistory();
3894
+ if (cancelled) return;
3895
+ setPhase({ kind: "app", provider: phase.provider, initialHistory: history });
3896
+ })();
3897
+ return () => {
3898
+ cancelled = true;
3899
+ };
3900
+ }, [phase]);
3901
+ if (phase.kind === "wizard") {
3902
+ return /* @__PURE__ */ jsx7(
3903
+ ConfigWizard,
3904
+ {
3905
+ onSuccess: (provider) => {
3906
+ setPhase({ kind: "loading-history", provider });
3907
+ }
3908
+ }
3909
+ );
3910
+ }
3911
+ if (phase.kind === "loading-history") {
3912
+ return /* @__PURE__ */ jsx7(Box7, { paddingX: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", children: "\u6B63\u5728\u52A0\u8F7D\u5386\u53F2\u4E0A\u4E0B\u6587..." }) });
3913
+ }
3914
+ return /* @__PURE__ */ jsx7(App, { provider: phase.provider, initialHistory: phase.initialHistory });
3915
+ }
3916
+ async function buildInitialHistory() {
3917
+ const content = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
3918
+ const fresh = { role: "system", content };
3919
+ const persisted = await loadContext();
3920
+ if (!persisted || persisted.length === 0) return [fresh];
3921
+ const rest = persisted[0]?.role === "system" ? persisted.slice(1) : persisted;
3922
+ return [fresh, ...rest];
3923
+ }
3924
+
3925
+ // src/cli/print.ts
3926
+ var TOOL_OUTPUT_PREVIEW_MAX = 200;
3927
+ var STDIN_TIMEOUT_MS = 3e3;
3928
+ function handleEPIPE(stream) {
3929
+ return (err) => {
3930
+ if (err.code === "EPIPE") stream.destroy();
3931
+ };
3932
+ }
3933
+ function truncateForDisplay(content, max = TOOL_OUTPUT_PREVIEW_MAX) {
3934
+ if (content.length <= max) return content;
3935
+ return content.slice(0, max) + "...";
3936
+ }
3937
+ function extractPromptArgs(args) {
3938
+ const FLAGS = /* @__PURE__ */ new Set(["-p", "--print", "--verbose", "-v", "-h", "--help"]);
3939
+ return args.filter((a) => !FLAGS.has(a));
3940
+ }
3941
+ async function runPrintMode(provider, args, initialHistory, options) {
3942
+ process.stdout.on("error", handleEPIPE(process.stdout));
3943
+ process.stderr.on("error", handleEPIPE(process.stderr));
3944
+ const promptArgs = extractPromptArgs(args);
3945
+ let prompt;
3946
+ if (promptArgs.length > 0) {
3947
+ prompt = promptArgs.join(" ");
3948
+ } else if (!process.stdin.isTTY) {
3949
+ prompt = await readFromStdin();
3950
+ } else {
3951
+ console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u63D0\u793A\u6587\u672C\u6216\u4F7F\u7528\u7BA1\u9053\u8F93\u5165");
3952
+ process.exit(1);
3953
+ }
3954
+ if (!prompt.trim()) {
3955
+ console.error("\u9519\u8BEF: \u63D0\u793A\u4E0D\u80FD\u4E3A\u7A7A");
3956
+ process.exit(1);
3957
+ }
3958
+ const abortController = new AbortController();
3959
+ let interrupted = false;
3960
+ process.on("SIGINT", () => {
3961
+ if (interrupted) process.exit(130);
3962
+ interrupted = true;
3963
+ abortController.abort();
3964
+ console.error("\n\u5DF2\u4E2D\u65AD");
3965
+ void saveContext(initialHistory).finally(() => process.exit(130));
3966
+ });
3967
+ const history = initialHistory;
3968
+ const output = { buffer: "" };
3969
+ try {
3970
+ for await (const event of runQuery(prompt, {
3971
+ provider,
3972
+ history,
3973
+ signal: abortController.signal
3974
+ })) {
3975
+ const result = handleEvent2(event, output, options.verbose);
3976
+ if (result.exitCode !== void 0) {
3977
+ await saveContext(history);
3978
+ process.exit(result.exitCode);
3979
+ }
3980
+ }
3981
+ } catch (e) {
3982
+ if (e.name === "AbortError") {
3983
+ await saveContext(history);
3984
+ process.exit(130);
3985
+ }
3986
+ console.error(`
3987
+ \u672A\u6355\u83B7\u5F02\u5E38: ${e.message}`);
3988
+ await saveContext(history);
3989
+ process.exit(1);
3990
+ }
3991
+ await saveContext(history);
3992
+ }
3993
+ function handleEvent2(event, output, verbose) {
3994
+ switch (event.type) {
3995
+ case "text":
3996
+ if (verbose) process.stdout.write(event.delta);
3997
+ output.buffer += event.delta;
3998
+ return {};
3999
+ case "assistant_message":
4000
+ return {};
4001
+ case "tool_start":
4002
+ if (verbose) {
4003
+ process.stderr.write(`
4004
+ \u{1F527} \u5DE5\u5177: ${event.toolName}(${event.argsPreview})
4005
+ `);
4006
+ }
4007
+ return {};
4008
+ case "tool_end":
4009
+ if (verbose) {
4010
+ process.stderr.write(
4011
+ `${event.ok ? "\u2705" : "\u274C"} ${event.toolName}: ${truncateForDisplay(event.content)}
4012
+ `
4013
+ );
4014
+ }
4015
+ return {};
4016
+ case "compact_start":
4017
+ if (verbose) process.stderr.write("\n\u{1F4DD} \u6B63\u5728\u538B\u7F29\u4E0A\u4E0B\u6587...\n");
4018
+ return {};
4019
+ case "compact_done":
4020
+ if (verbose) {
4021
+ process.stderr.write(
4022
+ `\u{1F4DD} \u538B\u7F29\u5B8C\u6210: ${event.before} \u2192 ${event.after} tokens
4023
+ `
4024
+ );
4025
+ }
4026
+ return {};
4027
+ case "turn_done":
4028
+ if (!verbose) console.log(output.buffer);
4029
+ return {};
4030
+ case "error":
4031
+ console.error(`
4032
+ \u9519\u8BEF: ${event.error}`);
4033
+ return { exitCode: 1 };
4034
+ default:
4035
+ return {};
4036
+ }
4037
+ }
4038
+ function readFromStdin() {
4039
+ return new Promise((resolve7) => {
4040
+ let data = "";
4041
+ let settled = false;
4042
+ const timer = setTimeout(() => {
4043
+ if (!settled) {
4044
+ settled = true;
4045
+ resolve7("");
4046
+ }
4047
+ }, STDIN_TIMEOUT_MS);
4048
+ process.stdin.setEncoding("utf8");
4049
+ function onData(chunk) {
4050
+ data += chunk;
4051
+ }
4052
+ function onEnd() {
4053
+ if (!settled) {
4054
+ clearTimeout(timer);
4055
+ settled = true;
4056
+ resolve7(data.trim());
4057
+ }
4058
+ }
4059
+ process.stdin.on("data", onData);
4060
+ process.stdin.on("end", onEnd);
4061
+ });
4062
+ }
4063
+
4064
+ // src/main.tsx
4065
+ import { jsx as jsx8 } from "react/jsx-runtime";
4066
+ var require2 = createRequire(import.meta.url);
4067
+ var pkg = require2("../package.json");
4068
+ async function main() {
4069
+ const args = process.argv.slice(2);
4070
+ if (args.includes("-h") || args.includes("--help")) {
4071
+ printHelp();
4072
+ return;
4073
+ }
4074
+ if (args.includes("--version") || args.includes("-V")) {
4075
+ console.log(`minimal-agent v${pkg.version}`);
4076
+ return;
4077
+ }
4078
+ const isPrintMode = args.includes("-p") || args.includes("--print");
4079
+ if (isPrintMode) {
4080
+ let provider;
4081
+ try {
4082
+ provider = await loadProvider();
4083
+ } catch (e) {
4084
+ process.stderr.write(
4085
+ `
4086
+ ${e.message}
4087
+
4088
+ \u63D0\u793A\uFF1A-p \u6A21\u5F0F\u4E0D\u652F\u6301\u4EA4\u4E92\u914D\u7F6E\u3002\u8BF7\u5148\u76F4\u63A5\u8FD0\u884C \`minimal-agent\` \u5B8C\u6210\u9996\u6B21\u914D\u7F6E\u5411\u5BFC\uFF0C\u6216\u5728 .env \u4E2D\u624B\u52A8\u586B\u597D BASE_URL / API_KEY / MODEL\u3002
4089
+
4090
+ `
4091
+ );
4092
+ process.exit(1);
4093
+ }
4094
+ const initialHistory = await buildInitialHistory2();
4095
+ const verbose = args.includes("--verbose") || args.includes("-v");
4096
+ await runPrintMode(provider, args, initialHistory, { verbose });
4097
+ return;
4098
+ }
4099
+ const initialProvider = await loadProviderLayered();
4100
+ const { waitUntilExit } = render(/* @__PURE__ */ jsx8(Root, { initialProvider }));
4101
+ await waitUntilExit();
4102
+ }
4103
+ async function buildInitialHistory2() {
4104
+ const content = await buildFullSystemPrompt(process.cwd(), ALL_TOOLS);
4105
+ const fresh = { role: "system", content };
4106
+ const persisted = await loadContext();
4107
+ if (!persisted || persisted.length === 0) return [fresh];
4108
+ const rest = persisted[0]?.role === "system" ? persisted.slice(1) : persisted;
4109
+ return [fresh, ...rest];
4110
+ }
4111
+ function printHelp() {
4112
+ console.log(`
4113
+ minimal-agent - \u8F7B\u91CF\u7EA7 AI \u7F16\u7A0B\u52A9\u624B
4114
+
4115
+ \u7528\u6CD5:
4116
+ minimal-agent [\u9009\u9879] [\u63D0\u793A...]
4117
+ echo "\u63D0\u793A" | minimal-agent [\u9009\u9879]
4118
+
4119
+ \u9009\u9879:
4120
+ -p, --print \u975E\u4EA4\u4E92\u6A21\u5F0F\uFF0C\u76F4\u63A5\u6267\u884C\u5355\u6B21\u95EE\u7B54
4121
+ -v, --verbose \u663E\u793A\u8BE6\u7EC6\u8F93\u51FA\uFF08\u5DE5\u5177\u8C03\u7528\u3001\u538B\u7F29\u4FE1\u606F\uFF09
4122
+ -h, --help \u663E\u793A\u5E2E\u52A9\u4FE1\u606F
4123
+
4124
+ \u4F1A\u8BDD\u8BB0\u5FC6:
4125
+ \u81EA\u52A8\u52A0\u8F7D / \u4FDD\u5B58 ~/.minimal-agent/last-context.json
4126
+ TUI \u4E0E -p \u5171\u4EAB\u540C\u4E00\u4EFD\u4E0A\u4E0B\u6587\uFF1BTUI \u4E2D\u8F93\u5165 /new \u6E05\u7A7A
4127
+
4128
+ \u793A\u4F8B:
4129
+ minimal-agent -p "\u5E2E\u6211\u5199\u4E00\u4E2A hello world"
4130
+ echo "\u89E3\u91CA\u4EE3\u7801" | minimal-agent -p
4131
+ minimal-agent -p --verbose "\u8FD0\u884C\u6D4B\u8BD5\u5E76\u62A5\u544A\u7ED3\u679C"
4132
+ `);
4133
+ }
4134
+ main().catch((e) => {
4135
+ process.stderr.write(`
4136
+ \u672A\u6355\u83B7\u5F02\u5E38\uFF1A${e.message}
4137
+ ${e.stack ?? ""}
4138
+ `);
4139
+ process.exit(1);
4140
+ });