agentworkshop 0.7.40 → 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 (327) 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/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. 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>