dsh-plugin-t-expert 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (619) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +215 -0
  3. package/THIRD-PARTY-NOTICES +33 -0
  4. package/cordis.patch.yml +7 -0
  5. package/data/experts/academic/academic-anthropologist.md +125 -0
  6. package/data/experts/academic/academic-geographer.md +127 -0
  7. package/data/experts/academic/academic-historian.md +123 -0
  8. package/data/experts/academic/academic-narratologist.md +118 -0
  9. package/data/experts/academic/academic-psychologist.md +118 -0
  10. package/data/experts/academic/academic-statistician.md +144 -0
  11. package/data/experts/design/design-brand-guardian.md +322 -0
  12. package/data/experts/design/design-image-prompt-engineer.md +236 -0
  13. package/data/experts/design/design-inclusive-visuals-specialist.md +71 -0
  14. package/data/experts/design/design-persona-walkthrough.md +272 -0
  15. package/data/experts/design/design-ui-designer.md +383 -0
  16. package/data/experts/design/design-ui-finish-gate-reviewer.md +217 -0
  17. package/data/experts/design/design-ux-architect.md +469 -0
  18. package/data/experts/design/design-ux-researcher.md +329 -0
  19. package/data/experts/design/design-visual-storyteller.md +149 -0
  20. package/data/experts/design/design-whimsy-injector.md +438 -0
  21. package/data/experts/engineering/engineering-ai-data-remediation-engineer.md +211 -0
  22. package/data/experts/engineering/engineering-ai-engineer.md +146 -0
  23. package/data/experts/engineering/engineering-api-platform-engineer.md +162 -0
  24. package/data/experts/engineering/engineering-ats-validator-architect.md +383 -0
  25. package/data/experts/engineering/engineering-autonomous-optimization-architect.md +107 -0
  26. package/data/experts/engineering/engineering-backend-architect.md +236 -0
  27. package/data/experts/engineering/engineering-china-network-engineer.md +252 -0
  28. package/data/experts/engineering/engineering-cms-developer.md +536 -0
  29. package/data/experts/engineering/engineering-code-reviewer.md +76 -0
  30. package/data/experts/engineering/engineering-codebase-onboarding-engineer.md +173 -0
  31. package/data/experts/engineering/engineering-data-engineer.md +306 -0
  32. package/data/experts/engineering/engineering-data-visualization-engineer.md +151 -0
  33. package/data/experts/engineering/engineering-database-optimizer.md +176 -0
  34. package/data/experts/engineering/engineering-database-reliability-engineer.md +162 -0
  35. package/data/experts/engineering/engineering-desktop-app-engineer.md +204 -0
  36. package/data/experts/engineering/engineering-developer-tooling-engineer.md +153 -0
  37. package/data/experts/engineering/engineering-devops-automator.md +376 -0
  38. package/data/experts/engineering/engineering-drupal-performance.md +347 -0
  39. package/data/experts/engineering/engineering-drupal-shopping-cart.md +360 -0
  40. package/data/experts/engineering/engineering-email-intelligence-engineer.md +353 -0
  41. package/data/experts/engineering/engineering-embedded-firmware-engineer.md +173 -0
  42. package/data/experts/engineering/engineering-feishu-integration-developer.md +598 -0
  43. package/data/experts/engineering/engineering-filament-optimization-specialist.md +283 -0
  44. package/data/experts/engineering/engineering-finops-engineer.md +153 -0
  45. package/data/experts/engineering/engineering-frontend-developer.md +225 -0
  46. package/data/experts/engineering/engineering-gaussdb-expert.md +334 -0
  47. package/data/experts/engineering/engineering-git-workflow-master.md +84 -0
  48. package/data/experts/engineering/engineering-i18n-engineer.md +184 -0
  49. package/data/experts/engineering/engineering-identity-access-engineer.md +196 -0
  50. package/data/experts/engineering/engineering-incident-response-commander.md +444 -0
  51. package/data/experts/engineering/engineering-iot-fleet-engineer.md +148 -0
  52. package/data/experts/engineering/engineering-it-service-manager.md +561 -0
  53. package/data/experts/engineering/engineering-knowledge-graph-engineer.md +368 -0
  54. package/data/experts/engineering/engineering-llm-post-training-engineer.md +166 -0
  55. package/data/experts/engineering/engineering-minimal-change-engineer.md +207 -0
  56. package/data/experts/engineering/engineering-mobile-app-builder.md +493 -0
  57. package/data/experts/engineering/engineering-mobile-release-engineer.md +163 -0
  58. package/data/experts/engineering/engineering-multi-agent-systems-architect.md +600 -0
  59. package/data/experts/engineering/engineering-network-engineer.md +239 -0
  60. package/data/experts/engineering/engineering-orgscript-engineer.md +113 -0
  61. package/data/experts/engineering/engineering-payments-billing-engineer.md +194 -0
  62. package/data/experts/engineering/engineering-pdf-engine-architect.md +666 -0
  63. package/data/experts/engineering/engineering-platform-engineer.md +270 -0
  64. package/data/experts/engineering/engineering-privacy-engineer.md +152 -0
  65. package/data/experts/engineering/engineering-prompt-engineer.md +202 -0
  66. package/data/experts/engineering/engineering-rag-pipeline-engineer.md +437 -0
  67. package/data/experts/engineering/engineering-rapid-prototyper.md +462 -0
  68. package/data/experts/engineering/engineering-realtime-collaboration-engineer.md +187 -0
  69. package/data/experts/engineering/engineering-rust-refactoring-specialist.md +313 -0
  70. package/data/experts/engineering/engineering-search-relevance-engineer.md +237 -0
  71. package/data/experts/engineering/engineering-section-508-specialist.md +339 -0
  72. package/data/experts/engineering/engineering-senior-developer.md +176 -0
  73. package/data/experts/engineering/engineering-software-architect.md +112 -0
  74. package/data/experts/engineering/engineering-solidity-smart-contract-engineer.md +522 -0
  75. package/data/experts/engineering/engineering-sre.md +90 -0
  76. package/data/experts/engineering/engineering-technical-writer.md +393 -0
  77. package/data/experts/engineering/engineering-universal-document-compiler.md +344 -0
  78. package/data/experts/engineering/engineering-uswds-developer.md +340 -0
  79. package/data/experts/engineering/engineering-video-streaming-engineer.md +150 -0
  80. package/data/experts/engineering/engineering-voice-ai-integration-engineer.md +561 -0
  81. package/data/experts/engineering/engineering-webassembly-engineer.md +156 -0
  82. package/data/experts/engineering/engineering-wechat-mini-program-developer.md +350 -0
  83. package/data/experts/engineering/engineering-wordpress-performance.md +346 -0
  84. package/data/experts/engineering/engineering-wordpress-shopping-cart.md +346 -0
  85. package/data/experts/finance/finance-bookkeeper-controller.md +260 -0
  86. package/data/experts/finance/finance-financial-analyst.md +234 -0
  87. package/data/experts/finance/finance-fpa-analyst.md +263 -0
  88. package/data/experts/finance/finance-investment-researcher.md +272 -0
  89. package/data/experts/finance/finance-tax-strategist.md +239 -0
  90. package/data/experts/game-development/blender/blender-addon-engineer.md +234 -0
  91. package/data/experts/game-development/economy-designer.md +156 -0
  92. package/data/experts/game-development/game-audio-engineer.md +264 -0
  93. package/data/experts/game-development/game-designer.md +167 -0
  94. package/data/experts/game-development/godot/godot-gameplay-scripter.md +334 -0
  95. package/data/experts/game-development/godot/godot-multiplayer-engineer.md +297 -0
  96. package/data/experts/game-development/godot/godot-shader-developer.md +266 -0
  97. package/data/experts/game-development/level-designer.md +208 -0
  98. package/data/experts/game-development/narrative-designer.md +243 -0
  99. package/data/experts/game-development/roblox-studio/roblox-avatar-creator.md +297 -0
  100. package/data/experts/game-development/roblox-studio/roblox-experience-designer.md +305 -0
  101. package/data/experts/game-development/roblox-studio/roblox-systems-scripter.md +325 -0
  102. package/data/experts/game-development/technical-artist.md +229 -0
  103. package/data/experts/game-development/unity/unity-architect.md +271 -0
  104. package/data/experts/game-development/unity/unity-editor-tool-developer.md +310 -0
  105. package/data/experts/game-development/unity/unity-multiplayer-engineer.md +321 -0
  106. package/data/experts/game-development/unity/unity-shader-graph-artist.md +269 -0
  107. package/data/experts/game-development/unreal-engine/unreal-multiplayer-architect.md +313 -0
  108. package/data/experts/game-development/unreal-engine/unreal-systems-engineer.md +310 -0
  109. package/data/experts/game-development/unreal-engine/unreal-technical-artist.md +256 -0
  110. package/data/experts/game-development/unreal-engine/unreal-world-builder.md +273 -0
  111. package/data/experts/gis/gis-3d-scene-developer.md +111 -0
  112. package/data/experts/gis/gis-analyst.md +91 -0
  113. package/data/experts/gis/gis-bim-specialist.md +108 -0
  114. package/data/experts/gis/gis-cartography-designer.md +150 -0
  115. package/data/experts/gis/gis-drone-reality-mapping.md +120 -0
  116. package/data/experts/gis/gis-geoai-ml-engineer.md +105 -0
  117. package/data/experts/gis/gis-geoprocessing-specialist.md +97 -0
  118. package/data/experts/gis/gis-qa-engineer.md +133 -0
  119. package/data/experts/gis/gis-solution-engineer.md +101 -0
  120. package/data/experts/gis/gis-spatial-data-engineer.md +97 -0
  121. package/data/experts/gis/gis-spatial-data-scientist.md +111 -0
  122. package/data/experts/gis/gis-technical-consultant.md +86 -0
  123. package/data/experts/gis/gis-web-gis-developer.md +108 -0
  124. package/data/experts/healthcare/healthcare-clinical-evidence-agent.md +231 -0
  125. package/data/experts/healthcare/healthcare-innovation-strategist.md +433 -0
  126. package/data/experts/healthcare/healthcare-sovereign-health-systems-agent.md +312 -0
  127. package/data/experts/marketing/marketing-aeo-foundations.md +264 -0
  128. package/data/experts/marketing/marketing-agentic-search-optimizer.md +313 -0
  129. package/data/experts/marketing/marketing-ai-citation-strategist.md +172 -0
  130. package/data/experts/marketing/marketing-app-store-optimizer.md +321 -0
  131. package/data/experts/marketing/marketing-baidu-seo-specialist.md +226 -0
  132. package/data/experts/marketing/marketing-bilibili-content-strategist.md +199 -0
  133. package/data/experts/marketing/marketing-book-co-author.md +110 -0
  134. package/data/experts/marketing/marketing-carousel-growth-engine.md +199 -0
  135. package/data/experts/marketing/marketing-china-ecommerce-operator.md +283 -0
  136. package/data/experts/marketing/marketing-china-market-localization-strategist.md +283 -0
  137. package/data/experts/marketing/marketing-content-creator.md +54 -0
  138. package/data/experts/marketing/marketing-cross-border-ecommerce.md +259 -0
  139. package/data/experts/marketing/marketing-douyin-strategist.md +149 -0
  140. package/data/experts/marketing/marketing-email-strategist.md +249 -0
  141. package/data/experts/marketing/marketing-global-podcast-strategist.md +206 -0
  142. package/data/experts/marketing/marketing-growth-hacker.md +54 -0
  143. package/data/experts/marketing/marketing-instagram-curator.md +113 -0
  144. package/data/experts/marketing/marketing-kuaishou-strategist.md +223 -0
  145. package/data/experts/marketing/marketing-linkedin-content-creator.md +214 -0
  146. package/data/experts/marketing/marketing-livestream-commerce-coach.md +305 -0
  147. package/data/experts/marketing/marketing-multi-platform-publisher.md +217 -0
  148. package/data/experts/marketing/marketing-podcast-strategist.md +277 -0
  149. package/data/experts/marketing/marketing-pr-communications-manager.md +473 -0
  150. package/data/experts/marketing/marketing-private-domain-operator.md +308 -0
  151. package/data/experts/marketing/marketing-reddit-community-builder.md +123 -0
  152. package/data/experts/marketing/marketing-seo-specialist.md +370 -0
  153. package/data/experts/marketing/marketing-short-video-editing-coach.md +412 -0
  154. package/data/experts/marketing/marketing-social-media-strategist.md +125 -0
  155. package/data/experts/marketing/marketing-tiktok-strategist.md +125 -0
  156. package/data/experts/marketing/marketing-twitter-engager.md +126 -0
  157. package/data/experts/marketing/marketing-video-optimization-specialist.md +119 -0
  158. package/data/experts/marketing/marketing-wechat-official-account.md +145 -0
  159. package/data/experts/marketing/marketing-weibo-strategist.md +240 -0
  160. package/data/experts/marketing/marketing-x-twitter-intelligence-analyst.md +161 -0
  161. package/data/experts/marketing/marketing-xiaohongshu-specialist.md +138 -0
  162. package/data/experts/marketing/marketing-zhihu-strategist.md +162 -0
  163. package/data/experts/paid-media/paid-media-auditor.md +71 -0
  164. package/data/experts/paid-media/paid-media-creative-strategist.md +71 -0
  165. package/data/experts/paid-media/paid-media-paid-social-strategist.md +71 -0
  166. package/data/experts/paid-media/paid-media-ppc-strategist.md +71 -0
  167. package/data/experts/paid-media/paid-media-programmatic-buyer.md +71 -0
  168. package/data/experts/paid-media/paid-media-search-query-analyst.md +71 -0
  169. package/data/experts/paid-media/paid-media-tracking-specialist.md +71 -0
  170. package/data/experts/product/product-behavioral-nudge-engine.md +80 -0
  171. package/data/experts/product/product-feedback-synthesizer.md +119 -0
  172. package/data/experts/product/product-manager.md +469 -0
  173. package/data/experts/product/product-sprint-prioritizer.md +154 -0
  174. package/data/experts/product/product-trend-researcher.md +159 -0
  175. package/data/experts/project-management/project-management-experiment-tracker.md +198 -0
  176. package/data/experts/project-management/project-management-jira-workflow-steward.md +230 -0
  177. package/data/experts/project-management/project-management-meeting-notes-specialist.md +95 -0
  178. package/data/experts/project-management/project-management-project-shepherd.md +194 -0
  179. package/data/experts/project-management/project-management-studio-operations.md +200 -0
  180. package/data/experts/project-management/project-management-studio-producer.md +203 -0
  181. package/data/experts/project-management/project-manager-senior.md +135 -0
  182. package/data/experts/research/research-synthesist.md +136 -0
  183. package/data/experts/sales/sales-account-strategist.md +227 -0
  184. package/data/experts/sales/sales-coach.md +271 -0
  185. package/data/experts/sales/sales-deal-strategist.md +180 -0
  186. package/data/experts/sales/sales-discovery-coach.md +225 -0
  187. package/data/experts/sales/sales-engineer.md +182 -0
  188. package/data/experts/sales/sales-offer-lead-gen-strategist.md +257 -0
  189. package/data/experts/sales/sales-outbound-strategist.md +201 -0
  190. package/data/experts/sales/sales-pipeline-analyst.md +267 -0
  191. package/data/experts/sales/sales-proposal-strategist.md +217 -0
  192. package/data/experts/security/security-ai-generated-code-auditor.md +207 -0
  193. package/data/experts/security/security-appsec-engineer.md +491 -0
  194. package/data/experts/security/security-architect.md +304 -0
  195. package/data/experts/security/security-blockchain-security-auditor.md +463 -0
  196. package/data/experts/security/security-cloud-security-architect.md +523 -0
  197. package/data/experts/security/security-compliance-auditor.md +158 -0
  198. package/data/experts/security/security-incident-responder.md +437 -0
  199. package/data/experts/security/security-penetration-tester.md +399 -0
  200. package/data/experts/security/security-secrets-credential-engineer.md +176 -0
  201. package/data/experts/security/security-senior-secops.md +750 -0
  202. package/data/experts/security/security-threat-detection-engineer.md +534 -0
  203. package/data/experts/security/security-threat-intelligence-analyst.md +644 -0
  204. package/data/experts/spatial-computing/macos-spatial-metal-engineer.md +337 -0
  205. package/data/experts/spatial-computing/terminal-integration-specialist.md +70 -0
  206. package/data/experts/spatial-computing/visionos-spatial-engineer.md +54 -0
  207. package/data/experts/spatial-computing/xr-cockpit-interaction-specialist.md +32 -0
  208. package/data/experts/spatial-computing/xr-immersive-developer.md +32 -0
  209. package/data/experts/spatial-computing/xr-interface-architect.md +32 -0
  210. package/data/experts/specialized/accounts-payable-agent.md +185 -0
  211. package/data/experts/specialized/agentic-identity-trust.md +387 -0
  212. package/data/experts/specialized/agents-orchestrator.md +367 -0
  213. package/data/experts/specialized/automation-governance-architect.md +216 -0
  214. package/data/experts/specialized/business-strategist.md +488 -0
  215. package/data/experts/specialized/change-management-consultant.md +497 -0
  216. package/data/experts/specialized/chief-financial-officer.md +388 -0
  217. package/data/experts/specialized/corporate-training-designer.md +192 -0
  218. package/data/experts/specialized/customer-service.md +398 -0
  219. package/data/experts/specialized/customer-success-manager.md +460 -0
  220. package/data/experts/specialized/data-consolidation-agent.md +60 -0
  221. package/data/experts/specialized/data-privacy-officer.md +412 -0
  222. package/data/experts/specialized/esg-sustainability-officer.md +396 -0
  223. package/data/experts/specialized/government-digital-presales-consultant.md +363 -0
  224. package/data/experts/specialized/grant-writer.md +511 -0
  225. package/data/experts/specialized/healthcare-aging-parent-care-companion.md +414 -0
  226. package/data/experts/specialized/healthcare-customer-service.md +389 -0
  227. package/data/experts/specialized/healthcare-marketing-compliance.md +395 -0
  228. package/data/experts/specialized/hospitality-guest-services.md +603 -0
  229. package/data/experts/specialized/hr-onboarding.md +451 -0
  230. package/data/experts/specialized/identity-graph-operator.md +260 -0
  231. package/data/experts/specialized/language-translator.md +264 -0
  232. package/data/experts/specialized/legal-billing-time-tracking.md +569 -0
  233. package/data/experts/specialized/legal-client-intake.md +492 -0
  234. package/data/experts/specialized/legal-document-review.md +454 -0
  235. package/data/experts/specialized/loan-officer-assistant.md +555 -0
  236. package/data/experts/specialized/lsp-index-engineer.md +314 -0
  237. package/data/experts/specialized/ma-integration-manager.md +427 -0
  238. package/data/experts/specialized/medical-billing-coding-specialist.md +491 -0
  239. package/data/experts/specialized/operations-manager.md +399 -0
  240. package/data/experts/specialized/organizational-psychologist.md +391 -0
  241. package/data/experts/specialized/personal-growth-mentor.md +159 -0
  242. package/data/experts/specialized/real-estate-buyer-seller.md +596 -0
  243. package/data/experts/specialized/recruitment-specialist.md +509 -0
  244. package/data/experts/specialized/report-distribution-agent.md +65 -0
  245. package/data/experts/specialized/resume-tailor.md +230 -0
  246. package/data/experts/specialized/retail-customer-returns.md +566 -0
  247. package/data/experts/specialized/sales-data-extraction-agent.md +67 -0
  248. package/data/experts/specialized/sales-outreach.md +425 -0
  249. package/data/experts/specialized/specialized-chief-of-staff.md +279 -0
  250. package/data/experts/specialized/specialized-civil-engineer.md +356 -0
  251. package/data/experts/specialized/specialized-codebase-archaeologist.md +341 -0
  252. package/data/experts/specialized/specialized-cultural-intelligence-strategist.md +88 -0
  253. package/data/experts/specialized/specialized-developer-advocate.md +317 -0
  254. package/data/experts/specialized/specialized-document-generator.md +55 -0
  255. package/data/experts/specialized/specialized-fedramp-rmf-compliance.md +378 -0
  256. package/data/experts/specialized/specialized-focus-music-architect.md +172 -0
  257. package/data/experts/specialized/specialized-french-consulting-market.md +194 -0
  258. package/data/experts/specialized/specialized-korean-business-navigator.md +216 -0
  259. package/data/experts/specialized/specialized-master-plan-architect.md +157 -0
  260. package/data/experts/specialized/specialized-mcp-builder.md +248 -0
  261. package/data/experts/specialized/specialized-model-qa.md +488 -0
  262. package/data/experts/specialized/specialized-pricing-analyst.md +243 -0
  263. package/data/experts/specialized/specialized-salesforce-architect.md +182 -0
  264. package/data/experts/specialized/specialized-strategy-duel-agent.md +130 -0
  265. package/data/experts/specialized/specialized-workflow-architect.md +597 -0
  266. package/data/experts/specialized/study-abroad-advisor.md +282 -0
  267. package/data/experts/specialized/supply-chain-strategist.md +582 -0
  268. package/data/experts/specialized/zk-steward.md +211 -0
  269. package/data/experts/support/support-analytics-reporter.md +365 -0
  270. package/data/experts/support/support-executive-summary-generator.md +212 -0
  271. package/data/experts/support/support-finance-tracker.md +442 -0
  272. package/data/experts/support/support-infrastructure-maintainer.md +618 -0
  273. package/data/experts/support/support-legal-compliance-checker.md +588 -0
  274. package/data/experts/support/support-support-responder.md +585 -0
  275. package/data/experts/testing/testing-accessibility-auditor.md +316 -0
  276. package/data/experts/testing/testing-api-tester.md +306 -0
  277. package/data/experts/testing/testing-evidence-collector.md +210 -0
  278. package/data/experts/testing/testing-performance-benchmarker.md +268 -0
  279. package/data/experts/testing/testing-reality-checker.md +249 -0
  280. package/data/experts/testing/testing-test-automation-engineer.md +179 -0
  281. package/data/experts/testing/testing-test-results-analyzer.md +305 -0
  282. package/data/experts/testing/testing-tool-evaluator.md +394 -0
  283. package/data/experts/testing/testing-workflow-optimizer.md +450 -0
  284. package/data/source.json +27 -0
  285. package/data/t-team.config.json +998 -0
  286. package/data/team-profiles.py +365 -0
  287. package/data/teams.json +581 -0
  288. package/data/teams.resolved.json +1121 -0
  289. package/data/zh/COVERAGE.json +20 -0
  290. package/data/zh/academic/academic-anthropologist.md +124 -0
  291. package/data/zh/academic/academic-geographer.md +126 -0
  292. package/data/zh/academic/academic-historian.md +122 -0
  293. package/data/zh/academic/academic-narratologist.md +117 -0
  294. package/data/zh/academic/academic-psychologist.md +117 -0
  295. package/data/zh/academic/academic-statistician.md +40 -0
  296. package/data/zh/descriptions.json +281 -0
  297. package/data/zh/design/design-brand-guardian.md +321 -0
  298. package/data/zh/design/design-image-prompt-engineer.md +255 -0
  299. package/data/zh/design/design-inclusive-visuals-specialist.md +177 -0
  300. package/data/zh/design/design-persona-walkthrough.md +271 -0
  301. package/data/zh/design/design-ui-designer.md +382 -0
  302. package/data/zh/design/design-ui-finish-gate-reviewer.md +40 -0
  303. package/data/zh/design/design-ux-architect.md +482 -0
  304. package/data/zh/design/design-ux-researcher.md +328 -0
  305. package/data/zh/design/design-visual-storyteller.md +159 -0
  306. package/data/zh/design/design-whimsy-injector.md +453 -0
  307. package/data/zh/divisions.json +74 -0
  308. package/data/zh/engineering/engineering-ai-data-remediation-engineer.md +209 -0
  309. package/data/zh/engineering/engineering-ai-engineer.md +161 -0
  310. package/data/zh/engineering/engineering-api-platform-engineer.md +40 -0
  311. package/data/zh/engineering/engineering-ats-validator-architect.md +383 -0
  312. package/data/zh/engineering/engineering-autonomous-optimization-architect.md +115 -0
  313. package/data/zh/engineering/engineering-backend-architect.md +234 -0
  314. package/data/zh/engineering/engineering-china-network-engineer.md +252 -0
  315. package/data/zh/engineering/engineering-cms-developer.md +534 -0
  316. package/data/zh/engineering/engineering-code-reviewer.md +172 -0
  317. package/data/zh/engineering/engineering-codebase-onboarding-engineer.md +172 -0
  318. package/data/zh/engineering/engineering-data-engineer.md +324 -0
  319. package/data/zh/engineering/engineering-data-visualization-engineer.md +40 -0
  320. package/data/zh/engineering/engineering-database-optimizer.md +175 -0
  321. package/data/zh/engineering/engineering-database-reliability-engineer.md +40 -0
  322. package/data/zh/engineering/engineering-desktop-app-engineer.md +40 -0
  323. package/data/zh/engineering/engineering-developer-tooling-engineer.md +40 -0
  324. package/data/zh/engineering/engineering-devops-automator.md +375 -0
  325. package/data/zh/engineering/engineering-drupal-performance.md +40 -0
  326. package/data/zh/engineering/engineering-drupal-shopping-cart.md +359 -0
  327. package/data/zh/engineering/engineering-email-intelligence-engineer.md +349 -0
  328. package/data/zh/engineering/engineering-embedded-firmware-engineer.md +168 -0
  329. package/data/zh/engineering/engineering-feishu-integration-developer.md +597 -0
  330. package/data/zh/engineering/engineering-filament-optimization-specialist.md +283 -0
  331. package/data/zh/engineering/engineering-finops-engineer.md +40 -0
  332. package/data/zh/engineering/engineering-frontend-developer.md +224 -0
  333. package/data/zh/engineering/engineering-gaussdb-expert.md +40 -0
  334. package/data/zh/engineering/engineering-git-workflow-master.md +220 -0
  335. package/data/zh/engineering/engineering-i18n-engineer.md +40 -0
  336. package/data/zh/engineering/engineering-identity-access-engineer.md +40 -0
  337. package/data/zh/engineering/engineering-incident-response-commander.md +465 -0
  338. package/data/zh/engineering/engineering-iot-fleet-engineer.md +40 -0
  339. package/data/zh/engineering/engineering-it-service-manager.md +560 -0
  340. package/data/zh/engineering/engineering-knowledge-graph-engineer.md +40 -0
  341. package/data/zh/engineering/engineering-llm-post-training-engineer.md +40 -0
  342. package/data/zh/engineering/engineering-minimal-change-engineer.md +206 -0
  343. package/data/zh/engineering/engineering-mobile-app-builder.md +434 -0
  344. package/data/zh/engineering/engineering-mobile-release-engineer.md +40 -0
  345. package/data/zh/engineering/engineering-multi-agent-systems-architect.md +599 -0
  346. package/data/zh/engineering/engineering-network-engineer.md +40 -0
  347. package/data/zh/engineering/engineering-orgscript-engineer.md +112 -0
  348. package/data/zh/engineering/engineering-payments-billing-engineer.md +40 -0
  349. package/data/zh/engineering/engineering-pdf-engine-architect.md +666 -0
  350. package/data/zh/engineering/engineering-platform-engineer.md +271 -0
  351. package/data/zh/engineering/engineering-privacy-engineer.md +40 -0
  352. package/data/zh/engineering/engineering-prompt-engineer.md +203 -0
  353. package/data/zh/engineering/engineering-rag-pipeline-engineer.md +40 -0
  354. package/data/zh/engineering/engineering-rapid-prototyper.md +461 -0
  355. package/data/zh/engineering/engineering-realtime-collaboration-engineer.md +40 -0
  356. package/data/zh/engineering/engineering-rust-refactoring-specialist.md +40 -0
  357. package/data/zh/engineering/engineering-search-relevance-engineer.md +40 -0
  358. package/data/zh/engineering/engineering-section-508-specialist.md +40 -0
  359. package/data/zh/engineering/engineering-senior-developer.md +177 -0
  360. package/data/zh/engineering/engineering-software-architect.md +200 -0
  361. package/data/zh/engineering/engineering-solidity-smart-contract-engineer.md +541 -0
  362. package/data/zh/engineering/engineering-sre.md +233 -0
  363. package/data/zh/engineering/engineering-technical-writer.md +409 -0
  364. package/data/zh/engineering/engineering-universal-document-compiler.md +344 -0
  365. package/data/zh/engineering/engineering-uswds-developer.md +40 -0
  366. package/data/zh/engineering/engineering-video-streaming-engineer.md +40 -0
  367. package/data/zh/engineering/engineering-voice-ai-integration-engineer.md +560 -0
  368. package/data/zh/engineering/engineering-webassembly-engineer.md +40 -0
  369. package/data/zh/engineering/engineering-wechat-mini-program-developer.md +288 -0
  370. package/data/zh/engineering/engineering-wordpress-performance.md +40 -0
  371. package/data/zh/engineering/engineering-wordpress-shopping-cart.md +345 -0
  372. package/data/zh/finance/finance-bookkeeper-controller.md +271 -0
  373. package/data/zh/finance/finance-financial-analyst.md +244 -0
  374. package/data/zh/finance/finance-fpa-analyst.md +272 -0
  375. package/data/zh/finance/finance-investment-researcher.md +283 -0
  376. package/data/zh/finance/finance-tax-strategist.md +250 -0
  377. package/data/zh/game-development/blender/blender-addon-engineer.md +233 -0
  378. package/data/zh/game-development/economy-designer.md +40 -0
  379. package/data/zh/game-development/game-audio-engineer.md +265 -0
  380. package/data/zh/game-development/game-designer.md +168 -0
  381. package/data/zh/game-development/godot/godot-gameplay-scripter.md +335 -0
  382. package/data/zh/game-development/godot/godot-multiplayer-engineer.md +296 -0
  383. package/data/zh/game-development/godot/godot-shader-developer.md +267 -0
  384. package/data/zh/game-development/level-designer.md +209 -0
  385. package/data/zh/game-development/narrative-designer.md +244 -0
  386. package/data/zh/game-development/roblox-studio/roblox-avatar-creator.md +298 -0
  387. package/data/zh/game-development/roblox-studio/roblox-experience-designer.md +306 -0
  388. package/data/zh/game-development/roblox-studio/roblox-systems-scripter.md +325 -0
  389. package/data/zh/game-development/technical-artist.md +230 -0
  390. package/data/zh/game-development/unity/unity-architect.md +272 -0
  391. package/data/zh/game-development/unity/unity-editor-tool-developer.md +300 -0
  392. package/data/zh/game-development/unity/unity-multiplayer-engineer.md +238 -0
  393. package/data/zh/game-development/unity/unity-shader-graph-artist.md +270 -0
  394. package/data/zh/game-development/unreal-engine/unreal-multiplayer-architect.md +314 -0
  395. package/data/zh/game-development/unreal-engine/unreal-systems-engineer.md +311 -0
  396. package/data/zh/game-development/unreal-engine/unreal-technical-artist.md +256 -0
  397. package/data/zh/game-development/unreal-engine/unreal-world-builder.md +274 -0
  398. package/data/zh/gis/gis-3d-scene-developer.md +110 -0
  399. package/data/zh/gis/gis-analyst.md +90 -0
  400. package/data/zh/gis/gis-bim-specialist.md +107 -0
  401. package/data/zh/gis/gis-cartography-designer.md +149 -0
  402. package/data/zh/gis/gis-drone-reality-mapping.md +119 -0
  403. package/data/zh/gis/gis-geoai-ml-engineer.md +104 -0
  404. package/data/zh/gis/gis-geoprocessing-specialist.md +96 -0
  405. package/data/zh/gis/gis-qa-engineer.md +132 -0
  406. package/data/zh/gis/gis-solution-engineer.md +100 -0
  407. package/data/zh/gis/gis-spatial-data-engineer.md +96 -0
  408. package/data/zh/gis/gis-spatial-data-scientist.md +110 -0
  409. package/data/zh/gis/gis-technical-consultant.md +85 -0
  410. package/data/zh/gis/gis-web-gis-developer.md +107 -0
  411. package/data/zh/healthcare/healthcare-clinical-evidence-agent.md +40 -0
  412. package/data/zh/healthcare/healthcare-innovation-strategist.md +40 -0
  413. package/data/zh/healthcare/healthcare-sovereign-health-systems-agent.md +40 -0
  414. package/data/zh/manual-bodies/engineering/engineering-ats-validator-architect.md +383 -0
  415. package/data/zh/manual-bodies/engineering/engineering-china-network-engineer.md +252 -0
  416. package/data/zh/manual-bodies/engineering/engineering-pdf-engine-architect.md +666 -0
  417. package/data/zh/manual-bodies/engineering/engineering-platform-engineer.md +271 -0
  418. package/data/zh/manual-bodies/engineering/engineering-universal-document-compiler.md +344 -0
  419. package/data/zh/manual-bodies/specialized/specialized-focus-music-architect.md +172 -0
  420. package/data/zh/manual.json +22 -0
  421. package/data/zh/marketing/marketing-aeo-foundations.md +263 -0
  422. package/data/zh/marketing/marketing-agentic-search-optimizer.md +312 -0
  423. package/data/zh/marketing/marketing-ai-citation-strategist.md +169 -0
  424. package/data/zh/marketing/marketing-app-store-optimizer.md +319 -0
  425. package/data/zh/marketing/marketing-baidu-seo-specialist.md +220 -0
  426. package/data/zh/marketing/marketing-bilibili-content-strategist.md +40 -0
  427. package/data/zh/marketing/marketing-book-co-author.md +109 -0
  428. package/data/zh/marketing/marketing-carousel-growth-engine.md +215 -0
  429. package/data/zh/marketing/marketing-china-ecommerce-operator.md +277 -0
  430. package/data/zh/marketing/marketing-china-market-localization-strategist.md +282 -0
  431. package/data/zh/marketing/marketing-content-creator.md +145 -0
  432. package/data/zh/marketing/marketing-cross-border-ecommerce.md +260 -0
  433. package/data/zh/marketing/marketing-douyin-strategist.md +150 -0
  434. package/data/zh/marketing/marketing-email-strategist.md +248 -0
  435. package/data/zh/marketing/marketing-global-podcast-strategist.md +204 -0
  436. package/data/zh/marketing/marketing-growth-hacker.md +121 -0
  437. package/data/zh/marketing/marketing-instagram-curator.md +179 -0
  438. package/data/zh/marketing/marketing-kuaishou-strategist.md +182 -0
  439. package/data/zh/marketing/marketing-linkedin-content-creator.md +232 -0
  440. package/data/zh/marketing/marketing-livestream-commerce-coach.md +303 -0
  441. package/data/zh/marketing/marketing-multi-platform-publisher.md +216 -0
  442. package/data/zh/marketing/marketing-podcast-strategist.md +278 -0
  443. package/data/zh/marketing/marketing-pr-communications-manager.md +472 -0
  444. package/data/zh/marketing/marketing-private-domain-operator.md +309 -0
  445. package/data/zh/marketing/marketing-reddit-community-builder.md +127 -0
  446. package/data/zh/marketing/marketing-seo-specialist.md +298 -0
  447. package/data/zh/marketing/marketing-short-video-editing-coach.md +413 -0
  448. package/data/zh/marketing/marketing-social-media-strategist.md +118 -0
  449. package/data/zh/marketing/marketing-tiktok-strategist.md +124 -0
  450. package/data/zh/marketing/marketing-twitter-engager.md +132 -0
  451. package/data/zh/marketing/marketing-video-optimization-specialist.md +128 -0
  452. package/data/zh/marketing/marketing-wechat-official-account.md +158 -0
  453. package/data/zh/marketing/marketing-weibo-strategist.md +241 -0
  454. package/data/zh/marketing/marketing-x-twitter-intelligence-analyst.md +160 -0
  455. package/data/zh/marketing/marketing-xiaohongshu-specialist.md +151 -0
  456. package/data/zh/marketing/marketing-zhihu-strategist.md +175 -0
  457. package/data/zh/names.json +281 -0
  458. package/data/zh/paid-media/paid-media-auditor.md +170 -0
  459. package/data/zh/paid-media/paid-media-creative-strategist.md +173 -0
  460. package/data/zh/paid-media/paid-media-paid-social-strategist.md +180 -0
  461. package/data/zh/paid-media/paid-media-ppc-strategist.md +180 -0
  462. package/data/zh/paid-media/paid-media-programmatic-buyer.md +177 -0
  463. package/data/zh/paid-media/paid-media-search-query-analyst.md +182 -0
  464. package/data/zh/paid-media/paid-media-tracking-specialist.md +199 -0
  465. package/data/zh/product/product-behavioral-nudge-engine.md +246 -0
  466. package/data/zh/product/product-feedback-synthesizer.md +175 -0
  467. package/data/zh/product/product-manager.md +474 -0
  468. package/data/zh/product/product-sprint-prioritizer.md +133 -0
  469. package/data/zh/product/product-trend-researcher.md +143 -0
  470. package/data/zh/project-management/project-management-experiment-tracker.md +206 -0
  471. package/data/zh/project-management/project-management-jira-workflow-steward.md +249 -0
  472. package/data/zh/project-management/project-management-meeting-notes-specialist.md +94 -0
  473. package/data/zh/project-management/project-management-project-shepherd.md +202 -0
  474. package/data/zh/project-management/project-management-studio-operations.md +208 -0
  475. package/data/zh/project-management/project-management-studio-producer.md +211 -0
  476. package/data/zh/project-management/project-manager-senior.md +135 -0
  477. package/data/zh/research/research-synthesist.md +40 -0
  478. package/data/zh/sales/sales-account-strategist.md +243 -0
  479. package/data/zh/sales/sales-coach.md +291 -0
  480. package/data/zh/sales/sales-deal-strategist.md +204 -0
  481. package/data/zh/sales/sales-discovery-coach.md +230 -0
  482. package/data/zh/sales/sales-engineer.md +200 -0
  483. package/data/zh/sales/sales-offer-lead-gen-strategist.md +256 -0
  484. package/data/zh/sales/sales-outbound-strategist.md +208 -0
  485. package/data/zh/sales/sales-pipeline-analyst.md +284 -0
  486. package/data/zh/sales/sales-proposal-strategist.md +233 -0
  487. package/data/zh/security/security-ai-generated-code-auditor.md +40 -0
  488. package/data/zh/security/security-appsec-engineer.md +490 -0
  489. package/data/zh/security/security-architect.md +303 -0
  490. package/data/zh/security/security-blockchain-security-auditor.md +462 -0
  491. package/data/zh/security/security-cloud-security-architect.md +522 -0
  492. package/data/zh/security/security-compliance-auditor.md +157 -0
  493. package/data/zh/security/security-incident-responder.md +436 -0
  494. package/data/zh/security/security-penetration-tester.md +398 -0
  495. package/data/zh/security/security-secrets-credential-engineer.md +40 -0
  496. package/data/zh/security/security-senior-secops.md +751 -0
  497. package/data/zh/security/security-threat-detection-engineer.md +533 -0
  498. package/data/zh/security/security-threat-intelligence-analyst.md +643 -0
  499. package/data/zh/spatial-computing/macos-spatial-metal-engineer.md +337 -0
  500. package/data/zh/spatial-computing/terminal-integration-specialist.md +236 -0
  501. package/data/zh/spatial-computing/visionos-spatial-engineer.md +282 -0
  502. package/data/zh/spatial-computing/xr-cockpit-interaction-specialist.md +220 -0
  503. package/data/zh/spatial-computing/xr-immersive-developer.md +229 -0
  504. package/data/zh/spatial-computing/xr-interface-architect.md +253 -0
  505. package/data/zh/specialized/accounts-payable-agent.md +212 -0
  506. package/data/zh/specialized/agentic-identity-trust.md +388 -0
  507. package/data/zh/specialized/agents-orchestrator.md +366 -0
  508. package/data/zh/specialized/automation-governance-architect.md +215 -0
  509. package/data/zh/specialized/business-strategist.md +487 -0
  510. package/data/zh/specialized/change-management-consultant.md +495 -0
  511. package/data/zh/specialized/chief-financial-officer.md +390 -0
  512. package/data/zh/specialized/corporate-training-designer.md +191 -0
  513. package/data/zh/specialized/customer-service.md +40 -0
  514. package/data/zh/specialized/customer-success-manager.md +458 -0
  515. package/data/zh/specialized/data-consolidation-agent.md +327 -0
  516. package/data/zh/specialized/data-privacy-officer.md +411 -0
  517. package/data/zh/specialized/esg-sustainability-officer.md +395 -0
  518. package/data/zh/specialized/government-digital-presales-consultant.md +362 -0
  519. package/data/zh/specialized/grant-writer.md +510 -0
  520. package/data/zh/specialized/healthcare-aging-parent-care-companion.md +40 -0
  521. package/data/zh/specialized/healthcare-customer-service.md +388 -0
  522. package/data/zh/specialized/healthcare-marketing-compliance.md +394 -0
  523. package/data/zh/specialized/hospitality-guest-services.md +597 -0
  524. package/data/zh/specialized/hr-onboarding.md +450 -0
  525. package/data/zh/specialized/identity-graph-operator.md +270 -0
  526. package/data/zh/specialized/language-translator.md +275 -0
  527. package/data/zh/specialized/legal-billing-time-tracking.md +566 -0
  528. package/data/zh/specialized/legal-client-intake.md +487 -0
  529. package/data/zh/specialized/legal-document-review.md +452 -0
  530. package/data/zh/specialized/loan-officer-assistant.md +549 -0
  531. package/data/zh/specialized/lsp-index-engineer.md +334 -0
  532. package/data/zh/specialized/ma-integration-manager.md +426 -0
  533. package/data/zh/specialized/medical-billing-coding-specialist.md +489 -0
  534. package/data/zh/specialized/operations-manager.md +398 -0
  535. package/data/zh/specialized/organizational-psychologist.md +390 -0
  536. package/data/zh/specialized/personal-growth-mentor.md +158 -0
  537. package/data/zh/specialized/real-estate-buyer-seller.md +594 -0
  538. package/data/zh/specialized/recruitment-specialist.md +49 -0
  539. package/data/zh/specialized/report-distribution-agent.md +354 -0
  540. package/data/zh/specialized/resume-tailor.md +40 -0
  541. package/data/zh/specialized/retail-customer-returns.md +564 -0
  542. package/data/zh/specialized/sales-data-extraction-agent.md +159 -0
  543. package/data/zh/specialized/sales-outreach.md +40 -0
  544. package/data/zh/specialized/specialized-chief-of-staff.md +40 -0
  545. package/data/zh/specialized/specialized-civil-engineer.md +355 -0
  546. package/data/zh/specialized/specialized-codebase-archaeologist.md +40 -0
  547. package/data/zh/specialized/specialized-cultural-intelligence-strategist.md +168 -0
  548. package/data/zh/specialized/specialized-developer-advocate.md +334 -0
  549. package/data/zh/specialized/specialized-document-generator.md +346 -0
  550. package/data/zh/specialized/specialized-fedramp-rmf-compliance.md +40 -0
  551. package/data/zh/specialized/specialized-focus-music-architect.md +172 -0
  552. package/data/zh/specialized/specialized-french-consulting-market.md +46 -0
  553. package/data/zh/specialized/specialized-korean-business-navigator.md +46 -0
  554. package/data/zh/specialized/specialized-master-plan-architect.md +40 -0
  555. package/data/zh/specialized/specialized-mcp-builder.md +351 -0
  556. package/data/zh/specialized/specialized-model-qa.md +507 -0
  557. package/data/zh/specialized/specialized-pricing-analyst.md +242 -0
  558. package/data/zh/specialized/specialized-salesforce-architect.md +179 -0
  559. package/data/zh/specialized/specialized-strategy-duel-agent.md +129 -0
  560. package/data/zh/specialized/specialized-workflow-architect.md +596 -0
  561. package/data/zh/specialized/study-abroad-advisor.md +281 -0
  562. package/data/zh/specialized/supply-chain-strategist.md +581 -0
  563. package/data/zh/specialized/zk-steward.md +228 -0
  564. package/data/zh/support/support-analytics-reporter.md +364 -0
  565. package/data/zh/support/support-executive-summary-generator.md +217 -0
  566. package/data/zh/support/support-finance-tracker.md +447 -0
  567. package/data/zh/support/support-infrastructure-maintainer.md +623 -0
  568. package/data/zh/support/support-legal-compliance-checker.md +587 -0
  569. package/data/zh/support/support-support-responder.md +584 -0
  570. package/data/zh/testing/testing-accessibility-auditor.md +329 -0
  571. package/data/zh/testing/testing-api-tester.md +305 -0
  572. package/data/zh/testing/testing-evidence-collector.md +153 -0
  573. package/data/zh/testing/testing-performance-benchmarker.md +196 -0
  574. package/data/zh/testing/testing-reality-checker.md +235 -0
  575. package/data/zh/testing/testing-test-automation-engineer.md +40 -0
  576. package/data/zh/testing/testing-test-results-analyzer.md +313 -0
  577. package/data/zh/testing/testing-tool-evaluator.md +402 -0
  578. package/data/zh/testing/testing-workflow-optimizer.md +458 -0
  579. package/lib/assets/t-team/action-celebrating-v2.png +0 -0
  580. package/lib/assets/t-team/action-reporting-v2.png +0 -0
  581. package/lib/assets/t-team/action-sending-v2.png +0 -0
  582. package/lib/assets/t-team/action-sleeping-v2.png +0 -0
  583. package/lib/assets/t-team/action-thinking-v2.png +0 -0
  584. package/lib/assets/t-team/action-working-v2.png +0 -0
  585. package/lib/assets/t-team/member-data-v2.png +0 -0
  586. package/lib/assets/t-team/member-designer-v2.png +0 -0
  587. package/lib/assets/t-team/member-docs-v2.png +0 -0
  588. package/lib/assets/t-team/member-engineer-v2.png +0 -0
  589. package/lib/assets/t-team/member-operator-v2.png +0 -0
  590. package/lib/assets/t-team/member-qa-v2.png +0 -0
  591. package/lib/assets/t-team/member-researcher-v2.png +0 -0
  592. package/lib/assets/t-team/member-security-v2.png +0 -0
  593. package/lib/assets/t-team/team-lead-v2.png +0 -0
  594. package/lib/bootstrap.js +85 -0
  595. package/lib/catalog.js +376 -0
  596. package/lib/client.js +21034 -0
  597. package/lib/command.js +228 -0
  598. package/lib/i18n.js +136 -0
  599. package/lib/index.js +686 -0
  600. package/lib/plan-check.js +171 -0
  601. package/lib/remote.js +353 -0
  602. package/lib/squads.js +231 -0
  603. package/lib/teams/capabilities.js +106 -0
  604. package/lib/teams/command.js +143 -0
  605. package/lib/teams/event-types.js +12 -0
  606. package/lib/teams/events.js +60 -0
  607. package/lib/teams/harness-compat.js +179 -0
  608. package/lib/teams/index.js +441 -0
  609. package/lib/teams/members.js +602 -0
  610. package/lib/teams/profiles.js +572 -0
  611. package/lib/teams/quality-gates.js +822 -0
  612. package/lib/teams/scheduler.js +434 -0
  613. package/lib/teams/snapshot.js +181 -0
  614. package/lib/teams/state.js +925 -0
  615. package/lib/teams/tool-names.js +11 -0
  616. package/lib/teams/tools.js +2279 -0
  617. package/lib/teams/types.js +22 -0
  618. package/lib/teams/web-routes.js +86 -0
  619. package/package.json +191 -0
