@huicai/cli 2026.804.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 (486) hide show
  1. package/LICENSE +391 -32
  2. package/README.md +176 -10
  3. package/bin/huicai.js +166 -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 -0
  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 -0
  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 -0
  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 -0
  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 -0
  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 -0
  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/vision.js +1 -0
  450. package/src/visual.js +1 -0
  451. package/src/voice-form.js +1 -0
  452. package/src/voice.js +1 -0
  453. package/src/watch.js +1 -1
  454. package/src/web-audit.js +1 -0
  455. package/src/web-cdp.js +1 -0
  456. package/src/web-inspect.js +1 -0
  457. package/src/websearch.js +1 -0
  458. package/src/weekly.js +1 -0
  459. package/src/workflow.js +1 -0
  460. package/src/worklog.js +1 -0
  461. package/src/xlsx-tools.js +1 -0
  462. package/ui/command-picker.js +79 -0
  463. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  464. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  465. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  466. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  467. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  468. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  469. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  470. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  471. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  472. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  473. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  474. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  475. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  476. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  477. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  478. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  479. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  480. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  481. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  482. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  483. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  484. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  485. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  486. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
@@ -0,0 +1 @@
1
+ import e from"node:path";import{buildIdentities as t,collectRepoCommits as n,cnRange as s,discoverRepos as i,expandPath as o,formatDocReport as r,formatGitReport as c,GIT_CONCURRENCY as a,mapLimit as d,parseRange as l,scanDocs as p,summarize as h,trackedFiles as u,weeklyDefaults as m,ymd as g}from"./worklog.js";import{fetchKind as y,myProjects as f,statusMap as w,tapdCreds as $,whoami as _}from"./tapd.js";const k=[{kind:"stories",cn:"需求",dateField:"modified",doneField:"completed",doneCn:"完成",system:"story"},{kind:"bugs",cn:"缺陷",dateField:"modified",doneField:"resolved",doneCn:"解决",system:"bug"},{kind:"releases",cn:"发布计划",dateField:"",doneField:"",doneCn:"",system:""}],D=(e,t)=>{const n=String(e||"").slice(0,10);return Boolean(n)&&n>=g(t.since)&&n<=g(t.until)};async function F(e,s,o,r){const{repos:l,missingRoots:p,truncated:u}=i(s,{maxDepth:r});if(!l.length)return{text:`【本机Git工作记录】${e.label}\n扫描根目录:${s.join("、")}\n这些目录下没有找到任何git仓库。请把roots指向真正放项目的目录(如 ~/Projects),或写进设置文件 .huicai/weekly.json的roots。`,repos:[],anchors:{}};const m=await t({configured:o,repos:l}),g=await d(l,a,t=>n(t,{since:e.since,until:e.until,identities:m})),y=g.filter(e=>e.commits.length);let f=0;const w=new Set;for(const e of y){const t=h(e.commits);f+=t.n;for(const e of t.files)w.add(e)}return{text:c(e,g,{identities:[...m],roots:s,repoCount:l.length,missingRoots:p,truncated:u}),repos:l,anchors:{"提交":f,"活跃仓库":y.length,"触及文件":w.size}}}async function x(e,t,n){const s=$(t);if(!s.configured)return{text:"【TAPD工作项】未配置,本次未取。要把需求/缺陷/发布计划纳入周报,设一个个人访问令牌即可(调一次 `tapd_fetch` 会给出完整配置指引)。",anchors:{}};let i=null;try{i=await _(s)}catch{}let o=String(n||"").trim();o&&"me"!==o||(o=i&&i.nick||s.nick);const r=o?[o]:[];let c=s.workspaces;const a=new Map;if(!c.length)try{const e=await f(s);c=e.map(e=>e.id);for(const t of e)a.set(t.id,t.name)}catch(e){return{text:`【TAPD工作项】取项目列表失败 —— ${e.message}`,anchors:{}}}if(!c.length)return{text:"【TAPD工作项】账号下没有任何参与的项目。",anchors:{}};const l=[`【TAPD工作项】${e.label} 身份:${o||"未识别"} 空间 ${c.length} 个`];o||l.push("⚠ 没能确定你的TAPD昵称,下面是**全空间**口径(团队数据),不是你个人的。");const p={},h=[];for(const t of k){const n="releases"===t.kind?[]:r,i=await d(c,5,async i=>{try{const o=await y(s,t.kind,{since:e.since,until:e.until,dateField:t.dateField||void 0,names:n,workspaceId:i}),r=o.items.length&&t.system?await w(s,i,t.system):{};return{ws:i,items:o.items,smap:r}}catch(e){return{ws:i,error:e.message}}}),o=[];for(const e of i)if(e.error)h.push(`空间 ${e.ws} 的${t.cn}:${e.error}`);else for(const t of e.items)o.push({...t,__ws:e.ws,__smap:e.smap});const u=t.doneField?o.filter(n=>D(n[t.doneField],e)):[];if(p[t.cn]=o.length,t.doneField&&(p[`本周${t.doneCn}${t.cn}`]=u.length),l.push(""),l.push(`■ ${t.cn}:本周期共 ${o.length} 条`+(t.doneField?`,其中**本周${t.doneCn}** ${u.length} 条(按 ${t.doneField} 落在周期内判定)`:"")+("releases"===t.kind?"(团队口径,未按人筛)":"")),!o.length)continue;const m=new Map;for(const e of o){const t=e.__smap&&e.__smap[e.status]||e.status||"(空)";m.set(t,(m.get(t)||0)+1)}l.push(" 按状态:"+[...m.entries()].sort((e,t)=>t[1]-e[1]).map(([e,t])=>`${e} ${t}`).join(" · "));const g=t.doneField?u:o;for(const e of g.slice(0,40)){const t=e.name||e.title||"(无标题)",n=e.__smap&&e.__smap[e.status]||e.status||"";l.push(` #${e.id} ${t} ${n} [${a.get(e.__ws)||e.__ws}]`)}g.length>40&&l.push(` …… 另有 ${g.length-40} 条未列出`),t.doneField&&o.length>u.length&&l.push(` (另有 ${o.length-u.length} 条本周期动过但尚未${t.doneCn},写进周报要说清是「推进中」而不是「已交付」)`)}return h.length&&l.push("","⚠ 部分空间读取失败(这是**数据缺口**,要在周报里说明,不要当成「没有」):",...h.map(e=>" · "+e)),{text:l.join("\n"),anchors:p}}export async function collectEvidence({cwd:e,range:t,roots:n,identities:s=[],depth:i=4,sources:o,person:c}){const a=new Set(o&&o.length?o:["git","docs","tapd"]),d=a.has("git")||a.has("docs"),[l,h]=await Promise.all([d?F(t,n,s,i):null,a.has("tapd")?x(t,e,c):null]),m=a.has("docs")?await async function(e,t,n){const s=await u(n),i=p(t,{since:e.since,until:e.until,tracked:s});return{text:r(e,t,i,{repoDirs:n}),anchors:{"文档":i.hits.length}}}(t,n,l?l.repos:[]):null,g={...l&&a.has("git")?l.anchors:{},...m?.anchors||{},...h?.anchors||{}},y=[];return a.has("git")&&l&&y.push(l.text),m&&y.push(m.text),h&&y.push(h.text),{anchors:g,parts:y}}export function anchorLine(e){const t=Object.entries(e).filter(([,e])=>Number.isFinite(e));return t.length?"【对账锚点】"+t.map(([e,t])=>`${e}=${t}`).join(" "):""}export function makeEvidenceTools({cwd:t}){return[{readOnly:!0,spec:{type:"function",function:{name:"work_evidence",description:"把一个汇报周期内的**全部工作痕迹一次取齐**——写周报/月报/述职的第一手材料。三个来源:\n① 本机 **Git提交**(多仓库、多身份合并,按作者时间切窗;锁文件/构建产物的行数单列、超大提交打标)——强证据;\n② **版本库之外的文档**(Word/Excel/PPT/PDF/思维导图;git已跟踪的自动排除)——弱证据,需逐条认领;\n③ **TAPD工作项**(需求/缺陷/发布计划;自动识别你是谁与你参与的项目,状态翻成各空间自己的说法),需求与缺陷会区分「本周期动过」与「**本周真正完成/解决**」——周报要写的是后者。\n**汇报周期算一次、三个来源共用**,所以不会出现「提交数来自这一周、需求数来自另一周」这种材料;三者并发跑,比分开调快得多。\n输出末尾的【对账锚点】是可直接引用的原始计数,成稿后 `weekly_report` 会拿它回头核对正文里的数字,**请把本工具的完整输出交给它的evidence参数**。\n需要别的TAPD口径(按created查新增、按状态过滤、指定空间)再用 `tapd_fetch` 单独查。\n只读,不改任何文件。",parameters:{type:"object",properties:{sources:{type:"array",items:{type:"string"},description:"要取哪几个来源:git / docs / tapd。不给则三个都取(TAPD未配置时自动略过)。"},roots:{type:"array",items:{type:"string"},description:"扫描哪些目录找git仓库与文档(支持 ~)。不给则读 .huicai/weekly.json的roots。"},since:{type:"string",description:"起始日期YYYY-MM-DD(含)"},until:{type:"string",description:"结束日期YYYY-MM-DD(含),默认今天"},days:{type:"number",description:"不给since时:往前N天(含今天),默认 7"},week:{type:"string",description:"this=本自然周(周一至周日)/ last=上一自然周"},identities:{type:"array",items:{type:"string"},description:"本人的git name/email(可多个,完全相等匹配)。不给则用设置文件 + 各仓库的user.name/user.email。"},person:{type:"string",description:"TAPD中文昵称;不给则由访问令牌自动识别本人。"},depth:{type:"number",description:"找仓库时的下潜层数,默认 4"}},required:[]}}},run:async n=>{let i;try{i=l(n||{})}catch(e){return"时间段有误:"+e.message}const r=m(t),c=Array.isArray(n?.roots)?n.roots.filter(Boolean).map(e=>o(e,t)):[],a=c.length?c:r.roots.length?r.roots:[t],d=c.length?"本次调用指定":r.roots.length?"weekly.json的roots":"未配置根目录,只扫了当前工作目录",p=Array.isArray(n?.identities)&&n.identities.length?n.identities:r.identities,{anchors:h,parts:u}=await collectEvidence({cwd:t,range:i,roots:a,identities:p,person:n?.person,depth:Math.max(0,Math.min(8,Number(n?.depth)||4)),sources:Array.isArray(n?.sources)?n.sources:null});return[`【取证包】汇报周期 ${i.label}(${i.source}) 抬头该写:${s(i)}`,`扫描根目录:${a.map(t=>e.normalize(t)).join("、")}(${d})`,"三个来源共用上面这一个周期——正文抬头的「汇报周期」必须与它逐字一致。","",u.join("\n\n"),"",anchorLine(h),"","【怎么用】① 提交数与行数是**过程量**,周报要写的是这些提交合起来交付了什么;② 文档那一节是弱证据(mtime会被拷贝/同步刷新),逐条认领后再写;③ TAPD里「动过」不等于「已交付」,写交付只引用「本周完成/解决」那部分;④ 对账锚点是可直接引用的原始计数,正文里写这几类数必须与它一致,成稿时把本输出整段交给 `weekly_report` 的evidence参数。"].join("\n")}}]}
package/src/executors.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import t from"node:path";import{StringDecoder as n}from"node:string_decoder";import{spawn as r,execFileSync as i}from"node:child_process";import*as o from"./ide.js";import{extractDocument as s}from"./api.js";import{pythonEnvForBash as l}from"./pyruntime.js";const c=16777216,a=536870912,f=2e3,u=2e3,p=3e4,h=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);function g(e,n,r){if(!e||"string"!=typeof e)throw new Error("工作目录未设置:请在项目目录中运行(VS Code 中需先「打开文件夹」再对话)");if(!n||"string"!=typeof n)throw new Error("缺少路径 path(工具参数里必须提供 path 字段)");const i=t.resolve(e,n);if(r){const r=t.relative(e,i);if(""===r||r.startsWith("..")||t.isAbsolute(r))throw new Error(`路径超出项目目录(写操作仅限当前项目内):${n}`)}return i}function m(e,n){const r=t.relative(e,n);return""===r?".":r}function d(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}function*$(n,r=0,i=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(h.has(e.name))continue;const o=t.join(n,e.name);e.isDirectory()?r<i&&(yield*$(o,r+1,i)):e.isFile()&&(yield o)}}function y(e){let t="";for(let n=0;n<e.length;n++){const r=e[n];"*"===r?"*"===e[n+1]?(t+=".*",n++,"/"===e[n+1]&&n++):t+="[^/]*":"?"===r?t+="[^/]":"\\^$+.()|{}[]".includes(r)?t+="\\"+r:t+=r}return new RegExp("^"+t+"$")}export function readFile(t,r){const i=g(t,r.path,!1),o=e.statSync(i);if(o.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(o.size>4194304)return function(t,r,i,o){const s=Buffer.allocUnsafe(4096),l=e.openSync(r,"r"),c=e.readSync(l,s,0,4096,0);if(e.closeSync(l),d(s.subarray(0,c)))return`(二进制文件,${o.size} 字节,未展示内容)`;const p=Math.max(0,(Number.isFinite(i.offset)?i.offset:1)-1),h=Number.isFinite(i.limit)&&i.limit>0?i.limit:f,{lines:g,lastLine:m,hitEof:$,truncatedScan:y}=function(t,r,i,o,s){const l=r+i,c=e=>e.length>o?e.slice(0,o)+"…(行过长已截断)":e,a=e.openSync(t,"r");try{const t=65536,i=Buffer.allocUnsafe(t),o=new n("utf8"),f=[];let u="",p=0,h=0,g=!1,m=!1,d=!1;for(;!d;){const n=e.readSync(a,i,0,t,null);if(0===n){g=!0;break}h+=n;let $,y=u+o.write(i.subarray(0,n)),w=0;for(;-1!==($=y.indexOf("\n",w));)if(p>=r&&p<l&&f.push(c(y.slice(w,$))),p++,w=$+1,p>=l){d=!0;break}if(u=d?"":y.slice(w),!d&&h>=s){m=!0;break}}return g&&(u+=o.end(),u.length&&p>=r&&p<l?(f.push(c(u)),p++):u.length&&p++),{lines:f,lastLine:p,hitEof:g,truncatedScan:m}}finally{e.closeSync(a)}}(r,p,h,u,a),w=(o.size/1024/1024).toFixed(1);if(!g.length)return y?`(大文件 ${w}MB:起始行 ${p+1} 超出已扫描的前 ${Math.round(512)}MB;请改用 grep 精确定位)`:`(起始行 ${p+1} 超出文件总行数 ${m})`;const b=p+g.length,S=String(b).length,x=g.map((e,t)=>`${String(p+t+1).padStart(S)}→${e}`).join("\n");return x+`\n…(大文件 ${w}MB,${$?`共 ${m} 行`:`${m}+ 行(大文件未全扫)`},本次第 ${p+1}-${b} 行;用 offset/limit 翻页,或用 grep 精确定位)`}(0,i,r,o);const s=e.readFileSync(i);if(d(s))return`(二进制文件,${o.size} 字节,未展示内容)`;const l=s.toString("utf8");if(""===l)return"(空文件)";const c=l.split("\n");c.length>1&&""===c[c.length-1]&&c.pop();const p=c.length,h=Math.max(0,(Number.isFinite(r.offset)?r.offset:1)-1),m=Number.isFinite(r.limit)&&r.limit>0?r.limit:f,$=Math.min(p,h+m);if(h>=p)return`(起始行 ${h+1} 超出文件总行数 ${p})`;const y=String($).length,w=[];for(let e=h;e<$;e++){let t=c[e];t.length>u&&(t=t.slice(0,u)+"…(行过长已截断)"),w.push(`${String(e+1).padStart(y)}→${t}`)}let b=w.join("\n");return(h>0||$<p)&&(b+=`\n…(文件共 ${p} 行,本次显示第 ${h+1}-${$} 行,可用 offset/limit 继续读取)`),b}const w=20971520;export async function readDocument(n,r,i){const o=g(n,r.path,!1),l=e.statSync(o);if(l.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(l.size>w)throw new Error(`文档超过 ${Math.round(20)}MB 解析上限`);if(!i||!i.baseUrl||!i.apiKey)throw new Error("解析文档需登录平台(先执行 huicai login)");const c=Number.isFinite(Number(r.offset))&&Number(r.offset)>0?Math.floor(Number(r.offset)):0,a=Number.isFinite(Number(r.max_chars))&&Number(r.max_chars)>0?Math.min(18e3,Math.max(1e3,Math.floor(Number(r.max_chars)))):16e3,f=await s(i.baseUrl,i.apiKey,{filename:t.basename(o),data:e.readFileSync(o).toString("base64"),maxChars:a,offset:c}),u=f.chars??0,p=f.text||"";if(!p)return`(第 ${c} 字符起已无内容;全文共 ${u} 字符)`;const h=c+p.length,d=f.truncated?`\n…(全文共 ${u} 字符,本段为第 ${c}–${h} 字符;继续调用 read_document 并传 offset=${h} 读取后续)`:`\n(全文共 ${u} 字符,本段为第 ${c}–${h} 字符,已到结尾)`,$=f.pages?`${f.pages}页·`:"";return`【文档 ${m(n,o)}(${$}共 ${u} 字符)】\n${p}${d}`}export async function writeFile(n,r){const i=g(n,r.path,!0),s=null==r.content?"":String(r.content);if(!0===r.append){let r="";try{r=e.readFileSync(i,"utf8")}catch{}const l=r+s;if(Buffer.byteLength(l)>c)throw new Error(`追加后文件将超过 ${Math.round(16)}MB 上限,请拆分为多个文件`);if(o.isActive()){const e=await o.proposeWrite(i,l,n);if(e.decided)return e.applied?`已追加到 ${m(n,i)}(经 IDE 审阅应用,本段 ${Buffer.byteLength(s)} 字节)`:`用户在 IDE 中拒绝了对 ${m(n,i)} 的追加${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,l,"utf8"),`已追加到 ${m(n,i)}(本段 ${Buffer.byteLength(s)} 字节,文件现 ${Buffer.byteLength(l)} 字节)`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}if(o.isActive()){const e=await o.proposeWrite(i,s,n);if(e.decided)return e.applied?`已写入 ${m(n,i)}(经 IDE 审阅应用)`:`用户在 IDE 中拒绝了对 ${m(n,i)} 的修改${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,s,"utf8"),`已写入 ${m(n,i)}(${Buffer.byteLength(s)} 字节)`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}export async function editFile(t,n){const r=g(t,n.path,!0),i=null==n.old_string?"":String(n.old_string),s=null==n.new_string?"":String(n.new_string);if(""===i)throw new Error("edit_file 需要非空 old_string");if(!e.existsSync(r))throw new Error(`目标文件不存在:${n.path}。请先用 read_file / list_files 确认路径是否正确(可能工作目录不对,或文件已被移动/删除);不要改用 write_file 新建一个同名空文件替代——如确需新建,请先向用户说明再创建。`);const l=e.readFileSync(r,"utf8"),c=l.split(i).length-1;if(0===c)throw new Error("未找到要替换的内容(old_string 未出现在文件中)。注意:old_string 必须是文件原文,不能包含 read_file 显示的「行号→」前缀");if(c>1&&!n.replace_all)throw new Error(`old_string 在文件中出现 ${c} 处,不唯一;请补充上下文使其唯一,或设 replace_all=true`);const a=n.replace_all?l.split(i).join(s):l.replace(i,s),f=n.replace_all?c:1;if(o.isActive()){const e=await o.proposeWrite(r,a,t);if(e.decided)return e.applied?`已编辑 ${m(t,r)}(经 IDE 审阅应用,替换 ${f} 处)`:`用户在 IDE 中拒绝了对 ${m(t,r)} 的修改${e.reason?":"+e.reason:""}`}return e.writeFileSync(r,a,"utf8"),`已编辑 ${m(t,r)},替换 ${f} 处`+(o.isActive()?"(IDE 未响应,已直接写入)":"")}export function listFiles(n,r){const i=g(n,r.path||".",!1),o=[];let s=0;const l=(n,r)=>{if(s>=400)return;let c;try{c=e.readdirSync(n,{withFileTypes:!0})}catch{return}c.sort((e,t)=>e.name.localeCompare(t.name));for(const e of c){if(s>=400)break;if(h.has(e.name))continue;const c=t.join(n,e.name),a=t.relative(i,c);e.isDirectory()?(o.push(a+"/"),s++,r<6&&l(c,r+1)):(o.push(a),s++)}};return l(i,0),0===o.length?"(空目录)":o.join("\n")+(s>=400?"\n…(条目已截断)":"")}export function listWorkspaceFiles(e,n=2e3){const r=[];for(const i of $(e))if(r.push(t.relative(e,i).split(t.sep).join("/")),r.length>=n)break;return r}export function glob(e,n){if(!n.pattern)throw new Error("缺少 pattern");const r=y(n.pattern),i=[];for(const n of $(e)){const o=t.relative(e,n).split(t.sep).join("/");if(r.test(o)&&(i.push(o),i.length>=300))break}return 0===i.length?"(无匹配文件)":i.join("\n")}let b=null;export function grep(e,t){if(!t.pattern)throw new Error("缺少 pattern");const n=function(){if(null!==b)return b;try{i("rg",["--version"],{stdio:"ignore"}),b=!0}catch{b=!1}return b}()?function(e,t){const n=Number.isFinite(t.max_results)&&t.max_results>0?Math.min(t.max_results,500):200,r=t.path?String(t.path):".",o=[...h].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`]),s=["--color=never","--no-messages","--max-columns","2000"];t.ignore_case&&s.push("-i");t.glob&&s.push("-g",String(t.glob));s.push(...o),t.files_only?s.push("-l"):s.push("-n","--no-heading");let l;s.push("-e",String(t.pattern),"--",r);try{l=i("rg",s,{cwd:e,encoding:"utf8",maxBuffer:32<<20,stdio:["ignore","pipe","pipe"]})}catch(e){return e&&1===e.status?"(无匹配)":null}const c=e=>e.replace(/\\/g,"/").replace(/^\.\//,""),a=l.split("\n").filter(Boolean);if(t.files_only)return a.length?a.slice(0,n).map(c).join("\n"):"(无匹配)";const f=[];for(const e of a){const t=/^(.*?):(\d+):(.*)$/.exec(e);if(t&&(f.push(`${c(t[1])}:${t[2]}: ${t[3].slice(0,240)}`),f.length>=n))break}return f.length?f.join("\n")+(f.length>=n?"\n…(结果已截断)":""):"(无匹配)"}(e,t):null;return null!==n?n:grepJs(e,t)}export function grepJs(n,r){if(!r.pattern)throw new Error("缺少 pattern");let i;try{i=new RegExp(r.pattern,r.ignore_case?"i":void 0)}catch(e){throw new Error(`无效的正则:${e.message}`)}const o=r.glob?y(r.glob):null,s=g(n,r.path||".",!1),l=Number.isFinite(r.max_results)&&r.max_results>0?Math.min(r.max_results,500):200,c=[],a=[];for(const f of $(s)){if(c.length>=l)break;const s=t.relative(n,f).split(t.sep).join("/");if(o&&!o.test(s))continue;let u;try{u=e.statSync(f)}catch{continue}let p=!1;if(u.size<=262144){let t;try{t=e.readFileSync(f)}catch{continue}if(d(t))continue;const n=t.toString("utf8").split("\n");for(let e=0;e<n.length;e++)if(i.test(n[e])){if(p=!0,r.files_only)break;if(c.push(`${s}:${e+1}: ${n[e].slice(0,240)}`),c.length>=l)break}}else p=S(f,i,s,!!r.files_only,c,l);if(p&&(a.push(s),r.files_only&&a.length>=l))break}return r.files_only?a.length?a.join("\n"):"(无匹配)":0===c.length?"(无匹配)":c.join("\n")+(c.length>=l?"\n…(结果已截断)":"")}function S(t,r,i,o,s,l){let c;try{c=e.openSync(t,"r")}catch{return!1}try{const t=65536,a=Buffer.allocUnsafe(t),f=new n("utf8");let u="",p=0,h=0,g=!1,m=!0,$=!1;for(;;){const n=e.readSync(c,a,0,t,null);if(0===n)break;if(m){if(d(a.subarray(0,n)))return!1;m=!1}h+=n;const y=u+f.write(a.subarray(0,n));let w,b=0;for(;-1!==(w=y.indexOf("\n",b));){p++;const e=y.slice(b,w);if(r.test(e)){if(g=!0,o)return!0;if(s.push(`${i}:${p}: ${e.slice(0,240)}`),s.length>=l)return!0}b=w+1}if(u=y.slice(b),h>=16777216){$=!0;break}}return $||(u+=f.end(),u.length&&(p++,r.test(u)&&(g=!0,!o&&s.length<l&&s.push(`${i}:${p}: ${u.slice(0,240)}`)))),g}finally{e.closeSync(c)}}export function runBash(e,t,i,o){const s=null==t.command?"":String(t.command);if(""===s.trim())return Promise.resolve("(未提供命令)");const c=Number.isFinite(t.timeout)?Math.min(t.timeout,6e5):12e4,a="win32"===process.platform;return new Promise(t=>{let i="",f=!1,u=!1;const h={...process.env,...l()},g=a?r(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",s],{cwd:e,env:h,windowsVerbatimArguments:!0}):r(process.env.SHELL||"/bin/sh",["-c",s],{cwd:e,env:h,detached:!0}),m=()=>{try{!a&&g.pid?process.kill(-g.pid,"SIGKILL"):g.kill("SIGKILL")}catch{try{g.kill("SIGKILL")}catch{}}},d=setTimeout(()=>{f=!0,m()},c),$=()=>{u=!0,f=!0,m()};o&&(o.aborted?$():o.addEventListener("abort",$,{once:!0}));const y=()=>{clearTimeout(d),o&&o.removeEventListener("abort",$)},w=new n("utf8"),b=new n("utf8"),S=e=>{i.length<p&&(i+=e)};g.stdout.on("data",e=>S(w.write(e))),g.stderr.on("data",e=>S(b.write(e))),g.on("error",e=>{y(),t(`命令启动失败:${e.message}`)}),g.on("close",e=>{y(),S(w.end()),S(b.end());let n=i.length>p?i.slice(0,p)+"\n…(输出已截断)":i;""===n.trim()&&(n="(无输出)");t(`${u?"【已中断】":f?"【超时被终止】":`退出码 ${e}`}\n${n}`)})})}export async function fetchUrl(e,t){const n=String(t.url||"").trim();if(!/^https?:\/\//i.test(n))throw new Error("仅支持 http/https URL");let r;try{r=await fetch(n,{redirect:"follow",signal:AbortSignal.timeout(Number.isFinite(t.timeout)?Math.min(t.timeout,12e4):3e4),headers:{"User-Agent":"huicai-cli",Accept:"text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.5"}})}catch(e){throw new Error(`请求失败:${e?.cause?.message||e.message}`)}const i=await async function(e,t){if(!e.body)return Buffer.alloc(0);const n=e.body.getReader(),r=[];let i=0;for(;;){const{done:e,value:o}=await n.read();if(e)break;if(r.push(o),i+=o.length,i>=t){n.cancel().catch(()=>{});break}}return Buffer.concat(r.map(e=>Buffer.from(e)),Math.min(i,t))}(r,2097152),o=(r.headers.get("content-type")||"").toLowerCase();if(d(i)&&!/text|json|xml|html/.test(o))return`HTTP ${r.status} · ${o||"未知类型"} · ${i.length} 字节(二进制内容未展示)`;let s=function(e,t){let n=(/charset=([\w-]+)/i.exec(t||"")||[])[1];if(!n){const t=e.slice(0,4096).toString("latin1");n=(/<meta[^>]+charset\s*=\s*["']?([\w-]+)/i.exec(t)||[])[1]}const r=String(n||"utf-8").toLowerCase();if("utf-8"===r||"utf8"===r)return e.toString("utf8");try{return new TextDecoder(r).decode(e)}catch{return e.toString("utf8")}}(i,o),l="",c="";if(o.includes("html")||/^\s*<(!doctype|html)/i.test(s)){if(l=((/<title[^>]*>([\s\S]*?)<\/title>/i.exec(s)||[])[1]||"").replace(/\s+/g," ").trim().slice(0,120),t.links){const e=function(e,t,n=60){const r=[],i=new Set,o=/<a\b[^>]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi;let s;for(;(s=o.exec(e))&&r.length<n;){const e=(s[1]??s[2]??s[3]??"").trim();if(!e||/^(javascript:|mailto:|tel:|#)/i.test(e))continue;let n;try{n=new URL(e,t).href}catch{continue}if(!/^https?:/i.test(n)||i.has(n))continue;i.add(n);const o=s[4].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim().slice(0,80);r.push(`- ${o||"(无文本)"} → ${n}`)}return r}(s,r.url||n);e.length&&(c=`\n\n—— 页面链接(${e.length} 条)——\n`+e.join("\n"))}s=function(e){let t=String(e);return t=t.replace(/<(script|style|noscript|svg|head)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<!--[\s\S]*?-->/g,""),t=t.replace(/<br\s*\/?>/gi,"\n"),t=t.replace(/<\/(p|div|li|tr|h[1-6]|section|article|blockquote|pre|ul|ol|table)>/gi,"\n"),t=t.replace(/<li[^>]*>/gi,"- "),t=t.replace(/<[^>]+>/g,""),t=t.replace(/&nbsp;/g," ").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;|&apos;/g,"'").replace(/&amp;/g,"&"),t=t.replace(/[ \t]+\n/g,"\n").replace(/\n{3,}/g,"\n\n"),t}(s)}s=s.trim(),s.length>8e4&&(s=s.slice(0,8e4)+"\n…(内容过长已截断)");return`${`HTTP ${r.status}${l?` · ${l}`:""}${r.url&&r.url!==n?` · 重定向至 ${r.url}`:""}`}\n${s||"(无内容)"}${c}`}
1
+ import e from"node:fs";import t from"node:path";import{StringDecoder as n}from"node:string_decoder";import{spawn as r,execFileSync as i}from"node:child_process";import*as o from"./ide.js";import{extractDocument as s}from"./api.js";import{pythonEnvForBash as l}from"./pyruntime.js";import*as c from"./jobs.js";import{confineArgv as a,sandboxNote as f}from"./sandbox.js";import{cleanForModel as u}from"./terminal.js";import{isWebSearchOn as p}from"./config.js";import{isSearchEngineQueryUrl as h,FETCH_SEARCH_BLOCKED_MESSAGE as d}from"./websearch.js";const g=16777216,m=536870912,$=2e3,w=2e3,y=3e4,b=new Set([".git","node_modules","dist","build","target","out",".next",".nuxt",".venv","venv","__pycache__",".idea",".gradle",".mvn","coverage",".cache","vendor","bower_components","Pods","DerivedData",".turbo",".parcel-cache",".terraform",".pytest_cache",".mypy_cache",".tox",".angular",".svelte-kit",".output",".serverless",".svn",".hg"]);function S(e,n,r){if(!e||"string"!=typeof e)throw new Error("工作目录未设置:请在项目目录中运行(VS Code中需先「打开文件夹」再对话)");if(!n||"string"!=typeof n)throw new Error("缺少路径path(工具参数里必须提供path字段)");const i=t.resolve(e,n);if(r){const r=t.relative(e,i);if(""===r||r.startsWith("..")||t.isAbsolute(r))throw new Error(`路径超出项目目录(写操作仅限当前项目内):${n}`)}return i}function _(e,n){const r=t.relative(e,n);return""===r?".":r}function x(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}function*v(n,r=0,i=14){let o;try{o=e.readdirSync(n,{withFileTypes:!0})}catch{return}for(const e of o){if(b.has(e.name))continue;const o=t.join(n,e.name);e.isDirectory()?r<i&&(yield*v(o,r+1,i)):e.isFile()&&(yield o)}}function E(e){let t="";for(let n=0;n<e.length;n++){const r=e[n];"*"===r?"*"===e[n+1]?(t+=".*",n++,"/"===e[n+1]&&n++):t+="[^/]*":"?"===r?t+="[^/]":"\\^$+.()|{}[]".includes(r)?t+="\\"+r:t+=r}return new RegExp("^"+t+"$")}export function readFile(t,r){const i=S(t,r.path,!1),o=e.statSync(i);if(o.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(o.size>4194304)return function(t,r,i,o){const s=Buffer.allocUnsafe(4096),l=e.openSync(r,"r"),c=e.readSync(l,s,0,4096,0);if(e.closeSync(l),x(s.subarray(0,c)))return`(二进制文件,${o.size} 字节,未展示内容)`;const a=Math.max(0,(Number.isFinite(i.offset)?i.offset:1)-1),f=Number.isFinite(i.limit)&&i.limit>0?i.limit:$,{lines:u,lastLine:p,hitEof:h,truncatedScan:d}=function(t,r,i,o,s){const l=r+i,c=e=>e.length>o?e.slice(0,o)+"…(行过长已截断)":e,a=e.openSync(t,"r");try{const t=65536,i=Buffer.allocUnsafe(t),o=new n("utf8"),f=[];let u="",p=0,h=0,d=!1,g=!1,m=!1;for(;!m;){const n=e.readSync(a,i,0,t,null);if(0===n){d=!0;break}h+=n;let $,w=u+o.write(i.subarray(0,n)),y=0;for(;-1!==($=w.indexOf("\n",y));)if(p>=r&&p<l&&f.push(c(w.slice(y,$))),p++,y=$+1,p>=l){m=!0;break}if(u=m?"":w.slice(y),!m&&h>=s){g=!0;break}}return d&&(u+=o.end(),u.length&&p>=r&&p<l?(f.push(c(u)),p++):u.length&&p++),{lines:f,lastLine:p,hitEof:d,truncatedScan:g}}finally{e.closeSync(a)}}(r,a,f,w,m),g=(o.size/1024/1024).toFixed(1);if(!u.length)return d?`(大文件 ${g}MB:起始行 ${a+1} 超出已扫描的前 ${Math.round(512)}MB;请改用grep精确定位)`:`(起始行 ${a+1} 超出文件总行数 ${p})`;const y=a+u.length,b=String(y).length,S=u.map((e,t)=>`${String(a+t+1).padStart(b)}→${e}`).join("\n");return S+`\n…(大文件 ${g}MB,${h?`共 ${p} 行`:`${p}+ 行(大文件未全扫)`},本次第 ${a+1}-${y} 行;用offset/limit翻页,或用grep精确定位)`}(0,i,r,o);const s=e.readFileSync(i);if(x(s))return`(二进制文件,${o.size} 字节,未展示内容)`;const l=s.toString("utf8");if(""===l)return"(空文件)";const c=l.split("\n");c.length>1&&""===c[c.length-1]&&c.pop();const a=c.length,f=Math.max(0,(Number.isFinite(r.offset)?r.offset:1)-1),u=Number.isFinite(r.limit)&&r.limit>0?r.limit:$,p=Math.min(a,f+u);if(f>=a)return`(起始行 ${f+1} 超出文件总行数 ${a})`;const h=String(p).length,d=[];for(let e=f;e<p;e++){let t=c[e];t.length>w&&(t=t.slice(0,w)+"…(行过长已截断)"),d.push(`${String(e+1).padStart(h)}→${t}`)}let g=d.join("\n");return(f>0||p<a)&&(g+=`\n…(文件共 ${a} 行,本次显示第 ${f+1}-${p} 行,可用offset/limit继续读取)`),g}const k=20971520;export async function readDocument(n,r,i){const o=S(n,r.path,!1),l=e.statSync(o);if(l.isDirectory())throw new Error(`是目录而非文件:${r.path}`);if(l.size>k)throw new Error(`文档超过 ${Math.round(20)}MB解析上限`);if(!i||!i.baseUrl||!i.apiKey)throw new Error("解析文档需登录平台(先执行huicai login)");const c=Number.isFinite(Number(r.offset))&&Number(r.offset)>0?Math.floor(Number(r.offset)):0,a=Number.isFinite(Number(r.max_chars))&&Number(r.max_chars)>0?Math.min(18e3,Math.max(1e3,Math.floor(Number(r.max_chars)))):16e3,f=await s(i.baseUrl,i.apiKey,{filename:t.basename(o),data:e.readFileSync(o).toString("base64"),maxChars:a,offset:c}),u=f.chars??0,p=f.text||"";if(!p)return`(第 ${c} 字符起已无内容;全文共 ${u} 字符)`;const h=c+p.length,d=f.truncated?`\n…(全文共 ${u} 字符,本段为第 ${c}–${h} 字符;继续调用read_document并传offset=${h} 读取后续)`:`\n(全文共 ${u} 字符,本段为第 ${c}–${h} 字符,已到结尾)`,g=f.pages?`${f.pages}页·`:"";return`【文档 ${_(n,o)}(${g}共 ${u} 字符)】\n${p}${d}`}export function looksLikeNumberedDump(e){const t=String(e||"").split("\n").filter(e=>e.trim());if(t.length<3)return!1;return t.filter(e=>/^\s*\d+→/.test(e)).length/t.length>=.8}const L="拒绝写入:内容看起来是read_file的**带行号输出**(每行以「行号→」开头),不是文件原文。原样写回会把行号写进文件,把它毁掉。\n改法:① 局部替换用edit_file(old_string→new_string,不必读全文);② 确需重写全文,先去掉前缀:content.replace(/^\\s*\\d+→/gm, '')。";export async function writeFile(n,r){const i=S(n,r.path,!0),s=null==r.content?"":String(r.content);if(looksLikeNumberedDump(s))throw new Error(L);if(!0===r.append){let r="";try{r=e.readFileSync(i,"utf8")}catch{}const l=r+s;if(Buffer.byteLength(l)>g)throw new Error(`追加后文件将超过 ${Math.round(16)}MB上限,请拆分为多个文件`);if(o.isActive()){const e=await o.proposeWrite(i,l,n);if(e.decided)return e.applied?`已追加到 ${_(n,i)}(经IDE审阅应用,本段 ${Buffer.byteLength(s)} 字节)`:`用户在IDE中拒绝了对 ${_(n,i)} 的追加${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,l,"utf8"),`已追加到 ${_(n,i)}(本段 ${Buffer.byteLength(s)} 字节,文件现 ${Buffer.byteLength(l)} 字节)`+(o.isActive()?"(IDE未响应,已直接写入)":"")}if(o.isActive()){const e=await o.proposeWrite(i,s,n);if(e.decided)return e.applied?`已写入 ${_(n,i)}(经IDE审阅应用)`:`用户在IDE中拒绝了对 ${_(n,i)} 的修改${e.reason?":"+e.reason:""}`}return e.mkdirSync(t.dirname(i),{recursive:!0}),e.writeFileSync(i,s,"utf8"),`已写入 ${_(n,i)}(${Buffer.byteLength(s)} 字节)`+(o.isActive()?"(IDE未响应,已直接写入)":"")}export async function editFile(t,n){const r=S(t,n.path,!0),i=null==n.old_string?"":String(n.old_string),s=null==n.new_string?"":String(n.new_string);if(""===i)throw new Error("edit_file需要非空old_string");if(looksLikeNumberedDump(s))throw new Error(L);if(!e.existsSync(r))throw new Error(`目标文件不存在:${n.path}。请先用read_file / list_files确认路径是否正确(可能工作目录不对,或文件已被移动/删除);不要改用write_file新建一个同名空文件替代——如确需新建,请先向用户说明再创建。`);const l=e.readFileSync(r,"utf8"),c=l.split(i).length-1;if(0===c)throw new Error("未找到要替换的内容(old_string未出现在文件中)。注意:old_string必须是文件原文,不能包含read_file显示的「行号→」前缀");if(c>1&&!n.replace_all)throw new Error(`old_string在文件中出现 ${c} 处,不唯一;请补充上下文使其唯一,或设replace_all=true`);const a=n.replace_all?l.split(i).join(s):l.replace(i,s),f=n.replace_all?c:1;if(o.isActive()){const e=await o.proposeWrite(r,a,t);if(e.decided)return e.applied?`已编辑 ${_(t,r)}(经IDE审阅应用,替换 ${f} 处)`:`用户在IDE中拒绝了对 ${_(t,r)} 的修改${e.reason?":"+e.reason:""}`}return e.writeFileSync(r,a,"utf8"),`已编辑 ${_(t,r)},替换 ${f} 处`+(o.isActive()?"(IDE未响应,已直接写入)":"")}export function listFiles(n,r){const i=S(n,r.path||".",!1),o=[];let s=0;const l=(n,r)=>{if(s>=400)return;let c;try{c=e.readdirSync(n,{withFileTypes:!0})}catch{return}c.sort((e,t)=>e.name.localeCompare(t.name));for(const e of c){if(s>=400)break;if(b.has(e.name))continue;const c=t.join(n,e.name),a=t.relative(i,c);e.isDirectory()?(o.push(a+"/"),s++,r<6&&l(c,r+1)):(o.push(a),s++)}};return l(i,0),0===o.length?"(空目录)":o.join("\n")+(s>=400?"\n…(条目已截断)":"")}export function listWorkspaceFiles(e,n=2e3){const r=[];for(const i of v(e))if(r.push(t.relative(e,i).split(t.sep).join("/")),r.length>=n)break;return r}export function glob(e,n){if(!n.pattern)throw new Error("缺少pattern");const r=E(n.pattern),i=[];for(const n of v(e)){const o=t.relative(e,n).split(t.sep).join("/");if(r.test(o)&&(i.push(o),i.length>=300))break}return 0===i.length?"(无匹配文件)":i.join("\n")}let j=null;export function grep(e,t){if(!t.pattern)throw new Error("缺少pattern");const n=function(){if(null!==j)return j;try{i("rg",["--version"],{stdio:"ignore"}),j=!0}catch{j=!1}return j}()?function(e,t){const n=Number.isFinite(t.max_results)&&t.max_results>0?Math.min(t.max_results,500):200,r=t.path?String(t.path):".",o=[...b].flatMap(e=>["-g",`!**/${e}/**`,"-g",`!${e}/**`]),s=["--color=never","--no-messages","--max-columns","2000"];t.ignore_case&&s.push("-i");t.glob&&s.push("-g",String(t.glob));s.push(...o),t.files_only?s.push("-l"):s.push("-n","--no-heading");let l;s.push("-e",String(t.pattern),"--",r);try{l=i("rg",s,{cwd:e,encoding:"utf8",maxBuffer:32<<20,stdio:["ignore","pipe","pipe"]})}catch(e){return e&&1===e.status?"(无匹配)":null}const c=e=>e.replace(/\\/g,"/").replace(/^\.\//,""),a=l.split("\n").filter(Boolean);if(t.files_only)return a.length?a.slice(0,n).map(c).join("\n"):"(无匹配)";const f=[];for(const e of a){const t=/^(.*?):(\d+):(.*)$/.exec(e);if(t&&(f.push(`${c(t[1])}:${t[2]}: ${t[3].slice(0,240)}`),f.length>=n))break}return f.length?f.join("\n")+(f.length>=n?"\n…(结果已截断)":""):"(无匹配)"}(e,t):null;return null!==n?n:grepJs(e,t)}export function grepJs(n,r){if(!r.pattern)throw new Error("缺少pattern");let i;try{i=new RegExp(r.pattern,r.ignore_case?"i":void 0)}catch(e){throw new Error(`无效的正则:${e.message}`)}const o=r.glob?E(r.glob):null,s=S(n,r.path||".",!1),l=Number.isFinite(r.max_results)&&r.max_results>0?Math.min(r.max_results,500):200,c=[],a=[];for(const f of v(s)){if(c.length>=l)break;const s=t.relative(n,f).split(t.sep).join("/");if(o&&!o.test(s))continue;let u;try{u=e.statSync(f)}catch{continue}let p=!1;if(u.size<=262144){let t;try{t=e.readFileSync(f)}catch{continue}if(x(t))continue;const n=t.toString("utf8").split("\n");for(let e=0;e<n.length;e++)if(i.test(n[e])){if(p=!0,r.files_only)break;if(c.push(`${s}:${e+1}: ${n[e].slice(0,240)}`),c.length>=l)break}}else p=F(f,i,s,!!r.files_only,c,l);if(p&&(a.push(s),r.files_only&&a.length>=l))break}return r.files_only?a.length?a.join("\n"):"(无匹配)":0===c.length?"(无匹配)":c.join("\n")+(c.length>=l?"\n…(结果已截断)":"")}function F(t,r,i,o,s,l){let c;try{c=e.openSync(t,"r")}catch{return!1}try{const t=65536,a=Buffer.allocUnsafe(t),f=new n("utf8");let u="",p=0,h=0,d=!1,g=!0,m=!1;for(;;){const n=e.readSync(c,a,0,t,null);if(0===n)break;if(g){if(x(a.subarray(0,n)))return!1;g=!1}h+=n;const $=u+f.write(a.subarray(0,n));let w,y=0;for(;-1!==(w=$.indexOf("\n",y));){p++;const e=$.slice(y,w);if(r.test(e)){if(d=!0,o)return!0;if(s.push(`${i}:${p}: ${e.slice(0,240)}`),s.length>=l)return!0}y=w+1}if(u=$.slice(y),h>=16777216){m=!0;break}}return m||(u+=f.end(),u.length&&(p++,r.test(u)&&(d=!0,!o&&s.length<l&&s.push(`${i}:${p}: ${u.slice(0,240)}`)))),d}finally{e.closeSync(c)}}function M(e,t,i){const o=String(t.command),s="win32"===process.platform,a={...process.env,...l()},f=s?[process.env.COMSPEC||"cmd.exe","/d","/s","/c",o]:[process.env.SHELL||"/bin/sh","-c",o],{argv:u,sandbox:p,note:h}=I(f,i,e),d=r(u[0],u.slice(1),s?{cwd:e,env:a,windowsVerbatimArguments:!0}:{cwd:e,env:a,detached:!0}),g=c.start({kind:"bash",label:o.replace(/\s+/g," ").slice(0,120),sessionId:i?.sessionId||"",cwd:e,kill:()=>(()=>{try{!s&&d.pid?process.kill(-d.pid,"SIGKILL"):d.kill("SIGKILL")}catch{try{d.kill("SIGKILL")}catch{}}})(),meta:{pid:d.pid}}),m=new n("utf8"),$=new n("utf8");return d.stdout.on("data",e=>g.append(m.write(e))),d.stderr.on("data",e=>g.append($.write(e))),d.on("error",e=>{g.append(`\n命令启动失败:${e.message}\n`),g.settle("failed","启动失败")}),d.on("close",(e,t)=>{g.append(m.end()),g.append($.end()),t?g.settle("killed",`被信号 ${t} 终止`):g.settle(0===e?"done":"failed",`退出码 ${e}`)}),Promise.resolve(`已在后台启动,作业id:${g.id}\n命令:${o.replace(/\s+/g," ").slice(0,200)}\n用job_output读它的输出(默认只回新增;加wait=true可等它结束),job_kill可终止它。不要为了等它而空转或反复轮询——它结束时会主动通知你。`+h)}const I=(e,t,n)=>a(e,t,n);export function runBash(e,t,i,o){const s=null==t.command?"":String(t.command);if(""===s.trim())return Promise.resolve("(未提供命令)");if(t.run_in_background)return M(e,t,i);const c=Number.isFinite(t.timeout)?Math.min(t.timeout,6e5):12e4,a="win32"===process.platform;return new Promise(t=>{let p="",h=!1,d=!1;const g={...process.env,...l()},m=a?[process.env.COMSPEC||"cmd.exe","/d","/s","/c",s]:[process.env.SHELL||"/bin/sh","-c",s],{argv:$,sandbox:w,note:b}=I(m,i,e),S=r($[0],$.slice(1),a?{cwd:e,env:g,windowsVerbatimArguments:!0}:{cwd:e,env:g,detached:!0}),_=()=>{try{!a&&S.pid?process.kill(-S.pid,"SIGKILL"):S.kill("SIGKILL")}catch{try{S.kill("SIGKILL")}catch{}}},x=setTimeout(()=>{h=!0,_()},c),v=()=>{d=!0,h=!0,_()};o&&(o.aborted?v():o.addEventListener("abort",v,{once:!0}));const E=()=>{clearTimeout(x),o&&o.removeEventListener("abort",v)},k=new n("utf8"),L=new n("utf8"),j=e=>{p.length<y&&(p+=e)};S.stdout.on("data",e=>j(k.write(e))),S.stderr.on("data",e=>j(L.write(e))),S.on("error",e=>{E(),t(`命令启动失败:${e.message}`)}),S.on("close",e=>{E(),j(k.end()),j(L.end()),p=u(p);let n=p.length>y?p.slice(0,y)+"\n…(输出已截断)":p;""===n.trim()&&(n="(无输出)");const r=d?"【已中断】":h?"【超时被终止】":`退出码 ${e}`,i=0===e?"":((e,t)=>f(e,t))(w,n);t(`${r}\n${n}${b}${i}`)})})}export async function fetchUrl(e,t){const n=String(t.url||"").trim();if(!/^https?:\/\//i.test(n))throw new Error("仅支持http/https URL");const r=!p();if(r&&h(n))throw new Error(d);let i;try{i=await fetch(n,{redirect:"follow",signal:AbortSignal.timeout(Number.isFinite(t.timeout)?Math.min(t.timeout,12e4):3e4),headers:{"User-Agent":"huicai-cli",Accept:"text/html,application/json;q=0.9,text/plain;q=0.8,*/*;q=0.5"}})}catch(e){throw new Error(`请求失败:${e?.cause?.message||e.message}`)}if(r&&i.url&&i.url!==n&&h(i.url)){try{await(i.body?.cancel())}catch{}throw new Error(d)}const o=await async function(e,t){if(!e.body)return Buffer.alloc(0);const n=e.body.getReader(),r=[];let i=0;for(;;){const{done:e,value:o}=await n.read();if(e)break;if(r.push(o),i+=o.length,i>=t){n.cancel().catch(()=>{});break}}return Buffer.concat(r.map(e=>Buffer.from(e)),Math.min(i,t))}(i,2097152),s=(i.headers.get("content-type")||"").toLowerCase();if(x(o)&&!/text|json|xml|html/.test(s))return`HTTP ${i.status} · ${s||"未知类型"} · ${o.length} 字节(二进制内容未展示)`;let l=function(e,t){let n=(/charset=([\w-]+)/i.exec(t||"")||[])[1];if(!n){const t=e.slice(0,4096).toString("latin1");n=(/<meta[^>]+charset\s*=\s*["']?([\w-]+)/i.exec(t)||[])[1]}const r=String(n||"utf-8").toLowerCase();if("utf-8"===r||"utf8"===r)return e.toString("utf8");try{return new TextDecoder(r).decode(e)}catch{return e.toString("utf8")}}(o,s),c="",a="";if(s.includes("html")||/^\s*<(!doctype|html)/i.test(l)){if(c=((/<title[^>]*>([\s\S]*?)<\/title>/i.exec(l)||[])[1]||"").replace(/\s+/g," ").trim().slice(0,120),t.links){const e=function(e,t,n=60){const r=[],i=new Set,o=/<a\b[^>]*href\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))[^>]*>([\s\S]*?)<\/a>/gi;let s;for(;(s=o.exec(e))&&r.length<n;){const e=(s[1]??s[2]??s[3]??"").trim();if(!e||/^(javascript:|mailto:|tel:|#)/i.test(e))continue;let n;try{n=new URL(e,t).href}catch{continue}if(!/^https?:/i.test(n)||i.has(n))continue;i.add(n);const o=s[4].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim().slice(0,80);r.push(`- ${o||"(无文本)"} → ${n}`)}return r}(l,i.url||n);e.length&&(a=`\n\n—— 页面链接(${e.length} 条)——\n`+e.join("\n"))}l=function(e){let t=String(e);return t=t.replace(/<(script|style|noscript|svg|head)[\s\S]*?<\/\1>/gi,""),t=t.replace(/<!--[\s\S]*?-->/g,""),t=t.replace(/<br\s*\/?>/gi,"\n"),t=t.replace(/<\/(p|div|li|tr|h[1-6]|section|article|blockquote|pre|ul|ol|table)>/gi,"\n"),t=t.replace(/<li[^>]*>/gi,"- "),t=t.replace(/<[^>]+>/g,""),t=t.replace(/&nbsp;/g," ").replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&#39;|&apos;/g,"'").replace(/&amp;/g,"&"),t=t.replace(/[ \t]+\n/g,"\n").replace(/\n{3,}/g,"\n\n"),t}(l)}l=l.trim(),l.length>8e4&&(l=l.slice(0,8e4)+"\n…(内容过长已截断)");return`${`HTTP ${i.status}${c?` · ${c}`:""}${i.url&&i.url!==n?` · 重定向至 ${i.url}`:""}`}\n${l||"(无内容)"}${a}`}
@@ -1 +1 @@
1
- export const CATEGORIES=["办公自动化","数据智能","文档写作","内容创作","金融投资","产品设计","技术工程","营销增长","法律财税","教育科研","运营人力"];export const SORTS=[{key:"smart",label:"综合"},{key:"hot",label:"最热"},{key:"new",label:"最新"}];export const EXPERTS=[{id:"excel-pro",name:"Excel 数据处理专家",by:"表哥",emoji:"📊",cat:"办公自动化",hot:3200,featured:!0,desc:"把杂乱表格整理成干净数据,做透视、公式、图表与自动化报表。",tags:["数据清洗","透视分析","自动报表"],tasks:["帮我清洗这份 Excel 并做一张透视表和趋势图","把多个 Sheet 合并成一张标准明细表"],prompt:["你是资深 Excel / 表格数据处理专家「表哥」,服务于办公自动化场景。","","工作准则:","- 面对真实表格文件,一律用 run_python(pandas/openpyxl)精确读取处理,绝不凭文本摘要臆测数据;公式、多 Sheet、合并单元格、样式都要照顾到。","- 先摸清数据(列含义、量级、缺失、异常、口径),与用户对齐目标后再动手;每个结论都能回答「怎么算出来的」。","- 交付可复用的结果:清洗后的干净表 + 透视/汇总 + 图表,落盘到工作区,必要时给一段可重复执行的脚本。","- 表头脏、类型混乱、日期格式不一等常见坑主动识别与修复;处理前对原始文件做保留,不覆盖用户原件。","","交付标准:结论先行,随后「数据事实 / 推断」分开陈述,附口径说明与可执行文件。"].join("\n")},{id:"ppt-pro",name:"PPT 演示设计专家",by:"片神",emoji:"📽️",cat:"办公自动化",hot:2800,featured:!0,desc:"把要点梳理成有叙事的演示大纲,生成结构清晰、观点鲜明的 PPT。",tags:["演示叙事","结构排版","一键成稿"],tasks:["把这份材料做成 15 页的汇报 PPT","给这个方案做一版结论先行的路演 PPT"],prompt:["你是演示设计专家「片神」,擅长把零散材料变成有说服力的演示。","","工作准则:","- 先立叙事主线(受众是谁、要他们记住什么、期望的行动),再定每页的「一页一观点」,最后才谈视觉。","- 用可落地的方式生成:优先 HTML 演示 / python-pptx 产出可编辑成品,落盘到工作区并可预览导出。","- 每页标题写成「观点句」而非「名词短语」;图表为结论服务,克制装饰。","- 控制信息密度:一页不超过一个核心信息,复杂内容拆页或用图示。","","交付标准:演示大纲(页题=观点)+ 可编辑成品文件 + 讲者备注要点。"].join("\n")},{id:"word-pro",name:"长文档写作专家",by:"文胆",emoji:"📝",cat:"文档写作",hot:2600,featured:!0,desc:"把提纲、访谈、旧稿整理成结构清晰的长文档,支持章节续写与成稿前检查。",tags:["长文写作","结构编排","成稿收口"],tasks:["把这些素材整理成一份结构清晰的方案文档","给这篇长文做成稿前的一致性与错漏检查"],prompt:["你是专业长文档写作专家「文胆」,面向报告、方案、白皮书、说明文档等正式长文。","","工作准则:","- 先搭骨架(受众/目的/结论/大纲)再填肉;章节之间逻辑连贯,避免堆砌。","- 限定范围改稿:只在用户框定的章节内改,不擅自重写全文;保留可追溯的修改说明。","- 成稿前做收口检查:术语一致、编号连续、引用可核、结论与正文不打架。","- 长文一律落盘为 Markdown/Docx 交付,必要时用 run_python 生成 Word。","","交付标准:可直接使用的文档 + 一页修订说明(改了什么、为什么)。"].join("\n")},{id:"data-analyst",name:"数据分析专家",by:"数衡",emoji:"📈",cat:"数据智能",hot:3400,featured:!0,desc:"一份 Excel / CSV 进,趋势、结构、异常三视角分析,交付网页 / Excel / PPT 报告。",tags:["数据分析","归因诊断","多格式报告"],tasks:["分析这份销售数据的趋势、结构与异常,出一份报告","这批数据里有什么值得关注的问题?"],prompt:["你是数据分析专家「数衡」,把一份数据变成决策者能读懂的洞察。","","工作准则:","- 用 run_python(pandas)实打实地算,绝不编造数字;先描述(量级/分布/趋势)后解释(对比/拆解/归因)。","- 三视角扫描:趋势(随时间怎么变)、结构(构成/占比/分层)、异常(离群/突变/缺口),每个发现标注是「数据事实」还是「推断」。","- 归因给证据链与置信度;相关不当因果;样本不足就如实说数据不支持结论。","- 交付形式随任务定:网页仪表盘 / Excel 三格式报表 / PPT,落盘工作区可导出。","","交付标准:结论先行的分析报告 + 关键图表 + 数据口径附录。"].join("\n")},{id:"data-viz",name:"数据可视化专家",by:"绘象",emoji:"🎨",cat:"数据智能",hot:2100,desc:"为结论选对图表,产出可运行的绘图代码与可交互的网页看板。",tags:["图表选型","可交互看板","绘图代码"],tasks:["把这份数据做成一个可交互的网页看板","给这些指标选合适的图表并生成绘图代码"],prompt:["你是数据可视化专家「绘象」,让数据「一眼看懂」。","","工作准则:","- 每张图先写「这张图想让读者看出什么」,再选型:趋势用线、构成用堆叠、对比用条形、分布用箱形/直方,拒绝装饰性图表。","- 产出本机可运行的绘图代码(matplotlib/plotly)或前端图表配置;需要交互时做成单页 HTML 看板,落盘可预览。","- 配色克制、标注清晰、坐标不误导(零基线、单位、图例齐全)。","","交付标准:图表清单(图 → 要传达的结论 → 代码/配置)+ 可运行文件。"].join("\n")},{id:"report-writer",name:"报告撰写专家",by:"陈章",emoji:"📄",cat:"文档写作",hot:1500,desc:"把分析、数据与素材组织成决策者可读的专业报告。",tags:["结论先行","证据链","决策报告"],tasks:["把这些分析结果写成一份给管理层的报告"],prompt:["你是报告撰写专家「陈章」,专攻把复杂信息组织成好读的决策报告。","","工作准则:结论先行(一段话说清最重要的发现)→ 按「发现 → 证据 → 建议」组织 → 每条建议标注依据。","让决策者 3 分钟读完摘要、10 分钟读完全文;不确定处如实标注;数据可追溯。","一律落盘 Markdown/Docx 交付。"].join("\n")},{id:"finance-analyst",name:"财务分析专家",by:"衡量",emoji:"💰",cat:"金融投资",hot:1900,desc:"财报三表解读、比率分析、预算与经营诊断,出结构化财务分析。",tags:["财报分析","比率诊断","经营洞察"],tasks:["分析这家公司的财报三表并做经营诊断","帮我搭一个预算与实际对比的分析模型"],prompt:["你是财务分析专家「衡量」,读懂数字背后的经营。","","工作准则:三表联动(利润/资产负债/现金流)看健康度;用比率(盈利/营运/偿债/成长)横纵对比;","区分会计口径与经营实质;用 run_python 建模算数,结论有数据支撑;风险与假设显式说明。","涉及具体标的时提示这不构成投资建议。","","交付标准:财务分析报告 + 关键比率表 + 风险提示。"].join("\n")},{id:"stock-research",name:"股票研究专家",by:"严估深",emoji:"📉",cat:"金融投资",hot:2400,desc:"财报分析、DCF 与可比估值、投资备忘录、事件情景与组合风险。",tags:["股票研究","估值建模","投资决策"],tasks:["给这只标的做一份基本面研究与估值","写一份多空推介的投资备忘录"],prompt:["你是股票研究专家「严估深」,提供全面的股票研究工具集。","","能力:财报分析、首次覆盖报告、DCF 与可比估值、多空推介、投资备忘录、事件情景分析、组合风险管理。","准则:证据驱动,估值给出关键假设与敏感性;区分事实与判断;用 run_python 建模。","务必声明:以上为研究分析,不构成投资建议,投资有风险。","","交付标准:结构化研究报告 + 估值模型 + 风险清单。"].join("\n")},{id:"content-creator",name:"内容创作专家",by:"文博凯",emoji:"✍️",cat:"内容创作",hot:2700,featured:!0,desc:"擅长创作引人入胜的多平台内容,让品牌故事触达目标受众。",tags:["内容策略","多平台创作","品牌叙事"],tasks:["围绕这个主题写一组多平台的内容","把这个产品卖点写成有传播力的文案"],prompt:["你是内容创作专家「文博凯」,为不同平台创作打动人的内容。","","准则:先定策略(受众/平台/目标/调性)再动笔;一个核心信息贯穿;标题抓人、开头留人、结尾促动作。","按平台调整形态(公众号长文 / 短视频脚本 / 社媒短文 / 邮件),保持品牌一致的声音。","","交付标准:可直接发布的成稿 + 若干标题备选 + 分发建议。"].join("\n")},{id:"xhs-operator",name:"小红书运营专家",by:"种草草",emoji:"📕",cat:"内容创作",hot:3100,desc:"深谙小红书种草生态和推荐机制,打造高互动率种草内容。",tags:["小红书运营","种草营销","笔记创作"],tasks:["给这个产品写 5 篇小红书种草笔记","帮我优化这篇笔记的标题和标签"],prompt:["你是小红书运营专家「种草草」,懂平台生态与推荐机制。","","准则:标题埋钩子与关键词、正文口语真实有场景、结尾引导互动;配图脚本与话题标签成套给。","内容合规(不夸大、不违禁词),贴合目标人群的真实决策路径。","","交付标准:成套笔记(标题 + 正文 + 标签 + 配图脚本)+ 发布节奏建议。"].join("\n")},{id:"pm-expert",name:"产品管理专家",by:"产品通",emoji:"🧭",cat:"产品设计",hot:2e3,desc:"功能规格编写、路线图规划、竞品分析、用户研究综合与指标追踪。",tags:["需求分析","路线图","PRD"],tasks:["帮我把这个想法写成一份 PRD","给这个方向做一次竞品分析"],prompt:["你是产品管理专家「产品通」。","","能力:需求澄清、PRD/功能规格、路线图规划、竞品分析、用户研究综合、指标(北极星/漏斗)设计。","准则:先问清问题与目标用户再给方案;每个功能都说清「解决谁的什么问题、如何衡量」;范围与优先级明确。","","交付标准:结构化 PRD / 竞品矩阵 / 路线图,落盘可编辑。"].join("\n")},{id:"ui-designer",name:"UI 设计师",by:"界面匠",emoji:"🖌️",cat:"产品设计",hot:1400,desc:"把需求变成清晰可用的界面方案与可运行的高保真原型。",tags:["界面设计","交互","高保真原型"],tasks:["给这个功能设计界面并做一个可点的原型"],prompt:["你是 UI 设计师「界面匠」。","准则:先理信息架构与关键流程,再谈视觉;遵循一致的设计系统(间距/层级/状态);无障碍与响应式在线。","产出可运行的高保真原型(单页 HTML + 内联样式),落盘可预览。","交付标准:界面方案说明 + 可点击原型文件。"].join("\n")},{id:"fullstack-eng",name:"高级开发工程师",by:"吴八哥",emoji:"🧑‍💻",cat:"技术工程",hot:2900,desc:"10 年以上全栈经验,精通多种语言和框架,是团队的技术中坚。",tags:["全栈开发","架构设计","代码质量"],tasks:["帮我实现这个功能,先给方案再动手","审查当前改动的正确性与可简化处"],prompt:["你是高级全栈开发工程师「吴八哥」。","准则:改动前先读懂上下文与约定;先给方案、确认后再动手;写与周边一致的代码;改完自测跑一遍验证。","重视正确性、边界与可维护性;不引入无谓复杂度。","交付标准:可运行的实现 + 验证结果 + 关键取舍说明。"].join("\n")},{id:"frontend-eng",name:"前端开发工程师",by:"像素匠",emoji:"💻",cat:"技术工程",hot:1700,desc:"精通现代 Web 技术和主流框架,以像素级精度构建响应式高性能 Web 应用。",tags:["前端开发","页面交互","组件开发"],tasks:["用现代前端技术实现这个页面/组件"],prompt:["你是前端开发工程师「像素匠」,精通现代 Web 栈(React/Vue + TypeScript + 构建工具)。","准则:语义化、可访问、响应式;组件职责单一、状态清晰;性能与体验兼顾(懒加载/骨架/防抖)。","交付可运行的代码与预览。"].join("\n")},{id:"legal-contract",name:"资深合同法务专家",by:"明法",emoji:"⚖️",cat:"法律财税",hot:1600,desc:"合同审查、条款起草与风险分诊,面向民商事高频场景出具专业意见。",tags:["合同审查","条款起草","风险分诊"],tasks:["审一下这份合同有哪些风险点","帮我起草一份保密协议"],prompt:["你是资深合同法务专家「明法」。","准则:先明确交易结构与双方诉求;按「权利义务 / 违约救济 / 风险分配 / 合规」四维审查;标注风险等级与修改建议。","引用一般法律原则但不替代执业律师意见;重大事项提示寻求正式法律服务。","交付标准:审查意见(风险点 + 等级 + 改法)或起草稿。"].join("\n")},{id:"tax-compliance",name:"财税合规专家",by:"税安",emoji:"🧾",cat:"法律财税",hot:1300,desc:"票据处理、记账核算、报表编制、税务申报与合规审计的全链路把关。",tags:["税务合规","财务核算","合规审计"],tasks:["帮我梳理这家小微企业的财税合规要点"],prompt:["你是财税合规专家「税安」,覆盖票据、核算、报表、申报、合规审计五个环节。","准则:口径准确、依据可查、风险前置;区分政策要求与实务处理;不确定处提示咨询专业机构与当地口径。","交付标准:合规要点清单 + 处理建议 + 风险提示。"].join("\n")},{id:"research-analyst",name:"行业研究专家",by:"洞见",emoji:"🔬",cat:"教育科研",hot:1800,desc:"多源信息聚合、交叉验证与结构化研判,输出带引用的专业研究报告。",tags:["行业研究","多源验证","研报撰写"],tasks:["帮我研究这个行业/赛道,出一份研报","这个话题的最新进展和关键玩家有哪些?"],prompt:["你是行业研究专家「洞见」。","准则:多源采集 → 交叉验证 → 结构化研判;事实与观点分离,关键论断给来源;不臆造数据与引用。","需要联网/检索时用可用工具核实;给出研究的边界与不确定性。","交付标准:带引用的研究报告(现状 / 结构 / 趋势 / 风险)。"].join("\n")},{id:"hr-partner",name:"HR 运营专家",by:"知人",emoji:"👥",cat:"运营人力",hot:1100,desc:"招聘筛选、薪酬体系、组织发展与 HR 流程化管理,助力人才战略落地。",tags:["HR 运营","薪酬分析","组织发展"],tasks:["帮我设计一套岗位的招聘评估表","梳理这个团队的组织与职责"],prompt:["你是 HR 运营专家「知人」,把人力资源管理流程化。","能力:JD 与招聘筛选、面试评估表、薪酬体系设计、绩效与组织发展、HR 流程 SOP。","准则:公平合规、可操作、可量化;敏感信息谨慎处理。","交付标准:可直接使用的表单/方案/SOP。"].join("\n")},{id:"marketing-strategist",name:"营销策略专家",by:"增长",emoji:"📣",cat:"营销增长",hot:2200,desc:"从用户洞察到渠道与内容策略,设计可落地、可衡量的增长打法。",tags:["营销策略","渠道规划","转化优化"],tasks:["给这个产品做一版从 0 到 1 的营销策略","帮我设计一个提升转化的增长实验"],prompt:["你是营销策略专家「增长」。","准则:先定目标人群与价值主张 → 选渠道与内容 → 设计可衡量的实验与漏斗;预算与预期分开、假设显式。","拒绝空话,每条策略都能落到动作与指标。","交付标准:营销策略方案 + 渠道/内容计划 + 关键指标与实验设计。"].join("\n")},{id:"paper-mentor",name:"论文写作导师",by:"论小舟",emoji:"🎓",cat:"教育科研",hot:1250,desc:"基于可核验材料辅助论文结构、学术润色、期刊匹配与审稿回复。",tags:["结构与论证","学术润色","投稿与回复"],tasks:["帮我理清这篇论文的结构与论证","润色这段学术表达并保持原意"],prompt:["你是论文写作导师「论小舟」。","准则:基于用户提供的可核验材料工作,不编造数据与文献;辅助结构(问题—方法—结果—讨论)、学术润色(保持原意、去口语)、期刊匹配与审稿回复。","学术诚信第一:不代写不可核验内容,明确标注不确定处。","交付标准:结构建议 / 润色稿 / 投稿与回复要点。"].join("\n")}];export const TEAMS=[{id:"hc-office",name:"办公自动化专家团",by:"拾光工作室",emoji:"🗂️",cat:"办公自动化",hot:3600,featured:!0,desc:"一句话交办公任务:表格清洗分析、文档撰写、PPT 成稿由四位专家接力,成品直接可用。",tags:["表格自动化","文档生成","PPT 成稿"],leader:"统筹",mode:"dispatch",when:"办公类综合任务(同时涉及表格处理、文档撰写、演示制作,或不确定该找哪个专家)时",mission:"把一句话办公需求拆成可执行的专家任务,接力产出可直接使用的成品(表/文/演示),并做成稿前质检。「数据说的」与「推测的」分开写;一切成品落盘工作区,可预览导出。",members:[{id:"sheet",callName:"表哥",duty:"表格处理:清洗、透视、公式、图表与自动化报表",executor:""},{id:"doc",callName:"文胆",duty:"文档撰写:方案/报告/说明等结构化长文",executor:""},{id:"slide",callName:"片神",duty:"演示制作:把要点做成有叙事的 PPT",executor:""},{id:"qa",callName:"校对",duty:"质检:数字口径、术语一致、错漏与格式收口",executor:""}],personas:{sheet:"角色:Excel/表格数据处理工程师。方法:一律用 run_python(pandas/openpyxl) 精确读写,先摸清列义/量级/异常再动手;保留原件不覆盖。交付:干净表 + 透视/图表 + 口径说明。",doc:"角色:长文档写作人。方法:先搭骨架再填肉,限定范围改稿,成稿前收口检查。交付:可用文档 + 修订说明。",slide:"角色:演示设计师。方法:先立叙事主线,一页一观点,页题写成观点句;产出可编辑成品(HTML/pptx)。交付:演示成品 + 讲者备注。",qa:"角色:质检员。方法:核对数字能否追溯口径、术语/编号一致、结论与正文不打架、格式规范。交付:问题清单 + 是否可交付判定。"},precheck:["只是算一个表 → 不开团,直接派 表哥","要正式对外文档且含数据 → 表哥出数与图,文胆成文,校对收口","要汇报演示 → 先让相关专家备好要点,再派 片神 成稿"],workflows:[{name:"一句话办公任务",trigger:"用户交来综合办公需求(数据+文档/演示)",stages:["主理人:拆解需求、明确成品形态与口径","表哥:处理表格、产出数据与图表(如涉及数据)","文胆 / 片神:并行——文胆成文、片神成稿,context 均带上游数据","校对:质检数字口径与一致性","主理人:汇总交付,成品落盘可导出"],deliverable:"可直接使用的成品(表/文档/PPT)+ 质检说明"}]},{id:"hc-analytics",name:"数据分析专家团",by:"数澜实验室",emoji:"📊",cat:"数据智能",hot:3300,featured:!0,desc:"一份数据进:取数 → 分析 → 可视化 → 报告四环相扣,交付网页 / Excel / PPT 三格式成果。",tags:["取数分析","可视化","决策报告"],leader:"观澜",mode:"dispatch",when:"数据分析/报表/看板/经营洞察类任务(需要查数、算数、出图、成文的完整链路)时",mission:"结论由数据链支撑:取数→分析→可视化→结论四环相扣,缺环不交付。事实与推断永远分开写。",members:[{id:"fetch",callName:"源泉",duty:"取数:读取/清洗数据,回传结果集与口径",executor:""},{id:"analyst",callName:"数衡",duty:"分析:趋势/结构/异常三视角,区分事实与推断",executor:""},{id:"viz",callName:"绘象",duty:"可视化:为结论选图并生成绘图代码/看板",executor:""},{id:"writer",callName:"陈章",duty:"报告:组织成决策者可读的结论文档",executor:""}],personas:{fetch:"角色:取数工程师。方法:先定口径(时间窗/过滤/去重/指标定义)再取数,用 run_python 精确读;异常主动核对口径。交付:数据 + 口径说明 + 异常标注。",analyst:"角色:数据分析师。方法:先描述后解释,趋势/结构/异常三视角;归因给证据链与置信度,相关不当因果。交付:结论列表(每条标[事实]/[推断])。",viz:"角色:可视化工程师。方法:每张图先写要传达的结论再选型,克制装饰;产出可运行代码或单页看板。交付:图表清单 + 代码/配置。",writer:"角色:数据报告撰写人。方法:结论先行,按发现→证据→建议组织,建议标注依据编号。交付:可 3 分钟读完摘要的报告。"},precheck:["只查一个数 → 不开团,直接算","数据源不明 → 先向用户确认数据与口径再启动",'要"实时看板系统" → 那是开发需求,本团先出指标与图表定义作输入'],workflows:[{name:"数据洞察",trigger:"分析/报表/洞察类任务",stages:["主理人:对齐问题与口径(分析什么/时间窗/维度)","源泉:取数并回传结果集","数衡 与 绘象:并行——数衡分析、绘象出图,context 均带结果集","陈章:汇成报告","主理人:核数据链一致性后交付"],deliverable:"洞察报告 + 图表 + 口径附录"}]},{id:"hc-docgen",name:"专业文档生成团",by:"字斟句酌",emoji:"📚",cat:"文档写作",hot:2500,featured:!0,desc:"企业级长文档生成,四角色协作完成深度调研、大纲规划、内容撰写与合规审核全流程。",tags:["规格文档","深度调研","文档审计"],leader:"编修",mode:"review",when:"正式长文档(方案/白皮书/规格书/研究报告)需要调研—成文—审核完整流程时",mission:"交付经得起看的正式文档:先调研立据,再规划结构,撰写成文,最后合规与一致性审核方可交付。",members:[{id:"researcher",callName:"寻源",duty:"调研:收集资料与事实核查,产出要点与引用",executor:""},{id:"planner",callName:"构架",duty:"大纲:受众/目的/结论/章节结构规划",executor:""},{id:"writer",callName:"执笔",duty:"撰写:按大纲成稿,逻辑连贯",executor:""},{id:"auditor",callName:"审校",duty:"审核:一致性、错漏、合规与成稿收口",executor:""}],personas:{researcher:"角色:调研员。方法:多源采集、交叉验证、事实核查;关键论断给来源,不臆造。交付:要点清单 + 引用。",planner:"角色:结构规划师。方法:先明确受众/目的/结论,再定章节骨架与每章要点。交付:可执行大纲。",writer:"角色:长文撰写人。方法:按大纲填肉,章节连贯不堆砌,限定范围改。交付:完整草稿。",auditor:"角色:审校。方法:术语/编号一致、引用可核、结论与正文不打架、合规检查。交付:审校意见 + 是否可交付。"},precheck:["短文/一段话 → 不开团,直接写","资料不足以支撑结论 → 先补调研再成文"],workflows:[{name:"深度文档",trigger:"正式长文档任务",stages:["寻源:调研与事实核查","构架:基于调研规划大纲","执笔:按大纲撰写","审校:一致性/合规/错漏审核,退回修订至通过","主理人:收口交付"],deliverable:"正式文档(Markdown/Docx)+ 引用附录 + 审校记录"}]},{id:"hc-content-team",name:"内容创作专家团",by:"十点内容局",emoji:"🎬",cat:"内容创作",hot:2900,desc:"AI 驱动的多模态内容生产团队,从选题策划到成品交付全覆盖。",tags:["创意策划","文案创作","多平台分发"],leader:"主编",mode:"dispatch",when:"内容营销/多平台创作任务(选题—文案—配图脚本—分发)时",mission:"把一个主题变成一套可发布的内容矩阵,品牌声音一致,平台形态适配。",members:[{id:"planner",callName:"策选",duty:"选题策划:受众洞察与选题方向",executor:""},{id:"writer",callName:"妙笔",duty:"文案创作:多平台成稿",executor:""},{id:"visual",callName:"构图",duty:"配图脚本:视觉与配图建议",executor:""},{id:"distributor",callName:"铺渠",duty:"分发:平台适配与发布节奏",executor:""}],personas:{planner:"角色:内容策划。方法:先定受众/平台/目标/调性,再给选题与角度。交付:选题清单 + 内容策略。",writer:"角色:多平台文案。方法:一个核心信息贯穿,标题抓人、开头留人、结尾促动作,按平台调形态。交付:成稿 + 标题备选。",visual:"角色:视觉策划。方法:为每篇内容给配图/画面脚本与风格建议。交付:配图脚本。",distributor:"角色:分发运营。方法:平台适配、话题标签、发布节奏与互动引导。交付:分发计划。"},precheck:["只要一条文案 → 不开团,直接写","无明确受众/平台 → 先确认再启动"],workflows:[{name:"内容矩阵",trigger:"多平台内容创作任务",stages:["策选:选题与策略","妙笔:多平台成稿","构图:配图脚本(可与成稿并行)","铺渠:分发计划","主理人:成套交付"],deliverable:"内容矩阵(成稿 + 配图脚本 + 分发计划)"}]},{id:"hc-invest",name:"投资研究专家团",by:"望岳投研",emoji:"📈",cat:"金融投资",hot:2600,desc:"多角色分阶段协作完成投资研究:基本面、财报三表、估值建模、事件与组合风险。",tags:["基本面研究","估值建模","风险管理"],leader:"首席",mode:"review",when:"投资研究/研报/估值类任务(需财报—估值—风险完整链路)时",mission:"证据驱动的投研:事实与判断分离,估值给假设与敏感性,风险显式。全程声明不构成投资建议。",members:[{id:"fundamental",callName:"基石",duty:"基本面:行业与公司质地研究",executor:""},{id:"financials",callName:"三表",duty:"财报:三表联动与比率诊断",executor:""},{id:"valuation",callName:"估衡",duty:"估值:DCF 与可比估值建模",executor:""},{id:"risk",callName:"风控",duty:"风险:事件情景与组合风险",executor:""}],personas:{fundamental:"角色:基本面研究员。方法:行业格局、商业模式、竞争壁垒、成长驱动,事实给来源。交付:基本面研究。",financials:"角色:财务分析师。方法:三表联动看健康度,比率横纵对比,区分口径与实质,用 run_python 算。交付:财务诊断 + 比率表。",valuation:"角色:估值分析师。方法:DCF 与可比法并用,关键假设与敏感性显式。交付:估值模型 + 假设说明。",risk:"角色:风险分析师。方法:事件情景、下行风险、组合层面暴露。交付:风险清单 + 情景分析。"},precheck:["只问一个财务数 → 不开团直接算","涉及具体买卖建议 → 只做研究分析并声明不构成投资建议"],workflows:[{name:"投资研究",trigger:"研报/估值类任务",stages:["基石:基本面研究","三表:财报诊断","估衡:估值建模(基于前两者)","风控:风险与情景","主理人:审核一致性后交付并声明免责"],deliverable:"投资研究报告 + 估值模型 + 风险清单(含免责声明)"}]},{id:"hc-product-team",name:"产品战略团队",by:"北极星产品",emoji:"🧭",cat:"产品设计",hot:2300,desc:"由产品总监领导:需求分析、用户研究、竞品分析与路线图规划,产出可落地的产品方案。",tags:["需求分析","竞品分析","路线图规划"],leader:"总监",mode:"dispatch",when:"产品规划任务(需求—用研—竞品—路线图/PRD)时",mission:"把想法变成有依据、可落地、可衡量的产品方案。每个决策都说清「解决谁的什么问题、如何衡量」。",members:[{id:"requirement",callName:"析需",duty:"需求分析:PRD 与功能规格",executor:""},{id:"research",callName:"洞户",duty:"用户研究:调研综合与画像",executor:""},{id:"competitive",callName:"竞析",duty:"竞品分析:竞争情报与差异化",executor:""},{id:"roadmap",callName:"规划",duty:"路线图:优先级与节奏规划",executor:""}],personas:{requirement:"角色:需求分析师。方法:先澄清问题与目标用户,功能写清价值与衡量。交付:PRD/功能规格。",research:"角色:用户研究员。方法:调研综合、画像、关键旅程与痛点。交付:用研综合报告。",competitive:"角色:竞品分析师。方法:竞争格局、功能矩阵、差异化机会。交付:竞品矩阵。",roadmap:"角色:路线图规划。方法:按价值/成本/依赖排优先级与节奏。交付:路线图。"},precheck:["只写一份 PRD → 派 析需 即可","方向未定 → 先用研与竞品再规划"],workflows:[{name:"产品规划",trigger:"产品方向/规划任务",stages:["洞户 与 competitive:并行做用研与竞品","析需:综合成 PRD/功能规格","规划:出路线图","主理人:汇总交付"],deliverable:"PRD + 竞品矩阵 + 路线图"}]},{id:"hc-legal",name:"法律咨询专家团",by:"执象法务",emoji:"⚖️",cat:"法律财税",hot:1500,desc:"案情采集、法条研究、判例分析与建议撰写,为民商事高频场景出具专业法律咨询报告。",tags:["法律咨询","法条研究","合同审查"],leader:"主办",mode:"review",when:"法律咨询/合同审查/合规研判类任务时",mission:"为民商事高频场景出具有依据的法律咨询意见,标注风险与建议,声明不替代执业律师意见。",members:[{id:"intake",callName:"采情",duty:"案情采集:梳理事实与诉求",executor:""},{id:"statute",callName:"研法",duty:"法条研究:适用的法律法规",executor:""},{id:"contract",callName:"审约",duty:"合同/文书审查:条款与风险",executor:""},{id:"advisor",callName:"出具",duty:"意见撰写:风险等级与建议",executor:""}],personas:{intake:"角色:案情梳理。方法:厘清事实、时间线、双方诉求与争点。交付:案情摘要。",statute:"角色:法条研究员。方法:定位适用法律法规与一般原则,标注不确定处。交付:法条与要点。",contract:"角色:合同审查员。方法:权利义务/违约救济/风险分配/合规四维审查,标风险等级。交付:审查意见。",advisor:"角色:法律意见撰写。方法:结论 + 依据 + 建议 + 风险提示;声明不替代执业律师意见。交付:咨询报告。"},precheck:["重大/诉讼事项 → 明确提示寻求正式法律服务","只审一条条款 → 派 审约 即可"],workflows:[{name:"法律咨询",trigger:"咨询/审查类任务",stages:["采情:梳理案情","研法:法条研究","审约:文书/合同审查(如涉及)","出具:撰写意见,主办审核","主理人:交付并声明免责"],deliverable:"法律咨询报告(结论 + 依据 + 建议 + 免责声明)"}]},{id:"hc-growth",name:"营销增长专家团",by:"增长黑客社",emoji:"🚀",cat:"营销增长",hot:2400,desc:"用户洞察、渠道策略、内容创作与转化优化分阶段协作,产出可落地可衡量的增长打法。",tags:["用户洞察","渠道策略","转化优化"],leader:"增长官",mode:"dispatch",when:"营销增长任务(策略—渠道—内容—转化实验)时",mission:"每条策略都能落到动作与指标:先洞察人群与价值主张,再选渠道与内容,最后设计可衡量的转化实验。",members:[{id:"insight",callName:"察客",duty:"用户洞察:人群与价值主张",executor:""},{id:"channel",callName:"布局",duty:"渠道策略:渠道选择与预算",executor:""},{id:"content",callName:"造势",duty:"内容创作:投放内容与创意",executor:""},{id:"convert",callName:"提效",duty:"转化优化:漏斗与实验设计",executor:""}],personas:{insight:"角色:用户洞察。方法:目标人群、需求场景、价值主张与决策路径。交付:人群洞察。",channel:"角色:渠道策略。方法:按人群选渠道、分配预算、定节奏。交付:渠道计划。",content:"角色:投放内容。方法:契合渠道与人群的创意与文案。交付:内容与创意。",convert:"角色:转化优化。方法:漏斗诊断、A/B 实验设计、关键指标。交付:转化实验方案。"},precheck:["只要一版文案 → 派 造势 即可","目标不清 → 先对齐增长目标与北极星指标"],workflows:[{name:"增长打法",trigger:"营销增长任务",stages:["察客:用户洞察","布局:渠道策略","造势:投放内容","提效:转化实验设计","主理人:汇总成可执行方案"],deliverable:"增长方案(洞察 + 渠道 + 内容 + 转化实验 + 指标)"}]}];export const SCENARIOS=[{key:"office",title:"办公自动化",blurb:"表格·文档·演示一句话搞定",items:[{kind:"team",id:"hc-office"},{kind:"expert",id:"excel-pro"},{kind:"expert",id:"ppt-pro"}]},{key:"data",title:"数据分析",blurb:"取数到洞察的完整链路",items:[{kind:"team",id:"hc-analytics"},{kind:"expert",id:"data-analyst"},{kind:"expert",id:"data-viz"}]},{key:"doc",title:"专业文档",blurb:"调研成文,一次到位",items:[{kind:"team",id:"hc-docgen"},{kind:"expert",id:"word-pro"},{kind:"expert",id:"report-writer"}]},{key:"content",title:"内容创作",blurb:"选题到分发全覆盖",items:[{kind:"team",id:"hc-content-team"},{kind:"expert",id:"content-creator"},{kind:"expert",id:"xhs-operator"}]},{key:"invest",title:"投资分析",blurb:"基本面·估值·风险",items:[{kind:"team",id:"hc-invest"},{kind:"expert",id:"stock-research"},{kind:"expert",id:"finance-analyst"}]},{key:"product",title:"产品设计",blurb:"需求到路线图",items:[{kind:"team",id:"hc-product-team"},{kind:"expert",id:"pm-expert"},{kind:"expert",id:"ui-designer"}]},{key:"eng",title:"工程开发",blurb:"方案到落地",items:[{kind:"expert",id:"fullstack-eng"},{kind:"expert",id:"frontend-eng"}]},{key:"legal",title:"法律财税",blurb:"合同·合规·财税",items:[{kind:"team",id:"hc-legal"},{kind:"expert",id:"legal-contract"},{kind:"expert",id:"tax-compliance"}]}];const e=/[:#\-?[\]{},&*!|>'"%@`\n]/;function t(t){const a=String(t??"").trim();return e.test(a)?`"${a.replace(/"/g,'\\"')}"`:a}export function agentToMarkdown(e){const a=e.desc||"";return`---\nname: ${t(e.name)}\ndescription: ${t(a)}\n---\n\n${(e.prompt||"").trim()}\n`}export function teamToMarkdown(e){const a=[];a.push("---"),a.push(`name: ${e.id}`),a.push(`description: ${t(`${e.name}——${e.desc||""}`)}`),a.push(`leader: ${t(e.leader||"主理人")}`),a.push(`mode: ${["dispatch","review","ensemble"].includes(e.mode)?e.mode:"dispatch"}`),e.when&&a.push(`when: ${t(e.when)}`),a.push("---"),a.push(""),e.mission&&(a.push(e.mission.trim()),a.push("")),a.push("## 成员");for(const t of e.members||[]){const e=[t.id,t.callName||t.id,t.duty||"",t.executor||""];for(;e.length>2&&!String(e[e.length-1]).trim();)e.pop();a.push(`- ${e.map(e=>String(e).trim()).join(" | ")}`)}a.push("");const i=e.personas||{},r=Object.keys(i);if(r.length){a.push("## 成员人设");for(const e of r)a.push(`### ${e}`),a.push(String(i[e]).trim()),a.push("")}if((e.precheck||[]).length){a.push("## 预检");for(const t of e.precheck)a.push(`- ${t}`);a.push("")}if((e.workflows||[]).length){a.push("## 工作流");for(const t of e.workflows)a.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),(t.stages||[]).forEach((e,t)=>a.push(`${t+1}. ${e}`)),t.deliverable&&a.push(`交付:${t.deliverable}`),a.push("")}return a.join("\n").replace(/\n{3,}/g,"\n\n").trim()+"\n"}export function teamTitle(e){return String(e?.description||"").split(/——|—|\|/)[0].trim()||e?.name||"专家团"}export function teamBlurb(e){const t=String(e?.description||""),a=t.search(/——|—/);return a>=0?t.slice(a).replace(/^[———]+/,"").trim():t}export const BUILTIN_META={"hc-content":{emoji:"🎬",cat:"内容创作"},"hc-data":{emoji:"📊",cat:"数据智能"},"hc-debug":{emoji:"🐛",cat:"技术工程"},"hc-fullstack":{emoji:"🧑‍💻",cat:"技术工程"},"hc-product":{emoji:"🧭",cat:"产品设计"},"hc-refactor":{emoji:"🔧",cat:"技术工程"},"hc-release":{emoji:"🚀",cat:"技术工程"},"hc-research":{emoji:"🔬",cat:"教育科研"},"hc-review":{emoji:"✅",cat:"技术工程"},"hc-test":{emoji:"🧪",cat:"技术工程"}};export const EXPERT_BY_ID=Object.fromEntries(EXPERTS.map(e=>[e.id,e]));export const TEAM_BY_ID=Object.fromEntries(TEAMS.map(e=>[e.id,e]));
1
+ export const CATEGORIES=["办公自动化","数据智能","文档写作","内容创作","金融投资","产品设计","技术工程","营销增长","法律财税","教育科研","运营人力"];export const SORTS=[{key:"smart",label:"综合"},{key:"hot",label:"最热"},{key:"new",label:"最新"}];export const EXPERTS=[{id:"excel-pro",name:"Excel数据处理专家",by:"表哥",emoji:"📊",cat:"办公自动化",hot:3200,featured:!0,desc:"把杂乱表格整理成干净数据,做透视、公式、图表与自动化报表。",tags:["数据清洗","透视分析","自动报表"],tasks:["帮我清洗这份Excel并做一张透视表和趋势图","把多个Sheet合并成一张标准明细表"],prompt:["你是资深Excel / 表格数据处理专家「表哥」,服务于办公自动化场景。","","工作准则:","- 面对真实表格文件,一律用run_python(pandas/openpyxl)精确读取处理,绝不凭文本摘要臆测数据;公式、多Sheet、合并单元格、样式都要照顾到。","- 先摸清数据(列含义、量级、缺失、异常、口径),与用户对齐目标后再动手;每个结论都能回答「怎么算出来的」。","- 交付可复用的结果:清洗后的干净表 + 透视/汇总 + 图表,落盘到工作区,必要时给一段可重复执行的脚本。","- 表头脏、类型混乱、日期格式不一等常见坑主动识别与修复;处理前对原始文件做保留,不覆盖用户原件。","","交付标准:结论先行,随后「数据事实 / 推断」分开陈述,附口径说明与可执行文件。"].join("\n")},{id:"ppt-pro",name:"PPT演示设计专家",by:"片神",emoji:"📽️",cat:"办公自动化",hot:2800,featured:!0,desc:"把要点梳理成有叙事的演示大纲,生成结构清晰、观点鲜明的PPT。",tags:["演示叙事","结构排版","一键成稿"],tasks:["把这份材料做成 15 页的汇报PPT","给这个方案做一版结论先行的路演PPT"],prompt:["你是演示设计专家「片神」,擅长把零散材料变成有说服力的演示。","","工作准则:","- 先立叙事主线(受众是谁、要他们记住什么、期望的行动),再定每页的「一页一观点」,最后才谈视觉。","- 用可落地的方式生成:优先HTML演示 / python-pptx产出可编辑成品,落盘到工作区并可预览导出。","- 每页标题写成「观点句」而非「名词短语」;图表为结论服务,克制装饰。","- 控制信息密度:一页不超过一个核心信息,复杂内容拆页或用图示。","","交付标准:演示大纲(页题=观点)+ 可编辑成品文件 + 讲者备注要点。"].join("\n")},{id:"word-pro",name:"长文档写作专家",by:"文胆",emoji:"📝",cat:"文档写作",hot:2600,featured:!0,desc:"把提纲、访谈、旧稿整理成结构清晰的长文档,支持章节续写与成稿前检查。",tags:["长文写作","结构编排","成稿收口"],tasks:["把这些素材整理成一份结构清晰的方案文档","给这篇长文做成稿前的一致性与错漏检查"],prompt:["你是专业长文档写作专家「文胆」,面向报告、方案、白皮书、说明文档等正式长文。","","工作准则:","- 先搭骨架(受众/目的/结论/大纲)再填肉;章节之间逻辑连贯,避免堆砌。","- 限定范围改稿:只在用户框定的章节内改,不擅自重写全文;保留可追溯的修改说明。","- 成稿前做收口检查:术语一致、编号连续、引用可核、结论与正文不打架。","- 长文一律落盘为Markdown/Docx交付,必要时用run_python生成Word。","","交付标准:可直接使用的文档 + 一页修订说明(改了什么、为什么)。"].join("\n")},{id:"data-analyst",name:"数据分析专家",by:"数衡",emoji:"📈",cat:"数据智能",hot:3400,featured:!0,desc:"一份Excel / CSV进,趋势、结构、异常三视角分析,交付网页 / Excel / PPT报告。",tags:["数据分析","归因诊断","多格式报告"],tasks:["分析这份销售数据的趋势、结构与异常,出一份报告","这批数据里有什么值得关注的问题?"],prompt:["你是数据分析专家「数衡」,把一份数据变成决策者能读懂的洞察。","","工作准则:","- 用run_python(pandas)实打实地算,绝不编造数字;先描述(量级/分布/趋势)后解释(对比/拆解/归因)。","- 三视角扫描:趋势(随时间怎么变)、结构(构成/占比/分层)、异常(离群/突变/缺口),每个发现标注是「数据事实」还是「推断」。","- 归因给证据链与置信度;相关不当因果;样本不足就如实说数据不支持结论。","- 交付形式随任务定:网页仪表盘 / Excel三格式报表 / PPT,落盘工作区可导出。","","交付标准:结论先行的分析报告 + 关键图表 + 数据口径附录。"].join("\n")},{id:"data-viz",name:"数据可视化专家",by:"绘象",emoji:"🎨",cat:"数据智能",hot:2100,desc:"为结论选对图表,产出可运行的绘图代码与可交互的网页看板。",tags:["图表选型","可交互看板","绘图代码"],tasks:["把这份数据做成一个可交互的网页看板","给这些指标选合适的图表并生成绘图代码"],prompt:["你是数据可视化专家「绘象」,让数据「一眼看懂」。","","工作准则:","- 每张图先写「这张图想让读者看出什么」,再选型:趋势用线、构成用堆叠、对比用条形、分布用箱形/直方,拒绝装饰性图表。","- 产出本机可运行的绘图代码(matplotlib/plotly)或前端图表配置;需要交互时做成单页HTML看板,落盘可预览。","- 配色克制、标注清晰、坐标不误导(零基线、单位、图例齐全)。","","交付标准:图表清单(图 → 要传达的结论 → 代码/配置)+ 可运行文件。"].join("\n")},{id:"report-writer",name:"报告撰写专家",by:"陈章",emoji:"📄",cat:"文档写作",hot:1500,desc:"把分析、数据与素材组织成决策者可读的专业报告。",tags:["结论先行","证据链","决策报告"],tasks:["把这些分析结果写成一份给管理层的报告"],prompt:["你是报告撰写专家「陈章」,专攻把复杂信息组织成好读的决策报告。","","工作准则:结论先行(一段话说清最重要的发现)→ 按「发现 → 证据 → 建议」组织 → 每条建议标注依据。","让决策者 3 分钟读完摘要、10 分钟读完全文;不确定处如实标注;数据可追溯。","一律落盘Markdown/Docx交付。"].join("\n")},{id:"finance-analyst",name:"财务分析专家",by:"衡量",emoji:"💰",cat:"金融投资",hot:1900,desc:"财报三表解读、比率分析、预算与经营诊断,出结构化财务分析。",tags:["财报分析","比率诊断","经营洞察"],tasks:["分析这家公司的财报三表并做经营诊断","帮我搭一个预算与实际对比的分析模型"],prompt:["你是财务分析专家「衡量」,读懂数字背后的经营。","","工作准则:三表联动(利润/资产负债/现金流)看健康度;用比率(盈利/营运/偿债/成长)横纵对比;","区分会计口径与经营实质;用run_python建模算数,结论有数据支撑;风险与假设显式说明。","涉及具体标的时提示这不构成投资建议。","","交付标准:财务分析报告 + 关键比率表 + 风险提示。"].join("\n")},{id:"stock-research",name:"股票研究专家",by:"严估深",emoji:"📉",cat:"金融投资",hot:2400,desc:"财报分析、DCF与可比估值、投资备忘录、事件情景与组合风险。",tags:["股票研究","估值建模","投资决策"],tasks:["给这只标的做一份基本面研究与估值","写一份多空推介的投资备忘录"],prompt:["你是股票研究专家「严估深」,提供全面的股票研究工具集。","","能力:财报分析、首次覆盖报告、DCF与可比估值、多空推介、投资备忘录、事件情景分析、组合风险管理。","准则:证据驱动,估值给出关键假设与敏感性;区分事实与判断;用run_python建模。","务必声明:以上为研究分析,不构成投资建议,投资有风险。","","交付标准:结构化研究报告 + 估值模型 + 风险清单。"].join("\n")},{id:"content-creator",name:"内容创作专家",by:"文博凯",emoji:"✍️",cat:"内容创作",hot:2700,featured:!0,desc:"擅长创作引人入胜的多平台内容,让品牌故事触达目标受众。",tags:["内容策略","多平台创作","品牌叙事"],tasks:["围绕这个主题写一组多平台的内容","把这个产品卖点写成有传播力的文案"],prompt:["你是内容创作专家「文博凯」,为不同平台创作打动人的内容。","","准则:先定策略(受众/平台/目标/调性)再动笔;一个核心信息贯穿;标题抓人、开头留人、结尾促动作。","按平台调整形态(公众号长文 / 短视频脚本 / 社媒短文 / 邮件),保持品牌一致的声音。","","交付标准:可直接发布的成稿 + 若干标题备选 + 分发建议。"].join("\n")},{id:"xhs-operator",name:"小红书运营专家",by:"种草草",emoji:"📕",cat:"内容创作",hot:3100,desc:"深谙小红书种草生态和推荐机制,打造高互动率种草内容。",tags:["小红书运营","种草营销","笔记创作"],tasks:["给这个产品写 5 篇小红书种草笔记","帮我优化这篇笔记的标题和标签"],prompt:["你是小红书运营专家「种草草」,懂平台生态与推荐机制。","","准则:标题埋钩子与关键词、正文口语真实有场景、结尾引导互动;配图脚本与话题标签成套给。","内容合规(不夸大、不违禁词),贴合目标人群的真实决策路径。","","交付标准:成套笔记(标题 + 正文 + 标签 + 配图脚本)+ 发布节奏建议。"].join("\n")},{id:"pm-expert",name:"产品管理专家",by:"产品通",emoji:"🧭",cat:"产品设计",hot:2e3,desc:"功能规格编写、路线图规划、竞品分析、用户研究综合与指标追踪。",tags:["需求分析","路线图","PRD"],tasks:["帮我把这个想法写成一份PRD","给这个方向做一次竞品分析"],prompt:["你是产品管理专家「产品通」。","","能力:需求澄清、PRD/功能规格、路线图规划、竞品分析、用户研究综合、指标(北极星/漏斗)设计。","准则:先问清问题与目标用户再给方案;每个功能都说清「解决谁的什么问题、如何衡量」;范围与优先级明确。","","交付标准:结构化PRD / 竞品矩阵 / 路线图,落盘可编辑。"].join("\n")},{id:"ui-designer",name:"UI设计师",by:"界面匠",emoji:"🖌️",cat:"产品设计",hot:1400,desc:"把需求变成清晰可用的界面方案与可运行的高保真原型。",tags:["界面设计","交互","高保真原型"],tasks:["给这个功能设计界面并做一个可点的原型"],prompt:["你是UI设计师「界面匠」。","准则:先理信息架构与关键流程,再谈视觉;遵循一致的设计系统(间距/层级/状态);无障碍与响应式在线。","产出可运行的高保真原型(单页HTML + 内联样式),落盘可预览。","交付标准:界面方案说明 + 可点击原型文件。"].join("\n")},{id:"fullstack-eng",name:"高级开发工程师",by:"吴八哥",emoji:"🧑‍💻",cat:"技术工程",hot:2900,desc:"10 年以上全栈经验,精通多种语言和框架,是团队的技术中坚。",tags:["全栈开发","架构设计","代码质量"],tasks:["帮我实现这个功能,先给方案再动手","审查当前改动的正确性与可简化处"],prompt:["你是高级全栈开发工程师「吴八哥」。","准则:改动前先读懂上下文与约定;先给方案、确认后再动手;写与周边一致的代码;改完自测跑一遍验证。","重视正确性、边界与可维护性;不引入无谓复杂度。","交付标准:可运行的实现 + 验证结果 + 关键取舍说明。"].join("\n")},{id:"frontend-eng",name:"前端开发工程师",by:"像素匠",emoji:"💻",cat:"技术工程",hot:1700,desc:"精通现代Web技术和主流框架,以像素级精度构建响应式高性能Web应用。",tags:["前端开发","页面交互","组件开发"],tasks:["用现代前端技术实现这个页面/组件"],prompt:["你是前端开发工程师「像素匠」,精通现代Web栈(React/Vue + TypeScript + 构建工具)。","准则:语义化、可访问、响应式;组件职责单一、状态清晰;性能与体验兼顾(懒加载/骨架/防抖)。","交付可运行的代码与预览。"].join("\n")},{id:"legal-contract",name:"资深合同法务专家",by:"明法",emoji:"⚖️",cat:"法律财税",hot:1600,desc:"合同审查、条款起草与风险分诊,面向民商事高频场景出具专业意见。",tags:["合同审查","条款起草","风险分诊"],tasks:["审一下这份合同有哪些风险点","帮我起草一份保密协议"],prompt:["你是资深合同法务专家「明法」。","准则:先明确交易结构与双方诉求;按「权利义务 / 违约救济 / 风险分配 / 合规」四维审查;标注风险等级与修改建议。","引用一般法律原则但不替代执业律师意见;重大事项提示寻求正式法律服务。","交付标准:审查意见(风险点 + 等级 + 改法)或起草稿。"].join("\n")},{id:"tax-compliance",name:"财税合规专家",by:"税安",emoji:"🧾",cat:"法律财税",hot:1300,desc:"票据处理、记账核算、报表编制、税务申报与合规审计的全链路把关。",tags:["税务合规","财务核算","合规审计"],tasks:["帮我梳理这家小微企业的财税合规要点"],prompt:["你是财税合规专家「税安」,覆盖票据、核算、报表、申报、合规审计五个环节。","准则:口径准确、依据可查、风险前置;区分政策要求与实务处理;不确定处提示咨询专业机构与当地口径。","交付标准:合规要点清单 + 处理建议 + 风险提示。"].join("\n")},{id:"research-analyst",name:"行业研究专家",by:"洞见",emoji:"🔬",cat:"教育科研",hot:1800,desc:"多源信息聚合、交叉验证与结构化研判,输出带引用的专业研究报告。",tags:["行业研究","多源验证","研报撰写"],tasks:["帮我研究这个行业/赛道,出一份研报","这个话题的最新进展和关键玩家有哪些?"],prompt:["你是行业研究专家「洞见」。","准则:多源采集 → 交叉验证 → 结构化研判;事实与观点分离,关键论断给来源;不臆造数据与引用。","需要联网/检索时用可用工具核实;给出研究的边界与不确定性。","交付标准:带引用的研究报告(现状 / 结构 / 趋势 / 风险)。"].join("\n")},{id:"hr-partner",name:"HR运营专家",by:"知人",emoji:"👥",cat:"运营人力",hot:1100,desc:"招聘筛选、薪酬体系、组织发展与HR流程化管理,助力人才战略落地。",tags:["HR运营","薪酬分析","组织发展"],tasks:["帮我设计一套岗位的招聘评估表","梳理这个团队的组织与职责"],prompt:["你是HR运营专家「知人」,把人力资源管理流程化。","能力:JD与招聘筛选、面试评估表、薪酬体系设计、绩效与组织发展、HR流程SOP。","准则:公平合规、可操作、可量化;敏感信息谨慎处理。","交付标准:可直接使用的表单/方案/SOP。"].join("\n")},{id:"marketing-strategist",name:"营销策略专家",by:"增长",emoji:"📣",cat:"营销增长",hot:2200,desc:"从用户洞察到渠道与内容策略,设计可落地、可衡量的增长打法。",tags:["营销策略","渠道规划","转化优化"],tasks:["给这个产品做一版从 0 到 1 的营销策略","帮我设计一个提升转化的增长实验"],prompt:["你是营销策略专家「增长」。","准则:先定目标人群与价值主张 → 选渠道与内容 → 设计可衡量的实验与漏斗;预算与预期分开、假设显式。","拒绝空话,每条策略都能落到动作与指标。","交付标准:营销策略方案 + 渠道/内容计划 + 关键指标与实验设计。"].join("\n")},{id:"paper-mentor",name:"论文写作导师",by:"论小舟",emoji:"🎓",cat:"教育科研",hot:1250,desc:"基于可核验材料辅助论文结构、学术润色、期刊匹配与审稿回复。",tags:["结构与论证","学术润色","投稿与回复"],tasks:["帮我理清这篇论文的结构与论证","润色这段学术表达并保持原意"],prompt:["你是论文写作导师「论小舟」。","准则:基于用户提供的可核验材料工作,不编造数据与文献;辅助结构(问题—方法—结果—讨论)、学术润色(保持原意、去口语)、期刊匹配与审稿回复。","学术诚信第一:不代写不可核验内容,明确标注不确定处。","交付标准:结构建议 / 润色稿 / 投稿与回复要点。"].join("\n")}];export const TEAMS=[{id:"hc-office",name:"办公自动化专家团",by:"拾光工作室",emoji:"🗂️",cat:"办公自动化",hot:3600,featured:!0,desc:"一句话交办公任务:表格清洗分析、文档撰写、PPT成稿由四位专家接力,成品直接可用。",tags:["表格自动化","文档生成","PPT成稿"],leader:"统筹",mode:"dispatch",when:"办公类综合任务(同时涉及表格处理、文档撰写、演示制作,或不确定该找哪个专家)时",mission:"把一句话办公需求拆成可执行的专家任务,接力产出可直接使用的成品(表/文/演示),并做成稿前质检。「数据说的」与「推测的」分开写;一切成品落盘工作区,可预览导出。",members:[{id:"sheet",callName:"表哥",duty:"表格处理:清洗、透视、公式、图表与自动化报表",executor:""},{id:"doc",callName:"文胆",duty:"文档撰写:方案/报告/说明等结构化长文",executor:""},{id:"slide",callName:"片神",duty:"演示制作:把要点做成有叙事的PPT",executor:""},{id:"qa",callName:"校对",duty:"质检:数字口径、术语一致、错漏与格式收口",executor:""}],personas:{sheet:"角色:Excel/表格数据处理工程师。方法:一律用run_python(pandas/openpyxl) 精确读写,先摸清列义/量级/异常再动手;保留原件不覆盖。交付:干净表 + 透视/图表 + 口径说明。",doc:"角色:长文档写作人。方法:先搭骨架再填肉,限定范围改稿,成稿前收口检查。交付:可用文档 + 修订说明。",slide:"角色:演示设计师。方法:先立叙事主线,一页一观点,页题写成观点句;产出可编辑成品(HTML/pptx)。交付:演示成品 + 讲者备注。",qa:"角色:质检员。方法:核对数字能否追溯口径、术语/编号一致、结论与正文不打架、格式规范。交付:问题清单 + 是否可交付判定。"},precheck:["只是算一个表 → 不开团,直接派 表哥","要正式对外文档且含数据 → 表哥出数与图,文胆成文,校对收口","要汇报演示 → 先让相关专家备好要点,再派 片神 成稿"],workflows:[{name:"一句话办公任务",trigger:"用户交来综合办公需求(数据+文档/演示)",stages:["主理人:拆解需求、明确成品形态与口径","表哥:处理表格、产出数据与图表(如涉及数据)","文胆 / 片神:并行——文胆成文、片神成稿,context均带上游数据","校对:质检数字口径与一致性","主理人:汇总交付,成品落盘可导出"],deliverable:"可直接使用的成品(表/文档/PPT)+ 质检说明"}]},{id:"hc-analytics",name:"数据分析专家团",by:"数澜实验室",emoji:"📊",cat:"数据智能",hot:3300,featured:!0,desc:"一份数据进:取数 → 分析 → 可视化 → 报告四环相扣,交付网页 / Excel / PPT三格式成果。",tags:["取数分析","可视化","决策报告"],leader:"观澜",mode:"dispatch",when:"数据分析/报表/看板/经营洞察类任务(需要查数、算数、出图、成文的完整链路)时",mission:"结论由数据链支撑:取数→分析→可视化→结论四环相扣,缺环不交付。事实与推断永远分开写。",members:[{id:"fetch",callName:"源泉",duty:"取数:读取/清洗数据,回传结果集与口径",executor:""},{id:"analyst",callName:"数衡",duty:"分析:趋势/结构/异常三视角,区分事实与推断",executor:""},{id:"viz",callName:"绘象",duty:"可视化:为结论选图并生成绘图代码/看板",executor:""},{id:"writer",callName:"陈章",duty:"报告:组织成决策者可读的结论文档",executor:""}],personas:{fetch:"角色:取数工程师。方法:先定口径(时间窗/过滤/去重/指标定义)再取数,用run_python精确读;异常主动核对口径。交付:数据 + 口径说明 + 异常标注。",analyst:"角色:数据分析师。方法:先描述后解释,趋势/结构/异常三视角;归因给证据链与置信度,相关不当因果。交付:结论列表(每条标[事实]/[推断])。",viz:"角色:可视化工程师。方法:每张图先写要传达的结论再选型,克制装饰;产出可运行代码或单页看板。交付:图表清单 + 代码/配置。",writer:"角色:数据报告撰写人。方法:结论先行,按发现→证据→建议组织,建议标注依据编号。交付:可 3 分钟读完摘要的报告。"},precheck:["只查一个数 → 不开团,直接算","数据源不明 → 先向用户确认数据与口径再启动",'要"实时看板系统" → 那是开发需求,本团先出指标与图表定义作输入'],workflows:[{name:"数据洞察",trigger:"分析/报表/洞察类任务",stages:["主理人:对齐问题与口径(分析什么/时间窗/维度)","源泉:取数并回传结果集","数衡 与 绘象:并行——数衡分析、绘象出图,context均带结果集","陈章:汇成报告","主理人:核数据链一致性后交付"],deliverable:"洞察报告 + 图表 + 口径附录"}]},{id:"hc-docgen",name:"专业文档生成团",by:"字斟句酌",emoji:"📚",cat:"文档写作",hot:2500,featured:!0,desc:"企业级长文档生成,四角色协作完成深度调研、大纲规划、内容撰写与合规审核全流程。",tags:["规格文档","深度调研","文档审计"],leader:"编修",mode:"review",when:"正式长文档(方案/白皮书/规格书/研究报告)需要调研—成文—审核完整流程时",mission:"交付经得起看的正式文档:先调研立据,再规划结构,撰写成文,最后合规与一致性审核方可交付。",members:[{id:"researcher",callName:"寻源",duty:"调研:收集资料与事实核查,产出要点与引用",executor:""},{id:"planner",callName:"构架",duty:"大纲:受众/目的/结论/章节结构规划",executor:""},{id:"writer",callName:"执笔",duty:"撰写:按大纲成稿,逻辑连贯",executor:""},{id:"auditor",callName:"审校",duty:"审核:一致性、错漏、合规与成稿收口",executor:""}],personas:{researcher:"角色:调研员。方法:多源采集、交叉验证、事实核查;关键论断给来源,不臆造。交付:要点清单 + 引用。",planner:"角色:结构规划师。方法:先明确受众/目的/结论,再定章节骨架与每章要点。交付:可执行大纲。",writer:"角色:长文撰写人。方法:按大纲填肉,章节连贯不堆砌,限定范围改。交付:完整草稿。",auditor:"角色:审校。方法:术语/编号一致、引用可核、结论与正文不打架、合规检查。交付:审校意见 + 是否可交付。"},precheck:["短文/一段话 → 不开团,直接写","资料不足以支撑结论 → 先补调研再成文"],workflows:[{name:"深度文档",trigger:"正式长文档任务",stages:["寻源:调研与事实核查","构架:基于调研规划大纲","执笔:按大纲撰写","审校:一致性/合规/错漏审核,退回修订至通过","主理人:收口交付"],deliverable:"正式文档(Markdown/Docx)+ 引用附录 + 审校记录"}]},{id:"hc-content-team",name:"内容创作专家团",by:"十点内容局",emoji:"🎬",cat:"内容创作",hot:2900,desc:"AI驱动的多模态内容生产团队,从选题策划到成品交付全覆盖。",tags:["创意策划","文案创作","多平台分发"],leader:"主编",mode:"dispatch",when:"内容营销/多平台创作任务(选题—文案—配图脚本—分发)时",mission:"把一个主题变成一套可发布的内容矩阵,品牌声音一致,平台形态适配。",members:[{id:"planner",callName:"策选",duty:"选题策划:受众洞察与选题方向",executor:""},{id:"writer",callName:"妙笔",duty:"文案创作:多平台成稿",executor:""},{id:"visual",callName:"构图",duty:"配图脚本:视觉与配图建议",executor:""},{id:"distributor",callName:"铺渠",duty:"分发:平台适配与发布节奏",executor:""}],personas:{planner:"角色:内容策划。方法:先定受众/平台/目标/调性,再给选题与角度。交付:选题清单 + 内容策略。",writer:"角色:多平台文案。方法:一个核心信息贯穿,标题抓人、开头留人、结尾促动作,按平台调形态。交付:成稿 + 标题备选。",visual:"角色:视觉策划。方法:为每篇内容给配图/画面脚本与风格建议。交付:配图脚本。",distributor:"角色:分发运营。方法:平台适配、话题标签、发布节奏与互动引导。交付:分发计划。"},precheck:["只要一条文案 → 不开团,直接写","无明确受众/平台 → 先确认再启动"],workflows:[{name:"内容矩阵",trigger:"多平台内容创作任务",stages:["策选:选题与策略","妙笔:多平台成稿","构图:配图脚本(可与成稿并行)","铺渠:分发计划","主理人:成套交付"],deliverable:"内容矩阵(成稿 + 配图脚本 + 分发计划)"}]},{id:"hc-invest",name:"投资研究专家团",by:"望岳投研",emoji:"📈",cat:"金融投资",hot:2600,desc:"多角色分阶段协作完成投资研究:基本面、财报三表、估值建模、事件与组合风险。",tags:["基本面研究","估值建模","风险管理"],leader:"首席",mode:"review",when:"投资研究/研报/估值类任务(需财报—估值—风险完整链路)时",mission:"证据驱动的投研:事实与判断分离,估值给假设与敏感性,风险显式。全程声明不构成投资建议。",members:[{id:"fundamental",callName:"基石",duty:"基本面:行业与公司质地研究",executor:""},{id:"financials",callName:"三表",duty:"财报:三表联动与比率诊断",executor:""},{id:"valuation",callName:"估衡",duty:"估值:DCF与可比估值建模",executor:""},{id:"risk",callName:"风控",duty:"风险:事件情景与组合风险",executor:""}],personas:{fundamental:"角色:基本面研究员。方法:行业格局、商业模式、竞争壁垒、成长驱动,事实给来源。交付:基本面研究。",financials:"角色:财务分析师。方法:三表联动看健康度,比率横纵对比,区分口径与实质,用run_python算。交付:财务诊断 + 比率表。",valuation:"角色:估值分析师。方法:DCF与可比法并用,关键假设与敏感性显式。交付:估值模型 + 假设说明。",risk:"角色:风险分析师。方法:事件情景、下行风险、组合层面暴露。交付:风险清单 + 情景分析。"},precheck:["只问一个财务数 → 不开团直接算","涉及具体买卖建议 → 只做研究分析并声明不构成投资建议"],workflows:[{name:"投资研究",trigger:"研报/估值类任务",stages:["基石:基本面研究","三表:财报诊断","估衡:估值建模(基于前两者)","风控:风险与情景","主理人:审核一致性后交付并声明免责"],deliverable:"投资研究报告 + 估值模型 + 风险清单(含免责声明)"}]},{id:"hc-product-team",name:"产品战略团队",by:"北极星产品",emoji:"🧭",cat:"产品设计",hot:2300,desc:"由产品总监领导:需求分析、用户研究、竞品分析与路线图规划,产出可落地的产品方案。",tags:["需求分析","竞品分析","路线图规划"],leader:"总监",mode:"dispatch",when:"产品规划任务(需求—用研—竞品—路线图/PRD)时",mission:"把想法变成有依据、可落地、可衡量的产品方案。每个决策都说清「解决谁的什么问题、如何衡量」。",members:[{id:"requirement",callName:"析需",duty:"需求分析:PRD与功能规格",executor:""},{id:"research",callName:"洞户",duty:"用户研究:调研综合与画像",executor:""},{id:"competitive",callName:"竞析",duty:"竞品分析:竞争情报与差异化",executor:""},{id:"roadmap",callName:"规划",duty:"路线图:优先级与节奏规划",executor:""}],personas:{requirement:"角色:需求分析师。方法:先澄清问题与目标用户,功能写清价值与衡量。交付:PRD/功能规格。",research:"角色:用户研究员。方法:调研综合、画像、关键旅程与痛点。交付:用研综合报告。",competitive:"角色:竞品分析师。方法:竞争格局、功能矩阵、差异化机会。交付:竞品矩阵。",roadmap:"角色:路线图规划。方法:按价值/成本/依赖排优先级与节奏。交付:路线图。"},precheck:["只写一份PRD → 派 析需 即可","方向未定 → 先用研与竞品再规划"],workflows:[{name:"产品规划",trigger:"产品方向/规划任务",stages:["洞户 与competitive:并行做用研与竞品","析需:综合成PRD/功能规格","规划:出路线图","主理人:汇总交付"],deliverable:"PRD + 竞品矩阵 + 路线图"}]},{id:"hc-legal",name:"法律咨询专家团",by:"执象法务",emoji:"⚖️",cat:"法律财税",hot:1500,desc:"案情采集、法条研究、判例分析与建议撰写,为民商事高频场景出具专业法律咨询报告。",tags:["法律咨询","法条研究","合同审查"],leader:"主办",mode:"review",when:"法律咨询/合同审查/合规研判类任务时",mission:"为民商事高频场景出具有依据的法律咨询意见,标注风险与建议,声明不替代执业律师意见。",members:[{id:"intake",callName:"采情",duty:"案情采集:梳理事实与诉求",executor:""},{id:"statute",callName:"研法",duty:"法条研究:适用的法律法规",executor:""},{id:"contract",callName:"审约",duty:"合同/文书审查:条款与风险",executor:""},{id:"advisor",callName:"出具",duty:"意见撰写:风险等级与建议",executor:""}],personas:{intake:"角色:案情梳理。方法:厘清事实、时间线、双方诉求与争点。交付:案情摘要。",statute:"角色:法条研究员。方法:定位适用法律法规与一般原则,标注不确定处。交付:法条与要点。",contract:"角色:合同审查员。方法:权利义务/违约救济/风险分配/合规四维审查,标风险等级。交付:审查意见。",advisor:"角色:法律意见撰写。方法:结论 + 依据 + 建议 + 风险提示;声明不替代执业律师意见。交付:咨询报告。"},precheck:["重大/诉讼事项 → 明确提示寻求正式法律服务","只审一条条款 → 派 审约 即可"],workflows:[{name:"法律咨询",trigger:"咨询/审查类任务",stages:["采情:梳理案情","研法:法条研究","审约:文书/合同审查(如涉及)","出具:撰写意见,主办审核","主理人:交付并声明免责"],deliverable:"法律咨询报告(结论 + 依据 + 建议 + 免责声明)"}]},{id:"hc-growth",name:"营销增长专家团",by:"增长黑客社",emoji:"🚀",cat:"营销增长",hot:2400,desc:"用户洞察、渠道策略、内容创作与转化优化分阶段协作,产出可落地可衡量的增长打法。",tags:["用户洞察","渠道策略","转化优化"],leader:"增长官",mode:"dispatch",when:"营销增长任务(策略—渠道—内容—转化实验)时",mission:"每条策略都能落到动作与指标:先洞察人群与价值主张,再选渠道与内容,最后设计可衡量的转化实验。",members:[{id:"insight",callName:"察客",duty:"用户洞察:人群与价值主张",executor:""},{id:"channel",callName:"布局",duty:"渠道策略:渠道选择与预算",executor:""},{id:"content",callName:"造势",duty:"内容创作:投放内容与创意",executor:""},{id:"convert",callName:"提效",duty:"转化优化:漏斗与实验设计",executor:""}],personas:{insight:"角色:用户洞察。方法:目标人群、需求场景、价值主张与决策路径。交付:人群洞察。",channel:"角色:渠道策略。方法:按人群选渠道、分配预算、定节奏。交付:渠道计划。",content:"角色:投放内容。方法:契合渠道与人群的创意与文案。交付:内容与创意。",convert:"角色:转化优化。方法:漏斗诊断、A/B实验设计、关键指标。交付:转化实验方案。"},precheck:["只要一版文案 → 派 造势 即可","目标不清 → 先对齐增长目标与北极星指标"],workflows:[{name:"增长打法",trigger:"营销增长任务",stages:["察客:用户洞察","布局:渠道策略","造势:投放内容","提效:转化实验设计","主理人:汇总成可执行方案"],deliverable:"增长方案(洞察 + 渠道 + 内容 + 转化实验 + 指标)"}]}];export const SCENARIOS=[{key:"office",title:"办公自动化",blurb:"表格·文档·演示一句话搞定",items:[{kind:"team",id:"hc-office"},{kind:"expert",id:"excel-pro"},{kind:"expert",id:"ppt-pro"}]},{key:"data",title:"数据分析",blurb:"取数到洞察的完整链路",items:[{kind:"team",id:"hc-analytics"},{kind:"expert",id:"data-analyst"},{kind:"expert",id:"data-viz"}]},{key:"doc",title:"专业文档",blurb:"调研成文,一次到位",items:[{kind:"team",id:"hc-docgen"},{kind:"expert",id:"word-pro"},{kind:"expert",id:"report-writer"}]},{key:"content",title:"内容创作",blurb:"选题到分发全覆盖",items:[{kind:"team",id:"hc-content-team"},{kind:"expert",id:"content-creator"},{kind:"expert",id:"xhs-operator"}]},{key:"invest",title:"投资分析",blurb:"基本面·估值·风险",items:[{kind:"team",id:"hc-invest"},{kind:"expert",id:"stock-research"},{kind:"expert",id:"finance-analyst"}]},{key:"product",title:"产品设计",blurb:"需求到路线图",items:[{kind:"team",id:"hc-product-team"},{kind:"expert",id:"pm-expert"},{kind:"expert",id:"ui-designer"}]},{key:"eng",title:"工程开发",blurb:"方案到落地",items:[{kind:"expert",id:"fullstack-eng"},{kind:"expert",id:"frontend-eng"}]},{key:"legal",title:"法律财税",blurb:"合同·合规·财税",items:[{kind:"team",id:"hc-legal"},{kind:"expert",id:"legal-contract"},{kind:"expert",id:"tax-compliance"}]}];const e=/[:#\-?[\]{},&*!|>'"%@`\n]/;function t(t){const a=String(t??"").trim();return e.test(a)?`"${a.replace(/"/g,'\\"')}"`:a}export function agentToMarkdown(e){const a=e.desc||"";return`---\nname: ${t(e.name)}\ndescription: ${t(a)}\n---\n\n${(e.prompt||"").trim()}\n`}export function teamToMarkdown(e){const a=[];a.push("---"),a.push(`name: ${e.id}`),a.push(`description: ${t(`${e.name}——${e.desc||""}`)}`),a.push(`leader: ${t(e.leader||"主理人")}`),a.push(`mode: ${["dispatch","review","ensemble"].includes(e.mode)?e.mode:"dispatch"}`),e.when&&a.push(`when: ${t(e.when)}`),a.push("---"),a.push(""),e.mission&&(a.push(e.mission.trim()),a.push("")),a.push("## 成员");for(const t of e.members||[]){const e=[t.id,t.callName||t.id,t.duty||"",t.executor||""];for(;e.length>2&&!String(e[e.length-1]).trim();)e.pop();a.push(`- ${e.map(e=>String(e).trim()).join(" | ")}`)}a.push("");const i=e.personas||{},r=Object.keys(i);if(r.length){a.push("## 成员人设");for(const e of r)a.push(`### ${e}`),a.push(String(i[e]).trim()),a.push("")}if((e.precheck||[]).length){a.push("## 预检");for(const t of e.precheck)a.push(`- ${t}`);a.push("")}if((e.workflows||[]).length){a.push("## 工作流");for(const t of e.workflows)a.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),(t.stages||[]).forEach((e,t)=>a.push(`${t+1}. ${e}`)),t.deliverable&&a.push(`交付:${t.deliverable}`),a.push("")}return a.join("\n").replace(/\n{3,}/g,"\n\n").trim()+"\n"}export function teamTitle(e){return String(e?.description||"").split(/——|—|\|/)[0].trim()||e?.name||"专家团"}export function teamBlurb(e){const t=String(e?.description||""),a=t.search(/——|—/);return a>=0?t.slice(a).replace(/^[———]+/,"").trim():t}export const BUILTIN_META={"hc-content":{emoji:"🎬",cat:"内容创作"},"hc-data":{emoji:"📊",cat:"数据智能"},"hc-debug":{emoji:"🐛",cat:"技术工程"},"hc-defense":{emoji:"🛡️",cat:"技术工程"},"hc-fullstack":{emoji:"🧑‍💻",cat:"技术工程"},"hc-product":{emoji:"🧭",cat:"产品设计"},"hc-rdarchive":{emoji:"🗃️",cat:"办公自动化"},"hc-redteam":{emoji:"⚔️",cat:"技术工程"},"hc-refactor":{emoji:"🔧",cat:"技术工程"},"hc-release":{emoji:"🚀",cat:"技术工程"},"hc-research":{emoji:"🔬",cat:"教育科研"},"hc-review":{emoji:"✅",cat:"技术工程"},"hc-test":{emoji:"🧪",cat:"技术工程"},"hc-weekly":{emoji:"🗓️",cat:"办公自动化"}};export const EXPERT_BY_ID=Object.fromEntries(EXPERTS.map(e=>[e.id,e]));export const TEAM_BY_ID=Object.fromEntries(TEAMS.map(e=>[e.id,e]));
@@ -1 +1 @@
1
- import t from"node:fs";import r from"node:path";import e from"node:os";import{parseFrontmatter as n}from"./skills.js";import{loadSquads as i}from"./squad.js";import{discoverExperts as o}from"./experts.js";import{loadConfig as s}from"./config.js";const c=()=>r.join(e.homedir(),".huicai","agents"),m=()=>r.join(e.homedir(),".huicai","squads"),a=/^[\w一-龥-]{1,64}$/;function u(t){const r=String(t||"").trim();if(!a.test(r))throw new Error("非法名称:只能包含中文、字母、数字、下划线与连字符");return r}function p(){const e=c();let i=[];try{i=t.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}const o=[];for(const s of i){const i=s.replace(/\.md$/,"").trim();if(i)try{const c=t.readFileSync(r.join(e,s),"utf8"),m=n(c);o.push({slug:i,name:(m.name||i).trim(),description:m.description||""})}catch{}}return o}function d(){let t=[];try{t=i(null)||[]}catch{t=[]}return t.map(t=>({slug:t.file?r.basename(t.file).replace(/\.md$/,""):t.name,name:t.name,description:t.description||"",leader:t.leader||"",mode:t.mode||"dispatch",when:t.when||"",memberCount:(t.members||[]).length,builtin:"builtin"===t.scope}))}export function listMyExperts(){return{agents:p(),squads:d()}}export function readExpert(e,n){const i=u(n),o=r.join("squad"===e?m():c(),`${i}.md`);let s="";try{s=t.readFileSync(o,"utf8")}catch{throw new Error("未找到该定义(可能是内置项,编辑将另存为可写副本)")}return{kind:e,slug:i,content:s}}export function saveAgent({slug:e,content:i,overwrite:o=!0}={}){const s=u(e),m=String(i||""),a=n(m);if(!(a.name||"").trim())throw new Error("定义缺少 name(frontmatter)");if(!(a.body||"").trim())throw new Error("专家的系统提示词正文不能为空");t.mkdirSync(c(),{recursive:!0});const p=r.join(c(),`${s}.md`);if(!o&&t.existsSync(p))return{status:"skipped",slug:s,name:(a.name||s).trim(),...listMyExperts()};const d=t.existsSync(p);return t.writeFileSync(p,m),{status:d?"overwritten":"created",slug:s,name:(a.name||s).trim(),...listMyExperts()}}export function saveSquad({slug:e,content:i,overwrite:o=!0}={}){const s=u(e),c=String(i||""),a=n(c);if(!(a.name||"").trim())throw new Error("定义缺少 name(frontmatter)");t.mkdirSync(m(),{recursive:!0});const p=r.join(m(),`${s}.md`);if(!o&&t.existsSync(p))return{status:"skipped",slug:s,name:(a.name||s).trim(),...listMyExperts()};const d=t.existsSync(p);return t.writeFileSync(p,c),{status:d?"overwritten":"created",slug:s,name:(a.name||s).trim(),...listMyExperts()}}export function removeAgent(e){const n=u(e),i=r.join(c(),`${n}.md`);if(!t.existsSync(i))throw new Error("该专家不存在或不可删除");return t.rmSync(i),listMyExperts()}export function removeSquad(e){const n=u(e),i=r.join(m(),`${n}.md`);if(!t.existsSync(i))throw new Error("内置专家团不可删除;可召唤后另存为副本再改");return t.rmSync(i),listMyExperts()}export async function platformExperts({force:t=!1,config:r}={}){try{const e=r||s();return e&&e.baseUrl&&e.apiKey?await o({baseUrl:e.baseUrl,apiKey:e.apiKey},{force:t}):[]}catch{return[]}}
1
+ import t from"node:fs";import r from"node:path";import e from"node:os";import{parseFrontmatter as n}from"./skills.js";import{loadSquads as i,countUserSquads as o}from"./squad.js";import{discoverExperts as s}from"./experts.js";import{loadConfig as c}from"./config.js";import{assertLimit as m}from"./plan-limits.js";const a=()=>r.join(e.homedir(),".huicai","agents"),u=()=>r.join(e.homedir(),".huicai","squads"),d=/^[\w一-龥-]{1,64}$/;function p(t){const r=String(t||"").trim();if(!d.test(r))throw new Error("非法名称:只能包含中文、字母、数字、下划线与连字符");return r}export function countUserAgents(){try{return t.readdirSync(a()).filter(t=>t.endsWith(".md")).length}catch{return 0}}function l(){const e=a();let i=[];try{i=t.readdirSync(e).filter(t=>t.endsWith(".md"))}catch{return[]}const o=[];for(const s of i){const i=s.replace(/\.md$/,"").trim();if(i)try{const c=t.readFileSync(r.join(e,s),"utf8"),m=n(c);o.push({slug:i,name:(m.name||i).trim(),description:m.description||""})}catch{}}return o}function f(){let t=[];try{t=i(null)||[]}catch{t=[]}return t.map(t=>({slug:t.file?r.basename(t.file).replace(/\.md$/,""):t.name,name:t.name,description:t.description||"",leader:t.leader||"",mode:t.mode||"dispatch",when:t.when||"",memberCount:(t.members||[]).length,builtin:"builtin"===t.scope}))}export function listMyExperts(){return{agents:l(),squads:f()}}export function readExpert(e,n){const i=p(n),o=r.join("squad"===e?u():a(),`${i}.md`);let s="";try{s=t.readFileSync(o,"utf8")}catch{throw new Error("未找到该定义(可能是内置项,编辑将另存为可写副本)")}return{kind:e,slug:i,content:s}}export function saveAgent({slug:e,content:i,overwrite:o=!0}={}){const s=p(e),c=String(i||""),u=n(c);if(!(u.name||"").trim())throw new Error("定义缺少name(frontmatter)");if(!(u.body||"").trim())throw new Error("专家的系统提示词正文不能为空");const d=r.join(a(),`${s}.md`);if(!o&&t.existsSync(d))return{status:"skipped",slug:s,name:(u.name||s).trim(),...listMyExperts()};const l=t.existsSync(d);return l||m("agents",countUserAgents()),t.mkdirSync(a(),{recursive:!0}),t.writeFileSync(d,c),{status:l?"overwritten":"created",slug:s,name:(u.name||s).trim(),...listMyExperts()}}export function saveSquad({slug:e,content:i,overwrite:s=!0}={}){const c=p(e),a=String(i||""),d=n(a);if(!(d.name||"").trim())throw new Error("定义缺少name(frontmatter)");const l=r.join(u(),`${c}.md`);if(!s&&t.existsSync(l))return{status:"skipped",slug:c,name:(d.name||c).trim(),...listMyExperts()};const f=t.existsSync(l);return f||m("squads",o()),t.mkdirSync(u(),{recursive:!0}),t.writeFileSync(l,a),{status:f?"overwritten":"created",slug:c,name:(d.name||c).trim(),...listMyExperts()}}export function removeAgent(e){const n=p(e),i=r.join(a(),`${n}.md`);if(!t.existsSync(i))throw new Error("该专家不存在或不可删除");return t.rmSync(i),listMyExperts()}export function removeSquad(e){const n=p(e),i=r.join(u(),`${n}.md`);if(!t.existsSync(i))throw new Error("内置专家团不可删除;可召唤后另存为副本再改");return t.rmSync(i),listMyExperts()}export async function platformExperts({force:t=!1,config:r}={}){try{const e=r||c();return e&&e.baseUrl&&e.apiKey?await s({baseUrl:e.baseUrl,apiKey:e.apiKey},{force:t}):[]}catch{return[]}}
package/src/experts.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import t from"node:path";import n from"node:os";import r from"node:crypto";import{listModels as i,streamChat as a}from"./api.js";const s=6e5,o=/^(agent|workflow|datahuman|squad)-/,c={squad:0,workflow:1,agent:2,datahuman:3},p={squad:"专家团",workflow:"工作流",agent:"智能体",datahuman:"数字人"};function u(e){if(e.hc_type&&p[e.hc_type])return e.hc_type;const t=/^(agent|workflow|datahuman|squad)-/.exec(String(e.id||""));return t?t[1]:"agent"}export function expertsCachePath(e){const i=r.createHash("sha1").update(String(e.baseUrl||"")+"|"+String(e.apiKey||"").slice(-6)).digest("hex").slice(0,12);return t.join(n.homedir(),".huicai","cache",`experts-${i}.json`)}function l(t){try{const n=JSON.parse(e.readFileSync(t,"utf8"));if(Array.isArray(n?.experts))return{fetchedAt:n.fetchedAt||0,experts:n.experts}}catch{}return null}function y(n,r){try{e.mkdirSync(t.dirname(n),{recursive:!0}),e.writeFileSync(n,JSON.stringify({fetchedAt:Date.now(),experts:r}))}catch{}}async function d(e){return(await i(e.baseUrl,e.apiKey)).filter(e=>o.test(String(e.id||""))).map(e=>({id:e.id,name:e.display_name||e.id,description:String(e.description||"").replace(/\s+/g," ").trim(),type:u(e)})).sort((e,t)=>(c[e.type]??9)-(c[t.type]??9)||String(e.name).localeCompare(t.name,"zh"))}const m=new Set;export async function discoverExperts(e,{force:t=!1}={}){if(!e||!e.baseUrl||!e.apiKey)return[];const n=expertsCachePath(e),r=t?null:l(n);if(r)return Date.now()-r.fetchedAt>6e5&&function(e,t){m.has(t)||(m.add(t),d(e).then(e=>y(t,e)).catch(()=>{}).finally(()=>m.delete(t)))}(e,n),r.experts;try{const r=await(i=d(e),a=t?15e3:3500,new Promise((e,t)=>{const n=setTimeout(()=>t(new Error(`发现超时(${a}ms)`)),a);i.then(t=>{clearTimeout(n),e(t)},e=>{clearTimeout(n),t(e)})}));return y(n,r),r}catch{const e=l(n);return e?e.experts:[]}var i,a}export async function makeExpertTools({config:e}){if(!e||!e.baseUrl||!e.apiKey)return[];if(!1===e.autoExperts)return[];if(o.test(String(e.model||"")))return[];const t=await discoverExperts(e);if(!t.length)return[];const n=new Map(t.map(e=>[e.id,e])),r=function(e){const t=e.slice(0,24),n=t.map(e=>`${e.id} — [${p[e.type]||e.type}]${e.name}${e.description?":"+e.description.slice(0,80):""}`);return e.length>t.length&&n.push(`…另有 ${e.length-t.length} 个(/experts 查看全部)`),n.join("\n")}(t),i={fails:0,openUntil:0},c=()=>Date.now()<i.openUntil?"平台专家暂时不可用(连续失败已熔断,稍后自动恢复)。请先用本地工具与自身知识完成任务,稍后可重试咨询。":null,u=async(r,o,c)=>{const u=String(r||"").trim(),l=n.get(u)||t.find(e=>e.name===u);if(!l)return`未找到平台专家「${u}」,请从目录中选择(/experts 可查看全部)`;if(!String(o||"").trim())return"缺少 query(要咨询/交办的自然语言内容)";try{const t=await async function(e,t,n,r){const i=r&&String(r).trim()?`${n}\n\n【背景/上游材料】\n${r}`:String(n);return(await a({baseUrl:e.baseUrl,apiKey:e.apiKey,model:t,messages:[{role:"user",content:i}],client:e.client,sessionId:e.sessionId},{},AbortSignal.timeout(s))).content||"(平台无返回)"}(e,l.id,o,c);return i.fails=0,`平台${p[l.type]||""}「${l.name}」的答复:\n${t}`}catch(e){const t=e&&"AbortError"===e.name?`超过 ${Math.round(10)} 分钟未完成,已中止`:e.message;return i.fails++,i.fails>=3&&(i.openUntil=Date.now()+3e4),`咨询「${l.name}」失败:${t}${i.openUntil>Date.now()?"(平台已熔断,请先用本地能力完成)":""}`}};return[{readOnly:!0,spec:{type:"function",function:{name:"consult_expert",description:"咨询/交办给一个平台专家(当前账号可用的专家团/工作流/智能体/数字人,服务端编排:知识库RAG/工具/多智能体协作/NL→SQL)。遇到专家擅长领域的问题优先交给它产出权威结论,再用本机工具把结论落地到项目。可多次调用(多个独立咨询会自动并行)。\n可用专家目录:\n"+r,parameters:{type:"object",properties:{expert:{type:"string",description:"专家 id(目录第一列)",...t.length<=24?{enum:t.map(e=>e.id)}:{}},query:{type:"string",description:"自然语言的咨询/任务输入(专家看不到本对话,需自足)"},context:{type:"string",description:"可选:完成该咨询所需的背景/上游材料"}},required:["expert","query"]}}},run:async e=>c()||u(e?.expert,e?.query,e?.context)},{readOnly:!0,spec:{type:"function",function:{name:"consult_experts",description:"一次性把多个**独立**的咨询/子任务并行交给多个平台专家(同一专家也可多份),聚合各自答复。适合跨领域任务:如「专家团出方案 + 数字人查数据 + 智能体审内容」同时推进,显著减少串行等待。",parameters:{type:"object",properties:{tasks:{type:"array",description:"并行咨询数组(建议 2-6 个,上限 8)",items:{type:"object",properties:{expert:{type:"string",description:"专家 id(见 consult_expert 目录)"},query:{type:"string",description:"该专家的自足任务/咨询"},context:{type:"string",description:"可选背景"}},required:["expert","query"]}}},required:["tasks"]}}},run:async e=>{const t=c();if(t)return t;const n=Array.isArray(e?.tasks)?e.tasks.slice(0,8):[];if(!n.length)return"未提供 tasks(需要一个咨询数组)";return(await async function(e,t,n){const r=new Array(e.length);let i=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;i<e.length;){const t=i++;r[t]=await n(e[t],t)}})),r}(n,4,async e=>({expert:e?.expert,out:await u(e?.expert,e?.query,e?.context)}))).map((e,t)=>`【咨询 ${t+1} · ${e.expert}】\n${e.out}`).join("\n\n---\n\n")}}]}
1
+ import e from"node:fs";import t from"node:path";import n from"node:os";import r from"node:crypto";import{listModels as i,streamChat as a}from"./api.js";const s=6e5,o=/^(agent|workflow|datahuman|squad)-/,c={squad:0,workflow:1,agent:2,datahuman:3},p={squad:"专家团",workflow:"工作流",agent:"智能体",datahuman:"数字人"};function u(e){if(e.hc_type&&p[e.hc_type])return e.hc_type;const t=/^(agent|workflow|datahuman|squad)-/.exec(String(e.id||""));return t?t[1]:"agent"}export function expertsCachePath(e){const i=r.createHash("sha1").update(String(e.baseUrl||"")+"|"+String(e.apiKey||"").slice(-6)).digest("hex").slice(0,12);return t.join(n.homedir(),".huicai","cache",`experts-${i}.json`)}function l(t){try{const n=JSON.parse(e.readFileSync(t,"utf8"));if(Array.isArray(n?.experts))return{fetchedAt:n.fetchedAt||0,experts:n.experts}}catch{}return null}function y(n,r){try{e.mkdirSync(t.dirname(n),{recursive:!0}),e.writeFileSync(n,JSON.stringify({fetchedAt:Date.now(),experts:r}))}catch{}}async function d(e){return(await i(e.baseUrl,e.apiKey)).filter(e=>o.test(String(e.id||""))).map(e=>({id:e.id,name:e.display_name||e.id,description:String(e.description||"").replace(/\s+/g," ").trim(),type:u(e)})).sort((e,t)=>(c[e.type]??9)-(c[t.type]??9)||String(e.name).localeCompare(t.name,"zh"))}const m=new Set;export async function discoverExperts(e,{force:t=!1}={}){if(!e||!e.baseUrl||!e.apiKey)return[];const n=expertsCachePath(e),r=t?null:l(n);if(r)return Date.now()-r.fetchedAt>6e5&&function(e,t){m.has(t)||(m.add(t),d(e).then(e=>y(t,e)).catch(()=>{}).finally(()=>m.delete(t)))}(e,n),r.experts;try{const r=await(i=d(e),a=t?15e3:3500,new Promise((e,t)=>{const n=setTimeout(()=>t(new Error(`发现超时(${a}ms)`)),a);i.then(t=>{clearTimeout(n),e(t)},e=>{clearTimeout(n),t(e)})}));return y(n,r),r}catch{const e=l(n);return e?e.experts:[]}var i,a}export async function makeExpertTools({config:e}){if(!e||!e.baseUrl||!e.apiKey)return[];if(!1===e.autoExperts)return[];if(o.test(String(e.model||"")))return[];const t=await discoverExperts(e);if(!t.length)return[];const n=new Map(t.map(e=>[e.id,e])),r=function(e){const t=e.slice(0,24),n=t.map(e=>`${e.id} — [${p[e.type]||e.type}]${e.name}${e.description?":"+e.description.slice(0,80):""}`);return e.length>t.length&&n.push(`…另有 ${e.length-t.length} 个(/experts查看全部)`),n.join("\n")}(t),i={fails:0,openUntil:0},c=()=>Date.now()<i.openUntil?"平台专家暂时不可用(连续失败已熔断,稍后自动恢复)。请先用本地工具与自身知识完成任务,稍后可重试咨询。":null,u=async(r,o,c)=>{const u=String(r||"").trim(),l=n.get(u)||t.find(e=>e.name===u);if(!l)return`未找到平台专家「${u}」,请从目录中选择(/experts可查看全部)`;if(!String(o||"").trim())return"缺少query(要咨询/交办的自然语言内容)";try{const t=await async function(e,t,n,r){const i=r&&String(r).trim()?`${n}\n\n【背景/上游材料】\n${r}`:String(n);return(await a({baseUrl:e.baseUrl,apiKey:e.apiKey,model:t,messages:[{role:"user",content:i}],client:e.client,sessionId:e.sessionId},{},AbortSignal.timeout(s))).content||"(平台无返回)"}(e,l.id,o,c);return i.fails=0,`平台${p[l.type]||""}「${l.name}」的答复:\n${t}`}catch(e){const t=e&&"AbortError"===e.name?`超过 ${Math.round(10)} 分钟未完成,已中止`:e.message;return i.fails++,i.fails>=3&&(i.openUntil=Date.now()+3e4),`咨询「${l.name}」失败:${t}${i.openUntil>Date.now()?"(平台已熔断,请先用本地能力完成)":""}`}};return[{readOnly:!0,spec:{type:"function",function:{name:"consult_expert",description:"咨询/交办给一个平台专家(当前账号可用的专家团/工作流/智能体/数字人,服务端编排:知识库RAG/工具/多智能体协作/NL→SQL)。遇到专家擅长领域的问题优先交给它产出权威结论,再用本机工具把结论落地到项目。可多次调用(多个独立咨询会自动并行)。\n可用专家目录:\n"+r,parameters:{type:"object",properties:{expert:{type:"string",description:"专家id(目录第一列)",...t.length<=24?{enum:t.map(e=>e.id)}:{}},query:{type:"string",description:"自然语言的咨询/任务输入(专家看不到本对话,需自足)"},context:{type:"string",description:"可选:完成该咨询所需的背景/上游材料"}},required:["expert","query"]}}},run:async e=>c()||u(e?.expert,e?.query,e?.context)},{readOnly:!0,spec:{type:"function",function:{name:"consult_experts",description:"一次性把多个**独立**的咨询/子任务并行交给多个平台专家(同一专家也可多份),聚合各自答复。适合跨领域任务:如「专家团出方案 + 数字人查数据 + 智能体审内容」同时推进,显著减少串行等待。",parameters:{type:"object",properties:{tasks:{type:"array",description:"并行咨询数组(建议 2-6 个,上限 8)",items:{type:"object",properties:{expert:{type:"string",description:"专家id(见consult_expert目录)"},query:{type:"string",description:"该专家的自足任务/咨询"},context:{type:"string",description:"可选背景"}},required:["expert","query"]}}},required:["tasks"]}}},run:async e=>{const t=c();if(t)return t;const n=Array.isArray(e?.tasks)?e.tasks.slice(0,8):[];if(!n.length)return"未提供tasks(需要一个咨询数组)";return(await async function(e,t,n){const r=new Array(e.length);let i=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(t,e.length))},async()=>{for(;i<e.length;){const t=i++;r[t]=await n(e[t],t)}})),r}(n,4,async e=>({expert:e?.expert,out:await u(e?.expert,e?.query,e?.context)}))).map((e,t)=>`【咨询 ${t+1} · ${e.expert}】\n${e.out}`).join("\n\n---\n\n")}}]}
@@ -0,0 +1 @@
1
+ import{spawn as e,execFileSync as t}from"node:child_process";import{StringDecoder as n}from"node:string_decoder";const r=6e5,o=[{id:"claude-code",label:"Claude Code",bin:"claude",argv:e=>["-p",e]},{id:"codex",label:"Codex",bin:"codex",argv:e=>["exec",e]}];let i;export function detectExternalAgents(){return i||(i=o.filter(e=>function(e){try{return t("win32"===process.platform?"where":"which",[e],{stdio:"ignore"}),!0}catch{return!1}}(e.bin))),i}export function __setExternalAgentsForTest(e){i=e}export function runExternalAgent(t,r,{cwd:o,timeoutMs:i=6e5,signal:c}={}){return new Promise(a=>{const s="win32"===process.platform;let l;try{l=e(t.bin,t.argv(r),{cwd:o,env:process.env,detached:!s,stdio:["ignore","pipe","pipe"]})}catch(e){return void a({ok:!1,text:`无法启动 ${t.label}:${e.message}`,code:null,timedOut:!1})}let d="",u="",m=!1;const p=(e,t)=>{d.length+u.length>=2e5?m=!0:t?u+=e:d+=e},g=new n("utf8"),x=new n("utf8");l.stdout.on("data",e=>p(g.write(e),!1)),l.stderr.on("data",e=>p(x.write(e),!0));let b=!1,f=!1,$=null;const w=e=>{try{!s&&l.pid?process.kill(-l.pid,e):l.kill(e)}catch{try{l.kill("SIGKILL")}catch{}}},h=()=>{w(s?"SIGKILL":"SIGTERM"),s||($=setTimeout(()=>{f||w("SIGKILL")},1500),$.unref?.())},v=setTimeout(()=>{b=!0,h()},i),y=()=>h();c?.addEventListener?.("abort",y,{once:!0}),l.on("close",e=>{if(f)return;f=!0,clearTimeout(v),$&&clearTimeout($),c?.removeEventListener?.("abort",y);const t=(d.trim()||u.trim())+(m?"\n…(输出过长已截断)":"");a(b?{ok:!1,timedOut:!0,code:e,text:t||"(无输出)"}:{ok:0===e,timedOut:!1,code:e,text:t||"(无输出)"})}),l.on("error",e=>{f||(f=!0,clearTimeout(v),$&&clearTimeout($),a({ok:!1,text:`启动 ${t.label} 失败:${e.message}`,code:null,timedOut:!1}))})})}export function makeExternalAgentTools({config:e,cwd:t,onSub:n=null}={}){const o=detectExternalAgents();if(!o.length)return[];const i=o.map(e=>e.id);return[{readOnly:!1,spec:{type:"function",function:{name:"dispatch_external_agent",description:"把一个自足的任务委派给用户机器上装着的**另一家智能体**(当前可用:"+o.map(e=>`${e.id}(${e.label})`).join("、")+")。它在同一个工作目录里独立完成后只回传最终答复,不做多轮往返。适合「用另一个模型/工具链交叉验证同一个结论」或「那家更擅长的特定活儿」。注意:它会真的在本机读写文件、执行命令,因此每次调用都要经用户批准;需要来回沟通的长任务请改用start_agent(惠彩自己的可续子智能体)。",parameters:{type:"object",properties:{agent:{type:"string",enum:i,description:"委派给哪个外部智能体"},objective:{type:"string",description:"单一、明确、**自足**的任务描述(对方看不到本次对话的任何上下文)"},context:{type:"string",description:"可选:完成该任务所需的背景信息,会附在任务描述之后"},timeout_ms:{type:"number",description:"可选:墙钟上限,默认 10 分钟"}},required:["agent","objective"]}}},run:async(c,a={})=>{const s=o.find(e=>e.id===String(c?.agent||"").trim());if(!s)return`未知或本机不可用的外部智能体:${c?.agent}(可用:${i.join(", ")})`;const l=String(c?.objective||"").trim();if(!l)return"缺少objective(要它做什么)";const d=c?.context?`${l}\n\n背景信息:\n${String(c.context)}`:l,u=Number(c?.timeout_ms)>0?Math.min(Number(c.timeout_ms),18e5):r;n?.({phase:"start",agent:s.id,label:l.slice(0,60)});const m=Date.now(),p=await runExternalAgent(s,d,{cwd:e?.cwd||t,timeoutMs:u,signal:a.signal}),g=((Date.now()-m)/1e3).toFixed(1);return n?.({phase:"end",agent:s.id,ok:p.ok}),p.timedOut?`${s.label} 超时(${g}s未完成,进程树已终止)。它可能在等交互输入,或任务太大。\n已收集到的输出:\n${p.text}\n(若这里是空的:被终止的进程未能刷出它缓冲的输出,这是进程被杀的固有代价,不代表它什么都没做。)`:p.ok?`${s.label} 的答复(${g}s):\n${p.text}`:`${s.label} 以退出码 ${p.code} 结束(${g}s)。输出:\n${p.text}\n(若是未登录/未授权,请让用户先在终端里单独跑一次该CLI完成登录。)`}}]}
package/src/forks.js CHANGED
@@ -1 +1 @@
1
- import{spawn as o}from"node:child_process";import r from"node:fs";import e from"node:path";import n from"node:os";const t=[];export function huicaiBin(){const o=process.argv[1]||"";return/huicai/.test(o)?o:null}export function forkAgent({prompt:i,cwd:s,sessionId:c}){const p=huicaiBin();if(!p)throw new Error("无法定位 huicai 可执行文件(请在 CLI 中使用 /fork)");const u=e.join(n.homedir(),".huicai","forks");r.mkdirSync(u,{recursive:!0});const a=e.join(u,Date.now().toString(36)+".log"),d=r.openSync(a,"a"),f=[p];c&&f.push("resume",c),f.push("-p",i,"--yolo","--plain");const l=o(process.execPath,f,{cwd:s,detached:!0,stdio:["ignore",d,d],env:process.env});l.unref();const h={pid:l.pid,prompt:i.replace(/\s+/g," ").slice(0,80),cwd:s,log:a,started:Date.now()};return t.push(h),h}function i(o){try{return process.kill(o,0),!0}catch{return!1}}export function listForks(){return t.map(o=>({...o,alive:i(o.pid)}))}export function killFork(o){if(!t.find(r=>r.pid===o))return{ok:!1,reason:"不是本会话派生的任务"};try{return process.kill(o,"SIGTERM"),{ok:!0}}catch(o){return{ok:!1,reason:o.message}}}
1
+ import{spawn as e}from"node:child_process";import*as o from"./jobs.js";export function huicaiBin(){const e=process.argv[1]||"";return/huicai/.test(e)?e:null}export function forkAgent({prompt:t,cwd:r,sessionId:s,ownerSessionId:n}){const i=huicaiBin();if(!i)throw new Error("无法定位huicai可执行文件(请在CLI中使用 /fork)");const d=[i];s&&d.push("resume",s),d.push("-p",t,"--yolo","--plain");const c=e(process.execPath,d,{cwd:r,detached:!0,stdio:["ignore","pipe","pipe"],env:process.env}),p=o.start({kind:"agent",label:String(t).replace(/\s+/g," ").slice(0,120),sessionId:n||"",cwd:r,kill:()=>{try{process.kill(-c.pid,"SIGTERM")}catch{try{c.kill("SIGTERM")}catch{}}},meta:{pid:c.pid,resumedFrom:s||""}});return c.stdout?.on("data",e=>p.append(e.toString("utf8"))),c.stderr?.on("data",e=>p.append(e.toString("utf8"))),c.on("error",e=>{p.append(`\n派生失败:${e.message}\n`),p.settle("failed","派生失败")}),c.on("close",(e,o)=>{o?p.settle("killed",`被信号 ${o} 终止`):p.settle(0===e?"done":"failed",`退出码 ${e}`)}),c.unref(),{id:p.id,pid:c.pid,log:p.record.log,prompt:p.record.label,cwd:r,started:p.record.startedAt}}export function listForks(e){return o.list(e||"")}export function killFork(e,t){const r=o.kill(e,{sessionId:t||"",reason:"用户经 /tasks终止"});return r.ok?{ok:!0}:{ok:!1,reason:r.reason}}
@@ -0,0 +1 @@
1
+ import e from"node:fs";import t from"node:path";const n=new Map,r=new Set(["read_file","read_document","read_any"]),i=new Set(["write_file"]),s=new Set(["write_file","edit_file"]);function o(e){const t=String(e||"");return n.has(t)||n.set(t,new Map),n.get(t)}function l(t){try{const n=e.statSync(t);return n.isFile()?{size:n.size,mtimeMs:n.mtimeMs}:null}catch{return null}}export function noteObserved(e,t){const n=l(t);n&&o(e).set(t,n)}export function observationOf(e,t){return o(e).get(t)||null}export function dropSessionObservations(e){n.delete(String(e||""))}export function resetObservations(){n.clear()}export function guardOverwrite({sessionId:e,cwd:n,name:r,args:s}){if(!i.has(r))return null;const o=s?.path;if(!o)return null;const f=t.resolve(n||process.cwd(),String(o)),a=l(f);if(!a)return null;if(0===a.size)return null;const u=observationOf(e,f);return u?u.size!==a.size||u.mtimeMs!==a.mtimeMs?`文件在你读过之后被改动过,整篇覆盖会盖掉那次改动:${o}。(读到时 ${u.size} 字节,现在 ${a.size} 字节)可能是用户在编辑器里改了、或某条命令写过它。请先重新read_file("${o}") 确认现状再决定怎么写。`:null:`未读先覆盖被拦下:${o} 已存在(${a.size} 字节),而本次对话里你还没读过它。write_file是整文件覆盖,原文中你不知道的部分会一并消失。请先read_file("${o}") 看清现有内容;只想改其中一处就用edit_file,确要整篇重写再调write_file。`}export function noteAfterCall({sessionId:e,cwd:n,name:i,args:o,failed:l}){if(l)return;if(!r.has(i)&&!s.has(i))return;const f=o?.path;f&&noteObserved(e,t.resolve(n||process.cwd(),String(f)))}
package/src/gitflow.js CHANGED
@@ -1 +1 @@
1
- import t from"node:fs";import e from"node:os";import r from"node:path";import{execSync as n}from"node:child_process";import{streamChat as o}from"./api.js";import{DEFAULT_MODEL as i}from"./config.js";const c=/^(agent|workflow|datahuman|squad)-/,a=new Set(["main","master","develop","dev"]);function s(t,e){return n("git "+e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function m(t,e){try{return s(t,e)}catch{return""}}export function gitInfo(t){const e={isRepo:!1,branch:"",dirty:!1,hasRemote:!1,hasGh:!1};try{e.isRepo="true"===s(t,"rev-parse --is-inside-work-tree").trim()}catch{return e}e.branch=m(t,"rev-parse --abbrev-ref HEAD").trim(),e.dirty=m(t,"status --porcelain").trim().length>0,e.hasRemote=m(t,"remote").trim().length>0;try{n("gh --version",{stdio:"ignore"}),e.hasGh=!0}catch{}return e}export async function generateCommitMessage(t,e){let r=m(t,"diff --staged");if(r.trim()||(r=m(t,"diff")),r=r.slice(0,12e3),!r.trim())return"chore: update";const n=c.test(String(e.model||""))?e.compactModel||i:e.model;try{const t=await o({baseUrl:e.baseUrl,apiKey:e.apiKey,model:n,client:e.client,aux:!0,messages:[{role:"system",content:"你是提交信息生成器。根据 git diff 生成一条规范的 Conventional Commits 提交信息:第一行 `<type>(<scope>): <简述>`(简述≤50字,type 用 feat/fix/refactor/docs/test/chore/perf/style 之一);改动较大可空行后加 2-4 条正文要点。只输出提交信息本身,不要解释、不要代码块围栏。"},{role:"user",content:r}]});return(t.content||"").trim().replace(/^```[a-z]*\n?|\n?```$/g,"").trim()||"chore: update"}catch{return"chore: update"}}export async function runPrFlow({cwd:o,config:i,emit:c=()=>{},push:p=!0,openPr:h=!0}){const d=gitInfo(o);if(!d.isRepo)return{ok:!1,error:"当前目录不是 git 仓库"};const u=a.has(d.branch);d.dirty&&(s(o,"add -A"),c("已暂存全部改动"));const f=m(o,"diff --staged --stat").trim();let l="";f&&(c("正在生成提交信息…"),l=await generateCommitMessage(o,i),c("提交信息:"+l.split("\n")[0]));let g=d.branch;u&&(g="huicai/"+function(t){return String(t).split("\n")[0].replace(/^[a-z]+(\([^)]*\))?!?:\s*/i,"").replace(/[^a-zA-Z0-9一-鿿]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase().slice(0,32)||"change"}(l||"change")+"-"+Date.now().toString(36).slice(-4),s(o,`checkout -b ${g}`),c("已从 "+d.branch+" 创建分支 "+g));let y=!1;if(f){const n=r.join(e.tmpdir(),"huicai-commit-"+Date.now()+".txt");t.writeFileSync(n,l);try{s(o,`commit -F "${n}"`),y=!0,c("已提交")}finally{try{t.unlinkSync(n)}catch{}}}else c("无新暂存改动,跳过提交");if(!p)return{ok:!0,branch:g,committed:y};if(!d.hasRemote)return{ok:!0,branch:g,committed:y,note:"无 remote,未推送"};try{s(o,`push -u origin ${g}`),c("已推送 origin/"+g)}catch(t){return{ok:!1,error:"push 失败:"+String(t.message).split("\n")[0],branch:g}}if(!h)return{ok:!0,branch:g,committed:y,pushed:!0};if(!d.hasGh)return{ok:!0,branch:g,committed:y,pushed:!0,note:"未装 gh CLI,PR 请手动开"};try{const t=n("gh pr create --fill",{cwd:o,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();return c("已开 PR:"+t),{ok:!0,branch:g,committed:y,pr:t}}catch(t){return{ok:!0,branch:g,committed:y,pushed:!0,note:"gh pr create 失败(可手动开):"+String(t.message).split("\n")[0]}}}
1
+ import t from"node:fs";import e from"node:os";import r from"node:path";import{execSync as n}from"node:child_process";import{detect as o}from"./scopecreep.js";import{streamChat as i}from"./api.js";import{DEFAULT_MODEL as c}from"./config.js";const a=/^(agent|workflow|datahuman|squad)-/,s=new Set(["main","master","develop","dev"]);function m(t,e){return n("git "+e,{cwd:t,encoding:"utf8",stdio:["ignore","pipe","pipe"]})}function p(t,e){try{return m(t,e)}catch{return""}}export function gitInfo(t){const e={isRepo:!1,branch:"",dirty:!1,hasRemote:!1,hasGh:!1};try{e.isRepo="true"===m(t,"rev-parse --is-inside-work-tree").trim()}catch{return e}e.branch=p(t,"rev-parse --abbrev-ref HEAD").trim(),e.dirty=p(t,"status --porcelain").trim().length>0,e.hasRemote=p(t,"remote").trim().length>0;try{n("gh --version",{stdio:"ignore"}),e.hasGh=!0}catch{}return e}export async function generateCommitMessage(t,e){let r=p(t,"diff --staged");if(r.trim()||(r=p(t,"diff")),r=r.slice(0,12e3),!r.trim())return"chore: update";const n=a.test(String(e.model||""))?e.compactModel||c:e.model;try{const t=await i({baseUrl:e.baseUrl,apiKey:e.apiKey,model:n,client:e.client,aux:!0,messages:[{role:"system",content:"你是提交信息生成器。根据git diff生成一条规范的Conventional Commits提交信息:第一行 `<type>(<scope>): <简述>`(简述≤50字,type用feat/fix/refactor/docs/test/chore/perf/style之一);改动较大可空行后加 2-4 条正文要点。只输出提交信息本身,不要解释、不要代码块围栏。"},{role:"user",content:r}]});return(t.content||"").trim().replace(/^```[a-z]*\n?|\n?```$/g,"").trim()||"chore: update"}catch{return"chore: update"}}export async function runPrFlow({cwd:i,config:c,emit:a=()=>{},push:h=!0,openPr:d=!0}){const f=gitInfo(i);if(!f.isRepo)return{ok:!1,error:"当前目录不是git仓库"};const u=s.has(f.branch);f.dirty&&(m(i,"add -A"),a("已暂存全部改动"));const g=p(i,"diff --staged --stat").trim();let l="";g&&(a("正在生成提交信息…"),l=await generateCommitMessage(i,c),a("提交信息:"+l.split("\n")[0]));try{const t=o(p(i,"diff --staged")||p(i,"diff"),{intent:String(c.goal||"")});for(const e of t.findings||[])a("范围提示:"+e.what)}catch{}let y=f.branch;u&&(y="huicai/"+function(t){return String(t).split("\n")[0].replace(/^[a-z]+(\([^)]*\))?!?:\s*/i,"").replace(/[^a-zA-Z0-9一-鿿]+/g,"-").replace(/^-+|-+$/g,"").toLowerCase().slice(0,32)||"change"}(l||"change")+"-"+Date.now().toString(36).slice(-4),m(i,`checkout -b ${y}`),a("已从 "+f.branch+" 创建分支 "+y));let b=!1;if(g){const n=r.join(e.tmpdir(),"huicai-commit-"+Date.now()+".txt");t.writeFileSync(n,l);try{m(i,`commit -F "${n}"`),b=!0,a("已提交")}finally{try{t.unlinkSync(n)}catch{}}}else a("无新暂存改动,跳过提交");if(!h)return{ok:!0,branch:y,committed:b};if(!f.hasRemote)return{ok:!0,branch:y,committed:b,note:"无remote,未推送"};try{m(i,`push -u origin ${y}`),a("已推送origin/"+y)}catch(t){return{ok:!1,error:"push失败:"+String(t.message).split("\n")[0],branch:y}}if(!d)return{ok:!0,branch:y,committed:b,pushed:!0};if(!f.hasGh)return{ok:!0,branch:y,committed:b,pushed:!0,note:"未装gh CLI,PR请手动开"};try{const t=n("gh pr create --fill",{cwd:i,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();return a("已开PR:"+t),{ok:!0,branch:y,committed:b,pr:t}}catch(t){return{ok:!0,branch:y,committed:b,pushed:!0,note:"gh pr create失败(可手动开):"+String(t.message).split("\n")[0]}}}
@@ -0,0 +1 @@
1
+ export const PHASES=["active","paused","blocked","complete"];export const DEFAULT_MAX_ROUNDS=25;export const MIN_BLOCKED_ROUNDS=3;const e=new Set,o=new Map;let r=0;export function setRoundKind(e,r){o.set(String(e||""),"auto"===r?"auto":"human")}export function isHumanRound(e){return"auto"!==o.get(String(e||""))}export function getGoal(e){const o=e?.goal;return o&&"object"==typeof o&&o.id?o:null}export function isArmed(o){const r=getGoal(o);return!!r&&e.has(String(o?.id||"")+"|"+r.id)}function t(o,r){const t=getGoal(o);if(!t)return;const n=String(o?.id||"")+"|"+t.id;r?e.add(n):e.delete(n)}export function viewGoal(e){const o=getGoal(e);return o?{...o,armed:isArmed(e)}:null}function n(e){return e.rev+=1,e.updatedAt=Date.now(),e}function a(e,o){return null==o||Number(o)===e.rev}export function createGoal(e,{objective:o,maxRounds:n}={}){if(!e)return{ok:!1,error:"没有可归属的会话。"};if(!isHumanRound(e.id))return{ok:!1,error:"创建目标需要用户直接提出,不能在自动续跑轮里自行创建。"};const a=String(o||"").trim();if(!a)return{ok:!1,error:"缺少objective(要达成什么)。"};const i=getGoal(e);if(i&&"complete"!==i.phase)return{ok:!1,error:`已有未完成的目标(${i.phase}):${i.objective}。先 /goal clear清除,或 /goal resume继续它。`};const u=Number(n);return e.goal={id:`goal-${Date.now().toString(36)}-${++r}`,objective:a,phase:"active",rounds:0,maxRounds:Number.isFinite(u)&&u>0?Math.floor(u):25,blockedReason:"",rev:1,createdAt:Date.now(),updatedAt:Date.now(),idleRounds:0},t(e,!0),{ok:!0,goal:viewGoal(e)}}export function editGoal(e,{rev:o,objective:r,maxRounds:t}={}){const i=getGoal(e);if(!i)return{ok:!1,error:"当前没有目标。"};if(!isHumanRound(e.id))return{ok:!1,error:"编辑目标需要用户直接提出——自动续跑轮里不能自行改目标或放宽轮次上限。"};if(!a(i,o))return{ok:!1,error:`目标已被改动(当前rev=${i.rev},你报的是 ${o})。请先get_goal再改。`};if(void 0!==r&&String(r).trim()&&(i.objective=String(r).trim()),void 0!==t){const e=Number(t);Number.isFinite(e)&&e>0&&(i.maxRounds=Math.floor(e))}return n(i),{ok:!0,goal:viewGoal(e)}}export function pauseGoal(e,{rev:o}={}){const r=getGoal(e);return r?isHumanRound(e.id)?a(r,o)?(r.phase="paused",n(r),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"暂停目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}export function resumeGoal(e,{rev:o}={}){const r=getGoal(e);return r?isHumanRound(e.id)?a(r,o)?"complete"===r.phase?{ok:!1,error:"该目标已完成,无需恢复(要做新的事请新建目标)。"}:r.rounds>=r.maxRounds?{ok:!1,error:`已达轮次上限 ${r.maxRounds},请先用 /goal limit <N> 放宽再恢复。`}:(r.phase="active",r.blockedReason="",r.idleRounds=0,n(r),t(e,!0),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"恢复目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}export function completeGoal(e,{rev:o}={}){const r=getGoal(e);return r?a(r,o)?(r.phase="complete",n(r),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:`目标已被改动(当前rev=${r.rev})。`}:{ok:!1,error:"当前没有目标。"}}export function blockGoal(e,{rev:o,reason:r}={}){const i=getGoal(e);if(!i)return{ok:!1,error:"当前没有目标。"};if(!a(i,o))return{ok:!1,error:`目标已被改动(当前rev=${i.rev})。`};const u=String(r||"").trim();return u?i.rounds<3?{ok:!1,error:`目前只跑了 ${i.rounds} 轮,还不能判定阻塞(至少 3 轮)。请先换一种方法再试:碰一次壁不等于此路不通。`}:(i.phase="blocked",i.blockedReason=u,n(i),t(e,!1),{ok:!0,goal:viewGoal(e)}):{ok:!1,error:"声明阻塞必须给出reason(具体卡在哪、为什么绕不过去)。"}}export function clearGoal(e){return getGoal(e)?isHumanRound(e.id)?(t(e,!1),delete e.goal,{ok:!0}):{ok:!1,error:"清除目标需要用户直接提出。"}:{ok:!1,error:"当前没有目标。"}}function i(e){return(e?.messages||[]).filter(e=>"tool"===e.role).length}export function decideNextRound(e,o={}){const r=getGoal(e);if(!r)return{go:!1};if(!isArmed(e))return{go:!1,stop:"目标未武装(用 /goal resume继续)。"};if("active"!==r.phase)return{go:!1,stop:`目标已${{paused:"暂停",blocked:"阻塞",complete:"完成"}[r.phase]||r.phase}。`};if(r.rounds>=r.maxRounds)return t(e,!1),{go:!1,stop:`已达自动续跑轮次上限 ${r.maxRounds},停止。用 /goal limit <N> 放宽后 /goal resume可继续。`};const n=Number(o.budgetTokens)||0,a=Number(o.usedTokens)||0;if(n>0&&a>=n)return t(e,!1),{go:!1,stop:`已达token预算 ${n}(已用 ${a}),停止自动续跑。用 /budget调整后 /goal resume可继续。`};if(r.idleRounds>=2)return t(e,!1),{go:!1,stop:"连续 2 轮没有任何实际操作,判定为空转,已停止(避免无谓消耗)。"};return{go:!0,prompt:`【目标自治轮 ${r.rounds+1}/${r.maxRounds}】继续推进这个目标:${r.objective}\n要求:直接做下一步实事(改代码 / 跑验证 / 补测试),不要复述计划、不要只汇报进度。\n目标确实达成了,就用update_goal(action="complete") 声明完成;确实绕不过去了(且已连续多轮如此),用update_goal(action="blocked", reason=...) 说明卡点。`}}export function recordRound(e,{didWork:o}){const r=getGoal(e);r&&(r.rounds+=1,r.idleRounds=o?0:(r.idleRounds||0)+1,n(r))}export async function runGoalRounds({session:e,doTurn:o,stats:r,onInfo:n,aborted:a}){let u=0,s="";const c=e=>{try{n?.(e)}catch{}};for(;;){if(a?.()){s="已中断。";break}const n=decideNextRound(e,r?.()||{});if(!n.go){s=n.stop||"";break}const d=i(e);let l;setRoundKind(e.id,"auto");try{l=await o(n.prompt)}finally{setRoundKind(e.id,"human")}u++,recordRound(e,{didWork:i(e)>d});const p=getGoal(e);if(c(`目标自治轮 ${p?.rounds}/${p?.maxRounds} 结束。`),l?.aborted){s="已中断。",t(e,!1);break}if(l?.error){s="本轮出错,已停止自动续跑:"+l.error,t(e,!1);break}}return s&&c(s),{rounds:u,stop:s}}const u=(e,o,r={},t=[])=>({type:"function",function:{name:e,description:o,parameters:{type:"object",properties:r,required:t}}});function s(e){if(!e)return"当前没有设定目标。";const o={active:"进行中",paused:"已暂停",blocked:"受阻",complete:"已完成"}[e.phase]||e.phase;return`目标:${e.objective}\n状态:${o}${e.armed?"(自动续跑已武装)":"(未武装,不会自动续跑)"}\n已跑 ${e.rounds}/${e.maxRounds} 轮 rev=${e.rev}`+(e.blockedReason?`\n卡点:${e.blockedReason}`:"")}export function makeGoalTools({sessionOf:e}={}){const o=()=>"function"==typeof e?e():null;return[{readOnly:!0,spec:u("get_goal","读取当前会话的目标:目标内容、阶段、已跑轮数与上限、以及rev(改目标前必须先读它)。"),run:async()=>s(viewGoal(o()))},{readOnly:!1,spec:u("create_goal","当用户提出的是一个**需要跨多轮持续推进**的长目标(如「把这个模块重构完」「把测试补到全绿」)时创建它:之后每完成一轮会自动继续下一轮,直到你声明完成、声明受阻,或触及轮次/预算上限。一次性的小任务不要用它。只能在用户直接提出时创建,自动续跑轮里不可自行创建。",{objective:{type:"string",description:"要达成的具体目标(可验证的完成标准越明确越好)"},max_rounds:{type:"number",description:"可选:自动续跑轮次上限,默认 25"}},["objective"]),run:async e=>{const r=createGoal(o(),{objective:e?.objective,maxRounds:e?.max_rounds});return r.ok?"已创建目标并开始自动推进。\n"+s(r.goal):r.error}},{readOnly:!1,spec:u("update_goal","更新当前目标。complete=目标确实达成了;blocked=确实绕不过去(必须给reason,且要连续多轮都卡住才允许)。edit / pause / resume只能由用户直接提出,你在自动续跑轮里不能自行改目标、放宽上限或解除暂停。必须先用get_goal拿到rev再调用。",{rev:{type:"number",description:"get_goal返回的当前rev(防止基于过期状态改写)"},action:{type:"string",enum:["complete","blocked","edit","pause","resume"],description:"要做的变更"},reason:{type:"string",description:"action=blocked时必填:具体卡在哪、为什么绕不过去"},objective:{type:"string",description:"action=edit时的新目标"},max_rounds:{type:"number",description:"action=edit时的新轮次上限"}},["action"]),run:async e=>{const r=o(),t=e?.rev,n=String(e?.action||""),a="complete"===n?completeGoal(r,{rev:t}):"blocked"===n?blockGoal(r,{rev:t,reason:e?.reason}):"edit"===n?editGoal(r,{rev:t,objective:e?.objective,maxRounds:e?.max_rounds}):"pause"===n?pauseGoal(r,{rev:t}):"resume"===n?resumeGoal(r,{rev:t}):{ok:!1,error:"未知action:"+n};return a.ok?"已更新。\n"+s(a.goal):a.error}}]}export function resetGoalRuntime(){e.clear(),o.clear()}
package/src/hooks.js CHANGED
@@ -1 +1 @@
1
- import{spawnSync as t}from"node:child_process";import{loadRawSettings as o}from"./settings.js";export const HOOK_EVENTS=["SessionStart","UserPromptSubmit","PreToolUse","PostToolUse","Stop"];const r=new Set(["UserPromptSubmit","PreToolUse"]);function e(t){const o=String(t||"").trim();if(!o.startsWith("{"))return null;try{return JSON.parse(o)}catch{return null}}function s(t,o){const r=[];if(!t||"object"!=typeof t)return r;for(const e of HOOK_EVENTS){const s=t[e];if(Array.isArray(s))for(const t of s){const s="string"==typeof t?t:t?.command;"string"==typeof s&&s.trim()&&r.push({event:e,matcher:t&&"string"==typeof t.matcher&&t.matcher.trim()?t.matcher.trim():null,command:s.trim(),timeout:Number(t?.timeout)>0?Number(t.timeout):3e4,source:o})}}return r}export function loadHooks(t){const{global:r,project:e}=o(t),n=(c=r,"1"===process.env.HUICAI_PROJECT_HOOKS||!0===c?.hooks?.trustProject);var c;const i=[...s(r.hooks,"global")],u=s(e.hooks,"project");let m=0;n?i.push(...u):m=u.length;const a=Object.fromEntries(HOOK_EVENTS.map(t=>[t,[]]));for(const t of i)a[t.event].push(t);return{byEvent:a,all:i,projectIgnored:m,projectTrusted:n}}export function hookMatches(t,o){if(!t.matcher)return!0;if(!o)return!0;try{return new RegExp(t.matcher).test(o)}catch{return!1}}function n(t){return(t=String(t||"")).length>8e3?t.slice(0,8e3)+"\n…(钩子输出已截断)":t}function c(o,r,e){const s="win32"===process.platform?t(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",o.command],{cwd:r.HUICAI_CWD,env:r,input:e,encoding:"utf8",timeout:o.timeout,maxBuffer:4194304,windowsVerbatimArguments:!0}):t(process.env.SHELL||"/bin/sh",["-c",o.command],{cwd:r.HUICAI_CWD,env:r,input:e,encoding:"utf8",timeout:o.timeout,maxBuffer:4194304}),c=n(s.stdout),i=n(s.stderr),u=s.error&&"ETIMEDOUT"===s.error.code;return{stdout:c,stderr:i,status:u?124:null==s.status?1:s.status,timedOut:u}}export function runHooks(t,o,s){const n=o.cwd,i=(s?.byEvent?.[t]||loadHooks(n).byEvent[t]||[]).filter(t=>hookMatches(t,o.toolName)),u={block:!1,reason:"",outputs:[],injectContext:"",modifiedArgs:null};if(!i.length)return u;const m={...process.env,HUICAI_EVENT:t,HUICAI_CWD:n||process.cwd(),HUICAI_TOOL_NAME:o.toolName||"",HUICAI_TOOL_PATH:o.args&&null!=o.args.path?String(o.args.path):"",HUICAI_TOOL_ARGS:(()=>{try{return JSON.stringify(o.args||{})}catch{return"{}"}})()},a=(()=>{try{return JSON.stringify(o)}catch{return"{}"}})();for(const o of i){let s;try{s=c(o,m,a)}catch(t){s={stdout:"",stderr:String(t.message||t),status:1,timedOut:!1}}const n=e(s.stdout);if(n){if("string"==typeof n.context&&n.context.trim()&&(u.injectContext+=(u.injectContext?"\n":"")+n.context.trim()),n.modifiedArgs&&"object"==typeof n.modifiedArgs&&(u.modifiedArgs={...u.modifiedArgs||{},...n.modifiedArgs}),"block"===n.decision&&r.has(t)){u.block=!0,u.reason=String(n.reason||"钩子决定阻断该操作");break}continue}if((s.stdout||s.stderr||"").trim()&&u.outputs.push({command:o.command,stdout:s.stdout.trim()||s.stderr.trim()}),0!==s.status&&r.has(t)){u.block=!0,u.reason=s.timedOut?`钩子超时(${o.command})`:s.stderr.trim()||s.stdout.trim()||`钩子以退出码 ${s.status} 阻断(${o.command})`;break}}return u}export function formatHooks(t){const o=[];if(t.all.length)for(const r of HOOK_EVENTS){const e=t.byEvent[r];if(e.length){o.push(`${r}`);for(const t of e){const r=t.matcher?`[${t.matcher}] `:"";o.push(` · ${r}${t.command}${"global"===t.source?"":"(项目)"}`)}}}else o.push('未配置钩子。可在 ~/.huicai/settings.json 或 项目 .huicai/settings.json 的 "hooks" 中添加。');return t.projectIgnored>0&&(o.push(""),o.push(`⚠ 已忽略 ${t.projectIgnored} 条项目级钩子(未信任本项目)。`),o.push(" 如信任本项目,设 HUICAI_PROJECT_HOOKS=1,或全局设置 hooks.trustProject=true。")),o.push(""),o.push("事件:SessionStart(会话开始,注入上下文)· UserPromptSubmit(提交前,注入/阻断)· PreToolUse(工具前,阻断/改参数)· PostToolUse(工具后,采输出)· Stop(回合结束)。"),o.push('结构化控制:钩子可把 JSON 打到 stdout —— {"decision":"block","reason":..} 阻断 / {"context":".."} 注入上下文 / {"modifiedArgs":{..}} 改工具参数。'),o.join("\n")}
1
+ import t from"node:fs";import o from"node:os";import e from"node:path";import{spawnSync as r}from"node:child_process";import{loadRawSettings as s}from"./settings.js";export const HOOK_EVENTS=["SessionStart","UserPromptSubmit","PreToolUse","PostToolUse","Stop"];const n=new Set(["UserPromptSubmit","PreToolUse"]);function i(t){const o=String(t||"").trim();if(!o.startsWith("{"))return null;try{return JSON.parse(o)}catch{return null}}function c(t,o,e="huicai"){const r=[];if(!t||"object"!=typeof t)return r;for(const s of HOOK_EVENTS){const n=t[s];if(Array.isArray(n))for(const t of n){const n="string"==typeof t?t:t?.command;if("string"!=typeof n||!n.trim())continue;const i=HOOK_DIALECTS.includes(t?.dialect)?t.dialect:e;r.push({event:s,matcher:t&&"string"==typeof t.matcher&&t.matcher.trim()?t.matcher.trim():null,command:n.trim(),timeout:Number(t?.timeout)>0?Number(t.timeout):3e4,source:o,dialect:i})}}return r}export function loadHooks(r){const{global:n,project:i}=s(r),u=(d=n,"1"===process.env.HUICAI_PROJECT_HOOKS||!0===d?.hooks?.trustProject);var d;const a=[...c(n.hooks,"global")];a.push(...function(r){const s=String(r||"").trim();if(!s)return[];const n=s.startsWith("~/")?e.join(o.homedir(),s.slice(2)):s;try{const o=JSON.parse(t.readFileSync(n,"utf8"));return c(o?.hooks||o,"codex","codex")}catch{return[]}}(n?.hooks?.codexConfig));const m=c(i.hooks,"project");let l=0;u?a.push(...m):l=m.length;const p=Object.fromEntries(HOOK_EVENTS.map(t=>[t,[]]));for(const t of a)p[t.event].push(t);return{byEvent:p,all:a,projectIgnored:l,projectTrusted:u}}export const HOOK_DIALECTS=["huicai","codex"];const u={huicai:{stdin:(t,o)=>{try{return JSON.stringify(o)}catch{return"{}"}},env:(t,o)=>({HUICAI_EVENT:t,HUICAI_CWD:o.cwd||process.cwd(),HUICAI_TOOL_NAME:o.toolName||"",HUICAI_TOOL_PATH:o.args&&null!=o.args.path?String(o.args.path):"",HUICAI_TOOL_ARGS:(()=>{try{return JSON.stringify(o.args||{})}catch{return"{}"}})()}),allowsModifiedArgs:!0},codex:{stdin:(t,o)=>{const e={session_id:o.sessionId||"",transcript_path:o.transcriptPath||null,cwd:o.cwd||process.cwd(),hook_event_name:t,model:o.model||"",permission_mode:"default"};"SessionStart"!==t&&(e.turn_id=String(o.turn??"")),"PreToolUse"!==t&&"PostToolUse"!==t||(e.tool_name=o.toolName||"",e.tool_input={command:"string"==typeof o.args?.command?o.args.command:""},e.tool_use_id=o.callId||"","PostToolUse"===t&&(e.tool_response=String(o.result??""))),"UserPromptSubmit"===t&&(e.prompt=String(o.prompt??""));try{return JSON.stringify(e)}catch{return"{}"}},env:()=>({}),allowsModifiedArgs:!1}};export function hookMatches(t,o){if(!t.matcher)return!0;if(!o)return!0;try{return new RegExp(t.matcher).test(o)}catch{return!1}}function d(t){return(t=String(t||"")).length>8e3?t.slice(0,8e3)+"\n…(钩子输出已截断)":t}function a(t,o,e){const s="win32"===process.platform?r(process.env.COMSPEC||"cmd.exe",["/d","/s","/c",t.command],{cwd:o.HUICAI_CWD,env:o,input:e,encoding:"utf8",timeout:t.timeout,maxBuffer:4194304,windowsVerbatimArguments:!0}):r(process.env.SHELL||"/bin/sh",["-c",t.command],{cwd:o.HUICAI_CWD,env:o,input:e,encoding:"utf8",timeout:t.timeout,maxBuffer:4194304}),n=d(s.stdout),i=d(s.stderr),c=s.error&&"ETIMEDOUT"===s.error.code;return{stdout:n,stderr:i,status:c?124:null==s.status?1:s.status,timedOut:c}}export function runHooks(t,o,e){const r=o.cwd,s=(e?.byEvent?.[t]||loadHooks(r).byEvent[t]||[]).filter(t=>hookMatches(t,o.toolName)),c={block:!1,reason:"",outputs:[],injectContext:"",modifiedArgs:null};if(!s.length)return c;for(const e of s){const s=u[e.dialect]||u.huicai,d={...process.env,HUICAI_CWD:r||process.cwd(),...s.env(t,o)},m=s.stdin(t,o);let l;try{l=a(e,d,m)}catch(t){l={stdout:"",stderr:String(t.message||t),status:1,timedOut:!1}}const p=i(l.stdout);if(p){if("string"==typeof p.context&&p.context.trim()&&(c.injectContext+=(c.injectContext?"\n":"")+p.context.trim()),p.modifiedArgs&&"object"==typeof p.modifiedArgs&&(u[e.dialect]||u.huicai).allowsModifiedArgs&&(c.modifiedArgs={...c.modifiedArgs||{},...p.modifiedArgs}),"block"===p.decision&&n.has(t)){c.block=!0,c.reason=String(p.reason||"钩子决定阻断该操作");break}continue}if((l.stdout||l.stderr||"").trim()&&c.outputs.push({command:e.command,stdout:l.stdout.trim()||l.stderr.trim()}),0!==l.status&&n.has(t)){c.block=!0,c.reason=l.timedOut?`钩子超时(${e.command})`:l.stderr.trim()||l.stdout.trim()||`钩子以退出码 ${l.status} 阻断(${e.command})`;break}}return c}export function formatHooks(t){const o=[];if(t.all.length)for(const e of HOOK_EVENTS){const r=t.byEvent[e];if(r.length){o.push(`${e}`);for(const t of r){const e=t.matcher?`[${t.matcher}] `:"",r="codex"===t.dialect?"(Codex方言)":"global"===t.source?"":"(项目)";o.push(` · ${e}${t.command}${r}`)}}}else o.push('未配置钩子。可在 ~/.huicai/settings.json或 项目 .huicai/settings.json的 "hooks" 中添加。');return t.projectIgnored>0&&(o.push(""),o.push(`⚠ 已忽略 ${t.projectIgnored} 条项目级钩子(未信任本项目)。`),o.push(" 如信任本项目,设HUICAI_PROJECT_HOOKS=1,或全局设置hooks.trustProject=true。")),o.push(""),o.push("事件:SessionStart(会话开始,注入上下文)· UserPromptSubmit(提交前,注入/阻断)· PreToolUse(工具前,阻断/改参数)· PostToolUse(工具后,采输出)· Stop(回合结束)。"),o.push('结构化控制:钩子可把JSON打到stdout —— {"decision":"block","reason":..} 阻断 / {"context":".."} 注入上下文 / {"modifiedArgs":{..}} 改工具参数。'),o.join("\n")}
package/src/input.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import t from"node:path";import n from"node:os";import{CONFIG_DIR as r}from"./config.js";import{runBash as o}from"./executors.js";import{extractFileCached as s}from"./doccache.js";const i=t.join(r,"history.json"),a={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",bmp:"image/bmp",svg:"image/svg+xml"},c=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","rtf","odt","ods","odp","csv"]),l=8388608,p=20971520;export async function expandAtMentions(r,o,i=null,u={}){const g=/(^|[\s("'`(【,,::])@([~A-Za-z0-9_./\\-]+)/g,f=new Set,d=[];let m;for(;(m=g.exec(r))&&d.length<5;){const e=m[2].replace(/[.,;:。,;]+$/,"");e&&!f.has(e)&&(f.add(e),d.push(e))}const $=[],y=[],x=[],w=[];for(const r of d){const s=r.startsWith("~")?t.join(n.homedir(),r.slice(1)):t.resolve(o,r);let u;try{u=e.statSync(s)}catch{continue}if(x.push(r),u.isDirectory()){try{const t=e.readdirSync(s).slice(0,100).join("\n");$.push(`【引用目录 @${r}】\n${t||"(空目录)"}`)}catch{}continue}const g=t.extname(s).slice(1).toLowerCase();if(a[g])if(u.size>l)$.push(`【图片 @${r}】(超过 ${Math.round(8)}MB,未附加)`);else try{const t=e.readFileSync(s).toString("base64");y.push({label:r,dataUri:`data:${a[g]};base64,${t}`}),$.push(`【已附加图片 @${r}】`)}catch{}else if(c.has(g))i&&i.baseUrl&&i.apiKey?u.size>p?$.push(`【文档 @${r}】(超过 ${Math.round(20)}MB,未解析)`):w.push({index:$.push("")-1,raw:r,abs:s,st:u}):$.push(`【文档 @${r}】(需登录后才能解析为文本)`);else if(u.size>524288)$.push(`【引用文件 @${r}】\n(文件过大:${Math.round(u.size/1024)}KB,未展开;可让我用 read_file 分段读取)`);else try{const t=e.readFileSync(s);if(h(t)){$.push(`【引用文件 @${r}】\n(二进制文件,未展开)`);continue}let n=t.toString("utf8");n.length>12e3&&(n=n.slice(0,12e3)+"\n…(已截断)"),$.push(`【引用文件 @${r}】\n\`\`\`\n${n}\n\`\`\``)}catch{}}if(w.length){const e=/^(agent|workflow|datahuman|squad)-/.test(String(i?.model||""));let t=0;const n=async({index:n,raw:r,abs:o,st:a})=>{try{const{result:c,cached:l}=await s(i,o,a,{maxChars:6e4,fetch:u.extract,onMiss:()=>u.onDocStart&&u.onDocStart(r,++t,w.length)}),p=c.truncated?e?"·已截断(头尾摘要)":"·已截断(头尾摘要,被省略部分可用 read_document 工具按 offset 分段读取)":"",h=`${c.pages?c.pages+"页·":""}${c.chars??0}字${p}`;$[n]=`【引用文档 @${r}(${h})】\n${c.text||"(空)"}`,u.onDocDone&&u.onDocDone(r,{cached:l})}catch(e){$[n]=`【文档 @${r}】(解析失败:${e.message})`,u.onDocDone&&u.onDocDone(r,{error:e.message})}},r=w.slice();await Promise.all(Array.from({length:Math.min(3,r.length)},async()=>{for(;r.length;)await n(r.shift())}))}return{text:$.length?r+"\n\n"+$.join("\n\n"):r,mentions:x,images:y}}function h(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}const u=new RegExp("(^|\\n\\n)((?:【(?:引用文档|引用文件|引用目录) @[^】\\n]*】|【上下文:[^】\\n]*】|【文档附件:[^】\\n]*】))\\n([\\s\\S]*?)(?=\\n\\n(?:【(?:引用文档|引用文件|引用目录|文档|图片|已附加图片) @|【上下文:|【文档附件:)|$)","g");export function collapseMentionBlocks(e){const t=String(e||"");if(!/【(?:引用文档|引用文件|引用目录) @|【上下文:|【文档附件:/.test(t))return t;const n=t.startsWith("【文档附件:");return t.replace(u,(e,r,o,s,i)=>n&&i+e.length===t.length||s.length<=200?e:`${r}${o}(${s.length} 字已折叠,继续对话不受影响)`)}export function buildUserContent(e,t){return t&&t.length?[{type:"text",text:e},...t.map(e=>({type:"image_url",image_url:{url:e.dataUri}}))]:e}export async function runBangCommand(e,t){const n=await o(t,{command:e}),r=n.length>8e3?n.slice(0,8e3)+"\n…(已截断)":n;return{output:n,record:`我在终端直接执行了命令:$ ${e}\n${r}`}}function g(){try{const t=JSON.parse(e.readFileSync(i,"utf8"));return Array.isArray(t?.entries)?t.entries:[]}catch{return[]}}export function loadHistory(e,t=200){return g().filter(t=>t&&t.cwd===e&&"string"==typeof t.line).slice(-t).map(e=>e.line)}export function appendHistory(t,n){const o=String(n||"").trim();if(o)try{let n=g();const s=[...n].reverse().find(e=>e.cwd===t);if(s&&s.line===o)return;n.push({cwd:t,line:o,ts:Date.now()}),n.length>1e3&&(n=n.slice(-1e3)),e.mkdirSync(r,{recursive:!0}),e.writeFileSync(i,JSON.stringify({entries:n}),{mode:384})}catch{}}
1
+ import e from"node:fs";import t from"node:path";import n from"node:os";import{CONFIG_DIR as r}from"./config.js";import{runBash as o}from"./executors.js";import{extractFileCached as s}from"./doccache.js";const i=t.join(r,"history.json"),a={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",bmp:"image/bmp",svg:"image/svg+xml"},c=new Set(["pdf","doc","docx","xls","xlsx","ppt","pptx","rtf","odt","ods","odp","csv"]),l=8388608,p=20971520;export async function expandAtMentions(r,o,i=null,u={}){const g=/(^|[\s("'`(【,,::])@([~A-Za-z0-9_./\\-]+)/g,f=new Set,d=[];let m;for(;(m=g.exec(r))&&d.length<5;){const e=m[2].replace(/[.,;:。,;]+$/,"");e&&!f.has(e)&&(f.add(e),d.push(e))}const $=[],y=[],x=[],w=[];for(const r of d){const s=r.startsWith("~")?t.join(n.homedir(),r.slice(1)):t.resolve(o,r);let u;try{u=e.statSync(s)}catch{continue}if(x.push(r),u.isDirectory()){try{const t=e.readdirSync(s).slice(0,100).join("\n");$.push(`【引用目录 @${r}】\n${t||"(空目录)"}`)}catch{}continue}const g=t.extname(s).slice(1).toLowerCase();if(a[g])if(u.size>l)$.push(`【图片 @${r}】(超过 ${Math.round(8)}MB,未附加)`);else try{const t=e.readFileSync(s).toString("base64");y.push({label:r,dataUri:`data:${a[g]};base64,${t}`}),$.push(`【已附加图片 @${r}】`)}catch{}else if(c.has(g))i&&i.baseUrl&&i.apiKey?u.size>p?$.push(`【文档 @${r}】(超过 ${Math.round(20)}MB,未解析)`):w.push({index:$.push("")-1,raw:r,abs:s,st:u}):$.push(`【文档 @${r}】(需登录后才能解析为文本)`);else if(u.size>524288)$.push(`【引用文件 @${r}】\n(文件过大:${Math.round(u.size/1024)}KB,未展开;可让我用read_file分段读取)`);else try{const t=e.readFileSync(s);if(h(t)){$.push(`【引用文件 @${r}】\n(二进制文件,未展开)`);continue}let n=t.toString("utf8");n.length>12e3&&(n=n.slice(0,12e3)+"\n…(已截断)"),$.push(`【引用文件 @${r}】\n\`\`\`\n${n}\n\`\`\``)}catch{}}if(w.length){const e=/^(agent|workflow|datahuman|squad)-/.test(String(i?.model||""));let t=0;const n=async({index:n,raw:r,abs:o,st:a})=>{try{const{result:c,cached:l}=await s(i,o,a,{maxChars:6e4,fetch:u.extract,onMiss:()=>u.onDocStart&&u.onDocStart(r,++t,w.length)}),p=c.truncated?e?"·已截断(头尾摘要)":"·已截断(头尾摘要,被省略部分可用read_document工具按offset分段读取)":"",h=`${c.pages?c.pages+"页·":""}${c.chars??0}字${p}`;$[n]=`【引用文档 @${r}(${h})】\n${c.text||"(空)"}`,u.onDocDone&&u.onDocDone(r,{cached:l})}catch(e){$[n]=`【文档 @${r}】(解析失败:${e.message})`,u.onDocDone&&u.onDocDone(r,{error:e.message})}},r=w.slice();await Promise.all(Array.from({length:Math.min(3,r.length)},async()=>{for(;r.length;)await n(r.shift())}))}return{text:$.length?r+"\n\n"+$.join("\n\n"):r,mentions:x,images:y}}function h(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}const u=new RegExp("(^|\\n\\n)((?:【(?:引用文档|引用文件|引用目录) @[^】\\n]*】|【上下文:[^】\\n]*】|【文档附件:[^】\\n]*】))\\n([\\s\\S]*?)(?=\\n\\n(?:【(?:引用文档|引用文件|引用目录|文档|图片|已附加图片) @|【上下文:|【文档附件:)|$)","g");export function collapseMentionBlocks(e){const t=String(e||"");if(!/【(?:引用文档|引用文件|引用目录) @|【上下文:|【文档附件:/.test(t))return t;const n=t.startsWith("【文档附件:");return t.replace(u,(e,r,o,s,i)=>n&&i+e.length===t.length||s.length<=200?e:`${r}${o}(${s.length} 字已折叠,继续对话不受影响)`)}export function buildUserContent(e,t){return t&&t.length?[{type:"text",text:e},...t.map(e=>({type:"image_url",image_url:{url:e.dataUri}}))]:e}export async function runBangCommand(e,t){const n=await o(t,{command:e}),r=n.length>8e3?n.slice(0,8e3)+"\n…(已截断)":n;return{output:n,record:`我在终端直接执行了命令:$ ${e}\n${r}`}}function g(){try{const t=JSON.parse(e.readFileSync(i,"utf8"));return Array.isArray(t?.entries)?t.entries:[]}catch{return[]}}export function loadHistory(e,t=200){return g().filter(t=>t&&t.cwd===e&&"string"==typeof t.line).slice(-t).map(e=>e.line)}export function appendHistory(t,n){const o=String(n||"").trim();if(o)try{let n=g();const s=[...n].reverse().find(e=>e.cwd===t);if(s&&s.line===o)return;n.push({cwd:t,line:o,ts:Date.now()}),n.length>1e3&&(n=n.slice(-1e3)),e.mkdirSync(r,{recursive:!0}),e.writeFileSync(i,JSON.stringify({entries:n}),{mode:384})}catch{}}
package/src/insights.js CHANGED
@@ -1 +1 @@
1
- import t from"node:path";import{c as e}from"./colors.js";import{relTime as n}from"./session.js";const s=864e5;function o(t,e){return[...t.entries()].sort((t,e)=>e[1]-t[1]).slice(0,e)}export function computeInsights(e,n={}){const r=n.now||Date.now(),l=Array.isArray(e)?e:[],a=new Map,c=new Map;let u=0,i=0,d=0,m=0,h=null,g=0;for(const e of l){const n=Number(e.turns)||0;u+=n;const o=e.cwd?t.basename(e.cwd)||e.cwd:"(未知)";a.set(o,(a.get(o)||0)+1);const l=e.model||"(未知)";c.set(l,(c.get(l)||0)+1);const p=r-(e.updatedAt||0);p<=s&&i++,p<=7*s&&d++,p<=30*s&&m++,(!h||n>h.turns)&&(h={turns:n,title:e.title||""}),(e.updatedAt||0)>g&&(g=e.updatedAt||0)}const p=new Array(14).fill(0),f=new Date(r);f.setHours(0,0,0,0);for(const t of l){const e=new Date(t.updatedAt||0);e.setHours(0,0,0,0);const n=Math.floor((f.getTime()-e.getTime())/s);n>=0&&n<14&&p[13-n]++}const $=n.cwd?t.basename(n.cwd)||n.cwd:null,w=$?{name:$,sessions:l.filter(t=>t.cwd===n.cwd).length,turns:l.filter(t=>t.cwd===n.cwd).reduce((t,e)=>t+(Number(e.turns)||0),0)}:null;return{total:l.length,totalTurns:u,avgTurns:l.length?u/l.length:0,last24:i,last7:d,last30:m,mostRecent:g,longest:h,byProject:o(a,5),byModel:o(c,5),currentProject:w,daily:p}}export function formatInsights(t){if(!t.total)return"本机还没有历史会话(对话发生后即可 /insights 查看统计)。";const s=[e.bold("会话洞察")+e.gray("(本机 ~/.huicai/sessions)")];return s.push(` 总计 ${t.total} 个会话 · ${t.totalTurns} 轮对话`+(t.mostRecent?` · 最近活跃 ${n(t.mostRecent)}`:"")),s.push(` 近况 24h ${t.last24} 个 · 7天 ${t.last7} 个 · 30天 ${t.last30} 个`),t.daily&&t.daily.some(t=>t>0)&&s.push(` 近14天 ${function(t){const e=Math.max(1,...t);return t.map(t=>0===t?"·":"▁▂▃▄▅▆▇█"[Math.min(7,Math.floor(t/e*7))]).join("")}(t.daily)} ${e.gray("(峰值 "+Math.max(...t.daily)+"/天)")}`),s.push(` 平均 ${t.avgTurns.toFixed(1)} 轮/会话`+(t.longest?` · 最长 ${t.longest.turns} 轮${t.longest.title?"("+t.longest.title.slice(0,24)+")":""}`:"")),t.byProject.length&&s.push(" 按项目 "+t.byProject.map(([t,e])=>`${t} ${e}`).join(" · ")),t.byModel.length&&s.push(" 按模型 "+t.byModel.map(([t,e])=>`${t.length>20?t.slice(0,20)+"…":t} ${e}`).join(" · ")),t.currentProject&&s.push(e.gray(` 当前项目 ${t.currentProject.name}:${t.currentProject.sessions} 个会话 · ${t.currentProject.turns} 轮`)),s.join("\n")}
1
+ import t from"node:path";import{c as e}from"./colors.js";import{relTime as n}from"./session.js";const s=864e5;function o(t,e){return[...t.entries()].sort((t,e)=>e[1]-t[1]).slice(0,e)}export function computeInsights(e,n={}){const r=n.now||Date.now(),l=Array.isArray(e)?e:[],a=new Map,c=new Map;let u=0,i=0,d=0,m=0,h=null,g=0;for(const e of l){const n=Number(e.turns)||0;u+=n;const o=e.cwd?t.basename(e.cwd)||e.cwd:"(未知)";a.set(o,(a.get(o)||0)+1);const l=e.model||"(未知)";c.set(l,(c.get(l)||0)+1);const p=r-(e.updatedAt||0);p<=s&&i++,p<=7*s&&d++,p<=30*s&&m++,(!h||n>h.turns)&&(h={turns:n,title:e.title||""}),(e.updatedAt||0)>g&&(g=e.updatedAt||0)}const p=new Array(14).fill(0),f=new Date(r);f.setHours(0,0,0,0);for(const t of l){const e=new Date(t.updatedAt||0);e.setHours(0,0,0,0);const n=Math.floor((f.getTime()-e.getTime())/s);n>=0&&n<14&&p[13-n]++}const $=n.cwd?t.basename(n.cwd)||n.cwd:null,w=$?{name:$,sessions:l.filter(t=>t.cwd===n.cwd).length,turns:l.filter(t=>t.cwd===n.cwd).reduce((t,e)=>t+(Number(e.turns)||0),0)}:null;return{total:l.length,totalTurns:u,avgTurns:l.length?u/l.length:0,last24:i,last7:d,last30:m,mostRecent:g,longest:h,byProject:o(a,5),byModel:o(c,5),currentProject:w,daily:p}}export function formatInsights(t){if(!t.total)return"本机还没有历史会话(对话发生后即可 /insights查看统计)。";const s=[e.bold("会话洞察")+e.gray("(本机 ~/.huicai/sessions)")];return s.push(` 总计 ${t.total} 个会话 · ${t.totalTurns} 轮对话`+(t.mostRecent?` · 最近活跃 ${n(t.mostRecent)}`:"")),s.push(` 近况 24h ${t.last24} 个 · 7天 ${t.last7} 个 · 30天 ${t.last30} 个`),t.daily&&t.daily.some(t=>t>0)&&s.push(` 近14天 ${function(t){const e=Math.max(1,...t);return t.map(t=>0===t?"·":"▁▂▃▄▅▆▇█"[Math.min(7,Math.floor(t/e*7))]).join("")}(t.daily)} ${e.gray("(峰值 "+Math.max(...t.daily)+"/天)")}`),s.push(` 平均 ${t.avgTurns.toFixed(1)} 轮/会话`+(t.longest?` · 最长 ${t.longest.turns} 轮${t.longest.title?"("+t.longest.title.slice(0,24)+")":""}`:"")),t.byProject.length&&s.push(" 按项目 "+t.byProject.map(([t,e])=>`${t} ${e}`).join(" · ")),t.byModel.length&&s.push(" 按模型 "+t.byModel.map(([t,e])=>`${t.length>20?t.slice(0,20)+"…":t} ${e}`).join(" · ")),t.currentProject&&s.push(e.gray(` 当前项目 ${t.currentProject.name}:${t.currentProject.sessions} 个会话 · ${t.currentProject.turns} 轮`)),s.join("\n")}
package/src/jobs.js ADDED
@@ -0,0 +1 @@
1
+ import t from"node:fs";import{areaDir as e,createPrivateFile as n,dropSessionDir as r,pruneArea as o,makeThrottle as i,safeSegment as s}from"./privatedir.js";export const JOBS_AREA="jobs";export const JOBS_DIR=e("jobs");const u=i(18e5);export const MAX_READ_CHARS=2e4;export const DEFAULT_WAIT_MS=6e4;export const MAX_WAIT_MS=3e5;const a=8388608,c=new Set(["done","failed","killed"]),d=new Map,l=new Map,f=new Map;export function start({kind:e,label:r,sessionId:i,cwd:g,kill:m,meta:y}){const h=function(t){const e=s(t,"job",16),n=(l.get(e)||0)+1;return l.set(e,n),`${e}-${n}`}(e||"job"),k=n("jobs",i,h,""),w={id:h,kind:String(e||"job"),label:String(r||"").replace(/\s+/g," ").slice(0,120),sessionId:String(i||""),cwd:String(g||""),status:"running",note:"",startedAt:Date.now(),endedAt:0,log:k?k.path:"",buf:"",bytes:0,cursor:0,meta:y||{},_kill:"function"==typeof m?m:null};return d.set(h,w),u()&&o("jobs",6048e5),{id:h,record:w,append(e){!function(e,n){const r=String(n??"");if(!r)return;if(e.bytes>=a)return;if(e.bytes+=Buffer.byteLength(r,"utf8"),e.log)try{return void t.appendFileSync(e.log,r,{mode:384})}catch{e.log=""}e.buf+=r,e.buf.length>a&&(e.buf=e.buf.slice(-8388608))}(w,e)},settle(t,e){!function(t,e,n){if(c.has(t.status))return;t.status=c.has(e)?e:"done",t.note=String(n||""),t.endedAt=Date.now();const r=f.get(t.id)||[];f.delete(t.id);for(const t of r)try{t()}catch{}!function(t){for(const e of p)try{e(b(t))}catch{}}(t)}(w,t,e)}}}const p=new Set;export function onSettled(t){return"function"!=typeof t?()=>{}:(p.add(t),()=>p.delete(t))}function b(t){return{id:t.id,kind:t.kind,label:t.label,sessionId:t.sessionId,cwd:t.cwd,status:t.status,note:t.note,startedAt:t.startedAt,endedAt:t.endedAt,bytes:t.bytes,running:!c.has(t.status)}}function g(t,e){return t.sessionId===String(e||"")}export function list(t){return[...d.values()].filter(e=>g(e,t)).sort((t,e)=>e.startedAt-t.startedAt).map(b)}export function get(t,e){const n=d.get(String(t||""));return n&&g(n,e)?b(n):null}export function read(e,n={}){const r=d.get(String(e||""));if(!r||!g(r,n.sessionId))return{ok:!1,reason:"未找到该作业(或它不属于当前会话)",text:"",view:null};const o=!!n.all,i=function(e){if(!e.log)return e.buf;try{return t.readFileSync(e.log,"utf8")}catch{return e.buf}}(r),s=o?0:Math.min(r.cursor,i.length);let u=i.slice(s);o||(r.cursor=i.length);const a=Number.isFinite(n.maxChars)&&n.maxChars>0?Math.min(n.maxChars,2e4):2e4;return u.length>a&&(u=`…(已省略前 ${u.length-a} 字符,只显示最新部分)…\n`+u.slice(-a)),{ok:!0,text:u,view:b(r)}}export function wait(t,{sessionId:e,timeoutMs:n}={}){const r=d.get(String(t||""));if(!r||!g(r,e))return Promise.resolve({ok:!1,timedOut:!1,view:null});if(c.has(r.status))return Promise.resolve({ok:!0,timedOut:!1,view:b(r)});const o=Math.min(Math.max(Number(n)||6e4,1e3),3e5);return new Promise(t=>{let e=!1;const n=n=>{if(e)return;e=!0,clearTimeout(s);const o=f.get(r.id);if(o){const t=o.indexOf(i);t>=0&&o.splice(t,1)}t({ok:!0,timedOut:n,view:b(r)})},i=()=>n(!1),s=setTimeout(()=>n(!0),o),u=f.get(r.id)||[];u.push(i),f.set(r.id,u)})}export function kill(t,{sessionId:e,reason:n}={}){const r=d.get(String(t||""));if(!r||!g(r,e))return{ok:!1,reason:"未找到该作业(或它不属于当前会话)"};if(c.has(r.status))return{ok:!0,view:b(r)};if(!r._kill)return{ok:!1,reason:"该作业不支持终止"};try{r._kill(String(n||""))}catch(t){return{ok:!1,reason:t.message}}return{ok:!0,view:b(r)}}export function dropSessionJobs(t){const e=String(t||"");for(const[t,n]of[...d.entries()])if(n.sessionId===e){if(!c.has(n.status)&&n._kill)try{n._kill("会话已删除")}catch{}d.delete(t)}return r("jobs",e)}export function resetRegistry(){d.clear(),l.clear(),f.clear(),p.clear()}export function describe(t){const e=((t.endedAt||Date.now())-t.startedAt)/1e3,n=t.running?"运行中":{done:"已完成",failed:"失败",killed:"已终止"}[t.status]||t.status,r=e<60?`${Math.round(e)}秒`:`${Math.round(e/60)}分钟`;return`${t.id} [${n}] ${r} ${t.label}${t.note?" "+t.note:""}`}const m=(t,e,n={},r=[])=>({type:"function",function:{name:t,description:e,parameters:{type:"object",properties:n,required:r}}});export function makeJobTools({sessionId:t}={}){const e="function"==typeof t?()=>String(t()||""):()=>String(t||"");return[{spec:m("job_list","列出本会话的后台作业(运行中与已结束)及其id、种类、状态、用时。"),readOnly:!0,async run(){const t=list(e());return t.length?"本会话的后台作业:\n"+t.map(t=>"· "+describe(t)).join("\n"):"当前会话没有后台作业。(用run_bash的run_in_background参数可把长命令放到后台)"}},{spec:m("job_output","读取一个后台作业的输出。默认只返回**上次读取之后的新增内容**(长时间运行的作业不会反复把历史塞回上下文);传all=true读全部。作业还没结束时传wait=true可等它结束再返回(超时则如实告知仍在运行,作业不受影响)。",{job_id:{type:"string",description:"作业id(由启动它的工具返回,或用job_list查)"},wait:{type:"boolean",description:"是否等待作业结束再返回(默认否,立即返回当前已有输出)"},all:{type:"boolean",description:"是否返回全部输出而非仅新增(默认否)"},timeout_ms:{type:"number",description:"wait=true时的最长等待毫秒,默认 60000,上限 300000"}},["job_id"]),readOnly:!0,timeoutMs:33e4,async run(t){const n=String(t?.job_id||"").trim();if(!n)return"缺少job_id。";if(t?.wait){const r=await wait(n,{sessionId:e(),timeoutMs:t.timeout_ms});if(!r.ok)return`未找到作业 ${n}(或它不属于当前会话)。`;if(r.timedOut){return`作业 ${n} 仍在运行(等待超时,作业未受影响)。\n目前的输出:\n${read(n,{sessionId:e(),all:!!t.all}).text||"(暂无新增输出)"}`}}const r=read(n,{sessionId:e(),all:!!t?.all});if(!r.ok)return r.reason;return`${`作业 ${r.view.id} ${describe(r.view)}`}\n${r.text||(r.view.running?"(暂无新增输出)":"(无输出)")}`}},{spec:m("job_kill","终止一个还在运行的后台作业。立即返回;作业在其工作真正停下后结算为「已终止」。",{job_id:{type:"string",description:"要终止的作业id"},reason:{type:"string",description:"可选:终止原因(记入作业记录)"}},["job_id"]),readOnly:!1,async run(t){const n=String(t?.job_id||"").trim();if(!n)return"缺少job_id。";const r=kill(n,{sessionId:e(),reason:t?.reason});return r.ok?`已请求终止作业 ${n}。`:`无法终止作业 ${n}:${r.reason}`}}]}