@huicai/cli 2026.814.0 → 2026.1019.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 (485) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +137 -8
  3. package/bin/huicai.js +146 -73
  4. package/package.json +27 -8
  5. package/skills/ab-testing/trigger-cases.json +30 -0
  6. package/skills/academic-writing/trigger-cases.json +30 -0
  7. package/skills/accounting-ops/trigger-cases.json +30 -0
  8. package/skills/ai-agent/trigger-cases.json +30 -0
  9. package/skills/ai-aiops/trigger-cases.json +30 -0
  10. package/skills/ai-coding/trigger-cases.json +30 -0
  11. package/skills/ai-compliance/trigger-cases.json +30 -0
  12. package/skills/ai-eval/trigger-cases.json +30 -0
  13. package/skills/ai-llm-app/trigger-cases.json +30 -0
  14. package/skills/ai-mlops/trigger-cases.json +30 -0
  15. package/skills/ai-multimodal/trigger-cases.json +30 -0
  16. package/skills/ai-prompt/trigger-cases.json +30 -0
  17. package/skills/ai-rag/trigger-cases.json +30 -0
  18. package/skills/anomaly-detection/trigger-cases.json +30 -0
  19. package/skills/api-design/trigger-cases.json +31 -0
  20. package/skills/api-integration/trigger-cases.json +31 -0
  21. package/skills/arch-design/trigger-cases.json +12 -0
  22. package/skills/arch-diagram/SKILL.md +118 -0
  23. package/skills/arch-diagram/trigger-cases.json +30 -0
  24. package/skills/arch-distributed/trigger-cases.json +30 -0
  25. package/skills/arch-multitenant/trigger-cases.json +30 -0
  26. package/skills/audio-brief/SKILL.md +65 -0
  27. package/skills/audio-brief/trigger-cases.json +30 -0
  28. package/skills/audit-assist/trigger-cases.json +30 -0
  29. package/skills/audit-chain/trigger-cases.json +31 -0
  30. package/skills/banking-credit/trigger-cases.json +30 -0
  31. package/skills/brand-design/trigger-cases.json +30 -0
  32. package/skills/business-plan/trigger-cases.json +30 -0
  33. package/skills/capital-ops/trigger-cases.json +30 -0
  34. package/skills/cicd-pipeline/trigger-cases.json +31 -0
  35. package/skills/code-archaeology/SKILL.md +55 -0
  36. package/skills/code-archaeology/trigger-cases.json +9 -0
  37. package/skills/code-quality/SKILL.md +138 -0
  38. package/skills/code-quality/references/languages.md +107 -0
  39. package/skills/code-quality/scripts/__pycache__/code_battery.cpython-312.pyc +0 -0
  40. package/skills/code-quality/scripts/__pycache__/code_check.cpython-312.pyc +0 -0
  41. package/skills/code-quality/scripts/__pycache__/code_check.cpython-313.pyc +0 -0
  42. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-312.pyc +0 -0
  43. package/skills/code-quality/scripts/__pycache__/code_csharp.cpython-313.pyc +0 -0
  44. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-312.pyc +0 -0
  45. package/skills/code-quality/scripts/__pycache__/code_fix.cpython-313.pyc +0 -0
  46. package/skills/code-quality/scripts/__pycache__/code_java.cpython-312.pyc +0 -0
  47. package/skills/code-quality/scripts/__pycache__/code_java.cpython-313.pyc +0 -0
  48. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-312.pyc +0 -0
  49. package/skills/code-quality/scripts/__pycache__/code_jsts.cpython-313.pyc +0 -0
  50. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-312.pyc +0 -0
  51. package/skills/code-quality/scripts/__pycache__/code_lens.cpython-313.pyc +0 -0
  52. package/skills/code-quality/scripts/__pycache__/code_python.cpython-312.pyc +0 -0
  53. package/skills/code-quality/scripts/__pycache__/code_python.cpython-313.pyc +0 -0
  54. package/skills/code-quality/scripts/__pycache__/code_testplan.cpython-312.pyc +0 -0
  55. package/skills/code-quality/scripts/code_battery.py +1072 -0
  56. package/skills/code-quality/scripts/code_check.py +1269 -0
  57. package/skills/code-quality/scripts/code_csharp.py +371 -0
  58. package/skills/code-quality/scripts/code_fix.py +201 -0
  59. package/skills/code-quality/scripts/code_java.py +695 -0
  60. package/skills/code-quality/scripts/code_jsts.py +313 -0
  61. package/skills/code-quality/scripts/code_lens.py +1079 -0
  62. package/skills/code-quality/scripts/code_python.py +536 -0
  63. package/skills/code-quality/scripts/code_testplan.py +239 -0
  64. package/skills/code-quality/trigger-cases.json +45 -0
  65. package/skills/code-review/SKILL.md +24 -0
  66. package/skills/code-review/trigger-cases.json +35 -0
  67. package/skills/commit-and-pr/trigger-cases.json +31 -0
  68. package/skills/content-creation/trigger-cases.json +30 -0
  69. package/skills/corporate-governance/trigger-cases.json +30 -0
  70. package/skills/crawl-chain/trigger-cases.json +31 -0
  71. package/skills/customer-service/trigger-cases.json +30 -0
  72. package/skills/data-analysis/trigger-cases.json +30 -0
  73. package/skills/data-chain/SKILL.md +1 -1
  74. package/skills/data-chain/trigger-cases.json +31 -0
  75. package/skills/data-governance/trigger-cases.json +30 -0
  76. package/skills/data-report-pro/trigger-cases.json +30 -0
  77. package/skills/data-streaming/trigger-cases.json +30 -0
  78. package/skills/data-wrangling/SKILL.md +1 -1
  79. package/skills/data-wrangling/trigger-cases.json +30 -0
  80. package/skills/db-es/trigger-cases.json +30 -0
  81. package/skills/db-migration/trigger-cases.json +30 -0
  82. package/skills/db-mysql/trigger-cases.json +30 -0
  83. package/skills/db-postgres/trigger-cases.json +30 -0
  84. package/skills/db-redis/trigger-cases.json +30 -0
  85. package/skills/debug-root-cause/trigger-cases.json +30 -0
  86. package/skills/dependency-audit/SKILL.md +56 -0
  87. package/skills/dependency-audit/trigger-cases.json +7 -0
  88. package/skills/dependency-upgrade/trigger-cases.json +31 -0
  89. package/skills/dev-cpp/trigger-cases.json +30 -0
  90. package/skills/dev-cross-platform/trigger-cases.json +30 -0
  91. package/skills/dev-csharp/SKILL.md +23 -0
  92. package/skills/dev-csharp/trigger-cases.json +30 -0
  93. package/skills/dev-frontend/SKILL.md +19 -0
  94. package/skills/dev-frontend/trigger-cases.json +30 -0
  95. package/skills/dev-go/trigger-cases.json +30 -0
  96. package/skills/dev-harmony/trigger-cases.json +30 -0
  97. package/skills/dev-java/SKILL.md +20 -0
  98. package/skills/dev-java/trigger-cases.json +30 -0
  99. package/skills/dev-mobile/trigger-cases.json +30 -0
  100. package/skills/dev-node/trigger-cases.json +30 -0
  101. package/skills/dev-php/trigger-cases.json +30 -0
  102. package/skills/dev-python/SKILL.md +21 -0
  103. package/skills/dev-python/trigger-cases.json +30 -0
  104. package/skills/dev-rust/trigger-cases.json +30 -0
  105. package/skills/dev-toolchain/trigger-cases.json +31 -0
  106. package/skills/doc-chain/trigger-cases.json +31 -0
  107. package/skills/doc-compare/trigger-cases.json +30 -0
  108. package/skills/docx-author/SKILL.md +127 -25
  109. package/skills/docx-author/scripts/__pycache__/docx_battery.cpython-312.pyc +0 -0
  110. package/skills/docx-author/scripts/__pycache__/docx_build.cpython-312.pyc +0 -0
  111. package/skills/docx-author/scripts/__pycache__/docx_check.cpython-312.pyc +0 -0
  112. package/skills/docx-author/scripts/__pycache__/docx_layout.cpython-312.pyc +0 -0
  113. package/skills/docx-author/scripts/__pycache__/zh_prose.cpython-312.pyc +0 -0
  114. package/skills/docx-author/scripts/docx_battery.py +586 -0
  115. package/skills/docx-author/scripts/docx_build.py +995 -0
  116. package/skills/docx-author/scripts/docx_check.py +1306 -0
  117. package/skills/docx-author/scripts/docx_doc.py +14 -154
  118. package/skills/docx-author/scripts/docx_layout.py +490 -0
  119. package/skills/docx-author/scripts/zh_prose.py +639 -0
  120. package/skills/docx-author/trigger-cases.json +58 -0
  121. package/skills/echo-brief/SKILL.md +84 -0
  122. package/skills/echo-brief/references/echo-format.md +68 -0
  123. package/skills/echo-brief/trigger-cases.json +40 -0
  124. package/skills/ecommerce-ops/trigger-cases.json +30 -0
  125. package/skills/education-training/trigger-cases.json +30 -0
  126. package/skills/ehs-safety/trigger-cases.json +30 -0
  127. package/skills/email-writing/trigger-cases.json +30 -0
  128. package/skills/eng-ai/trigger-cases.json +30 -0
  129. package/skills/eng-architecture/trigger-cases.json +31 -0
  130. package/skills/eng-automation/trigger-cases.json +31 -0
  131. package/skills/eng-collaboration/trigger-cases.json +30 -0
  132. package/skills/eng-complex-env/trigger-cases.json +30 -0
  133. package/skills/eng-data-platform/trigger-cases.json +31 -0
  134. package/skills/eng-database/trigger-cases.json +31 -0
  135. package/skills/eng-develop/trigger-cases.json +30 -0
  136. package/skills/eng-docs/SKILL.md +24 -0
  137. package/skills/eng-docs/trigger-cases.json +30 -0
  138. package/skills/eng-efficiency/trigger-cases.json +30 -0
  139. package/skills/eng-infra/trigger-cases.json +30 -0
  140. package/skills/eng-integration/trigger-cases.json +31 -0
  141. package/skills/eng-legacy/trigger-cases.json +31 -0
  142. package/skills/eng-observability/trigger-cases.json +31 -0
  143. package/skills/eng-ops/trigger-cases.json +31 -0
  144. package/skills/eng-performance/trigger-cases.json +31 -0
  145. package/skills/eng-planning/trigger-cases.json +31 -0
  146. package/skills/eng-quality/SKILL.md +8 -0
  147. package/skills/eng-quality/trigger-cases.json +31 -0
  148. package/skills/eng-release/trigger-cases.json +31 -0
  149. package/skills/eng-reliability/trigger-cases.json +31 -0
  150. package/skills/eng-requirements/trigger-cases.json +12 -0
  151. package/skills/eng-research/trigger-cases.json +30 -0
  152. package/skills/eng-security/trigger-cases.json +31 -0
  153. package/skills/eng-ux/trigger-cases.json +30 -0
  154. package/skills/engineering-chain/trigger-cases.json +30 -0
  155. package/skills/env-offline/trigger-cases.json +31 -0
  156. package/skills/env-private-deploy/trigger-cases.json +31 -0
  157. package/skills/env-xinchuang/trigger-cases.json +31 -0
  158. package/skills/equipment-tpm/trigger-cases.json +30 -0
  159. package/skills/esg-report/trigger-cases.json +30 -0
  160. package/skills/event-management/trigger-cases.json +30 -0
  161. package/skills/excel-analysis-chain/SKILL.md +17 -4
  162. package/skills/excel-analysis-chain/trigger-cases.json +31 -0
  163. package/skills/explain-codebase/trigger-cases.json +11 -0
  164. package/skills/file-ingest/trigger-cases.json +30 -0
  165. package/skills/finance-analysis/trigger-cases.json +31 -0
  166. package/skills/finance-chain/trigger-cases.json +31 -0
  167. package/skills/financial-modeling/trigger-cases.json +31 -0
  168. package/skills/find-simplifications/SKILL.md +43 -0
  169. package/skills/find-simplifications/trigger-cases.json +23 -0
  170. package/skills/foreign-trade/trigger-cases.json +30 -0
  171. package/skills/funnel-cohort/trigger-cases.json +30 -0
  172. package/skills/gov-document/trigger-cases.json +30 -0
  173. package/skills/gui-change-evidence/SKILL.md +47 -0
  174. package/skills/gui-change-evidence/trigger-cases.json +23 -0
  175. package/skills/hr-chain/trigger-cases.json +31 -0
  176. package/skills/hr-operations/trigger-cases.json +30 -0
  177. package/skills/info-disclosure/trigger-cases.json +31 -0
  178. package/skills/infra-gateway/trigger-cases.json +31 -0
  179. package/skills/infra-k8s/trigger-cases.json +31 -0
  180. package/skills/infra-mq/trigger-cases.json +30 -0
  181. package/skills/insurance-advisory/trigger-cases.json +30 -0
  182. package/skills/internal-audit/trigger-cases.json +30 -0
  183. package/skills/ip-management/trigger-cases.json +31 -0
  184. package/skills/ipo-listing/trigger-cases.json +31 -0
  185. package/skills/it-audit/trigger-cases.json +31 -0
  186. package/skills/itsm-ops/trigger-cases.json +31 -0
  187. package/skills/knowledge-sop/trigger-cases.json +31 -0
  188. package/skills/legal-chain/trigger-cases.json +31 -0
  189. package/skills/legal-contract/trigger-cases.json +30 -0
  190. package/skills/litigation-support/trigger-cases.json +31 -0
  191. package/skills/logistics-warehouse/trigger-cases.json +30 -0
  192. package/skills/long-doc-analysis/trigger-cases.json +31 -0
  193. package/skills/management-accounting/trigger-cases.json +30 -0
  194. package/skills/manufacturing-chain/trigger-cases.json +31 -0
  195. package/skills/manufacturing-quality/trigger-cases.json +30 -0
  196. package/skills/marketing-analytics/trigger-cases.json +31 -0
  197. package/skills/marketing-chain/trigger-cases.json +31 -0
  198. package/skills/meeting-minutes/trigger-cases.json +30 -0
  199. package/skills/metrics-system/trigger-cases.json +31 -0
  200. package/skills/negotiation-prep/trigger-cases.json +30 -0
  201. package/skills/obs-prometheus/trigger-cases.json +31 -0
  202. package/skills/office-automation/trigger-cases.json +31 -0
  203. package/skills/okr-review/trigger-cases.json +30 -0
  204. package/skills/ops-fleet/trigger-cases.json +31 -0
  205. package/skills/ops-linux/trigger-cases.json +31 -0
  206. package/skills/optimization-solver/trigger-cases.json +30 -0
  207. package/skills/patent-disclosure/SKILL.md +105 -0
  208. package/skills/patent-disclosure/references/figures.md +110 -0
  209. package/skills/patent-disclosure/references/outline.md +162 -0
  210. package/skills/patent-disclosure/scripts/patent_docx.py +230 -0
  211. package/skills/patent-disclosure/trigger-cases.json +31 -0
  212. package/skills/patent-drafting/SKILL.md +108 -0
  213. package/skills/patent-drafting/trigger-cases.json +31 -0
  214. package/skills/patent-portfolio/SKILL.md +81 -0
  215. package/skills/patent-portfolio/trigger-cases.json +31 -0
  216. package/skills/pdf-toolkit/trigger-cases.json +31 -0
  217. package/skills/perf-optimize/trigger-cases.json +31 -0
  218. package/skills/pivot-crosstab/trigger-cases.json +30 -0
  219. package/skills/policy-analysis/trigger-cases.json +31 -0
  220. package/skills/portfolio-analysis/trigger-cases.json +31 -0
  221. package/skills/ppt-chain/SKILL.md +14 -6
  222. package/skills/ppt-chain/trigger-cases.json +31 -0
  223. package/skills/pptx-author/SKILL.md +86 -33
  224. package/skills/pptx-author/scripts/__pycache__/pptx_check.cpython-312.pyc +0 -0
  225. package/skills/pptx-author/scripts/__pycache__/pptx_deck.cpython-312.pyc +0 -0
  226. package/skills/pptx-author/scripts/__pycache__/pptx_layout.cpython-312.pyc +0 -0
  227. package/skills/pptx-author/scripts/pptx_battery.py +178 -0
  228. package/skills/pptx-author/scripts/pptx_build.py +119 -0
  229. package/skills/pptx-author/scripts/pptx_check.py +1182 -0
  230. package/skills/pptx-author/scripts/pptx_deck.py +736 -145
  231. package/skills/pptx-author/scripts/pptx_layout.py +384 -0
  232. package/skills/pptx-author/trigger-cases.json +15 -0
  233. package/skills/pr-comms/trigger-cases.json +30 -0
  234. package/skills/procurement-supply/trigger-cases.json +31 -0
  235. package/skills/product-design/trigger-cases.json +31 -0
  236. package/skills/production-planning/trigger-cases.json +31 -0
  237. package/skills/project-management/trigger-cases.json +31 -0
  238. package/skills/project-proposal/trigger-cases.json +31 -0
  239. package/skills/prose-standard/SKILL.md +53 -0
  240. package/skills/prose-standard/trigger-cases.json +23 -0
  241. package/skills/qa-api-automation/trigger-cases.json +31 -0
  242. package/skills/qa-mobile-automation/trigger-cases.json +31 -0
  243. package/skills/qa-ui-automation/trigger-cases.json +31 -0
  244. package/skills/quant-analysis/trigger-cases.json +31 -0
  245. package/skills/rag-triage/SKILL.md +64 -0
  246. package/skills/rag-triage/evals.json +57 -0
  247. package/skills/rag-triage/references/triage-playbook.md +75 -0
  248. package/skills/rag-triage/trigger-cases.json +31 -0
  249. package/skills/real-estate-property/trigger-cases.json +30 -0
  250. package/skills/refactor-safely/SKILL.md +1 -1
  251. package/skills/refactor-safely/trigger-cases.json +30 -0
  252. package/skills/regression-forecast/trigger-cases.json +31 -0
  253. package/skills/release-checklist/trigger-cases.json +31 -0
  254. package/skills/report-craft/SKILL.md +76 -0
  255. package/skills/report-craft/references/feasibility.md +118 -0
  256. package/skills/report-craft/references/review-checklist.md +51 -0
  257. package/skills/report-craft/references/visuals.md +114 -0
  258. package/skills/report-craft/references/writing.md +80 -0
  259. package/skills/report-craft/scripts/__pycache__/chart_render.cpython-312.pyc +0 -0
  260. package/skills/report-craft/scripts/__pycache__/fin_model.cpython-312.pyc +0 -0
  261. package/skills/report-craft/scripts/__pycache__/project_facts.cpython-312.pyc +0 -0
  262. package/skills/report-craft/scripts/chart_render.py +1222 -0
  263. package/skills/report-craft/scripts/fin_model.py +602 -0
  264. package/skills/report-craft/scripts/project_facts.py +580 -0
  265. package/skills/report-craft/scripts/report_battery.py +349 -0
  266. package/skills/report-craft/trigger-cases.json +15 -0
  267. package/skills/retail-store-ops/trigger-cases.json +30 -0
  268. package/skills/risk-compliance/trigger-cases.json +31 -0
  269. package/skills/scope-creep/SKILL.md +45 -0
  270. package/skills/scope-creep/trigger-cases.json +7 -0
  271. package/skills/sec-auth/trigger-cases.json +31 -0
  272. package/skills/sec-compliance/trigger-cases.json +31 -0
  273. package/skills/securities-chain/trigger-cases.json +31 -0
  274. package/skills/securities-research/trigger-cases.json +31 -0
  275. package/skills/security-audit/trigger-cases.json +31 -0
  276. package/skills/slides-html/SKILL.md +25 -10
  277. package/skills/slides-html/trigger-cases.json +11 -0
  278. package/skills/sql-analysis/SKILL.md +110 -0
  279. package/skills/sql-analysis/trigger-cases.json +34 -0
  280. package/skills/sql-tuning/trigger-cases.json +31 -0
  281. package/skills/statistical-testing/trigger-cases.json +31 -0
  282. package/skills/strategy-planning/trigger-cases.json +30 -0
  283. package/skills/supply-chain/trigger-cases.json +31 -0
  284. package/skills/tax-compliance/trigger-cases.json +30 -0
  285. package/skills/tech-doc/SKILL.md +262 -0
  286. package/skills/tech-doc/scripts/__pycache__/archive_battery.cpython-312.pyc +0 -0
  287. package/skills/tech-doc/scripts/__pycache__/archive_build.cpython-312.pyc +0 -0
  288. package/skills/tech-doc/scripts/__pycache__/archive_check.cpython-312.pyc +0 -0
  289. package/skills/tech-doc/scripts/__pycache__/archive_spec.cpython-312.pyc +0 -0
  290. package/skills/tech-doc/scripts/__pycache__/doc_templates.cpython-312.pyc +0 -0
  291. package/skills/tech-doc/scripts/archive_battery.py +860 -0
  292. package/skills/tech-doc/scripts/archive_build.py +599 -0
  293. package/skills/tech-doc/scripts/archive_check.py +1307 -0
  294. package/skills/tech-doc/scripts/archive_spec.py +499 -0
  295. package/skills/tech-doc/scripts/doc_templates.py +624 -0
  296. package/skills/tech-doc/trigger-cases.json +57 -0
  297. package/skills/tech-research/trigger-cases.json +31 -0
  298. package/skills/tender-bidding/trigger-cases.json +31 -0
  299. package/skills/translation-pro/trigger-cases.json +30 -0
  300. package/skills/treasury-mgmt/trigger-cases.json +31 -0
  301. package/skills/trim-cot-leakage/SKILL.md +58 -0
  302. package/skills/trim-cot-leakage/trigger-cases.json +23 -0
  303. package/skills/web-craft/SKILL.md +103 -0
  304. package/skills/web-craft/references/craft-checklist.md +75 -0
  305. package/skills/web-craft/references/design-direction.md +122 -0
  306. package/skills/web-craft/references/h5-mobile.md +100 -0
  307. package/skills/web-craft/references/motion.md +136 -0
  308. package/skills/web-craft/references/recipes.md +74 -0
  309. package/skills/web-craft/templates/wedding-invitation.html +1180 -0
  310. package/skills/web-craft/trigger-cases.json +14 -0
  311. package/skills/web-scraping/trigger-cases.json +34 -0
  312. package/skills/web-search/SKILL.md +11 -10
  313. package/skills/web-search/trigger-cases.json +31 -0
  314. package/skills/weekly-report/SKILL.md +134 -0
  315. package/skills/weekly-report/trigger-cases.json +11 -0
  316. package/skills/write-tests/trigger-cases.json +31 -0
  317. package/skills/xlsx-analysis/SKILL.md +143 -77
  318. package/skills/xlsx-analysis/scripts/__pycache__/data_profile.cpython-312.pyc +0 -0
  319. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_build.cpython-312.pyc +0 -0
  320. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_check.cpython-312.pyc +0 -0
  321. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_layout.cpython-312.pyc +0 -0
  322. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  323. package/skills/xlsx-analysis/scripts/data_profile.py +391 -0
  324. package/skills/xlsx-analysis/scripts/xlsx_battery.py +389 -0
  325. package/skills/xlsx-analysis/scripts/xlsx_build.py +522 -0
  326. package/skills/xlsx-analysis/scripts/xlsx_check.py +924 -0
  327. package/skills/xlsx-analysis/scripts/xlsx_layout.py +235 -0
  328. package/skills/xlsx-analysis/scripts/xlsx_style.py +28 -66
  329. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +61 -5
  330. package/skills/xlsx-analysis/trigger-cases.json +59 -0
  331. package/squads/hc-defense.md +47 -0
  332. package/squads/hc-rdarchive.md +110 -0
  333. package/squads/hc-redteam.md +50 -0
  334. package/squads/hc-weekly.md +125 -0
  335. package/src/account.js +1 -1
  336. package/src/acp.js +1 -0
  337. package/src/advisor.js +1 -1
  338. package/src/agent.js +1 -1
  339. package/src/api.js +1 -1
  340. package/src/archaeology.js +1 -0
  341. package/src/ask-ui.js +1 -0
  342. package/src/assets-import.js +1 -1
  343. package/src/autofix.js +1 -1
  344. package/src/automation-tools.js +1 -1
  345. package/src/automation.js +1 -1
  346. package/src/autorun.js +1 -1
  347. package/src/banner.js +1 -1
  348. package/src/capability-probe.js +1 -0
  349. package/src/checkpoints.js +1 -1
  350. package/src/code-tools.js +1 -0
  351. package/src/codeindex.js +1 -1
  352. package/src/codemode.js +1 -0
  353. package/src/command-bridge.js +1 -0
  354. package/src/command-ui.js +1 -0
  355. package/src/commands.js +1 -1
  356. package/src/compaction.js +1 -1
  357. package/src/computer.js +1 -1
  358. package/src/config.js +1 -1
  359. package/src/consult.js +1 -1
  360. package/src/context-report.js +1 -1
  361. package/src/context-window.js +1 -0
  362. package/src/context.js +1 -1
  363. package/src/crew.js +1 -0
  364. package/src/crush.js +1 -0
  365. package/src/daemon.js +1 -1
  366. package/src/decisions.js +1 -1
  367. package/src/depaudit.js +1 -0
  368. package/src/device.js +1 -1
  369. package/src/diagram-draw.js +1 -0
  370. package/src/diagram-layouts.js +1 -0
  371. package/src/diagram-raster.js +1 -0
  372. package/src/diagram.js +1 -0
  373. package/src/dictation.js +1 -0
  374. package/src/diffview.js +1 -1
  375. package/src/doctor.js +1 -1
  376. package/src/docx-tools.js +1 -0
  377. package/src/errkind.js +1 -1
  378. package/src/evidence.js +1 -0
  379. package/src/executors.js +1 -1
  380. package/src/expert-catalog.js +1 -1
  381. package/src/experts-store.js +1 -1
  382. package/src/experts.js +1 -1
  383. package/src/external-agents.js +1 -0
  384. package/src/forks.js +1 -1
  385. package/src/fs-observe.js +1 -0
  386. package/src/gitflow.js +1 -1
  387. package/src/goalround.js +1 -0
  388. package/src/hooks.js +1 -1
  389. package/src/input.js +1 -1
  390. package/src/insights.js +1 -1
  391. package/src/jobs.js +1 -0
  392. package/src/kbsync.js +1 -1
  393. package/src/lsp.js +1 -0
  394. package/src/mcp-apps.js +1 -0
  395. package/src/mcp.js +1 -1
  396. package/src/memory.js +1 -1
  397. package/src/notify.js +1 -1
  398. package/src/permissions.js +1 -1
  399. package/src/plan-limits.js +1 -0
  400. package/src/policy.js +1 -0
  401. package/src/pptx-tools.js +1 -0
  402. package/src/presence.js +1 -1
  403. package/src/privatedir.js +1 -0
  404. package/src/pyruntime.js +1 -1
  405. package/src/radar.js +1 -0
  406. package/src/render.js +1 -1
  407. package/src/repl.js +1 -1
  408. package/src/report-tools.js +1 -0
  409. package/src/routing.js +1 -0
  410. package/src/sandbox-probe.js +1 -0
  411. package/src/sandbox-win-launcher.mjs +156 -0
  412. package/src/sandbox.js +1 -0
  413. package/src/scan.js +1 -1
  414. package/src/schedule.js +1 -1
  415. package/src/scopecreep.js +1 -0
  416. package/src/sdk.js +1 -0
  417. package/src/selection.js +1 -0
  418. package/src/session-events.js +1 -0
  419. package/src/session.js +1 -1
  420. package/src/sessionindex.js +1 -0
  421. package/src/sessionlog.js +1 -0
  422. package/src/skill-catalog.js +1 -0
  423. package/src/skill-lint.js +1 -0
  424. package/src/skill-optimize.js +1 -0
  425. package/src/skill-scan.js +1 -0
  426. package/src/skills.js +1 -1
  427. package/src/spill.js +1 -0
  428. package/src/squad.js +1 -1
  429. package/src/subagent.js +1 -1
  430. package/src/surface-state.js +1 -0
  431. package/src/tapd.js +1 -0
  432. package/src/terminal-select.js +1 -0
  433. package/src/terminal.js +1 -0
  434. package/src/test-runner.js +1 -0
  435. package/src/todo.js +1 -1
  436. package/src/toolpipe.js +1 -0
  437. package/src/tools.js +1 -1
  438. package/src/toon.js +1 -0
  439. package/src/transcript.js +1 -1
  440. package/src/tui/app.js +1 -1
  441. package/src/tui/command-menu.js +1 -0
  442. package/src/tui/progress.js +1 -0
  443. package/src/tui/scroll.js +1 -0
  444. package/src/tui/welcome.js +1 -0
  445. package/src/tui/workbench.js +1 -0
  446. package/src/ui-spec.js +1 -0
  447. package/src/uitree.js +1 -0
  448. package/src/uninstall.js +1 -1
  449. package/src/visual.js +1 -1
  450. package/src/voice-form.js +1 -0
  451. package/src/voice.js +1 -0
  452. package/src/watch.js +1 -1
  453. package/src/web-audit.js +1 -0
  454. package/src/web-cdp.js +1 -0
  455. package/src/web-inspect.js +1 -0
  456. package/src/websearch.js +1 -0
  457. package/src/weekly.js +1 -0
  458. package/src/workflow.js +1 -0
  459. package/src/worklog.js +1 -0
  460. package/src/xlsx-tools.js +1 -0
  461. package/ui/command-picker.js +79 -0
  462. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  463. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  464. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  465. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  466. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  467. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  468. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  469. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  470. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  471. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  472. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  473. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  474. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  475. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  476. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  477. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  478. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  479. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  480. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  481. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  482. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  483. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  484. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  485. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: hc-redteam