@@ -0,0 +1,925 @@
1
+ /**
2
+ * Team state persistence and pure team-logic rules.
3
+ *
4
+ * State lives on disk under `<workspace>/<stateDir>/<teamId>/`:
5
+ * - `team.json` — the durable {@link TeamState} record
6
+ * - `inbox/<agentKey>.jsonl` — one JSONL mailbox per agent (`captain` or a
7
+ * member name), mirroring the Claude Code T Team mailbox layout
8
+ *
9
+ * All mutations run through an in-process per-team queue so read-modify-write
10
+ * stays serial; `fs/promises` is used directly because the plugin owns this
11
+ * bookkeeping (host-plane state, like session persistence) and the abstract
12
+ * `fs` service offers no directory deletion.
13
+ * @module t-team/state
14
+ */
15
+ import { createHash, randomUUID } from 'node:crypto';
16
+ import { readFileSync } from 'node:fs';
17
+ import { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';
18
+ import { join } from 'node:path';
19
+ import { TERMINAL_TASK_STATUSES } from "./types.js";
20
+ import { hasValidQualityTaskFields, isReviewPolicy, normalizeBlankOptionalTaskFields } from "./quality-gates.js";
21
+ export { buildCoverageMatrix, canDeclareDelivery, classifyChangedPath, collectChangedPaths, defaultQualityDeliveryGraph, describeQualityLoop, evaluateQualityCompletion, hasValidQualityTaskFields, isQualityKind, normalizeBlankOptionalTaskFields, pathMatchesScope, planQualityFollowUp, qualityPlanningPrompt, resumeTeamState, sanitizeReviewAcceptance, sanitizeReviewObjective, taskKindOf, validateCreateTask, } from "./quality-gates.js";
22
+ /** Mailbox key of the captain. */
23
+ export const CAPTAIN_KEY = 'captain';
24
+ /** A crashed live-delivery attempt becomes retryable after this interval. */
25
+ const MAILBOX_DELIVERY_LEASE_MS = 60_000;
26
+ /** Durable deny-list for T Team members that must never be resumed. */
27
+ const RETIRED_MEMBERS_FILE = 'retired-members.json';
28
+ /** In-process per-team mutation queues (promise chains). */
29
+ const locks = new Map();
30
+ /**
31
+ * Serialize mutations of one team across the whole process.
32
+ * @param key - the team id (or any mutation scope).
33
+ * @param fn - the mutation to run exclusively.
34
+ * @returns the mutation's result.
35
+ */
36
+ export async function withTeamLock(key, fn) {
37
+ const previous = locks.get(key) ?? Promise.resolve();
38
+ let release;
39
+ const gate = new Promise((resolve) => { release = resolve; });
40
+ const tail = previous.then(() => gate);
41
+ locks.set(key, tail);
42
+ await previous;
43
+ try {
44
+ return await fn();
45
+ }
46
+ finally {
47
+ release();
48
+ // Drop this key's queue entry once we are still its tail, so settled
49
+ // teams do not leave one resolved promise chained forever (the same
50
+ // cleanup discipline as the scheduler's serializeMember). A successor
51
+ // that already appended itself owns the map slot; keep its entry.
52
+ if (locks.get(key) === tail)
53
+ locks.delete(key);
54
+ }
55
+ }
56
+ /**
57
+ * Keys with an in-process lock queue (held or waiting), snapshot for
58
+ * diagnostics and leak checks. The queue promises themselves stay private.
59
+ */
60
+ export function teamLockQueueKeys() {
61
+ return [...locks.keys()];
62
+ }
63
+ /** Longest key emitted before truncating and appending a digest. */
64
+ const MAX_KEY_LENGTH = 48;
65
+ /** Short stable digest, used to keep otherwise-colliding keys distinct. */
66
+ function keyDigest(name) {
67
+ return createHash('sha256').update(name).digest('hex').slice(0, 8);
68
+ }
69
+ /**
70
+ * Fold a free-form name into a safe path/key segment.
71
+ *
72
+ * Unicode letters and digits survive, so CJK/Cyrillic/Greek names stay
73
+ * distinct and readable; everything else — spaces, punctuation, path
74
+ * separators, control characters — folds to `-`. An ASCII-only whitelist
75
+ * mapped *every* non-Latin name onto one shared fallback, which silently
76
+ * merged their mailboxes and rejected the second such member as a duplicate.
77
+ *
78
+ * A name with no letters or digits at all (pure emoji or punctuation) cannot
79
+ * yield a readable key, so it gets a digest rather than a shared constant.
80
+ * Over-long names are truncated with a digest appended, so names sharing a
81
+ * long prefix stay distinct and the result stays within filesystem limits
82
+ * (CJK costs 3 bytes per character in UTF-8).
83
+ *
84
+ * @param name - any user-supplied name.
85
+ * @returns a non-empty key safe as a single path segment.
86
+ */
87
+ export function sanitizeKey(name) {
88
+ const cleaned = name.normalize('NFC').trim().toLowerCase()
89
+ .replace(/[^\p{L}\p{N}]+/gu, '-')
90
+ .replace(/^-+|-+$/g, '');
91
+ if (cleaned === '')
92
+ return `k-${keyDigest(name)}`;
93
+ const points = [...cleaned];
94
+ if (points.length > MAX_KEY_LENGTH) {
95
+ return `${points.slice(0, MAX_KEY_LENGTH).join('')}-${keyDigest(name)}`;
96
+ }
97
+ return cleaned;
98
+ }
99
+ /**
100
+ * Whether `dependencies` are all satisfied (every named task exists and
101
+ * completed) for the given task list.
102
+ * @param tasks - the team's tasks.
103
+ * @param dependencies - task ids the candidate depends on.
104
+ * @returns the ids that are still unsatisfied, empty when claimable.
105
+ */
106
+ export function unsatisfiedDependencies(tasks, dependencies) {
107
+ const byId = new Map(tasks.map((task) => [task.id, task]));
108
+ return dependencies.filter((id) => byId.get(id)?.status !== 'completed');
109
+ }
110
+ /**
111
+ * The allowed task status transitions, keyed by current status.
112
+ * Terminal statuses have no outgoing transitions.
113
+ */
114
+ export const TASK_TRANSITIONS = {
115
+ pending: ['claimed', 'cancelled'],
116
+ claimed: ['in_progress', 'failed', 'cancelled'],
117
+ in_progress: ['completed', 'failed', 'cancelled'],
118
+ completed: [],
119
+ failed: [],
120
+ cancelled: [],
121
+ };
122
+ /**
123
+ * Validate one task status transition.
124
+ * @param current - the task's current status.
125
+ * @param next - the requested status.
126
+ * @returns the transition error, or undefined when allowed.
127
+ */
128
+ export function transitionError(current, next) {
129
+ if (current === next)
130
+ return undefined;
131
+ if (!TASK_TRANSITIONS[current].includes(next)) {
132
+ return `task status cannot move from "${current}" to "${next}"`;
133
+ }
134
+ return undefined;
135
+ }
136
+ /** Activate the task's current generation for one owner and return its capability id. */
137
+ export function activateTaskAttempt(task, assignee) {
138
+ const attemptId = randomUUID();
139
+ task.status = 'claimed';
140
+ task.assignee = assignee;
141
+ task.attemptId = attemptId;
142
+ task.handoffId = undefined;
143
+ task.reassigning = false;
144
+ task.output = undefined;
145
+ task.updatedAt = Date.now();
146
+ return attemptId;
147
+ }
148
+ /** Start a fresh task generation for one owner. */
149
+ export function beginTaskAttempt(task, assignee) {
150
+ task.attempt = (task.attempt ?? 0) + 1;
151
+ return activateTaskAttempt(task, assignee);
152
+ }
153
+ /**
154
+ * Revoke the current worker immediately. Clearing its capability makes old
155
+ * updates stale; a separate handoff generation serializes async quiescence.
156
+ */
157
+ /** Cancel one unfinished task without returning it to the ready pool. */
158
+ export function cancelUnfinishedTask(task, output) {
159
+ if (TERMINAL_TASK_STATUSES.includes(task.status))
160
+ return;
161
+ task.status = 'cancelled';
162
+ task.attemptId = undefined;
163
+ task.handoffId = undefined;
164
+ task.reassigning = false;
165
+ if (output !== undefined)
166
+ task.output = output;
167
+ task.updatedAt = Date.now();
168
+ }
169
+ export function invalidateTaskAttempt(task, nextAssignee, reassigning = false) {
170
+ task.attemptId = undefined;
171
+ task.handoffId = randomUUID();
172
+ task.status = 'pending';
173
+ task.assignee = nextAssignee;
174
+ task.reassigning = reassigning;
175
+ task.output = undefined;
176
+ task.updatedAt = Date.now();
177
+ }
178
+ /**
179
+ * Create the team directory structure and the initial team record.
180
+ * @param stateRoot - resolved absolute state root directory.
181
+ * @param state - the initial team record.
182
+ */
183
+ export async function createTeamDir(stateRoot, state) {
184
+ const dir = join(stateRoot, state.id);
185
+ await mkdir(join(dir, 'inbox'), { recursive: true });
186
+ await atomicWriteText(join(dir, 'team.json'), JSON.stringify(state, null, 2));
187
+ }
188
+ /**
189
+ * Read one team record; `undefined` when absent.
190
+ * @param stateRoot - resolved absolute state root directory.
191
+ * @param teamId - the team's sanitized id.
192
+ */
193
+ export async function readTeam(stateRoot, teamId) {
194
+ try {
195
+ const raw = await readFile(join(stateRoot, teamId, 'team.json'), 'utf8');
196
+ const value = JSON.parse(stripLeadingBom(raw));
197
+ const team = coerceTeamState(value, teamId);
198
+ if (team === undefined) {
199
+ throw new Error(`invalid T Team state in team "${teamId}"`);
200
+ }
201
+ return team;
202
+ }
203
+ catch (error) {
204
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
205
+ return undefined;
206
+ }
207
+ throw error;
208
+ }
209
+ }
210
+ /**
211
+ * Synchronously read one team record while a continuable child is being
212
+ * composed. Harness requires child setup contributions to be synchronous;
213
+ * this narrow boundary lets a cold-resumed member restore its durable model
214
+ * selection before its first request can be published.
215
+ * @param stateRoot - resolved absolute state root directory.
216
+ * @param teamId - the team's sanitized id.
217
+ * @returns the team record, or `undefined` when absent.
218
+ */
219
+ export function readTeamSync(stateRoot, teamId) {
220
+ try {
221
+ const raw = readFileSync(join(stateRoot, teamId, 'team.json'), 'utf8');
222
+ const value = JSON.parse(stripLeadingBom(raw));
223
+ const team = coerceTeamState(value, teamId);
224
+ if (team === undefined) {
225
+ throw new Error(`invalid T Team state in team "${teamId}"`);
226
+ }
227
+ return team;
228
+ }
229
+ catch (error) {
230
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
231
+ return undefined;
232
+ }
233
+ throw error;
234
+ }
235
+ }
236
+ /**
237
+ * Persist one team record (inside the caller's lock).
238
+ * @param stateRoot - resolved absolute state root directory.
239
+ * @param state - the record to persist.
240
+ */
241
+ export async function writeTeam(stateRoot, state) {
242
+ await atomicWriteText(join(stateRoot, state.id, 'team.json'), JSON.stringify(state, null, 2));
243
+ }
244
+ /** Read the durable set of member session ids retired by remove/delete. */
245
+ function parseRetiredMemberIds(raw) {
246
+ const parsed = JSON.parse(stripLeadingBom(raw));
247
+ if (!Array.isArray(parsed) || parsed.some(value => typeof value !== 'string' || value === '')) {
248
+ throw new Error('invalid T Team retired member index');
249
+ }
250
+ return new Set(parsed);
251
+ }
252
+ /** Synchronous role hydration before the host's first prompt assembly. */
253
+ export function readRetiredMemberIdsSync(stateRoot) {
254
+ try {
255
+ return parseRetiredMemberIds(readFileSync(join(stateRoot, RETIRED_MEMBERS_FILE), 'utf8'));
256
+ }
257
+ catch (error) {
258
+ if (error.code === 'ENOENT')
259
+ return new Set();
260
+ throw error;
261
+ }
262
+ }
263
+ export async function readRetiredMemberIds(stateRoot) {
264
+ try {
265
+ return parseRetiredMemberIds(await readFile(join(stateRoot, RETIRED_MEMBERS_FILE), 'utf8'));
266
+ }
267
+ catch (error) {
268
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
269
+ return new Set();
270
+ }
271
+ throw error;
272
+ }
273
+ }
274
+ /** Atomically add session ids to the durable retired-member deny-list. */
275
+ export async function recordRetiredMemberIds(stateRoot, memberIds) {
276
+ const additions = memberIds.filter(id => id !== '');
277
+ if (additions.length === 0)
278
+ return;
279
+ await withTeamLock(`retired-members:${stateRoot}`, async () => {
280
+ const retired = await readRetiredMemberIds(stateRoot);
281
+ for (const id of additions)
282
+ retired.add(id);
283
+ await mkdir(stateRoot, { recursive: true });
284
+ await atomicWriteText(join(stateRoot, RETIRED_MEMBERS_FILE), `${JSON.stringify([...retired].sort(), null, 2)}\n`);
285
+ });
286
+ }
287
+ /**
288
+ * Find the team owned by one captain session (at most one per captain).
289
+ * @param stateRoot - resolved absolute state root directory.
290
+ * @param captainSessionId - the owning session id.
291
+ * @returns the team record, or undefined when the captain leads no team.
292
+ */
293
+ export async function findTeamByCaptain(stateRoot, captainSessionId) {
294
+ let entries;
295
+ try {
296
+ entries = await readdir(stateRoot, { withFileTypes: true });
297
+ }
298
+ catch (error) {
299
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
300
+ return undefined;
301
+ }
302
+ throw error;
303
+ }
304
+ let found;
305
+ for (const entry of entries) {
306
+ if (!entry.isDirectory())
307
+ continue;
308
+ const team = await readTeam(stateRoot, entry.name);
309
+ if (team?.captainSessionId === captainSessionId) {
310
+ if (found !== undefined && found.id !== team.id) {
311
+ throw new Error(`captain session leads multiple active teams ("${found.id}", "${team.id}"); archive one before continuing`);
312
+ }
313
+ found = team;
314
+ }
315
+ }
316
+ return found;
317
+ }
318
+ /**
319
+ * Find the team in which one session is an active participant.
320
+ * Captains match `captainSessionId`; members match their durable child session
321
+ * id. Removed members no longer have access to team-scoped tools.
322
+ * @param stateRoot - resolved absolute state root directory.
323
+ * @param agentSessionId - calling captain/member session id.
324
+ * @returns the team record, or undefined when the caller belongs to no team.
325
+ */
326
+ export async function findTeamByParticipant(stateRoot, agentSessionId) {
327
+ let entries;
328
+ try {
329
+ entries = await readdir(stateRoot, { withFileTypes: true });
330
+ }
331
+ catch (error) {
332
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
333
+ return undefined;
334
+ }
335
+ throw error;
336
+ }
337
+ let found;
338
+ for (const entry of entries) {
339
+ if (!entry.isDirectory())
340
+ continue;
341
+ const team = await readTeam(stateRoot, entry.name);
342
+ const participates = team?.captainSessionId === agentSessionId
343
+ || team?.members.some((member) => member.id === agentSessionId && member.status !== 'removed') === true;
344
+ if (participates && team !== undefined) {
345
+ if (found !== undefined && found.id !== team.id) {
346
+ throw new Error(`agent session belongs to multiple active teams ("${found.id}", "${team.id}"); the target team is ambiguous`);
347
+ }
348
+ found = team;
349
+ }
350
+ }
351
+ return found;
352
+ }
353
+ /** Build a fresh message record. */
354
+ export function createMessage(from, to, content) {
355
+ return { id: randomUUID(), from, to, content, ts: Date.now() };
356
+ }
357
+ /**
358
+ * Append one message to an agent's mailbox (JSONL).
359
+ * @param stateRoot - resolved absolute state root directory.
360
+ * @param teamId - the team id.
361
+ * @param agentKey - `captain` or a member name.
362
+ * @param message - the message to append.
363
+ */
364
+ export async function appendMailbox(stateRoot, teamId, agentKey, message) {
365
+ const file = join(stateRoot, teamId, 'inbox', `${sanitizeKey(agentKey)}.jsonl`);
366
+ await mkdir(join(stateRoot, teamId, 'inbox'), { recursive: true });
367
+ let existing = '';
368
+ try {
369
+ existing = await readFile(file, 'utf8');
370
+ }
371
+ catch (error) {
372
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
373
+ throw error;
374
+ }
375
+ }
376
+ const separator = existing !== '' && !existing.endsWith('\n') ? '\n' : '';
377
+ await atomicWriteText(file, `${existing}${separator}${JSON.stringify(message)}\n`);
378
+ }
379
+ /**
380
+ * Read one agent's whole mailbox, oldest first.
381
+ * @param stateRoot - resolved absolute state root directory.
382
+ * @param teamId - the team id.
383
+ * @param agentKey - `captain` or a member name.
384
+ * @param onMalformedLine - optional diagnostic hook; malformed records are
385
+ * skipped so one manually damaged line cannot make the whole team unreadable.
386
+ * @returns the messages, empty when the mailbox does not exist yet.
387
+ */
388
+ export async function readMailbox(stateRoot, teamId, agentKey, onMalformedLine) {
389
+ const file = join(stateRoot, teamId, 'inbox', `${sanitizeKey(agentKey)}.jsonl`);
390
+ try {
391
+ const raw = await readFile(file, 'utf8');
392
+ const messages = [];
393
+ for (const [index, rawLine] of raw.split('\n').entries()) {
394
+ const line = stripLeadingBom(rawLine);
395
+ if (line.trim() === '')
396
+ continue;
397
+ let value;
398
+ try {
399
+ value = JSON.parse(line);
400
+ }
401
+ catch {
402
+ onMalformedLine?.(index + 1, new Error('invalid JSON'));
403
+ continue;
404
+ }
405
+ if (!isTeamMessage(value)) {
406
+ onMalformedLine?.(index + 1, new Error('invalid message shape'));
407
+ continue;
408
+ }
409
+ messages.push(value);
410
+ }
411
+ return messages;
412
+ }
413
+ catch (error) {
414
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
415
+ return [];
416
+ }
417
+ throw error;
418
+ }
419
+ }
420
+ /** Read only messages that have not been acknowledged by their recipient. */
421
+ export async function readUnreadMailbox(stateRoot, teamId, agentKey, onMalformedLine) {
422
+ const now = Date.now();
423
+ return (await readMailbox(stateRoot, teamId, agentKey, onMalformedLine))
424
+ .filter(message => message.readAt === undefined
425
+ && (message.deliveryClaimedAt === undefined
426
+ || now - message.deliveryClaimedAt >= MAILBOX_DELIVERY_LEASE_MS));
427
+ }
428
+ async function mutateMailbox(stateRoot, teamId, agentKey, messageIds, mutate) {
429
+ if (messageIds.length === 0)
430
+ return;
431
+ const file = join(stateRoot, teamId, 'inbox', `${sanitizeKey(agentKey)}.jsonl`);
432
+ let raw;
433
+ try {
434
+ raw = await readFile(file, 'utf8');
435
+ }
436
+ catch (error) {
437
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT')
438
+ return;
439
+ throw error;
440
+ }
441
+ const selected = new Set(messageIds);
442
+ const lines = raw.split('\n').map((rawLine) => {
443
+ const line = stripLeadingBom(rawLine);
444
+ if (line.trim() === '')
445
+ return rawLine;
446
+ try {
447
+ const value = JSON.parse(line);
448
+ if (!isTeamMessage(value) || !selected.has(value.id))
449
+ return rawLine;
450
+ return JSON.stringify(mutate(value));
451
+ }
452
+ catch {
453
+ return rawLine;
454
+ }
455
+ });
456
+ await atomicWriteText(file, lines.join('\n'));
457
+ }
458
+ /** Lease selected fallback messages to one delivery path. */
459
+ export async function claimMailboxDelivery(stateRoot, teamId, agentKey, messageIds) {
460
+ const now = Date.now();
461
+ await mutateMailbox(stateRoot, teamId, agentKey, messageIds, message => ({
462
+ ...message,
463
+ deliveryClaimedAt: now,
464
+ }));
465
+ }
466
+ /** Release a failed delivery lease so the scheduler can retry it later. */
467
+ export async function releaseMailboxDelivery(stateRoot, teamId, agentKey, messageIds) {
468
+ await mutateMailbox(stateRoot, teamId, agentKey, messageIds, (message) => {
469
+ const { deliveryClaimedAt: _claimed, ...released } = message;
470
+ return released;
471
+ });
472
+ }
473
+ /**
474
+ * Mark selected durable mailbox records delivered/read while preserving
475
+ * malformed lines for diagnostics. Callers serialize this with the team lock.
476
+ */
477
+ export async function acknowledgeMailbox(stateRoot, teamId, agentKey, messageIds) {
478
+ const now = Date.now();
479
+ await mutateMailbox(stateRoot, teamId, agentKey, messageIds, (message) => {
480
+ const { deliveryClaimedAt: _claimed, ...rest } = message;
481
+ return {
482
+ ...rest,
483
+ deliveredAt: message.deliveredAt ?? now,
484
+ readAt: message.readAt ?? now,
485
+ };
486
+ });
487
+ }
488
+ /** Remove the optional UTF-8 BOM some editors prepend to JSON text. */
489
+ function stripLeadingBom(value) {
490
+ return value.charCodeAt(0) === 0xFEFF ? value.slice(1) : value;
491
+ }
492
+ /** Rename attempts before falling back to a direct overwrite. */
493
+ const ATOMIC_RENAME_RETRIES = 3;
494
+ /** Pause between rename attempts, giving a briefly-locking owner time to finish. */
495
+ const ATOMIC_RENAME_RETRY_DELAY_MS = 50;
496
+ /**
497
+ * Rename error codes worth retrying before the direct-write fallback. On
498
+ * Windows, replacing an existing file whose target is momentarily held open
499
+ * without FILE_SHARE_DELETE surfaces as EPERM (or EACCES/EBUSY variants);
500
+ * EEXIST/ENOTEMPTY cover other "target busy" edge shapes.
501
+ */
502
+ const RETRYABLE_RENAME_CODES = new Set(['EPERM', 'EACCES', 'EBUSY', 'EEXIST', 'ENOTEMPTY']);
503
+ function isRetryableRenameError(error) {
504
+ return error instanceof Error
505
+ && 'code' in error
506
+ && RETRYABLE_RENAME_CODES.has(error.code ?? '');
507
+ }
508
+ function sleep(ms) {
509
+ return new Promise((resolve) => setTimeout(resolve, ms));
510
+ }
511
+ /**
512
+ * Replace `file` with `content`, preferring an atomic same-directory rename of
513
+ * an already-written temp file.
514
+ *
515
+ * On Windows, `rename(tmp, file)` over an existing target throws EPERM while
516
+ * any other process keeps the target open without FILE_SHARE_DELETE (editors,
517
+ * indexers, antivirus scans, preview panes). By that point the payload has
518
+ * already been fully written to the temp file, so a direct overwrite of the
519
+ * target is a content-equivalent degraded path: retry the rename a few times
520
+ * (transient locks clear quickly), then write the target in place. Every path
521
+ * removes the temp file; when both the atomic rename and the direct write
522
+ * fail, the combined error surfaces as an {@link AggregateError}.
523
+ *
524
+ * @returns nothing once the file has been replaced by one of the two paths.
525
+ */
526
+ export async function replaceFileAtomicOrDirect(temporary, file, content, primitives, options = {}) {
527
+ const retries = options.retries ?? ATOMIC_RENAME_RETRIES;
528
+ const retryDelayMs = options.retryDelayMs ?? ATOMIC_RENAME_RETRY_DELAY_MS;
529
+ for (let attempt = 0;; attempt += 1) {
530
+ try {
531
+ await primitives.rename(temporary, file);
532
+ return;
533
+ }
534
+ catch (error) {
535
+ if (isRetryableRenameError(error) && attempt < retries) {
536
+ await sleep(retryDelayMs);
537
+ continue;
538
+ }
539
+ let fallbackError;
540
+ try {
541
+ await primitives.writeFile(file, content);
542
+ }
543
+ catch (writeError) {
544
+ fallbackError = writeError;
545
+ }
546
+ await primitives.remove(temporary).catch(() => undefined);
547
+ if (fallbackError !== undefined) {
548
+ throw new AggregateError([error, fallbackError], `failed to replace "${file}" atomically (${String(error)}) or by direct write (${String(fallbackError)})`);
549
+ }
550
+ return;
551
+ }
552
+ }
553
+ }
554
+ /**
555
+ * Atomically replace one UTF-8 state file from a same-directory temp file,
556
+ * degrading to a direct overwrite when the atomic rename cannot proceed
557
+ * (see {@link replaceFileAtomicOrDirect} for the Windows EPERM rationale).
558
+ */
559
+ async function atomicWriteText(file, content) {
560
+ const temporary = `${file}.${process.pid}.${randomUUID()}.tmp`;
561
+ try {
562
+ await writeFile(temporary, content, { encoding: 'utf8', flag: 'wx' });
563
+ }
564
+ catch (error) {
565
+ await rm(temporary, { force: true }).catch(() => undefined);
566
+ throw error;
567
+ }
568
+ await replaceFileAtomicOrDirect(temporary, file, content, {
569
+ rename: (from, to) => rename(from, to),
570
+ writeFile: (target, payload) => writeFile(target, payload, 'utf8'),
571
+ remove: (path) => rm(path, { force: true }),
572
+ });
573
+ }
574
+ /** Whether a parsed JSON value is a plain record. */
575
+ function isRecord(value) {
576
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
577
+ }
578
+ /** Whether a value is an optional string. */
579
+ function isOptionalString(value) {
580
+ return value === undefined || typeof value === 'string';
581
+ }
582
+ /** Whether a value is a finite timestamp/counter number. */
583
+ function isFiniteNumber(value) {
584
+ return typeof value === 'number' && Number.isFinite(value);
585
+ }
586
+ /** Validate one member record at the durable JSON boundary. */
587
+ function isTeamMember(value) {
588
+ if (!isRecord(value))
589
+ return false;
590
+ return typeof value['id'] === 'string'
591
+ && typeof value['name'] === 'string'
592
+ && value['name'].trim() !== ''
593
+ && isOptionalString(value['role'])
594
+ && isOptionalString(value['provider'])
595
+ && isOptionalString(value['model'])
596
+ && isOptionalString(value['reasoningEffort'])
597
+ && isOptionalString(value['activeProvider'])
598
+ && isOptionalString(value['activeModel'])
599
+ && (value['executionPrompt'] === undefined || typeof value['executionPrompt'] === 'string')
600
+ && (value['fallback'] === undefined || (isRecord(value['fallback']) && typeof value['fallback']['provider'] === 'string' && typeof value['fallback']['model'] === 'string'))
601
+ && (value['fallbackActive'] === undefined || typeof value['fallbackActive'] === 'boolean')
602
+ && isFiniteNumber(value['joinedAt'])
603
+ && (value['status'] === 'idle' || value['status'] === 'working' || value['status'] === 'removed');
604
+ }
605
+ /** Validate one task record at the durable JSON boundary. */
606
+ function isTeamProfileSnapshot(value) {
607
+ return isRecord(value)
608
+ && typeof value['name'] === 'string'
609
+ && value['name'].trim() !== ''
610
+ && isOptionalString(value['description'])
611
+ && isOptionalString(value['protocol'])
612
+ && (value['executionPrompt'] === undefined || typeof value['executionPrompt'] === 'string')
613
+ && (value['fallback'] === undefined || (isRecord(value['fallback']) && typeof value['fallback']['provider'] === 'string' && typeof value['fallback']['model'] === 'string'))
614
+ && (value['taskPlanning'] === undefined || value['taskPlanning'] === 'captain' || value['taskPlanning'] === 'seed')
615
+ && (value['reviewPolicy'] === undefined || isReviewPolicy(value['reviewPolicy']));
616
+ }
617
+ function coerceProfileSnapshot(value) {
618
+ if (typeof value === 'string') {
619
+ const name = value.trim();
620
+ return name === '' ? undefined : { name };
621
+ }
622
+ if (!isRecord(value))
623
+ return undefined;
624
+ if (!isTeamProfileSnapshot(value))
625
+ return undefined;
626
+ return {
627
+ name: value.name.trim(),
628
+ ...value.description === undefined ? {} : { description: value.description },
629
+ ...value.protocol === undefined ? {} : { protocol: value.protocol },
630
+ ...value.taskPlanning === undefined ? {} : { taskPlanning: value.taskPlanning },
631
+ };
632
+ }
633
+ function coerceTeamState(value, expectedId) {
634
+ if (!isRecord(value))
635
+ return undefined;
636
+ if (value['profile'] !== undefined && !isTeamProfileSnapshot(value['profile']) && typeof value['profile'] !== 'string') {
637
+ const next = { ...value };
638
+ delete next['profile'];
639
+ value = next;
640
+ }
641
+ else if (typeof value['profile'] === 'string') {
642
+ const upgraded = coerceProfileSnapshot(value['profile']);
643
+ value = upgraded === undefined
644
+ ? (() => {
645
+ const next = { ...value };
646
+ delete next['profile'];
647
+ return next;
648
+ })()
649
+ : { ...value, profile: upgraded };
650
+ }
651
+ if (!isRecord(value) || !Array.isArray(value['tasks'])) {
652
+ return isTeamState(value, expectedId) ? value : undefined;
653
+ }
654
+ const tasks = value['tasks'].map((task) => {
655
+ if (!isRecord(task))
656
+ return task;
657
+ // Tolerate legacy dirty records instead of bricking the whole team on
658
+ // reload: blank optional fields written by older builds (or by models that
659
+ // materialize optionals as "") are normalized to omitted, matching the
660
+ // profileSeedId handling below and the tool-input normalization.
661
+ const cleaned = normalizeBlankOptionalTaskFields(task);
662
+ if (cleaned['profileSeedId'] !== undefined && (typeof cleaned['profileSeedId'] !== 'string' || cleaned['profileSeedId'].trim() === '')) {
663
+ const next = { ...cleaned };
664
+ delete next['profileSeedId'];
665
+ return next;
666
+ }
667
+ return cleaned;
668
+ });
669
+ const coerced = { ...value, tasks };
670
+ return isTeamState(coerced, expectedId) ? coerced : undefined;
671
+ }
672
+ export function isTeamTask(value) {
673
+ if (!isRecord(value))
674
+ return false;
675
+ return typeof value['id'] === 'string'
676
+ && isOptionalString(value['profileSeedId'])
677
+ && (value['profileSeedId'] === undefined || value['profileSeedId'].trim() !== '')
678
+ && typeof value['subject'] === 'string'
679
+ && isOptionalString(value['description'])
680
+ && (value['status'] === 'pending'
681
+ || value['status'] === 'claimed'
682
+ || value['status'] === 'in_progress'
683
+ || value['status'] === 'completed'
684
+ || value['status'] === 'failed'
685
+ || value['status'] === 'cancelled')
686
+ && isOptionalString(value['assignee'])
687
+ && Array.isArray(value['dependencies'])
688
+ && value['dependencies'].every((dependency) => typeof dependency === 'string')
689
+ && isOptionalString(value['output'])
690
+ && (value['attempt'] === undefined
691
+ || (Number.isSafeInteger(value['attempt']) && value['attempt'] >= 0))
692
+ && isOptionalString(value['attemptId'])
693
+ && isOptionalString(value['handoffId'])
694
+ && (value['reassigning'] === undefined || typeof value['reassigning'] === 'boolean')
695
+ && isFiniteNumber(value['createdAt'])
696
+ && isFiniteNumber(value['updatedAt'])
697
+ && hasValidQualityTaskFields(value);
698
+ }
699
+ /** Validate the full team record before it can participate in authorization. */
700
+ function isTeamState(value, expectedId) {
701
+ if (!isRecord(value))
702
+ return false;
703
+ const validShape = value['id'] === expectedId
704
+ && typeof value['name'] === 'string'
705
+ && value['name'].trim() !== ''
706
+ && isOptionalString(value['description'])
707
+ && (value['profile'] === undefined || isTeamProfileSnapshot(value['profile']))
708
+ && typeof value['captainSessionId'] === 'string'
709
+ && value['captainSessionId'] !== ''
710
+ && isFiniteNumber(value['createdAt'])
711
+ && Array.isArray(value['members'])
712
+ && value['members'].every(isTeamMember)
713
+ && Array.isArray(value['tasks'])
714
+ && value['tasks'].every(isTeamTask)
715
+ && Number.isSafeInteger(value['taskSeq'])
716
+ && value['taskSeq'] >= 0
717
+ && (value['phase'] === undefined || value['phase'] === 'staged' || value['phase'] === 'running')
718
+ && (value['planReviewState'] === undefined
719
+ || value['planReviewState'] === 'awaiting_review'
720
+ || value['planReviewState'] === 'awaiting_feedback')
721
+ && (value['approvedAt'] === undefined || isFiniteNumber(value['approvedAt']))
722
+ && (value['halted'] === undefined || typeof value['halted'] === 'boolean')
723
+ && (value['haltedAt'] === undefined || isFiniteNumber(value['haltedAt']))
724
+ && (value['reviewPolicy'] === undefined || isReviewPolicy(value['reviewPolicy']))
725
+ && (value['escalated'] === undefined || typeof value['escalated'] === 'boolean');
726
+ if (!validShape)
727
+ return false;
728
+ const members = value['members'];
729
+ const tasks = value['tasks'];
730
+ const memberIds = new Set();
731
+ const memberKeys = new Set();
732
+ const staged = value['phase'] === 'staged';
733
+ for (const member of members) {
734
+ const key = sanitizeKey(member.name);
735
+ if ((!staged && member.id === '') || key === CAPTAIN_KEY || memberKeys.has(key))
736
+ return false;
737
+ if (member.id !== '') {
738
+ if (memberIds.has(member.id))
739
+ return false;
740
+ memberIds.add(member.id);
741
+ }
742
+ memberKeys.add(key);
743
+ }
744
+ const taskIds = new Set();
745
+ for (const task of tasks) {
746
+ if (task.id === '' || taskIds.has(task.id))
747
+ return false;
748
+ taskIds.add(task.id);
749
+ }
750
+ return true;
751
+ }
752
+ /** Validate a mailbox record so later rendering cannot crash on `{}`/`null`. */
753
+ function isTeamMessage(value) {
754
+ if (!isRecord(value))
755
+ return false;
756
+ return typeof value['id'] === 'string'
757
+ && typeof value['from'] === 'string'
758
+ && typeof value['to'] === 'string'
759
+ && typeof value['content'] === 'string'
760
+ && isFiniteNumber(value['ts'])
761
+ && (value['deliveryClaimedAt'] === undefined || isFiniteNumber(value['deliveryClaimedAt']))
762
+ && (value['deliveredAt'] === undefined || isFiniteNumber(value['deliveredAt']))
763
+ && (value['readAt'] === undefined || isFiniteNumber(value['readAt']));
764
+ }
765
+ /**
766
+ * Remove a team's whole directory (members should be interrupted first).
767
+ * @param stateRoot - resolved absolute state root directory.
768
+ * @param teamId - the team id.
769
+ */
770
+ export async function removeTeamDir(stateRoot, teamId) {
771
+ await rm(join(stateRoot, teamId), { recursive: true, force: true });
772
+ }
773
+ /**
774
+ * `rename` with the same transient retry policy as the state-file atomic
775
+ * write, for paths (like archiving a whole team directory) where there is no
776
+ * content-equivalent direct-write degradation on Windows. A short-lived
777
+ * delete-sharing lock on any file below the renamed path is retried a few
778
+ * times before the error propagates.
779
+ * @param from - source path.
780
+ * @param to - destination path.
781
+ */
782
+ async function renameWithRetry(from, to) {
783
+ for (let attempt = 0;; attempt += 1) {
784
+ try {
785
+ await rename(from, to);
786
+ return;
787
+ }
788
+ catch (error) {
789
+ if (isRetryableRenameError(error) && attempt < ATOMIC_RENAME_RETRIES) {
790
+ await sleep(ATOMIC_RENAME_RETRY_DELAY_MS);
791
+ continue;
792
+ }
793
+ throw error;
794
+ }
795
+ }
796
+ }
797
+ /**
798
+ * Archive a team instead of deleting it: the whole directory (team.json with
799
+ * tasks and dependency graph, plus the mailboxes) moves under
800
+ * `<stateRoot>/archive/<teamId>/` so later sessions can review how tasks were
801
+ * planned and rebuild dependency relationships. The archive directory has no
802
+ * team.json of its own, so the live activity scan skips it naturally.
803
+ * @param stateRoot - resolved absolute state root directory.
804
+ * @param teamId - the team id.
805
+ */
806
+ export async function archiveTeamDir(stateRoot, teamId) {
807
+ const archiveRoot = join(stateRoot, 'archive');
808
+ await mkdir(archiveRoot, { recursive: true });
809
+ const source = join(stateRoot, teamId);
810
+ const target = join(archiveRoot, teamId);
811
+ const previous = join(archiveRoot, `.${teamId}.previous-${randomUUID()}`);
812
+ let displaced = false;
813
+ try {
814
+ // The same Windows EPERM-on-rename applies at the directory boundary: a
815
+ // delete-sharing violation on any file below `target` blocks the move, so
816
+ // retry the transient-lock case before giving up.
817
+ await renameWithRetry(target, previous);
818
+ displaced = true;
819
+ }
820
+ catch (error) {
821
+ // Only ENOENT means there was nothing to displace; any other failure
822
+ // (including a persistent EPERM lock) surfaces to the caller.
823
+ if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT')) {
824
+ throw error;
825
+ }
826
+ }
827
+ try {
828
+ await renameWithRetry(source, target);
829
+ }
830
+ catch (error) {
831
+ if (displaced) {
832
+ try {
833
+ await renameWithRetry(previous, target);
834
+ }
835
+ catch (restoreError) {
836
+ throw new AggregateError([error, restoreError], `failed to archive team "${teamId}" and restore its previous archive`);
837
+ }
838
+ }
839
+ throw error;
840
+ }
841
+ // The new generation is authoritative. A failed cleanup only leaves a
842
+ // hidden recovery directory, which archive discovery deliberately ignores.
843
+ if (displaced)
844
+ await rm(previous, { recursive: true, force: true }).catch(() => undefined);
845
+ }
846
+ /**
847
+ * Read one archived team (already moved under `archive/`), or undefined when
848
+ * it was never archived.
849
+ * @param stateRoot - resolved absolute state root directory.
850
+ * @param teamId - the team id.
851
+ */
852
+ export async function readArchivedTeam(stateRoot, teamId) {
853
+ return readTeam(join(stateRoot, 'archive'), teamId);
854
+ }
855
+ /**
856
+ * List every archived team id under the state root.
857
+ * @param stateRoot - resolved absolute state root directory.
858
+ * @returns the archived team ids, empty when the archive does not exist.
859
+ */
860
+ export async function listArchivedTeamIds(stateRoot) {
861
+ try {
862
+ const entries = await readdir(join(stateRoot, 'archive'), { withFileTypes: true });
863
+ return entries
864
+ .filter((entry) => entry.isDirectory() && !entry.name.startsWith('.'))
865
+ .map((entry) => entry.name);
866
+ }
867
+ catch (error) {
868
+ if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
869
+ return [];
870
+ }
871
+ throw error;
872
+ }
873
+ }
874
+ /**
875
+ * The visual state of one task: `running` while in_progress, `completed`
876
+ * when done, `failed`/`cancelled` when terminal without success, `blocked`
877
+ * while any dependency is unfinished, else `open`.
878
+ */
879
+ export function taskVisualState(status, dependencies, tasks) {
880
+ if (status === 'completed')
881
+ return 'completed';
882
+ if (status === 'failed')
883
+ return 'failed';
884
+ if (status === 'cancelled')
885
+ return 'cancelled';
886
+ if (status === 'in_progress')
887
+ return 'running';
888
+ const byId = new Map(tasks.map((task) => [task.id, task]));
889
+ const openDependency = dependencies.some((dependencyId) => {
890
+ const dependency = byId.get(dependencyId);
891
+ return dependency !== undefined && dependency.status !== 'completed';
892
+ });
893
+ return openDependency ? 'blocked' : 'open';
894
+ }
895
+ /**
896
+ * Longest dependency path depth per task id (each depth = one lane column).
897
+ */
898
+ export function taskDepthsById(tasks) {
899
+ const byId = new Map(tasks.map((task) => [task.id, task]));
900
+ const depths = new Map();
901
+ const visiting = new Set();
902
+ const depthOf = (taskId) => {
903
+ const cached = depths.get(taskId);
904
+ if (cached !== undefined)
905
+ return cached;
906
+ if (visiting.has(taskId))
907
+ return 0;
908
+ const task = byId.get(taskId);
909
+ if (task === undefined)
910
+ return 0;
911
+ visiting.add(taskId);
912
+ const dependencies = task.dependencies
913
+ .filter((dependencyId) => byId.has(dependencyId))
914
+ .sort();
915
+ const depth = dependencies.length === 0
916
+ ? 0
917
+ : 1 + Math.max(...dependencies.map(depthOf));
918
+ visiting.delete(taskId);
919
+ depths.set(taskId, depth);
920
+ return depth;
921
+ };
922
+ for (const task of tasks)
923
+ depthOf(task.id);
924
+ return depths;
925
+ }