agentworkshop 0.7.41 → 0.7.44

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 (265) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  11. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  12. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  13. package/.output/public/_nuxt/{DGbd-UiG.js → EHlEMRii.js} +1 -1
  14. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  15. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  16. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  17. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  18. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  19. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  20. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  21. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  22. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  23. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  24. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  25. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  26. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  27. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  28. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  29. package/.output/public/_nuxt/builds/latest.json +1 -1
  30. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  31. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  32. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  33. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  34. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  35. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  36. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  37. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  38. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  39. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  40. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  41. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  42. package/.output/server/chunks/_/nitro.mjs +748 -733
  43. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  44. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{TownView--otsnr_7.mjs → TownView-CJ6IsDCc.mjs} +226 -225
  46. package/.output/server/chunks/build/{TownView--otsnr_7.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  47. package/.output/server/chunks/build/{_id_-Brv-JSvr.mjs → _id_-CVD_H0ir.mjs} +143 -142
  48. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  49. package/.output/server/chunks/build/{_id_-D9pl1OZy.mjs → _id_-CtxMb3qp.mjs} +41 -41
  50. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  51. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs → _wsId_-DpY3T-md.mjs} +514 -444
  52. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  54. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  55. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  56. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  57. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  58. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  59. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  60. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  61. package/.output/server/chunks/build/{daq-DkMvD8sg.mjs → daq-DWWSKGAV.mjs} +89 -88
  62. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  63. package/.output/server/chunks/build/{dcw-C1jgVJkO.mjs → dcw-CT8k17_E.mjs} +44 -43
  64. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  65. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  66. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  68. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{pages-B0bUWnC6.mjs → pages-BHcSBM2D.mjs} +24 -18
  70. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  71. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  72. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  73. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  74. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  75. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  76. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  77. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  78. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  79. package/.output/server/chunks/build/{town-CoQdblbZ.mjs → town-QJWB5wng.mjs} +3 -2
  80. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  81. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  82. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  83. package/.output/server/chunks/routes/renderer.mjs +1 -1
  84. package/.output/server/chunks/virtual/entry.mjs +637 -86
  85. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  86. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  87. package/.output/server/package.json +1 -1
  88. package/README-zh.md +13 -3
  89. package/README.md +13 -3
  90. package/app/components/AppHeader.vue +961 -959
  91. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  92. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  93. package/app/components/workshop/AgentLanesView.vue +998 -998
  94. package/app/components/workshop/ArtifactCard.vue +120 -120
  95. package/app/components/workshop/AssetLibrary.vue +452 -452
  96. package/app/components/workshop/ChannelSessionList.vue +812 -804
  97. package/app/components/workshop/CommandPalette.vue +269 -269
  98. package/app/components/workshop/Composer.vue +827 -827
  99. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  100. package/app/components/workshop/EventBlock.vue +602 -602
  101. package/app/components/workshop/FileSelectorModal.vue +188 -188
  102. package/app/components/workshop/InspectorPanel.vue +301 -301
  103. package/app/components/workshop/MemoryPanel.vue +364 -364
  104. package/app/components/workshop/MultiChannelView.vue +189 -189
  105. package/app/components/workshop/TaskBoardView.vue +686 -686
  106. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  107. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  108. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  109. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  110. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  111. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  112. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  113. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  114. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  115. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  116. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  117. package/app/components/workshop/town/TownView.vue +56 -56
  118. package/app/middleware/locale-cookie.global.ts +22 -0
  119. package/app/pages/aml/index.vue +24 -2
  120. package/app/pages/daq/[id].vue +11 -11
  121. package/app/pages/daq/index.vue +2316 -2316
  122. package/app/pages/dcw/[id].vue +18 -19
  123. package/app/pages/dcw/index.vue +3 -3
  124. package/app/pages/index.vue +987 -981
  125. package/app/pages/plugins/index.vue +317 -311
  126. package/app/pages/settings.vue +1564 -1562
  127. package/app/pages/tokens.vue +15 -15
  128. package/app/pages/workshop/agents.vue +612 -609
  129. package/app/pages/workshop/channel-templates.vue +18 -11
  130. package/app/pages/workshop/index.vue +668 -668
  131. package/app/pages/workshop/teams.vue +743 -740
  132. package/app/pages/workshop/w/[wsId].vue +704 -704
  133. package/app/plugins/locale-restore.client.ts +16 -1
  134. package/app/utils/builtin-catalog.ts +104 -0
  135. package/config.yml +170 -170
  136. package/docs/full-test-plan.md +2 -3
  137. package/i18n/locales/en.ts +293 -35
  138. package/i18n/locales/zh-CN.ts +265 -7
  139. package/package.json +2 -2
  140. package/scripts/_dbg-perms-e2e.mjs +132 -109
  141. package/scripts/_demo-agentteam-capture.mjs +278 -0
  142. package/scripts/_e2e-ui-functional.mjs +371 -0
  143. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  144. package/scripts/_i18n-en-verify.mjs +107 -0
  145. package/scripts/_i18n-extra-checks.mjs +53 -0
  146. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  147. package/scripts/_i18n-verify-keys.mjs +50 -0
  148. package/scripts/_loop-biax-campaign.mjs +15 -9
  149. package/scripts/_sweep-button-contrast.mjs +87 -0
  150. package/scripts/ui/record-demo.mjs +557 -0
  151. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  152. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  153. package/server/services/workshop/daq/drivers.ts +11 -0
  154. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  155. package/.output/public/_nuxt/BN16aRdQ.js +0 -1
  156. package/.output/public/_nuxt/BWVhZntO.js +0 -1
  157. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  158. package/.output/public/_nuxt/BsROmY89.js +0 -1
  159. package/.output/public/_nuxt/C0ZrS1KL.js +0 -19
  160. package/.output/public/_nuxt/C4s_niy-.js +0 -14
  161. package/.output/public/_nuxt/D6mbFA-a.js +0 -1
  162. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  163. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  164. package/.output/public/_nuxt/Dap5PmTL.js +0 -2
  165. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  166. package/.output/public/_nuxt/DvLxILQP.js +0 -1
  167. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  168. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  169. package/.output/public/_nuxt/_id_.D9ymtHB5.css +0 -1
  170. package/.output/public/_nuxt/_id_.jf-_8E_c.css +0 -1
  171. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  172. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  173. package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +0 -1
  174. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  175. package/.output/public/_nuxt/daq.DjSgY94l.css +0 -1
  176. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  177. package/.output/public/_nuxt/eqq3wWQF.js +0 -18
  178. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  179. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  180. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  181. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  182. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  183. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  184. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  185. package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +0 -1
  186. package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +0 -1
  187. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  188. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  189. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  190. package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +0 -1
  191. package/.output/server/chunks/build/dcw-C1jgVJkO.mjs.map +0 -1
  192. package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +0 -1
  193. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  194. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  195. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  196. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  197. package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +0 -1
  198. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  199. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  200. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  201. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  202. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  203. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  204. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  205. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  206. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  207. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  208. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  209. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  210. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  211. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  212. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  213. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  214. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  215. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  216. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  217. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  218. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  219. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  220. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  221. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  222. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  223. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  224. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  225. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  226. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  227. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  228. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  229. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  230. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  231. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  232. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  233. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  234. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  235. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  236. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  237. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  238. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  239. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  240. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  241. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  242. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  243. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  244. package/scripts/_dbg-minimal-omp.mjs +0 -77
  245. package/scripts/_dbg-mode-nav.mjs +0 -173
  246. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  247. package/scripts/_dbg-team-repro.mjs +0 -76
  248. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  249. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  250. package/scripts/_townview_style.css +0 -1185
  251. package/scripts/_townview_template.html +0 -1215
  252. package/server/data/agent-node-bindings.json +0 -554
  253. package/server/data/daq-templates.json +0 -16
  254. package/server/data/daqs.json +0 -7464
  255. package/server/data/dcw-lines.json +0 -1787
  256. package/server/data/dcw-products.json +0 -1626
  257. package/server/data/dcw-recipes.json +0 -4672
  258. package/server/data/dcw-rollback.json +0 -20754
  259. package/server/data/dcw-runs.json +0 -6354
  260. package/server/data/dcw-templates.json +0 -146
  261. package/server/data/dcw-writes.json +0 -15650
  262. package/server/data/dcws.json +0 -9033
  263. package/server/data/device-twins.json +0 -133
  264. package/server/data/line-runs.json +0 -322
  265. package/server/data/scene-layouts.json +0 -277