3
+ description: 方案红队——蓝队给设计、红队专攻它怎么坏、裁判裁定哪些攻击成立(adversarial 模式)
4
+ leader: 演习长
5
+ mode: adversarial
6
+ when: 技术方案/架构/上线计划要评审,想先被攻一遍时;也用于安全设计、容灾预案、大促预案的推演
7
+ ---
8
+
9
+ 「评审通过」和「扛得住」是两件事。评审看的是方案说得通不通,红队看的是**它在什么情况下会坏**。
10
+ 本团只做后者:不讨论方案好不好,只找它坏掉的路径。
11
+
12
+ ## 成员
13
+ - blue | 蓝队 | 给出方案设计与其防御假设 | | proponent
14
+ - red | 红队 | 专攻方案的失效路径:边界、并发、故障、依赖、安全、运维 | | opponent
15
+ - judge | 裁判 | 裁定哪些攻击真的成立、按严重度排序 | | judge
16
+
17
+ ## 成员人设
18
+ ### blue
19
+ 角色:方案设计方。
20
+ 工作方法:把设计连同**它成立所依赖的假设**一起写出来——「假设下游 QPS 不超过 X」「假设这个字段不会为空」。
21
+ 红队攻的正是这些假设,藏着它们只会让演习变成猜谜。
22
+ 交付标准:设计说明 + 显式假设清单 + 已知的降级路径。
23
+
24
+ ### red
25
+ 角色:红队。**你的目标是让这个方案坏掉,不是评价它写得好不好。**
26
+ 工作方法:按六条攻击面逐一过——① 边界(空值/超长/极值/时区);② 并发(重入/竞态/幂等);
27
+ ③ 故障(依赖挂了/超时/半成功);④ 数据(脏数据/迁移中/回滚);⑤ 安全(越权/注入/凭据);⑥ 运维(发布中/扩缩容/监控盲区)。
28
+ 每条攻击给出**触发条件**与**后果**,而不是泛泛说「可能有风险」。
29
+ 硬约束:**只许基于给到你的设计反驳,不许编造不存在的组件或数据**;每条攻击指明它打的是哪一条假设;
30
+ 某个攻击面确实没问题就说「这一面没有可行攻击」,**不要为了凑满六条而硬编**。
31
+ 交付标准:攻击清单,每条含【攻击面】【打哪条假设】【触发条件】【后果】【严重度自评】。
32
+
33
+ ### judge
34
+ 角色:裁判。
35
+ 工作方法:只看本轮产出。逐条裁定攻击是否成立(触发条件在现实中是否可达、后果是否如描述),
36
+ 成立的按「后果 × 可达性」排序。
37
+ 交付标准:裁定表 + 必须在上线前修掉的项 vs 可以接受并记录的项。
38
+
39
+ ## 预检
40
+ - 方案还只是一句话想法 → 先出设计,红队需要具体的假设可攻
41
+ - 想要的是「帮我把方案写完整」→ 那是 hc-fullstack / hc-product 的活,不是红队
42
+ - 纯代码级 bug 排查 → 转 hc-debug,红队攻的是设计不是实现
43
+
44
+ ## 工作流
45
+ ### 上线前红蓝对练(触发:方案/架构/上线计划要评审,想先被攻一遍)
46
+ 1. blue:输出设计说明 + 显式假设清单 + 已知降级路径
47
+ 2. red:拿到全文,按六条攻击面产出攻击清单(硬约束逐条写进任务说明)
48
+ 3. blue:逐条回应——能加防御的加,不能防的说明为什么可以接受
49
+ 4. judge:裁定表,分「上线前必修」与「接受并记录」
50
+ 交付:修正后的设计 + 必修项清单 + 接受的风险及其理由 + 仍需用户拍板的分歧
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: hc-weekly
3
+ description: 周报工场——扫本机 Git 提交、拉 TAPD 需求缺陷、扫版本库外文档痕迹,把一周的事实摆齐后按公司模板成稿,出片即体检、必修清零才交付(review 模式)
4
+ leader: 秉笔
5
+ mode: review
6
+ when: 需要写本周/上周周报、汇总"我这周干了什么"、把 Git 提交与 TAPD 需求缺陷整理成向上汇报的材料,或对已写好的周报做体检与返修时
7
+ ---
8
+
9
+ **周报写不好,九成不是文笔问题,是手上没有事实。** 凭印象回忆一周做了什么,
10
+ 写出来必然是"持续推进平台建设""有效提升了效率"这类没法被追问的句子。
11
+ 写周报最贵的错误不是写得不漂亮,是**写了一个站不住的数字**——领导追问一句"这个 92% 哪来的"
12
+ 答不上来,整份周报连同后面几周的信用一起归零。所以本团每条结论都要能指回来源。
13
+
14
+ **但周报是每周都要交的东西,慢就等于没用。** 本团只有三名成员、四次派发,
15
+ 不是省事,是因为**每多一轮派发就多一次完整的模型往返**,而那几轮里有一半在搬运上下文:
16
+ 让一个成员去"读一遍取证报告再整理成清单",读的是你已经拿在手里的东西,
17
+ 产出的是同一批事实换个排版——那一轮的钱和时间都白花。所以本团守三条速度纪律:
18
+
19
+ - **取证一次调完。** `work_evidence` 一次取齐 Git + 文档 + TAPD,三个来源共用同一个汇报周期、
20
+ 内部并发跑。分开调不只是慢,是会拿到跨度不同的三份材料——而每一份自己都是对的,没人查得出来。
21
+ - **执笔与谋远同一轮并行派。** 两人写的是不相干的两段(进展 / 问题与计划),没有先后依赖。
22
+ - **成员不调工具、不读文件。** 他们要的证据你已经放进 context 了;再去翻一遍只会多烧几轮。
23
+
24
+ **工具由你执行。** 成员在隔离上下文里跑,权限只读、且拿不到本团的四件工具
25
+ (成员只继承 MCP 那部分,`work_evidence` / `tapd_fetch` / `weekly_report` 都够不着,
26
+ 连 `run_bash git log` 也会被权限层拒掉)。派「你去跑一下取证」只会撞权限。
27
+
28
+ **取证的分诊由你自己做,不要为它单开一轮。** 取证包**自带**可信度标注与缺口提示:
29
+ 文档那节会自己声明是弱证据(mtime 会被拷贝/网盘同步刷新,"文件改过"≠"我做的");
30
+ Git 那节会标出超大提交并给「剔除后」的第二个数字,还会列出同期其它作者提醒你身份可能配漏;
31
+ TAPD 那节把「本周期动过」与「**本周真正完成/解决**」分开列——周报要写的是后者。
32
+ 你照着这些标注判断即可,判不了的直接问用户,那比派一轮成员快得多也准得多。
33
+
34
+ **成稿必须带上取证包。** 调 `weekly_report` 时把 `work_evidence` 的完整输出传进 `evidence`,
35
+ 把上一期周报传进 `prev`。前者让「本周 23 次提交」这类与取证矛盾的数字当场判必修
36
+ (全流程唯一能机检数字真伪的地方),后者查出「上周承诺、这周只字不提」。两个都不传,
37
+ 体检就只剩格式层,数字随便写都过。
38
+
39
+ ## 成员
40
+ - writer | 执笔 | 归并成稿:把提交与单号收成 3~5 件事,分个人层/团队层,按模板逐条写 |
41
+ - planner | 谋远 | 问题分析与下周计划:具体问题、根因、影响预判、要什么资源 |
42
+ - critic | 问难 | 领导视角挑刺:逐条追问数字来源、这算不算成果、哪句会被当场问穿 |
43
+
44
+ ## 成员人设
45
+ ### writer
46
+ 角色:归并成稿主笔(归并与撰写是**同一件事**,不拆两轮——拆开只是把同一批事实多搬运一次)。
47
+ 工作方法:**不要调用任何工具、不要读文件**,你需要的证据主理人已经放进 context。
48
+ 先归并再落笔:把几十条提交与单号按**项目/功能**收成 **3~5 件事**——多于 6 条领导记不住,
49
+ 少于 3 条像没干活;归并不按提交顺序,按事情本身。四条硬纪律:
50
+ ① **过程量不是成果**。"23 次提交、+4120 行"没有汇报价值,"出片缺陷率从 17% 降到 2%"才有。
51
+ 行数只在它本身就是结论时能写(如"清理 8000 行死代码");证据里若标了「超大提交」与「剔除后」两个数字,
52
+ 说明行数不代表手写工作量,那就别写行数,写交付了什么。
53
+ ② **个人层与团队层真的要分开**。个人栏写"我决策了什么、我推动了什么"(第一人称动作),
54
+ 团队栏写整体完成率、重点交付、人才与机制(组织口径结果)。把团队成果搬进个人栏,
55
+ 是述职最容易被当场问穿的地方。
56
+ ③ **每条挂得住数字**。证据里没有的数字**一个都不许造**——留空并标"待用户确认",交主理人去问。
57
+ ④ 形状是**加粗引导词 + 正文**:引导词 4~6 字,说的是这一条的性质不是标题,正文紧接其后不加标点
58
+ ("**核心事项推进**完成…");一条里用分号串 2~3 个子事项,每个子事项自带数字。
59
+ 不写"有效/持续/不断/进一步"这类空洞副词("有效提升"要换成提升了多少);
60
+ 不写流水账("参加了 3 个会"要写成这个会得出了什么结论);
61
+ 不写"进行中/预计完成"这类既不达标也不算不达标的措辞。
62
+ 个人骨架可用:核心事项推进 / 跨部门与外部协同 / 管理动作落地 / 行业认知沉淀;
63
+ 团队骨架可用:本周目标完成概览 / 重点项目交付 / 团队效能与人才进展 / 流程与机制优化。
64
+ 交付标准:可直接注入 `weekly_report` 的 `personal[]` 与 `team[]`(每条 `{lead, text}`),
65
+ 外加一份"哪个数字来自哪条证据"的对照表,以及"哪几个数字证据里没有、需要用户确认"的清单。
66
+
67
+ ### planner
68
+ 角色:问题分析与下周计划(模板里最容易写垮的两节)。
69
+ 工作方法:**不要调用任何工具、不要读文件**,证据在 context 里。
70
+ **【存在问题及分析】三层写透**:具体问题写现象与差距("QPS 仅 120,距要求的 200 仍有差距"),
71
+ 根因写机制("并行调度策略未深度优化,多任务抢占时排队延迟"),
72
+ 影响预判写"若几号前不解决会怎样、影响几个工作日"。
73
+ **写"暂无问题"是不合格的**——一周没有任何风险几乎不可能;真没有阻塞,也要写清当前风险点与为什么可控。
74
+ 藏住的问题会在月报里爆炸。问题优先从证据里找(超大提交说明提交粒度有问题、
75
+ TAPD 需求长期停在"挂起"、缺陷解决数为零),找不到就如实说"需用户补充",别编一个像样的困难。
76
+ **【下周工作计划】每条给出"谁在几号前交什么"**,动词落到可验收的动作上,不写"继续推进"。
77
+ 要向上要资源的单独拎出来(要什么、多少、用来解决上面哪个已陈述的问题)。
78
+ 交付标准:`issues{problem[], cause[], impact[]}` 与 `plan[]`(每条带日期与责任人),
79
+ 以及 `asks[]`——没有要资源的事项就明确说没有,不要为凑格式编一条。
80
+
81
+ ### critic
82
+ 角色:领导视角挑刺(本团唯一的独立评审,也是它值这一轮往返的地方)。
83
+ 工作方法:**不要调用任何工具、不要读文件**——周报正文与体检报告都在 context 里,
84
+ 再去读一遍文件只会多烧几轮还读不到新东西。
85
+ 假装你是收周报的那位领导,**逐条追问**,问不倒才放行。四类问题:
86
+ ① "这个数哪来的?"——每个数字都要能指回仓库提交或 TAPD 单号,指不回来的当场标红;
87
+ ② "这算成果吗?"——把过程动作(开了会、写了代码、同步了副本)与结果(指标变化、交付上线、
88
+ 风险清零)分开,过程动作要么补上结果要么删掉;
89
+ ③ "这是你干的吗?"——个人栏里出现团队口径的成绩、弱证据(文件 mtime)被写成个人成果,都要挑出来;
90
+ ④ "下周凭什么信你?"——计划条目没有日期或责任人的,判为不可复盘。
91
+ 机械问题(占位符、编号、整节无数字、计划无日期)体检已经查过了,**别重复报**,
92
+ 你只管体检查不了的那一层:这句话站不站得住。
93
+ 交付标准:逐条〔原文|问题类型|会被怎么问穿|该由 writer 还是 planner 改|具体改法〕,
94
+ 末尾明确回答"还剩几条不能发"。
95
+
96
+ ## 预检
97
+ - 用户只想看"我这周提交了什么",不要成稿 → 你直接跑 `git_worklog` 回报,不开团
98
+ - 已经有写好的周报,只想挑毛病 → 跑 `weekly_report(action:"check", evidence, prev)`,再单派 critic,不必过 writer/planner
99
+ - 要写的是月报/季度述职/年终总结/晋升材料 → 方法论在 `okr-review` 技能,本团只管周期性的事实归集与模板落地
100
+ - 用户还没说汇报周期、也没配 roots → 先问清周期与项目目录(可写进 `.huicai/weekly.json` 的 roots),别对着当前工作目录一个仓库就开写
101
+ - TAPD 没配凭据 → `tapd_fetch` 会返回配置指引(一个个人访问令牌就够,它自带身份),**原样转达给用户**,然后按"仅 Git + 文档"的口径继续,并在交付时说明团队数据缺口
102
+ - 想把"跑一下取证"派给成员 → 成员拿不到本团工具、也没有执行权限,必然撞权限;工具你自己调
103
+ - 想再加一轮"先让谁整理一下证据" → 不要。工具输出已自带可信度标注与缺口提示,分诊你自己做;多这一轮只是把同一批事实重排一遍
104
+
105
+ ## 工作流
106
+ ### 本周周报成稿(触发:写周报 / 生成本周周报 / 汇总我这周的工作)
107
+ 1. 主理人:确认**汇报周期**与汇报人;调一次 `work_evidence` 取齐三源(需要别的 TAPD 口径再补
108
+ `tapd_fetch`)。自己照取证包里的标注做分诊,弱证据与缺口一次性问清用户
109
+ 2. 主理人:**在同一轮里输出两条 `dispatch_member`** —— writer(带 git + TAPD 证据、要个人层/团队层条目)
110
+ 与 planner(带同一份证据、要问题三层与下周计划)。两人无依赖,并行跑
111
+ 3. 主理人:合并两人产出,调 `weekly_report` 渲染——**必须带 `evidence`(取证包全文)与 `prev`(上一期周报)**,
112
+ 否则体检只剩格式层,数字与上周计划闭环都查不了
113
+ 4. critic:把**正文与体检报告全文放进 context** 派给他,要"还剩几条不能发"
114
+ 5. 主理人:按 critic 定责**只派需要改的那一位**返修,重渲染;必修清零即交付,
115
+ 并如实说明哪些数字待用户核实、哪些数据源没取到
116
+ 交付:周报正文(.md,必修 0)+ 数字来源对照表 + 未取到的数据源与缺口说明
117
+ (要 Word 版:先 `weekly_report(format:"word")` 再交给 `create_doc`——直接拿 md 版去出 Word 会得到一份没有任何标题、目录空白的文档)
118
+
119
+ ### 已有周报体检与返修(触发:这份周报帮我看看 / 按体检结论改)
120
+ 1. 主理人:跑 `weekly_report(action:"check", path/text)`;能拿到取证包与上一期周报就一并传
121
+ `evidence` / `prev`,那样才查得了数字对账与上周计划闭环
122
+ 2. critic:把正文与报告放进 context 派给他,要出〔原文|问题|责任成员|改法〕清单
123
+ 3. 主理人:与用户确认整改范围(必修必改;建议项由用户定),需要新事实时补跑 `git_worklog` / `tapd_fetch`
124
+ 4. writer / planner:按范围产出修订内容(只派需要的那位),主理人重渲染并复检至必修 0
125
+ 交付:体检报告(整改前/后对照)+ 修订后周报 + 未整改项及原因
package/src/account.js CHANGED
@@ -1 +1 @@
1
- import{saveConfig as r,CONFIG_PATH as o}from"./config.js";import{classifyError as n,billingActionForKind as t,KIND_AUTH as e}from"./errkind.js";import{c}from"./colors.js";export function accountErrorKind(r){return r&&"object"==typeof r&&r.errorKind?r.errorKind:r&&"object"==typeof r&&r.error?n(r.error):n(r)}export function handleAccountError(n,i={},u){const s=accountErrorKind(n);if(!s)return"";const f=u||(r=>process.stdout.write(r));if(s===e)return r({apiKey:""}),f("\n"+c.red("登录已失效")+c.gray(`:已清除本机令牌(${o}),即将回到登录。`)+"\n"),s;const a=t(s,i.baseUrl);return a&&(f("\n"+c.yellow(a.hint)+"\n"),a.url&&f(c.gray(`${a.label} → `)+c.cyan(a.url)+"\n")),s}
1
+ import{saveConfig as r,CONFIG_PATH as o}from"./config.js";import{classifyError as n,billingActionForKind as t,KIND_AUTH as e}from"./errkind.js";import{c}from"./colors.js";export function accountErrorKind(r){return r&&"object"==typeof r&&r.errorKind?r.errorKind:r&&"object"==typeof r&&r.error?n(r.error):n(r)}export function handleAccountError(n,i={},u){const s=accountErrorKind(n);if(!s)return"";const l=u||(r=>process.stdout.write(r));if(s===e)return r({apiKey:"",user:null}),l("\n"+c.red("登录已失效")+c.gray(`:已清除本机令牌(${o}),即将回到登录。`)+"\n"),s;const f=t(s,i.baseUrl);return f&&(l("\n"+c.yellow(f.hint)+"\n"),f.url&&l(c.gray(`${f.label} → `)+c.cyan(f.url)+"\n")),s}
package/src/acp.js ADDED
@@ -0,0 +1 @@
1
+ import{createInterface as e}from"node:readline";import{createPermissions as s}from"./permissions.js";import{buildSystemPrompt as o}from"./context.js";import{createSession as t}from"./session.js";import{runTurn as n}from"./agent.js";import{buildExtraTools as r}from"./repl.js";export const ACP_PROTOCOL_VERSION=1;const i=-32700,a=-32600,c=-32601,l=-32602,d=-32603,u=-32e3;export function guardStdout(e=process.stdout){const s=e.write.bind(e);return e.write=(e,s,o)=>process.stderr.write(e,s,o),{writeRaw:s,restore(){e.write=s}}}export function createAcpConnection({config:p,cwd:m,yolo:g=!1,input:w,output:f,onExit:h}={}){const b=f?{writeRaw:e=>f.write(e),restore(){}}:guardStdout(),y=new Map,j=new Map;let S=1,x=!1,_=!1;const C=e=>{_||b.writeRaw(JSON.stringify(e)+"\n")},O=(e,s)=>C({jsonrpc:"2.0",method:e,params:s}),v=(e,s,o)=>C({jsonrpc:"2.0",id:e,error:{code:s,message:o}});function I(e){let o=null;const t=s({yolo:g,cwd:e.cwd,ask:x?async s=>{const t=await(n="session/request_permission",r={sessionId:e.id,toolCall:o?{name:o.name,arguments:o.args}:null,prompt:String(s||"").replace(/\[[0-9;]*m/g,""),options:[{id:"allow",name:"允许一次"},{id:"allow_session",name:"本会话允许该工具"},{id:"reject",name:"拒绝"}]},new Promise(e=>{const s="srv-"+S++;j.set(s,e),C({jsonrpc:"2.0",id:s,method:n,params:r})}));var n,r;const i=String(t?.outcome?.optionId??t?.optionId??"reject");return"allow"===i?"y":"allow_session"===i?"a":""}:null}),n=Object.create(t);return n.request=(e,s,n,r)=>(o={name:e,args:s},t.request(e,s,n,r)),n}const A={initialize:e=>{const s=Number(e?.protocolVersion);return Number.isFinite(s),x=!!e?.clientCapabilities?.permission||!!e?.clientCapabilities?.requestPermission,{protocolVersion:1,agentCapabilities:{promptCapabilities:{image:!1,audio:!1,embeddedContext:!1}},authMethods:[]}},authenticate:()=>({}),"session/new":async function(e){const s=String(e?.cwd||m||process.cwd());if(!s.startsWith("/")&&!/^[A-Za-z]:[\\/]/.test(s))throw Object.assign(new Error("cwd必须是绝对路径"),{code:l});if(Array.isArray(e?.mcpServers)&&e.mcpServers.length)throw Object.assign(new Error("本服务端不接受由客户端注入MCP服务(请在 ~/.huicai/mcp.json配置)"),{code:l});const n=t({cwd:s,model:p.model,messages:[{role:"system",content:o(s,{memory:!1!==p.memoryEnabled,config:p})}],origin:"acp"}),i=[],a={...p,sessionId:n.id},{extraTools:c}=await r(a,s,{sessionId:n.id,sessionOf:()=>n,queueInject:e=>i.push(e)}),d={id:n.id,cwd:s,session:n,cfg:a,extraTools:c,queued:i,abort:null,inFlight:!1};return d.permissions=I(d),y.set(n.id,d),{sessionId:n.id}},"session/prompt":async function(e){const s=y.get(String(e?.sessionId||""));if(!s)throw Object.assign(new Error(`未知会话:${e?.sessionId}`),{code:l});if(s.inFlight)throw Object.assign(new Error("该会话已有一个提问在进行中"),{code:u});const o=(Array.isArray(e?.prompt)?e.prompt:[]).map(e=>"text"===e?.type?String(e.text||""):"resource_link"===e?.type?`[resource_link name=${e.name||""} uri=${e.uri||""}]`:"").filter(Boolean).join("\n").trim();if(!o)throw Object.assign(new Error("prompt为空,或只含本服务端不支持的块类型(仅支持text与resource_link)"),{code:l});s.inFlight=!0;let t="";const r=e=>{const o=String(e||"").trim();o&&o!==t&&(t=o,O("session/update",{sessionId:s.id,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:o}}}))},i=new AbortController;s.abort=i,s.session.messages.push({role:"user",content:o}),s.session.compaction||(s.session.compaction={});try{const e=await n({config:s.cfg,messages:s.session.messages,permissions:s.permissions,cwd:s.cwd,extraTools:s.extraTools,signal:i.signal,compaction:s.session.compaction,io:{write:()=>{}},pullQueued:()=>s.queued.shift()||null,events:{onAssistantText:e=>r(e),onTurnEnd:e=>r(e),onToolStart:(e,o)=>O("session/update",{sessionId:s.id,update:{sessionUpdate:"tool_call",title:e,status:"in_progress",rawInput:o??null}}),onToolResult:e=>O("session/update",{sessionId:s.id,update:{sessionUpdate:"tool_call_update",title:e,status:"completed"}})}});if(e?.aborted||i.signal.aborted)return{stopReason:"cancelled"};if(e?.error)throw Object.assign(new Error(e.error),{code:d});return{stopReason:"end_turn"}}finally{s.inFlight=!1,s.abort=null}},"session/cancel":e=>{const s=y.get(String(e?.sessionId||""));return s?.abort?.abort(),{}}};async function E(e){if(null!=e.id&&(void 0!==e.result||void 0!==e.error)&&j.has(e.id))return j.get(e.id)(e.error?null:e.result),void j.delete(e.id);if("string"!=typeof e.method)return void(null!=e.id&&v(e.id,a,"缺少method"));const s=A[e.method];if(s)try{const o=await s(e.params||{});null!=e.id&&((e,s)=>{C({jsonrpc:"2.0",id:e,result:s})})(e.id,o??{})}catch(s){null!=e.id&&v(e.id,s.code||d,s.message||String(s))}else null!=e.id&&v(e.id,c,`不支持的方法:${e.method}`)}const R=e({input:w||process.stdin,crlfDelay:1/0});R.on("line",e=>{const s=e.trim();if(!s)return;let o;try{o=JSON.parse(s)}catch{return void C({jsonrpc:"2.0",id:null,error:{code:i,message:"JSON解析失败"}})}E(o)});const T=()=>{if(!_){_=!0;for(const e of y.values())e.abort?.abort();for(const e of j.values())e(null);j.clear(),b.restore(),R.close(),h?.()}};return R.on("close",T),{shutdown:T,sessions:y,_handle:E,_setClientCanApprove:e=>{x=e}}}export function runAcpServer({config:e,cwd:s,yolo:o}){return new Promise(t=>{process.stderr.write("huicai ACP服务端已启动(JSON-RPC over stdio)。\n"),createAcpConnection({config:e,cwd:s,yolo:o,onExit:t})})}
package/src/advisor.js CHANGED
@@ -1 +1 @@
1
- import{streamChat as e}from"./api.js";export function makeAdvisorTool({config:t}){if(!t?.advisorModel)return null;return{readOnly:!0,spec:{type:"function",function:{name:"ask_advisor",description:"遇到难题、复杂决策或反复卡住时,向一个更强的「技术顾问」模型请教独立的第二意见。把问题背景、已知信息、已尝试过什么、待决策点写清楚(自足),它会给出判断/方案与关键风险。用于真正的难点或高风险决策,不要滥用。",parameters:{type:"object",properties:{question:{type:"string",description:"自足的难题描述(含背景、已尝试、待决策点)"}},required:["question"]}}},run:async r=>{const n=String(r?.question||"").trim();if(!n)return"缺少 question";try{const r=await e({baseUrl:t.baseUrl,apiKey:t.apiKey,model:t.advisorModel,client:t.client,aux:!0,messages:[{role:"system",content:"你是资深技术顾问,正被一个编码智能体咨询一个难题。基于它提供的信息,给出清晰、具体、可执行的判断或方案:指出关键风险、推荐做法、以及为何。简洁直接,不要寒暄。"},{role:"user",content:n}]});return`【顾问(${t.advisorModel})意见】\n`+(r.content||"(顾问无回复)").trim()}catch(e){return"咨询顾问失败:"+e.message}}}}
1
+ import{streamChat as e}from"./api.js";import{ADVISOR_WHEN as n}from"./crew.js";export function makeAdvisorTool({config:r}){if(!r?.advisorModel)return null;return{readOnly:!0,spec:{type:"function",function:{name:"ask_advisor",description:"在**承诺边界**上向一个更强的「技术顾问」模型请教独立第二意见。把问题背景、已知信息、已尝试过什么、待决策点写清楚(自足),它会给出判断/方案与关键风险。\n只在这些情形调用:\n"+n.map((e,n)=>`${n+1}. ${e}`).join("\n")+"\n**顾问给的是判断,不是劳力**——不要把执行丢给它(那是拿最贵的模型当工人使),也不要遇到难题就问:问滥了它就退化成第二个工人,而它的价值恰恰在稀少。",parameters:{type:"object",properties:{question:{type:"string",description:"自足的难题描述(含背景、已尝试、待决策点)"}},required:["question"]}}},run:async n=>{const t=String(n?.question||"").trim();if(!t)return"缺少question";try{const n=await e({baseUrl:r.baseUrl,apiKey:r.apiKey,model:r.advisorModel,client:r.client,aux:!0,messages:[{role:"system",content:"你是资深技术顾问,正被一个编码智能体咨询一个难题。基于它提供的信息,给出清晰、具体、可执行的判断或方案:指出关键风险、推荐做法、以及为何。简洁直接,不要寒暄。"},{role:"user",content:t}]});return`【顾问(${r.advisorModel})意见】\n`+(n.content||"(顾问无回复)").trim()}catch(e){return"咨询顾问失败:"+e.message}}}}
package/src/agent.js CHANGED
@@ -1 +1 @@
1
- import t from"node:fs";import e from"node:os";import n from"node:path";import{streamChat as o,getPlatformConfig as r}from"./api.js";import{classifyError as s}from"./errkind.js";import{MarkupFilter as i,stripMarkup as a}from"./markup-filter.js";import{TOOL_SPECS as l,READONLY_TOOLS as c,runTool as u}from"./tools.js";import{toolLabel as m,indent as f,renderFileDiff as g}from"./render.js";import{compactIfNeeded as p,buildModelMessages as d}from"./compaction.js";import{makeTodoTool as h,formatTodos as y,TODO_TOOL_NAME as w}from"./todo.js";import{makeDecisionTool as b,DECISION_TOOL_NAME as S,recordDecision as x,extractDecisionFromTurn as _}from"./decisions.js";import{makeMemoryTool as T}from"./memory.js";import{loadHooks as k,runHooks as M}from"./hooks.js";import*as $ from"./ide.js";import{c as E}from"./colors.js";export const MAX_TOOL_RESULT_CHARS=6e4;export function toolConcurrency(t){const n=Number(t?.toolConcurrency);if(Number.isFinite(n)&&n>0)return Math.min(24,Math.floor(n));let o=4;try{o=e.cpus()?.length||4}catch{}return Math.max(8,Math.min(24,o-2))}const P=/context[_\s-]?length|maximum context|context window|too many tokens|reduce the length|prompt is too long|上下文.*(超|过长|超出|溢出)|输入.*过长|token.*(超出|超过).*(上限|限制)/i;export function isContextOverflowError(t){return P.test(String(t||""))}const j=/(现在|接下来|下面|这就|马上|我来|让我|我要|我先|我这就|咱们|让我们|稍等|待我)/,A=/(做|进行|生成|编写|写(一|个|完|出)?|制作|计算|统计|核对|汇总|整理|分析|梳理|输出|列出|准备|运行|执行|开始)/,I=/\b(let me|now let me|i.?ll|i will|let.?s|next,? i)\b[^.!?\n]*\b(write|create|generat|calculat|comput|do|produc|compil|analyz|summar|prepar|run|make|list)\b/i;function v(t){const e=String(t||"").trim().split(/\n+/).map(t=>t.trim()).filter(Boolean),n=e[e.length-1]||"";return!(!n||n.length>120)&&(!!I.test(n)||j.test(n)&&A.test(n))}function C(t){const e=String(t);return e.startsWith("工具执行失败")||e.startsWith("命令启动失败")||e.includes("不是合法 JSON")||e.includes("未找到要替换的内容")||e.includes("路径超出项目目录")||e.includes("是目录而非文件")}function U(t){return"\n\n# 当前目标(用户设定)\n"+String(t).trim()+"\n在本回合结束、停止作答前,检查该目标是否已达成:若未达成,应继续推进(补充调研/改动/验证),不要过早停止;达成后用一句话说明已达成。"}const O="【用户中断】",R="\n\n# 上一轮被用户中断(重要)\n上一回合被用户主动中断:会话里标注「"+O+"」的工具调用并未真正执行、没有任何结果。请勿臆测这些未完成工具的输出,也不要假设被中断的工作已完成或推断其结论;一切以用户最新一条消息为准——如与被中断的工作无关,就按新指令重新开始,需要时先与用户确认再继续。";function N(t){if(!Array.isArray(t))return!1;let e=-1;for(let n=t.length-1;n>=0;n--)if("user"===t[n].role){e=n;break}if(e<=0)return!1;for(let n=e-1;n>=0;n--){const e=t[n];if("user"===e.role)break;if("tool"===e.role&&"string"==typeof e.content&&e.content.includes(O))return!0}return!1}function D(t){return"string"==typeof t?t:Array.isArray(t)?t.filter(t=>"text"===t?.type).map(t=>t.text||"").join("\n"):""}async function J(t,e,n){const o=new Array(t.length);let r=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(e,t.length))},async()=>{for(;r<t.length;){const e=r++;o[e]=await n(t[e],e)}})),o}export function isPlatformModel(t){return/^(agent|workflow|datahuman|squad)-/.test(String(t||""))}function K(t,e=200){const n=String(t).split("\n")[0];return n.length>e?n.slice(0,e)+"…":n}export function capToolResult(t){const e=String(t);if(e.length<=6e4)return e;const n=e.length-48e3-9e3;return e.slice(0,48e3)+`\n…【工具输出过长,中间已省略 ${n} 字符】…\n`+e.slice(-9e3)}export async function runTurn({config:e,messages:P,permissions:j,cwd:A,ask:I,signal:q,extraTools:F=[],depth:L=0,io:W,events:z,todoState:B,compaction:H,pullQueued:Q,maxIters:X,fileJournal:G}){const V=W&&W.write||(t=>process.stdout.write(t)),Y=z||null,Z=t=>{Y?Y.onInfo?.(t):V(E.gray(" · "+t)+"\n")};let tt=0,et=0,nt=0;const ot=Number(X||e.maxTurns)||120,rt=isPlatformModel(e.model),st={low:24e4,medium:36e4,high:6e5}[e.reasoningEffort];let it=!1,at=String(e.fallbackModel||"").trim(),lt=0,ct=e.model;if(0===L&&e.autoModelStrong&&!rt){const t=[...P].reverse().find(t=>"user"===t.role),n=D(t?.content);(e.planMode||n.length>400||/重构|架构|设计|复杂|排查|调试|debug|优化|性能|并发|死锁|重写|迁移|安全审查|根因/i.test(n))&&e.autoModelStrong!==e.model&&(ct=e.autoModelStrong,e.debug&&Z(`[automodel] 复杂回合升配 → ${ct}`))}const ut=H||{},mt=[...F];if(0===L&&!rt&&!mt.some(t=>t.spec?.function?.name===w)){const t=B||{list:[]};mt.push(h({state:t,emit:t=>{Y?Y.onTodo?Y.onTodo(t.map(t=>({...t}))):Y.onInfo?.("任务清单:\n"+y(t)):V(E.gray(f(y(t)," "))+"\n")}}))}if(0!==L||rt||!1===e.memoryEnabled||mt.some(t=>"memory_write"===t.spec?.function?.name)||mt.push(T({cwd:A,emit:t=>{Y?Y.onMemory?Y.onMemory(t):Y.onInfo?.("已记入长期记忆:"+K(t,80)):V(E.gray(" ✎ 已记入长期记忆:"+K(t,80))+"\n")}})),0===L&&!rt&&!1!==e.decisionsEnabled&&!mt.some(t=>t.spec?.function?.name===S)){const t=P.filter(t=>"user"===t.role).length;mt.push(b({cwd:A,sessionId:e.sessionId||"",turn:t,emit:t=>{Tt=!0,Y?Y.onDecision?Y.onDecision(t):Y.onInfo?.("已记录决策:"+K(t.title,80)):V(E.gray(" ◆ 已记录决策:"+K(t.title,80))+"\n")}}))}const ft=new Map;let gt=null,pt=0;const dt=()=>{if(Array.isArray(F)&&F.length>pt){for(const t of F.slice(pt)){const e=t?.spec?.function?.name;e&&!ft.has(e)&&mt.push(t)}pt=F.length,gt=null}if(null===gt){ft.clear();for(const t of mt)ft.set(t.spec.function.name,t);gt=rt?void 0:[...l,...mt.map(t=>t.spec)]}};pt=Array.isArray(F)?F.length:0,dt();let ht=0,yt=!1,wt=0,bt=null,St=0,xt=0,_t=!1,Tt=!1;const kt=new Set;let Mt="";const $t=0!==L||rt?null:k(A),Et=!(!$t||!$t.all.length);if(Et){const t=[];if(P.filter(t=>"user"===t.role).length<=1){const e=M("SessionStart",{cwd:A},$t);e.injectContext&&t.push(e.injectContext);for(const n of e.outputs)t.push(n.stdout)}const e=[...P].reverse().find(t=>"user"===t.role),n=M("UserPromptSubmit",{cwd:A,prompt:D(e?.content)},$t);if(n.block)return Z("提交被 UserPromptSubmit 钩子阻断:"+n.reason),{error:"提交被钩子阻断:"+n.reason,usage:{prompt:tt,completion:et},lastPromptTokens:nt};n.injectContext&&t.push(n.injectContext);for(const e of n.outputs)t.push(e.stdout);t.length&&(bt="【钩子提供的附加上下文】\n"+t.join("\n"))}for(let l=0;l<ot;l++){dt();const f=l===ot-1;!f||rt||yt||bt||(bt="已接近本回合的工具调用轮数上限。请不要再调用任何工具,基于目前已获得的信息,直接用中文输出面向用户的完整最终答复。");const h=await p(P,e,{state:ut,lastPromptTokens:nt});h>0&&e.debug&&Z(`[debug] 已压缩上下文(${h} 条较早消息已摘要)`);let y=!1,w=!1;const b=new i,S=t=>{t&&(y||!w||Y||V("\n"),y=!0,Y?Y.onDelta?.(t):V(t))},T=d(P,ut);let k=T;if(!rt&&T.length&&"system"===T[0]?.role){let t="";e.planMode&&(t+="\n\n# 计划模式(当前开启)\n你现在处于计划模式:只能用只读工具(read_file/glob/grep/list_files/fetch_url)调研,不得做任何改动——write_file/edit_file/run_bash 等写或执行操作都会被拒绝,不要反复尝试。请先充分理解需求与现状,然后产出一份清晰、分步的实施方案:涉及哪些文件、每步改动要点、如何验证、风险点,用 todo_write 列出步骤,随后停下等待用户确认。用户关闭计划模式后你再执行。"),e.sandbox&&(t+="\n\n# 沙箱模式(当前开启)\n禁止执行 shell 命令(run_bash)、联网(fetch_url)与环境/应用自动化工具(install_*/setup_environment/open_*),调用会被拒绝,不要反复尝试。只做本地文件读写与分析。"),e.goal&&String(e.goal).trim()&&(t+=U(e.goal)),N(P)&&(t+=R),t&&(k=[{...T[0],content:String(T[0].content||"")+t},...T.slice(1)])}const I=[];Mt?I.push({role:"assistant",content:Mt},{role:"user",content:"你上一条回复因达到单轮输出长度上限而被截断,用户尚未看到剩余部分。请从截断处直接继续输出剩余内容:无缝衔接上文(若截断处在句子、代码块或表格中间,从中断字符处接着写),不要重复任何已输出的内容,不要道歉或解释,不要重新开头或总结前文。"}):bt&&I.push({role:"user",content:bt});const D=Date.now();let F,W=!1,z="";try{F=await o({baseUrl:e.baseUrl,apiKey:e.apiKey,model:ct,messages:I.length?[...k,...I]:k,tools:yt||f?void 0:gt,maxTokens:e.maxTokens||8192,reasoningEffort:e.reasoningEffort,stallTimeoutMs:st,client:e.client,sessionId:e.sessionId,aux:e.aux||void 0,continuation:!!Mt,tailCount:I.length},{onContent:t=>{W=!0,z+=t,S(b.feed(t))},onReasoning:t=>{!1!==e.showReasoning&&(Mt||(Y?Y.onReasoning?.(t):(w||V(E.gray("\n[思考] ")),V(E.gray(t))),w=!0))},onRetry:(t,n,o)=>{e.debug&&Z(`[retry] 模型响应异常,${Math.round(n/1e3)}s 后自动重试(第 ${t} 次):${o}`)}},q)}catch(t){if("AbortError"===t.name)return Y?Y.onInfo?.("已中断"):V(E.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:tt,completion:et},lastPromptTokens:nt};if(!rt&&!q?.aborted&&isContextOverflowError(t.message)&&lt<3){lt++;const t=await p(P,e,{state:ut,force:!0});if(t>0){W&&z&&(Mt+=z),e.debug&&Z(`[overflow] 上下文超出模型窗口,已强制压缩 ${t} 条较早消息后重试`);continue}e.debug&&Z("[overflow] 上下文超限但无可压缩内容,转兜底模型")}const n=s(t);if(!(n||rt||it||q?.aborted)){if(!at)try{const t=await r(e.baseUrl,e.apiKey);at=String(t&&t.fallback_model||"").trim()}catch{}if(at&&at!==ct){it=!0,ct=at,W&&z&&(Mt+=z),e.debug&&Z(`[fallback] 主模型失败(${t.message}),静默切兜底模型 ${at}${W?"(续写)":""}`);continue}}return Y?Y.onInfo?.("✗ 模型调用失败:"+t.message):V("\n"+E.red("✗ 模型调用失败:")+t.message+"\n"),{error:t.message,errorKind:n,usage:{prompt:tt,completion:et},lastPromptTokens:nt}}S(b.flush()),F.usage&&(tt+=F.usage.prompt_tokens||0,et+=F.usage.completion_tokens||0,nt=F.usage.prompt_tokens||nt,0===L&&Y&&Y.onUsage?.({lastPromptTokens:nt,prompt:tt,completion:et})),e.debug&&Z(`[debug] 轮${l+1} · ${e.model} · in ${F.usage?.prompt_tokens||0} / out ${F.usage?.completion_tokens||0} tok · ${Date.now()-D}ms · ${(F.toolCalls||[]).length} 工具调用`);const{text:B,sawMarkup:H}=a(F.content),X=H||b.sawMarkup;bt=null;const mt=rt?[]:F.toolCalls;if(!mt.length){if(X&&!B.trim()&&!rt){if(ht<2){ht++,2===ht&&(yt=!0),bt=1===ht?"你刚才的输出把工具调用写成了文本标记,工具并未被执行,该内容也不会展示给用户。请重新处理:若需调用工具,必须使用规范的函数调用(tool_calls)通道发起,绝不要以任何文本形式书写工具调用标记;若无需工具,请直接用中文输出面向用户的最终答复。":"请停止调用任何工具。基于目前已获得的全部信息,直接用中文输出面向用户的完整最终答复,不要输出任何工具调用或协议标记。",Z(`已拦截模型的文本化工具调用标记(工具未被执行),纠正重试中(第 ${ht} 次)`);continue}Z("模型持续输出无效的工具调用标记,已全部拦截;本轮无有效答复(可换个说法重试或更换模型)")}if(!rt&&"length"===F.finishReason&&B&&St<3){St++,Mt+=B,e.debug&&Z(`[debug] 输出达单轮长度上限,自动续写(第 ${St} 段)`);continue}y&&!Y&&V("\n");const t=Mt?Mt+B:B;Mt="",t&&P.push({role:"assistant",content:t});const n=!t.trim()||v(t);if(_t&&n&&"length"!==F.finishReason&&!yt&&xt<2){xt++,e.debug&&Z(`[debug] 检测到「宣告未落地」收尾(空正文或预告式),注入续跑纠偏(第 ${xt} 次)`),P.push({role:"user",content:'你上一条只是说明"接下来要做什么",但既没有发起工具调用、也没有给出结果——用户还没拿到任何最终产物。请立即执行你刚才所说的步骤(需要就用 run_python/工具,不需要就直接算),并直接输出面向用户的完整最终结果(例如完整的按月汇总表与逐月核对表、差异结论),不要再只描述你打算做什么。'});continue}Y&&Y.onTurnEnd?.(t);const o=Q?.();if(o){P.push({role:"user",content:o});continue}if(Et){const e=M("Stop",{cwd:A,result:t},$t);for(const t of e.outputs)Z("钩子·Stop:"+K(t.stdout,160))}if(!rt&&e.decisionAutoExtract&&!1!==e.decisionsEnabled&&_t&&!Tt&&t&&t.trim())try{const n=[...P].reverse().find(t=>"user"===t.role),o=n&&"string"==typeof n.content?n.content:"",r=await _(e,{userText:o,assistantText:t,toolNames:[...kt]}),s="number"==typeof e.decisionExtractMinConfidence?e.decisionExtractMinConfidence:.55;if(r&&(r.confidence??0)>=s){const t=P.filter(t=>"user"===t.role).length,n=x(A,{...r,title:r.what,status:"draft",origin:"agent",confidence:Math.min(r.confidence??.5,.7),sessionId:e.sessionId||"",turn:t},{origin:"agent"});Y?Y.onDecision?Y.onDecision(n):Y.onInfo?.("已抽取决策草稿(待确认):"+K(n.title,80)):V(E.gray(" ◇ 已抽取决策草稿(待确认):"+K(n.title,80))+"\n")}}catch{}return{usage:{prompt:tt,completion:et},lastPromptTokens:nt}}_t=!0,y&&!Y&&V("\n"),Y&&B&&Y.onAssistantText?.(B),P.push({role:"assistant",content:Mt?Mt+B:B,tool_calls:mt}),Mt="";const pt=mt.map((t,e)=>{const n=t.function?.name||"";let o={},r=null;const s=t.function?.arguments||"";if(s)try{o=JSON.parse(s)}catch(t){r=`工具参数不是合法 JSON(${t.message}):${String(s).slice(0,200)}…请修正参数后重新调用。`}const i=ft.get(n),a=$.isActive()&&("write_file"===n||"edit_file"===n);return{tc:t,idx:e,name:n,args:o,argsError:r,extra:i,readOnly:i?!!i.readOnly:c.has(n),deferToIde:a}}),Pt=t=>{for(let e=t;e<pt.length;e++)P.push({role:"tool",tool_call_id:pt[e].tc.id||`call_${e}`,content:O+" 该工具未执行,无任何结果,请勿臆测其输出。"});return Y?Y.onInfo?.("已中断"):V(E.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:tt,completion:et},lastPromptTokens:nt}},jt=async t=>{if(t.argsError)return t.argsError;let n;try{n=t.extra?await t.extra.run(t.args):await u(t.name,t.args,A,e,q)}catch(t){n=`工具执行失败:${t.message}`}return capToolResult(String(n))},At=t=>{kt.add(t.name),Y?Y.onToolStart?.(t.name,t.args):V(E.cyan(" ⏺ ")+m(t.name,t.args)+"\n")},It=(t,e)=>{C(e)&&wt++,Y?Y.onToolResult?.(t.name,String(e)):V(E.gray(" "+K(e))+"\n"),P.push({role:"tool",tool_call_id:t.tc.id,content:String(e)})},vt=(t,e)=>{Y?Y.onSkip?.(t.name,t.args):V(E.gray(` ⊘ ${m(t.name,t.args)} — 已跳过\n`)),P.push({role:"tool",tool_call_id:t.tc.id,content:e||"未授权,未执行。"})},Ct=t=>"edit_file"===t||"write_file"===t,Ut=e=>{try{return t.readFileSync(e,"utf8")}catch{return""}},Ot=(t,e)=>{const o=n.resolve(A,String(t.args.path)),r=g(e,Ut(o),{path:String(t.args.path)});r&&(Y?Y.onFileDiff?.(r):V(r+"\n"))};let Rt=0;for(;Rt<pt.length;){if(q?.aborted)return Pt(Rt);const o=pt[Rt];if(o.argsError){At(o),It(o,o.argsError),Rt++;continue}if(o.readOnly){let t=Rt;for(;t<pt.length&&pt[t].readOnly&&!pt[t].argsError;)t++;const n=pt.slice(Rt,t),o=await J(n,toolConcurrency(e),async t=>{const e=await j.request(t.name,t.args,A,!0);return e.allow?(At(t),{item:t,result:await jt(t)}):{item:t,denied:e.reason||"未授权,未执行。"}});for(const t of o)null!=t.denied?vt(t.item,t.denied):It(t.item,t.result);if(Rt=t,q?.aborted)return Pt(Rt);continue}const r=await j.request(o.name,o.args,A,o.readOnly||o.deferToIde);if(q?.aborted)return Pt(Rt);if(!r.allow){vt(o,r.reason),Rt++;continue}if(Et){const t=M("PreToolUse",{cwd:A,toolName:o.name,args:o.args},$t);for(const e of t.outputs)Z("钩子·PreToolUse:"+K(e.stdout,120));if(t.block){vt(o,"PreToolUse 钩子阻断:"+t.reason),Rt++;continue}t.modifiedArgs&&(o.args={...o.args,...t.modifiedArgs},Z("钩子·PreToolUse 已修改工具参数"))}At(o);const s=Ct(o.name)&&o.args?.path?Ut(n.resolve(A,String(o.args.path))):null;if(G&&null!=s&&o.args?.path){const e=t.existsSync(n.resolve(A,String(o.args.path)));G.push(s.length>2097152?{path:String(o.args.path),tooLarge:!0}:{path:String(o.args.path),before:s,existed:e})}let i=await jt(o);if(Et){const t=M("PostToolUse",{cwd:A,toolName:o.name,args:o.args,result:String(i)},$t);if(t.outputs.length){for(const e of t.outputs)Z("钩子·PostToolUse:"+K(e.stdout,120));i=String(i)+"\n\n[PostToolUse 钩子输出]\n"+t.outputs.map(t=>t.stdout).join("\n")}}It(o,i),null!=s&&Ot(o,s),Rt++}wt>=3&&!yt&&!bt&&(bt="你刚才连续多次工具调用失败。请停下来重新审视:是不是文件路径/内容/前提假设错了?先用 read_file / grep / list_files 核实现状,再换一种更可靠的方法,绝不要重复同样会失败的调用。",wt=0,Z("检测到连续工具失败,已提示智能体换一种更可靠的方法"));const Nt=Q?.();Nt&&P.push({role:"user",content:Nt})}return Mt&&(P.push({role:"assistant",content:Mt}),Y&&Y.onTurnEnd?.(Mt)),Y?Y.onInfo?.("已达最大工具轮数,停止"):V(E.yellow("\n[已达最大工具轮数,停止]\n")),{usage:{prompt:tt,completion:et},lastPromptTokens:nt}}
1
+ import e from"node:fs";import t from"node:os";import n from"node:path";import{streamChat as o,getPlatformConfig as s}from"./api.js";import{classifyError as r}from"./errkind.js";import{MarkupFilter as i,stripMarkup as a}from"./markup-filter.js";import{TOOL_SPECS as l,READONLY_TOOLS as c}from"./tools.js";import{toolLabel as u,indent as d,renderFileDiff as m}from"./render.js";import{compactIfNeeded as f,buildModelMessages as g,contextGauge as p}from"./compaction.js";import{catalogTokenBudget as h,noteMemberLimitFromError as b}from"./context-window.js";import{syncSkillCatalog as y,catalogDigestOf as w}from"./skill-catalog.js";import{makeTodoTool as $,formatTodos as S,TODO_TOOL_NAME as x}from"./todo.js";import{makeDecisionTool as k,DECISION_TOOL_NAME as _,recordDecision as M,extractDecisionFromTurn as j}from"./decisions.js";import{makeMemoryTool as I}from"./memory.js";import{loadHooks as T,runHooks as E}from"./hooks.js";import{capToolResult as C,capOrSpill as v,MAX_TOOL_RESULT_CHARS as A}from"./spill.js";import{runToolCall as O,isBinaryFile as R}from"./toolpipe.js";import{inspectForTurn as P,isCodeFile as N}from"./code-tools.js";import{inspectWebForTurn as U,isWebFile as J,turnCorrection as D}from"./web-inspect.js";import{inspectDocsForTurn as z,docTurnCorrection as K}from"./docx-tools.js";import{isFileWriteTool as F}from"./permissions.js";import{decideUpgrade as W}from"./routing.js";import{renderSdk as B,CODE_MODE_HIDDEN as q}from"./codemode.js";import*as L from"./ide.js";import{c as Q}from"./colors.js";export{C as capToolResult,v as capOrSpill,A as MAX_TOOL_RESULT_CHARS};export function toolConcurrency(e){const n=Number(e?.toolConcurrency);if(Number.isFinite(n)&&n>0)return Math.min(24,Math.floor(n));let o=4;try{o=t.cpus()?.length||4}catch{}return Math.max(8,Math.min(24,o-2))}const G=/context[_\s-]?length|maximum context|context window|too many tokens|reduce the length|prompt is too long|上下文.*(超|过长|超出|溢出)|输入.*过长|token.*(超出|超过).*(上限|限制)/i;export function isContextOverflowError(e){return G.test(String(e||""))}const H=32768,V=new Map;function X(e,t){e&&(V.delete(e),V.set(e,t),V.size>200&&V.delete(V.keys().next().value))}function Y(e,t){let n="";try{n=JSON.stringify(t??{},(e,t)=>t&&"object"==typeof t&&!Array.isArray(t)?Object.fromEntries(Object.keys(t).sort().map(e=>[e,t[e]])):t)}catch{n=String(t)}return e+"|"+n.slice(0,4e3)}const Z=/(现在|接下来|下面|这就|马上|我来|让我|我要|我先|我这就|咱们|让我们|稍等|待我)/,ee=/(做|进行|生成|编写|写(一|个|完|出)?|制作|计算|统计|核对|汇总|整理|分析|梳理|输出|列出|准备|运行|执行|开始)/,te=/\b(let me|now let me|i.?ll|i will|let.?s|next,? i)\b[^.!?\n]*\b(write|create|generat|calculat|comput|do|produc|compil|analyz|summar|prepar|run|make|list)\b/i;export function jsonErrorContext(e,t){const n=String(e||""),o=String(t?.message||"").match(/position (\d+)/);if(!o)return`:${n.slice(0,200)}…`;const s=Number(o[1]);return`,出错位置附近:…${n.slice(Math.max(0,s-60),s)+"⟦此处⟧"+n.slice(s,s+30)}…${/[^\\]"[^,:}\]\s]/.test(n.slice(Math.max(0,s-60),s+1))?'看起来是字符串里夹了没转义的英文双引号(中文里引述请用「」或“”,或写成 \\")。':""}`}export function truncatedArgs(e,t,n){if("length"===n)return!0;const o=String(t?.message||"");if(!/Unterminated string|Unexpected end of JSON|Expected .* after .* in JSON at position/.test(o))return!1;const s=o.match(/position (\d+)/),r=s?Number(s[1]):String(e).length;return String(e).length>40&&r>=String(e).length-2}function ne(e){const t=String(e||"").trim().split(/\n+/).map(e=>e.trim()).filter(Boolean),n=t[t.length-1]||"";return!(!n||n.length>120)&&(!!te.test(n)||Z.test(n)&&ee.test(n))}function oe(e){const t=String(e);return t.startsWith("工具执行失败")||t.startsWith("命令启动失败")||t.startsWith("【工具超时】")||t.includes("不是合法JSON")||t.includes("不是合法 JSON")||t.includes("未找到要替换的内容")||t.includes("路径超出项目目录")||t.includes("是目录而非文件")}function se(e){return"\n\n# 当前目标(用户设定)\n"+String(e).trim()+"\n在本回合结束、停止作答前,检查该目标是否已达成:若未达成,应继续推进(补充调研/改动/验证),不要过早停止;达成后用一句话说明已达成。"}const re="【用户中断】",ie="\n\n# 上一轮被用户中断(重要)\n上一回合被用户主动中断:会话里标注「"+re+"」的工具调用并未真正执行、没有任何结果。请勿臆测这些未完成工具的输出,也不要假设被中断的工作已完成或推断其结论;一切以用户最新一条消息为准——如与被中断的工作无关,就按新指令重新开始,需要时先与用户确认再继续。";function ae(e){if(!Array.isArray(e))return!1;let t=-1;for(let n=e.length-1;n>=0;n--)if("user"===e[n].role){t=n;break}if(t<=0)return!1;for(let n=t-1;n>=0;n--){const t=e[n];if("user"===t.role)break;if("tool"===t.role&&"string"==typeof t.content&&t.content.includes(re))return!0}return!1}function le(e){return"string"==typeof e?e:Array.isArray(e)?e.filter(e=>"text"===e?.type).map(e=>e.text||"").join("\n"):""}async function ce(e,t,n){const o=new Array(e.length);let s=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;s<e.length;){const t=s++;o[t]=await n(e[t],t)}})),o}export function isPlatformModel(e){return/^(agent|workflow|datahuman|squad)-/.test(String(e||""))}export function buildPlatformMessages(e){return e.filter(e=>("user"===e.role||"assistant"===e.role)&&!w(e)&&("string"==typeof e.content?e.content.trim().length>0:Array.isArray(e.content)&&e.content.length>0)).map(e=>({role:e.role,content:e.content}))}function ue(e,t=200){const n=String(e).split("\n")[0];return n.length>t?n.slice(0,t)+"…":n}export async function runTurn({config:t,messages:w,permissions:C,cwd:v,ask:A,signal:G,extraTools:Z=[],depth:ee=0,io:te,events:de,todoState:me,compaction:fe,pullQueued:ge,maxIters:pe,fileJournal:he}){const be=te&&te.write||(e=>process.stdout.write(e)),ye=de||null,we=e=>{ye?ye.onInfo?.(e):be(Q.gray(" · "+e)+"\n")};let $e=0,Se=0,xe=0,ke=null;const _e=Number(pe||t.maxTurns)||120,Me=isPlatformModel(t.model),je={low:24e4,medium:36e4,high:6e5}[t.reasoningEffort];let Ie=!1,Te=String(t.fallbackModel||"").trim(),Ee=0,Ce=t.model,ve=null;if(0===ee&&t.autoModelStrong&&!Me){const e=[...w].reverse().find(e=>"user"===e.role),n=le(e?.content);ve=W({text:n,planMode:t.planMode,attachmentCount:t.attachmentCount},{threshold:t.autoModelThreshold}),ve.upgrade&&t.autoModelStrong!==t.model&&(Ce=t.autoModelStrong),ye?.onRouteDecision?.({...ve,model:Ce,upgraded:Ce!==t.model}),t.debug&&we(`[automodel] ${ve.reason}`+(Ce!==t.model?` → 升配 ${Ce}`:" → 维持"))}const Ae=fe||{},Oe=[...Z];if(0===ee&&!Me&&!Oe.some(e=>e.spec?.function?.name===x)){const e=me||{list:[]};Oe.push($({state:e,emit:e=>{ye?ye.onTodo?ye.onTodo(e.map(e=>({...e}))):ye.onInfo?.("任务清单:\n"+S(e)):be(Q.gray(d(S(e)," "))+"\n")}}))}if(0!==ee||Me||!1===t.memoryEnabled||Oe.some(e=>"memory_write"===e.spec?.function?.name)||Oe.push(I({cwd:v,emit:e=>{ye?ye.onMemory?ye.onMemory(e):ye.onInfo?.("已记入长期记忆:"+ue(e,80)):be(Q.gray(" ✎ 已记入长期记忆:"+ue(e,80))+"\n")}})),0===ee&&!Me&&!1!==t.decisionsEnabled&&!Oe.some(e=>e.spec?.function?.name===_)){const e=w.filter(e=>"user"===e.role).length;Oe.push(k({cwd:v,sessionId:t.sessionId||"",turn:e,emit:e=>{it=!0,ye?ye.onDecision?ye.onDecision(e):ye.onInfo?.("已记录决策:"+ue(e.title,80)):be(Q.gray(" ◆ 已记录决策:"+ue(e.title,80))+"\n")}}))}const Re=new Map;let Pe=null,Ne=[],Ue=new Set,Je=0,De=null,ze=new Set,Ke=null;const Fe=e=>{if("function"!=typeof e?.enabled)return!1;try{return!1===e.enabled(t)}catch{return!1}},We=()=>{if(Array.isArray(Z)&&Z.length>Je){for(const e of Z.slice(Je)){const t=e?.spec?.function?.name;t&&!Re.has(t)&&Oe.push(e)}Je=Z.length,Pe=null}De!==t.toolMode&&(De=t.toolMode,Pe=null);const e=Me?[]:Oe.filter(Fe).map(e=>e.spec.function.name),n=e.join(",");if(Ke!==n&&(Ke=n,ze=new Set(e),Pe=null),null===Pe){Re.clear();for(const e of Oe)Re.set(e.spec.function.name,e);const e="both"===t.toolMode||"code"===t.toolMode;Ne=Me?[]:[...l,...Oe.filter(e=>!ze.has(e.spec.function.name)).map(e=>e.spec)].filter(t=>e||"run_program"!==t.function.name),Ue=new Set(Ne.map(e=>e.function.name));const n="code"===t.toolMode&&Ue.has("run_program");Pe=Me?void 0:n?Ne.filter(e=>!q.has(e.function.name)):Ne}};Je=Array.isArray(Z)?Z.length:0,We();let Be=0,qe=!1,Le=0,Qe="",Ge=0,He=0,Ve=null;const Xe=new Set;let Ye=0;const Ze=new Set;let et=0;const tt=new Set;let nt=0,ot=0,st=0,rt=!1,it=!1;const at=new Set;let lt="";const ct=t.maxTokens||8192;let ut=Math.max(ct,V.get(t.sessionId)||0),dt=0,mt=!1,ft=!1;const gt=0!==ee||Me?null:T(v),pt=!(!gt||!gt.all.length);if(pt){const e=[];if(w.filter(e=>"user"===e.role).length<=1){const n=E("SessionStart",{cwd:v,sessionId:t.sessionId||"",model:t.model||""},gt);n.injectContext&&e.push(n.injectContext);for(const t of n.outputs)e.push(t.stdout)}const n=[...w].reverse().find(e=>"user"===e.role),o=E("UserPromptSubmit",{cwd:v,prompt:le(n?.content),sessionId:t.sessionId||"",model:t.model||"",turn:w.filter(e=>"user"===e.role).length},gt);if(o.block)return we("提交被UserPromptSubmit钩子阻断:"+o.reason),{error:"提交被钩子阻断:"+o.reason,usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke};o.injectContext&&e.push(o.injectContext);for(const t of o.outputs)e.push(t.stdout);e.length&&(Ve="【钩子提供的附加上下文】\n"+e.join("\n"))}for(let l=0;l<_e;l++){We();const d=l===_e-1;!d||Me||qe||Ve||(Ve="已接近本回合的工具调用轮数上限。请不要再调用任何工具,基于目前已获得的信息,直接用中文输出面向用户的完整最终答复。");const $=Me?0:await f(w,t,{state:Ae,lastPromptTokens:xe});$>0&&t.debug&&we(`[debug] 已压缩上下文(${$} 条较早消息已摘要)`);let S=!1,x=!1;const k=new i,_=e=>{e&&(S||!x||ye||be("\n"),S=!0,ye?ye.onDelta?.(e):be(e))};if(0===ee&&!Me&&!1!==t.skillsEnabled){const e=g(w,Ae),n=y(w,e,v,{allowInsert:!(Ae.upTo>0),maxTokens:h(t)});"none"!==n.action&&t.debug&&we(`[skills] 目录${{initial:"首投",replace:"已刷新",restore:"压缩后重投"}[n.action]}(${n.count} 条,指纹 ${n.digest})`),"none"!==n.action&&ye?.onSkillCatalog?.({...n})}const I=Me?buildPlatformMessages(w):g(w,Ae);let T=I;if(!Me&&I.length&&"system"===I[0]?.role){let e="";t.planMode&&(e+="\n\n# 计划模式(当前开启)\n你现在处于计划模式:只能用只读工具(read_file/glob/grep/list_files/fetch_url)调研,不得做任何改动——write_file/edit_file/run_bash等写或执行操作都会被拒绝,不要反复尝试。请先充分理解需求与现状,然后产出一份清晰、分步的实施方案:涉及哪些文件、每步改动要点、如何验证、风险点,用todo_write列出步骤,随后停下等待用户确认。用户关闭计划模式后你再执行。"),t.sandbox&&(e+="\n\n# 沙箱模式(当前开启)\n禁止执行shell命令(run_bash)、联网(fetch_url)与环境/应用自动化工具(install_*/setup_environment/open_*),调用会被拒绝,不要反复尝试。只做本地文件读写与分析。"),t.goal&&String(t.goal).trim()&&(e+=se(t.goal)),ae(w)&&(e+=ie),Ue.has("run_program")&&(e+="\n\n# 用程序编排工具(run_program)\n"+B(Ne)),e&&(T=[{...I[0],content:String(I[0].content||"")+e},...I.slice(1)])}const A=[];lt?A.push({role:"assistant",content:lt},{role:"user",content:"你上一条回复因达到单轮输出长度上限而被截断,用户尚未看到剩余部分。请从截断处直接继续输出剩余内容:无缝衔接上文(若截断处在句子、代码块或表格中间,从中断字符处接着写),不要重复任何已输出的内容,不要道歉或解释,不要重新开头或总结前文。"}):Ve&&A.push({role:"user",content:Ve});const W=Date.now();let q,Z=!1,te="";try{q=await o({baseUrl:t.baseUrl,apiKey:t.apiKey,model:Ce,messages:A.length?[...T,...A]:T,tools:qe||d?void 0:Pe,maxTokens:ut,extraBody:mt?{enable_thinking:!1}:void 0,reasoningEffort:t.reasoningEffort,stallTimeoutMs:je,client:t.client,sessionId:t.sessionId,aux:t.aux||void 0,continuation:!!lt,tailCount:A.length},{onContent:e=>{Z=!0,te+=e,_(k.feed(e))},onReasoning:e=>{!1!==t.showReasoning&&(lt||(ye?ye.onReasoning?.(e):(x||be(Q.gray("\n[思考] ")),be(Q.gray(e))),x=!0))},onReasoningEnd:()=>ye?.onReasoningEnd?.(),onRetry:(e,n,o,s)=>{s?.network?we(`${o.replace(/[::].*$/,"")},${Math.round(n/1e3)} 秒后自动重试(第 ${e}/${s.max} 次)`):t.debug&&we(`[retry] 模型响应异常,${Math.round(n/1e3)}s后自动重试(第 ${e} 次):${o}`)}},G)}catch(e){if("AbortError"===e.name)return ye?ye.onInfo?.("已中断"):be(Q.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke};if(!e.nonRetryable&&!e.partial&&ut>ct&&!ft&&!G?.aborted){ft=!0,dt=2,ut=ct,V.delete(t.sessionId),t.debug&&we(`[debug] 抬高输出预算后请求失败(${e.message}),已撤回到 ${ct} 重试`);continue}if(!e.nonRetryable&&!e.partial&&!Me&&!G?.aborted&&isContextOverflowError(e.message)&&Ee<3){Ee++,b(t,e.message);const n=await f(w,t,{state:Ae,force:!0});if(n>0){Z&&te&&(lt+=te),t.debug&&we(`[overflow] 上下文超出模型窗口,已强制压缩 ${n} 条较早消息后重试`);continue}t.debug&&we("[overflow] 上下文超限但无可压缩内容,转兜底模型")}const n=r(e);if(!(n||e.nonRetryable||e.partial||e.network||Me||Ie||G?.aborted)){if(!Te)try{const e=await s(t.baseUrl,t.apiKey);Te=String(e&&e.fallback_model||"").trim()}catch{}if(Te&&Te!==Ce){Ie=!0,Ce=Te,Z&&te&&(lt+=te),t.debug&&we(`[fallback] 主模型失败(${e.message}),静默切兜底模型 ${Te}${Z?"(续写)":""}`);continue}}return ye?ye.onInfo?.("✗ 模型调用失败:"+e.message):be("\n"+Q.red("✗ 模型调用失败:")+e.message+"\n"),{error:e.message,errorKind:n,usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke}}_(k.flush()),q.usage&&($e+=q.usage.prompt_tokens||0,Se+=q.usage.completion_tokens||0,xe=q.usage.prompt_tokens||xe,ke=q.usage,0===ee&&ye&&ye.onUsage?.({lastPromptTokens:xe,prompt:$e,completion:Se,ctx:p({messages:w,compaction:Ae,config:t,lastPromptTokens:xe})})),t.debug&&we(`[debug] 轮${l+1} · ${t.model} · in ${q.usage?.prompt_tokens||0} / out ${q.usage?.completion_tokens||0} tok · ${Date.now()-W}ms · ${(q.toolCalls||[]).length} 工具调用`);const{text:le,sawMarkup:de}=a(q.content),me=de||k.sawMarkup;Ve=null;const fe=Me?[]:q.toolCalls;if(!fe.length){if(me&&!le.trim()&&!Me){if(Be<2){Be++,2===Be&&(qe=!0),Ve=1===Be?"你刚才的输出把工具调用写成了文本标记,工具并未被执行,该内容也不会展示给用户。请重新处理:若需调用工具,必须使用规范的函数调用(tool_calls)通道发起,绝不要以任何文本形式书写工具调用标记;若无需工具,请直接用中文输出面向用户的最终答复。":"请停止调用任何工具。基于目前已获得的全部信息,直接用中文输出面向用户的完整最终答复,不要输出任何工具调用或协议标记。",we(`已拦截模型的文本化工具调用标记(工具未被执行),纠正重试中(第 ${Be} 次)`);continue}we("模型持续输出无效的工具调用标记,已全部拦截;本轮无有效答复(可换个说法重试或更换模型)")}if(!Me&&"length"===q.finishReason&&le&&ot<3){ot++,lt+=le,t.debug&&we(`[debug] 输出达单轮长度上限,自动续写(第 ${ot} 段)`);continue}if(!Me&&"length"===q.finishReason&&!le.trim()&&!lt){if(dt<2&&ut<H){dt++,ut=Math.min(3*ut,H),X(t.sessionId,ut),t.debug&&we(`[debug] 思考吃光输出预算(正文为空),抬高单轮上限到 ${ut} 重试(第 ${dt} 次)`);continue}if(!mt){mt=!0,Ve="你上一条把全部输出预算用在了思考上,没有输出任何正文,用户什么都没看到。请立刻直接输出面向用户的最终答复(结论、表格、结果放最前面),思考尽量精简。",t.debug&&we("[debug] 抬高预算后仍无正文,改为要求先出正文(并best-effort关思考)");continue}}S&&!ye&&be("\n");const e=lt?lt+le:le;lt="",e&&w.push({role:"assistant",content:e});const o=!e.trim();if(rt&&!o&&ne(e)&&"length"!==q.finishReason&&!qe&&st<2){st++,t.debug&&we(`[debug] 检测到「宣告未落地」预告式收尾,注入续跑纠偏(第 ${st} 次)`),w.push({role:"user",content:'你上一条只是说明"接下来要做什么",但既没有发起工具调用、也没有给出结果——用户还没拿到任何最终产物。请立即执行你刚才所说的步骤(需要就用run_python/工具,不需要就直接算),并直接输出面向用户的完整最终结果(例如完整的按月汇总表与逐月核对表、差异结论),不要再只描述你打算做什么。'});continue}if(o&&!Me&&"length"!==q.finishReason&&!qe&&st<2){st++,t.debug&&we(`[debug] 本回合正文为空,注入续跑纠偏(第 ${st} 次)`),Ve="你上一条回复没有输出任何面向用户的正文(内容可能全部留在了思考里),用户一个字都没拿到。请立刻用中文直接输出完整的最终答复,不要只在思考里推演。";continue}if(!Me&&0===ee&&!1!==t.codeSelfCheck&&Xe.size&&Ye<1&&!qe&&!o&&"length"!==q.finishReason){Ye++;const e=await P(v,[...Xe],t);if(e&&e.issues.length){const t=e.issues.slice(0,12),o=t.map(e=>{const t=n.relative(v,e.path)||e.path;return` [${e.rule}] ${t}:${e.line}${e.unit?` 〔${e.unit}〕`:""}\n 问题:${e.msg}\n 改法:${e.hint||"按上面的说明处理"}`}).join("\n"),s=e.issues.length>t.length?`\n …… 另有 ${e.issues.length-t.length} 条,用code_inspect看全量。`:"";we(`代码自检:本回合改动的 ${Xe.size} 个源码文件有 ${e.issues.length} 处必修问题,已要求修复`),Ve="【代码自检】你本回合改动的源码经体检发现以下**必修**问题,请逐条改完再结束:\n"+o+s+"\n\n改完用code_inspect(path) 复检确认必修清零。\n若确认某一条是误报,在那一行上方加 `// code-inspect-ignore: 规则名 简短理由`,并在回复里说明为什么判定它是误报——不要默默跳过,也不要在没修完时就宣布完成。";continue}}if(!Me&&0===ee&&!1!==t.webSelfCheck&&Ze.size&&et<1&&!qe&&!o&&"length"!==q.finishReason){et++;const e=await U(v,[...Ze],t).catch(()=>null);if(e&&e.errors.length){we(`网页自检:本回合改动的页面(${e.entries.join("、")})在浏览器里有 ${e.errors.length} 处必修问题,已要求修复`),Ve=D(e);continue}}if(!Me&&0===ee&&!1!==t.docSelfCheck&&tt.size&&nt<1&&!qe&&!o&&"length"!==q.finishReason){nt++;const e=await z(v,[...tt],t).catch(()=>null);if(e&&e.issues.length){we(`文档自检:本回合生成的 ${tt.size} 份文档有 ${e.issues.length} 处必修问题,已要求修复`),Ve=K(e,v);continue}}if(o&&!me){const e=dt>0||mt?",自动抬高上限并重试后仍未产出":"";we("length"===q.finishReason?`模型把本回合的输出预算(单轮上限 ${ut})全用在了思考上,没有产出正文${e}。建议把任务拆小一点分步做,或换个模型再试。`:`模型本回合没有返回任何正文${st>0?"(已自动重试)":""}。可以直接重发,或换个说法/换个模型再试。`)}ye&&ye.onTurnEnd?.(e);const s=ge?.();if(s){w.push({role:"user",content:s});continue}if(pt){const n=E("Stop",{cwd:v,result:e,sessionId:t.sessionId||"",model:t.model||"",turn:w.filter(e=>"user"===e.role).length},gt);for(const e of n.outputs)we("钩子·Stop:"+ue(e.stdout,160))}if(!Me&&t.decisionAutoExtract&&!1!==t.decisionsEnabled&&rt&&!it&&e&&e.trim())try{const n=[...w].reverse().find(e=>"user"===e.role),o=n&&"string"==typeof n.content?n.content:"",s=await j(t,{userText:o,assistantText:e,toolNames:[...at]}),r="number"==typeof t.decisionExtractMinConfidence?t.decisionExtractMinConfidence:.55;if(s&&(s.confidence??0)>=r){const e=w.filter(e=>"user"===e.role).length,n=M(v,{...s,title:s.what,status:"draft",origin:"agent",confidence:Math.min(s.confidence??.5,.7),sessionId:t.sessionId||"",turn:e},{origin:"agent"});ye?ye.onDecision?ye.onDecision(n):ye.onInfo?.("已抽取决策草稿(待确认):"+ue(n.title,80)):be(Q.gray(" ◇ 已抽取决策草稿(待确认):"+ue(n.title,80))+"\n")}}catch{}return{usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke}}rt=!0,S&&!ye&&be("\n"),ye&&le&&ye.onAssistantText?.(le),w.push({role:"assistant",content:lt?lt+le:le,tool_calls:fe,...q.nativeState?{huicai_anthropic:q.nativeState}:{}}),lt="";const pe=fe.map((e,n)=>{const o=e.function?.name||"";let s={},r=null,i=!1;const a=e.function?.arguments||"";if(a)try{s=JSON.parse(a)}catch(e){i=truncatedArgs(a,e,q.finishReason),r=i?`工具参数在第 ${String(a).length} 个字符处被截断:这次回复的输出触到了单次上限(约 ${ut} token),参数没写完——不是字符或转义的问题,原样重试还会在同一处断。常见成因是一段内容太长,或一条回复里并行发了好几个大参数的调用(上限是整条回复共用的)。请拆小:大参数的调用一次少发几个;write_file每段约 3000 字以内,后续段落用append=true追加;很长的JSON(测算假设、图表spec)先write_file落盘,再用对应的 *_path / spec_path参数传入。`:`工具参数不是合法JSON(${e.message})${jsonErrorContext(a,e)}请修正参数后重新调用。`}const l=Re.get(o);if(l&&ze.has(o)&&!r){const e="function"==typeof l.disabledMessage?l.disabledMessage(t):l.disabledMessage;r=String(e||`工具 ${o} 当前已停用(开关已关闭),未执行。`)}const u=L.isActive()&&("write_file"===o||"edit_file"===o);return{tc:e,idx:n,name:o,args:s,argsError:r,argsTruncated:i,extra:l,readOnly:l?!!l.readOnly:c.has(o),deferToIde:u}});!Me&&pe.some(e=>e.argsTruncated)&&dt<2&&ut<H&&(dt++,ut=Math.min(3*ut,H),X(t.sessionId,ut),t.debug&&we(`[debug] 工具参数被单轮输出上限截断,抬高到 ${ut}`));const ve=e=>{for(let t=e;t<pe.length;t++)w.push({role:"tool",tool_call_id:pe[t].tc.id||`call_${t}`,content:re+" 该工具未执行,无任何结果,请勿臆测其输出。"});return ye?ye.onInfo?.("已中断"):be(Q.gray("\n[已中断]\n")),{aborted:!0,usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke}},Oe={cwd:v,config:t,signal:G,permissions:C,hooks:gt,hooksActive:pt,fileJournal:he,info:we,shortLine:ue,turn:w.filter(e=>"user"===e.role).length,onStart:e=>e.nested?we("程序内调用:"+u(e.name,e.args)):De(e),listTools:()=>Ne,resolveTool:e=>{const t=Re.get(e);return t&&ze.has(e)?null:t?{readOnly:!!t.readOnly,extra:t}:Ue.has(e)?{readOnly:c.has(e),extra:null}:null}},Je=e=>O({name:e.name,args:e.args,readOnly:e.readOnly,extra:e.extra,callId:e.tc?.id,deferToIde:e.deferToIde,argsError:e.argsError,item:e},Oe),De=e=>{at.add(e.name),ye?ye.onToolStart?.(e.name,e.args,{callId:e.callId||e.tc?.id}):be(Q.cyan(" ⏺ ")+u(e.name,e.args)+"\n")},Ke=(e,t)=>{const n=e.item||e;oe(t)&&Le++;const o=Y(e.name,e.args);o===Qe?Ge++:(Qe=o,Ge=1,He=0),ye?ye.onToolResult?.(e.name,String(t),{callId:n.tc?.id,failed:oe(t)}):be(Q.gray(" "+ue(t))+"\n"),w.push({role:"tool",tool_call_id:n.tc.id,content:String(t)})},Fe=(e,t)=>{const n=e.item||e;ye?ye.onSkip?.(e.name,e.args,{callId:n.tc?.id,reason:t}):be(Q.gray(` ⊘ ${u(e.name,e.args)} — 已跳过\n`)),w.push({role:"tool",tool_call_id:n.tc.id,content:t||"未授权,未执行。"})},ht=e=>"render_diagram"!==e,bt=t=>{try{return e.readFileSync(t,"utf8")}catch{return""}},yt=(e,t)=>{const o=n.resolve(v,String(e.args.path));if(R(o))return;const s=m(t,bt(o),{path:String(e.args.path)});s&&(N(o)&&Xe.add(o),J(o)&&Ze.add(o),ye?ye.onFileDiff?.(s):be(s+"\n"))};let wt=0;for(;wt<pe.length;){if(G?.aborted)return ve(wt);const o=pe[wt];if(o.readOnly&&!o.argsError){let e=wt;for(;e<pe.length&&pe[e].readOnly&&!pe[e].argsError;)e++;const n=pe.slice(wt,e),o=await ce(n,toolConcurrency(t),e=>Je(e));for(const e of o)null!=e.denied?Fe(e,e.denied):Ke(e,e.result);if(wt=e,G?.aborted)return ve(wt);continue}const s=await Je(o);if(G?.aborted&&null==s.denied&&!s.settled)return ve(wt);if(null==s.denied)if(s.settled)De(s),Ke(s,s.result),wt++;else{if(Ke(s,s.result),null!=s.meta.before&&ht(s.name)&&yt(s,s.meta.before),F(s.name)&&/\.docx$/i.test(String(s.args?.path||""))){const t=n.resolve(v,String(s.args.path));e.existsSync(t)&&tt.add(t)}wt++}else Fe(s,s.denied),wt++}Le>=3&&!qe&&!Ve&&(Ve="你刚才连续多次工具调用失败。请停下来重新审视:是不是文件路径/内容/前提假设错了?先用read_file / grep / list_files核实现状,再换一种更可靠的方法,绝不要重复同样会失败的调用。",Le=0,we("检测到连续工具失败,已提示智能体换一种更可靠的方法")),qe||Ve||(Ge>=5&&He<2?(Ve="停止重复调用。你已经用完全相同的参数调用同一工具达 5 次,这不会产生任何新信息,只是在空转。现在必须二选一:① 基于已有结果直接给出结论/继续下一步;② 明确说明你缺少什么、为什么现有结果不够,然后换一种**明显不同**的方法去拿。",He=2,we(`检测到同一调用连续重复 ${Ge} 次,已要求智能体停止空转`)):Ge>=3&&He<1&&(Ve="注意:你刚刚用**完全相同的参数**连续调用了同一个工具多次,结果不会因为再调一次而改变。请先仔细重读上一次的返回内容——答案很可能已经在里面;若确实需要更多信息,请换不同的参数或换一个工具,不要重复同一次调用。",He=1,we(`检测到同一调用连续重复 ${Ge} 次,已提示智能体重读上次结果`)));const $t=ge?.();$t&&w.push({role:"user",content:$t})}return lt&&(w.push({role:"assistant",content:lt}),ye&&ye.onTurnEnd?.(lt)),ye?ye.onInfo?.("已达最大工具轮数,停止"):be(Q.yellow("\n[已达最大工具轮数,停止]\n")),{usage:{prompt:$e,completion:Se},lastPromptTokens:xe,lastUsage:ke}}
package/src/api.js CHANGED
@@ -1 +1 @@
1
- import{sanitizeMessagesForModel as t}from"./vision.js";const e=new Set([429,500,502,503,504]),n=15e3;function o(t){return{"Content-Type":"application/json",Authorization:`Bearer ${t}`}}function r(t,e){try{const e=JSON.parse(t),n=e?.error?.type||e?.type||"";if(e?.error?.message)return{message:e.error.message,type:n};if(e?.message)return{message:e.message,type:n};if(e?.msg)return{message:e.msg,type:n}}catch{}return{message:t||e,type:""}}async function a(t){let e="";try{e=await t.text()}catch{}return{...r(e,`HTTP ${t.status}`),status:t.status}}function s(t){return Object.assign(new Error(t.message),{status:t.status,type:t.type})}function i(t,e){for(const n of e){const e="number"==typeof n.index?n.index:t.length;t[e]||(t[e]={id:"",type:"function",function:{name:"",arguments:""}}),n.id&&(t[e].id=n.id),n.type&&(t[e].type=n.type),n.function?.name&&(t[e].function.name+=n.function.name),n.function?.arguments&&(t[e].function.arguments+=n.function.arguments)}}function c(t,e){return new Promise((n,o)=>{if(e?.aborted)return o(u());const r=setTimeout(()=>{e?.removeEventListener?.("abort",a),n()},t),a=()=>{clearTimeout(r),o(u())};e?.addEventListener?.("abort",a,{once:!0})})}function u(){const t=new Error("已中断");return t.name="AbortError",t}function f(t,e){const n=new AbortController;let o=!1;const r=()=>n.abort();t&&(t.aborted?n.abort():t.addEventListener("abort",r,{once:!0}));const a=setTimeout(()=>{o=!0,n.abort()},e);return{signal:n.signal,ms:e,get timedOut(){return o},done(){clearTimeout(a),t?.removeEventListener?.("abort",r)}}}function l(t){const e=String(t?.message||t||""),n=t?.cause?.code||t?.code||"";return"ECONNREFUSED"===n||/ECONNREFUSED|connection refused/i.test(e)?"无法连接模型服务(连接被拒绝):请检查网络或稍后重试。":"ENOTFOUND"===n||"EAI_AGAIN"===n||/getaddrinfo|ENOTFOUND|EAI_AGAIN/i.test(e)?"无法解析模型服务地址(DNS 异常):请检查网络连接后重试。":"ETIMEDOUT"===n||/ETIMEDOUT|timeout|timed out/i.test(e)?"连接模型服务超时:网络较慢或服务繁忙,请稍后重试。":"ECONNRESET"===n||/ECONNRESET|socket hang up|network|fetch failed/i.test(e)?"与模型服务的连接中断:网络波动,请稍后重试。":"网络错误:"+e}function m(t,e){const n=e?.headers?.get?.("retry-after"),o=null!=n?Number(n):NaN;return Number.isFinite(o)&&o>=0?Math.min(1e3*o,3e4):Math.min(500*2**t+Math.floor(250*Math.random()),8e3)}export async function streamChat(n,r={},i){const h=`${n.baseUrl}/v1/chat/completions`,{messages:g}=t(n.messages,n.model),y={model:n.model,messages:g,stream:!0,stream_options:{include_usage:!0}};n.tools&&n.tools.length&&(y.tools=n.tools,y.tool_choice="auto"),"number"==typeof n.temperature&&(y.temperature=n.temperature),Number.isFinite(n.maxTokens)&&n.maxTokens>0&&(y.max_tokens=Math.floor(n.maxTokens)),n.reasoningEffort&&"off"!==n.reasoningEffort&&(y.reasoning_effort=n.reasoningEffort),n.extraBody&&"object"==typeof n.extraBody&&Object.assign(y,n.extraBody);const p=o(n.apiKey);n.client&&(p["X-Huicai-Client"]=n.client),n.sessionId&&(p["X-Huicai-Session"]=n.sessionId),n.aux&&(p["X-Huicai-Aux"]="1"),n.continuation&&(p["X-Huicai-Continue"]="1"),n.tailCount>0&&(p["X-Huicai-Tail"]=String(n.tailCount));const w=JSON.stringify(y);for(let t=0;;t++){let o;const g=f(i,n.headersTimeoutMs??12e4);try{o=await fetch(h,{method:"POST",headers:p,body:w,signal:g.signal})}catch(e){if(i?.aborted)throw u();if(g.timedOut){const e=`等待模型服务响应超时(${Math.round(g.ms/1e3)}s 内未返回响应头)`;if(t<2){const n=m(t,null);r.onRetry?.(t+1,n,e),await c(n,i);continue}throw new Error(e+",请稍后重试。")}if("AbortError"===e?.name)throw u();if(t<2){const n=m(t,null);r.onRetry?.(t+1,n,l(e)),await c(n,i);continue}throw new Error(l(e))}finally{g.done()}if(!o.ok||!o.body){const n=await a(o);if(!("usage_limit_exceeded"===n.type)&&e.has(o.status)&&t<2){const e=m(t,o);r.onRetry?.(t+1,e,`HTTP ${o.status}:${n.message}`),await c(e,i);continue}throw s(n)}return d(o,r,i,n.stallTimeoutMs??18e4)}}async function d(t,e,n,o){const r=t.body.getReader(),a=new TextDecoder;let s="",c="";const f=[];let l=null,m=null,d=!1,h=null,g=!1;const y=()=>{h&&clearTimeout(h),h=setTimeout(()=>{d=!0,r.cancel().catch(()=>{})},o)},p=()=>{g=!0,y()},w=()=>new Error(g?`响应停滞超过 ${Math.round(o/1e3)}s,已中止(可重试)`:`模型 ${Math.round(o/1e3)}s 内没有任何输出,已中止(可重试)`),b=()=>r.cancel().catch(()=>{});n?.addEventListener?.("abort",b,{once:!0});try{for(y();;){let t;try{t=await r.read()}catch(t){if(n?.aborted)throw u();if(d)throw w();throw t}if(n?.aborted)throw u();if(d)throw w();const{done:o,value:h}=t;if(o)break;s+=a.decode(h,{stream:!0});const g=s.split("\n");s=g.pop()??"";for(const t of g){const n=t.trim();if(!n.startsWith("data:"))continue;const o=n.slice(5).trim();if("[DONE]"===o||""===o)continue;let r;try{r=JSON.parse(o)}catch{continue}if(r.error){const t=r.error&&(r.error.message||r.error.msg||r.error);throw new Error("模型返回错误:"+("string"==typeof t?t:JSON.stringify(t)))}r.usage&&(m=r.usage,p());const a=r.choices?.[0];if(!a)continue;const s=a.delta||{},u=s.reasoning_content??s.reasoning;u&&(p(),e.onReasoning?.(u)),s.content&&(p(),c+=s.content,e.onContent?.(s.content)),Array.isArray(s.tool_calls)&&(p(),i(f,s.tool_calls)),a.finish_reason&&(p(),l=a.finish_reason)}}}finally{h&&clearTimeout(h),n?.removeEventListener?.("abort",b)}return{content:c,toolCalls:f.filter(Boolean),finishReason:l,usage:m}}export async function embed(t,e,n,r){const i=await fetch(`${t}/v1/embeddings`,{method:"POST",headers:o(e),body:JSON.stringify({model:n,input:Array.isArray(r)?r:[r]}),signal:AbortSignal.timeout(3e4)});if(!i.ok)throw s(await a(i));const c=await i.json();return Array.isArray(c?.data)?c.data.map(t=>t.embedding):null}export async function syncKnowledge(t,e,n,r){const i=await fetch(`${t}/v1/knowledge/${encodeURIComponent(n)}/sync`,{method:"POST",headers:o(e),body:JSON.stringify({files:r}),signal:AbortSignal.timeout(12e4)});if(!i.ok)throw s(await a(i));return i.json()}export async function pushDecisions(t,e,n,r){const i=await fetch(`${t}/v1/decisions/sync`,{method:"POST",headers:o(e),body:JSON.stringify({projectKey:n,nodes:r}),signal:AbortSignal.timeout(6e4)});if(!i.ok)throw s(await a(i));const c=await i.json();return Number(c?.accepted||0)}export async function pullDecisions(t,e,n,r){const i=new URLSearchParams({projectKey:n});r&&i.set("since",String(r));const c=await fetch(`${t}/v1/decisions?${i.toString()}`,{headers:o(e),signal:AbortSignal.timeout(6e4)});if(!c.ok)throw s(await a(c));const u=await c.json();return Array.isArray(u?.data)?u.data:[]}export async function listModels(t,e){const r=await fetch(`${t}/v1/models`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!r.ok)throw s(await a(r));const i=await r.json();return Array.isArray(i?.data)?i.data:[]}export async function getPlatformConfig(t,e){try{const r=await fetch(`${t}/v1/config`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!r.ok)return{};const a=await r.json();return a&&"object"==typeof a?a:{}}catch{return{}}}export async function getAgentPersona(t,e,r){const i=await fetch(`${t}/v1/agents/${encodeURIComponent(r)}/persona`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!i.ok)throw s(await a(i));return i.json()}export async function fetchUsage(t,e){const r=await fetch(`${t}/v1/usage`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!r.ok)throw s(await a(r));return r.json()}export async function extractDocument(t,e,n,r){const i=n.bytes||(n.data?Buffer.from(String(n.data),"base64"):null);if(i&&!1!==g.get(t))try{const{body:a,contentType:s}=function(t,e,n,o){const r="----huicai"+Date.now().toString(36)+Math.random().toString(36).slice(2,10),a=String(t||"file").replace(/[\r\n"]/g,"_"),s=[];s.push(Buffer.from(`--${r}\r\nContent-Disposition: form-data; name="file"; filename="${a}"\r\nContent-Type: application/octet-stream\r\n\r\n`)),s.push(e,Buffer.from("\r\n"));for(const[t,e]of[["maxChars",n],["offset",o]])null!=e&&s.push(Buffer.from(`--${r}\r\nContent-Disposition: form-data; name="${t}"\r\n\r\n${e}\r\n`));return s.push(Buffer.from(`--${r}--\r\n`)),{body:Buffer.concat(s),contentType:`multipart/form-data; boundary=${r}`}}(n.filename,i,n.maxChars,n.offset),c=h(await b(`${t}/v1/files/extract`,{...o(e),"content-type":s},a,r),!0);return g.set(t,!0),c}catch(e){if(!e||!e._noMultipart)throw e;g.set(t,!1)}const c=n.bytes?{...n,bytes:void 0,data:n.bytes.toString("base64")}:n,u=JSON.stringify(c);if("function"==typeof r){return h(await b(`${t}/v1/files/extract`,{...o(e)},Buffer.from(u),r),!1)}const f=await fetch(`${t}/v1/files/extract`,{method:"POST",headers:o(e),body:u,signal:AbortSignal.timeout(12e4)});if(!f.ok)throw s(await a(f));return h(await f.text(),!1)}function h(t,e){let n=null;try{n=JSON.parse(t)}catch{}if(n&&"string"==typeof n.text)return n;const o=n&&(n.message||n.msg||n.error?.message)||"解析服务返回异常响应",r=new Error(o);throw e&&(r._noMultipart=!0),r}const g=new Map;const y=12e4,p=25,w=9e5;async function b(t,e,n,o){const{request:a}=await import("http:"===new URL(t).protocol?"node:http":"node:https"),s={};for(const[t,n]of Object.entries(e||{}))s[String(t).toLowerCase()]=n;return s["content-type"]||(s["content-type"]="application/json"),s["content-length"]=n.length,new Promise((e,i)=>{const c=a(t,{method:"POST",headers:s,agent:!1},t=>{const n=[];t.on("data",t=>n.push(t)),t.on("error",i),t.on("end",()=>{const o=Buffer.concat(n).toString("utf8");t.statusCode>=200&&t.statusCode<300?e(o):415===t.statusCode||405===t.statusCode?i(Object.assign(new Error(r(o,`HTTP ${t.statusCode}`).message),{_noMultipart:!0})):i(new Error(r(o,`HTTP ${t.statusCode}`).message))})}),u=Math.min(w,y+Math.floor(n.length/p));c.setTimeout(u,()=>c.destroy(new Error(`网络传输/服务端处理超时(已等待 ${Math.round(u/1e3)} 秒无响应),请检查网络后重试`))),c.on("error",i);let f=0;const l=()=>{for(;f<n.length;){const t=Math.min(f+262144,n.length),e=c.write(n.subarray(f,t));f=t;try{o&&o(f,n.length)}catch{}if(!e)return void c.once("drain",l)}c.end()};l()})}export function startParseTicker(t,e,n=200){const o=Math.max(600,400+t/15360),r=Date.now(),a=setInterval(()=>{const t=Date.now()-r,n=Math.min(99,Math.round(100*(1-Math.exp(-t/o))));try{e(n,t)}catch{}},n);return()=>clearInterval(a)}export async function listPlatformSkills(t,e){const r=await fetch(`${t}/v1/skills`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!r.ok)throw s(await a(r));const i=await r.json();return Array.isArray(i?.data)?i.data:[]}export async function getPlatformSkill(t,e,r){const i=await fetch(`${t}/v1/skills/${encodeURIComponent(r)}`,{headers:o(e),signal:AbortSignal.timeout(n)});if(!i.ok)throw s(await a(i));return i.json()}export async function pushPlatformSkill(t,e,r){const i=await fetch(`${t}/v1/skills/import`,{method:"POST",headers:{...o(e),"Content-Type":"application/json"},body:JSON.stringify(r),signal:AbortSignal.timeout(n)});if(!i.ok)throw s(await a(i));return i.json()}
1
+ import{sanitizeMessagesForModel as t}from"./vision.js";import{isWebSearchOn as e}from"./config.js";const n=new Set([429,500,502,503,504,529]),o=15e3;function r(t){return{"Content-Type":"application/json",Authorization:`Bearer ${t}`}}function a(t,e){try{const e=JSON.parse(t),n=e?.error?.type||e?.type||"";if(e?.error?.message)return{message:e.error.message,type:n};if(e?.message)return{message:e.message,type:n};if(e?.msg)return{message:e.msg,type:n}}catch{}return{message:t||e,type:""}}async function s(t){let e="";try{e=await t.text()}catch{}return{...a(e,`HTTP ${t.status}`),status:t.status}}function i(t){return Object.assign(new Error(t.message),{status:t.status,type:t.type,nonRetryable:/upstream_(protocol|configuration)_error/.test(t.type||"")})}function c(t,e){return new Promise((n,o)=>{if(e?.aborted)return o(u());const r=setTimeout(()=>{e?.removeEventListener?.("abort",a),n()},t),a=()=>{clearTimeout(r),o(u())};e?.addEventListener?.("abort",a,{once:!0})})}function u(){const t=new Error("已中断");return t.name="AbortError",t}function f(t,e){const n=new AbortController;let o=!1;const r=()=>n.abort();t&&(t.aborted?n.abort():t.addEventListener("abort",r,{once:!0}));const a=setTimeout(()=>{o=!0,n.abort()},e);return{signal:n.signal,ms:e,get timedOut(){return o},done(){clearTimeout(a),t?.removeEventListener?.("abort",r)}}}function l(t){const e=String(t?.message||t||""),n=t?.cause?.code||t?.code||"";return"ECONNREFUSED"===n||/ECONNREFUSED|connection refused/i.test(e)?"无法连接模型服务(连接被拒绝):请检查网络或稍后重试。":"ENOTFOUND"===n||"EAI_AGAIN"===n||/getaddrinfo|ENOTFOUND|EAI_AGAIN/i.test(e)?"无法解析模型服务地址(DNS异常):请检查网络连接后重试。":"ETIMEDOUT"===n||/ETIMEDOUT|timeout|timed out/i.test(e)?"连接模型服务超时:网络较慢或服务繁忙,请稍后重试。":"ECONNRESET"===n||/ECONNRESET|socket hang up|network|fetch failed/i.test(e)?"与模型服务的连接中断:网络波动,请稍后重试。":"网络错误:"+e}const h=/ENOTFOUND|EAI_AGAIN|ECONNRESET|ETIMEDOUT|EPIPE|ENETUNREACH|EHOSTUNREACH|ENETDOWN|UND_ERR_SOCKET|UND_ERR_CONNECT_TIMEOUT|socket hang up/i;export function isTransientNetError(t){const e=String(t?.cause?.code||t?.code||""),n=`${t?.cause?.message||""} ${t?.message||""}`;return!/ECONNREFUSED/i.test(e+n)&&(h.test(e)||h.test(n))}const m=[2e3,5e3,1e4,2e4,3e4];function d(t,e){const n=e?.headers?.get?.("retry-after"),o=null!=n?Number(n):NaN;if(Number.isFinite(o)&&o>=0)return Math.min(1e3*o,3e4);const r=n?Date.parse(n):NaN;return Number.isFinite(r)?Math.max(0,Math.min(r-Date.now(),3e4)):Math.min(500*2**t+Math.floor(250*Math.random()),8e3)}export async function streamChat(o,a={},h){const y=`${o.baseUrl}/v1/chat/completions`,{messages:p}=t(o.messages,o.model),w={model:o.model,messages:p,stream:!0,stream_options:{include_usage:!0}};o.tools&&o.tools.length&&(w.tools=o.tools,w.tool_choice="auto"),"number"==typeof o.temperature&&(w.temperature=o.temperature),Number.isFinite(o.maxTokens)&&o.maxTokens>0&&(w.max_tokens=Math.floor(o.maxTokens)),o.reasoningEffort&&"off"!==o.reasoningEffort&&(w.reasoning_effort=o.reasoningEffort),o.extraBody&&"object"==typeof o.extraBody&&Object.assign(w,o.extraBody);const b=r(o.apiKey);o.client&&(b["X-Huicai-Client"]=o.client),o.sessionId&&(b["X-Huicai-Session"]=o.sessionId),o.aux&&(b["X-Huicai-Aux"]="1"),o.continuation&&(b["X-Huicai-Continue"]="1"),o.tailCount>0&&(b["X-Huicai-Tail"]=String(o.tailCount)),e()||(b["X-Huicai-Web-Search"]="off");const E=JSON.stringify(w),S=o.netRetryWaits||m;let N=0;for(let t=0;;t++){let e;const r=f(h,o.headersTimeoutMs??12e4);try{e=await fetch(y,{method:"POST",headers:b,body:E,signal:r.signal})}catch(e){if(h?.aborted)throw u();if(r.timedOut){const e=`等待模型服务响应超时(${Math.round(r.ms/1e3)}s内未返回响应头)`;if(t<2){const n=d(t,null);a.onRetry?.(t+1,n,e),await c(n,h);continue}throw new Error(e+",请稍后重试。")}if("AbortError"===e?.name)throw u();const n=isTransientNetError(e);if(n&&N<S.length){const t=S[N++];a.onRetry?.(N,t,l(e),{network:!0,max:S.length}),await c(t,h);continue}if(!n&&t<2){const n=d(t,null);a.onRetry?.(t+1,n,l(e)),await c(n,h);continue}const o=new Error(l(e));throw n&&(o.network=!0),o}finally{r.done()}if(!e.ok||!e.body){const o=await s(e);if(!("usage_limit_exceeded"===o.type)&&n.has(e.status)&&t<2){const n=d(t,e);a.onRetry?.(t+1,n,`HTTP ${e.status}:${o.message}`),await c(n,h);continue}throw i(o)}return g(e,a,h,o.stallTimeoutMs??18e4)}}async function g(t,e,n,o){const r=t.body.getReader(),a=new TextDecoder;let s="",c="";const f=[];let l,h=null,m=null,d=!1,g=!1,y=!1,p=!1;const w=()=>{p&&(p=!1,e.onReasoningEnd?.())},b=(t.headers.get("content-type")||"").toLowerCase(),E=/application\/(?:[\w.-]+\+)?json/.test(b),S=t=>Object.assign(new Error(t),{type:"upstream_protocol_error",nonRetryable:!0});if(!E&&!b.includes("text/event-stream"))throw await r.cancel().catch(()=>{}),S("模型服务未返回SSE/JSON,请检查渠道API地址和协议。");let N=[];const T=t=>{if(!t.trim())return;if("[DONE]"===t.trim())return w(),g=!0,void(y=!0);let n;try{n=JSON.parse(t)}catch{throw S("模型响应包含无效JSON,无法解析。")}if(!n||"object"!=typeof n)throw S("模型返回了无效响应。");if(n.error){const t="object"==typeof n.error?n.error:{message:String(n.error)};throw i({message:"模型返回错误:"+(t.message||t.msg||"未知错误"),type:t.type||"",status:t.code})}if(/^(message|content_block)/.test(n.type||""))throw S("渠道配置为OpenAI,但上游返回Anthropic事件,请检查渠道协议。");if(!Object.keys(n).length||"ping"===n.type)return;if(!Array.isArray(n.choices))throw S("模型响应不符合OpenAI Chat Completions格式,请检查渠道地址和协议。");n.usage&&(m=n.usage,v());const o=n.choices[0];if(!o)return;d=!0;const r=o.delta||o.message;if(!r||"object"!=typeof r)throw S("模型响应缺少delta/message。");const a=r.reasoning_content??r.reasoning;if(a&&(v(),p=!0,e.onReasoning?.(a)),null!=r.content&&"string"!=typeof r.content)throw S("模型正文不是字符串,响应格式不兼容。");r.content&&(w(),v(),c+=r.content,e.onContent?.(r.content)),Array.isArray(r.tool_calls)&&r.tool_calls.length&&(w(),v(),function(t,e){for(const n of e){const e="number"==typeof n.index?n.index:t.length;t[e]||(t[e]={id:"",type:"function",function:{name:"",arguments:""}}),n.id&&(t[e].id=n.id),n.type&&(t[e].type=n.type),n.function?.name&&(t[e].function.name+=n.function.name),n.function?.arguments&&(t[e].function.arguments+=n.function.arguments)}}(f,r.tool_calls)),r.huicai_anthropic&&(l=r.huicai_anthropic),o.finish_reason&&(w(),v(),h=o.finish_reason,g=!0)},O=t=>{if(!y){if(""===t)return N.length&&T(N.join("\n")),void(N=[]);if(t.startsWith("data:")&&(N.push(t.slice(5).replace(/^ /,"")),N.reduce((t,e)=>t+e.length,0)>8388608))throw S("模型SSE事件过大。")}},A=(t=!1)=>{for(;s.length;){const e=s.search(/[\r\n]/);if(e<0||!t&&e===s.length-1&&"\r"===s[e])break;const n="\r"===s[e]&&"\n"===s[e+1]?2:1;O(s.slice(0,e)),s=s.slice(e+n)}t&&(s&&O(s),s="",O(""))};let x=!1,C=null,$=!1;const k=()=>{C&&clearTimeout(C),C=setTimeout(()=>{x=!0,r.cancel().catch(()=>{})},o)},v=()=>{$=!0,k()},j=()=>new Error($?`响应停滞超过 ${Math.round(o/1e3)}s,已中止(可重试)`:`模型 ${Math.round(o/1e3)}s内没有任何输出,已中止(可重试)`),M=()=>r.cancel().catch(()=>{});n?.addEventListener?.("abort",M,{once:!0});try{for(k();;){let t;try{t=await r.read()}catch(t){if(n?.aborted)throw u();if(x)throw j();throw t}if(n?.aborted)throw u();if(x)throw j();const{done:e,value:o}=t;if(e)break;if(s+=a.decode(o,{stream:!0}),s.length>8388608)throw S("模型响应超过解析大小限制。");if(E||A(),y)break}if(s+=a.decode(),E?(T(s),g=!0):A(!0),!d)throw S("模型响应没有有效的对话事件,请检查渠道地址和协议。");if(!g)throw S("模型响应流提前结束,未收到结束事件。")}catch(t){throw(c||f.length)&&(t.partial=!0),t}finally{C&&clearTimeout(C),w(),await r.cancel().catch(()=>{}),n?.removeEventListener?.("abort",M)}return{content:c,toolCalls:f.filter(Boolean),finishReason:h,usage:m,...l?{nativeState:l}:{}}}export async function embed(t,e,n,o){const a=await fetch(`${t}/v1/embeddings`,{method:"POST",headers:r(e),body:JSON.stringify({model:n,input:Array.isArray(o)?o:[o]}),signal:AbortSignal.timeout(3e4)});if(!a.ok)throw i(await s(a));const c=await a.json();return Array.isArray(c?.data)?c.data.map(t=>t.embedding):null}export async function syncKnowledge(t,e,n,o){const a=await fetch(`${t}/v1/knowledge/${encodeURIComponent(n)}/sync`,{method:"POST",headers:r(e),body:JSON.stringify({files:o}),signal:AbortSignal.timeout(12e4)});if(!a.ok)throw i(await s(a));return a.json()}export async function pushDecisions(t,e,n,o){const a=await fetch(`${t}/v1/decisions/sync`,{method:"POST",headers:r(e),body:JSON.stringify({projectKey:n,nodes:o}),signal:AbortSignal.timeout(6e4)});if(!a.ok)throw i(await s(a));const c=await a.json();return Number(c?.accepted||0)}export async function pullDecisions(t,e,n,o){const a=new URLSearchParams({projectKey:n});o&&a.set("since",String(o));const c=await fetch(`${t}/v1/decisions?${a.toString()}`,{headers:r(e),signal:AbortSignal.timeout(6e4)});if(!c.ok)throw i(await s(c));const u=await c.json();return Array.isArray(u?.data)?u.data:[]}export async function listModels(t,e){const n=await fetch(`${t}/v1/models`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!n.ok)throw i(await s(n));const a=await n.json();return Array.isArray(a?.data)?a.data:[]}export async function getPlatformConfig(t,e,{timeoutMs:n=15e3}={}){try{const o=await fetch(`${t}/v1/config`,{headers:r(e),signal:AbortSignal.timeout(n)});if(!o.ok)return{};const a=await o.json();return a&&"object"==typeof a?a:{}}catch{return{}}}export async function webSearch(t,e,{query:n,count:o,client:a}={},c){const l=r(e);a&&(l["X-Huicai-Client"]=a);const h={query:String(n??"")};Number.isFinite(Number(o))&&Number(o)>0&&(h.count=Math.floor(Number(o)));const m=f(c,75e3);try{let e;try{e=await fetch(`${t}/v1/web/search`,{method:"POST",headers:l,body:JSON.stringify(h),signal:m.signal})}catch(t){if(c?.aborted)throw u();if(m.timedOut)throw new Error(`平台联网搜索超时(${Math.round(m.ms/1e3)}s内无响应)`);throw new Error("无法连接平台:"+(t?.cause?.code||t?.cause?.message||t?.message||String(t)))}if(!e.ok)throw i(await s(e));const n=await e.json();return n&&"object"==typeof n?n:{}}finally{m.done()}}export async function getAgentPersona(t,e,n){const a=await fetch(`${t}/v1/agents/${encodeURIComponent(n)}/persona`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!a.ok)throw i(await s(a));return a.json()}export async function fetchUsage(t,e){const n=await fetch(`${t}/v1/usage`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!n.ok)throw i(await s(n));return n.json()}export async function extractDocument(t,e,n,o){const a=n.bytes||(n.data?Buffer.from(String(n.data),"base64"):null);if(a&&!1!==p.get(t))try{const{body:s,contentType:i}=function(t,e,n,o){const r="----huicai"+Date.now().toString(36)+Math.random().toString(36).slice(2,10),a=String(t||"file").replace(/[\r\n"]/g,"_"),s=[];s.push(Buffer.from(`--${r}\r\nContent-Disposition: form-data; name="file"; filename="${a}"\r\nContent-Type: application/octet-stream\r\n\r\n`)),s.push(e,Buffer.from("\r\n"));for(const[t,e]of[["maxChars",n],["offset",o]])null!=e&&s.push(Buffer.from(`--${r}\r\nContent-Disposition: form-data; name="${t}"\r\n\r\n${e}\r\n`));return s.push(Buffer.from(`--${r}--\r\n`)),{body:Buffer.concat(s),contentType:`multipart/form-data; boundary=${r}`}}(n.filename,a,n.maxChars,n.offset),c=y(await S(`${t}/v1/files/extract`,{...r(e),"content-type":i},s,o),!0);return p.set(t,!0),c}catch(e){if(!e||!e._noMultipart)throw e;p.set(t,!1)}const c=n.bytes?{...n,bytes:void 0,data:n.bytes.toString("base64")}:n,u=JSON.stringify(c);if("function"==typeof o){return y(await S(`${t}/v1/files/extract`,{...r(e)},Buffer.from(u),o),!1)}const f=await fetch(`${t}/v1/files/extract`,{method:"POST",headers:r(e),body:u,signal:AbortSignal.timeout(12e4)});if(!f.ok)throw i(await s(f));return y(await f.text(),!1)}function y(t,e){let n=null;try{n=JSON.parse(t)}catch{}if(n&&"string"==typeof n.text)return n;const o=n&&(n.message||n.msg||n.error?.message)||"解析服务返回异常响应",r=new Error(o);throw e&&(r._noMultipart=!0),r}const p=new Map;const w=12e4,b=25,E=9e5;async function S(t,e,n,o){const{request:r}=await import("http:"===new URL(t).protocol?"node:http":"node:https"),s={};for(const[t,n]of Object.entries(e||{}))s[String(t).toLowerCase()]=n;return s["content-type"]||(s["content-type"]="application/json"),s["content-length"]=n.length,new Promise((e,i)=>{const c=r(t,{method:"POST",headers:s,agent:!1},t=>{const n=[];t.on("data",t=>n.push(t)),t.on("error",i),t.on("end",()=>{const o=Buffer.concat(n).toString("utf8");t.statusCode>=200&&t.statusCode<300?e(o):415===t.statusCode||405===t.statusCode?i(Object.assign(new Error(a(o,`HTTP ${t.statusCode}`).message),{_noMultipart:!0})):i(new Error(a(o,`HTTP ${t.statusCode}`).message))})}),u=Math.min(E,w+Math.floor(n.length/b));c.setTimeout(u,()=>c.destroy(new Error(`网络传输/服务端处理超时(已等待 ${Math.round(u/1e3)} 秒无响应),请检查网络后重试`))),c.on("error",i);let f=0;const l=()=>{for(;f<n.length;){const t=Math.min(f+262144,n.length),e=c.write(n.subarray(f,t));f=t;try{o&&o(f,n.length)}catch{}if(!e)return void c.once("drain",l)}c.end()};l()})}export function startParseTicker(t,e,n=200){const o=Math.max(600,400+t/15360),r=Date.now(),a=setInterval(()=>{const t=Date.now()-r,n=Math.min(99,Math.round(100*(1-Math.exp(-t/o))));try{e(n,t)}catch{}},n);return()=>clearInterval(a)}export async function listPlatformSkills(t,e){const n=await fetch(`${t}/v1/skills`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!n.ok)throw i(await s(n));const a=await n.json();return Array.isArray(a?.data)?a.data:[]}export async function getPlatformSkill(t,e,n){const a=await fetch(`${t}/v1/skills/${encodeURIComponent(n)}`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!a.ok)throw i(await s(a));return a.json()}export async function pushPlatformSkill(t,e,n){const a=await fetch(`${t}/v1/skills/import`,{method:"POST",headers:{...r(e),"Content-Type":"application/json"},body:JSON.stringify(n),signal:AbortSignal.timeout(o)});if(!a.ok)throw i(await s(a));return a.json()}export async function listNotifyChannels(t,e){const n=await fetch(`${t}/v1/notify/channels`,{headers:r(e),signal:AbortSignal.timeout(o)});if(!n.ok)throw i(await s(n));const a=await n.json();return Array.isArray(a?.data)?a.data:[]}export async function pushNotifyChannel(t,e,n,a,c){const u=await fetch(`${t}/v1/notify/push`,{method:"POST",headers:{...r(e),"Content-Type":"application/json"},body:JSON.stringify({channel_id:n,title:a,content:c}),signal:AbortSignal.timeout(o)});if(!u.ok)throw i(await s(u));const f=await u.json();return!!f?.delivered}
@@ -0,0 +1 @@
1
+ import{execFileSync as e}from"node:child_process";const t=[["merge",/^merge\b|^合并/i],["revert",/\brevert(ed|s|ing)?\b|回滚|还原|撤销/i],["hotfix",/\bhot[\s-]?fix\b|热修|紧急修复|线上修复/i],["refactor",/\b(refactor|cleanup|restructure|rename)\b|重构|整理|重命名|清理/i],["fix",/\b(fix|fixed|fixes|bug|patch)\b|修复|修正|解决|修掉/i],["feat",/\b(feat|feature|add|added|introduce|implement)\b|新增|增加|实现|引入|支持/i]],o=[["issue",/#\d+|[A-Z]{2,}-\d{1,6}/,"关联工单/议题"],["incident",/\b(incident|outage|p[0-3]\b|postmortem)\b|事故|故障|复盘|线上问题/i,"线上事故"],["perf",/\b(perf|performance|slow|latency|timeout|oom)\b|性能|超时|卡顿|内存/i,"性能问题"],["security",/\b(cve-\d|security|vuln|xss|sqli|injection)\b|漏洞|安全|注入/i,"安全问题"],["compat",/\b(compat|compatib|backward|legacy|workaround|polyfill)\b|兼容|历史遗留|绕过|临时方案/i,"兼容/权宜之计"],["revertOf",/\brevert(s|ed)?\s+["']?commit|回滚了|撤销了/i,"回滚了此前的改动"]];export function classifyCommit(e,o=""){const n=`${e||""}\n${o||""}`;for(const[e,o]of t)if(o.test(n))return e;return"other"}export function findClues(e,t=""){const n=`${e||""}\n${t||""}`,r=[];for(const[e,t,i]of o){const o=t.exec(n);o&&r.push({type:e,label:i,evidence:String(o[0]).slice(0,60)})}return r}function n(t,o){try{return((t,o)=>e("git",o,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:33554432}).trim())(t,o)}catch{return null}}export function excavate(e,t,o={}){const r=o.git||n,i=String(t?.file||"").trim();if(!i)return{ok:!1,error:"缺少文件路径"};if("true"!==r(e,["rev-parse","--is-inside-work-tree"]))return{ok:!1,error:"当前目录不是git仓库,无从考古"};const s=Math.max(1,Math.min(t?.max||40,200)),c=Number.isFinite(t?.from)&&Number.isFinite(t?.to),u=["%H","%an","%aI","%s","%b"].join("%x00"),a=r(e,c?["log",`--format=${u}%x01`,"--no-patch",`-L${t.from},${t.to}:${i}`,`-n${s}`]:["log",`--format=${u}%x01`,"--no-patch","--follow",`-n${s}`,"--",i]);if(null==a)return{ok:!1,error:`取不到 ${i} 的历史(路径不对,或它从未被提交过)`};const f=[];for(const e of a.split("")){const t=e.trim();if(!t)continue;const[o,n,r,i,s]=t.split("\0");o&&f.push({hash:o.slice(0,10),author:n||"",date:(r||"").slice(0,10),subject:(i||"").trim(),kind:classifyCommit(i,s),clues:findClues(i,s)})}if(!f.length)return{ok:!1,error:`${i} 没有提交历史`};const l=f[f.length-1],p=f[0],h=new Map;for(const t of f){const o=(r(e,["show","--name-only","--format=",t.hash])||"").split(/\r?\n/).map(e=>e.trim()).filter(Boolean);if(o.length&&!(o.length>30))for(const e of o)e!==i&&h.set(e,(h.get(e)||0)+1)}const m=[...h.entries()].filter(([,e])=>e>=2).sort((e,t)=>t[1]-e[1]).slice(0,8).map(([e,t])=>({file:e,times:t})),d=[],$=r(e,["blame","--line-porcelain","--",i]);if($){const e=new Map;for(const t of $.matchAll(/^author (.+)$/gm)){const o=t[1].trim();o&&"Not Committed Yet"!==o&&e.set(o,(e.get(o)||0)+1)}const t=[...e.values()].reduce((e,t)=>e+t,0)||1;d.push(...[...e.entries()].sort((e,t)=>t[1]-e[1]).slice(0,3).map(([e,o])=>({name:e,lines:o,share:Math.round(o/t*100)})))}const b={};for(const e of f)b[e.kind]=(b[e.kind]||0)+1;const g=[];for(const e of f)for(const t of e.clues)g.push({...t,commit:e.hash,subject:e.subject});return{ok:!0,file:i,ranged:c,commits:f.length,introduced:l,latest:p,timeline:f,companions:m,owners:d,kinds:b,clues:g}}export function formatReport(e){if(!e.ok)return`考古失败:${e.error}`;const t=[];t.push(`【${e.file}${e.ranged?" 指定行区间":""}】共 ${e.commits} 次相关提交`),t.push(""),t.push(`引入:${e.introduced.date} · ${e.introduced.author} · ${e.introduced.hash}`),t.push(` 「${e.introduced.subject}」`),t.push(`最近:${e.latest.date} · ${e.latest.author} · ${e.latest.hash}`),t.push(` 「${e.latest.subject}」`);const o=Object.entries(e.kinds).sort((e,t)=>t[1]-e[1]).map(([e,t])=>`${e}×${t}`).join("、");if(o&&t.push(`改动性质分布:${o}`),e.owners.length&&(t.push(""),t.push("当前归属(按存活行数):"+e.owners.map(e=>`${e.name} ${e.share}%`).join("、")),t.push(" —— 这是「现在谁的代码最多」,不等于「谁最懂」。要问人时它是个起点,不是结论。")),e.companions.length){t.push(""),t.push("常一起改的文件:");for(const o of e.companions)t.push(` · ${o.file}(同改 ${o.times} 次)`);t.push(" —— 反复同改通常意味着两者之间有隐式耦合。改这个文件时,多半也得看看它们。")}if(e.clues.length){t.push(""),t.push("意图线索(提交信息里留下的痕迹):");const o=new Set;for(const n of e.clues){const e=n.type+n.evidence;o.has(e)||(o.add(e),t.push(` · [${n.label}] ${n.evidence} —— ${n.commit}「${n.subject}」`))}}t.push(""),t.push("时间线(近 → 远):");for(const o of e.timeline.slice(0,15))t.push(` ${o.date} ${o.hash} [${o.kind}] ${o.author}:${o.subject}`);return e.timeline.length>15&&t.push(` …(另有 ${e.timeline.length-15} 次更早的提交)`),t.push(""),t.push("以上全部是**史料**(git里确凿记录的事实)。「这段代码为什么存在」需要你据此推断——"),t.push("下结论时请把**推断的部分明确标出来**:把推测写成事实,会让后来的人拿着一个编出来的理由去做决定,"),t.push("那比不知道更糟。线索不足时,如实说「历史里看不出来,建议问XXX」。"),t.join("\n")}export function makeArchaeologyTool({cwd:e}){return[{readOnly:!0,timeoutMs:12e4,spec:{type:"function",function:{name:"code_archaeology",description:"从git历史重建「这段代码为什么存在」:引入提交、后续改动性质、常一起改的文件、当前归属、以及提交信息里留下的意图线索(工单号、事故、性能、安全、兼容)。\n什么时候用:看到一段看不懂的判断/兼容代码/魔数;决定要不要删一段历史遗留;接手陌生模块想知道该问谁;用户问「这为什么在这儿」「这还需要吗」。\n**它只给史料,不给结论**——「为什么」要你看过史料之后自己推断,并把推断的部分标出来。",parameters:{type:"object",properties:{file:{type:"string",description:"文件路径(相对仓库根)"},from:{type:"number",description:"(可选)起始行号,与to一起给则只考古这一段"},to:{type:"number",description:"(可选)结束行号"}},required:["file"]}}},run:async t=>formatReport(excavate(e,{file:t?.file,from:t?.from,to:t?.to}))}]}export function toDecisionNode(e,t={}){if(!e?.ok)return null;const o=String(t.why||"").trim();return{title:`[考古] ${e.file}`,what:`${e.introduced.date} 由 ${e.introduced.author} 引入(${e.introduced.hash}):${e.introduced.subject}`,why:o?!1===t.inferred?o:`(据历史推断,非当时记录)${o}`:"(未给出结论:史料已附,为什么留待判断)",affects:[e.file,...e.companions.map(e=>e.file)],tags:["archaeology",...Object.keys(e.kinds)].slice(0,12),origin:"archaeology",confidence:o?.5:.2}}
package/src/ask-ui.js ADDED
@@ -0,0 +1 @@
1
+ export const ASK_KINDS={single_choice:{desc:"从若干候选里选一个。给方案、给路线、二选一以上的决策用它。",needsOptions:!0,answer:"被选中的value(字符串)"},multi_choice:{desc:"从若干候选里选任意个(可一个不选)。圈定要处理的文件/字段/行用它。",needsOptions:!0,answer:"value数组(可为空数组)"},confirm:{desc:"一句话的是否确认。注意:破坏性操作的授权走权限审批,不要用它替代。",needsOptions:!1,answer:"true / false"},text:{desc:"要一段自由文本(命名、原因、补充说明)。",needsOptions:!1,answer:"用户输入的字符串"},date_range:{desc:"要一个起止日期区间。",needsOptions:!1,answer:"{ start, end } 两个YYYY-MM-DD字符串"}};const e=e=>null!==e&&"object"==typeof e&&!Array.isArray(e),n=(e,n=300)=>String(null==e?"":e).trim().slice(0,n);export function validateAsk(t){const s=[];if(!e(t))return{ok:!1,errors:["ask必须是对象"]};const i=String(t.kind||"").trim();if(!ASK_KINDS[i])return s.push(`kind必须是以下之一:${Object.keys(ASK_KINDS).join(" / ")}(当前:${i||"(空)"})`),{ok:!1,errors:s};const r=n(t.question);r||s.push("question不能为空");let o=[];if(ASK_KINDS[i].needsOptions){const r=Array.isArray(t.options)?t.options:[];r.length?r.length>20&&s.push(`options最多 20 项(当前 ${r.length})——太多的选择等于没有选择,请先归并`):s.push(`${i} 必须给options(至少一项)`);const l=new Set;for(const[t,i]of r.slice(0,20).entries()){const r=e(i)?n(i.value,80):n(i,80),a=e(i)&&n(i.label)||r;r?l.has(r)?s.push(`options[${t}] 的value「${r}」重复`):(l.add(r),o.push({value:r,label:a,hint:e(i)?n(i.hint):""})):s.push(`options[${t}] 缺少value`)}}else Array.isArray(t.options)&&t.options.length&&s.push(`${i} 不接受options`);return s.length?{ok:!1,errors:s}:{ok:!0,errors:[],ask:{kind:i,question:r,options:o,note:n(t.note),defaultValue:t.default}}}export function renderAskText(e){const n=[e.question];switch(e.note&&n.push(e.note),e.options.length&&e.options.forEach((e,t)=>{n.push(` ${t+1}. ${e.label}${e.hint?`(${e.hint})`:""}`)}),e.kind){case"single_choice":n.push("请输入编号;或直接用文字说明你的想法。");break;case"multi_choice":n.push("请输入编号(多选用逗号分隔,如 1,3);不选任何一项输入 0;或直接用文字说明。");break;case"confirm":n.push("请输入y(是)/ n(否);或直接用文字说明。");break;case"date_range":n.push("请输入起止日期,如 2026-01-01 ~ 2026-03-31;或直接用文字说明。");break;default:n.push("请直接输入内容。")}return n.join("\n")}const t=/(\d{4})-(\d{1,2})-(\d{1,2})/g;export function parseAskAnswer(e,n){const s=String(null==n?"":n).trim();if(!s)return{value:null,freeText:""};const i=n=>n>=1&&n<=e.options.length?e.options[n-1].value:null;switch(e.kind){case"single_choice":{if(/^\d+$/.test(s)){const e=i(Number(s));if(null!==e)return{value:e}}const n=e.options.find(e=>e.value===s||e.label===s);return n?{value:n.value}:{value:null,freeText:s}}case"multi_choice":{if(/^0$/.test(s))return{value:[]};if(/^[\d\s,,、]+$/.test(s)){const e=[];for(const n of s.split(/[\s,,、]+/).filter(Boolean)){const t=i(Number(n));null===t||e.includes(t)||e.push(t)}if(e.length)return{value:e}}const n=e.options.filter(e=>s.includes(e.label)||s.includes(e.value));return n.length?{value:n.map(e=>e.value)}:{value:null,freeText:s}}case"confirm":return/^(y|yes|是|好|确认|同意|ok)$/i.test(s)?{value:!0}:/^(n|no|否|不|取消|拒绝)$/i.test(s)?{value:!1}:{value:null,freeText:s};case"date_range":{const e=[...s.matchAll(t)].map(e=>`${e[1]}-${String(e[2]).padStart(2,"0")}-${String(e[3]).padStart(2,"0")}`);return e.length>=2?{value:{start:e[0],end:e[1]}}:{value:null,freeText:s}}default:return{value:s}}}export function formatAnswer(e,n){if(!n||null==n.value&&!n.freeText)return"用户没有作出选择(可能是超时或直接关闭)。请不要替用户拿主意:把当前进展如实说明,并请用户明确指示后再继续。";if(n.freeText)return`用户没有按选项作答,而是直接说明:\n${n.freeText}\n\n请按用户这句话的实际意思继续——不要硬套回原来的选项。`;const t=n.value;if("multi_choice"===e.kind){const n=t.map(n=>e.options.find(e=>e.value===n)?.label||n);return t.length?`用户选择了:${n.join("、")}\n结构化答案(values):${JSON.stringify(t)}`:"用户一项都没有选择。请据此调整方案,或询问是否换一组候选。"}if("single_choice"===e.kind){return`用户选择了:${e.options.find(e=>e.value===t)?.label||t}\n结构化答案(value):${JSON.stringify(t)}`}return"confirm"===e.kind?`用户${t?"确认(是)":"否定(否)"}。`:"date_range"===e.kind?`用户给出的区间:${t.start} ~ ${t.end}`:`用户输入:${t}`}export function makeAskUiTool({ask:n=null,askUi:t=null}={}){return{readOnly:!0,timeoutMs:6e5,spec:{type:"function",function:{name:"ask_ui",description:`向用户提一个**结构化问题**并等待回答:从候选里选一个/选多个、确认是否、要一段文本、要一个日期区间。\n什么时候用它:方案有多条合理路线需要用户拍板、要处理的范围需要用户圈定、关键参数你只能猜时。**不要替用户拿主意然后一路做下去**——返工的代价远高于问一句。\n让用户选择方案时必须用 single_choice 或 multi_choice 并提供 options(label 和 hint 说明取舍),由终端展示可选择的视图;不要在普通回复或 text 问题里要求用户输入编号、方案名称或“回复 A/B”。\n什么时候不要用它:答案能从代码/文件里查到(自己去查);破坏性操作的授权(那走权限审批,不是提问);一轮里连问好几个(合并成一个多选,或分步问)。\n可用的kind:\n ${Object.entries(ASK_KINDS).map(([e,n])=>`${e}(${n.desc} 回传:${n.answer})`).join("\n ")}`,parameters:{type:"object",properties:{kind:{type:"string",description:`提问类型:${Object.keys(ASK_KINDS).join(" / ")}`},question:{type:"string",description:"问题正文,一句话说清要用户定什么"},options:{type:"array",description:"single_choice / multi_choice的候选;每项 {value, label, hint?}。value是回传标识,label给用户看",items:{type:"object",properties:{value:{type:"string",description:"回传标识(英文短标识最省事)"},label:{type:"string",description:"给用户看的名称"},hint:{type:"string",description:"(可选)一句取舍说明,帮用户判断"}},required:["value"]}},note:{type:"string",description:"(可选)补充背景,如各选项的共同前提"}},required:["kind","question"]}}},run:async s=>{const i=validateAsk(s);if(!i.ok)return"提问描述不合法,未向用户展示:\n"+i.errors.map(e=>" · "+e).join("\n")+"\n\n修正后重试;也可以直接在回答里把问题问出来(那样拿不到结构化答案,但不会卡住)。";const r=i.ask;if("function"==typeof t)try{const n=await t(r);return formatAnswer(r,e(n)&&("value"in n||"freeText"in n)?n:parseAskAnswer(r,n))}catch(e){}if("function"!=typeof n)return"当前环境无法向用户提问(非交互运行)。请不要假设用户的选择:要么选一个明确保守、可撤销的默认做法并在最终答复里写清你替用户做了什么假设,要么停下来把待定事项列出来交给用户。";return formatAnswer(r,parseAskAnswer(r,await n(renderAskText(r)+"\n> ")))}}}
@@ -1 +1 @@
1
- import e from"node:fs";import s from"node:path";import t from"node:os";import{parseFrontmatter as i}from"./skills.js";export const EXTERNAL_SOURCES=[{cli:"Claude Code",agents:".claude/agents",skills:".claude/skills"},{cli:"Codex",agents:".codex/agents",skills:".codex/skills"},{cli:"Gemini CLI",agents:".gemini/agents",skills:".gemini/skills"},{cli:"Cursor",agents:".cursor/agents",skills:".cursor/skills"}];const n=new Set([".md",".txt",".py",".js",".ts",".sh",".json",".yaml",".yml",".csv",".sql",".toml",".ini"]),r=/^[a-zA-Z0-9_-]{1,64}$/;function c(){return s.join(t.homedir(),".huicai","agents")}function o(){return s.join(t.homedir(),".huicai","skills")}export function discoverExternalAssets({base:n=t.homedir()}={}){const l=function(){try{return new Set(e.readdirSync(c()).filter(e=>e.endsWith(".md")).map(e=>e.replace(/\.md$/,"")))}catch{return new Set}}(),a=function(){try{return new Set(e.readdirSync(o(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name))}catch{return new Set}}(),m=[],d=[],p=new Set,u=new Set;for(const t of EXTERNAL_SOURCES){const c=s.join(n,t.agents);let o=[];try{o=e.readdirSync(c).filter(e=>e.endsWith(".md"))}catch{}for(const n of o){const o=s.join(c,n);let a;try{a=i(e.readFileSync(o,"utf8"))}catch{continue}const d=String(a.name||n.replace(/\.md$/,"")).trim();r.test(d)&&!p.has(d)&&(p.add(d),m.push({name:d,description:(a.description||"").slice(0,200),cli:t.cli,src:o,exists:l.has(d)}))}const f=s.join(n,t.skills);let h=[];try{h=e.readdirSync(f,{withFileTypes:!0}).filter(e=>e.isDirectory())}catch{}for(const n of h){const c=n.name.trim(),o=s.join(f,c),l=s.join(o,"SKILL.md");if(!r.test(c)||u.has(c)||!e.existsSync(l))continue;u.add(c);let m="";try{m=(i(e.readFileSync(l,"utf8")).description||"").slice(0,200)}catch{}d.push({name:c,description:m,cli:t.cli,src:o,exists:a.has(c)})}}return{agents:m,skills:d}}function l(t,i,r=0){if(r>3)return 0;let c=0,o=[];try{o=e.readdirSync(t,{withFileTypes:!0})}catch{return 0}e.mkdirSync(i,{recursive:!0});for(const a of o){const o=s.join(t,a.name),m=s.join(i,a.name);if(a.isDirectory())c+=l(o,m,r+1);else if(a.isFile()&&("SKILL.md"===a.name||n.has(s.extname(a.name).toLowerCase())))try{e.copyFileSync(o,m),c++}catch{}}return c}export function importExternalAssets(t={},{overwrite:i=!1}={}){const n=[],a={agents:{imported:0,skipped:0},skills:{imported:0,skipped:0},details:n},m=c(),d=o();for(const c of t.agents||[]){if(!c||!r.test(String(c.name))||!c.src)continue;const t=s.join(m,c.name+".md");if(i||!e.existsSync(t))try{e.mkdirSync(m,{recursive:!0}),e.copyFileSync(c.src,t),a.agents.imported++,n.push(`导入 agent:${c.name}`)}catch(e){n.push(`失败 agent ${c.name}:${e.message}`)}else a.agents.skipped++,n.push(`跳过 agent(已存在):${c.name}`)}for(const c of t.skills||[]){if(!c||!r.test(String(c.name))||!c.src)continue;const t=s.join(d,c.name);if(!i&&e.existsSync(t)){a.skills.skipped++,n.push(`跳过 skill(已存在):${c.name}`);continue}const o=l(c.src,t);o>0?(a.skills.imported++,n.push(`导入 skill:${c.name}(${o} 文件)`)):(a.skills.skipped++,n.push(`跳过 skill(无内容):${c.name}`))}return a}export function importAllExternalAssets(e={}){const s=discoverExternalAssets(e);return importExternalAssets({agents:s.agents.filter(e=>!e.exists),skills:s.skills.filter(e=>!e.exists)},e)}
1
+ import e from"node:fs";import s from"node:path";import t from"node:os";import{parseFrontmatter as i,countUserSkills as n}from"./skills.js";import{checkLimit as r,currentPlan as o,remainingOf as c}from"./plan-limits.js";export const EXTERNAL_SOURCES=[{cli:"Claude Code",agents:".claude/agents",skills:".claude/skills"},{cli:"Codex",agents:".codex/agents",skills:".codex/skills"},{cli:"Gemini CLI",agents:".gemini/agents",skills:".gemini/skills"},{cli:"Cursor",agents:".cursor/agents",skills:".cursor/skills"}];const l=new Set([".md",".txt",".py",".js",".ts",".sh",".json",".yaml",".yml",".csv",".sql",".toml",".ini"]),a=/^[a-zA-Z0-9_-]{1,64}$/;function m(){return s.join(t.homedir(),".huicai","agents")}function d(){return s.join(t.homedir(),".huicai","skills")}function p(){try{return new Set(e.readdirSync(m()).filter(e=>e.endsWith(".md")).map(e=>e.replace(/\.md$/,"")))}catch{return new Set}}export function discoverExternalAssets({base:n=t.homedir()}={}){const r=p(),o=function(){try{return new Set(e.readdirSync(d(),{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name))}catch{return new Set}}(),c=[],l=[],m=new Set,u=new Set;for(const t of EXTERNAL_SOURCES){const d=s.join(n,t.agents);let p=[];try{p=e.readdirSync(d).filter(e=>e.endsWith(".md"))}catch{}for(const n of p){const o=s.join(d,n);let l;try{l=i(e.readFileSync(o,"utf8"))}catch{continue}const p=String(l.name||n.replace(/\.md$/,"")).trim();a.test(p)&&!m.has(p)&&(m.add(p),c.push({name:p,description:(l.description||"").slice(0,200),cli:t.cli,src:o,exists:r.has(p)}))}const f=s.join(n,t.skills);let h=[];try{h=e.readdirSync(f,{withFileTypes:!0}).filter(e=>e.isDirectory())}catch{}for(const n of h){const r=n.name.trim(),c=s.join(f,r),m=s.join(c,"SKILL.md");if(!a.test(r)||u.has(r)||!e.existsSync(m))continue;u.add(r);let d="";try{d=(i(e.readFileSync(m,"utf8")).description||"").slice(0,200)}catch{}l.push({name:r,description:d,cli:t.cli,src:c,exists:o.has(r)})}}return{agents:c,skills:l}}function u(t,i,n=0){if(n>3)return 0;let r=0,o=[];try{o=e.readdirSync(t,{withFileTypes:!0})}catch{return 0}e.mkdirSync(i,{recursive:!0});for(const c of o){const o=s.join(t,c.name),a=s.join(i,c.name);if(c.isDirectory())r+=u(o,a,n+1);else if(c.isFile()&&("SKILL.md"===c.name||l.has(s.extname(c.name).toLowerCase())))try{e.copyFileSync(o,a),r++}catch{}}return r}export function importExternalAssets(t={},{overwrite:i=!1}={}){const l=[],f=[],h={agents:{imported:0,skipped:0},skills:{imported:0,skipped:0},details:l,limited:f},k=m(),y=d(),g=o(),S=p().size,x=n();let j=c("agents",S,g),$=c("skills",x,g);const w=(e,s)=>{const t=r(e,s,g)?.message;t&&!f.includes(t)&&f.push(t)};for(const n of t.agents||[]){if(!n||!a.test(String(n.name))||!n.src)continue;const t=s.join(k,n.name+".md"),r=e.existsSync(t);if(i||!r)if(!r&&j<=0)w("agents",S+h.agents.imported),h.agents.skipped++,l.push(`跳过agent(已达会员上限):${n.name}`);else try{e.mkdirSync(k,{recursive:!0}),e.copyFileSync(n.src,t),h.agents.imported++,r||j--,l.push(`导入agent:${n.name}`)}catch(e){l.push(`失败agent ${n.name}:${e.message}`)}else h.agents.skipped++,l.push(`跳过agent(已存在):${n.name}`)}for(const n of t.skills||[]){if(!n||!a.test(String(n.name))||!n.src)continue;const t=s.join(y,n.name),r=e.existsSync(s.join(t,"SKILL.md"));if(!i&&e.existsSync(t)){h.skills.skipped++,l.push(`跳过skill(已存在):${n.name}`);continue}if(!r&&$<=0){w("skills",x+h.skills.imported),h.skills.skipped++,l.push(`跳过skill(已达会员上限):${n.name}`);continue}const o=u(n.src,t);o>0?(h.skills.imported++,r||$--,l.push(`导入skill:${n.name}(${o} 文件)`)):(h.skills.skipped++,l.push(`跳过skill(无内容):${n.name}`))}return h}export function importAllExternalAssets(e={}){const s=discoverExternalAssets(e);return importExternalAssets({agents:s.agents.filter(e=>!e.exists),skills:s.skills.filter(e=>!e.exists)},e)}
package/src/autofix.js CHANGED
@@ -1 +1 @@
1
- import{spawn as t}from"node:child_process";import{StringDecoder as e}from"node:string_decoder";import r from"node:fs";import n from"node:path";const o=16e3;export function captureCommand(r,n,s=18e4){const i="win32"===process.platform;return new Promise(c=>{let d="",a=!1;const p=i?t(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",n],{cwd:r,windowsVerbatimArguments:!0}):t(process.env.SHELL||"/bin/sh",["-c",n],{cwd:r,detached:!0}),u=setTimeout(()=>{a=!0;try{!i&&p.pid?process.kill(-p.pid,"SIGKILL"):p.kill("SIGKILL")}catch{}},s),m=new e("utf8"),l=new e("utf8"),f=t=>{d.length<32e3&&(d+=t)};p.stdout.on("data",t=>f(m.write(t))),p.stderr.on("data",t=>f(l.write(t))),p.on("error",t=>{clearTimeout(u),c({code:-1,output:"命令启动失败:"+t.message})}),p.on("close",t=>{var e;clearTimeout(u),f(m.end()),f(l.end()),c({code:a?124:null==t?1:t,output:(e=d,((e=String(e)).length>o?e.slice(0,o)+"\n…(输出过长已截断)":e)||"(无输出)")})})})}export function detectVerifyCommand(t){const e=e=>{try{return r.existsSync(n.join(t,e))}catch{return!1}};try{if(e("package.json")){const e=JSON.parse(r.readFileSync(n.join(t,"package.json"),"utf8"));if(e.scripts?.test&&!/no test specified/i.test(e.scripts.test))return"npm test";if(e.scripts?.build)return"npm run build"}}catch{}return e("pyproject.toml")||e("pytest.ini")||e("tox.ini")?"pytest -q":e("pom.xml")?"mvn -q test":e("build.gradle")||e("build.gradle.kts")?"./gradlew test":e("Cargo.toml")?"cargo test":e("go.mod")?"go test ./...":e("Makefile")?"make test":null}export async function runVerifyLoop({command:t,maxIters:e=5,cwd:r,runTurnOnce:n,emit:o}){for(let s=0;s<e;s++){o(`▶ 验证 ${s+1}/${e}:${t}`);const{code:i,output:c}=await captureCommand(r,t);if(0===i)return o(`✅ 验证通过(第 ${s+1} 轮)。`),{passed:!0,iters:s+1};if(o(`✗ 未通过(退出码 ${i})`+(s<e-1?",让智能体定位修复后自动重跑…":"")),s===e-1)return o(`⚠ 已达上限 ${e} 轮仍未通过。`),{passed:!1,iters:s+1,output:c};const d=await n(`自动验证:运行 \`${t}\` 失败(退出码 ${i})。输出如下:\n\n${c}\n\n请定位根因并**修复代码/配置**。不要自己重跑该命令——我会自动重跑验证。本轮只做必要的修复。`);if(d&&d.aborted)return o("已中断修复循环。"),{passed:!1,aborted:!0,iters:s+1}}return{passed:!1}}
1
+ import{spawn as e}from"node:child_process";import{StringDecoder as t}from"node:string_decoder";import r from"node:fs";import o from"node:path";const n=16e3;export function captureCommand(r,o,s=18e4,i){if(i?.aborted)return Promise.resolve({code:130,output:"已停止验证。",aborted:!0});const d="win32"===process.platform;return new Promise(a=>{let c="",p=!1;const u=d?e(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",o],{cwd:r,windowsVerbatimArguments:!0}):e(process.env.SHELL||"/bin/sh",["-c",o],{cwd:r,detached:!0}),m=()=>{p=!0;try{!d&&u.pid?process.kill(-u.pid,"SIGKILL"):u.pid&&e("taskkill",["/pid",String(u.pid),"/T","/F"],{stdio:"ignore"}).on("error",()=>u.kill())}catch{}},l=setTimeout(m,s);i?.addEventListener("abort",m,{once:!0}),i?.aborted&&m();const f=()=>{clearTimeout(l),i?.removeEventListener("abort",m)},b=new t("utf8"),g=new t("utf8"),w=e=>{c.length<32e3&&(c+=e)};u.stdout.on("data",e=>w(b.write(e))),u.stderr.on("data",e=>w(g.write(e))),u.on("error",e=>{f(),a({code:-1,output:"命令启动失败:"+e.message,aborted:!!i?.aborted})}),u.on("close",e=>{var t;f(),w(b.end()),w(g.end()),a({code:i?.aborted?130:p?124:null==e?1:e,output:(t=c,((t=String(t)).length>n?t.slice(0,n)+"\n…(输出过长已截断)":t)||"(无输出)"),aborted:!!i?.aborted})})})}export function detectVerifyCommand(e){const t=t=>{try{return r.existsSync(o.join(e,t))}catch{return!1}};try{if(t("package.json")){const t=JSON.parse(r.readFileSync(o.join(e,"package.json"),"utf8"));if(t.scripts?.test&&!/no test specified/i.test(t.scripts.test))return"npm test";if(t.scripts?.build)return"npm run build"}}catch{}return t("pyproject.toml")||t("pytest.ini")||t("tox.ini")?"pytest -q":t("pom.xml")?"mvn -q test":t("build.gradle")||t("build.gradle.kts")?"./gradlew test":t("Cargo.toml")?"cargo test":t("go.mod")?"go test ./...":t("Makefile")?"make test":null}export async function runVerifyLoop({command:e,maxIters:t=5,cwd:r,runTurnOnce:o,emit:n,signal:s}){for(let i=0;i<t;i++){if(s?.aborted)return{passed:!1,aborted:!0,iters:i};n(`▶ 验证 ${i+1}/${t}:${e}`);const{code:d,output:a,aborted:c}=await captureCommand(r,e,18e4,s);if(c||s?.aborted)return{passed:!1,aborted:!0,iters:i+1};if(0===d)return n(`✅ 验证通过(第 ${i+1} 轮)。`),{passed:!0,iters:i+1};if(n(`✗ 未通过(退出码 ${d})`+(i<t-1?",让智能体定位修复后自动重跑…":"")),i===t-1)return n(`⚠ 已达上限 ${t} 轮仍未通过。`),{passed:!1,iters:i+1,output:a};const p=await o(`自动验证:运行 \`${e}\` 失败(退出码 ${d})。输出如下:\n\n${a}\n\n请定位根因并**修复代码/配置**。不要自己重跑该命令——我会自动重跑验证。本轮只做必要的修复。`);if(s?.aborted||p?.aborted)return n("已中断修复循环。"),{passed:!1,aborted:!0,iters:i+1};if(p?.error)return{passed:!1,iters:i+1,output:String(p.error)}}return{passed:!1}}
@@ -1 +1 @@
1
- import*as e from"./schedule.js";import{isValidCron as r}from"./cron.js";import{describeSchedule as n}from"./schedule-freq.js";const t=(e,r,n={},t=[])=>({type:"function",function:{name:e,description:r,parameters:{type:"object",properties:n,required:t}}}),i=e=>({type:"string",description:e}),o=e=>String(e).padStart(2,"0");export function parseWhen(e,r=new Date){const n=String(e||"").trim();if(!n)return{error:"缺少时间"};if(/^(\S+\s+){4}\S+$/.test(n))return{cron:n};let t=/^每\s*隔?\s*(\d+)\s*(分钟?|min|m)$/i.exec(n);if(t){const e=Math.min(59,Math.max(1,Number(t[1])));return{cron:`*/${e} * * * *`,freq:{kind:"interval",every:e,unit:"minute"}}}if(t=/^每\s*隔?\s*(\d+)\s*(小时|时|h)$/i.exec(n),t){const e=Math.min(23,Math.max(1,Number(t[1])));return{cron:`0 */${e} * * *`,freq:{kind:"interval",every:e,unit:"hour"}}}const i=/^\+\s*(\d+)\s*(m|min|分钟?|h|小时|时)?$/i.exec(n);if(i){const e=Number(i[1]),n=(i[2]||"m").toLowerCase(),t=new Date(r.getTime()+6e4*(/h|小时|时/.test(n)?60*e:e));return{cron:`${t.getMinutes()} ${t.getHours()} ${t.getDate()} ${t.getMonth()+1} *`,once:!0,freq:{kind:"once",date:`${t.getFullYear()}-${o(t.getMonth()+1)}-${o(t.getDate())}`,time:`${o(t.getHours())}:${o(t.getMinutes())}`}}}const s=/^每\s*(周|星期)\s*([一二三四五六日天1-7])\s*(?:的?\s*(\d{1,2}):(\d{2}))?$/.exec(n);if(s){const e={"日":0,"天":0,"一":1,"二":2,"三":3,"四":4,"五":5,"六":6,7:0},r=null!=e[s[2]]?e[s[2]]:Number(s[2])%7,n=null!=s[3]?Number(s[3]):9,t=null!=s[4]?Number(s[4]):0;return n>23||t>59?{error:"无效时间"}:{cron:`${t} ${n} * * ${r}`,freq:{kind:"periodic",period:"weekly",weekday:r,time:`${o(n)}:${o(t)}`}}}const a=/^(?:每天\s*)?(\d{1,2}):(\d{2})$/.exec(n);if(a){const e=Number(a[1]),r=Number(a[2]);return e>23||r>59?{error:"无效时间"}:{cron:`${r} ${e} * * *`,freq:{kind:"periodic",period:"daily",time:`${o(e)}:${o(r)}`}}}return/每天|daily/i.test(n)?{cron:"0 9 * * *",freq:{kind:"periodic",period:"daily",time:"09:00"}}:/工作日|weekday/i.test(n)?{cron:"0 9 * * 1-5"}:{error:"无法解析时间(用 +30m / 14:30 / 每隔5分钟 / 每天 / 工作日 / 每周一 / cron 五段)"}}export function toCron(e,r=new Date){const n=parseWhen(e,r);return n.error?{error:n.error}:n.once?{cron:n.cron,once:!0}:{cron:n.cron}}export function resolveTask(e,{id:r,name:n,match:t}={}){const i=e&&e()||[];if(r){const e=i.find(e=>e.id===r);if(e)return e}const o=String(n||t||r||"").trim().toLowerCase();return o&&(i.find(e=>String(e.name||"").toLowerCase()===o)||i.find(e=>String(e.name||"").toLowerCase().includes(o))||i.find(e=>String(e.prompt||"").toLowerCase().includes(o)))||null}export function makeAutomationTools({cwd:o,deps:s={},selfSchedule:a=!0,runnerHint:c=""}={}){const u=s.addTask||e.addTask,m=s.listTasks||e.listTasks,d=s.updateTask||e.updateTask,l=s.removeTask||e.removeTask,p=s.setEnabled||e.setEnabled,f=s.isValidCron||r,$=c||"到点由调度器在该目录自动执行(桌面端需保持应用运行;终端/IDE 需有 `huicai daemon` 在跑)。",h=[];a&&h.push({readOnly:!1,spec:t("schedule_followup","给自己安排一个后续任务,到点由系统在同一项目目录自动继续执行(无需用户在场,也无需你还开着)。适合「稍后重试」「等 CI/构建完成后检查」「每天定时汇总」这类需要延迟或周期执行的工作。时间写法:+30m、+2h、14:30、每隔5分钟、每天、工作日、每周一、或标准 cron 五段。",{when:i("触发时间:+30m / +2h / 14:30 / 每隔5分钟 / 每天 / 工作日 / 每周一 / cron 五段"),task:i("到点要执行的任务说明(必须独立自足——届时是全新会话,看不到当前对话)"),name:i("(可选)给这个自动化起个简短名字")},["when","task"]),run:async e=>{const{cron:r,freq:n,once:t,error:i}=parseWhen(e?.when);if(i)return"排期失败:"+i;if(!f(r))return"排期失败:生成的 cron 无效("+r+")。";const s=String(e?.task||"").trim();if(!s)return"排期失败:任务说明不能为空。";const{id:a}=u({cron:r,freq:n,prompt:s,cwd:o,name:String(e?.name||"").trim()||s.slice(0,20)});return`✅ 已安排自动化 #${a}(${e.when}${t?",一次性":""}):${s.slice(0,60)}\n${$}`}}),h.push({readOnly:!0,spec:t("list_automations","列出当前已配置的自动化任务(含 id、名称、频率、启用状态、上次运行)。用户问「我都有哪些定时任务」或你要改动某条之前,先用它定位。"),run:async()=>{const e=m()||[];return e.length?"当前自动化任务:\n"+e.map(e=>{const r=e.lastRunAt?new Date(e.lastRunAt).toLocaleString("zh-CN"):"从未运行";return`- #${e.id}|${e.name||String(e.prompt||"").slice(0,16)}|${n(e)}|${!1===e.enabled?"已暂停":"启用中"}|${"full"===e.permission?"完全访问":"默认权限"}|上次:${r}`}).join("\n"):"当前没有任何自动化任务。可用 create_automation 新建。"}}),h.push({readOnly:!1,spec:t("create_automation","按用户口述创建一条新的自动化任务。用于「以后每天…」「每隔…就…」「下周一提醒我…」这类请求。任务说明要写成「到时候一个全新会话读了就能独立执行」的样子。",{name:i("自动化名称(简短)"),task:i("到点要执行的任务说明(独立自足、可单独执行)"),when:i("触发时间:每隔5分钟 / 每天 / 14:30 / 工作日 / 每周一 / +30m / cron 五段"),workspace:i("(可选)执行的工作目录,默认当前项目"),permission:i("(可选)default=默认权限(更安全,需审批的操作会被跳过)| full=完全访问权限(可写文件/跑命令,真正无人值守)")},["task","when"]),run:async e=>{const{cron:r,freq:t,once:i,error:s}=parseWhen(e?.when);if(s)return"创建失败:"+s;if(!f(r))return"创建失败:生成的 cron 无效("+r+")。";const a=String(e?.task||"").trim();if(!a)return"创建失败:任务说明不能为空。";const c="full"===e?.permission?"full":"default",{id:d}=u({cron:r,freq:t,prompt:a,cwd:String(e?.workspace||"").trim()||o,name:String(e?.name||"").trim()||a.slice(0,20),permission:c}),l=resolveTask(m,{id:d});return`✅ 已创建自动化 #${d}「${l?.name||""}」:${n(l||{cron:r})}${i?"(一次性)":""},${"full"===c?"完全访问权限":"默认权限"}运行。\n${$}`+("default"===c?"\n提示:默认权限下需审批的操作(写文件/跑命令)会被自动跳过;若这条任务要真正动手干活,改成 full。":"")}}),h.push({readOnly:!1,spec:t("update_automation","修改一条既有自动化(改频率/提示词/名称/权限/工作目录)。先用 id 或名称定位,只填要变更的字段,其余保持不变。改完请用「新旧配置对照」的方式向用户复述变化。",{id:i("目标自动化 id(优先)"),match:i("若无 id,用名称或关键词定位目标"),name:i("(可选)新名称"),task:i("(可选)新的任务提示词"),when:i("(可选)新的触发时间:每隔5分钟 / 每天 / 14:30 / cron 五段"),workspace:i("(可选)新的工作目录"),permission:i("(可选)default | full")}),run:async e=>{const r=resolveTask(m,{id:e?.id,match:e?.match||e?.name});if(!r)return"未找到匹配的自动化,请先用 list_automations 查看现有任务。";const t=r.name,i=n(r),o=String(r.prompt||"").slice(0,40),s={};if(e?.name&&(s.name=String(e.name).trim()),e?.task&&(s.prompt=String(e.task).trim()),e?.workspace&&(s.cwd=String(e.workspace).trim()),e?.permission&&(s.permission="full"===e.permission?"full":"default"),e?.when){const{cron:r,freq:n,error:t}=parseWhen(e.when);if(t)return"修改失败:"+t;if(!f(r))return"修改失败:生成的 cron 无效("+r+")。";s.cron=r,s.freq=n||null}if(!Object.keys(s).length)return"没有需要修改的字段。请指明要改的名称/提示词/频率/权限/目录。";d(r.id,s);const a=resolveTask(m,{id:r.id});return`✅ 已更新自动化 #${r.id}\n|项目|旧配置|新配置\n|名称|${t||"—"}|${a?.name||"—"}\n|频率|${i||"—"}|${n(a||{})}\n|提示|${o||"—"}|${String(a?.prompt||"").slice(0,40)}`}});for(const[e,r,n]of[["pause_automation",!1,"暂停"],["resume_automation",!0,"启用"]])h.push({readOnly:!1,spec:t(e,`${n}一条自动化(用 id 或名称定位)。`,{id:i("目标 id(优先)"),match:i("或用名称/关键词定位")}),run:async e=>{const t=resolveTask(m,{id:e?.id,match:e?.match});return t?(p(t.id,r),`✅ 已${n}自动化 #${t.id}「${t.name||""}」。`):"未找到匹配的自动化。"}});return h.push({readOnly:!1,spec:t("delete_automation","删除一条自动化(用 id 或名称定位)。**不可恢复**:调用前必须先向用户复述这条任务的名称与频率并得到明确确认,不要凭「像是要删」就动手。",{id:i("目标 id(优先)"),match:i("或用名称/关键词定位")}),run:async e=>{const r=resolveTask(m,{id:e?.id,match:e?.match});return r?(l(r.id),`🗑️ 已删除自动化 #${r.id}「${r.name||""}」(${n(r)})。`):"未找到匹配的自动化。"}}),h}
1
+ import*as e from"./schedule.js";import{isValidCron as r}from"./cron.js";import{describeSchedule as n}from"./schedule-freq.js";import{refreshPlan as t,isPlanLimitError as i,planDeniedForModel as o}from"./plan-limits.js";const s=(e,r,n={},t=[])=>({type:"function",function:{name:e,description:r,parameters:{type:"object",properties:n,required:t}}}),a=e=>({type:"string",description:e}),c=e=>String(e).padStart(2,"0"),u=e=>async r=>{await t();try{return await e(r)}catch(e){if(i(e))return o(e);throw e}};export function parseWhen(e,r=new Date){const n=String(e||"").trim();if(!n)return{error:"缺少时间"};if(/^(\S+\s+){4}\S+$/.test(n))return{cron:n};let t=/^每\s*隔?\s*(\d+)\s*(分钟?|min|m)$/i.exec(n);if(t){const e=Math.min(59,Math.max(1,Number(t[1])));return{cron:`*/${e} * * * *`,freq:{kind:"interval",every:e,unit:"minute"}}}if(t=/^每\s*隔?\s*(\d+)\s*(小时|时|h)$/i.exec(n),t){const e=Math.min(23,Math.max(1,Number(t[1])));return{cron:`0 */${e} * * *`,freq:{kind:"interval",every:e,unit:"hour"}}}const i=/^\+\s*(\d+)\s*(m|min|分钟?|h|小时|时)?$/i.exec(n);if(i){const e=Number(i[1]),n=(i[2]||"m").toLowerCase(),t=new Date(r.getTime()+6e4*(/h|小时|时/.test(n)?60*e:e));return{cron:`${t.getMinutes()} ${t.getHours()} ${t.getDate()} ${t.getMonth()+1} *`,once:!0,freq:{kind:"once",date:`${t.getFullYear()}-${c(t.getMonth()+1)}-${c(t.getDate())}`,time:`${c(t.getHours())}:${c(t.getMinutes())}`}}}const o=/^每\s*(周|星期)\s*([一二三四五六日天1-7])\s*(?:的?\s*(\d{1,2}):(\d{2}))?$/.exec(n);if(o){const e={"日":0,"天":0,"一":1,"二":2,"三":3,"四":4,"五":5,"六":6,7:0},r=null!=e[o[2]]?e[o[2]]:Number(o[2])%7,n=null!=o[3]?Number(o[3]):9,t=null!=o[4]?Number(o[4]):0;return n>23||t>59?{error:"无效时间"}:{cron:`${t} ${n} * * ${r}`,freq:{kind:"periodic",period:"weekly",weekday:r,time:`${c(n)}:${c(t)}`}}}const s=/^(?:每天\s*)?(\d{1,2}):(\d{2})$/.exec(n);if(s){const e=Number(s[1]),r=Number(s[2]);return e>23||r>59?{error:"无效时间"}:{cron:`${r} ${e} * * *`,freq:{kind:"periodic",period:"daily",time:`${c(e)}:${c(r)}`}}}return/每天|daily/i.test(n)?{cron:"0 9 * * *",freq:{kind:"periodic",period:"daily",time:"09:00"}}:/工作日|weekday/i.test(n)?{cron:"0 9 * * 1-5"}:{error:"无法解析时间(用 +30m / 14:30 / 每隔5分钟 / 每天 / 工作日 / 每周一 / cron五段)"}}export function toCron(e,r=new Date){const n=parseWhen(e,r);return n.error?{error:n.error}:n.once?{cron:n.cron,once:!0}:{cron:n.cron}}export function resolveTask(e,{id:r,name:n,match:t}={}){const i=e&&e()||[];if(r){const e=i.find(e=>e.id===r);if(e)return e}const o=String(n||t||r||"").trim().toLowerCase();return o&&(i.find(e=>String(e.name||"").toLowerCase()===o)||i.find(e=>String(e.name||"").toLowerCase().includes(o))||i.find(e=>String(e.prompt||"").toLowerCase().includes(o)))||null}export function makeAutomationTools({cwd:t,deps:i={},selfSchedule:o=!0,runnerHint:c=""}={}){const m=i.addTask||e.addTask,d=i.listTasks||e.listTasks,l=i.updateTask||e.updateTask,p=i.removeTask||e.removeTask,f=i.setEnabled||e.setEnabled,$=i.isValidCron||r,h=c||"到点由调度器在该目录自动执行(桌面端需保持应用运行;终端/IDE需有 `huicai daemon` 在跑)。",k=[];o&&k.push({readOnly:!1,spec:s("schedule_followup","给自己安排一个后续任务,到点由系统在同一项目目录自动继续执行(无需用户在场,也无需你还开着)。适合「稍后重试」「等CI/构建完成后检查」「每天定时汇总」这类需要延迟或周期执行的工作。时间写法:+30m、+2h、14:30、每隔5分钟、每天、工作日、每周一、或标准cron五段。",{when:a("触发时间:+30m / +2h / 14:30 / 每隔5分钟 / 每天 / 工作日 / 每周一 / cron五段"),task:a("到点要执行的任务说明(必须独立自足——届时是全新会话,看不到当前对话)"),name:a("(可选)给这个自动化起个简短名字")},["when","task"]),run:u(async e=>{const{cron:r,freq:n,once:i,error:o}=parseWhen(e?.when);if(o)return"排期失败:"+o;if(!$(r))return"排期失败:生成的cron无效("+r+")。";const s=String(e?.task||"").trim();if(!s)return"排期失败:任务说明不能为空。";const{id:a}=m({cron:r,freq:n,prompt:s,cwd:t,name:String(e?.name||"").trim()||s.slice(0,20)});return`✅ 已安排自动化 #${a}(${e.when}${i?",一次性":""}):${s.slice(0,60)}\n${h}`})}),k.push({readOnly:!0,spec:s("list_automations","列出当前已配置的自动化任务(含id、名称、频率、启用状态、上次运行)。用户问「我都有哪些定时任务」或你要改动某条之前,先用它定位。"),run:async()=>{const e=d()||[];return e.length?"当前自动化任务:\n"+e.map(e=>{const r=e.lastRunAt?new Date(e.lastRunAt).toLocaleString("zh-CN"):"从未运行";return`- #${e.id}|${e.name||String(e.prompt||"").slice(0,16)}|${n(e)}|${!1===e.enabled?"已暂停":"启用中"}|${"full"===e.permission?"完全访问":"默认权限"}|上次:${r}`}).join("\n"):"当前没有任何自动化任务。可用create_automation新建。"}}),k.push({readOnly:!1,spec:s("create_automation","按用户口述创建一条新的自动化任务。用于「以后每天…」「每隔…就…」「下周一提醒我…」这类请求。任务说明要写成「到时候一个全新会话读了就能独立执行」的样子。",{name:a("自动化名称(简短)"),task:a("到点要执行的任务说明(独立自足、可单独执行)"),when:a("触发时间:每隔5分钟 / 每天 / 14:30 / 工作日 / 每周一 / +30m / cron五段"),workspace:a("(可选)执行的工作目录,默认当前项目"),permission:a("(可选)default=默认权限(更安全,需审批的操作会被跳过)| full=完全访问权限(可写文件/跑命令,真正无人值守)")},["task","when"]),run:u(async e=>{const{cron:r,freq:i,once:o,error:s}=parseWhen(e?.when);if(s)return"创建失败:"+s;if(!$(r))return"创建失败:生成的cron无效("+r+")。";const a=String(e?.task||"").trim();if(!a)return"创建失败:任务说明不能为空。";const c="full"===e?.permission?"full":"default",{id:u}=m({cron:r,freq:i,prompt:a,cwd:String(e?.workspace||"").trim()||t,name:String(e?.name||"").trim()||a.slice(0,20),permission:c}),l=resolveTask(d,{id:u});return`✅ 已创建自动化 #${u}「${l?.name||""}」:${n(l||{cron:r})}${o?"(一次性)":""},${"full"===c?"完全访问权限":"默认权限"}运行。\n${h}`+("default"===c?"\n提示:默认权限下需审批的操作(写文件/跑命令)会被自动跳过;若这条任务要真正动手干活,改成full。":"")})}),k.push({readOnly:!1,spec:s("update_automation","修改一条既有自动化(改频率/提示词/名称/权限/工作目录)。先用id或名称定位,只填要变更的字段,其余保持不变。改完请用「新旧配置对照」的方式向用户复述变化。",{id:a("目标自动化id(优先)"),match:a("若无id,用名称或关键词定位目标"),name:a("(可选)新名称"),task:a("(可选)新的任务提示词"),when:a("(可选)新的触发时间:每隔5分钟 / 每天 / 14:30 / cron五段"),workspace:a("(可选)新的工作目录"),permission:a("(可选)default | full")}),run:u(async e=>{const r=resolveTask(d,{id:e?.id,match:e?.match||e?.name});if(!r)return"未找到匹配的自动化,请先用list_automations查看现有任务。";const t=r.name,i=n(r),o=String(r.prompt||"").slice(0,40),s={};if(e?.name&&(s.name=String(e.name).trim()),e?.task&&(s.prompt=String(e.task).trim()),e?.workspace&&(s.cwd=String(e.workspace).trim()),e?.permission&&(s.permission="full"===e.permission?"full":"default"),e?.when){const{cron:r,freq:n,error:t}=parseWhen(e.when);if(t)return"修改失败:"+t;if(!$(r))return"修改失败:生成的cron无效("+r+")。";s.cron=r,s.freq=n||null}if(!Object.keys(s).length)return"没有需要修改的字段。请指明要改的名称/提示词/频率/权限/目录。";l(r.id,s);const a=resolveTask(d,{id:r.id});return`✅ 已更新自动化 #${r.id}\n|项目|旧配置|新配置\n|名称|${t||"—"}|${a?.name||"—"}\n|频率|${i||"—"}|${n(a||{})}\n|提示|${o||"—"}|${String(a?.prompt||"").slice(0,40)}`})});for(const[e,r,n]of[["pause_automation",!1,"暂停"],["resume_automation",!0,"启用"]]){const t=async e=>{const t=resolveTask(d,{id:e?.id,match:e?.match});return t?(f(t.id,r),`✅ 已${n}自动化 #${t.id}「${t.name||""}」。`):"未找到匹配的自动化。"};k.push({readOnly:!1,spec:s(e,`${n}一条自动化(用id或名称定位)。`,{id:a("目标id(优先)"),match:a("或用名称/关键词定位")}),run:r?u(t):t})}return k.push({readOnly:!1,spec:s("delete_automation","删除一条自动化(用id或名称定位)。**不可恢复**:调用前必须先向用户复述这条任务的名称与频率并得到明确确认,不要凭「像是要删」就动手。",{id:a("目标id(优先)"),match:a("或用名称/关键词定位")}),run:async e=>{const r=resolveTask(d,{id:e?.id,match:e?.match});return r?(p(r.id),`🗑️ 已删除自动化 #${r.id}「${r.name||""}」(${n(r)})。`):"未找到匹配的自动化。"}}),k}