@huicai/cli 2026.714.0 → 2026.804.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 (354) hide show
  1. package/README.md +13 -5
  2. package/bin/huicai.js +83 -0
  3. package/package.json +2 -2
  4. package/skills/ab-testing/SKILL.md +58 -0
  5. package/skills/ab-testing/scripts/__pycache__/ab_test.cpython-312.pyc +0 -0
  6. package/skills/ab-testing/scripts/ab_test.py +71 -0
  7. package/skills/academic-writing/SKILL.md +54 -0
  8. package/skills/accounting-ops/SKILL.md +68 -0
  9. package/skills/accounting-ops/scripts/__pycache__/acct_tools.cpython-312.pyc +0 -0
  10. package/skills/accounting-ops/scripts/acct_tools.py +89 -0
  11. package/skills/ai-agent/SKILL.md +85 -0
  12. package/skills/ai-agent/references/tool-design-checklist.md +51 -0
  13. package/skills/ai-aiops/SKILL.md +73 -0
  14. package/skills/ai-aiops/references/aiops-rollout-checklist.md +36 -0
  15. package/skills/ai-coding/SKILL.md +89 -0
  16. package/skills/ai-coding/references/task-brief-template.md +48 -0
  17. package/skills/ai-compliance/SKILL.md +74 -0
  18. package/skills/ai-compliance/references/genai-compliance-checklist.md +40 -0
  19. package/skills/ai-eval/SKILL.md +80 -0
  20. package/skills/ai-eval/scripts/eval-runner.py +150 -0
  21. package/skills/ai-llm-app/SKILL.md +90 -0
  22. package/skills/ai-llm-app/references/llm-integration-checklist.md +39 -0
  23. package/skills/ai-mlops/SKILL.md +72 -0
  24. package/skills/ai-mlops/scripts/token-cost-calc.py +108 -0
  25. package/skills/ai-multimodal/SKILL.md +86 -0
  26. package/skills/ai-multimodal/scripts/field-validate.py +182 -0
  27. package/skills/ai-prompt/SKILL.md +75 -0
  28. package/skills/ai-prompt/references/prompt-template.md +46 -0
  29. package/skills/ai-rag/SKILL.md +76 -0
  30. package/skills/ai-rag/scripts/chunk-inspect.py +129 -0
  31. package/skills/anomaly-detection/SKILL.md +54 -0
  32. package/skills/anomaly-detection/scripts/__pycache__/anomaly.cpython-312.pyc +0 -0
  33. package/skills/anomaly-detection/scripts/anomaly.py +99 -0
  34. package/skills/api-integration/SKILL.md +74 -0
  35. package/skills/api-integration/references/signing-recipes.md +141 -0
  36. package/skills/arch-design/SKILL.md +57 -0
  37. package/skills/arch-distributed/SKILL.md +81 -0
  38. package/skills/arch-distributed/references/idempotency-review.md +43 -0
  39. package/skills/arch-multitenant/SKILL.md +73 -0
  40. package/skills/arch-multitenant/references/tenant-isolation-checklist.md +34 -0
  41. package/skills/audit-assist/SKILL.md +63 -0
  42. package/skills/audit-assist/scripts/__pycache__/audit_sampling.cpython-312.pyc +0 -0
  43. package/skills/audit-assist/scripts/audit_sampling.py +93 -0
  44. package/skills/audit-chain/SKILL.md +60 -0
  45. package/skills/banking-credit/SKILL.md +59 -0
  46. package/skills/banking-credit/scripts/__pycache__/credit_metrics.cpython-312.pyc +0 -0
  47. package/skills/banking-credit/scripts/credit_metrics.py +53 -0
  48. package/skills/brand-design/SKILL.md +56 -0
  49. package/skills/business-plan/SKILL.md +56 -0
  50. package/skills/capital-ops/SKILL.md +52 -0
  51. package/skills/cicd-pipeline/SKILL.md +85 -0
  52. package/skills/cicd-pipeline/references/pipeline-review-checklist.md +39 -0
  53. package/skills/content-creation/SKILL.md +56 -0
  54. package/skills/corporate-governance/SKILL.md +56 -0
  55. package/skills/crawl-chain/SKILL.md +61 -0
  56. package/skills/customer-service/SKILL.md +51 -0
  57. package/skills/data-analysis/SKILL.md +46 -0
  58. package/skills/data-analysis/scripts/cjk_font.py +56 -0
  59. package/skills/data-chain/SKILL.md +54 -0
  60. package/skills/data-governance/SKILL.md +54 -0
  61. package/skills/data-report-pro/SKILL.md +65 -0
  62. package/skills/data-report-pro/scripts/__pycache__/bigdata.cpython-312.pyc +0 -0
  63. package/skills/data-report-pro/scripts/bigdata.py +79 -0
  64. package/skills/data-streaming/SKILL.md +87 -0
  65. package/skills/data-streaming/references/job-checklist.md +36 -0
  66. package/skills/data-wrangling/SKILL.md +52 -0
  67. package/skills/data-wrangling/scripts/__pycache__/join_tools.cpython-312.pyc +0 -0
  68. package/skills/data-wrangling/scripts/join_tools.py +99 -0
  69. package/skills/db-es/SKILL.md +68 -0
  70. package/skills/db-es/scripts/es-health.sh +72 -0
  71. package/skills/db-migration/SKILL.md +72 -0
  72. package/skills/db-migration/scripts/checksum-mysql.sql +46 -0
  73. package/skills/db-migration/scripts/checksum-postgres.sql +39 -0
  74. package/skills/db-migration/scripts/compare-reports.py +98 -0
  75. package/skills/db-mysql/SKILL.md +78 -0
  76. package/skills/db-mysql/scripts/mysql-health.sql +80 -0
  77. package/skills/db-postgres/SKILL.md +86 -0
  78. package/skills/db-postgres/scripts/pg-health.sql +67 -0
  79. package/skills/db-redis/SKILL.md +83 -0
  80. package/skills/db-redis/scripts/redis-health.sh +92 -0
  81. package/skills/dev-cpp/SKILL.md +94 -0
  82. package/skills/dev-cpp/references/cmake-sanitizer-snippets.md +56 -0
  83. package/skills/dev-cross-platform/SKILL.md +73 -0
  84. package/skills/dev-cross-platform/references/capability-matrix-template.md +39 -0
  85. package/skills/dev-csharp/SKILL.md +96 -0
  86. package/skills/dev-csharp/references/review-checklist.md +37 -0
  87. package/skills/dev-frontend/SKILL.md +91 -0
  88. package/skills/dev-frontend/scripts/bundle-budget.py +106 -0
  89. package/skills/dev-go/SKILL.md +88 -0
  90. package/skills/dev-go/scripts/go-triage.sh +54 -0
  91. package/skills/dev-harmony/SKILL.md +64 -0
  92. package/skills/dev-harmony/references/harmony-checklist.md +40 -0
  93. package/skills/dev-java/SKILL.md +77 -0
  94. package/skills/dev-java/scripts/jvm-triage.sh +84 -0
  95. package/skills/dev-mobile/SKILL.md +82 -0
  96. package/skills/dev-mobile/references/store-compliance-checklist.md +33 -0
  97. package/skills/dev-node/SKILL.md +82 -0
  98. package/skills/dev-node/scripts/node-triage.sh +78 -0
  99. package/skills/dev-php/SKILL.md +86 -0
  100. package/skills/dev-php/scripts/fpm-calc.py +48 -0
  101. package/skills/dev-python/SKILL.md +81 -0
  102. package/skills/dev-python/scripts/venv-doctor.sh +75 -0
  103. package/skills/dev-rust/SKILL.md +84 -0
  104. package/skills/dev-rust/references/cargo-ci-snippets.md +50 -0
  105. package/skills/dev-toolchain/SKILL.md +78 -0
  106. package/skills/dev-toolchain/scripts/secret-scan.sh +83 -0
  107. package/skills/doc-chain/SKILL.md +58 -0
  108. package/skills/doc-compare/SKILL.md +44 -0
  109. package/skills/doc-compare/scripts/compare.py +93 -0
  110. package/skills/docx-author/SKILL.md +39 -0
  111. package/skills/docx-author/scripts/docx_doc.py +161 -0
  112. package/skills/ecommerce-ops/SKILL.md +54 -0
  113. package/skills/ecommerce-ops/scripts/__pycache__/gmv_decompose.cpython-312.pyc +0 -0
  114. package/skills/ecommerce-ops/scripts/gmv_decompose.py +61 -0
  115. package/skills/education-training/SKILL.md +53 -0
  116. package/skills/ehs-safety/SKILL.md +63 -0
  117. package/skills/email-writing/SKILL.md +52 -0
  118. package/skills/eng-ai/SKILL.md +74 -0
  119. package/skills/eng-ai/references/ai-feature-checklist.md +36 -0
  120. package/skills/eng-architecture/SKILL.md +85 -0
  121. package/skills/eng-architecture/scripts/capacity-calc.py +100 -0
  122. package/skills/eng-automation/SKILL.md +84 -0
  123. package/skills/eng-automation/references/automation-inventory.md +38 -0
  124. package/skills/eng-automation/references/routine-presets.md +73 -0
  125. package/skills/eng-collaboration/SKILL.md +76 -0
  126. package/skills/eng-collaboration/references/collab-templates.md +43 -0
  127. package/skills/eng-complex-env/SKILL.md +84 -0
  128. package/skills/eng-complex-env/references/env-dossier-template.md +52 -0
  129. package/skills/eng-complex-env/scripts/env-recon.sh +152 -0
  130. package/skills/eng-data-platform/SKILL.md +81 -0
  131. package/skills/eng-data-platform/references/table-spec-template.md +39 -0
  132. package/skills/eng-database/SKILL.md +79 -0
  133. package/skills/eng-database/references/schema-review-checklist.md +37 -0
  134. package/skills/eng-develop/SKILL.md +76 -0
  135. package/skills/eng-develop/references/dod-checklist.md +43 -0
  136. package/skills/eng-docs/SKILL.md +71 -0
  137. package/skills/eng-docs/references/runbook-template.md +57 -0
  138. package/skills/eng-efficiency/SKILL.md +75 -0
  139. package/skills/eng-efficiency/scripts/dora-calc.py +104 -0
  140. package/skills/eng-infra/SKILL.md +81 -0
  141. package/skills/eng-infra/references/production-readiness.md +46 -0
  142. package/skills/eng-integration/SKILL.md +82 -0
  143. package/skills/eng-integration/references/integration-dossier-template.md +38 -0
  144. package/skills/eng-legacy/SKILL.md +81 -0
  145. package/skills/eng-legacy/references/modernization-workbook.md +46 -0
  146. package/skills/eng-observability/SKILL.md +78 -0
  147. package/skills/eng-observability/scripts/slo-calc.py +82 -0
  148. package/skills/eng-ops/SKILL.md +74 -0
  149. package/skills/eng-ops/references/postmortem-template.md +67 -0
  150. package/skills/eng-performance/SKILL.md +73 -0
  151. package/skills/eng-performance/scripts/perf-baseline.py +110 -0
  152. package/skills/eng-planning/SKILL.md +79 -0
  153. package/skills/eng-planning/scripts/schedule-calc.py +101 -0
  154. package/skills/eng-quality/SKILL.md +74 -0
  155. package/skills/eng-quality/references/test-plan-templates.md +46 -0
  156. package/skills/eng-release/SKILL.md +78 -0
  157. package/skills/eng-release/references/release-plan-template.md +36 -0
  158. package/skills/eng-reliability/SKILL.md +72 -0
  159. package/skills/eng-reliability/references/resilience-review.md +43 -0
  160. package/skills/eng-requirements/SKILL.md +86 -0
  161. package/skills/eng-requirements/references/story-templates.md +47 -0
  162. package/skills/eng-research/SKILL.md +73 -0
  163. package/skills/eng-research/references/research-templates.md +42 -0
  164. package/skills/eng-security/SKILL.md +73 -0
  165. package/skills/eng-security/references/security-review-checklist.md +33 -0
  166. package/skills/eng-ux/SKILL.md +85 -0
  167. package/skills/eng-ux/references/ux-review-checklist.md +36 -0
  168. package/skills/engineering-chain/SKILL.md +101 -0
  169. package/skills/engineering-chain/references/eng-health-review.md +37 -0
  170. package/skills/env-offline/SKILL.md +81 -0
  171. package/skills/env-offline/scripts/bundle-manifest.sh +94 -0
  172. package/skills/env-private-deploy/SKILL.md +72 -0
  173. package/skills/env-private-deploy/references/delivery-templates.md +35 -0
  174. package/skills/env-xinchuang/SKILL.md +73 -0
  175. package/skills/env-xinchuang/references/adaptation-matrix-template.md +41 -0
  176. package/skills/equipment-tpm/SKILL.md +56 -0
  177. package/skills/equipment-tpm/scripts/oee.py +58 -0
  178. package/skills/esg-report/SKILL.md +58 -0
  179. package/skills/esg-report/scripts/carbon_calc.py +61 -0
  180. package/skills/event-management/SKILL.md +51 -0
  181. package/skills/excel-analysis-chain/SKILL.md +73 -0
  182. package/skills/file-ingest/SKILL.md +51 -0
  183. package/skills/file-ingest/scripts/__pycache__/file_toolkit.cpython-312.pyc +0 -0
  184. package/skills/file-ingest/scripts/file_toolkit.py +404 -0
  185. package/skills/finance-analysis/SKILL.md +60 -0
  186. package/skills/finance-analysis/references/ratios.md +54 -0
  187. package/skills/finance-analysis/scripts/fin_ratios.py +132 -0
  188. package/skills/finance-chain/SKILL.md +59 -0
  189. package/skills/financial-modeling/SKILL.md +58 -0
  190. package/skills/financial-modeling/scripts/fin_model.py +123 -0
  191. package/skills/foreign-trade/SKILL.md +54 -0
  192. package/skills/funnel-cohort/SKILL.md +53 -0
  193. package/skills/funnel-cohort/scripts/__pycache__/funnel.cpython-312.pyc +0 -0
  194. package/skills/funnel-cohort/scripts/funnel.py +105 -0
  195. package/skills/gov-document/SKILL.md +57 -0
  196. package/skills/hr-chain/SKILL.md +51 -0
  197. package/skills/hr-operations/SKILL.md +53 -0
  198. package/skills/info-disclosure/SKILL.md +56 -0
  199. package/skills/infra-gateway/SKILL.md +85 -0
  200. package/skills/infra-gateway/references/golive-checklist.md +36 -0
  201. package/skills/infra-k8s/SKILL.md +78 -0
  202. package/skills/infra-k8s/scripts/k8s-triage.sh +84 -0
  203. package/skills/infra-mq/SKILL.md +87 -0
  204. package/skills/infra-mq/references/mq-review-checklist.md +36 -0
  205. package/skills/insurance-advisory/SKILL.md +57 -0
  206. package/skills/internal-audit/SKILL.md +66 -0
  207. package/skills/ip-management/SKILL.md +54 -0
  208. package/skills/ipo-listing/SKILL.md +60 -0
  209. package/skills/it-audit/SKILL.md +56 -0
  210. package/skills/itsm-ops/SKILL.md +55 -0
  211. package/skills/knowledge-sop/SKILL.md +51 -0
  212. package/skills/legal-chain/SKILL.md +55 -0
  213. package/skills/legal-contract/SKILL.md +58 -0
  214. package/skills/legal-contract/references/contract-checklists.md +59 -0
  215. package/skills/litigation-support/SKILL.md +53 -0
  216. package/skills/logistics-warehouse/SKILL.md +53 -0
  217. package/skills/logistics-warehouse/scripts/freight_check.py +71 -0
  218. package/skills/long-doc-analysis/SKILL.md +52 -0
  219. package/skills/management-accounting/SKILL.md +57 -0
  220. package/skills/management-accounting/scripts/__pycache__/costing.cpython-312.pyc +0 -0
  221. package/skills/management-accounting/scripts/costing.py +90 -0
  222. package/skills/manufacturing-chain/SKILL.md +57 -0
  223. package/skills/manufacturing-quality/SKILL.md +57 -0
  224. package/skills/manufacturing-quality/scripts/__pycache__/spc.cpython-312.pyc +0 -0
  225. package/skills/manufacturing-quality/scripts/spc.py +86 -0
  226. package/skills/marketing-analytics/SKILL.md +59 -0
  227. package/skills/marketing-analytics/scripts/__pycache__/rfm.cpython-312.pyc +0 -0
  228. package/skills/marketing-analytics/scripts/rfm.py +60 -0
  229. package/skills/marketing-chain/SKILL.md +57 -0
  230. package/skills/meeting-minutes/SKILL.md +55 -0
  231. package/skills/metrics-system/SKILL.md +55 -0
  232. package/skills/negotiation-prep/SKILL.md +56 -0
  233. package/skills/obs-prometheus/SKILL.md +74 -0
  234. package/skills/obs-prometheus/references/promql-cheatsheet.md +78 -0
  235. package/skills/office-automation/SKILL.md +54 -0
  236. package/skills/okr-review/SKILL.md +44 -0
  237. package/skills/ops-fleet/SKILL.md +69 -0
  238. package/skills/ops-fleet/scripts/batch-exec-guard.sh +119 -0
  239. package/skills/ops-linux/SKILL.md +69 -0
  240. package/skills/ops-linux/scripts/quick-triage.sh +138 -0
  241. package/skills/optimization-solver/SKILL.md +53 -0
  242. package/skills/optimization-solver/scripts/__pycache__/solver.cpython-312.pyc +0 -0
  243. package/skills/optimization-solver/scripts/solver.py +107 -0
  244. package/skills/pdf-toolkit/SKILL.md +43 -0
  245. package/skills/pdf-toolkit/scripts/pdf_ops.py +137 -0
  246. package/skills/pivot-crosstab/SKILL.md +44 -0
  247. package/skills/pivot-crosstab/scripts/__pycache__/pivot_tools.cpython-312.pyc +0 -0
  248. package/skills/pivot-crosstab/scripts/pivot_tools.py +90 -0
  249. package/skills/policy-analysis/SKILL.md +47 -0
  250. package/skills/portfolio-analysis/SKILL.md +56 -0
  251. package/skills/portfolio-analysis/scripts/__pycache__/portfolio.cpython-312.pyc +0 -0
  252. package/skills/portfolio-analysis/scripts/portfolio.py +78 -0
  253. package/skills/ppt-chain/SKILL.md +61 -0
  254. package/skills/pptx-author/SKILL.md +48 -0
  255. package/skills/pptx-author/scripts/pptx_deck.py +193 -0
  256. package/skills/pr-comms/SKILL.md +58 -0
  257. package/skills/pr-comms/references/pr-templates.md +54 -0
  258. package/skills/procurement-supply/SKILL.md +47 -0
  259. package/skills/procurement-supply/scripts/__pycache__/supply.cpython-312.pyc +0 -0
  260. package/skills/procurement-supply/scripts/supply.py +71 -0
  261. package/skills/product-design/SKILL.md +66 -0
  262. package/skills/product-design/references/prd-framework.md +60 -0
  263. package/skills/production-planning/SKILL.md +56 -0
  264. package/skills/production-planning/scripts/__pycache__/mrp_capacity.cpython-312.pyc +0 -0
  265. package/skills/production-planning/scripts/mrp_capacity.py +70 -0
  266. package/skills/project-management/SKILL.md +53 -0
  267. package/skills/project-management/scripts/__pycache__/cpm.cpython-312.pyc +0 -0
  268. package/skills/project-management/scripts/cpm.py +84 -0
  269. package/skills/project-proposal/SKILL.md +54 -0
  270. package/skills/project-proposal/scripts/budget_sheet.py +90 -0
  271. package/skills/qa-api-automation/SKILL.md +67 -0
  272. package/skills/qa-api-automation/references/three-layer-skeleton.md +61 -0
  273. package/skills/qa-mobile-automation/SKILL.md +70 -0
  274. package/skills/qa-mobile-automation/references/device-matrix-template.md +34 -0
  275. package/skills/qa-ui-automation/SKILL.md +80 -0
  276. package/skills/qa-ui-automation/references/stability-conventions.md +41 -0
  277. package/skills/quant-analysis/SKILL.md +60 -0
  278. package/skills/quant-analysis/scripts/__pycache__/quant.cpython-312.pyc +0 -0
  279. package/skills/quant-analysis/scripts/quant.py +102 -0
  280. package/skills/real-estate-property/SKILL.md +51 -0
  281. package/skills/real-estate-property/scripts/__pycache__/property_calc.cpython-312.pyc +0 -0
  282. package/skills/real-estate-property/scripts/property_calc.py +106 -0
  283. package/skills/regression-forecast/SKILL.md +52 -0
  284. package/skills/regression-forecast/scripts/__pycache__/regress.cpython-312.pyc +0 -0
  285. package/skills/regression-forecast/scripts/regress.py +106 -0
  286. package/skills/retail-store-ops/SKILL.md +57 -0
  287. package/skills/retail-store-ops/scripts/store_ops.py +57 -0
  288. package/skills/risk-compliance/SKILL.md +51 -0
  289. package/skills/sec-auth/SKILL.md +89 -0
  290. package/skills/sec-auth/references/auth-review-checklist.md +38 -0
  291. package/skills/sec-compliance/SKILL.md +74 -0
  292. package/skills/sec-compliance/references/dengbao-checklist.md +43 -0
  293. package/skills/securities-chain/SKILL.md +69 -0
  294. package/skills/securities-research/SKILL.md +58 -0
  295. package/skills/securities-research/scripts/__pycache__/valuation.cpython-312.pyc +0 -0
  296. package/skills/securities-research/scripts/valuation.py +93 -0
  297. package/skills/slides-html/SKILL.md +50 -0
  298. package/skills/slides-html/scripts/template.html +117 -0
  299. package/skills/statistical-testing/SKILL.md +51 -0
  300. package/skills/statistical-testing/scripts/__pycache__/stat_tests.cpython-312.pyc +0 -0
  301. package/skills/statistical-testing/scripts/stat_tests.py +131 -0
  302. package/skills/strategy-planning/SKILL.md +53 -0
  303. package/skills/supply-chain/SKILL.md +53 -0
  304. package/skills/tax-compliance/SKILL.md +57 -0
  305. package/skills/tax-compliance/scripts/tax_calc.py +90 -0
  306. package/skills/tech-research/SKILL.md +55 -0
  307. package/skills/tech-research/references/research-framework.md +50 -0
  308. package/skills/tender-bidding/SKILL.md +65 -0
  309. package/skills/tender-bidding/references/bid-checklist.md +44 -0
  310. package/skills/translation-pro/SKILL.md +44 -0
  311. package/skills/treasury-mgmt/SKILL.md +56 -0
  312. package/skills/treasury-mgmt/scripts/__pycache__/cashflow_forecast.cpython-312.pyc +0 -0
  313. package/skills/treasury-mgmt/scripts/cashflow_forecast.py +51 -0
  314. package/skills/web-scraping/SKILL.md +81 -0
  315. package/skills/web-search/SKILL.md +67 -0
  316. package/skills/xlsx-analysis/SKILL.md +96 -0
  317. package/skills/xlsx-analysis/scripts/__pycache__/xlsx_toolkit.cpython-312.pyc +0 -0
  318. package/skills/xlsx-analysis/scripts/xlsx_style.py +71 -0
  319. package/skills/xlsx-analysis/scripts/xlsx_toolkit.py +335 -0
  320. package/src/advisor.js +1 -1
  321. package/src/agent.js +1 -1
  322. package/src/api.js +1 -1
  323. package/src/assets-import.js +1 -0
  324. package/src/autofix.js +1 -1
  325. package/src/automation.js +1 -1
  326. package/src/codeindex.js +1 -1
  327. package/src/commands.js +1 -1
  328. package/src/compaction.js +1 -1
  329. package/src/config.js +1 -1
  330. package/src/context-report.js +1 -1
  331. package/src/context.js +1 -1
  332. package/src/decisions.js +1 -0
  333. package/src/doccache.js +1 -0
  334. package/src/doctor.js +1 -1
  335. package/src/executors.js +1 -1
  336. package/src/expert-catalog.js +1 -0
  337. package/src/experts-store.js +1 -0
  338. package/src/gitflow.js +1 -1
  339. package/src/input.js +1 -1
  340. package/src/mcp.js +1 -1
  341. package/src/permissions.js +1 -1
  342. package/src/pyruntime.js +1 -0
  343. package/src/repl.js +1 -1
  344. package/src/scan.js +1 -1
  345. package/src/schedule-freq.js +1 -0
  346. package/src/schedule.js +1 -1
  347. package/src/session.js +1 -1
  348. package/src/skills.js +1 -1
  349. package/src/squad.js +1 -1
  350. package/src/subagent.js +1 -1
  351. package/src/title.js +1 -0
  352. package/src/tools.js +1 -1
  353. package/src/transcript.js +1 -1
  354. package/src/tui/app.js +1 -1