@@ -1,740 +1,743 @@
1
- <script setup lang="ts">
2
- /**
3
- * AgentTeam 编组库:用户级隔离的编组 CRUD + 成员管理 + 一键 deploy 到 Channel。
4
- * v10 可见性:private 仅本人;public 全员可读可用(仅属主可改删);内置(锁)不可变更。
5
- * admin:全量视图(含他人私有),附创建者;可改删任意非内置编组。
6
- */
7
- import { message } from 'ant-design-vue'
8
- import { useWorkshopApi, type TeamDto, type AgentTemplateDto, type ChannelDto, type WorkshopPluginDto, type ChannelPluginStateDto } from '../../composables/workshop/useWorkshopApi'
9
- import { useUserStore } from '../../stores/workshop/user'
10
-
11
- const { t } = useI18n()
12
-
13
- definePageMeta({ layout: 'default' })
14
-
15
- const api = useWorkshopApi()
16
- const userStore = useUserStore()
17
- const teams = ref<TeamDto[]>([])
18
- const templates = ref<AgentTemplateDto[]>([])
19
- const channels = ref<ChannelDto[]>([])
20
- const loading = ref(false)
21
-
22
- const load = async (): Promise<void> => {
23
- loading.value = true
24
- try {
25
- const [t, tpl, ch] = await Promise.all([api.listTeams(), api.listTemplates(), api.listChannels()])
26
- teams.value = (t as unknown as { data?: TeamDto[] })?.data ?? []
27
- templates.value = (tpl as unknown as { data?: AgentTemplateDto[] })?.data ?? []
28
- channels.value = (ch as unknown as { data?: ChannelDto[] })?.data ?? []
29
- }
30
- finally {
31
- loading.value = false
32
- }
33
- }
34
- // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求(同 agents 页注释)
35
- if (import.meta.client) void load()
36
-
37
- // ===== harness 可用性(成员选择禁用引擎未安装的模板;deploy 由后端 assert 兜底) =====
38
- const harnessAvail = ref<Record<string, boolean>>({})
39
- const loadHarnessAvail = async (): Promise<void> => {
40
- try {
41
- const res = await api.listHarnesses()
42
- const list = (res as unknown as { data?: { harnesses?: Array<{ id: string, available?: boolean }> } })?.data?.harnesses
43
- if (Array.isArray(list)) {
44
- const m: Record<string, boolean> = {}
45
- for (const h of list) m[h.id] = h.available !== false
46
- harnessAvail.value = m
47
- }
48
- }
49
- catch { /* 探测不可得时不限制选项(后端仍有强校验) */ }
50
- }
51
- if (import.meta.client) void loadHarnessAvail()
52
- const tplUnavailable = (tpl: AgentTemplateDto): boolean => harnessAvail.value[tpl.harness] === false
53
- const memberOptions = computed(() => templates.value.map((tpl) => {
54
- const un = tplUnavailable(tpl)
55
- return { value: tpl.id, label: un ? `${tpl.name}(${tpl.harness} · ${t('agents.notInstalled')})` : `${tpl.name}(${tpl.harness})`, disabled: un }
56
- }))
57
-
58
- // ===== 过滤 =====
59
- type Filter = 'all' | 'mine' | 'public' | 'builtin'
60
- const filter = ref<Filter>('all')
61
- const shown = computed(() => {
62
- const uid = userStore.user?.id
63
- switch (filter.value) {
64
- case 'mine': return teams.value.filter(t => t.ownerUserId === uid)
65
- case 'public': return teams.value.filter(t => t.visibility === 'public')
66
- case 'builtin': return teams.value.filter(t => t.isBuiltin)
67
- default: return teams.value
68
- }
69
- })
70
-
71
- const canWrite = (team: TeamDto): boolean =>
72
- !team.isBuiltin && (team.ownerUserId === userStore.user?.id || userStore.isAdmin)
73
-
74
- const visTag = (team: TeamDto): { text: string, color: string, icon?: string } => {
75
- if (team.isBuiltin) return { text: t('teams.k3x23c018'), color: 'default', icon: 'i-tabler-lock' }
76
- if (team.visibility === 'public') return { text: t('teams.k3wv1t019'), color: 'green' }
77
- return { text: t('teams.k447jj020'), color: 'default' }
78
- }
79
-
80
- const createOpen = ref(false)
81
- const createForm = reactive({ name: '', description: '', visibility: 'private' as 'private' | 'public' })
82
-
83
- // ===== 插件(创建时勾选启用哪些;平台清单中 enabled 的插件,默认全选) =====
84
- const platformPlugins = ref<WorkshopPluginDto[]>([])
85
- const createPluginSel = ref<Record<string, boolean>>({})
86
- const loadPlatformPlugins = async (): Promise<void> => {
87
- try {
88
- const res = await api.listPlugins()
89
- // 顶层 plugins key(非信封);兼容 {code,data} 信封;只取平台已启用的插件
90
- const list = res?.plugins ?? (res as { data?: { plugins?: WorkshopPluginDto[] } })?.data?.plugins ?? []
91
- const enabled = (Array.isArray(list) ? list : []).filter(p => p.enabled !== false)
92
- platformPlugins.value = enabled
93
- const sel: Record<string, boolean> = {}
94
- for (const p of enabled) sel[p.name] = true
95
- createPluginSel.value = sel
96
- }
97
- catch { /* 清单不可得时隐藏插件多选(创建仍可走默认) */ }
98
- }
99
- if (import.meta.client) void loadPlatformPlugins()
100
-
101
- /** checkbox-group 双向绑定(勾选集 ↔ plugins 开关视图) */
102
- const pluginChecked = computed({
103
- get: () => Object.entries(createPluginSel.value).filter(([, v]) => v).map(([k]) => k),
104
- set: (vals: Array<string>) => {
105
- const next: Record<string, boolean> = {}
106
- for (const p of platformPlugins.value) next[p.name] = vals.includes(p.name)
107
- createPluginSel.value = next
108
- },
109
- })
110
-
111
- const create = async (): Promise<void> => {
112
- if (!createForm.name.trim()) {
113
- message.warning(t('teams.k1bvcdo2021'))
114
- return
115
- }
116
- // 勾选结果作为 plugins:[{name,enabled}] 附加(后端创建时写入该团队的插件开关)
117
- const plugins = Object.entries(createPluginSel.value).map(([name, enabled]) => ({ name, enabled }))
118
- await api.createTeam({ name: createForm.name.trim(), description: createForm.description || undefined, visibility: createForm.visibility, plugins })
119
- message.success(t('teams.k3n5hak022'))
120
- createOpen.value = false
121
- createForm.name = ''
122
- createForm.description = ''
123
- createForm.visibility = 'private'
124
- createPluginSel.value = Object.fromEntries(Object.entries(createPluginSel.value).map(([k]) => [k, true]))
125
- void load()
126
- }
127
-
128
- /** 一键切换可见性(属主/admin) */
129
- const toggleVisibility = async (team: TeamDto, pub: boolean): Promise<void> => {
130
- try {
131
- await api.updateTeam(team.id, { visibility: pub ? 'public' : 'private' })
132
- message.success(pub ? t('teams.k1globhp023') : t('teams.k1xxabaf024'))
133
- void load()
134
- }
135
- catch (e) {
136
- message.error(apiErrorMessage(e))
137
- }
138
- }
139
-
140
- const addOpen = ref(false)
141
- const addTeam = ref<TeamDto | null>(null)
142
- const addTemplateId = ref<string>('')
143
- const addRole = ref<'lead' | 'worker'>('worker')
144
- const openAdd = (team: TeamDto): void => {
145
- addTeam.value = team
146
- addTemplateId.value = ''
147
- addRole.value = 'worker'
148
- addOpen.value = true
149
- }
150
- const addMember = async (): Promise<void> => {
151
- if (!addTeam.value || !addTemplateId.value) {
152
- message.warning(t('teams.k1kw4rtj025'))
153
- return
154
- }
155
- try {
156
- await api.addTeamMember(addTeam.value.id, { agentId: addTemplateId.value, role: addRole.value })
157
- message.success(t('teams.k3n5hzw026'))
158
- addOpen.value = false
159
- void load()
160
- }
161
- catch (e) {
162
- message.error(apiErrorMessage(e))
163
- }
164
- }
165
- const removeMember = async (team: TeamDto, templateId: string): Promise<void> => {
166
- await api.removeTeamMember(team.id, templateId)
167
- void load()
168
- }
169
-
170
- const deployOpen = ref(false)
171
- const deployTeamRef = ref<TeamDto | null>(null)
172
- const deployChannelId = ref<string>('')
173
- const deploying = ref(false)
174
- const openDeploy = (team: TeamDto): void => {
175
- deployTeamRef.value = team
176
- deployChannelId.value = ''
177
- deployOpen.value = true
178
- }
179
- const deploy = async (): Promise<void> => {
180
- if (!deployTeamRef.value || !deployChannelId.value) {
181
- message.warning('选择目标 Channel')
182
- return
183
- }
184
- deploying.value = true
185
- try {
186
- const res = await api.deployTeam(deployTeamRef.value.id, deployChannelId.value)
187
- const agents = (res as unknown as { data?: { agents?: unknown[] } })?.data?.agents?.length ?? 0
188
- message.success(t('teams.kh90glh031', { p0: agents }))
189
- deployOpen.value = false
190
- }
191
- catch (e) {
192
- message.error(apiErrorMessage(e))
193
- }
194
- finally {
195
- deploying.value = false
196
- }
197
- }
198
-
199
- const removeTeam = async (team: TeamDto): Promise<void> => {
200
- await api.deleteTeam(team.id)
201
- message.success(t('teams.k1oxjrxx027'))
202
- void load()
203
- }
204
-
205
- // ===== 团队级插件开关(GET → 展示各插件开关;切换即 PUT) =====
206
- const plugOpen = ref(false)
207
- const plugTeamRef = ref<TeamDto | null>(null)
208
- const plugRows = ref<ChannelPluginStateDto[]>([])
209
- const plugSource = ref<'explicit' | 'default'>('default')
210
- const plugLoading = ref(false)
211
- const plugSaving = ref<string | null>(null)
212
- const openPlugins = (team: TeamDto): void => {
213
- plugTeamRef.value = team
214
- plugRows.value = []
215
- plugSource.value = 'default'
216
- plugOpen.value = true
217
- void loadTeamPlugins(team)
218
- }
219
- const loadTeamPlugins = async (team: TeamDto): Promise<void> => {
220
- plugLoading.value = true
221
- try {
222
- const res = await api.listTeamPlugins(team.id)
223
- const data = res?.data ?? {}
224
- plugRows.value = data.plugins ?? []
225
- plugSource.value = data.source === 'explicit' ? 'explicit' : 'default'
226
- }
227
- catch (e) {
228
- message.error(apiErrorMessage(e))
229
- }
230
- finally {
231
- plugLoading.value = false
232
- }
233
- }
234
- const toggleTeamPlugin = async (row: ChannelPluginStateDto, next: boolean): Promise<void> => {
235
- if (!plugTeamRef.value || plugSaving.value) return
236
- const teamId = plugTeamRef.value.id
237
- plugSaving.value = row.name
238
- try {
239
- // PUT 全量提交当前开关视图(仅翻转目标行)
240
- const payload = plugRows.value.map(r => ({ name: r.name, enabled: r.name === row.name ? next : r.enabled }))
241
- const res = await api.putTeamPlugins(teamId, { plugins: payload })
242
- const data = res?.data ?? {}
243
- plugRows.value = data.plugins ?? payload.map(p => ({ ...p }))
244
- plugSource.value = data.source === 'explicit' ? 'explicit' : 'default'
245
- message.success(t('teams.k1plugon043'))
246
- }
247
- catch (e) {
248
- message.error(apiErrorMessage(e))
249
- }
250
- finally {
251
- plugSaving.value = null
252
- }
253
- }
254
-
255
- useHead({ title: () => t('titles.teams') })
256
- </script>
257
-
258
- <template>
259
- <div class="page">
260
- <div class="head">
261
- <div>
262
- <h2>AgentTeam {{ $t('teams.k3svl8y028') }}</h2>
263
- <p class="sub">
264
- {{ $t('teams.k12gl4wn008') }}
265
- </p>
266
- </div>
267
- <a-space>
268
- <a-button @click="navigateTo('/workshop')">
269
- {{ $t('teams.krpx6qa009') }}
270
- </a-button>
271
- <a-button
272
- type="primary"
273
- @click="createOpen = true"
274
- >
275
- {{ $t('teams.k1efmuyx002') }}
276
- </a-button>
277
- </a-space>
278
- </div>
279
-
280
- <div class="toolbar">
281
- <a-segmented
282
- v-model:value="filter"
283
- size="small"
284
- :options="[
285
- { value: 'all', label: $t('chips.chipAll', { n: teams.length }) },
286
- { value: 'mine', label: $t('chips.mine') },
287
- { value: 'public', label: $t('chips.public') },
288
- { value: 'builtin', label: $t('chips.builtin') },
289
- ]"
290
- />
291
- <span
292
- v-if="userStore.isAdmin"
293
- class="admin-note"
294
- ><span class="i-tabler-shield-check" /> {{ $t('teams.k1bpgi8h010') }}</span>
295
- </div>
296
-
297
- <a-spin :spinning="loading">
298
- <div class="grid">
299
- <div
300
- v-for="team in shown"
301
- :key="team.id"
302
- class="card"
303
- >
304
- <div class="card-head">
305
- <span class="name">{{ team.name }}</span>
306
- <a-tag
307
- :color="visTag(team).color"
308
- class="vis-tag"
309
- >
310
- <span
311
- v-if="visTag(team).icon"
312
- :class="visTag(team).icon"
313
- />{{ visTag(team).text }}
314
- </a-tag>
315
- <!-- 开关不带文字:左边那枚 tag 已经在说"公开/私有"了(与模板库同一条规则) -->
316
- <a-switch
317
- v-if="!team.isBuiltin && canWrite(team)"
318
- :checked="team.visibility === 'public'"
319
- size="small"
320
- :title="team.visibility === 'public' ? '点击转为私有' : '点击公开(全员可部署)'"
321
- @change="(v: unknown) => toggleVisibility(team, v === true)"
322
- />
323
- <span class="owner">{{ team.ownerName ?? '-' }}</span>
324
- <a-dropdown v-if="canWrite(team)">
325
- <span class="i-tabler-dots op" />
326
- <template #overlay>
327
- <a-menu>
328
- <a-menu-item @click="openDeploy(team)">
329
- 部署到 Channel…
330
- </a-menu-item>
331
- <a-menu-item @click="openPlugins(team)">
332
- {{ $t('teams.k1plugon047') }}
333
- </a-menu-item>
334
- <a-menu-item
335
- danger
336
- @click="removeTeam(team)"
337
- >
338
- {{ $t('teams.k1bpojhf011') }}
339
- </a-menu-item>
340
- </a-menu>
341
- </template>
342
- </a-dropdown>
343
- <!-- 用 outlined 而不是 text:text 按钮在卡片头上与旁边的 owner 文本
344
- 长得一模一样,用户看不出这里是**唯一的主操作**(实测三张卡片都如此)。 -->
345
- <a-button
346
- v-else
347
- class="deploy-btn"
348
- size="small"
349
- type="default"
350
- :title="$t('teams.kxheuf1001')"
351
- @click="openDeploy(team)"
352
- >
353
- {{ $t('teams.k48ja7012') }}
354
- </a-button>
355
- </div>
356
- <div class="members">
357
- <div
358
- v-for="m in team.members"
359
- :key="m.templateId"
360
- class="member"
361
- >
362
- <a-tag
363
- :color="m.role === 'lead' ? 'gold' : 'blue'"
364
- class="role"
365
- >
366
- {{ m.role }}
367
- </a-tag>
368
- <span class="member-name">{{ m.name }}</span>
369
- <span class="member-harness">{{ m.harness }}</span>
370
- <span
371
- v-if="canWrite(team)"
372
- class="i-tabler-x rm"
373
- @click="removeMember(team, m.templateId)"
374
- />
375
- </div>
376
- <a-button
377
- v-if="canWrite(team)"
378
- size="small"
379
- type="dashed"
380
- block
381
- @click="openAdd(team)"
382
- >
383
- {{ $t('teams.krt2oib013') }}
384
- </a-button>
385
- </div>
386
- </div>
387
- <div
388
- v-if="shown.length === 0"
389
- class="card placeholder"
390
- @click="createOpen = true"
391
- >
392
- <span class="i-tabler-plus big" />
393
- <span>{{ $t('teams.k6cbbgf014') }}</span>
394
- </div>
395
- </div>
396
- </a-spin>
397
-
398
- <a-modal
399
- v-model:open="createOpen"
400
- :title="$t('teams.k1efmuyx002')"
401
- ok-text="创建"
402
- cancel-text="取消"
403
- @ok="create"
404
- >
405
- <a-form layout="vertical">
406
- <a-form-item :label="$t('teams.k3xhia003')">
407
- <a-input v-model:value="createForm.name" />
408
- </a-form-item>
409
- <a-form-item :label="$t('teams.k40gkk004')">
410
- <a-input v-model:value="createForm.description" />
411
- </a-form-item>
412
- <a-form-item :label="$t('teams.k3lrqn0005')">
413
- <a-radio-group v-model:value="createForm.visibility">
414
- <a-radio value="private">
415
- {{ $t('teams.k17jfge3015') }}
416
- </a-radio>
417
- <a-radio value="public">
418
- {{ $t('teams.k1h2lq0o016') }}
419
- </a-radio>
420
- </a-radio-group>
421
- </a-form-item>
422
- <a-form-item
423
- v-if="platformPlugins.length"
424
- :label="$t('teams.k1plugon041')"
425
- >
426
- <a-checkbox-group
427
- v-model:value="pluginChecked"
428
- class="plug-create-group"
429
- >
430
- <a-checkbox
431
- v-for="p in platformPlugins"
432
- :key="p.name"
433
- :value="p.name"
434
- >
435
- <span class="aw-mono">{{ p.name }}</span>
436
- <span
437
- v-if="p.builtin"
438
- class="plug-builtin-tag"
439
- >{{ $t('teams.k3x23c018') }}</span>
440
- </a-checkbox>
441
- </a-checkbox-group>
442
- <div class="plug-hint">
443
- {{ $t('teams.k1plugon042') }}
444
- </div>
445
- </a-form-item>
446
- </a-form>
447
- </a-modal>
448
-
449
- <a-modal
450
- v-model:open="addOpen"
451
- :title="$t('teams.k6ljhyv029', { p0: addTeam?.name ?? '' })"
452
- ok-text="加入"
453
- cancel-text="取消"
454
- @ok="addMember"
455
- >
456
- <a-form layout="vertical">
457
- <a-form-item :label="$t('teams.k41ds5006')">
458
- <a-select
459
- v-model:value="addTemplateId"
460
- :options="memberOptions"
461
- />
462
- </a-form-item>
463
- <a-form-item :label="$t('teams.k479op007')">
464
- <a-radio-group v-model:value="addRole">
465
- <a-radio value="worker">
466
- worker
467
- </a-radio>
468
- <a-radio value="lead">
469
- {{ $t('teams.keowzlv017') }}
470
- </a-radio>
471
- </a-radio-group>
472
- </a-form-item>
473
- </a-form>
474
- </a-modal>
475
-
476
- <a-modal
477
- v-model:open="deployOpen"
478
- :title="$t('teams.k1c0o6oe030', { p0: deployTeamRef?.name ?? '' })"
479
- :confirm-loading="deploying"
480
- ok-text="部署"
481
- cancel-text="取消"
482
- @ok="deploy"
483
- >
484
- <a-form layout="vertical">
485
- <a-form-item label="目标 Channel(须无 lead 冲突)">
486
- <a-select
487
- v-model:value="deployChannelId"
488
- :options="channels.map(c => ({ value: c.id, label: c.name }))"
489
- />
490
- </a-form-item>
491
- </a-form>
492
- </a-modal>
493
-
494
- <a-modal
495
- v-model:open="plugOpen"
496
- :title="$t('teams.k1plugon040', { p0: plugTeamRef?.name ?? '' })"
497
- :footer="null"
498
- >
499
- <a-spin :spinning="plugLoading">
500
- <p class="plug-source">
501
- <template v-if="plugSource === 'explicit'">
502
- {{ $t('teams.k1plugon045') }}
503
- </template>
504
- <template v-else>
505
- {{ $t('teams.k1plugon044') }}
506
- </template>
507
- </p>
508
- <div
509
- v-for="row in plugRows"
510
- :key="row.name"
511
- class="plug-row"
512
- >
513
- <div class="plug-text">
514
- <div class="plug-name">
515
- <span class="aw-mono">{{ row.name }}</span>
516
- <span
517
- v-if="row.builtin"
518
- class="plug-builtin-tag"
519
- >{{ $t('teams.k3x23c018') }}</span>
520
- </div>
521
- <div class="plug-desc">
522
- {{ row.description || '—' }}
523
- </div>
524
- </div>
525
- <a-switch
526
- :checked="row.enabled"
527
- size="small"
528
- :loading="plugSaving === row.name"
529
- @change="(v: unknown) => toggleTeamPlugin(row, v === true)"
530
- />
531
- </div>
532
- <div
533
- v-if="plugRows.length === 0 && !plugLoading"
534
- class="plug-hint"
535
- >
536
- {{ $t('teams.k1plugon046') }}
537
- </div>
538
- </a-spin>
539
- </a-modal>
540
- </div>
541
- </template>
542
-
543
- <style scoped>
544
- .page { padding: 4px; }
545
- .head {
546
- display: flex;
547
- align-items: center;
548
- justify-content: space-between;
549
- margin-bottom: 12px;
550
- }
551
- h2 { margin: 0 0 4px; }
552
- .sub { margin: 0; font-size: 12px; opacity: 0.55; }
553
- .toolbar {
554
- display: flex;
555
- gap: 12px;
556
- align-items: center;
557
- margin-bottom: 12px;
558
- }
559
- .admin-note {
560
- display: inline-flex;
561
- gap: 5px;
562
- align-items: center;
563
- font-size: 11px;
564
- color: var(--ink-faint);
565
- }
566
- .grid {
567
- display: grid;
568
- grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
569
- gap: 12px;
570
- }
571
- .card {
572
- display: flex;
573
- flex-direction: column;
574
- gap: 10px;
575
- padding: 14px;
576
- background: var(--paper-raised);
577
- border: 1px solid var(--line);
578
- border-radius: var(--radius-panel);
579
- transition: border-color var(--transition-fast);
580
- }
581
- .card:hover { border-color: var(--line-strong); }
582
- .card-head {
583
- display: flex;
584
- gap: 8px;
585
- align-items: center;
586
- }
587
- .name {
588
- flex: 1 1 auto;
589
- font-size: 15px;
590
- font-weight: 600;
591
- }
592
- .vis-tag { margin-right: 2px; }
593
- .owner {
594
- font-size: 11px;
595
- color: var(--ink-faint);
596
- }
597
- .deploy-btn {
598
- flex: none;
599
- font-size: 11.5px;
600
- border-color: var(--line-strong);
601
- }
602
-
603
- .op { cursor: pointer; opacity: 0.4; }
604
- .op:hover { opacity: 1; }
605
- .members {
606
- display: flex;
607
- flex-direction: column;
608
- gap: 4px;
609
- }
610
- .member {
611
- display: flex;
612
- gap: 6px;
613
- align-items: center;
614
- font-size: 12px;
615
- }
616
- .member-name { font-weight: 500; }
617
- .member-harness {
618
- margin-left: auto;
619
- font-family: var(--font-mono);
620
- font-size: 10.5px;
621
- opacity: 0.5;
622
- }
623
- .rm { cursor: pointer; opacity: 0.35; }
624
- .rm:hover { opacity: 1; }
625
- .card.placeholder {
626
- align-items: center;
627
- justify-content: center;
628
- min-height: 140px;
629
- font-size: 13px;
630
- opacity: 0.55;
631
- cursor: pointer;
632
- border-style: dashed;
633
- }
634
- .big { font-size: 28px; }
635
-
636
- /* ===== 插件开关(创建勾选 + 团队弹层) ===== */
637
- .plug-create-group {
638
- display: flex;
639
- flex-direction: column;
640
- gap: 6px;
641
- }
642
- .plug-builtin-tag {
643
- margin-left: 6px;
644
- padding: 0 6px;
645
- font-family: var(--font-mono);
646
- font-size: 10.5px;
647
- color: var(--ink-faint);
648
- border: 1px solid var(--line-strong);
649
- border-radius: var(--radius-pill, 999px);
650
- }
651
- .plug-hint {
652
- margin-top: 6px;
653
- font-size: 11.5px;
654
- line-height: 1.5;
655
- color: var(--ink-faint);
656
- }
657
- .plug-source {
658
- margin: 0 0 10px;
659
- font-size: 12px;
660
- color: var(--ink-faint);
661
- }
662
- .plug-row {
663
- display: flex;
664
- align-items: center;
665
- justify-content: space-between;
666
- gap: 14px;
667
- padding: 10px 0;
668
- border-bottom: 1px solid var(--line);
669
- }
670
- .plug-row:last-of-type {
671
- border-bottom: 0;
672
- }
673
- .plug-text {
674
- min-width: 0;
675
- }
676
- .plug-name {
677
- display: flex;
678
- gap: 6px;
679
- align-items: center;
680
- font-size: 13px;
681
- font-weight: 500;
682
- }
683
- .plug-desc {
684
- margin-top: 2px;
685
- font-size: 11.5px;
686
- line-height: 1.5;
687
- color: var(--ink-faint);
688
- }
689
-
690
- /* ══ 窄屏(v9):页头纵向堆叠 / 筛选条换行 / 卡片单列 ══════════════════════ */
691
- @media (max-width: 900px) {
692
- .head {
693
- flex-direction: column;
694
- align-items: stretch;
695
- gap: 12px;
696
- }
697
-
698
- .head > div { min-width: 0; }
699
- .head h2 { font-size: 21px; line-height: 1.25; }
700
-
701
- .head :deep(.ant-space) {
702
- display: flex;
703
- flex-direction: column;
704
- gap: 8px;
705
- align-items: stretch;
706
- width: 100%;
707
- }
708
-
709
- .head :deep(.ant-space-item) { width: 100%; }
710
- .head :deep(.ant-btn) { width: 100%; min-height: 40px; }
711
-
712
- .toolbar {
713
- flex-wrap: wrap;
714
- gap: 8px;
715
- }
716
-
717
- .toolbar :deep(.ant-segmented) {
718
- flex: 1 1 100%;
719
- min-width: 0;
720
- }
721
-
722
- .admin-note {
723
- flex: 1 1 100%;
724
- min-width: 0;
725
- font-size: 11.5px;
726
- line-height: 1.5;
727
- }
728
-
729
- .grid { grid-template-columns: 1fr; }
730
- .card-head { flex-wrap: wrap; }
731
- .member { flex-wrap: wrap; }
732
- .member-harness { font-size: 11.5px; }
733
- }
734
-
735
- @media (max-width: 640px) {
736
- .page { padding: 0; }
737
- .head h2 { font-size: 19px; }
738
- .sub { font-size: 11.5px; line-height: 1.5; }
739
- }
740
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * AgentTeam 编组库:用户级隔离的编组 CRUD + 成员管理 + 一键 deploy 到 Channel。
4
+ * v10 可见性:private 仅本人;public 全员可读可用(仅属主可改删);内置(锁)不可变更。
5
+ * admin:全量视图(含他人私有),附创建者;可改删任意非内置编组。
6
+ */
7
+ import { message } from 'ant-design-vue'
8
+ import { useWorkshopApi, type TeamDto, type AgentTemplateDto, type ChannelDto, type WorkshopPluginDto, type ChannelPluginStateDto } from '../../composables/workshop/useWorkshopApi'
9
+ import { useUserStore } from '../../stores/workshop/user'
10
+
11
+ const { t } = useI18n()
12
+
13
+ /** 内置种子(编组/成员模板)按稳定 id 翻译;自建回退原名 */
14
+ const seedLabel = (x: { id?: string, templateId?: string, name: string }): string => seedName(t, x)
15
+
16
+ definePageMeta({ layout: 'default' })
17
+
18
+ const api = useWorkshopApi()
19
+ const userStore = useUserStore()
20
+ const teams = ref<TeamDto[]>([])
21
+ const templates = ref<AgentTemplateDto[]>([])
22
+ const channels = ref<ChannelDto[]>([])
23
+ const loading = ref(false)
24
+
25
+ const load = async (): Promise<void> => {
26
+ loading.value = true
27
+ try {
28
+ const [t, tpl, ch] = await Promise.all([api.listTeams(), api.listTemplates(), api.listChannels()])
29
+ teams.value = (t as unknown as { data?: TeamDto[] })?.data ?? []
30
+ templates.value = (tpl as unknown as { data?: AgentTemplateDto[] })?.data ?? []
31
+ channels.value = (ch as unknown as { data?: ChannelDto[] })?.data ?? []
32
+ }
33
+ finally {
34
+ loading.value = false
35
+ }
36
+ }
37
+ // SSR 安全:setup 期 $http(axios)无法在服务端发相对地址请求(同 agents 页注释)
38
+ if (import.meta.client) void load()
39
+
40
+ // ===== harness 可用性(成员选择禁用引擎未安装的模板;deploy 由后端 assert 兜底) =====
41
+ const harnessAvail = ref<Record<string, boolean>>({})
42
+ const loadHarnessAvail = async (): Promise<void> => {
43
+ try {
44
+ const res = await api.listHarnesses()
45
+ const list = (res as unknown as { data?: { harnesses?: Array<{ id: string, available?: boolean }> } })?.data?.harnesses
46
+ if (Array.isArray(list)) {
47
+ const m: Record<string, boolean> = {}
48
+ for (const h of list) m[h.id] = h.available !== false
49
+ harnessAvail.value = m
50
+ }
51
+ }
52
+ catch { /* 探测不可得时不限制选项(后端仍有强校验) */ }
53
+ }
54
+ if (import.meta.client) void loadHarnessAvail()
55
+ const tplUnavailable = (tpl: AgentTemplateDto): boolean => harnessAvail.value[tpl.harness] === false
56
+ const memberOptions = computed(() => templates.value.map((tpl) => {
57
+ const un = tplUnavailable(tpl)
58
+ return { value: tpl.id, label: un ? `${tpl.name}(${tpl.harness} · ${t('agents.notInstalled')})` : `${tpl.name}(${tpl.harness})`, disabled: un }
59
+ }))
60
+
61
+ // ===== 过滤 =====
62
+ type Filter = 'all' | 'mine' | 'public' | 'builtin'
63
+ const filter = ref<Filter>('all')
64
+ const shown = computed(() => {
65
+ const uid = userStore.user?.id
66
+ switch (filter.value) {
67
+ case 'mine': return teams.value.filter(t => t.ownerUserId === uid)
68
+ case 'public': return teams.value.filter(t => t.visibility === 'public')
69
+ case 'builtin': return teams.value.filter(t => t.isBuiltin)
70
+ default: return teams.value
71
+ }
72
+ })
73
+
74
+ const canWrite = (team: TeamDto): boolean =>
75
+ !team.isBuiltin && (team.ownerUserId === userStore.user?.id || userStore.isAdmin)
76
+
77
+ const visTag = (team: TeamDto): { text: string, color: string, icon?: string } => {
78
+ if (team.isBuiltin) return { text: t('teams.k3x23c018'), color: 'default', icon: 'i-tabler-lock' }
79
+ if (team.visibility === 'public') return { text: t('teams.k3wv1t019'), color: 'green' }
80
+ return { text: t('teams.k447jj020'), color: 'default' }
81
+ }
82
+
83
+ const createOpen = ref(false)
84
+ const createForm = reactive({ name: '', description: '', visibility: 'private' as 'private' | 'public' })
85
+
86
+ // ===== 插件(创建时勾选启用哪些;平台清单中 enabled 的插件,默认全选) =====
87
+ const platformPlugins = ref<WorkshopPluginDto[]>([])
88
+ const createPluginSel = ref<Record<string, boolean>>({})
89
+ const loadPlatformPlugins = async (): Promise<void> => {
90
+ try {
91
+ const res = await api.listPlugins()
92
+ // 顶层 plugins key(非信封);兼容 {code,data} 信封;只取平台已启用的插件
93
+ const list = res?.plugins ?? (res as { data?: { plugins?: WorkshopPluginDto[] } })?.data?.plugins ?? []
94
+ const enabled = (Array.isArray(list) ? list : []).filter(p => p.enabled !== false)
95
+ platformPlugins.value = enabled
96
+ const sel: Record<string, boolean> = {}
97
+ for (const p of enabled) sel[p.name] = true
98
+ createPluginSel.value = sel
99
+ }
100
+ catch { /* 清单不可得时隐藏插件多选(创建仍可走默认) */ }
101
+ }
102
+ if (import.meta.client) void loadPlatformPlugins()
103
+
104
+ /** checkbox-group 双向绑定(勾选集 ↔ plugins 开关视图) */
105
+ const pluginChecked = computed({
106
+ get: () => Object.entries(createPluginSel.value).filter(([, v]) => v).map(([k]) => k),
107
+ set: (vals: Array<string>) => {
108
+ const next: Record<string, boolean> = {}
109
+ for (const p of platformPlugins.value) next[p.name] = vals.includes(p.name)
110
+ createPluginSel.value = next
111
+ },
112
+ })
113
+
114
+ const create = async (): Promise<void> => {
115
+ if (!createForm.name.trim()) {
116
+ message.warning(t('teams.k1bvcdo2021'))
117
+ return
118
+ }
119
+ // 勾选结果作为 plugins:[{name,enabled}] 附加(后端创建时写入该团队的插件开关)
120
+ const plugins = Object.entries(createPluginSel.value).map(([name, enabled]) => ({ name, enabled }))
121
+ await api.createTeam({ name: createForm.name.trim(), description: createForm.description || undefined, visibility: createForm.visibility, plugins })
122
+ message.success(t('teams.k3n5hak022'))
123
+ createOpen.value = false
124
+ createForm.name = ''
125
+ createForm.description = ''
126
+ createForm.visibility = 'private'
127
+ createPluginSel.value = Object.fromEntries(Object.entries(createPluginSel.value).map(([k]) => [k, true]))
128
+ void load()
129
+ }
130
+
131
+ /** 一键切换可见性(属主/admin) */
132
+ const toggleVisibility = async (team: TeamDto, pub: boolean): Promise<void> => {
133
+ try {
134
+ await api.updateTeam(team.id, { visibility: pub ? 'public' : 'private' })
135
+ message.success(pub ? t('teams.k1globhp023') : t('teams.k1xxabaf024'))
136
+ void load()
137
+ }
138
+ catch (e) {
139
+ message.error(apiErrorMessage(e))
140
+ }
141
+ }
142
+
143
+ const addOpen = ref(false)
144
+ const addTeam = ref<TeamDto | null>(null)
145
+ const addTemplateId = ref<string>('')
146
+ const addRole = ref<'lead' | 'worker'>('worker')
147
+ const openAdd = (team: TeamDto): void => {
148
+ addTeam.value = team
149
+ addTemplateId.value = ''
150
+ addRole.value = 'worker'
151
+ addOpen.value = true
152
+ }
153
+ const addMember = async (): Promise<void> => {
154
+ if (!addTeam.value || !addTemplateId.value) {
155
+ message.warning(t('teams.k1kw4rtj025'))
156
+ return
157
+ }
158
+ try {
159
+ await api.addTeamMember(addTeam.value.id, { agentId: addTemplateId.value, role: addRole.value })
160
+ message.success(t('teams.k3n5hzw026'))
161
+ addOpen.value = false
162
+ void load()
163
+ }
164
+ catch (e) {
165
+ message.error(apiErrorMessage(e))
166
+ }
167
+ }
168
+ const removeMember = async (team: TeamDto, templateId: string): Promise<void> => {
169
+ await api.removeTeamMember(team.id, templateId)
170
+ void load()
171
+ }
172
+
173
+ const deployOpen = ref(false)
174
+ const deployTeamRef = ref<TeamDto | null>(null)
175
+ const deployChannelId = ref<string>('')
176
+ const deploying = ref(false)
177
+ const openDeploy = (team: TeamDto): void => {
178
+ deployTeamRef.value = team
179
+ deployChannelId.value = ''
180
+ deployOpen.value = true
181
+ }
182
+ const deploy = async (): Promise<void> => {
183
+ if (!deployTeamRef.value || !deployChannelId.value) {
184
+ message.warning(t('teams.selectChannel'))
185
+ return
186
+ }
187
+ deploying.value = true
188
+ try {
189
+ const res = await api.deployTeam(deployTeamRef.value.id, deployChannelId.value)
190
+ const agents = (res as unknown as { data?: { agents?: unknown[] } })?.data?.agents?.length ?? 0
191
+ message.success(t('teams.kh90glh031', { p0: agents }))
192
+ deployOpen.value = false
193
+ }
194
+ catch (e) {
195
+ message.error(apiErrorMessage(e))
196
+ }
197
+ finally {
198
+ deploying.value = false
199
+ }
200
+ }
201
+
202
+ const removeTeam = async (team: TeamDto): Promise<void> => {
203
+ await api.deleteTeam(team.id)
204
+ message.success(t('teams.k1oxjrxx027'))
205
+ void load()
206
+ }
207
+
208
+ // ===== 团队级插件开关(GET → 展示各插件开关;切换即 PUT) =====
209
+ const plugOpen = ref(false)
210
+ const plugTeamRef = ref<TeamDto | null>(null)
211
+ const plugRows = ref<ChannelPluginStateDto[]>([])
212
+ const plugSource = ref<'explicit' | 'default'>('default')
213
+ const plugLoading = ref(false)
214
+ const plugSaving = ref<string | null>(null)
215
+ const openPlugins = (team: TeamDto): void => {
216
+ plugTeamRef.value = team
217
+ plugRows.value = []
218
+ plugSource.value = 'default'
219
+ plugOpen.value = true
220
+ void loadTeamPlugins(team)
221
+ }
222
+ const loadTeamPlugins = async (team: TeamDto): Promise<void> => {
223
+ plugLoading.value = true
224
+ try {
225
+ const res = await api.listTeamPlugins(team.id)
226
+ const data = res?.data ?? {}
227
+ plugRows.value = data.plugins ?? []
228
+ plugSource.value = data.source === 'explicit' ? 'explicit' : 'default'
229
+ }
230
+ catch (e) {
231
+ message.error(apiErrorMessage(e))
232
+ }
233
+ finally {
234
+ plugLoading.value = false
235
+ }
236
+ }
237
+ const toggleTeamPlugin = async (row: ChannelPluginStateDto, next: boolean): Promise<void> => {
238
+ if (!plugTeamRef.value || plugSaving.value) return
239
+ const teamId = plugTeamRef.value.id
240
+ plugSaving.value = row.name
241
+ try {
242
+ // PUT 全量提交当前开关视图(仅翻转目标行)
243
+ const payload = plugRows.value.map(r => ({ name: r.name, enabled: r.name === row.name ? next : r.enabled }))
244
+ const res = await api.putTeamPlugins(teamId, { plugins: payload })
245
+ const data = res?.data ?? {}
246
+ plugRows.value = data.plugins ?? payload.map(p => ({ ...p }))
247
+ plugSource.value = data.source === 'explicit' ? 'explicit' : 'default'
248
+ message.success(t('teams.k1plugon043'))
249
+ }
250
+ catch (e) {
251
+ message.error(apiErrorMessage(e))
252
+ }
253
+ finally {
254
+ plugSaving.value = null
255
+ }
256
+ }
257
+
258
+ useHead({ title: () => t('titles.teams') })
259
+ </script>
260
+
261
+ <template>
262
+ <div class="page">
263
+ <div class="head">
264
+ <div>
265
+ <h2>AgentTeam {{ $t('teams.k3svl8y028') }}</h2>
266
+ <p class="sub">
267
+ {{ $t('teams.k12gl4wn008') }}
268
+ </p>
269
+ </div>
270
+ <a-space>
271
+ <a-button @click="navigateTo('/workshop')">
272
+ {{ $t('teams.krpx6qa009') }}
273
+ </a-button>
274
+ <a-button
275
+ type="primary"
276
+ @click="createOpen = true"
277
+ >
278
+ {{ $t('teams.k1efmuyx002') }}
279
+ </a-button>
280
+ </a-space>
281
+ </div>
282
+
283
+ <div class="toolbar">
284
+ <a-segmented
285
+ v-model:value="filter"
286
+ size="small"
287
+ :options="[
288
+ { value: 'all', label: $t('chips.chipAll', { n: teams.length }) },
289
+ { value: 'mine', label: $t('chips.mine') },
290
+ { value: 'public', label: $t('chips.public') },
291
+ { value: 'builtin', label: $t('chips.builtin') },
292
+ ]"
293
+ />
294
+ <span
295
+ v-if="userStore.isAdmin"
296
+ class="admin-note"
297
+ ><span class="i-tabler-shield-check" /> {{ $t('teams.k1bpgi8h010') }}</span>
298
+ </div>
299
+
300
+ <a-spin :spinning="loading">
301
+ <div class="grid">
302
+ <div
303
+ v-for="team in shown"
304
+ :key="team.id"
305
+ class="card"
306
+ >
307
+ <div class="card-head">
308
+ <span class="name">{{ seedLabel(team) }}</span>
309
+ <a-tag
310
+ :color="visTag(team).color"
311
+ class="vis-tag"
312
+ >
313
+ <span
314
+ v-if="visTag(team).icon"
315
+ :class="visTag(team).icon"
316
+ />{{ visTag(team).text }}
317
+ </a-tag>
318
+ <!-- 开关不带文字:左边那枚 tag 已经在说"公开/私有"了(与模板库同一条规则) -->
319
+ <a-switch
320
+ v-if="!team.isBuiltin && canWrite(team)"
321
+ :checked="team.visibility === 'public'"
322
+ size="small"
323
+ :title="team.visibility === 'public' ? $t('teams.toPrivate') : $t('teams.toPublic')"
324
+ @change="(v: unknown) => toggleVisibility(team, v === true)"
325
+ />
326
+ <span class="owner">{{ team.ownerName ?? '-' }}</span>
327
+ <a-dropdown v-if="canWrite(team)">
328
+ <span class="i-tabler-dots op" />
329
+ <template #overlay>
330
+ <a-menu>
331
+ <a-menu-item @click="openDeploy(team)">
332
+ {{ $t('teams.deployToChannel') }}
333
+ </a-menu-item>
334
+ <a-menu-item @click="openPlugins(team)">
335
+ {{ $t('teams.k1plugon047') }}
336
+ </a-menu-item>
337
+ <a-menu-item
338
+ danger
339
+ @click="removeTeam(team)"
340
+ >
341
+ {{ $t('teams.k1bpojhf011') }}
342
+ </a-menu-item>
343
+ </a-menu>
344
+ </template>
345
+ </a-dropdown>
346
+ <!-- 用 outlined 而不是 text:text 按钮在卡片头上与旁边的 owner 文本
347
+ 长得一模一样,用户看不出这里是**唯一的主操作**(实测三张卡片都如此)。 -->
348
+ <a-button
349
+ v-else
350
+ class="deploy-btn"
351
+ size="small"
352
+ type="default"
353
+ :title="$t('teams.kxheuf1001')"
354
+ @click="openDeploy(team)"
355
+ >
356
+ {{ $t('teams.k48ja7012') }}
357
+ </a-button>
358
+ </div>
359
+ <div class="members">
360
+ <div
361
+ v-for="m in team.members"
362
+ :key="m.templateId"
363
+ class="member"
364
+ >
365
+ <a-tag
366
+ :color="m.role === 'lead' ? 'gold' : 'blue'"
367
+ class="role"
368
+ >
369
+ {{ m.role }}
370
+ </a-tag>
371
+ <span class="member-name">{{ seedLabel(m) }}</span>
372
+ <span class="member-harness">{{ m.harness }}</span>
373
+ <span
374
+ v-if="canWrite(team)"
375
+ class="i-tabler-x rm"
376
+ @click="removeMember(team, m.templateId)"
377
+ />
378
+ </div>
379
+ <a-button
380
+ v-if="canWrite(team)"
381
+ size="small"
382
+ type="dashed"
383
+ block
384
+ @click="openAdd(team)"
385
+ >
386
+ {{ $t('teams.krt2oib013') }}
387
+ </a-button>
388
+ </div>
389
+ </div>
390
+ <div
391
+ v-if="shown.length === 0"
392
+ class="card placeholder"
393
+ @click="createOpen = true"
394
+ >
395
+ <span class="i-tabler-plus big" />
396
+ <span>{{ $t('teams.k6cbbgf014') }}</span>
397
+ </div>
398
+ </div>
399
+ </a-spin>
400
+
401
+ <a-modal
402
+ v-model:open="createOpen"
403
+ :title="$t('teams.k1efmuyx002')"
404
+ :ok-text="$t('common.create')"
405
+ :cancel-text="$t('common.cancel')"
406
+ @ok="create"
407
+ >
408
+ <a-form layout="vertical">
409
+ <a-form-item :label="$t('teams.k3xhia003')">
410
+ <a-input v-model:value="createForm.name" />
411
+ </a-form-item>
412
+ <a-form-item :label="$t('teams.k40gkk004')">
413
+ <a-input v-model:value="createForm.description" />
414
+ </a-form-item>
415
+ <a-form-item :label="$t('teams.k3lrqn0005')">
416
+ <a-radio-group v-model:value="createForm.visibility">
417
+ <a-radio value="private">
418
+ {{ $t('teams.k17jfge3015') }}
419
+ </a-radio>
420
+ <a-radio value="public">
421
+ {{ $t('teams.k1h2lq0o016') }}
422
+ </a-radio>
423
+ </a-radio-group>
424
+ </a-form-item>
425
+ <a-form-item
426
+ v-if="platformPlugins.length"
427
+ :label="$t('teams.k1plugon041')"
428
+ >
429
+ <a-checkbox-group
430
+ v-model:value="pluginChecked"
431
+ class="plug-create-group"
432
+ >
433
+ <a-checkbox
434
+ v-for="p in platformPlugins"
435
+ :key="p.name"
436
+ :value="p.name"
437
+ >
438
+ <span class="aw-mono">{{ p.name }}</span>
439
+ <span
440
+ v-if="p.builtin"
441
+ class="plug-builtin-tag"
442
+ >{{ $t('teams.k3x23c018') }}</span>
443
+ </a-checkbox>
444
+ </a-checkbox-group>
445
+ <div class="plug-hint">
446
+ {{ $t('teams.k1plugon042') }}
447
+ </div>
448
+ </a-form-item>
449
+ </a-form>
450
+ </a-modal>
451
+
452
+ <a-modal
453
+ v-model:open="addOpen"
454
+ :title="$t('teams.k6ljhyv029', { p0: addTeam?.name ?? '' })"
455
+ :ok-text="$t('teams.joinOk')"
456
+ :cancel-text="$t('common.cancel')"
457
+ @ok="addMember"
458
+ >
459
+ <a-form layout="vertical">
460
+ <a-form-item :label="$t('teams.k41ds5006')">
461
+ <a-select
462
+ v-model:value="addTemplateId"
463
+ :options="memberOptions"
464
+ />
465
+ </a-form-item>
466
+ <a-form-item :label="$t('teams.k479op007')">
467
+ <a-radio-group v-model:value="addRole">
468
+ <a-radio value="worker">
469
+ worker
470
+ </a-radio>
471
+ <a-radio value="lead">
472
+ {{ $t('teams.keowzlv017') }}
473
+ </a-radio>
474
+ </a-radio-group>
475
+ </a-form-item>
476
+ </a-form>
477
+ </a-modal>
478
+
479
+ <a-modal
480
+ v-model:open="deployOpen"
481
+ :title="$t('teams.k1c0o6oe030', { p0: deployTeamRef?.name ?? '' })"
482
+ :confirm-loading="deploying"
483
+ :ok-text="$t('teams.deployOk')"
484
+ :cancel-text="$t('common.cancel')"
485
+ @ok="deploy"
486
+ >
487
+ <a-form layout="vertical">
488
+ <a-form-item :label="$t('teams.targetChannelLabel')">
489
+ <a-select
490
+ v-model:value="deployChannelId"
491
+ :options="channels.map(c => ({ value: c.id, label: c.name }))"
492
+ />
493
+ </a-form-item>
494
+ </a-form>
495
+ </a-modal>
496
+
497
+ <a-modal
498
+ v-model:open="plugOpen"
499
+ :title="$t('teams.k1plugon040', { p0: plugTeamRef?.name ?? '' })"
500
+ :footer="null"
501
+ >
502
+ <a-spin :spinning="plugLoading">
503
+ <p class="plug-source">
504
+ <template v-if="plugSource === 'explicit'">
505
+ {{ $t('teams.k1plugon045') }}
506
+ </template>
507
+ <template v-else>
508
+ {{ $t('teams.k1plugon044') }}
509
+ </template>
510
+ </p>
511
+ <div
512
+ v-for="row in plugRows"
513
+ :key="row.name"
514
+ class="plug-row"
515
+ >
516
+ <div class="plug-text">
517
+ <div class="plug-name">
518
+ <span class="aw-mono">{{ row.name }}</span>
519
+ <span
520
+ v-if="row.builtin"
521
+ class="plug-builtin-tag"
522
+ >{{ $t('teams.k3x23c018') }}</span>
523
+ </div>
524
+ <div class="plug-desc">
525
+ {{ row.description || '—' }}
526
+ </div>
527
+ </div>
528
+ <a-switch
529
+ :checked="row.enabled"
530
+ size="small"
531
+ :loading="plugSaving === row.name"
532
+ @change="(v: unknown) => toggleTeamPlugin(row, v === true)"
533
+ />
534
+ </div>
535
+ <div
536
+ v-if="plugRows.length === 0 && !plugLoading"
537
+ class="plug-hint"
538
+ >
539
+ {{ $t('teams.k1plugon046') }}
540
+ </div>
541
+ </a-spin>
542
+ </a-modal>
543
+ </div>
544
+ </template>
545
+
546
+ <style scoped>
547
+ .page { padding: 4px; }
548
+ .head {
549
+ display: flex;
550
+ align-items: center;
551
+ justify-content: space-between;
552
+ margin-bottom: 12px;
553
+ }
554
+ h2 { margin: 0 0 4px; }
555
+ .sub { margin: 0; font-size: 12px; opacity: 0.55; }
556
+ .toolbar {
557
+ display: flex;
558
+ gap: 12px;
559
+ align-items: center;
560
+ margin-bottom: 12px;
561
+ }
562
+ .admin-note {
563
+ display: inline-flex;
564
+ gap: 5px;
565
+ align-items: center;
566
+ font-size: 11px;
567
+ color: var(--ink-faint);
568
+ }
569
+ .grid {
570
+ display: grid;
571
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
572
+ gap: 12px;
573
+ }
574
+ .card {
575
+ display: flex;
576
+ flex-direction: column;
577
+ gap: 10px;
578
+ padding: 14px;
579
+ background: var(--paper-raised);
580
+ border: 1px solid var(--line);
581
+ border-radius: var(--radius-panel);
582
+ transition: border-color var(--transition-fast);
583
+ }
584
+ .card:hover { border-color: var(--line-strong); }
585
+ .card-head {
586
+ display: flex;
587
+ gap: 8px;
588
+ align-items: center;
589
+ }
590
+ .name {
591
+ flex: 1 1 auto;
592
+ font-size: 15px;
593
+ font-weight: 600;
594
+ }
595
+ .vis-tag { margin-right: 2px; }
596
+ .owner {
597
+ font-size: 11px;
598
+ color: var(--ink-faint);
599
+ }
600
+ .deploy-btn {
601
+ flex: none;
602
+ font-size: 11.5px;
603
+ border-color: var(--line-strong);
604
+ }
605
+
606
+ .op { cursor: pointer; opacity: 0.4; }
607
+ .op:hover { opacity: 1; }
608
+ .members {
609
+ display: flex;
610
+ flex-direction: column;
611
+ gap: 4px;
612
+ }
613
+ .member {
614
+ display: flex;
615
+ gap: 6px;
616
+ align-items: center;
617
+ font-size: 12px;
618
+ }
619
+ .member-name { font-weight: 500; }
620
+ .member-harness {
621
+ margin-left: auto;
622
+ font-family: var(--font-mono);
623
+ font-size: 10.5px;
624
+ opacity: 0.5;
625
+ }
626
+ .rm { cursor: pointer; opacity: 0.35; }
627
+ .rm:hover { opacity: 1; }
628
+ .card.placeholder {
629
+ align-items: center;
630
+ justify-content: center;
631
+ min-height: 140px;
632
+ font-size: 13px;
633
+ opacity: 0.55;
634
+ cursor: pointer;
635
+ border-style: dashed;
636
+ }
637
+ .big { font-size: 28px; }
638
+
639
+ /* ===== 插件开关(创建勾选 + 团队弹层) ===== */
640
+ .plug-create-group {
641
+ display: flex;
642
+ flex-direction: column;
643
+ gap: 6px;
644
+ }
645
+ .plug-builtin-tag {
646
+ margin-left: 6px;
647
+ padding: 0 6px;
648
+ font-family: var(--font-mono);
649
+ font-size: 10.5px;
650
+ color: var(--ink-faint);
651
+ border: 1px solid var(--line-strong);
652
+ border-radius: var(--radius-pill, 999px);
653
+ }
654
+ .plug-hint {
655
+ margin-top: 6px;
656
+ font-size: 11.5px;
657
+ line-height: 1.5;
658
+ color: var(--ink-faint);
659
+ }
660
+ .plug-source {
661
+ margin: 0 0 10px;
662
+ font-size: 12px;
663
+ color: var(--ink-faint);
664
+ }
665
+ .plug-row {
666
+ display: flex;
667
+ align-items: center;
668
+ justify-content: space-between;
669
+ gap: 14px;
670
+ padding: 10px 0;
671
+ border-bottom: 1px solid var(--line);
672
+ }
673
+ .plug-row:last-of-type {
674
+ border-bottom: 0;
675
+ }
676
+ .plug-text {
677
+ min-width: 0;
678
+ }
679
+ .plug-name {
680
+ display: flex;
681
+ gap: 6px;
682
+ align-items: center;
683
+ font-size: 13px;
684
+ font-weight: 500;
685
+ }
686
+ .plug-desc {
687
+ margin-top: 2px;
688
+ font-size: 11.5px;
689
+ line-height: 1.5;
690
+ color: var(--ink-faint);
691
+ }
692
+
693
+ /* ══ 窄屏(v9):页头纵向堆叠 / 筛选条换行 / 卡片单列 ══════════════════════ */
694
+ @media (max-width: 900px) {
695
+ .head {
696
+ flex-direction: column;
697
+ align-items: stretch;
698
+ gap: 12px;
699
+ }
700
+
701
+ .head > div { min-width: 0; }
702
+ .head h2 { font-size: 21px; line-height: 1.25; }
703
+
704
+ .head :deep(.ant-space) {
705
+ display: flex;
706
+ flex-direction: column;
707
+ gap: 8px;
708
+ align-items: stretch;
709
+ width: 100%;
710
+ }
711
+
712
+ .head :deep(.ant-space-item) { width: 100%; }
713
+ .head :deep(.ant-btn) { width: 100%; min-height: 40px; }
714
+
715
+ .toolbar {
716
+ flex-wrap: wrap;
717
+ gap: 8px;
718
+ }
719
+
720
+ .toolbar :deep(.ant-segmented) {
721
+ flex: 1 1 100%;
722
+ min-width: 0;
723
+ }
724
+
725
+ .admin-note {
726
+ flex: 1 1 100%;
727
+ min-width: 0;
728
+ font-size: 11.5px;
729
+ line-height: 1.5;
730
+ }
731
+
732
+ .grid { grid-template-columns: 1fr; }
733
+ .card-head { flex-wrap: wrap; }
734
+ .member { flex-wrap: wrap; }
735
+ .member-harness { font-size: 11.5px; }
736
+ }
737
+
738
+ @media (max-width: 640px) {
739
+ .page { padding: 0; }
740
+ .head h2 { font-size: 19px; }
741
+ .sub { font-size: 11.5px; line-height: 1.5; }
742
+ }
743
+ </style>