@@ -0,0 +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]));
@@ -0,0 +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[]}}
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 o}from"node:child_process";import{streamChat as n}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 o("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{o("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 o=c.test(String(e.model||""))?e.compactModel||i:e.model;try{const t=await n({baseUrl:e.baseUrl,apiKey:e.apiKey,model:o,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:n,config:i,emit:c=()=>{},push:p=!0,openPr:h=!0}){const d=gitInfo(n);if(!d.isRepo)return{ok:!1,error:"当前目录不是 git 仓库"};const u=a.has(d.branch);d.dirty&&(s(n,"add -A"),c("已暂存全部改动"));const f=m(n,"diff --staged --stat").trim();let g="";f&&(c("正在生成提交信息…"),g=await generateCommitMessage(n,i),c("提交信息:"+g.split("\n")[0]));let l=d.branch;u&&(l="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"}(g||"change")+"-"+Date.now().toString(36).slice(-4),s(n,`checkout -b ${l}`),c("已从 "+d.branch+" 创建分支 "+l));let y=!1;if(f){const o=r.join(e.tmpdir(),"huicai-commit-"+Date.now()+".txt");t.writeFileSync(o,g);try{s(n,`commit -F "${o}"`),y=!0,c("已提交")}finally{try{t.unlinkSync(o)}catch{}}}else c("无新暂存改动,跳过提交");if(!p)return{ok:!0,branch:l,committed:y};if(!d.hasRemote)return{ok:!0,branch:l,committed:y,note:"无 remote,未推送"};try{s(n,`push -u origin ${l}`),c("已推送 origin/"+l)}catch(t){return{ok:!1,error:"push 失败:"+String(t.message).split("\n")[0],branch:l}}if(!h)return{ok:!0,branch:l,committed:y,pushed:!0};if(!d.hasGh)return{ok:!0,branch:l,committed:y,pushed:!0,note:"未装 gh CLI,PR 请手动开"};try{const t=o("gh pr create --fill",{cwd:n,encoding:"utf8",stdio:["ignore","pipe","pipe"]}).trim();return c("已开 PR:"+t),{ok:!0,branch:l,committed:y,pr:t}}catch(t){return{ok:!0,branch:l,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{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]}}}
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 i}from"./executors.js";import{extractDocument as s}from"./api.js";const o=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"]),p=8388608,l=20971520;export async function expandAtMentions(r,i,o=null){const d=/(^|[\s("'`(【,,::])@([~A-Za-z0-9_./\\-]+)/g,f=new Set,m=[];let g;for(;(g=d.exec(r))&&m.length<5;){const e=g[2].replace(/[.,;:。,;]+$/,"");e&&!f.has(e)&&(f.add(e),m.push(e))}const h=[],y=[],$=[];for(const r of m){const d=r.startsWith("~")?t.join(n.homedir(),r.slice(1)):t.resolve(i,r);let f;try{f=e.statSync(d)}catch{continue}if($.push(r),f.isDirectory()){try{const t=e.readdirSync(d).slice(0,100).join("\n");h.push(`【引用目录 @${r}】\n${t||"(空目录)"}`)}catch{}continue}const m=t.extname(d).slice(1).toLowerCase();if(a[m])if(f.size>p)h.push(`【图片 @${r}】(超过 ${Math.round(8)}MB,未附加)`);else try{const t=e.readFileSync(d).toString("base64");y.push({label:r,dataUri:`data:${a[m]};base64,${t}`}),h.push(`【已附加图片 @${r}】`)}catch{}else if(c.has(m))if(o&&o.baseUrl&&o.apiKey)if(f.size>l)h.push(`【文档 @${r}】(超过 ${Math.round(20)}MB,未解析)`);else try{const n=e.readFileSync(d).toString("base64"),i=await s(o.baseUrl,o.apiKey,{filename:t.basename(d),data:n,maxChars:6e4}),a=/^(agent|workflow|datahuman|squad)-/.test(String(o?.model||"")),c=i.truncated?a?"·已截断(头尾摘要)":"·已截断(头尾摘要,被省略部分可用 read_document 工具按 offset 分段读取)":"",p=`${i.pages?i.pages+"页·":""}${i.chars??0}字${c}`;h.push(`【引用文档 @${r}(${p})】\n${i.text||"(空)"}`)}catch(e){h.push(`【文档 @${r}】(解析失败:${e.message})`)}else h.push(`【文档 @${r}】(需登录后才能解析为文本)`);else if(f.size>524288)h.push(`【引用文件 @${r}】\n(文件过大:${Math.round(f.size/1024)}KB,未展开;可让我用 read_file 分段读取)`);else try{const t=e.readFileSync(d);if(u(t)){h.push(`【引用文件 @${r}】\n(二进制文件,未展开)`);continue}let n=t.toString("utf8");n.length>12e3&&(n=n.slice(0,12e3)+"\n…(已截断)"),h.push(`【引用文件 @${r}】\n\`\`\`\n${n}\n\`\`\``)}catch{}}return{text:h.length?r+"\n\n"+h.join("\n\n"):r,mentions:$,images:y}}function u(e){const t=Math.min(e.length,4096);for(let n=0;n<t;n++)if(0===e[n])return!0;return!1}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 i(t,{command:e}),r=n.length>8e3?n.slice(0,8e3)+"\n…(已截断)":n;return{output:n,record:`我在终端直接执行了命令:$ ${e}\n${r}`}}function d(){try{const t=JSON.parse(e.readFileSync(o,"utf8"));return Array.isArray(t?.entries)?t.entries:[]}catch{return[]}}export function loadHistory(e,t=200){return d().filter(t=>t&&t.cwd===e&&"string"==typeof t.line).slice(-t).map(e=>e.line)}export function appendHistory(t,n){const i=String(n||"").trim();if(i)try{let n=d();const s=[...n].reverse().find(e=>e.cwd===t);if(s&&s.line===i)return;n.push({cwd:t,line:i,ts:Date.now()}),n.length>1e3&&(n=n.slice(-1e3)),e.mkdirSync(r,{recursive:!0}),e.writeFileSync(o,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/mcp.js CHANGED
@@ -1 +1 @@
1
- import t from"node:fs";import e from"node:path";import{spawn as i}from"node:child_process";import{CONFIG_DIR as s}from"./config.js";import{c as n}from"./colors.js";export const MCP_CONFIG_PATH=e.join(s,"mcp.json");class o{constructor(t,e){this.name=t,this.spec=e,this.child=null,this.buffer="",this.nextId=1,this.pending=new Map,this.closed=!1}async start(){const t={...process.env,...this.spec.env||{}};this.child=i(this.spec.command,this.spec.args||[],{env:t,stdio:["pipe","pipe","pipe"]}),this.child.on("error",t=>this._failAll(new Error(`MCP[${this.name}] 进程错误:${t.message}`))),this.child.on("exit",()=>{this.closed=!0,this._failAll(new Error(`MCP[${this.name}] 进程已退出`))}),this.child.stdout.setEncoding("utf8"),this.child.stdout.on("data",t=>this._onData(t)),this.child.stderr.on("data",()=>{}),await this._request("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"huicai-cli",version:"0.2.0"}},2e4),this._notify("notifications/initialized",{})}async listTools(){const t=await this._request("tools/list",{},2e4);return Array.isArray(t?.tools)?t.tools:[]}async callTool(t,e){const i=await this._request("tools/call",{name:t,arguments:e||{}},12e4),s=(Array.isArray(i?.content)?i.content:[]).map(t=>"text"===t?.type?t.text:t?.text||JSON.stringify(t)).filter(Boolean).join("\n");return i?.isError?`工具报错:${s||"(无详情)"}`:s||"(无输出)"}close(){this.closed=!0;try{this.child?.kill("SIGTERM")}catch{}}_onData(t){let e;for(this.buffer+=t;(e=this.buffer.indexOf("\n"))>=0;){const t=this.buffer.slice(0,e).trim();if(this.buffer=this.buffer.slice(e+1),!t)continue;let i;try{i=JSON.parse(t)}catch{continue}if(null!=i.id&&this.pending.has(i.id)){const{resolve:t,reject:e,timer:s}=this.pending.get(i.id);clearTimeout(s),this.pending.delete(i.id),i.error?e(new Error(i.error.message||"MCP 错误")):t(i.result)}}}_request(t,e,i){if(this.closed)return Promise.reject(new Error(`MCP[${this.name}] 已关闭`));const s=this.nextId++,n=JSON.stringify({jsonrpc:"2.0",id:s,method:t,params:e})+"\n";return new Promise((e,o)=>{const r=setTimeout(()=>{this.pending.delete(s),o(new Error(`MCP[${this.name}] ${t} 超时`))},i);this.pending.set(s,{resolve:e,reject:o,timer:r});try{this.child.stdin.write(n)}catch(t){clearTimeout(r),this.pending.delete(s),o(t)}})}_notify(t,e){try{this.child.stdin.write(JSON.stringify({jsonrpc:"2.0",method:t,params:e})+"\n")}catch{}}_failAll(t){for(const{reject:e,timer:i}of this.pending.values())clearTimeout(i),e(t);this.pending.clear()}}function r(t){const e=String(t).replace(/[^a-zA-Z0-9_-]/g,"_");return e.length>60?e.slice(0,60):e}export async function loadMcpTools(){const e=function(){try{const e=JSON.parse(t.readFileSync(MCP_CONFIG_PATH,"utf8"));return e?.mcpServers&&"object"==typeof e.mcpServers?e.mcpServers:{}}catch{return{}}}(),i=Object.keys(e),s=[],c=[];if(0===i.length)return{tools:s,clients:c};for(const t of i){const i=e[t]||{};if(!i.command)continue;const a=new o(t,i);try{await a.start();const e=await a.listTools();c.push(a);for(const i of e){const e=i.name,n=r(`mcp__${t}__${e}`),o=!0===i.annotations?.readOnlyHint;s.push({readOnly:o,spec:{type:"function",function:{name:n,description:`[MCP:${t}] ${i.description||e}`,parameters:i.inputSchema&&"object"==typeof i.inputSchema?i.inputSchema:{type:"object",properties:{}}}},run:t=>a.callTool(e,t)})}process.stdout.write(n.gray(` · MCP[${t}] 已连接,工具 ${e.length} 个`)+"\n")}catch(e){a.close(),process.stdout.write(n.yellow(` · MCP[${t}] 加载失败,已跳过:${e.message}`)+"\n")}}return{tools:s,clients:c}}export function closeMcp(t){for(const e of t||[])e.close()}
1
+ import t from"node:fs";import e from"node:path";import{spawn as i}from"node:child_process";import{CONFIG_DIR as s}from"./config.js";import{c as n}from"./colors.js";export const MCP_CONFIG_PATH=e.join(s,"mcp.json");export class McpStdioClient{constructor(t,e){this.name=t,this.spec=e,this.child=null,this.buffer="",this.nextId=1,this.pending=new Map,this.closed=!1}async start(){const t={...process.env,...this.spec.env||{}};this.child=i(this.spec.command,this.spec.args||[],{env:t,stdio:["pipe","pipe","pipe"]}),this.child.on("error",t=>this._failAll(new Error(`MCP[${this.name}] 进程错误:${t.message}`))),this.child.on("exit",()=>{this.closed=!0,this._failAll(new Error(`MCP[${this.name}] 进程已退出`))}),this.child.stdout.setEncoding("utf8"),this.child.stdout.on("data",t=>this._onData(t)),this.child.stderr.on("data",()=>{}),await this._request("initialize",{protocolVersion:"2024-11-05",capabilities:{},clientInfo:{name:"huicai-cli",version:"0.2.0"}},2e4),this._notify("notifications/initialized",{})}async listTools(){const t=await this._request("tools/list",{},2e4);return Array.isArray(t?.tools)?t.tools:[]}async callTool(t,e){const i=await this._request("tools/call",{name:t,arguments:e||{}},12e4),s=(Array.isArray(i?.content)?i.content:[]).map(t=>"text"===t?.type?t.text:t?.text||JSON.stringify(t)).filter(Boolean).join("\n");return i?.isError?`工具报错:${s||"(无详情)"}`:s||"(无输出)"}close(){this.closed=!0;try{this.child?.kill("SIGTERM")}catch{}}_onData(t){let e;for(this.buffer+=t;(e=this.buffer.indexOf("\n"))>=0;){const t=this.buffer.slice(0,e).trim();if(this.buffer=this.buffer.slice(e+1),!t)continue;let i;try{i=JSON.parse(t)}catch{continue}if(null!=i.id&&this.pending.has(i.id)){const{resolve:t,reject:e,timer:s}=this.pending.get(i.id);clearTimeout(s),this.pending.delete(i.id),i.error?e(new Error(i.error.message||"MCP 错误")):t(i.result)}}}_request(t,e,i){if(this.closed)return Promise.reject(new Error(`MCP[${this.name}] 已关闭`));const s=this.nextId++,n=JSON.stringify({jsonrpc:"2.0",id:s,method:t,params:e})+"\n";return new Promise((e,o)=>{const r=setTimeout(()=>{this.pending.delete(s),o(new Error(`MCP[${this.name}] ${t} 超时`))},i);this.pending.set(s,{resolve:e,reject:o,timer:r});try{this.child.stdin.write(n)}catch(t){clearTimeout(r),this.pending.delete(s),o(t)}})}_notify(t,e){try{this.child.stdin.write(JSON.stringify({jsonrpc:"2.0",method:t,params:e})+"\n")}catch{}}_failAll(t){for(const{reject:e,timer:i}of this.pending.values())clearTimeout(i),e(t);this.pending.clear()}}function o(t){const e=String(t).replace(/[^a-zA-Z0-9_-]/g,"_");return e.length>60?e.slice(0,60):e}export async function loadMcpTools(){const e=function(){try{const e=JSON.parse(t.readFileSync(MCP_CONFIG_PATH,"utf8"));return e?.mcpServers&&"object"==typeof e.mcpServers?e.mcpServers:{}}catch{return{}}}(),i=Object.keys(e),s=[],r=[];if(0===i.length)return{tools:s,clients:r};for(const t of i){const i=e[t]||{};if(!i.command)continue;const c=new McpStdioClient(t,i);try{await c.start();const e=await c.listTools();r.push(c);for(const i of e){const e=i.name,n=o(`mcp__${t}__${e}`),r=!0===i.annotations?.readOnlyHint;s.push({readOnly:r,spec:{type:"function",function:{name:n,description:`[MCP:${t}] ${i.description||e}`,parameters:i.inputSchema&&"object"==typeof i.inputSchema?i.inputSchema:{type:"object",properties:{}}}},run:t=>c.callTool(e,t)})}process.stdout.write(n.gray(` · MCP[${t}] 已连接,工具 ${e.length} 个`)+"\n")}catch(e){c.close(),process.stdout.write(n.yellow(` · MCP[${t}] 加载失败,已跳过:${e.message}`)+"\n")}}return{tools:s,clients:r}}export function closeMcp(t){for(const e of t||[])e.close()}
@@ -1 +1 @@
1
- import{c as o}from"./colors.js";import{toolLabel as t,renderEditDiff as r,indent as e}from"./render.js";import{loadAllowRules as s,loadDenyRules as n,matchAllowRules as l,suggestRule as a,addProjectAllowRule as i,riskWarning as c}from"./settings.js";import{AUTOMATION_TOOLS as u,automationActionLabel as d,automationDetail as w}from"./automation.js";export{u as AUTOMATION_TOOLS,d as automationActionLabel,w as automationDetail};export function sandboxBlocked(o){return"run_bash"===o||"fetch_url"===o||u.has(o)}export const PLAN_DENY_REASON="当前处于计划模式:只做只读调研,不执行任何改动。请先产出完整方案交用户确认;用户 /plan 关闭计划模式后再执行改动。";export const SANDBOX_DENY_REASON="当前处于沙箱模式:禁止执行 shell 命令(run_bash)、联网(fetch_url)与环境/应用自动化操作。可继续读写本地文件;如需执行/联网,请用户 /sandbox off 关闭沙箱。";export function createPermissions({yolo:f=!1,ask:p=null,cwd:y=null}={}){const _=new Set;let m=!!f,g=!1,h=!1,b=y?s(y):[],N=y?n(y):[];return{request:async function(n,f,S,$=!1){if(N.length&&l(N,n,f))return{allow:!1,reason:"该操作被拒绝规则阻止(/permissions 查看或移除)。"};if(g&&!$)return{allow:!1,reason:PLAN_DENY_REASON};if(h&&sandboxBlocked(n))return{allow:!1,reason:SANDBOX_DENY_REASON};if($)return{allow:!0};if(m||_.has(n))return{allow:!0};if(b.length&&l(b,n,f))return{allow:!0};if(!p)return{allow:!1,reason:"当前为非交互模式,未授权写/执行操作(加 --yolo 可自动放行)。"};if(u.has(n)){!function(t,r){process.stdout.write("\n"+o.bold(o.yellow("● 是否继续?"))+d(t,r)+"\n");const s=w(t,r);s&&process.stdout.write(e(o.gray(s)," ")+"\n");process.stdout.write(" "+o.bold("1、是")+" · "+o.bold("2、是,并允许在当前对话中后续相同操作不再询问")+" · "+o.bold("3、否")+"\n")}(n,f);const t=(await p(o.yellow("请选择")+" [1/2/3]: ")||"").trim();return"1"===t||/^(y|yes|是)$/i.test(t)?{allow:!0}:"2"===t||"a"===t?(_.add(n),{allow:!0}):""===t||"3"===t||/^(n|no|否)$/i.test(t)?{allow:!1,reason:"用户选择了「否」,该操作未执行。"}:{allow:!1,reason:`用户拒绝并说明:${t}`}}!function(s,n){process.stdout.write("\n"+o.bold(o.yellow("● 需要授权:"))+t(s,n)+"\n");const l=c(s,n);l&&process.stdout.write(" "+o.bold(o.red("⚠ "+l))+"\n");if("run_bash"===s)process.stdout.write(o.gray(" $ ")+String(n.command??"")+"\n");else if("write_file"===s){const t=String(n.content??"").split("\n").slice(0,12).join("\n");process.stdout.write(o.gray(` 将写入 ${n.path}:`)+"\n"+e(t)+"\n")}else"edit_file"===s?process.stdout.write(o.gray(` 编辑 ${n.path}:`)+"\n"+e(r(n.old_string,n.new_string))+"\n"):"fetch_url"===s&&process.stdout.write(o.gray(" GET ")+String(n.url??"")+"\n")}(n,f);const x=(await p(`${o.yellow("允许执行?")} [y]是 · [a]本会话该工具 · [A]永久允许(写入项目设置) · [N]否(可附原因): `)||"").trim();if("a"===x)return _.add(n),{allow:!0};if("A"===x){if(_.add(n),y){const t=a(n,f);try{const r=i(y,t);b=s(y),process.stdout.write(o.gray(` 已写入规则 ${t} → ${r}`)+"\n")}catch(t){process.stdout.write(o.yellow(` 规则写入失败(本会话仍生效):${t.message}`)+"\n")}}return{allow:!0}}const A=x.toLowerCase();return"y"===A||"yes"===A?{allow:!0}:""===x||"n"===A||"no"===A?{allow:!1,reason:"用户拒绝了该操作。"}:{allow:!1,reason:`用户拒绝并说明:${x}`}},setYolo(o){m=!!o},isYolo:()=>m,setPlanMode(o){g=!!o},isPlanMode:()=>g,setSandbox(o){h=!!o},isSandbox:()=>h,reloadRules(){b=y?s(y):[],N=y?n(y):[]}}}
1
+ import{c as t}from"./colors.js";import{toolLabel as o,renderEditDiff as e,indent as n}from"./render.js";import{loadAllowRules as s,loadDenyRules as r,matchAllowRules as a,suggestRule as l,addProjectAllowRule as i,riskWarning as c}from"./settings.js";import{AUTOMATION_TOOLS as d,automationActionLabel as u,automationDetail as p}from"./automation.js";export{d as AUTOMATION_TOOLS,u as automationActionLabel,p as automationDetail};export function sandboxBlocked(t){return"run_bash"===t||"run_python"===t||"fetch_url"===t||d.has(t)}export const PLAN_DENY_REASON="当前处于计划模式:只做只读调研,不执行任何改动。请先产出完整方案交用户确认;用户 /plan 关闭计划模式后再执行改动。";export const SANDBOX_DENY_REASON="当前处于沙箱模式:禁止执行 shell 命令(run_bash)、Python 代码(run_python)、联网(fetch_url)与环境/应用自动化操作。可继续读写本地文件;如需执行/联网,请用户 /sandbox off 关闭沙箱。";export const PERMISSION_MODES=["manual","acceptEdits","auto","plan","sandbox"];export const MODE_ICON={manual:"🔒",acceptEdits:"✎",auto:"⚡",plan:"📋",sandbox:"🧪"};export const MODE_LABEL={manual:"请求批准",acceptEdits:"接受编辑",auto:"自动",plan:"计划",sandbox:"沙箱"};export const MODE_DESC={manual:"逐项审批",acceptEdits:"文件写改自动放行,命令仍审批",auto:"无任何限制、开放所有权限,任何命令免审批",plan:"只读调研,先出方案",sandbox:"禁命令 + 联网,仍可读写本地文件"};export function createPermissions({yolo:w=!1,ask:f=null,cwd:_=null}={}){const E=new Set;let y=!!w,m=!1,b=!1,x=!1,h=_?s(_):[],g=_?r(_):[];return{request:async function(r,w,S,N=!1){if(g.length&&a(g,r,w))return{allow:!1,reason:"该操作被拒绝规则阻止(/permissions 查看或移除)。"};if(m&&!N)return{allow:!1,reason:PLAN_DENY_REASON};if(b&&sandboxBlocked(r))return{allow:!1,reason:SANDBOX_DENY_REASON};if(N)return{allow:!0};if(y||E.has(r))return{allow:!0};if(x&&("write_file"===r||"edit_file"===r))return{allow:!0};if(h.length&&a(h,r,w))return{allow:!0};if(!f)return{allow:!1,reason:"当前为非交互模式,未授权写/执行操作(加 --yolo 可自动放行)。"};if(d.has(r)){!function(o,e){process.stdout.write("\n"+t.bold(t.yellow("● 是否继续?"))+u(o,e)+"\n");const s=p(o,e);s&&process.stdout.write(n(t.gray(s)," ")+"\n");process.stdout.write(" "+t.bold("1、是")+" · "+t.bold("2、是,并允许在当前对话中后续相同操作不再询问")+" · "+t.bold("3、否")+"\n")}(r,w);const o=(await f(t.yellow("请选择")+" [1/2/3]: ")||"").trim();return"1"===o||/^(y|yes|是)$/i.test(o)?{allow:!0}:"2"===o||"a"===o?(E.add(r),{allow:!0}):""===o||"3"===o||/^(n|no|否)$/i.test(o)?{allow:!1,reason:"用户选择了「否」,该操作未执行。"}:{allow:!1,reason:`用户拒绝并说明:${o}`}}!function(s,r){process.stdout.write("\n"+t.bold(t.yellow("● 需要授权:"))+o(s,r)+"\n");const a=c(s,r);a&&process.stdout.write(" "+t.bold(t.red("⚠ "+a))+"\n");if("run_bash"===s)process.stdout.write(t.gray(" $ ")+String(r.command??"")+"\n");else if("write_file"===s){const o=String(r.content??"").split("\n").slice(0,12).join("\n");process.stdout.write(t.gray(` 将写入 ${r.path}:`)+"\n"+n(o)+"\n")}else"edit_file"===s?process.stdout.write(t.gray(` 编辑 ${r.path}:`)+"\n"+n(e(r.old_string,r.new_string))+"\n"):"fetch_url"===s&&process.stdout.write(t.gray(" GET ")+String(r.url??"")+"\n")}(r,w);const A=(await f(`${t.yellow("允许执行?")} [y]是 · [a]本会话该工具 · [A]永久允许(写入项目设置) · [N]否(可附原因): `)||"").trim();if("a"===A)return E.add(r),{allow:!0};if("A"===A){if(E.add(r),_){const o=l(r,w);try{const e=i(_,o);h=s(_),process.stdout.write(t.gray(` 已写入规则 ${o} → ${e}`)+"\n")}catch(o){process.stdout.write(t.yellow(` 规则写入失败(本会话仍生效):${o.message}`)+"\n")}}return{allow:!0}}const O=A.toLowerCase();return"y"===O||"yes"===O?{allow:!0}:""===A||"n"===O||"no"===O?{allow:!1,reason:"用户拒绝了该操作。"}:{allow:!1,reason:`用户拒绝并说明:${A}`}},setYolo(t){y=!!t},isYolo:()=>y,setPlanMode(t){m=!!t},isPlanMode:()=>m,setSandbox(t){b=!!t},isSandbox:()=>b,setAcceptEdits(t){x=!!t},isAcceptEdits:()=>x,setMode(t){y="auto"===t,x="acceptEdits"===t,m="plan"===t,b="sandbox"===t},getMode:()=>m?"plan":b?"sandbox":y?"auto":x?"acceptEdits":"manual",reloadRules(){h=_?s(_):[],g=_?r(_):[]}}}
@@ -0,0 +1 @@
1
+ import t from"node:fs";import e from"node:path";import n from"node:os";import r from"node:crypto";import{spawn as o}from"node:child_process";import{StringDecoder as i}from"node:string_decoder";export const PYTHON_VERSION="3.12";export const CORE_PACKAGES=["pandas","openpyxl","xlrd","tabulate"];export const EXTRA_PACKAGES=["matplotlib","python-docx","python-pptx","pypdf","requests"];export const BASELINE_PACKAGES=[...CORE_PACKAGES,...EXTRA_PACKAGES];export const BUNDLE_MANIFEST="hc-python-manifest.json";const s=27e5,c=3e4;export function runtimeDir(){return e.join(n.homedir(),".huicai","python")}export function uvBin(){return e.join(runtimeDir(),"win32"===process.platform?"uv.exe":"uv")}function a(){return e.join(runtimeDir(),"venv")}export function venvPython(){if("win32"===process.platform)return e.join(a(),"Scripts","python.exe");const n=e.join(a(),"bin","python3");return t.existsSync(n)?n:e.join(a(),"bin","python")}export function pythonEnvForBash(){try{const e=venvPython();if(t.existsSync(e))return{HUICAI_PYTHON:e}}catch{}return{}}export function mirrors(t={}){return{bundle:(process.env.HUICAI_PYRT_BUNDLE||t.pyBundleUrl||"").trim().replace(/\/+$/,""),uv:(process.env.HUICAI_UV_MIRROR||t.pyUvMirror||"").trim(),uvVersion:(process.env.HUICAI_UV_VERSION||t.pyUvVersion||"").trim(),python:(process.env.HUICAI_PYTHON_MIRROR||t.pyPythonMirror||"").trim(),pypi:(process.env.HUICAI_PYPI_MIRROR||t.pyPypiMirror||"").trim()}}export function uvTarget({platform:t=process.platform,arch:e=process.arch}={}){const n={x64:"x86_64",arm64:"aarch64"}[e];if(!n)throw new Error(`不支持的 CPU 架构:${e}(托管 Python 仅支持 x64/arm64)`);if("darwin"===t)return{file:`uv-${n}-apple-darwin.tar.gz`};if("linux"===t)return{file:`uv-${n}-unknown-linux-gnu.tar.gz`};if("win32"===t)return{file:`uv-${n}-pc-windows-msvc.zip`};throw new Error(`不支持的平台:${t}`)}export function uvUrl({mirror:t="",version:e=""}={},n=uvTarget()){return t?t.replace(/\/+$/,"")+"/"+n.file:e?`https://github.com/astral-sh/uv/releases/download/${e}/${n.file}`:`https://github.com/astral-sh/uv/releases/latest/download/${n.file}`}function u(){return e.join(runtimeDir(),"state.json")}function l(){try{return JSON.parse(t.readFileSync(u(),"utf8"))}catch{return{}}}function y(e){const n={...l(),...e};return t.mkdirSync(runtimeDir(),{recursive:!0}),t.writeFileSync(u(),JSON.stringify(n,null,2)+"\n"),n}function p(){const t=l(),e=Array.isArray(t.baseline?.packages)?t.baseline.packages:[],n=Array.isArray(t.extras)?t.extras:[];return new Set([...e,...n])}function h(){const t=p();return BASELINE_PACKAGES.filter(e=>!t.has(e))}function f(){const t=p();return CORE_PACKAGES.filter(e=>!t.has(e))}function m(){const t=p();return EXTRA_PACKAGES.filter(e=>!t.has(e))}function w(){try{return t.readdirSync(e.join(runtimeDir(),"cpython")).length>0}catch{return!1}}export function runtimeStatus(e={}){const n=l(),r=t.existsSync(venvPython()),o=h(),i=t.existsSync(uvBin())&&w()&&r;return{dir:runtimeDir(),uv:t.existsSync(uvBin()),uvVersion:n.uv?.version||"",python:w(),pythonVersion:n.python?.version||"3.12",venv:r,venvPython:r?venvPython():"",baselineMissing:o,coreMissing:f(),extrasMissing:m(),packages:[...p()].sort(),usable:i&&0===f().length,ready:i&&0===o.length,mirrors:mirrors(e)}}export function resetRuntime(){t.rmSync(runtimeDir(),{recursive:!0,force:!0})}export function execFileCapture(t,e,{cwd:n,timeoutMs:r=12e4,env:s,signal:a}={}){const u="win32"===process.platform;return new Promise(l=>{let y,p="",h=!1,f=!1;try{y=o(t,e,{cwd:n,env:s?{...process.env,...s}:process.env,...u?{}:{detached:!0}})}catch(t){return void l({code:-1,killed:!1,output:`命令启动失败:${t.message}`})}const m=()=>{try{!u&&y.pid?process.kill(-y.pid,"SIGKILL"):y.kill("SIGKILL")}catch{try{y.kill("SIGKILL")}catch{}}},w=setTimeout(()=>{h=!0,m()},r),d=()=>{f=!0,h=!0,m()};a&&(a.aborted?d():a.addEventListener("abort",d,{once:!0}));const v=()=>{clearTimeout(w),a&&a.removeEventListener("abort",d)},S=new i("utf8"),g=new i("utf8"),A=t=>{p.length<c&&(p+=t)};y.stdout.on("data",t=>A(S.write(t))),y.stderr.on("data",t=>A(g.write(t))),y.on("error",t=>{v(),l({code:-1,killed:!1,aborted:f,output:`命令启动失败:${t.message}`})}),y.on("close",t=>{v(),A(S.end()),A(g.end());const e=p.length>c?p.slice(0,c)+"\n…(输出已截断)":p;l({code:t,killed:h,aborted:f,output:e})})})}async function d(n,r){let o;try{const t=await fetch(n,{redirect:"follow",signal:AbortSignal.timeout(6e5),headers:{"User-Agent":"huicai-cli"}});if(!t.ok)throw new Error(`HTTP ${t.status}`);o=Buffer.from(await t.arrayBuffer())}catch(t){throw new Error(`下载失败 ${n}:${t?.cause?.message||t.message}\n(网络受限时可配置镜像:HUICAI_UV_MIRROR 指向内网/加速基址,详见 docs/21)`)}return t.mkdirSync(e.dirname(r),{recursive:!0}),t.writeFileSync(r,o),o.length}async function v(e,n,r){t.mkdirSync(n,{recursive:!0});const o=await r("tar",["-xf",e,"-C",n],{timeoutMs:12e4});if(0!==o.code)throw new Error(`解压失败(退出码 ${o.code}):${o.output.slice(0,400)}`)}function S(n,r,o=0){if(o>3)return null;let i;try{i=t.readdirSync(n,{withFileTypes:!0})}catch{return null}for(const t of i){const i=e.join(n,t.name);if(t.isFile()&&t.name===r)return i;if(t.isDirectory()){const t=S(i,r,o+1);if(t)return t}}return null}function g(n,r,o=0){if(o>3)return null;let i;try{i=t.readdirSync(n,{withFileTypes:!0})}catch{return null}for(const t of i){if(!t.isDirectory())continue;const i=e.join(n,t.name);if(t.name===r)return i;const s=g(i,r,o+1);if(s)return s}return null}const A=t=>String(t||"").split("\n")[0],x=t=>Math.round(t/1024/1024);async function $(e,n){if(!n)return!0;const o=r.createHash("sha256");return await new Promise((n,r)=>{t.createReadStream(e).on("data",t=>o.update(t)).on("end",n).on("error",r)}),o.digest("hex").toLowerCase()===String(n).toLowerCase()}export async function downloadResumable(n,r,{sha256:o="",size:i=0,log:s=()=>{},inactivityMs:c=6e4}={}){if(t.mkdirSync(e.dirname(r),{recursive:!0}),t.existsSync(r)){if(await $(r,o))return;t.rmSync(r,{force:!0})}const a=r+".part";let u=0;try{u=t.statSync(a).size}catch{}const l=new AbortController;let y=null;const p=()=>{clearTimeout(y),y=setTimeout(()=>l.abort(),c)};let h;p();try{h=await fetch(n,{redirect:"follow",signal:l.signal,headers:{"User-Agent":"huicai-cli",...u>0?{Range:`bytes=${u}-`}:{}}})}catch(t){throw clearTimeout(y),new Error(`下载失败 ${n}:${t?.cause?.message||t.message}`)}if(u>0&&206!==h.status&&(u=0),!h.ok&&206!==h.status)throw clearTimeout(y),new Error(`下载失败 ${n}:HTTP ${h.status}`);const f=i||u+Number(h.headers.get("content-length")||0)||0;u>0&&s(`断点续传:本地已有 ${x(u)}MB,继续…`);const m=t.createWriteStream(a,{flags:u>0?"a":"w"});let w=u,d=-10;try{const t=h.body.getReader();for(;;){const{done:e,value:n}=await t.read();if(e)break;if(p(),w+=n.length,m.write(Buffer.from(n))||await new Promise(t=>m.once("drain",t)),f>0){const t=Math.floor(w/f*100);t>=d+10&&(d=t,s(`下载 ${t}%(${x(w)}/${x(f)}MB)`))}}await new Promise((t,e)=>m.end(n=>n?e(n):t()))}catch(t){clearTimeout(y);try{m.destroy()}catch{}throw new Error(`下载中断(已存 ${x(w)}MB,重试将从断点续传):${t?.cause?.message||t.message}`)}if(clearTimeout(y),!await $(a,o))throw t.rmSync(a,{force:!0}),new Error("整包校验失败(sha256 不符),已删除缓存;请重试或检查镜像内容");t.renameSync(a,r)}function E(t){return{UV_PYTHON_INSTALL_DIR:e.join(runtimeDir(),"cpython"),...t.python?{UV_PYTHON_INSTALL_MIRROR:t.python}:{}}}async function P(e,{log:n,run:r}){if(t.existsSync(venvPython()))return;n("创建默认虚拟环境…");const o=await r(uvBin(),["venv",a(),"--python","3.12"],{timeoutMs:12e4,env:E(e)});if(0!==o.code)throw new Error(`虚拟环境创建失败(退出码 ${o.code}):${o.output.slice(-600)}`);y({venv:{at:(new Date).toISOString()}}),n("✓ 虚拟环境就绪")}async function I(t,e,{log:n,run:r},{offline:o=!1}={}){const i=["pip","install","--python",venvPython(),...!o&&e.pypi?["--index-url",e.pypi]:[],...t],c=await r(uvBin(),i,{timeoutMs:s,env:E(e)});if(0!==c.code)throw new Error(`包安装失败(退出码 ${c.code}):${c.output.slice(-600)}`+(o?"":"\n(网络受限时可配置 HUICAI_PYPI_MIRROR,如 https://pypi.tuna.tsinghua.edu.cn/simple)"));n(`✓ 已安装:${t.filter(t=>!t.startsWith("-")).join(" ")||"(按 requirements)"}`)}function _(t){const e=p();for(const n of t)e.add(n);y({baseline:{packages:BASELINE_PACKAGES.filter(t=>e.has(t)),at:(new Date).toISOString()}})}async function T(n,r){const o=!t.existsSync(uvBin()),i=!w();if(!o&&!i&&t.existsSync(venvPython())&&0===h().length)return;r.log(`整包直下:读取清单 ${n.bundle}/${BUNDLE_MANIFEST} …`);const s=await async function(t){try{const e=await fetch(t,{redirect:"follow",signal:AbortSignal.timeout(6e4),headers:{"User-Agent":"huicai-cli"}});if(!e.ok)throw new Error(`HTTP ${e.status}`);return await e.json()}catch(e){throw new Error(`获取 ${t} 失败:${e?.cause?.message||e.message}`)}}(`${n.bundle}/${BUNDLE_MANIFEST}`),c=`${process.platform}-${process.arch}`,a=s&&s[c];if(!a||!a.file)throw new Error(`整包清单无本平台条目(${c})`);const u=e.join(runtimeDir(),"tmp"),l=e.join(u,a.file);await downloadResumable(`${n.bundle}/${a.file}`,l,{sha256:a.sha256,size:a.size,log:r.log});const p=e.join(u,"bundle-extract");t.rmSync(p,{recursive:!0,force:!0}),r.log("解压整包…"),await v(l,p,r.run);const f=(new Date).toISOString();if(o){const e=S(p,"win32"===process.platform?"uv.exe":"uv");if(!e)throw new Error("整包内未找到 uv");t.renameSync(e,uvBin()),"win32"!==process.platform&&t.chmodSync(uvBin(),493),y({uv:{version:"bundle",at:f}})}if(i){const n=g(p,"cpython");if(!n)throw new Error("整包内未找到 cpython 目录");t.renameSync(n,e.join(runtimeDir(),"cpython")),y({python:{version:a.python||"3.12",at:f}})}await P(n,r);const m=g(p,"wheels"),d=S(p,"requirements.txt");h().length>0&&m&&d&&(r.log("离线安装基础包(本地 wheels,不走网络)…"),await I(["--no-index","--find-links",m,"-r",d],n,r,{offline:!0}),_(BASELINE_PACKAGES)),t.rmSync(p,{recursive:!0,force:!0}),t.rmSync(l,{force:!0}),r.log("✓ 整包就绪")}export async function ensureRuntime({log:n=()=>{},config:r={},run:o=execFileCapture,fetchFile:i=d,retryExtrasNow:s=!1}={}){const c=mirrors(r),a=()=>t.existsSync(uvBin())&&w()&&t.existsSync(venvPython())&&0===h().length;return a()?{python:venvPython(),firstInstall:!1,extrasMissing:[]}:async function(n,r){const o=e.join(runtimeDir(),".lock");t.mkdirSync(runtimeDir(),{recursive:!0});const i=Date.now();let s=!1;for(;;)try{t.mkdirSync(o);break}catch(e){if("EEXIST"!==e.code)throw e;let n=null;try{n=t.statSync(o)}catch{continue}if(n&&Date.now()-n.mtimeMs>18e5){try{t.rmdirSync(o)}catch{}continue}if(Date.now()-i>9e5)throw new Error("等待其他会话完成 Python 运行时安装超时(15 分钟);稍后重试,或 huicai python reset --yes 后重装");s||(s=!0,r("另一会话正在安装运行时,等待其完成…")),await new Promise(t=>setTimeout(t,2e3))}try{return await n()}finally{try{t.rmdirSync(o)}catch{}}}(async()=>{if(a())return{python:venvPython(),firstInstall:!1,extrasMissing:[]};const r=Date.now(),u={log:n,run:o,fetchFile:i},p=t.existsSync(uvBin())&&w()&&t.existsSync(venvPython())&&0===f().length;if(!a()&&c.bundle)try{await T(c,u)}catch(t){n(`整包直下未成(${A(t.message)}),回退组装路径…`)}await async function(n,{log:r,run:o,fetchFile:i}){if(t.existsSync(uvBin()))return;const s=uvTarget(),c=uvUrl({mirror:n.uv,version:n.uvVersion},s),a=e.join(runtimeDir(),"tmp"),u=e.join(a,s.file);r(`下载 uv(${c})…`);const l=await i(c,u),p=e.join(a,"uv-extract");t.rmSync(p,{recursive:!0,force:!0}),await v(u,p,o);const h="win32"===process.platform?"uv.exe":"uv",f=S(p,h);if(!f)throw new Error(`解压后未找到 ${h}(工件结构异常:${s.file})`);t.renameSync(f,uvBin()),"win32"!==process.platform&&t.chmodSync(uvBin(),493),t.rmSync(p,{recursive:!0,force:!0}),t.rmSync(u,{force:!0}),y({uv:{version:n.uvVersion||"latest",at:(new Date).toISOString()}}),r(`✓ uv 就绪(${Math.round(l/1024/1024)}MB)`)}(c,u),await async function(t,{log:e,run:n}){if(w())return;e("安装 CPython 3.12(约 40MB,python-build-standalone)…");const r=await n(uvBin(),["python","install","3.12"],{timeoutMs:18e5,env:E(t)});if(0!==r.code)throw new Error(`CPython 安装失败(退出码 ${r.code}):${r.output.slice(-600)}\n(网络受限时可配置 HUICAI_PYTHON_MIRROR 指向 python-build-standalone 镜像)`);y({python:{version:"3.12",at:(new Date).toISOString()}}),e("✓ CPython 3.12 就绪")}(c,u),await P(c,u),await async function(t,e){const n=f();0!==n.length&&(e.log(`安装核心包(${n.join(" ")},约 30MB,Excel 分析优先可用)…`),await I(n,t,e),_(n))}(c,u),await async function(t,e,{throttle:n=!0}={}){const r=m();if(0===r.length)return;const o=l();if(n&&o.extrasTryAt&&Date.now()-o.extrasTryAt<6e5)e.log(`扩展包暂缺(${r.join(" ")}),稍后自动重试;huicai python setup 可立即补装`);else{y({extrasTryAt:Date.now()});try{e.log(`安装扩展包(${r.join(" ")})…`),await I(r,t,e),_(r),y({extrasTryAt:0})}catch(t){e.log(`⚠ 扩展包安装未完成:${A(t.message)}(核心已可用;图表/Word/PPT 用到时自动补装)`)}}}(c,u,{throttle:!s});const h=m();return n(`运行时${h.length?"核心":""}就绪(共 ${Math.round((Date.now()-r)/1e3)}s):${venvPython()}`),{python:venvPython(),firstInstall:!p,extrasMissing:h}},n)}export async function packRuntime({outDir:o,config:i={},log:c=()=>{},run:a=execFileCapture}={}){if(!runtimeStatus(i).ready)throw new Error("本机运行时未全量就绪:先执行 huicai python setup 再打包");const u=mirrors(i),l=e.resolve(o||".");t.mkdirSync(l,{recursive:!0});const y=t.mkdtempSync(e.join(n.tmpdir(),"hc-pyrt-pack-")),p=e.join(y,"hc-python");t.mkdirSync(p,{recursive:!0});try{c("导出依赖锁定清单(uv pip freeze)…");const n=await a(uvBin(),["pip","freeze","--python",venvPython()],{timeoutMs:6e4,env:E(u)});if(0!==n.code)throw new Error(`freeze 失败:${n.output.slice(-300)}`);const o=n.output.split("\n").map(t=>t.trim()).filter(t=>/^[A-Za-z0-9][A-Za-z0-9._-]*==/.test(t)).join("\n")+"\n",i=e.join(p,"requirements.txt");t.writeFileSync(i,o),c("准备 pip(wheels 下载器)…"),await I(["pip"],u,{log:c,run:a}),c("下载本平台 wheels(按锁定版本)…");const h=e.join(p,"wheels");t.mkdirSync(h,{recursive:!0});const f=await a(venvPython(),["-m","pip","download","--dest",h,"-r",i,"--only-binary",":all:",...u.pypi?["--index-url",u.pypi]:[]],{timeoutMs:s});if(0!==f.code)throw new Error(`wheels 下载失败:${f.output.slice(-400)}`);c("复制 uv 与 CPython …");const m=e.basename(uvBin());t.copyFileSync(uvBin(),e.join(p,m)),"win32"!==process.platform&&t.chmodSync(e.join(p,m),493),t.cpSync(e.join(runtimeDir(),"cpython"),e.join(p,"cpython"),{recursive:!0});const w=`hc-python-3.12-${process.platform}-${process.arch}.tar.gz`;c(`打包 ${w} …`);const d=await a("tar",["-czf",e.join(l,w),"-C",y,"hc-python"],{timeoutMs:9e5});if(0!==d.code)throw new Error(`打包失败:${d.output.slice(-300)}`);const v=e.join(l,w),S=t.statSync(v).size,g=r.createHash("sha256");await new Promise((e,n)=>{t.createReadStream(v).on("data",t=>g.update(t)).on("end",e).on("error",n)});const A=e.join(l,BUNDLE_MANIFEST);let x={};try{x=JSON.parse(t.readFileSync(A,"utf8"))}catch{}return x[`${process.platform}-${process.arch}`]={file:w,size:S,sha256:g.digest("hex"),python:"3.12",packages:BASELINE_PACKAGES,builtAt:(new Date).toISOString()},t.writeFileSync(A,JSON.stringify(x,null,2)+"\n"),{file:v,manifest:A,size:S}}finally{t.rmSync(y,{recursive:!0,force:!0})}}export function validPyPackage(t){return"string"==typeof t&&""!==t&&!t.startsWith("-")&&/^[A-Za-z0-9][A-Za-z0-9._-]*(\[[A-Za-z0-9._,-]+\])?([=<>!~]=?[A-Za-z0-9.*+!_-]+)?$/.test(t)}export async function ensurePackages(t,{log:e=()=>{},config:n={},run:r=execFileCapture}={}){const o=p(),i=t.map(t=>t.replace(/[\[=<>!~].*$/,"")),s=t.filter((t,e)=>!o.has(i[e]));if(0===s.length)return;const c=mirrors(n);e(`按需安装:${s.join(" ")} …`),await I(s,c,{log:e,run:r});const a=l(),u=new Set(Array.isArray(a.extras)?a.extras:[]);for(const t of i)u.add(t);y({extras:[...u].sort()})}export async function runPython(n,r={},o={},i){if("1"===process.env.HUICAI_PYTHON_DISABLED)return"Python 运行时已被禁用(HUICAI_PYTHON_DISABLED=1,企业策略或用户设置)。";const s=null==r.code?"":String(r.code),c=null==r.script?"":String(r.script);if(!s.trim()&&!c.trim())throw new Error("run_python 需要 code(内联代码)或 script(脚本路径)之一");if(s.trim()&&c.trim())throw new Error("code 与 script 只能二选一");const u=Array.isArray(r.packages)?r.packages.map(String):[];for(const t of u)if(!validPyPackage(t))throw new Error(`非法的包名:${t}`);const l=[],y=t=>l.push(t);let p;await ensureRuntime({log:y,config:o}),u.length&&await ensurePackages(u,{log:y,config:o});let h=null;if(c){if(p=e.resolve(n||".",c),!t.existsSync(p))throw new Error(`脚本不存在:${c}`)}else{const n=e.join(runtimeDir(),"tmp");t.mkdirSync(n,{recursive:!0}),p=e.join(n,`run-${Date.now()}-${Math.random().toString(36).slice(2,8)}.py`),t.writeFileSync(p,s,"utf8"),h=()=>{try{t.unlinkSync(p)}catch{}}}const f=Array.isArray(r.args)?r.args.map(String):[],m=Number.isFinite(r.timeout)?Math.min(r.timeout,6e5):12e4,w=venvPython();try{const t=await execFileCapture(w,["-X","utf8",p,...f],{cwd:n,timeoutMs:m,signal:i,env:{PYTHONIOENCODING:"utf-8",MPLBACKEND:"Agg",VIRTUAL_ENV:a(),PATH:e.dirname(w)+e.delimiter+(process.env.PATH||"")}});let r=t.output;""===r.trim()&&(r="(无输出)");const o=l.length?`【运行时准备】\n${l.join("\n")}\n\n`:"";return`${o}${t.killed?"【超时被终止】":`退出码 ${t.code}`}\n${r}`}finally{h&&h()}}
package/src/repl.js CHANGED
@@ -1 +1 @@
1
- import s from"node:readline";import{c as o}from"./colors.js";import{createPermissions as t}from"./permissions.js";import{runTurn as e}from"./agent.js";import{createSession as n,saveSession as r,turnCount as i}from"./session.js";import{createCheckpoints as a}from"./checkpoints.js";import{buildSystemPrompt as c}from"./context.js";import{runVerifyLoop as l}from"./autofix.js";import{runAutoFlow as m}from"./autorun.js";import{loadMcpTools as p,closeMcp as u}from"./mcp.js";import{makeSubagentTool as d,makeDispatchAgentsTool as g}from"./subagent.js";import{makeDispatchMemberTool as w}from"./squad.js";import{makeConsultTool as f}from"./consult.js";import{makeExpertTools as h}from"./experts.js";import{makeAdvisorTool as y}from"./advisor.js";import{makeRecallTool as x,readMemoryRaw as T}from"./memory.js";import{detectScanners as j,makeScanTool as k}from"./scan.js";import{runSlash as b}from"./commands.js";import{expandAtMentions as I,buildUserContent as v,runBangCommand as S,loadHistory as P,appendHistory as E}from"./input.js";import{makeSkillTools as $}from"./skills.js";import{renderWelcome as _}from"./banner.js";export async function buildExtraTools(s,t){let e={tools:[],clients:[]};try{e=await p()}catch(s){process.stdout.write(o.yellow("MCP 初始化失败(忽略):"+s.message)+"\n")}const n=[...$(t),...e.tools],r=d({config:s,cwd:t,extraTools:e.tools,depth:0});r&&n.push(r);const i=g({config:s,cwd:t,extraTools:e.tools,depth:0});i&&n.push(i);const a=w({config:s,cwd:t,extraTools:e.tools});a&&n.push(a);const c=f({config:s});c&&n.push(c);try{n.push(...await h({config:s}))}catch{}const l=y({config:s});return l&&n.push(l),!1!==s.memoryEnabled&&T(t)&&n.push(x({cwd:t})),j(t).length&&n.push(k({cwd:t})),{extraTools:n,clients:e.clients}}export async function startRepl({config:i,cwd:p,yolo:d,initialPrompt:g,session:w,version:f}){const h=s.createInterface({input:process.stdin,output:process.stdout,history:P(p).reverse(),historySize:200}),y=s=>new Promise(o=>h.question(s,o));let x=t({yolo:d,ask:y,cwd:p});const T={prompt:0,completion:0},j={list:[]},k=a();let $=null,O=null;const W={config:i,cwd:p,permissions:x,usage:T,session:w,version:f};h.on("SIGINT",()=>{$?$.abort():process.stdout.write("\n"+o.gray("输入 /exit 退出。")+"\n")}),function(s,o,t,e){process.stdout.write(_({model:s.model,cwd:o,version:e,platform:/^(agent|workflow|datahuman|squad)-/.test(String(s.model||"")),yolo:t.isYolo(),consult:s.consult?s.consultLabel||s.consult:null})+"\n")}(i,p,x,f),q(w);let{extraTools:C,clients:J}=await buildExtraTools(i,p);const N=async s=>{const{text:t,mentions:n,images:a}=await I(s,p,i);n.length&&process.stdout.write(o.gray(" · 已展开引用:"+n.join("、")+(a.length?`(含 ${a.length} 张图片)`:""))+"\n");const c=k.snapshot({session:W.session,label:s});W.session.messages.push({role:"user",content:v(t,a)}),$=new AbortController,W.session.compaction||(W.session.compaction={}),i.sessionId=W.session.id;const l=await e({config:i,messages:W.session.messages,permissions:x,cwd:p,signal:$.signal,extraTools:C,todoState:j,compaction:W.session.compaction,fileJournal:c});return l?.usage&&(T.prompt+=l.usage.prompt,T.completion+=l.usage.completion),l?.lastPromptTokens&&(W.session.lastPromptTokens=l.lastPromptTokens),$=null,r(W.session),l},Y=()=>({...W,session:W.session,extraTools:C,checkpoints:k,print:s=>process.stdout.write(String(s).endsWith("\n")?String(s):String(s)+"\n")});for(g&&(process.stdout.write(o.cyan("\nhuicai> ")+g+"\n"),await N(g));;){let s;try{s=(await y(o.cyan("\nhuicai> "))).trim()}catch{break}if(""!==s){if(E(p,s),"/exit"===s||"/quit"===s)break;if(s.startsWith("!")&&s.length>1){const t=s.slice(1).trim(),{output:e,record:n}=await S(t,p);process.stdout.write(o.gray(e.split("\n").map(s=>" "+s).join("\n"))+"\n"),W.session.messages.push({role:"user",content:n}),r(W.session);continue}if(s.startsWith("/")){const e=await b(s,Y());if("exit"===e.action)break;"clear"===e.action&&(W.session=n({cwd:p,model:i.model,messages:[W.session.messages[0]]}),j.list=[],k.list.length=0,process.stdout.write(o.gray("已开启新会话(旧会话已保存,可用 huicai --resume 恢复)。\n"))),"refresh"===e.action&&q(W.session),"cd"===e.action&&(p=e.cwd,W.cwd=p,x=t({yolo:x.isYolo(),ask:y,cwd:p}),W.permissions=x,u(J),({extraTools:C,clients:J}=await buildExtraTools(i,p)),W.session=n({cwd:p,model:i.model,messages:[{role:"system",content:c(p,{memory:!1!==i.memoryEnabled})}]}),j.list=[],k.list.length=0,i.sessionId=W.session.id,process.stdout.write(o.gray("已切换到 "+p+"(新会话,旧会话已保存)。\n"))),"autofix"===e.action&&await l({command:e.command,maxIters:e.maxIters,cwd:p,runTurnOnce:s=>N(s),emit:s=>process.stdout.write(o.gray(s)+"\n")}),"autorun"===e.action&&await m({goal:e.goal,config:i,permissions:x,cwd:p,runTurnOnce:s=>N(s),emit:s=>process.stdout.write(o.gray(" · "+s)+"\n")}),"loop"===e.action&&(O&&(clearInterval(O),O=null),e.stop?process.stdout.write(o.gray("已停止循环。\n")):(process.stdout.write(o.gray(`已开始循环:每 ${e.seconds}s 运行「${e.prompt}」(/loop off 停止)。\n`)),O=setInterval(()=>{$||N(e.prompt)},1e3*e.seconds))),e.prompt&&await N(e.prompt);continue}await N(s)}}O&&clearInterval(O),h.close(),u(J),process.stdout.write(o.gray("\n再见。\n"))}function q(s){const t=i(s.messages);if(0===t)return;process.stdout.write(o.gray(` 已恢复会话「${s.title||""}」(${t} 轮)`)+"\n");const e=[...s.messages].reverse().find(s=>"assistant"===s.role&&s.content);if(e){const s=String(e.content).trim(),t=s.length>400?s.slice(0,400)+"…":s;process.stdout.write(o.gray(" 上次回复:")+t.replace(/\n/g,"\n ")+"\n")}}export async function runOnce({config:s,cwd:o,yolo:n,prompt:a,session:c,persist:l,outputFormat:m,maxTurns:p}){a||(process.stderr.write('用法: huicai -p "你的指令"\n'),process.exit(1));const d="json"===m,g=t({yolo:n,ask:null,cwd:o}),{extraTools:w,clients:f}=await buildExtraTools(s,o),{text:h,images:y}=await I(a,o,s);c.messages.push({role:"user",content:v(h,y)}),c.compaction||(c.compaction={}),s.sessionId=c.id;const x=await e({config:s,messages:c.messages,permissions:g,cwd:o,extraTools:w,maxIters:p,compaction:c.compaction,io:d?{write:()=>{}}:void 0});if(x?.lastPromptTokens&&(c.lastPromptTokens=x.lastPromptTokens),l&&r(c),u(f),d){const o=[...c.messages].reverse().find(s=>"assistant"===s.role&&s.content);process.stdout.write(JSON.stringify({type:"result",is_error:!!x?.error,...x?.error?{error:x.error}:{},result:o?String(o.content):"",usage:{input_tokens:x?.usage?.prompt??0,output_tokens:x?.usage?.completion??0},session_id:c.id,model:s.model,num_turns:i(c.messages)})+"\n")}x?.error&&process.exit(1)}
1
+ import s from"node:readline";import{c as o}from"./colors.js";import{createPermissions as t}from"./permissions.js";import{runTurn as e}from"./agent.js";import{createSession as n,saveSession as r,turnCount as i}from"./session.js";import{ensureSessionTitle as a}from"./title.js";import{createCheckpoints as c}from"./checkpoints.js";import{buildSystemPrompt as l}from"./context.js";import{runVerifyLoop as m}from"./autofix.js";import{runAutoFlow as p}from"./autorun.js";import{loadMcpTools as u,closeMcp as d}from"./mcp.js";import{makeSubagentTool as g,makeDispatchAgentsTool as f}from"./subagent.js";import{makeDispatchMemberTool as w}from"./squad.js";import{makeConsultTool as y}from"./consult.js";import{makeExpertTools as h}from"./experts.js";import{listWorkspaceFiles as x}from"./executors.js";import{makeAdvisorTool as j}from"./advisor.js";import{makeRecallTool as T,readMemoryRaw as b}from"./memory.js";import{detectScanners as k,makeScanTool as S}from"./scan.js";import{runSlash as I}from"./commands.js";import{expandAtMentions as $,buildUserContent as v,runBangCommand as P,loadHistory as E,appendHistory as _}from"./input.js";import{makeSkillTools as q}from"./skills.js";import{renderWelcome as C}from"./banner.js";export async function buildExtraTools(s,t,e={}){const n="function"==typeof e.onSub?e.onSub:null;let r={tools:[],clients:[]};try{r=await u()}catch(s){process.stdout.write(o.yellow("MCP 初始化失败(忽略):"+s.message)+"\n")}const i=[...q(t),...r.tools],a=g({config:s,cwd:t,extraTools:r.tools,depth:0,onSub:n});a&&i.push(a);const c=f({config:s,cwd:t,extraTools:r.tools,depth:0,onSub:n});c&&i.push(c);const l=w({config:s,cwd:t,extraTools:r.tools,onSub:n});l&&i.push(l);const m=y({config:s});m&&i.push(m);try{i.push(...await h({config:s}))}catch{}const p=j({config:s});return p&&i.push(p),!1!==s.memoryEnabled&&b(t)&&i.push(T({cwd:t})),k(t).length&&i.push(S({cwd:t})),{extraTools:i,clients:r.clients}}export async function startRepl({config:i,cwd:u,yolo:g,initialPrompt:f,session:w,version:y}){const h=s.createInterface({input:process.stdin,output:process.stdout,history:E(u).reverse(),historySize:200,completer:s=>{const o=/(?:^|\s)@([^\s@]*)$/.exec(s);if(!o)return[[],s];const t="@"+o[1],e=t.toLowerCase();return[x(u).map(s=>"@"+s).filter(s=>s.toLowerCase().startsWith(e)).slice(0,40),t]}}),j=s=>new Promise(o=>h.question(s,o));let T=t({yolo:g,ask:j,cwd:u});const b={prompt:0,completion:0},k={list:[]},S=c();let q=null,O=null;const A={config:i,cwd:u,permissions:T,usage:b,session:w,version:y};h.on("SIGINT",()=>{q?q.abort():process.stdout.write("\n"+o.gray("输入 /exit 退出。")+"\n")}),function(s,o,t,e){process.stdout.write(C({model:s.model,cwd:o,version:e,platform:/^(agent|workflow|datahuman|squad)-/.test(String(s.model||"")),yolo:t.isYolo(),consult:s.consult?s.consultLabel||s.consult:null})+"\n")}(i,u,T,y),W(w);let{extraTools:L,clients:J}=await buildExtraTools(i,u);const N=()=>{a(A.session,i).then(s=>{s&&r(A.session)}).catch(()=>{})},Y=async s=>{const{text:t,mentions:n,images:a}=await $(s,u,i,{onDocStart:(s,t,e)=>process.stdout.write(o.gray(` · 正在解析文档 @${s}${e>1?`(${t}/${e})`:""}…`)+"\n")});n.length&&process.stdout.write(o.gray(" · 已展开引用:"+n.join("、")+(a.length?`(含 ${a.length} 张图片)`:""))+"\n");const c=S.snapshot({session:A.session,label:s});A.session.messages.push({role:"user",content:v(t,a)}),N(),q=new AbortController,A.session.compaction||(A.session.compaction={}),i.sessionId=A.session.id;const l=await e({config:i,messages:A.session.messages,permissions:T,cwd:u,signal:q.signal,extraTools:L,todoState:k,compaction:A.session.compaction,fileJournal:c});return l?.usage&&(b.prompt+=l.usage.prompt,b.completion+=l.usage.completion),l?.lastPromptTokens&&(A.session.lastPromptTokens=l.lastPromptTokens),q=null,r(A.session),N(),l},z=()=>({...A,session:A.session,extraTools:L,checkpoints:S,print:s=>process.stdout.write(String(s).endsWith("\n")?String(s):String(s)+"\n")});for(f&&(process.stdout.write(o.cyan("\nhuicai> ")+f+"\n"),await Y(f));;){let s;try{s=(await j(o.cyan("\nhuicai> "))).trim()}catch{break}if(""!==s){if(_(u,s),"/exit"===s||"/quit"===s)break;if(s.startsWith("!")&&s.length>1){const t=s.slice(1).trim(),{output:e,record:n}=await P(t,u);process.stdout.write(o.gray(e.split("\n").map(s=>" "+s).join("\n"))+"\n"),A.session.messages.push({role:"user",content:n}),r(A.session);continue}if(s.startsWith("/")){const e=await I(s,z());if("exit"===e.action)break;"clear"===e.action&&(A.session=n({cwd:u,model:i.model,messages:[A.session.messages[0]]}),k.list=[],S.list.length=0,process.stdout.write(o.gray("已开启新会话(旧会话已保存,可用 huicai --resume 恢复)。\n"))),"refresh"===e.action&&W(A.session),"cd"===e.action&&(u=e.cwd,A.cwd=u,T=t({yolo:T.isYolo(),ask:j,cwd:u}),A.permissions=T,d(J),({extraTools:L,clients:J}=await buildExtraTools(i,u)),A.session=n({cwd:u,model:i.model,messages:[{role:"system",content:l(u,{memory:!1!==i.memoryEnabled})}]}),k.list=[],S.list.length=0,i.sessionId=A.session.id,process.stdout.write(o.gray("已切换到 "+u+"(新会话,旧会话已保存)。\n"))),"autofix"===e.action&&await m({command:e.command,maxIters:e.maxIters,cwd:u,runTurnOnce:s=>Y(s),emit:s=>process.stdout.write(o.gray(s)+"\n")}),"autorun"===e.action&&await p({goal:e.goal,config:i,permissions:T,cwd:u,runTurnOnce:s=>Y(s),emit:s=>process.stdout.write(o.gray(" · "+s)+"\n")}),"loop"===e.action&&(O&&(clearInterval(O),O=null),e.stop?process.stdout.write(o.gray("已停止循环。\n")):(process.stdout.write(o.gray(`已开始循环:每 ${e.seconds}s 运行「${e.prompt}」(/loop off 停止)。\n`)),O=setInterval(()=>{q||Y(e.prompt)},1e3*e.seconds))),e.prompt&&await Y(e.prompt);continue}await Y(s)}}O&&clearInterval(O),h.close(),d(J),process.stdout.write(o.gray("\n再见。\n"))}function O(s){return"string"==typeof s?s:Array.isArray(s)?s.map(s=>"string"==typeof s?s:s&&"text"===s.type&&s.text||"").join(""):""}function W(s){const t=(s.messages||[]).filter(s=>"user"===s.role||"assistant"===s.role),e=i(s.messages);if(0===e)return;process.stdout.write(o.gray(` 已恢复会话「${s.title||""}」(${e} 轮)——历史对话如下:`)+"\n");const n=t.length>40?t.length-40:0;n&&process.stdout.write(o.gray(` (较早 ${n} 条已省略,完整记录在 ~/.huicai/sessions)`)+"\n");for(const s of t.slice(-40)){const t=O(s.content).trim();if(!t)continue;const e=t.length>800?t.slice(0,800)+"…":t,n="user"===s.role?o.bold(" 你 › "):o.gray(" 助手 › ");process.stdout.write(n+e.replace(/\n/g,"\n ")+"\n")}process.stdout.write(o.gray(" ——以上为历史,可继续对话——")+"\n")}export async function runOnce({config:s,cwd:o,yolo:n,prompt:a,session:c,persist:l,outputFormat:m,maxTurns:p}){a||(process.stderr.write('用法: huicai -p "你的指令"\n'),process.exit(1));const u="json"===m,g=t({yolo:n,ask:null,cwd:o}),{extraTools:f,clients:w}=await buildExtraTools(s,o),{text:y,images:h}=await $(a,o,s);c.messages.push({role:"user",content:v(y,h)}),c.compaction||(c.compaction={}),s.sessionId=c.id;const x=await e({config:s,messages:c.messages,permissions:g,cwd:o,extraTools:f,maxIters:p,compaction:c.compaction,io:u?{write:()=>{}}:void 0});if(x?.lastPromptTokens&&(c.lastPromptTokens=x.lastPromptTokens),l&&r(c),d(w),u){const o=[...c.messages].reverse().find(s=>"assistant"===s.role&&s.content);process.stdout.write(JSON.stringify({type:"result",is_error:!!x?.error,...x?.error?{error:x.error}:{},result:o?String(o.content):"",usage:{input_tokens:x?.usage?.prompt??0,output_tokens:x?.usage?.completion??0},session_id:c.id,model:s.model,num_turns:i(c.messages)})+"\n")}x?.error&&process.exit(1)}
package/src/scan.js CHANGED
@@ -1 +1 @@
1
- import n from"node:fs";import e from"node:path";import{execSync as t}from"node:child_process";import{captureCommand as o}from"./autofix.js";function r(t,o){try{return n.existsSync(e.join(t,o))}catch{return!1}}function a(n){try{return t("win32"===process.platform?`where ${n}`:`command -v ${n}`,{stdio:"ignore"}),!0}catch{return!1}}export function detectScanners(n){const e=[];return r(n,"package.json")&&a("npm")&&e.push({name:"npm audit",command:"npm audit --audit-level=high",why:"Node 依赖漏洞"}),(r(n,"requirements.txt")||r(n,"pyproject.toml"))&&a("pip-audit")&&e.push({name:"pip-audit",command:"pip-audit",why:"Python 依赖漏洞"}),r(n,"go.mod")&&a("govulncheck")&&e.push({name:"govulncheck",command:"govulncheck ./...",why:"Go 漏洞"}),r(n,"Cargo.toml")&&a("cargo-audit")&&e.push({name:"cargo audit",command:"cargo audit",why:"Rust 依赖漏洞"}),a("semgrep")&&e.push({name:"semgrep",command:"semgrep --config auto --error -q .",why:"代码静态安全(SAST)"}),a("gitleaks")&&e.push({name:"gitleaks",command:"gitleaks detect --no-banner -r /dev/stdout",why:"密钥泄漏"}),a("trivy")&&e.push({name:"trivy fs",command:"trivy fs --scanners vuln,secret -q .",why:"依赖漏洞+密钥"}),e}export async function runScanners(n,e){const t=[];for(const r of e){const{code:e,output:a}=await o(n,r.command,18e4);t.push({name:r.name,why:r.why,command:r.command,code:e,output:a})}return t}export function makeScanTool({cwd:n}){return{readOnly:!0,spec:{type:"function",function:{name:"security_scan",description:"自动检测并运行本项目适用的安全/质量扫描器(npm audit / semgrep / trivy / gitleaks / 依赖体检等,仅跑本机已装的),返回发现的问题。用于安全审查或提交前自检;发现问题后你应逐条修复再重扫确认。",parameters:{type:"object",properties:{},required:[]}}},run:async()=>{const e=detectScanners(n);if(!e.length)return"未检测到可用的扫描器(可安装 semgrep/trivy/gitleaks,或项目含 package.json 等)。";return(await runScanners(n,e)).map(n=>`## ${n.name}(${0===n.code?"无高危":"有发现"})\n${n.output.slice(0,4e3)}`).join("\n\n")}}}
1
+ import e from"node:fs";import t from"node:path";import{execSync as n,execFileSync as r}from"node:child_process";import{captureCommand as a}from"./autofix.js";function o(n,r){try{return e.existsSync(t.join(n,r))}catch{return!1}}function c(e){try{return n("win32"===process.platform?`where ${e}`:`command -v ${e}`,{stdio:"ignore"}),!0}catch{return!1}}export function detectScanners(e){const t=[];return o(e,"package.json")&&c("npm")&&t.push({name:"npm audit",command:"npm audit --audit-level=high",why:"Node 依赖漏洞"}),(o(e,"requirements.txt")||o(e,"pyproject.toml"))&&c("pip-audit")&&t.push({name:"pip-audit",command:"pip-audit",why:"Python 依赖漏洞"}),o(e,"go.mod")&&c("govulncheck")&&t.push({name:"govulncheck",command:"govulncheck ./...",why:"Go 漏洞"}),o(e,"Cargo.toml")&&c("cargo-audit")&&t.push({name:"cargo audit",command:"cargo audit",why:"Rust 依赖漏洞"}),c("semgrep")&&t.push({name:"semgrep",command:"semgrep --config auto --error -q .",why:"代码静态安全(SAST)"}),c("gitleaks")&&t.push({name:"gitleaks",command:"gitleaks detect --no-banner -r /dev/stdout",why:"密钥泄漏"}),c("trivy")&&t.push({name:"trivy fs",command:"trivy fs --scanners vuln,secret -q .",why:"依赖漏洞+密钥"}),t}export async function runScanners(e,t){const n=[];for(const r of t){const{code:t,output:o}=await a(e,r.command,18e4);n.push({name:r.name,why:r.why,command:r.command,code:t,output:o})}return n}export const HEALTH_RULES=[{id:"dynamic-eval",severity:"high",why:"动态执行 eval / new Function",pattern:"\\beval\\(|new Function\\("},{id:"hardcoded-secret",severity:"high",why:"疑似硬编码密钥/口令",pattern:"(password|passwd|secret|api[_-]?key|access[_-]?key)\\s*[:=]\\s*[\"'][^\"']{6,}"},{id:"insecure-random",severity:"high",why:"安全场景疑用不安全随机源",pattern:"Math\\.random\\(\\)[^\\n]*(token|secret|key|nonce|salt|otp|passw)"},{id:"print-stack-trace",severity:"warn",why:"Java 直接打印堆栈(应走日志框架)",pattern:"printStackTrace\\("},{id:"stdout-print",severity:"warn",why:"裸 System.out/err 输出(应走日志)",pattern:"System\\.(out|err)\\.print"},{id:"empty-catch",severity:"warn",why:"空 catch 吞异常",pattern:"catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}",multiline:!0},{id:"debugger",severity:"warn",why:"遗留 debugger 断点",pattern:"\\bdebugger\\b"},{id:"todo-fixme",severity:"info",why:"未完成标记 TODO/FIXME/HACK",pattern:"\\b(TODO|FIXME|HACK)\\b"}];const s=["!**/node_modules/**","!**/dist/**","!**/build/**","!**/target/**","!**/out/**","!**/.git/**","!**/*.min.js","!**/vendor/**"],i={high:0,warn:1,info:2};function u(e,t){if(!c("rg"))return{count:-1,sample:""};const n=s.flatMap(e=>["-g",e]),a=e.multiline?["-lU"]:["-l"];try{const o=r("rg",[...a,...n,"-e",e.pattern,"."],{cwd:t,encoding:"utf8",maxBuffer:16<<20,stdio:["ignore","pipe","ignore"]}).split("\n").filter(Boolean);return{count:o.length,sample:o[0]||""}}catch{return{count:0,sample:""}}}export function runHealthScan(e,t=u){const n=[];for(const r of HEALTH_RULES){const{count:a,sample:o}=t(r,e);a>0&&n.push({id:r.id,severity:r.severity,why:r.why,count:a,sample:o})}return n.sort((e,t)=>i[e.severity]-i[t.severity]||t.count-e.count),n}export function formatHealthScan(e){return e.length?["发现以下高信号项:",...e.map(e=>`- [${e.severity}] ${e.why}:命中 ${e.count} 文件${e.sample?`(如 ${e.sample})`:""}`)].join("\n"):"未发现高信号问题(eval/硬编码密钥/不安全随机/printStackTrace/空catch/debugger 等)。"}export function makeScanTool({cwd:e}){return{readOnly:!0,spec:{type:"function",function:{name:"security_scan",description:"运行内置代码健康自检(eval/硬编码密钥/不安全随机/printStackTrace/空catch/debugger 等高信号规则,无需外部工具)并自动检测运行本项目适用的外部扫描器(npm audit / semgrep / trivy / gitleaks,仅跑本机已装的),返回发现的问题。用于安全审查或提交前自检;发现问题后你应逐条修复再重扫确认。",parameters:{type:"object",properties:{},required:[]}}},run:async()=>{const t=[];t.push("## 代码健康自检(内置)\n"+formatHealthScan(runHealthScan(e)));const n=detectScanners(e);if(n.length){const r=await runScanners(e,n);t.push(...r.map(e=>`## ${e.name}(${0===e.code?"无高危":"有发现"})\n${e.output.slice(0,4e3)}`))}else t.push("(未检测到外部扫描器:可装 semgrep/trivy/gitleaks 获取更深的依赖漏洞/SAST/密钥扫描。)");return t.join("\n\n")}}}
@@ -0,0 +1 @@
1
+ import{cronMatches as e}from"./cron.js";const t=["周日","周一","周二","周三","周四","周五","周六"];function r(e,t,r,n){const i=Math.floor(Number(e));return Number.isNaN(i)?n:Math.min(r,Math.max(t,i))}function n(e){return String(e).padStart(2,"0")}export function parseHM(e){const t=/^(\d{1,2}):(\d{2})$/.exec(String(e||"").trim());if(!t)return null;const r=Number(t[1]),n=Number(t[2]);return r>23||n>59?null:{h:r,mi:n}}function i(e,t="09:00"){const r=parseHM(e);return r?`${n(r.h)}:${n(r.mi)}`:t}export function parseYMD(e){const t=/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})$/.exec(String(e||"").trim());if(!t)return null;const r=Number(t[1]),n=Number(t[2]),i=Number(t[3]);return n<1||n>12||i<1||i>31?null:{y:r,m:n,d:i}}export function freqToCron(e){if(!e||"object"!=typeof e)return null;const t=parseHM(e.time)||{h:9,mi:0},{h:n,mi:i}=t;if("periodic"===e.kind)switch(e.period){case"daily":default:return`${i} ${n} * * *`;case"weekly":case"biweekly":return`${i} ${n} * * ${r(e.weekday,0,6,1)}`;case"monthly":return`${i} ${n} ${r(e.day,1,31,1)} * *`;case"yearly":return`${i} ${n} ${r(e.day,1,31,1)} ${r(e.month,1,12,1)} *`}if("interval"===e.kind){const t=Array.isArray(e.weekdays)&&e.weekdays.length&&e.weekdays.length<7?[...new Set(e.weekdays.map(e=>r(e,0,6,0)))].sort((e,t)=>e-t).join(","):"*";if("hour"===e.unit){return`0 */${r(e.every,1,23,1)} * * ${t}`}return`*/${r(e.every,1,59,5)} * * * ${t}`}if("once"===e.kind){const t=parseYMD(e.date);return t?`${i} ${n} ${t.d} ${t.m} *`:null}return null}function o(e){const t=new Date(e.getFullYear(),e.getMonth(),e.getDate());return t.setDate(t.getDate()-(t.getDay()+6)%7),t}export function isDue(t,r=new Date){if(!t)return!1;const n=t.cron||freqToCron(t.freq);if(!n||!e(n,r))return!1;if(!function(e,t){const r=parseYMD(e.from),n=parseYMD(e.to);return!(r&&t<new Date(r.y,r.m-1,r.d,0,0,0,0)||n&&t>new Date(n.y,n.m-1,n.d,23,59,59,999))}(t,r))return!1;const i=t.freq;if(i&&"once"===i.kind){const e=parseYMD(i.date);if(!e)return!1;if(r.getFullYear()!==e.y||r.getMonth()+1!==e.m||r.getDate()!==e.d)return!1}return!(i&&"periodic"===i.kind&&"biweekly"===i.period&&!function(e,t){const r=o(new Date(Number(e.created)||Date.now())),n=o(t);return(Math.round((n-r)/6048e5)%2+2)%2==0}(t,r))}export function nextRunAt(e,t=new Date,r=366){if(!e)return null;if(!(e.cron||freqToCron(e.freq)))return null;if(e.freq&&"once"===e.freq.kind){const r=parseYMD(e.freq.date);if(!r)return null;const n=parseHM(e.freq.time)||{h:0,mi:0},i=new Date(r.y,r.m-1,r.d,n.h,n.mi,0,0).getTime();return i>t.getTime()?i:null}const n=new Date(t.getTime());n.setSeconds(0,0),n.setMinutes(n.getMinutes()+1);const i=n.getTime()+864e5*r;for(let t=n.getTime();t<=i;t+=6e4)if(isDue(e,new Date(t)))return t;return null}const u={"0 9 * * *":"每天 09:00","0 9 * * 1-5":"工作日 09:00","0 * * * *":"每小时整点","0 9 * * 1":"每周一 09:00"};export function describeSchedule(e){const o=e&&e.freq;if(o&&"object"==typeof o){const e=i(o.time);if("periodic"===o.kind)switch(o.period){case"daily":default:return`每天 ${e}`;case"weekly":return`每${t[r(o.weekday,0,6,1)]} ${e}`;case"biweekly":return`每两周·${t[r(o.weekday,0,6,1)]} ${e}`;case"monthly":return`每月${r(o.day,1,31,1)}号 ${e}`;case"yearly":return`每年${r(o.month,1,12,1)}月${r(o.day,1,31,1)}日 ${e}`}if("interval"===o.kind){let e=`每 ${r(o.every,1,999,5)} ${"hour"===o.unit?"小时":"分钟"}`;return Array.isArray(o.weekdays)&&o.weekdays.length&&o.weekdays.length<7&&(e+=` · ${[...new Set(o.weekdays)].sort((e,t)=>e-t).map(e=>t[r(e,0,6,0)]).join("")}`),e}if("once"===o.kind){const e=parseYMD(o.date);return e?`${e.y}/${n(e.m)}/${n(e.d)} ${i(o.time)}(单次)`:`单次 ${i(o.time)}`}}const a=String(e&&e.cron||"").trim();return u[a]||(a?`cron:${a}`:"")}
package/src/schedule.js CHANGED
@@ -1 +1 @@
1
- import r from"node:fs";import n from"node:path";import t from"node:os";export const SCHEDULE_FILE=n.join(t.homedir(),".huicai","schedule.json");function o(){try{const n=JSON.parse(r.readFileSync(SCHEDULE_FILE,"utf8"));return Array.isArray(n)?n:[]}catch{return[]}}function i(t){r.mkdirSync(n.dirname(SCHEDULE_FILE),{recursive:!0}),r.writeFileSync(SCHEDULE_FILE,JSON.stringify(t,null,2)+"\n",{mode:384})}export function listTasks(){return o()}export function addTask({cron:r,prompt:n,cwd:t}){const e=o(),c=Date.now().toString(36)+Math.random().toString(36).slice(2,4);return e.push({id:c,cron:r,prompt:n,cwd:t,created:Date.now()}),i(e),{id:c}}export function removeTask(r){const n=o(),t=n.filter(n=>n.id!==r);return i(t),t.length<n.length}export function crontabLine(r,o="huicai"){return`${r.cron} cd ${JSON.stringify(r.cwd)} && ${o} -p ${JSON.stringify(r.prompt)} --yolo --plain >> ${JSON.stringify(n.join(t.homedir(),".huicai","schedule-"+r.id+".log"))} 2>&1`}
1
+ import n from"node:fs";import e from"node:path";import t from"node:os";import{freqToCron as r}from"./schedule-freq.js";export const SCHEDULE_FILE=e.join(t.homedir(),".huicai","schedule.json");function o(){try{const e=JSON.parse(n.readFileSync(SCHEDULE_FILE,"utf8"));return Array.isArray(e)?e:[]}catch{return[]}}function i(t){n.mkdirSync(e.dirname(SCHEDULE_FILE),{recursive:!0}),n.writeFileSync(SCHEDULE_FILE,JSON.stringify(t,null,2)+"\n",{mode:384})}function d(n){const e={...n};if(e.freq&&"object"==typeof e.freq){const n=r(e.freq);n&&(e.cron=n)}return void 0===e.enabled&&(e.enabled=!0),null==e.permission&&(e.permission="default"),null==e.model&&(e.model="auto"),e}export function listTasks(){return o()}export function getTask(n){return o().find(e=>e.id===n)||null}export function addTask(n){const e=o(),t=n&&n.id||Date.now().toString(36)+Math.random().toString(36).slice(2,4),r=Date.now(),u=d({...n,id:t,created:n&&n.created||r,updated:r});if(!u.cron&&!u.freq)throw new Error("缺少执行频率(freq)或 cron 表达式");return e.push(u),i(e),{id:t}}export function updateTask(n,e){const t=o(),r=t.findIndex(e=>e.id===n);return!(r<0)&&(t[r]=d({...t[r],...e,id:n,updated:Date.now()}),i(t),!0)}export function removeTask(n){const e=o(),t=e.filter(e=>e.id!==n);return i(t),t.length<e.length}export function setEnabled(n,e){return updateTask(n,{enabled:!!e})}export function touchRun(n,e=Date.now()){const t=o(),r=t.findIndex(e=>e.id===n);return!(r<0)&&(t[r].lastRunAt=e,i(t),!0)}export function crontabLine(n,r="huicai"){return`${n.cron} cd ${JSON.stringify(n.cwd)} && ${r} -p ${JSON.stringify(n.prompt)} --yolo --plain >> ${JSON.stringify(e.join(t.homedir(),".huicai","schedule-"+n.id+".log"))} 2>&1`}
package/src/session.js CHANGED
@@ -1 +1 @@
1
- import t from"node:fs";import e from"node:path";import{CONFIG_DIR as n}from"./config.js";export const SESSIONS_DIR=e.join(n,"sessions");function r(){t.mkdirSync(SESSIONS_DIR,{recursive:!0})}function o(t){return e.join(SESSIONS_DIR,t+".json")}function s(t){const e=(t||[]).find(t=>"user"===t.role);return e&&e.content&&String(e.content).replace(/\s+/g," ").trim().slice(0,60)||"(空会话)"}export function turnCount(t){return(t||[]).filter(t=>"user"===t.role).length}export function createSession({cwd:t,model:e,messages:n}){const r=Date.now();return{id:Date.now().toString(36)+"-"+Math.random().toString(36).slice(2,6),cwd:t,model:e,createdAt:r,updatedAt:r,title:"",messages:n||[],compaction:{}}}export function saveSession(e){if(e&&e.id)try{r(),e.updatedAt=Date.now(),e.title||(e.title=s(e.messages)),t.writeFileSync(o(e.id),JSON.stringify(e),{mode:384})}catch(t){process.stderr.write(`(会话保存失败,不影响对话:${t.message})\n`)}}export function loadSession(e){if(!e)return null;try{const n=JSON.parse(t.readFileSync(o(e),"utf8"));return n&&Array.isArray(n.messages)?(n.id||(n.id=e),n.compaction&&"object"==typeof n.compaction||(n.compaction={}),n):null}catch{return null}}export function listSessions(n){let o;try{r(),o=t.readdirSync(SESSIONS_DIR).filter(t=>t.endsWith(".json"))}catch{return[]}const i=[];for(const r of o)try{const o=JSON.parse(t.readFileSync(e.join(SESSIONS_DIR,r),"utf8"));if(n&&o.cwd!==n)continue;i.push({id:o.id||r.replace(/\.json$/,""),title:o.title||s(o.messages),cwd:o.cwd,model:o.model,updatedAt:o.updatedAt||0,turns:turnCount(o.messages),user:o.user?String(o.user):""})}catch{}return i.sort((t,e)=>e.updatedAt-t.updatedAt),i}export function mostRecent(t){const e=listSessions(t);return e.length?e[0].id:null}export function deleteSession(e){try{return t.unlinkSync(o(e)),!0}catch{return!1}}export function relTime(t){if(!t)return"未知时间";const e=Math.max(0,Math.floor((Date.now()-t)/1e3));if(e<60)return e+" 秒前";const n=Math.floor(e/60);if(n<60)return n+" 分钟前";const r=Math.floor(n/60);if(r<24)return r+" 小时前";return Math.floor(r/24)+" 天前"}
1
+ import t from"node:fs";import e from"node:path";import{CONFIG_DIR as n}from"./config.js";export const SESSIONS_DIR=e.join(n,"sessions");function r(){t.mkdirSync(SESSIONS_DIR,{recursive:!0})}function o(t){return e.join(SESSIONS_DIR,t+".json")}export function turnCount(t){return(t||[]).filter(t=>"user"===t.role).length}export function createSession({cwd:t,model:e,messages:n}){const r=Date.now();return{id:Date.now().toString(36)+"-"+Math.random().toString(36).slice(2,6),cwd:t,model:e,createdAt:r,updatedAt:r,title:"",messages:n||[],compaction:{}}}export function saveSession(e){if(e&&e.id)try{r(),e.updatedAt=Date.now(),t.writeFileSync(o(e.id),JSON.stringify(e),{mode:384})}catch(t){process.stderr.write(`(会话保存失败,不影响对话:${t.message})\n`)}}export function loadSession(e){if(!e)return null;try{const n=JSON.parse(t.readFileSync(o(e),"utf8"));return n&&Array.isArray(n.messages)?(n.id||(n.id=e),n.compaction&&"object"==typeof n.compaction||(n.compaction={}),n):null}catch{return null}}export function listSessions(n){let o;try{r(),o=t.readdirSync(SESSIONS_DIR).filter(t=>t.endsWith(".json"))}catch{return[]}const i=[];for(const r of o)try{const o=JSON.parse(t.readFileSync(e.join(SESSIONS_DIR,r),"utf8"));if(n&&o.cwd!==n)continue;i.push({id:o.id||r.replace(/\.json$/,""),title:o.title||"(未命名会话)",cwd:o.cwd,model:o.model,updatedAt:o.updatedAt||0,turns:turnCount(o.messages),user:o.user?String(o.user):""})}catch{}return i.sort((t,e)=>e.updatedAt-t.updatedAt),i}export function mostRecent(t){const e=listSessions(t);return e.length?e[0].id:null}export function deleteSession(e){try{return t.unlinkSync(o(e)),!0}catch{return!1}}export function relTime(t){if(!t)return"未知时间";const e=Math.max(0,Math.floor((Date.now()-t)/1e3));if(e<60)return e+" 秒前";const n=Math.floor(e/60);if(n<60)return n+" 分钟前";const r=Math.floor(n/60);if(r<24)return r+" 小时前";return Math.floor(r/24)+" 天前"}
package/src/skills.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import n from"node:path";import t from"node:os";import{fileURLToPath as r}from"node:url";export const BUNDLED_SKILLS_DIR=n.join(n.dirname(r(import.meta.url)),"..","skills");const i=new Set([".md",".markdown",".txt",".json",".yaml",".yml",".py",".js",".mjs",".cjs",".ts",".sh",".sql",".csv",".html",".css",".xml",".toml"]),o=262144;export function parseFrontmatter(e){const n={name:"",description:"",version:"",body:(e||"").trim()},t=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(n.body);if(!t)return n;n.body=n.body.slice(t[0].length).trim();for(const e of t[1].split(/\r?\n/)){const t=/^(name|description|version)\s*:\s*(.*)$/.exec(e.trim());t&&(n[t[1]]=t[2].trim().replace(/^["']|["']$/g,""))}return n}export function skillDirs(e){const r=[];return e&&r.push(n.join(e,".huicai","skills")),r.push(n.join(t.homedir(),".huicai","skills")),r}function s(t,r="",o=0,l=[]){if(o>3||l.length>=50)return l;let c;try{c=e.readdirSync(t,{withFileTypes:!0})}catch{return l}for(const e of c){if(l.length>=50)break;if(e.name.startsWith("."))continue;const c=r?`${r}/${e.name}`:e.name;e.isDirectory()?s(n.join(t,e.name),c,o+1,l):e.isFile()&&"SKILL.md"!==c&&i.has(n.extname(e.name).toLowerCase())&&l.push(c)}return l}export function loadLocalSkills(r){const i=new Map,o=[...r?[{dir:n.join(r,".huicai","skills"),scope:"project"}]:[],{dir:n.join(t.homedir(),".huicai","skills"),scope:"global"},{dir:BUNDLED_SKILLS_DIR,scope:"builtin"}];for(const{dir:t,scope:r}of o){let o;try{o=e.readdirSync(t,{withFileTypes:!0})}catch{continue}for(const l of o){if(!l.isDirectory())continue;const o=n.join(t,l.name);let c;try{c=e.readFileSync(n.join(o,"SKILL.md"),"utf8")}catch{continue}const a=parseFrontmatter(c),p=(a.name||l.name).trim();p&&!i.has(p)&&i.set(p,{name:p,description:a.description||"",version:a.version||"",dir:o,scope:r,files:s(o)})}}return[...i.values()]}export function readSkillBody(t,r){const i=loadLocalSkills(t).find(e=>e.name===r);if(!i)return null;try{const t=e.readFileSync(n.join(i.dir,"SKILL.md"),"utf8");return{skill:i,raw:t.length>65536?t.slice(0,65536)+"\n…(正文已截断)":t}}catch{return null}}export function readSkillFileContent(t,r,i){const s=loadLocalSkills(t).find(e=>e.name===r);if(!s)throw new Error(`本地技能不存在:${r}`);const l=String(i||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,""),c=n.resolve(s.dir,l);if(!c.startsWith(n.resolve(s.dir)+n.sep))throw new Error(`路径越界:${i}`);if(e.statSync(c).size>o)throw new Error(`附件过大(>256KB):${l}`);return e.readFileSync(c,"utf8")}export function skillCatalog(e){const n=loadLocalSkills(e);if(!n.length)return"";return"以下本地技能可用 load_skill(name) 载入其操作指引后遵循执行(描述含「何时使用」,据此判断是否载入;无关任务不要载入):\n"+n.map(e=>{const n=(e.description||"(无描述)").replace(/\s+/g," ").slice(0,300);return`- ${e.name}:${n}`}).join("\n")}export function makeSkillTools(e){const n=loadLocalSkills(e);if(!n.length)return[];const t=[{readOnly:!0,spec:{type:"function",function:{name:"load_skill",description:"载入一个本地技能的操作指引(系统提示词「可用技能」目录中的名称)。载入后严格遵循其流程执行。",parameters:{type:"object",properties:{name:{type:"string",description:"技能名(见「可用技能」目录)"}},required:["name"]}}},run:async n=>{const t=String(n?.name||"").trim(),r=readSkillBody(e,t);if(!r){return`本地技能不存在:${t}(可用:${loadLocalSkills(e).map(e=>e.name).join(", ")||"无"})`}let i=r.raw;return r.skill.files.length&&(i+=`\n\n---\n本技能附带 ${r.skill.files.length} 个资源文件,需要时用 read_skill_file(skill="${t}", path=...) 读取:\n`+r.skill.files.map(e=>`- ${e}`).join("\n")),i}}];return n.some(e=>e.files.length)&&t.push({readOnly:!0,spec:{type:"function",function:{name:"read_skill_file",description:"读取本地技能附带的资源文件(先 load_skill 查看其文件清单,再按 path 读取)。",parameters:{type:"object",properties:{skill:{type:"string",description:"技能名"},path:{type:"string",description:"资源文件相对路径(技能载入结果中列出)"}},required:["skill","path"]}}},run:async n=>{try{const t=readSkillFileContent(e,String(n?.skill||"").trim(),n?.path);return`文件 ${n?.path}:\n${t}`}catch(e){return`读取失败:${e.message}`}}}),t}export function writeSkillToDir(t,r){const i=String(r?.name||"").trim();if(!i||!r?.content)throw new Error("技能数据不完整(缺 name 或 content)");const o=n.join(t,i.replace(/[^a-zA-Z0-9_-]/g,"-"));e.mkdirSync(o,{recursive:!0}),e.writeFileSync(n.join(o,"SKILL.md"),r.content,"utf8");for(const t of r.files||[]){const r=String(t?.path||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,"");if(!r||r.includes(".."))continue;const i=n.join(o,r);e.mkdirSync(n.dirname(i),{recursive:!0}),e.writeFileSync(i,t.content??"","utf8")}return o}export function readSkillDirForPush(t){const r=n.join(t,"SKILL.md"),i=e.readFileSync(r,"utf8"),l=[];for(const r of s(t))try{const i=n.join(t,r);if(e.statSync(i).size>o)continue;l.push({path:r,content:e.readFileSync(i,"utf8")})}catch{}return{content:i,filename:n.basename(t),files:l}}
1
+ import e from"node:fs";import n from"node:path";import r from"node:os";import{fileURLToPath as t}from"node:url";export const BUNDLED_SKILLS_DIR=n.join(n.dirname(t(import.meta.url)),"..","skills");const i=new Set([".md",".markdown",".txt",".json",".yaml",".yml",".py",".js",".mjs",".cjs",".ts",".sh",".sql",".csv",".html",".css",".xml",".toml"]),o=262144;export const SKILLS_STATE_PATH=n.join(r.homedir(),".huicai","skills-state.json");export function loadDisabledSet(){try{const n=JSON.parse(e.readFileSync(SKILLS_STATE_PATH,"utf8"));return new Set(Array.isArray(n?.disabled)?n.disabled.map(String):[])}catch{return new Set}}function l(r){e.mkdirSync(n.dirname(SKILLS_STATE_PATH),{recursive:!0}),e.writeFileSync(SKILLS_STATE_PATH,JSON.stringify({disabled:[...r].sort()},null,2)+"\n",{mode:384})}export function setSkillEnabled(e,n){const r=String(e||"").trim();if(!r)throw new Error("技能名为空");const t=loadDisabledSet();return n?t.delete(r):t.add(r),l(t),[...t]}export function parseFrontmatter(e){const n={name:"",description:"",version:"",body:(e||"").trim()},r=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(n.body);if(!r)return n;n.body=n.body.slice(r[0].length).trim();for(const e of r[1].split(/\r?\n/)){const r=/^(name|description|version)\s*:\s*(.*)$/.exec(e.trim());r&&(n[r[1]]=r[2].trim().replace(/^["']|["']$/g,""))}return n}export function skillDirs(e){const t=[];return e&&t.push(n.join(e,".huicai","skills")),t.push(n.join(r.homedir(),".huicai","skills")),t}function s(r,t="",o=0,l=[]){if(o>3||l.length>=50)return l;let a;try{a=e.readdirSync(r,{withFileTypes:!0})}catch{return l}for(const e of a){if(l.length>=50)break;if(e.name.startsWith("."))continue;const a=t?`${t}/${e.name}`:e.name;e.isDirectory()?s(n.join(r,e.name),a,o+1,l):e.isFile()&&"SKILL.md"!==a&&i.has(n.extname(e.name).toLowerCase())&&l.push(a)}return l}export function loadLocalSkills(t){const i=new Map,o=loadDisabledSet(),l=[...t?[{dir:n.join(t,".huicai","skills"),scope:"project"}]:[],{dir:n.join(r.homedir(),".huicai","skills"),scope:"global"},{dir:BUNDLED_SKILLS_DIR,scope:"builtin"}];for(const{dir:r,scope:t}of l){let l;try{l=e.readdirSync(r,{withFileTypes:!0})}catch{continue}for(const a of l){if(!a.isDirectory())continue;const l=n.join(r,a.name);let c;try{c=e.readFileSync(n.join(l,"SKILL.md"),"utf8")}catch{continue}const d=parseFrontmatter(c),m=(d.name||a.name).trim();m&&!i.has(m)&&i.set(m,{name:m,description:d.description||"",version:d.version||"",dir:l,scope:t,files:s(l),enabled:!o.has(m)})}}return[...i.values()]}export function readSkillBody(r,t){const i=loadLocalSkills(r).find(e=>e.name===t);if(!i)return null;try{const r=e.readFileSync(n.join(i.dir,"SKILL.md"),"utf8");return{skill:i,raw:r.length>65536?r.slice(0,65536)+"\n…(正文已截断)":r}}catch{return null}}export function readSkillFileContent(r,t,i){const l=loadLocalSkills(r).find(e=>e.name===t);if(!l)throw new Error(`本地技能不存在:${t}`);const s=String(i||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,""),a=n.resolve(l.dir,s);if(!a.startsWith(n.resolve(l.dir)+n.sep))throw new Error(`路径越界:${i}`);if(e.statSync(a).size>o)throw new Error(`附件过大(>256KB):${s}`);return e.readFileSync(a,"utf8")}export function skillCatalog(e){const n=loadLocalSkills(e).filter(e=>!1!==e.enabled);if(!n.length)return"";return"以下本地技能可用 load_skill(name) 载入其操作指引后遵循执行(描述含「何时使用」,据此判断是否载入;无关任务不要载入):\n"+n.map(e=>{const n=(e.description||"(无描述)").replace(/\s+/g," ").slice(0,300);return`- ${e.name}:${n}`}).join("\n")}export function makeSkillTools(e){const n=loadLocalSkills(e).filter(e=>!1!==e.enabled);if(!n.length)return[];const r=[{readOnly:!0,spec:{type:"function",function:{name:"load_skill",description:"载入一个本地技能的操作指引(系统提示词「可用技能」目录中的名称)。载入后严格遵循其流程执行。",parameters:{type:"object",properties:{name:{type:"string",description:"技能名(见「可用技能」目录)"}},required:["name"]}}},run:async n=>{const r=String(n?.name||"").trim(),t=readSkillBody(e,r);if(!t){return`本地技能不存在:${r}(可用:${loadLocalSkills(e).filter(e=>!1!==e.enabled).map(e=>e.name).join(", ")||"无"})`}if(!1===t.skill.enabled)return`技能「${r}」已被禁用,无法载入(可在「技能·MCP」中重新启用)。`;let i=t.raw;return t.skill.files.length&&(i+=`\n\n---\n本技能附带 ${t.skill.files.length} 个资源文件,需要时用 read_skill_file(skill="${r}", path=...) 读取:\n`+t.skill.files.map(e=>`- ${e}`).join("\n")),i}}];return n.some(e=>e.files.length)&&r.push({readOnly:!0,spec:{type:"function",function:{name:"read_skill_file",description:"读取本地技能附带的资源文件(先 load_skill 查看其文件清单,再按 path 读取)。",parameters:{type:"object",properties:{skill:{type:"string",description:"技能名"},path:{type:"string",description:"资源文件相对路径(技能载入结果中列出)"}},required:["skill","path"]}}},run:async n=>{try{const r=readSkillFileContent(e,String(n?.skill||"").trim(),n?.path);return`文件 ${n?.path}:\n${r}`}catch(e){return`读取失败:${e.message}`}}}),r}export function writeSkillToDir(r,t){const i=String(t?.name||"").trim();if(!i||!t?.content)throw new Error("技能数据不完整(缺 name 或 content)");const o=n.join(r,i.replace(/[^a-zA-Z0-9_-]/g,"-"));e.mkdirSync(o,{recursive:!0}),e.writeFileSync(n.join(o,"SKILL.md"),t.content,"utf8");for(const r of t.files||[]){const t=String(r?.path||"").replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,"");if(!t||t.includes(".."))continue;const i=n.join(o,t);e.mkdirSync(n.dirname(i),{recursive:!0}),e.writeFileSync(i,r.content??"","utf8")}return o}export function isValidSkillName(e){return/^[a-zA-Z0-9_-]{1,48}$/.test(String(e||""))}function a(e,t){if("project"===e){if(!t)throw new Error("项目级技能需要在会话/项目内创建");return n.join(t,".huicai","skills")}return n.join(r.homedir(),".huicai","skills")}export function scaffoldSkillMarkdown(e,n,r){return`---\nname: ${e}\ndescription: ${String(n||"").replace(/\r?\n/g," ").trim()||"(补充:这个技能做什么、何时使用)"}\nversion: 1.0.0\n---\n\n`+(null!=r&&String(r).trim()?String(r).trim()+"\n":`# ${e}\n\n## 何时使用\n\n(描述触发场景,让模型据此判断是否 load_skill)\n\n## 流程\n\n1. \n2. \n3. \n\n## 注意事项\n\n- \n`)}export function createSkillPackage({name:r,description:t="",scope:i="global",cwd:o=null,body:l=""}={}){const s=String(r||"").trim();if(!isValidSkillName(s))throw new Error("技能名非法:仅限字母/数字/下划线/连字符,长度 1–48");if("project"!==i&&"global"!==i)throw new Error("只能创建到项目级或全局级");if(loadLocalSkills(o).some(e=>e.name===s))throw new Error(`技能已存在:${s}`);const c=a(i,o),d=n.join(c,s);return e.mkdirSync(d,{recursive:!0}),e.writeFileSync(n.join(d,"SKILL.md"),scaffoldSkillMarkdown(s,t,l),"utf8"),{dir:d,name:s,scope:i}}export function installSkillPackage({name:r,content:t,files:i=[],scope:o="global",cwd:l=null,conflict:s="skip"}={}){const c=String(r||"").trim();if(!isValidSkillName(c))throw new Error("技能名非法:仅限字母/数字/下划线/连字符,长度 1–48(来源 SKILL.md 的 frontmatter name)");if(!String(t||"").trim())throw new Error("SKILL.md 正文为空");if("project"!==o&&"global"!==o)throw new Error("只能导入到项目级或全局级");const d=(Array.isArray(i)?i:[]).filter(e=>e&&"string"==typeof e.path),m=loadLocalSkills(l).find(e=>e.name===c&&"builtin"!==e.scope);if(m){if("overwrite"!==s)return{name:c,status:"skipped",dir:m.dir};e.writeFileSync(n.join(m.dir,"SKILL.md"),String(t),"utf8");for(const r of d){const t=String(r.path).replace(/\\/g,"/").replace(/^\.\//,"").replace(/^\/+/,"");if(!t||t.includes(".."))continue;const i=n.join(m.dir,t);e.mkdirSync(n.dirname(i),{recursive:!0}),e.writeFileSync(i,r.content??"","utf8")}return{name:c,status:"overwritten",dir:m.dir}}const f=writeSkillToDir(a(o,l),{name:c,content:t,files:d});return{name:c,status:"imported",dir:f}}export function overwriteSkillBody(r,t,i){const o=loadLocalSkills(r).find(e=>e.name===String(t||"").trim());if(!o)throw new Error("本地技能不存在:"+t);if("builtin"===o.scope)throw new Error("内置技能只读,请先「复制到全局」再编辑");if(!String(i||"").trim())throw new Error("SKILL.md 正文不能为空");return e.writeFileSync(n.join(o.dir,"SKILL.md"),String(i),"utf8"),{dir:o.dir,name:o.name}}export function removeSkillPackage(r,t){const i=loadLocalSkills(r).find(e=>e.name===String(t||"").trim());if(!i)throw new Error("本地技能不存在:"+t);if("builtin"===i.scope)throw new Error("内置技能不可卸载(可在「技能·MCP」中禁用)");const o=n.resolve(a(i.scope,r)),s=n.resolve(i.dir);if(s!==o&&!s.startsWith(o+n.sep))throw new Error("拒绝删除技能库之外的目录");e.rmSync(s,{recursive:!0,force:!0});const c=loadDisabledSet();return c.delete(i.name)&&l(c),{name:i.name,scope:i.scope}}export function forkSkillToWritable(e,n,r="global"){const t=loadLocalSkills(e).find(e=>e.name===String(n||"").trim());if(!t)throw new Error("本地技能不存在:"+n);if("project"!==r&&"global"!==r)throw new Error("只能复制到项目级或全局级");const i=new Set(loadLocalSkills(e).map(e=>e.name));let o=t.name;for(;i.has(o);)o=(o+"-copy").slice(0,48);const l=readSkillDirForPush(t.dir),s=l.content.replace(/^(---[\s\S]*?\nname:\s*).*$/m,`$1${o}`);return{dir:writeSkillToDir(a(r,e),{name:o,content:s,files:l.files}),name:o,scope:r}}export function readSkillDirForPush(r){const t=n.join(r,"SKILL.md"),i=e.readFileSync(t,"utf8"),l=[];for(const t of s(r))try{const i=n.join(r,t);if(e.statSync(i).size>o)continue;l.push({path:t,content:e.readFileSync(i,"utf8")})}catch{}return{content:i,filename:n.basename(r),files:l}}
package/src/squad.js CHANGED
@@ -1 +1 @@
1
- import e from"node:fs";import t from"node:path";import n from"node:os";import{fileURLToPath as r}from"node:url";import{runTurn as s}from"./agent.js";import{createPermissions as o}from"./permissions.js";import{loadAgents as i}from"./subagent.js";import{streamChat as a}from"./api.js";import{discoverExperts as c}from"./experts.js";export const BUNDLED_SQUADS_DIR=t.join(t.dirname(r(import.meta.url)),"..","squads");export function squadDirs(e){const r=[];return e&&r.push(t.join(e,".huicai","squads")),r.push(t.join(n.homedir(),".huicai","squads")),r}export function parseSquadDoc(e,t=""){const{attrs:n,body:r}=function(e){const t={attrs:{},body:String(e||"").trim()},n=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(t.body);if(!n)return t;t.body=t.body.slice(n[0].length).trim();for(const e of n[1].split(/\r?\n/)){const n=/^([A-Za-z][\w-]*)\s*:\s*(.*)$/.exec(e.trim());n&&(t.attrs[n[1]]=n[2].trim().replace(/^["']|["']$/g,""))}return t}(e),s={name:n.name||t,description:n.description||"",leader:n.leader||"主理人",mode:["dispatch","review","ensemble"].includes(n.mode)?n.mode:"dispatch",when:n.when||"",mission:"",members:[],precheck:[],workflows:[]},o=new Map,i=r.split(/^##\s+/m);s.mission=(i[0]||"").trim();for(const e of i.slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1);if(/^成员人设/.test(n))for(const e of r.split(/^###\s+/m).slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1).trim();n&&r&&o.set(n,r)}else if(/^成员/.test(n))for(const e of r.split(/\r?\n/)){const t=/^\s*[-*]\s+(.+)$/.exec(e);if(!t)continue;const n=t[1].split("|").map(e=>e.trim());n[0]&&s.members.push({id:n[0],callName:n[1]||n[0],duty:n[2]||"",executor:n[3]||""})}else if(/^预检/.test(n))for(const e of r.split(/\r?\n/)){const t=/^\s*[-*]\s+(.+)$/.exec(e);t&&s.precheck.push(t[1].trim())}else if(/^工作流/.test(n))for(const e of r.split(/^###\s+/m).slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1),o=/^(.*?)(?:(触发[::](.*?)))?$/.exec(n)||[],i={name:(o[1]||n).trim(),trigger:(o[2]||"").trim(),stages:[],deliverable:""};for(const e of r.split(/\r?\n/)){const t=/^\s*\d+[.、)]\s*(.+)$/.exec(e);if(t){i.stages.push(t[1].trim());continue}const n=/^\s*交付[::]\s*(.+)$/.exec(e);n&&(i.deliverable=n[1].trim())}i.name&&s.workflows.push(i)}}for(const[e,t]of o){const n=s.members.find(t=>t.id===e);n&&(n.persona=t)}return s}export function loadSquads(n){const r=new Map,s=[...squadDirs(n).map((e,t)=>({dir:e,scope:n&&0===t?"project":"global"})),{dir:BUNDLED_SQUADS_DIR,scope:"builtin"}];for(const{dir:n,scope:o}of s){let s;try{s=e.readdirSync(n).filter(e=>e.endsWith(".md"))}catch{continue}for(const i of s){const s=i.replace(/\.md$/,"").trim();if(s)try{const a=t.join(n,i),c=parseSquadDoc(e.readFileSync(a,"utf8"),s);c.scope=o,c.file=a,r.has(c.name)||r.set(c.name,c)}catch{}}}return[...r.values()]}export function ensureTeamDir(n,r){const s=(new Date).toISOString().slice(0,16).replace(/[-:]/g,"").replace("T","-"),o=t.join(n,".huicai","teams",`${r}-${s}`);try{e.mkdirSync(o,{recursive:!0})}catch{return null}return o}export function buildLeaderPrompt(e,t){const n=[];n.push("**Role Override**:以下专家团主理人定义优先于此前任何身份设定,是你在本任务中唯一、权威的角色。"),n.push(""),n.push(`# 专家团「${e.name}」· 主理人${e.leader}`),n.push(`你是专家团「${e.name}」的主理人${e.leader},负责把用户任务拆解、派发给团队成员并汇总交付。`),e.mission&&n.push("",e.mission),n.push("","## 关键规则:只调度,不代工","- 成员职责范围内的专业产出必须经 dispatch_member 由对应成员完成后采信,你不代写;","- 简单澄清/寒暄/对既有产出的说明可直接回答,无须派发。"),n.push("","## 团队架构(调度依据)","| 成员ID | 称呼 | 职责 | 执行者 |","|---|---|---|---|");for(const t of e.members)n.push(`| ${t.id} | ${t.callName} | ${t.duty} | ${t.executor||"(当前模型)"} |`);if(n.push("","## 任务分配预检(每次派发前必做)","成员不会说「这个我不会」——超能力范围的任务结果无保证。所以:","1. 解析用户意图(研究/分析/生成/编辑/写作/审校…);","2. 对照职责与速查表做能力匹配;","3. 匹配 → 派发;不匹配 → 告知用户团队能力限制与替代建议,禁止硬派;","4. 意图模糊 → 先向用户确认。"),e.precheck.length){n.push("### 速查表(常见误派提醒)");for(const t of e.precheck)n.push(`- ${t}`)}if(e.workflows.length){n.push("","## 预设工作流(识别到触发条件时按阶段执行,不自由发挥)");for(const t of e.workflows)n.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),t.stages.forEach((e,t)=>n.push(`${t+1}. ${e}`)),t.deliverable&&n.push(`交付:${t.deliverable}`)}return n.push("","## 团队协作规范","1. 成员在隔离上下文执行,看不到本对话与其他成员产出——任务说明必须独立自足;","2. 跨成员信息流经你中转:把上一阶段产出的必要部分放进下一位成员的 context;","3. 互相独立的任务同一轮并行派发(一次输出多个 dispatch_member 调用),减少串行等待;","4. 成员结论为准:如实汇编;产出不符时修正任务说明重派,而非替他改写。"),"review"===e.mode?n.push("### 生成-审查模式(本团约束)","每个关键产出完成后,必须把「产出全文+原始要求」派给职责匹配的审查成员评审,按意见让原成员修订至通过;未经审查的产出不得作为最终交付。"):"ensemble"===e.mode&&n.push("### 多候选聚合模式(本团约束)","对开放性核心任务,把同一任务并行派给多位匹配成员(或不同侧重多份),对比候选择优或综合,并向用户说明取舍。"),n.push("","## 异常处理(失败三级升级)","- 一级:修正任务说明(更自足、更具体、补 context)后重派原成员;同一任务累计失败满 2 次系统会终止该任务线;","- 二级:换人或换执行方式——改派职责相近的其他成员;平台资源不可用时由本机成员降级承接(系统会自动降级并注明);","- 三级:降级交付——完成可完成的部分,如实说明缺口与原因,并给出用户可选的替代方案。"),t&&n.push("","## 团队工作区",`本次协作的产物统一放在 ${t}(你可用文件工具写入;成员只回传文本结论,由你负责归档与整合)。`),n.push("","## 交付检查清单(最终答复前逐项自查)","- 用户目标的每一部分是否都有对应成员产出支撑?","- 是否汇总了全部关键产出、无散落遗漏?","- 是否标注了各部分由哪位成员产出?","- 未完成的部分是否如实说明?"),n.join("\n")}const m=/^(agent|workflow|datahuman|squad)-/;async function p({squad:e,member:t,ref:n,task:r,context:s,expectation:o,config:i}){const c=[`【专家团「${e.name}」成员任务】${r}`,s?`\n【上游产出/背景】\n${s}`:"",o?`\n【交付要求】\n${o}`:""].join("");return(await a({baseUrl:i.baseUrl,apiKey:i.apiKey,model:n,messages:[{role:"user",content:c}],client:i.client,sessionId:i.sessionId},{})).content||"(平台无返回)"}async function l({squad:e,member:t,task:n,context:r,expectation:a,config:l,cwd:u,readOnlyExtras:d}){const f=String(t.executor||"").trim();let h="";if(f.startsWith("platform:auto:")){const s=await async function(e,t){const n=/^platform:auto:(agent|workflow|datahuman|squad)(?::(.+))?$/.exec(e);if(!n)return null;const[,r,s]=n;try{const e=await c(t),n=(s||"").trim().toLowerCase();return e.find(e=>e.type===r&&(!n||(String(e.name)+" "+String(e.description)).toLowerCase().includes(n)))||null}catch{return null}}(f,l);if(s){const o=await p({squad:e,member:t,ref:s.id,task:n,context:r,expectation:a,config:l});return`(由平台资源「${s.name}」执行)\n${o}`}h=`(未发现可用的平台${f.split(":")[2]||""}资源,已由本机成员降级执行)\n`}else if(f.startsWith("platform:")){const s=f.slice(9).trim();return m.test(s)?p({squad:e,member:t,ref:s,task:n,context:r,expectation:a,config:l}):`成员「${t.callName}」的执行者 ${s} 不是有效的平台伪模型引用`}const $=["**Role Override**:以下角色定义在本次任务中拥有最高优先级。",""];t.persona&&$.push(t.persona,""),$.push(`你是专家团「${e.name}」的成员「${t.callName}」${t.duty?`(职责:${t.duty})`:""},正在执行主理人下发的一项独立任务。`,"- 只完成本任务,直接输出可被主理人直接采用的成果本身,不要寒暄、不要复述任务;","- 无法反问,信息不全时按合理假设执行并注明假设。"),r&&$.push("","## 主理人提供的上游产出/背景",String(r)),a&&$.push("","## 交付要求",String(a));let g=$.join("\n"),w=l;if(f.startsWith("agent:")){const e=f.slice(6).trim(),t=i(u).find(t=>t.name===e);t&&(g=t.prompt.trim()+"\n\n"+g)}else if(f.startsWith("model:")){const e=f.slice(6).trim();e&&(w={...l,model:e})}g+="\n工作目录:"+u;const x=[{role:"system",content:g},{role:"user",content:n}],b=await s({config:w,messages:x,permissions:o({yolo:!1,ask:null}),cwd:u,extraTools:d,depth:1,maxIters:50,io:{write:()=>{}}});if(b?.error)return`成员执行失败:${b.error}`;const y=[...x].reverse().find(e=>"assistant"===e.role&&e.content);return y?h+y.content:"(成员未产出结论)"}export function makeDispatchMemberTool({config:e,cwd:t,extraTools:n=[]}){const r=loadSquads(t);if(!r.length)return null;const s=n.filter(e=>e.readOnly),o=new Map;return{readOnly:!0,spec:{type:"function",function:{name:"dispatch_member",description:"把一项独立、自足的任务派发给某个专家团的一位成员:成员在隔离上下文中执行并只回传成果。复杂/跨角色任务优先按团开工:对照下方各团「何时使用」自主选团,按其预设工作流分阶段派发(互相独立的任务同一轮多次调用并行);成员产出由你汇总并用本机工具落地,落地后验证。派发前先做任务分配预检(对照成员职责做能力匹配)。\n可用专家团:\n"+r.map(e=>`- ${e.name}:${e.when||e.description||""}(成员 ${e.members.map(e=>e.id).join("/")})`).join("\n"),parameters:{type:"object",properties:{squad:{type:"string",enum:r.map(e=>e.name),description:"专家团名称"},member:{type:"string",description:"成员ID(推荐)或称呼,须来自该团的团队架构表"},task:{type:"string",description:"下发的任务说明——必须独立自足(成员看不到本对话与其他成员产出)"},context:{type:"string",description:"可选:上游产出/背景(跨成员信息流经主理人中转,把必要部分放这里)"},expectation:{type:"string",description:"可选:交付要求/验收标准"}},required:["squad","member","task"]}}},run:async n=>{const i=r.find(e=>e.name===String(n?.squad||"").trim());if(!i)return`未找到专家团「${n?.squad}」(可用:${r.map(e=>e.name).join("、")})`;const a=function(e,t){const n=String(t||"").trim();return e.members.find(e=>e.id===n)||e.members.find(e=>e.callName===n)||null}(i,n?.member);if(!a)return`专家团「${i.name}」中没有成员「${n?.member}」(可用:${i.members.map(e=>`${e.id}(${e.callName})`).join("、")})`;const c=String(n?.task||"").trim();if(!c)return"缺少 task(任务说明)";const m=`${i.name}|${a.id}|${c.slice(0,40)}`,p=o.get(m)||0;if(p>=2)return`该任务线已按异常协议终止(成员「${a.callName}」同一任务累计失败满 2 次)。请向用户说明原因并给出替代方案。`;try{const r=await l({squad:i,member:a,task:c,context:n?.context,expectation:n?.expectation,config:e,cwd:t,readOnlyExtras:s});return/^(成员执行失败|平台调用失败)/.test(r)?(o.set(m,p+1),`${r}(累计失败 ${p+1}/2,可修正任务说明后重试或改派他人)`):`成员「${a.callName}」对任务【${c.replace(/\s+/g," ").slice(0,60)}】的产出:\n${r}`}catch(e){if(e&&"AbortError"===e.name)throw e;return o.set(m,p+1),`成员「${a.callName}」执行失败:${e.message}(累计失败 ${p+1}/2)`}}}}export function squadCommandPrompt(e,t,n){return buildLeaderPrompt(e,n)+"\n\n---\n\n用户任务:"+t+`\n\n请以主理人${e.leader}的身份完成:先做任务分配预检(含预设工作流匹配),然后经 dispatch_member`+`(squad="${e.name}")派发执行(独立任务同轮并行),跨成员信息流经你中转,最后按交付检查清单汇总交付。`}export const SQUAD_TEMPLATE="---\nname: {NAME}\ndescription: 内容创作专家团(示例:改成你的团队)\nleader: 司远\nmode: dispatch\nwhen: 需要成套内容创作/深度文章且涉及调研-写作-审校多角色协作时\n---\n\n擅长把用户的创作需求拆解为具体执行方案,并调度合适的成员高效交付。\n\n## 成员\n- researcher | 调研员 | 资料收集、事实核查、竞品调研 | agent:researcher\n- writer | 笔澜 | 公众号长文、营销文案、脚本 |\n- reviewer | 审校 | 审查产出、给出修订意见 |\n- fetcher | 源泉 | (示例)平台数字人自动绑定查数 | platform:auto:datahuman\n\n## 成员人设\n### writer\n角色:资深中文内容主笔。\n工作方法:先定读者与核心论点,再列结构,后成文;每段只讲一件事;用事实与例子代替空话。\n交付标准:结构清晰可扫读、观点有依据、结尾给行动建议;附 3 条备选标题。\n\n## 预检\n- 写长文/文案 → writer,不要派给 researcher\n- 资料/数据收集 → researcher,产出交给 writer 前先经你提炼要点\n\n## 工作流\n### 深度长文(触发:用户要深度文章/长文)\n1. researcher:收集资料与事实\n2. writer:按资料成稿\n3. reviewer:审查并给修订意见(mode=review 时必经)\n交付:终稿 Markdown + 三条标题备选\n";
1
+ import e from"node:fs";import t from"node:path";import n from"node:os";import{fileURLToPath as r}from"node:url";import{runTurn as s}from"./agent.js";import{createPermissions as o}from"./permissions.js";import{loadAgents as i,makeSubEvents as a,withSubagentSlot as c}from"./subagent.js";import{streamChat as m}from"./api.js";import{discoverExperts as l}from"./experts.js";export const BUNDLED_SQUADS_DIR=t.join(t.dirname(r(import.meta.url)),"..","squads");export function squadDirs(e){const r=[];return e&&r.push(t.join(e,".huicai","squads")),r.push(t.join(n.homedir(),".huicai","squads")),r}export function parseSquadDoc(e,t=""){const{attrs:n,body:r}=function(e){const t={attrs:{},body:String(e||"").trim()},n=/^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(t.body);if(!n)return t;t.body=t.body.slice(n[0].length).trim();for(const e of n[1].split(/\r?\n/)){const n=/^([A-Za-z][\w-]*)\s*:\s*(.*)$/.exec(e.trim());n&&(t.attrs[n[1]]=n[2].trim().replace(/^["']|["']$/g,""))}return t}(e),s={name:n.name||t,description:n.description||"",leader:n.leader||"主理人",mode:["dispatch","review","ensemble"].includes(n.mode)?n.mode:"dispatch",when:n.when||"",mission:"",members:[],precheck:[],workflows:[]},o=new Map,i=r.split(/^##\s+/m);s.mission=(i[0]||"").trim();for(const e of i.slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1);if(/^成员人设/.test(n))for(const e of r.split(/^###\s+/m).slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1).trim();n&&r&&o.set(n,r)}else if(/^成员/.test(n))for(const e of r.split(/\r?\n/)){const t=/^\s*[-*]\s+(.+)$/.exec(e);if(!t)continue;const n=t[1].split("|").map(e=>e.trim());n[0]&&s.members.push({id:n[0],callName:n[1]||n[0],duty:n[2]||"",executor:n[3]||""})}else if(/^预检/.test(n))for(const e of r.split(/\r?\n/)){const t=/^\s*[-*]\s+(.+)$/.exec(e);t&&s.precheck.push(t[1].trim())}else if(/^工作流/.test(n))for(const e of r.split(/^###\s+/m).slice(1)){const t=e.indexOf("\n"),n=(t<0?e:e.slice(0,t)).trim(),r=t<0?"":e.slice(t+1),o=/^(.*?)(?:(触发[::](.*?)))?$/.exec(n)||[],i={name:(o[1]||n).trim(),trigger:(o[2]||"").trim(),stages:[],deliverable:""};for(const e of r.split(/\r?\n/)){const t=/^\s*\d+[.、)]\s*(.+)$/.exec(e);if(t){i.stages.push(t[1].trim());continue}const n=/^\s*交付[::]\s*(.+)$/.exec(e);n&&(i.deliverable=n[1].trim())}i.name&&s.workflows.push(i)}}for(const[e,t]of o){const n=s.members.find(t=>t.id===e);n&&(n.persona=t)}return s}export function loadSquads(n){const r=new Map,s=[...squadDirs(n).map((e,t)=>({dir:e,scope:n&&0===t?"project":"global"})),{dir:BUNDLED_SQUADS_DIR,scope:"builtin"}];for(const{dir:n,scope:o}of s){let s;try{s=e.readdirSync(n).filter(e=>e.endsWith(".md"))}catch{continue}for(const i of s){const s=i.replace(/\.md$/,"").trim();if(s)try{const a=t.join(n,i),c=parseSquadDoc(e.readFileSync(a,"utf8"),s);c.scope=o,c.file=a,r.has(c.name)||r.set(c.name,c)}catch{}}}return[...r.values()]}export function ensureTeamDir(n,r){const s=(new Date).toISOString().slice(0,16).replace(/[-:]/g,"").replace("T","-"),o=t.join(n,".huicai","teams",`${r}-${s}`);try{e.mkdirSync(o,{recursive:!0})}catch{return null}return o}export function buildLeaderPrompt(e,t){const n=[];n.push("**Role Override**:以下专家团主理人定义优先于此前任何身份设定,是你在本任务中唯一、权威的角色。"),n.push(""),n.push(`# 专家团「${e.name}」· 主理人${e.leader}`),n.push(`你是专家团「${e.name}」的主理人${e.leader},负责把用户任务拆解、派发给团队成员并汇总交付。`),e.mission&&n.push("",e.mission),n.push("","## 关键规则:只调度,不代工","- 成员职责范围内的专业产出必须经 dispatch_member 由对应成员完成后采信,你不代写;","- 简单澄清/寒暄/对既有产出的说明可直接回答,无须派发。"),n.push("","## 团队架构(调度依据)","| 成员ID | 称呼 | 职责 | 执行者 |","|---|---|---|---|");for(const t of e.members)n.push(`| ${t.id} | ${t.callName} | ${t.duty} | ${t.executor||"(当前模型)"} |`);if(n.push("","## 任务分配预检(每次派发前必做)","成员不会说「这个我不会」——超能力范围的任务结果无保证。所以:","1. 解析用户意图(研究/分析/生成/编辑/写作/审校…);","2. 对照职责与速查表做能力匹配;","3. 匹配 → 派发;不匹配 → 告知用户团队能力限制与替代建议,禁止硬派;","4. 意图模糊 → 先向用户确认。"),e.precheck.length){n.push("### 速查表(常见误派提醒)");for(const t of e.precheck)n.push(`- ${t}`)}if(e.workflows.length){n.push("","## 预设工作流(识别到触发条件时按阶段执行,不自由发挥)");for(const t of e.workflows)n.push(`### ${t.name}${t.trigger?`(触发:${t.trigger})`:""}`),t.stages.forEach((e,t)=>n.push(`${t+1}. ${e}`)),t.deliverable&&n.push(`交付:${t.deliverable}`)}return n.push("","## 团队协作规范","1. 成员在隔离上下文执行,看不到本对话与其他成员产出——任务说明必须独立自足;","2. 跨成员信息流经你中转:把上一阶段产出的必要部分放进下一位成员的 context;","3. 互相独立的任务同一轮并行派发(一次输出多个 dispatch_member 调用),减少串行等待;","4. 成员结论为准:如实汇编;产出不符时修正任务说明重派,而非替他改写。"),"review"===e.mode?n.push("### 生成-审查模式(本团约束)","每个关键产出完成后,必须把「产出全文+原始要求」派给职责匹配的审查成员评审,按意见让原成员修订至通过;未经审查的产出不得作为最终交付。"):"ensemble"===e.mode&&n.push("### 多候选聚合模式(本团约束)","对开放性核心任务,把同一任务并行派给多位匹配成员(或不同侧重多份),对比候选择优或综合,并向用户说明取舍。"),n.push("","## 异常处理(失败三级升级)","- 一级:修正任务说明(更自足、更具体、补 context)后重派原成员;同一任务累计失败满 2 次系统会终止该任务线;","- 二级:换人或换执行方式——改派职责相近的其他成员;平台资源不可用时由本机成员降级承接(系统会自动降级并注明);","- 三级:降级交付——完成可完成的部分,如实说明缺口与原因,并给出用户可选的替代方案。"),t&&n.push("","## 团队工作区",`本次协作的产物统一放在 ${t}(你可用文件工具写入;成员只回传文本结论,由你负责归档与整合)。`),n.push("","## 交付检查清单(最终答复前逐项自查)","- 用户目标的每一部分是否都有对应成员产出支撑?","- 是否汇总了全部关键产出、无散落遗漏?","- 是否标注了各部分由哪位成员产出?","- 未完成的部分是否如实说明?"),n.join("\n")}const p=/^(agent|workflow|datahuman|squad)-/;async function u({squad:e,member:t,ref:n,task:r,context:s,expectation:o,config:i}){const a=[`【专家团「${e.name}」成员任务】${r}`,s?`\n【上游产出/背景】\n${s}`:"",o?`\n【交付要求】\n${o}`:""].join("");return(await m({baseUrl:i.baseUrl,apiKey:i.apiKey,model:n,messages:[{role:"user",content:a}],client:i.client,sessionId:i.sessionId},{})).content||"(平台无返回)"}async function d({squad:e,member:t,task:n,context:r,expectation:m,config:d,cwd:f,readOnlyExtras:h,onSub:$,label:g}){const b=String(t.executor||"").trim();let w="";if(b.startsWith("platform:auto:")){const s=await async function(e,t){const n=/^platform:auto:(agent|workflow|datahuman|squad)(?::(.+))?$/.exec(e);if(!n)return null;const[,r,s]=n;try{const e=await l(t),n=(s||"").trim().toLowerCase();return e.find(e=>e.type===r&&(!n||(String(e.name)+" "+String(e.description)).toLowerCase().includes(n)))||null}catch{return null}}(b,d);if(s){const o=await u({squad:e,member:t,ref:s.id,task:n,context:r,expectation:m,config:d});return`(由平台资源「${s.name}」执行)\n${o}`}w=`(未发现可用的平台${b.split(":")[2]||""}资源,已由本机成员降级执行)\n`}else if(b.startsWith("platform:")){const s=b.slice(9).trim();return p.test(s)?u({squad:e,member:t,ref:s,task:n,context:r,expectation:m,config:d}):`成员「${t.callName}」的执行者 ${s} 不是有效的平台伪模型引用`}const x=["**Role Override**:以下角色定义在本次任务中拥有最高优先级。",""];t.persona&&x.push(t.persona,""),x.push(`你是专家团「${e.name}」的成员「${t.callName}」${t.duty?`(职责:${t.duty})`:""},正在执行主理人下发的一项独立任务。`,"- 只完成本任务,直接输出可被主理人直接采用的成果本身,不要寒暄、不要复述任务;","- 无法反问,信息不全时按合理假设执行并注明假设。"),r&&x.push("","## 主理人提供的上游产出/背景",String(r)),m&&x.push("","## 交付要求",String(m));let y=x.join("\n"),S=d;if(b.startsWith("agent:")){const e=b.slice(6).trim(),t=i(f).find(t=>t.name===e);t&&(y=t.prompt.trim()+"\n\n"+y)}else if(b.startsWith("model:")){const e=b.slice(6).trim();e&&(S={...d,model:e})}y+="\n工作目录:"+f;const k=[{role:"system",content:y},{role:"user",content:n}],q=await c(d,0,()=>s({config:{...S,aux:!0},messages:k,permissions:o({yolo:!1,ask:null}),cwd:f,extraTools:h,depth:1,maxIters:50,io:{write:()=>{}},events:a($,g||`${t.callName}·${String(n).slice(0,40)}`,"member")}));if(q?.error)return`成员执行失败:${q.error}`;const j=[...k].reverse().find(e=>"assistant"===e.role&&e.content);return j?w+j.content:"(成员未产出结论)"}export function makeDispatchMemberTool({config:e,cwd:t,extraTools:n=[],onSub:r=null}){const s=loadSquads(t);if(!s.length)return null;const o=n.filter(e=>e.readOnly),i=new Map;return{readOnly:!0,spec:{type:"function",function:{name:"dispatch_member",description:"把一项独立、自足的任务派发给某个专家团的一位成员:成员在隔离上下文中执行并只回传成果。复杂/跨角色任务优先按团开工:对照下方各团「何时使用」自主选团,按其预设工作流分阶段派发(互相独立的任务同一轮多次调用并行);成员产出由你汇总并用本机工具落地,落地后验证。派发前先做任务分配预检(对照成员职责做能力匹配)。\n可用专家团:\n"+s.map(e=>`- ${e.name}:${e.when||e.description||""}(成员 ${e.members.map(e=>e.id).join("/")})`).join("\n"),parameters:{type:"object",properties:{squad:{type:"string",enum:s.map(e=>e.name),description:"专家团名称"},member:{type:"string",description:"成员ID(推荐)或称呼,须来自该团的团队架构表"},task:{type:"string",description:"下发的任务说明——必须独立自足(成员看不到本对话与其他成员产出)"},context:{type:"string",description:"可选:上游产出/背景(跨成员信息流经主理人中转,把必要部分放这里)"},expectation:{type:"string",description:"可选:交付要求/验收标准"}},required:["squad","member","task"]}}},run:async n=>{const a=s.find(e=>e.name===String(n?.squad||"").trim());if(!a)return`未找到专家团「${n?.squad}」(可用:${s.map(e=>e.name).join("、")})`;const c=function(e,t){const n=String(t||"").trim();return e.members.find(e=>e.id===n)||e.members.find(e=>e.callName===n)||null}(a,n?.member);if(!c)return`专家团「${a.name}」中没有成员「${n?.member}」(可用:${a.members.map(e=>`${e.id}(${e.callName})`).join("、")})`;const m=String(n?.task||"").trim();if(!m)return"缺少 task(任务说明)";const l=`${a.name}|${c.id}|${m.slice(0,40)}`,p=i.get(l)||0;if(p>=2)return`该任务线已按异常协议终止(成员「${c.callName}」同一任务累计失败满 2 次)。请向用户说明原因并给出替代方案。`;try{const s=await d({squad:a,member:c,task:m,context:n?.context,expectation:n?.expectation,config:e,cwd:t,readOnlyExtras:o,onSub:r,label:`${c.callName}·${m.replace(/\s+/g," ").slice(0,40)}`});return/^(成员执行失败|平台调用失败)/.test(s)?(i.set(l,p+1),`${s}(累计失败 ${p+1}/2,可修正任务说明后重试或改派他人)`):`成员「${c.callName}」对任务【${m.replace(/\s+/g," ").slice(0,60)}】的产出:\n${s}`}catch(e){if(e&&"AbortError"===e.name)throw e;return i.set(l,p+1),`成员「${c.callName}」执行失败:${e.message}(累计失败 ${p+1}/2)`}}}}export function squadCommandPrompt(e,t,n){return buildLeaderPrompt(e,n)+"\n\n---\n\n用户任务:"+t+`\n\n请以主理人${e.leader}的身份完成:先做任务分配预检(含预设工作流匹配),然后经 dispatch_member`+`(squad="${e.name}")派发执行(独立任务同轮并行),跨成员信息流经你中转,最后按交付检查清单汇总交付。`}export const SQUAD_TEMPLATE="---\nname: {NAME}\ndescription: 内容创作专家团(示例:改成你的团队)\nleader: 司远\nmode: dispatch\nwhen: 需要成套内容创作/深度文章且涉及调研-写作-审校多角色协作时\n---\n\n擅长把用户的创作需求拆解为具体执行方案,并调度合适的成员高效交付。\n\n## 成员\n- researcher | 调研员 | 资料收集、事实核查、竞品调研 | agent:researcher\n- writer | 笔澜 | 公众号长文、营销文案、脚本 |\n- reviewer | 审校 | 审查产出、给出修订意见 |\n- fetcher | 源泉 | (示例)平台数字人自动绑定查数 | platform:auto:datahuman\n\n## 成员人设\n### writer\n角色:资深中文内容主笔。\n工作方法:先定读者与核心论点,再列结构,后成文;每段只讲一件事;用事实与例子代替空话。\n交付标准:结构清晰可扫读、观点有依据、结尾给行动建议;附 3 条备选标题。\n\n## 预检\n- 写长文/文案 → writer,不要派给 researcher\n- 资料/数据收集 → researcher,产出交给 writer 前先经你提炼要点\n\n## 工作流\n### 深度长文(触发:用户要深度文章/长文)\n1. researcher:收集资料与事实\n2. writer:按资料成稿\n3. reviewer:审查并给修订意见(mode=review 时必经)\n交付:终稿 Markdown + 三条标题备选\n";
package/src/subagent.js CHANGED
@@ -1 +1 @@
1
- import t from"node:fs";import e from"node:path";import n from"node:os";import{runTurn as r}from"./agent.js";import{createPermissions as o}from"./permissions.js";import{parseFrontmatter as i}from"./skills.js";export function loadAgents(r){const o=[r?e.join(r,".huicai","agents"):null,e.join(n.homedir(),".huicai","agents")].filter(Boolean),a=new Map;for(const n of o){let r;try{r=t.readdirSync(n).filter(t=>t.endsWith(".md"))}catch{continue}for(const o of r){const r=o.replace(/\.md$/,"").trim();if(r&&!a.has(r))try{const s=t.readFileSync(e.join(n,o),"utf8"),c=i(s);a.set(r,{name:(c.name||r).trim(),description:c.description||"",prompt:c.body||s})}catch{}}}return[...a.values()]}async function a({config:t,cwd:e,readOnlyExtras:n,depth:i,objective:a,context:s,agentDef:c}){if(!a)return"未提供子任务目标 objective";const p=c?c.prompt.trim()+"\n\n工作目录:"+e+(s&&String(s).trim()?"\n\n## 背景\n"+s:""):function(t,e){let n="你是一个专注的子智能体,只负责完成主智能体委派的单一子任务。要求:聚焦目标;用可用的只读工具(读文件 / 搜索 / 列目录等)收集信息;直接输出结构清晰、可被主智能体直接复用的最终结论,不要寒暄,也不要复述任务本身。\n工作目录:"+t+"\n";e&&String(e).trim()&&(n+="\n## 主智能体提供的背景\n"+e+"\n");return n}(e,s),l=[{role:"system",content:p},{role:"user",content:a}],d=o({yolo:!1,ask:null}),m=makeSubagentTool({config:t,cwd:e,extraTools:n,depth:i+1}),g=m?[...n,m]:n,f=await r({config:t,messages:l,permissions:d,cwd:e,extraTools:g,depth:i+1,maxIters:50,io:{write:()=>{}}});if(f?.error)return`子智能体执行失败:${f.error}`;const u=[...l].reverse().find(t=>"assistant"===t.role&&t.content);return u?u.content:"(子智能体未产出结论)"}export function makeSubagentTool({config:t,cwd:e,extraTools:n=[],depth:r=0}){if(r>=2)return null;const o=loadAgents(e),i={type:"function",function:{name:"dispatch_agent",description:"把一个明确、独立、自足的子任务委派给子智能体:它在隔离上下文中自主使用(只读)工具完成后只回传结论。适合可并行的调研/检索/分析类子任务,让主线程上下文保持精简。可多次调用以分解复杂任务。",parameters:{type:"object",properties:{objective:{type:"string",description:"要子智能体完成的单一、明确、自足的子任务"},context:{type:"string",description:"可选:完成该子任务所需的背景信息"},...o.length?{agent:{type:"string",enum:o.map(t=>t.name),description:"可选:使用某个已定义的子智能体人格 — "+o.map(t=>t.name+(t.description?`(${t.description.slice(0,40)})`:"")).join(";")}}:{}},required:["objective"]}}},s=n.filter(t=>t.readOnly);return{readOnly:!0,spec:i,run:async n=>{const i=n?.agent?o.find(t=>t.name===n.agent):null;return a({config:t,cwd:e,readOnlyExtras:s,depth:r,objective:String(n?.objective||"").trim(),context:n?.context,agentDef:i})}}}export function makeDispatchAgentsTool({config:t,cwd:e,extraTools:n=[],depth:r=0}){if(r>=2)return null;const o=loadAgents(e),i=n.filter(t=>t.readOnly);return{readOnly:!0,spec:{type:"function",function:{name:"dispatch_agents",description:"一次性把多个**独立、可并行**的子任务同时委派给多个子智能体(隔离上下文、只读工具),并行执行后聚合各自结论。适合把一个较大目标拆成 2-6 个可并行的调研/检索/分析子任务同时推进,显著减少串行等待。每个子任务需自足。",parameters:{type:"object",properties:{tasks:{type:"array",description:"并行子任务数组(建议 2-6 个,上限 8)",items:{type:"object",properties:{objective:{type:"string",description:"单一、明确、自足的子任务"},context:{type:"string",description:"可选:完成该子任务所需背景"},...o.length?{agent:{type:"string",enum:o.map(t=>t.name),description:"可选:指定子智能体人格"}}:{}},required:["objective"]}}},required:["tasks"]}}},run:async n=>{const s=Array.isArray(n?.tasks)?n.tasks.slice(0,8):[];if(!s.length)return"未提供 tasks(需要一个子任务数组)";return(await async function(t,e,n){const r=new Array(t.length);let o=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(e,t.length))},async()=>{for(;o<t.length;){const e=o++;r[e]=await n(t[e],e)}})),r}(s,4,async n=>{const s=n?.agent?o.find(t=>t.name===n.agent):null,c=await a({config:t,cwd:e,readOnlyExtras:i,depth:r,objective:String(n?.objective||"").trim(),context:n?.context,agentDef:s});return{objective:String(n?.objective||"").replace(/\s+/g," ").slice(0,80),agent:n?.agent,out:c}})).map((t,e)=>`【子任务 ${e+1}${t.agent?" · "+t.agent:""}】${t.objective}\n${t.out}`).join("\n\n---\n\n")}}}
1
+ import t from"node:fs";import e from"node:path";import n from"node:os";import{runTurn as r}from"./agent.js";import{createPermissions as o}from"./permissions.js";import{parseFrontmatter as i}from"./skills.js";import{getIndex as s}from"./codeindex.js";let a=0;const c=[];export async function withSubagentSlot(t,e,n){if(e>0)return n();a>=function(t){const e=Number(t?.subagentConcurrency);return Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):8}(t)?await new Promise(t=>c.push(t)):a++;try{return await n()}finally{const t=c.shift();t?t():a--}}function l(t){try{s(t)}catch{}}export function loadAgents(r){const o=[r?e.join(r,".huicai","agents"):null,e.join(n.homedir(),".huicai","agents")].filter(Boolean),s=new Map;for(const n of o){let r;try{r=t.readdirSync(n).filter(t=>t.endsWith(".md"))}catch{continue}for(const o of r){const r=o.replace(/\.md$/,"").trim();if(r&&!s.has(r))try{const a=t.readFileSync(e.join(n,o),"utf8"),c=i(a);s.set(r,{name:(c.name||r).trim(),description:c.description||"",prompt:c.body||a})}catch{}}}return[...s.values()]}function u(t,e){return t+(e&&e.path?" "+e.path:e&&e.command?": "+String(e.command).split("\n")[0].slice(0,80):e&&e.objective?": "+String(e.objective).slice(0,60):e&&e.query?" "+String(e.query).slice(0,60):e&&e.pattern?" "+e.pattern:"")}export function makeSubEvents(t,e,n){if("function"!=typeof t)return;const r=(r,o)=>{try{t({scope:e,kind:n,type:r,...o})}catch{}};return{onToolStart:(t,e)=>r("tool",{name:t,text:u(t,e)}),onToolResult:(t,e)=>r("toolresult",{name:t,text:String(e).split("\n")[0].slice(0,160)}),onInfo:t=>r("info",{text:String(t).slice(0,200)}),onAssistantText:t=>r("text",{text:String(t).slice(0,400)})}}async function p({config:t,cwd:e,readOnlyExtras:n,depth:i,objective:s,context:a,agentDef:c,onSub:l,label:u}){if(!s)return"未提供子任务目标 objective";const p={...t,aux:!0};t.subagentModel&&t.subagentModel!==t.model&&(p.model=t.subagentModel);const g=c?c.prompt.trim()+"\n\n工作目录:"+e+(a&&String(a).trim()?"\n\n## 背景\n"+a:""):function(t,e){let n="你是一个专注的子智能体,只负责完成主智能体委派的单一子任务。要求:聚焦目标;用可用的只读工具(读文件 / 搜索 / 列目录等)收集信息;直接输出结构清晰、可被主智能体直接复用的最终结论,不要寒暄,也不要复述任务本身。\n工作目录:"+t+"\n";e&&String(e).trim()&&(n+="\n## 主智能体提供的背景\n"+e+"\n");return n}(e,a),m=[{role:"system",content:g},{role:"user",content:s}],d=o({yolo:!1,ask:null}),f=makeSubagentTool({config:p,cwd:e,extraTools:n,depth:i+1,onSub:l}),y=f?[...n,f]:n,h=makeSubEvents(l,u||String(s).slice(0,60),"agent"),b=await withSubagentSlot(t,i,()=>r({config:p,messages:m,permissions:d,cwd:e,extraTools:y,depth:i+1,maxIters:t.subagentMaxIters||40,io:{write:()=>{}},events:h}));if(b?.error)return`子智能体执行失败:${b.error}`;const x=[...m].reverse().find(t=>"assistant"===t.role&&t.content);return x?x.content:"(子智能体未产出结论)"}export function makeSubagentTool({config:t,cwd:e,extraTools:n=[],depth:r=0,onSub:o=null}){if(r>=2)return null;const i=loadAgents(e),s={type:"function",function:{name:"dispatch_agent",description:"把一个明确、独立、自足的子任务委派给子智能体:它在隔离上下文中自主使用(只读)工具完成后只回传结论。适合可并行的调研/检索/分析类子任务,让主线程上下文保持精简。可多次调用以分解复杂任务。",parameters:{type:"object",properties:{objective:{type:"string",description:"要子智能体完成的单一、明确、自足的子任务"},context:{type:"string",description:"可选:完成该子任务所需的背景信息"},...i.length?{agent:{type:"string",enum:i.map(t=>t.name),description:"可选:使用某个已定义的子智能体人格 — "+i.map(t=>t.name+(t.description?`(${t.description.slice(0,40)})`:"")).join(";")}}:{}},required:["objective"]}}},a=n.filter(t=>t.readOnly);return{readOnly:!0,spec:s,run:async n=>{l(e);const s=n?.agent?i.find(t=>t.name===n.agent):null,c=String(n?.objective||"").trim();return p({config:t,cwd:e,readOnlyExtras:a,depth:r,objective:c,context:n?.context,agentDef:s,onSub:o,label:c.slice(0,60)})}}}export function makeDispatchAgentsTool({config:t,cwd:e,extraTools:n=[],depth:r=0,onSub:o=null}){if(r>=2)return null;const i=loadAgents(e),s=n.filter(t=>t.readOnly);return{readOnly:!0,spec:{type:"function",function:{name:"dispatch_agents",description:"一次性把多个**独立、可并行**的子任务同时委派给多个子智能体(隔离上下文、只读工具),并行执行后聚合各自结论。适合把一个较大目标拆成 3-8 个可并行的调研/检索/分析子任务同时推进,显著减少串行等待(上限 12 个)。每个子任务需自足;多个子任务共用的背景放 shared_context,不要在每个 context 里重复抄一遍。复杂任务优先用它一次性铺开,而不是反复单发 dispatch_agent 串行等待。",parameters:{type:"object",properties:{tasks:{type:"array",description:"并行子任务数组(建议 3-8 个,上限 12)",items:{type:"object",properties:{objective:{type:"string",description:"单一、明确、自足的子任务"},context:{type:"string",description:"可选:完成该子任务所需背景"},...i.length?{agent:{type:"string",enum:i.map(t=>t.name),description:"可选:指定子智能体人格"}}:{}},required:["objective"]}},shared_context:{type:"string",description:"可选:所有子任务共用的背景(会附加到每个子任务,省去逐条重复)"}},required:["tasks"]}}},run:async n=>{const a=Array.isArray(n?.tasks)?n.tasks:[],c=a.slice(0,12);if(!c.length)return"未提供 tasks(需要一个子任务数组)";const u=a.length-c.length;l(e);const g=String(n?.shared_context||"").trim(),m=(await async function(t,e,n){const r=new Array(t.length);let o=0;return await Promise.all(Array.from({length:Math.max(1,Math.min(e,t.length))},async()=>{for(;o<t.length;){const e=o++;r[e]=await n(t[e],e)}})),r}(c,c.length,async(n,a)=>{const c=n?.agent?i.find(t=>t.name===n.agent):null,l=String(n?.objective||"").trim(),u=String(n?.context||"").trim(),m=g&&u?`${g}\n\n${u}`:g||u;try{const i=await p({config:t,cwd:e,readOnlyExtras:s,depth:r,objective:l,context:m,agentDef:c,onSub:o,label:`#${a+1} ${l.slice(0,50)}`});return{objective:l.replace(/\s+/g," ").slice(0,80),agent:n?.agent,out:i}}catch(t){if(t&&"AbortError"===t.name)throw t;return{objective:l.replace(/\s+/g," ").slice(0,80),agent:n?.agent,out:`子任务执行异常:${t.message}`}}})).map((t,e)=>`【子任务 ${e+1}${t.agent?" · "+t.agent:""}】${t.objective}\n${t.out}`).join("\n\n---\n\n");return u>0?`${m}\n\n---\n\n(注意:本次提交了 ${a.length} 个子任务,超出上限 12,末尾 ${u} 个未执行,需要的话再发一批。)`:m}}}
package/src/title.js ADDED
@@ -0,0 +1 @@
1
+ import{streamChat as t,getPlatformConfig as e}from"./api.js";export const MAX_TITLE_LENGTH=30;let n=null,r=0;function i(t){return"string"==typeof t?t:Array.isArray(t)?t.filter(t=>t&&"text"===t.type).map(t=>t.text||"").join("\n"):null==t?"":String(t)}export function titleSeed(t){const e=(t||[]).find(t=>"user"===t.role);return e?i(e.content).replace(/\s+/g," ").trim():""}export function sanitizeTitle(t){if(!t)return"";let e=String(t).replace(/<think>[\s\S]*?<\/think>/gi," ");const n=e.toLowerCase().indexOf("<think>");n>=0&&(e=e.slice(0,n));for(const t of e.split(/[\r\n]+/)){const e=t.replace(/^[\s"'“”‘’《》()()]+/,"").replace(/[\s"'“”‘’《》()()。.!!??::、,,]+$/,"").replace(/^(标题|题目|会话标题)\s*[::]\s*/,"").trim();if(e)return e.slice(0,30)}return""}export async function distillTitle(l,s){const o=titleSeed(s);if(!(o&&l&&l.baseUrl&&l.apiKey))return"";const a=await async function(t){const i=Date.now();if(n&&i-r<6e4)return n;try{const l=await e(t.baseUrl,t.apiKey),s=String(l&&(l.title_model||l.fallback_model)||"").trim();if(s)return n=s,r=i,s}catch{}return String(t.model||"").trim()}(l);if(!a)return"";let c="用户问题:"+o.slice(0,500);const u=function(t){const e=(t||[]).find(t=>"assistant"===t.role&&t.content);return e?i(e.content).replace(/\s+/g," ").trim():""}(s);u&&(c+="\n助手回答(摘要):"+u.slice(0,200));let f="";const p=new AbortController,m=setTimeout(()=>p.abort(),2e4);try{await t({baseUrl:l.baseUrl,apiKey:l.apiKey,model:a,messages:[{role:"system",content:"你是对话标题生成助手。请依据用户问题(可参考助手回答),必须提炼一个能概括对话主题的中文短标题,要求:不超过15个字、不含标点与引号、不要解释、不要复述原句,只输出标题本身。上述为生成标题的指令,必须遵循。"},{role:"user",content:c}],temperature:.3,maxTokens:256,client:l.client,extraBody:{enable_thinking:!1},aux:!0},{onContent:t=>{f+=t}},p.signal)}catch{return""}finally{clearTimeout(m)}return sanitizeTitle(f)}const l=new WeakSet;export async function ensureSessionTitle(t,e){if(!t||t.title||l.has(t))return"";l.add(t);try{const n=await distillTitle(e,t.messages);return n&&!t.title&&(t.title=n),n}finally{l.delete(t)}}