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,998 +1,998 @@
1
- <script setup lang="ts">
2
- /**
3
- * Agent lanes 视图(P1):每个 agent 一列并排流(相邻面板范式,2-4 agent 最佳)。
4
- * 列头:状态徽标 + 队列上下文;列内:该 agent 的事件卡片(独立滚动)。
5
- * 团队成员管理(P3):添加成员(name/harness/role)/ 移除成员;lead 执行中自主管理的
6
- * 成员变更(agent.member 事件)同样在此实时呈现——实体列表是唯一状态源。
7
- *
8
- * harness 终端控制(rpc-ui HITL):每个 omp 成员 lane 可打开原生终端抽屉
9
- * (xterm 实时 TUI 渲染 + steer/follow_up 注入 + ask 对话框应答)。
10
- * 团队编排(任务指派/调度)仍由 channel 系统负责,终端是 harness 层的直接
11
- * 人类控制通道,二者互不干扰。omp lazy spawn:进程随首个任务启动,未启动时
12
- * 终端按钮仍可用(抽屉内等待 + 自动接入)。
13
- */
14
- import { message } from 'ant-design-vue'
15
- import { useStorage } from '@vueuse/core'
16
- import { useEntitiesStore } from '@/app/stores/workshop/entities'
17
- import { useWorkshopApi, type TerminalSessionDto } from '@/app/composables/workshop/useWorkshopApi'
18
- import { agentHueColor } from '@/app/composables/workshop/useEventBlocks'
19
- import LaneBlocks from '@/app/components/workshop/lanes/LaneBlocks.vue'
20
- import OmpTerminalPanel from '@/app/components/workshop/terminal/OmpTerminalPanel.vue'
21
-
22
- const { t } = useI18n()
23
-
24
- const props = defineProps<{ channelId: string }>()
25
- const entities = useEntitiesStore()
26
- const api = useWorkshopApi()
27
-
28
- const agents = computed(() => entities.agents[props.channelId] ?? [])
29
-
30
- // ===== 泳道列宽拖拽调节(PaneSplitter;按 agentId 持久化,双击复位默认宽) =====
31
- const LANE_W_DEFAULT = 320
32
- const laneWidths = useStorage<Record<string, number>>('aw.harness.laneW', {})
33
- const laneWidth = (id: string): number => laneWidths.value[id] ?? LANE_W_DEFAULT
34
- const resizeLane = (id: string, d: number): void => {
35
- laneWidths.value = { ...laneWidths.value, [id]: Math.min(720, Math.max(240, laneWidth(id) + d)) }
36
- }
37
- const resetLane = (id: string): void => {
38
- const next = { ...laneWidths.value }
39
- Reflect.deleteProperty(next, id)
40
- laneWidths.value = next
41
- }
42
-
43
- /** 泳道身份:头像章 + 稳定身份色(与聊天头像/提及卡同一哈希色相源) */
44
- const laneInitial = (name: string): string => name.trim().charAt(0).toUpperCase() || '?'
45
- const laneHue = (id: string): string => agentHueColor(id)
46
-
47
- /** harness 终端徽章色(antd 语义 → 本设计 tone 点;仅作指示,文字仍说明状态) */
48
- const TERM_DOT: Record<string, string> = {
49
- processing: 'var(--tone-live-dot)',
50
- warning: 'var(--tone-warning-dot)',
51
- success: 'var(--tone-success-dot)',
52
- }
53
-
54
- // ===== 成员管理(用户侧 REST;状态回流以 WS agent.member 事件为准) =====
55
- const memberModalOpen = ref(false)
56
- const memberSubmitting = ref(false)
57
- /** 添加模式:从零创建 / 从模板克隆 / 部署编组(批量) */
58
- const addMode = ref<'create' | 'template' | 'team'>('create')
59
- const memberForm = reactive({
60
- name: '',
61
- harness: 'omp' as 'omp' | 'mock' | 'claude',
62
- role: 'worker' as 'lead' | 'worker',
63
- systemPrompt: '',
64
- })
65
- /** 模板克隆 / 编组部署选项(弹窗打开时懒加载) */
66
- const templates = ref<Array<{ id: string, name: string, harness: string, enabled: number }>>([])
67
- const teams = ref<Array<{ id: string, name: string, memberCount: number, hasLead: boolean }>>([])
68
- const selectedTemplateId = ref<string>('')
69
- const selectedTeamId = ref<string>('')
70
-
71
- const loadCatalog = async (): Promise<void> => {
72
- try {
73
- const [tplRes, teamRes] = await Promise.all([api.listTemplates(), api.listTeams()])
74
- templates.value = (tplRes.data ?? []).map(t => ({ id: t.id, name: t.name, harness: t.harness, enabled: t.enabled }))
75
- teams.value = (teamRes.data ?? []).map(t => ({
76
- id: t.id,
77
- name: t.name,
78
- memberCount: t.members.length,
79
- hasLead: t.members.some(m => m.role === 'lead'),
80
- }))
81
- }
82
- catch { /* 目录拉取失败:对应模式显示空并提示刷新 */ }
83
- }
84
-
85
- // ===== 编辑成员(改名 / 改场景提示词 / 启停)=====
86
- const editModalOpen = ref(false)
87
- const editSubmitting = ref(false)
88
- const editForm = reactive({ agentId: '', name: '', role: 'worker' as 'lead' | 'worker', harness: '', systemPrompt: '' })
89
- const openEditMember = (a: { agentId: string, name: string, role: string, harness: string, config?: Record<string, unknown> }): void => {
90
- editForm.agentId = a.agentId
91
- editForm.name = a.name
92
- editForm.role = a.role === 'lead' ? 'lead' : 'worker'
93
- editForm.harness = a.harness
94
- editForm.systemPrompt = typeof a.config?.systemPromptPrefix === 'string' ? a.config.systemPromptPrefix : ''
95
- editModalOpen.value = true
96
- }
97
- const submitEditMember = async (): Promise<void> => {
98
- const name = editForm.name.trim()
99
- if (!name) {
100
- message.warning(t('agentLanesView.ky6jqt4032'))
101
- return
102
- }
103
- editSubmitting.value = true
104
- try {
105
- await api.updateChannelAgent(props.channelId, editForm.agentId, {
106
- name,
107
- config: {
108
- // 保留既有 config,仅更新 systemPromptPrefix(编辑弹窗只暴露该字段)
109
- ...(entities.agents[props.channelId]?.find(a => a.agentId === editForm.agentId)?.config ?? {}),
110
- systemPromptPrefix: editForm.systemPrompt.trim(),
111
- },
112
- reason: t('agentLanesView.kfvflle033'),
113
- })
114
- message.success(t('agentLanesView.k21xjz2044', { p0: name }))
115
- editModalOpen.value = false
116
- }
117
- catch (err) {
118
- message.error(t('agentLanesView.k3jmrw1045', { p0: apiErrorMessage(err) }))
119
- }
120
- finally {
121
- editSubmitting.value = false
122
- }
123
- }
124
-
125
- const openMemberModal = (): void => {
126
- memberForm.name = ''
127
- memberForm.harness = 'omp'
128
- memberForm.role = 'worker'
129
- memberForm.systemPrompt = ''
130
- addMode.value = 'create'
131
- selectedTemplateId.value = ''
132
- selectedTeamId.value = ''
133
- memberModalOpen.value = true
134
- void loadCatalog()
135
- }
136
-
137
- const submitMember = async (): Promise<void> => {
138
- if (addMode.value === 'create') {
139
- const name = memberForm.name.trim()
140
- if (!name) {
141
- message.warning(t('agentLanesView.ky6jqt4032'))
142
- return
143
- }
144
- memberSubmitting.value = true
145
- try {
146
- await api.addChannelAgent(props.channelId, {
147
- name,
148
- harness: memberForm.harness,
149
- role: memberForm.role,
150
- config: memberForm.systemPrompt.trim()
151
- ? { systemPromptPrefix: memberForm.systemPrompt.trim() }
152
- : undefined,
153
- })
154
- message.success(t('agentLanesView.k1g5ykr3046', { p0: name }))
155
- memberModalOpen.value = false
156
- }
157
- catch (err) {
158
- message.error(t('agentLanesView.k1j97j74047', { p0: apiErrorMessage(err) }))
159
- }
160
- finally {
161
- memberSubmitting.value = false
162
- }
163
- return
164
- }
165
- if (addMode.value === 'template') {
166
- if (!selectedTemplateId.value) {
167
- message.warning(t('agentLanesView.k13xo8sz034'))
168
- return
169
- }
170
- memberSubmitting.value = true
171
- try {
172
- const tpl = templates.value.find(t => t.id === selectedTemplateId.value)
173
- await api.addChannelAgent(props.channelId, {
174
- agentId: selectedTemplateId.value,
175
- role: memberForm.role,
176
- config: memberForm.systemPrompt.trim()
177
- ? { systemPromptPrefix: memberForm.systemPrompt.trim() }
178
- : undefined,
179
- })
180
- message.success(t('agentLanesView.kk3uwzo048', { p0: memberForm.role, p1: tpl?.name ?? '' }))
181
- memberModalOpen.value = false
182
- }
183
- catch (err) {
184
- const text = apiErrorMessage(err)
185
- message.error(`模板克隆失败${text.includes('LEAD_EXISTS') ? '(已有 lead,不能再添加 lead)' : `: ${text}`}`)
186
- }
187
- finally {
188
- memberSubmitting.value = false
189
- }
190
- return
191
- }
192
- // 部署编组:批量克隆全部成员模板(lead 冲突由服务端 409 拒绝)
193
- if (!selectedTeamId.value) {
194
- message.warning(t('agentLanesView.k1tjd5ab035'))
195
- return
196
- }
197
- memberSubmitting.value = true
198
- try {
199
- const team = teams.value.find(t => t.id === selectedTeamId.value)
200
- await api.deployTeam(selectedTeamId.value, props.channelId)
201
- message.success(t('agentLanesView.k1gslqf9050', { p0: team?.name ?? '', p1: team?.memberCount ?? 0 }))
202
- memberModalOpen.value = false
203
- }
204
- catch (err) {
205
- const text = apiErrorMessage(err)
206
- message.error(`编组部署失败${text.includes('LEAD_EXISTS') ? '(channel 已有 lead,编组内 lead 成员冲突;请先移除现有 lead 或选用无 lead 编组)' : `: ${text}`}`)
207
- }
208
- finally {
209
- memberSubmitting.value = false
210
- }
211
- }
212
-
213
- const removing = ref<string | null>(null)
214
- const removeMember = async (agentId: string, name: string): Promise<void> => {
215
- removing.value = agentId
216
- try {
217
- await api.removeChannelAgent(props.channelId, agentId)
218
- message.success(t('agentLanesView.k1n3o03a052', { p0: name }))
219
- }
220
- catch (err) {
221
- message.error(t('agentLanesView.k13dsp10053', { p0: apiErrorMessage(err) }))
222
- }
223
- finally {
224
- removing.value = null
225
- }
226
- }
227
-
228
- // ===== HITL:独立中断成员运行时(worker/lead 均可;成员保留,下次任务自动重装配) =====
229
- const stopping = ref<string | null>(null)
230
- const stopMember = async (agentId: string, name: string): Promise<void> => {
231
- stopping.value = agentId
232
- try {
233
- await api.stopChannelAgent(props.channelId, agentId)
234
- message.success(t('agentLanesView.kcpbv47054', { p0: name }))
235
- }
236
- catch (err) {
237
- message.error(t('agentLanesView.k139ec5j055', { p0: apiErrorMessage(err) }))
238
- }
239
- finally {
240
- stopping.value = null
241
- }
242
- }
243
-
244
- // ===== harness 终端控制(rpc-ui HITL;每成员独立 omp 会话) =====
245
- const terminals = ref<TerminalSessionDto[]>([])
246
- /** agentId → 存活终端会话(lane 头徽标 + 终端按钮态) */
247
- const terminalOf = computed(() => {
248
- const map = new Map<string, TerminalSessionDto>()
249
- for (const t of terminals.value) {
250
- if (!t.alive || !t.agentId) continue
251
- map.set(t.agentId, t)
252
- }
253
- return map
254
- })
255
- const termBadge = (t: TerminalSessionDto | undefined): { text: string, color: string } | null => {
256
- if (!t) return null
257
- if (t.streaming) return { text: 'streaming', color: 'processing' }
258
- if (t.running) return { text: 'turn', color: 'warning' }
259
- return { text: 'idle', color: 'success' }
260
- }
261
-
262
- let terminalsTimer: ReturnType<typeof setInterval> | null = null
263
- const loadTerminals = async (): Promise<void> => {
264
- try {
265
- const res = await api.listChannelTerminals(props.channelId)
266
- terminals.value = res.data ?? []
267
- }
268
- catch { /* 轮询失败静默(下次恢复) */ }
269
- }
270
-
271
- const terminalOpen = ref(false)
272
- const terminalAgentId = ref<string | null>(null)
273
- const terminalSubtitle = ref('')
274
- const openTerminal = (a: { agentId: string, name: string, role: string }): void => {
275
- terminalAgentId.value = a.agentId
276
- terminalSubtitle.value = `${a.name} · ${a.role}`
277
- terminalOpen.value = true
278
- }
279
-
280
- onMounted(() => {
281
- void loadTerminals()
282
- terminalsTimer = setInterval(() => void loadTerminals(), 5000)
283
- })
284
- onBeforeUnmount(() => {
285
- if (terminalsTimer) clearInterval(terminalsTimer)
286
- })
287
- </script>
288
-
289
- <template>
290
- <div class="lanes-wrap">
291
- <div class="toolbar">
292
- <div class="team-summary">
293
- <span class="ts-label">{{ $t('agentLanesView.k3y5ja016') }}</span>
294
- <span class="ts-count">{{ agents.length }}</span>
295
- <span class="ts-unit">{{ $t('agentLanesView.k3ll6sa017') }}</span>
296
- <span class="ts-detail">lead {{ agents.filter(a => a.role === 'lead').length }} · worker {{ agents.filter(a => a.role === 'worker').length }}</span>
297
- </div>
298
- <a-button
299
- size="small"
300
- type="primary"
301
- ghost
302
- @click="openMemberModal"
303
- >
304
- <span class="i-tabler-user-plus" />
305
- <span>{{ $t('agentLanesView.k1fmutgo018') }}</span>
306
- </a-button>
307
- </div>
308
- <div class="lanes">
309
- <div
310
- v-if="agents.length === 0"
311
- class="empty"
312
- >
313
- <!-- 快照未到 → 同步中;真无成员 → 空态指引(诚实区分,不永挂"等待") -->
314
- <template v-if="!entities.channels[channelId]">
315
- <span class="i-tabler-refresh empty-icon" />
316
- <p class="empty-title">
317
- {{ $t('agentLanesView.k1woev5v019') }}
318
- </p>
319
- </template>
320
- <template v-else>
321
- <span class="i-tabler-users-group empty-icon" />
322
- <p class="empty-title">
323
- {{ $t('agentLanesView.k122h4pc020') }}
324
- </p>
325
- <p class="empty-hint">
326
- {{ $t('agentLanesView.k7cr2h8021') }}
327
- </p>
328
- </template>
329
- </div>
330
- <template
331
- v-for="a in agents"
332
- :key="a.agentId"
333
- >
334
- <div
335
- class="lane"
336
- :style="{ flexBasis: `${laneWidth(a.agentId)}px` }"
337
- >
338
- <div class="lane-head">
339
- <!-- 第一行:身份(头像章 + 名称 + 中性徽标);名称为弹性吸收项,任意宽度截断不遮挡 -->
340
- <div class="head-top">
341
- <span class="lane-ava">
342
- <span :style="{ '--av': laneHue(a.agentId) }">{{ laneInitial(a.name) }}</span>
343
- <span
344
- class="lane-state"
345
- :class="a.state"
346
- />
347
- </span>
348
- <span
349
- class="lane-name"
350
- :title="a.name"
351
- >{{ a.name }}</span>
352
- <span
353
- v-if="a.config?.systemPromptPrefix"
354
- class="lane-chip"
355
- :title="$t('agentLanesView.k107s4am001')"
356
- >
357
- {{ $t('agentLanesView.k3xycu022') }}
358
- </span>
359
- <span
360
- class="lane-role"
361
- :class="a.role"
362
- >
363
- {{ a.role }}
364
- </span>
365
- <span
366
- v-if="termBadge(terminalOf.get(a.agentId))"
367
- class="term-badge"
368
- :title="$t('agentLanesView.k1wn2vmt036', { p0: terminalOf.get(a.agentId)?.pid })"
369
- >
370
- <span
371
- class="term-dot"
372
- :style="{ background: TERM_DOT[termBadge(terminalOf.get(a.agentId))!.color] ?? 'var(--tone-neutral-dot)' }"
373
- />
374
- {{ termBadge(terminalOf.get(a.agentId))!.text }}
375
- </span>
376
- </div>
377
- <!-- 第二行:状态摘要 + 操作簇(常驻可见;hairline 分隔破坏性操作) -->
378
- <div class="head-sub">
379
- <span class="lane-meta">
380
- <template v-if="a.state === 'busy' && a.currentTaskTitle">
381
- {{ a.currentTaskTitle }}
382
- <span
383
- v-if="a.currentTaskProgress != null"
384
- class="lane-progress"
385
- >{{ a.currentTaskProgress }}%</span>
386
- </template>
387
- <template v-else>{{ a.state }} · Q{{ a.queued ?? 0 }}</template>
388
- </span>
389
- <div class="lane-actions">
390
- <a-button
391
- v-if="a.harness === 'omp'"
392
- size="small"
393
- type="primary"
394
- ghost
395
- class="lane-term"
396
- :title="$t('agentLanesView.k1884q17002')"
397
- @click="openTerminal(a)"
398
- >
399
- <span class="i-tabler-terminal-2" />
400
- <span class="term-label">{{ $t('agentLanesView.k4588s023') }}</span>
401
- </a-button>
402
- <a-button
403
- size="small"
404
- type="text"
405
- class="lane-edit"
406
- :title="$t('agentLanesView.k1pgd3tf003')"
407
- @click="openEditMember(a)"
408
- >
409
- <span class="i-tabler-edit" />
410
- </a-button>
411
- <span class="actions-divider" />
412
- <a-popconfirm
413
- :title="$t('agentLanesView.k1l029kf037', { p0: a.name })"
414
- ok-text="停止"
415
- cancel-text="取消"
416
- @confirm="stopMember(a.agentId, a.name)"
417
- >
418
- <a-button
419
- size="small"
420
- type="text"
421
- class="lane-stop"
422
- :loading="stopping === a.agentId"
423
- title="HITL 停止该 Agent 运行时"
424
- >
425
- <span class="i-tabler-player-stop" />
426
- </a-button>
427
- </a-popconfirm>
428
- <a-popconfirm
429
- :title="$t('agentLanesView.kt3n27m038', { p0: a.name })"
430
- ok-text="移除"
431
- cancel-text="取消"
432
- @confirm="removeMember(a.agentId, a.name)"
433
- >
434
- <a-button
435
- size="small"
436
- type="text"
437
- danger
438
- class="lane-remove"
439
- :loading="removing === a.agentId"
440
- >
441
- <span class="i-tabler-x" />
442
- </a-button>
443
- </a-popconfirm>
444
- </div>
445
- </div>
446
- </div>
447
- <div class="lane-body">
448
- <!-- 列体:同类型连续事件聚合为块组件(实时/历史同一路径,无重复消费;
449
- 宽度随泳道拖拽自适应,EventBlock 26px+1fr 网格自收缩) -->
450
- <LaneBlocks
451
- :channel-id="channelId"
452
- :agent-id="a.agentId"
453
- />
454
- </div>
455
- </div>
456
- <!-- 泳道分隔条:每列右侧都挂(含最右列);统一调节"其左侧泳道"的宽度,
457
- 尾条即最右泳道的右缘调节柄(双击复位;键盘 ←→ 微调) -->
458
- <workshop-pane-splitter
459
- :label="$t('agentLanesView.k7xt1y6039', { p0: a.name })"
460
- @resize="d => resizeLane(a.agentId, d)"
461
- @reset="resetLane(a.agentId)"
462
- />
463
- </template>
464
- </div>
465
-
466
- <!-- 添加成员弹窗(三模式:从零创建 / 模板克隆 / 编组部署) -->
467
- <a-modal
468
- v-model:open="memberModalOpen"
469
- :title="$t('agentLanesView.k17kcn55004')"
470
- :confirm-loading="memberSubmitting"
471
- ok-text="添加"
472
- cancel-text="取消"
473
- @ok="submitMember"
474
- >
475
- <a-radio-group
476
- v-model:value="addMode"
477
- class="mode-switch"
478
- >
479
- <a-radio-button value="create">
480
- {{ $t('agentLanesView.k1b7cwvy024') }}
481
- </a-radio-button>
482
- <a-radio-button value="template">
483
- {{ $t('agentLanesView.k1ndey1w025') }}
484
- </a-radio-button>
485
- <a-radio-button value="team">
486
- {{ $t('agentLanesView.k1l5qyux026') }}
487
- </a-radio-button>
488
- </a-radio-group>
489
-
490
- <!-- 模式一:从零创建 -->
491
- <a-form
492
- v-if="addMode === 'create'"
493
- layout="vertical"
494
- class="member-form"
495
- >
496
- <a-form-item :label="$t('agentLanesView.k3nufdm005')">
497
- <a-input
498
- v-model:value="memberForm.name"
499
- placeholder="如 db-migrator / test-writer"
500
- @press-enter="submitMember"
501
- />
502
- </a-form-item>
503
- <a-form-item label="harness">
504
- <a-radio-group v-model:value="memberForm.harness">
505
- <a-radio value="omp">
506
- omp(完整 LLM agent)
507
- </a-radio>
508
- <a-radio value="mock">
509
- {{ $t('agentLanesView.kqg6783027') }}
510
- </a-radio>
511
- <a-radio value="claude">
512
- claude
513
- </a-radio>
514
- </a-radio-group>
515
- </a-form-item>
516
- <a-form-item :label="$t('agentLanesView.k479op006')">
517
- <a-radio-group v-model:value="memberForm.role">
518
- <a-radio value="worker">
519
- worker
520
- </a-radio>
521
- <a-radio value="lead">
522
- {{ $t('agentLanesView.k1weovo028') }}
523
- </a-radio>
524
- </a-radio-group>
525
- </a-form-item>
526
- <a-form-item :label="$t('agentLanesView.kaogfp007')">
527
- <a-textarea
528
- v-model:value="memberForm.systemPrompt"
529
- :rows="3"
530
- :placeholder="$t('agentLanesView.kbqn6w5008')"
531
- />
532
- </a-form-item>
533
- </a-form>
534
-
535
- <!-- 模式二:从已有模板克隆 -->
536
- <a-form
537
- v-else-if="addMode === 'template'"
538
- layout="vertical"
539
- class="member-form"
540
- >
541
- <a-form-item label="选择 Agent 模板(克隆 name/harness/config 为独立实例)">
542
- <a-select
543
- v-model:value="selectedTemplateId"
544
- :placeholder="$t('agentLanesView.kung925009')"
545
- :options="templates.map(t => ({ value: t.id, label: `${t.name}(${t.harness})${t.enabled === 0 ? ' · 已停用' : ''}` }))"
546
- />
547
- </a-form-item>
548
- <a-form-item :label="$t('agentLanesView.k1bl78fu010')">
549
- <a-radio-group v-model:value="memberForm.role">
550
- <a-radio value="worker">
551
- worker
552
- </a-radio>
553
- <a-radio value="lead">
554
- {{ $t('agentLanesView.k1weovo028') }}
555
- </a-radio>
556
- </a-radio-group>
557
- </a-form-item>
558
- <a-form-item :label="$t('agentLanesView.k1vhvbz7011')">
559
- <a-textarea
560
- v-model:value="memberForm.systemPrompt"
561
- :rows="3"
562
- :placeholder="$t('agentLanesView.k7ft6ig012')"
563
- />
564
- </a-form-item>
565
- <div class="mode-hint">
566
- {{ $t('agentLanesView.knybb6r029') }}
567
- </div>
568
- </a-form>
569
-
570
- <!-- 模式三:部署 AgentTeam(批量) -->
571
- <a-form
572
- v-else
573
- layout="vertical"
574
- class="member-form"
575
- >
576
- <a-form-item :label="$t('agentLanesView.k29om9b013')">
577
- <a-select
578
- v-model:value="selectedTeamId"
579
- :placeholder="$t('agentLanesView.kur1otz014')"
580
- :options="teams.map(t => ({ value: t.id, label: $t('agentLanesView.k1qcmxyu040', { p0: t.name, p1: t.memberCount, p2: t.hasLead ? ',含 lead' : '' }) }))"
581
- />
582
- </a-form-item>
583
- <div class="mode-hint">
584
- {{ $t('agentLanesView.k1qqnq5030') }}
585
- </div>
586
- </a-form>
587
- </a-modal>
588
-
589
- <!-- 编辑成员(名 / 场景提示词) -->
590
- <a-modal
591
- v-model:open="editModalOpen"
592
- :title="$t('agentLanesView.k19j5rho041', { p0: editForm.name || '' })"
593
- :confirm-loading="editSubmitting"
594
- ok-text="保存"
595
- cancel-text="取消"
596
- @ok="submitEditMember"
597
- >
598
- <a-form
599
- layout="vertical"
600
- class="member-form"
601
- >
602
- <a-form-item :label="$t('agentLanesView.k3nufdm005')">
603
- <a-input v-model:value="editForm.name" />
604
- </a-form-item>
605
- <a-form-item label="角色 / harness">
606
- <a-space>
607
- <a-tag :color="editForm.role === 'lead' ? 'purple' : 'blue'">
608
- {{ editForm.role }}
609
- </a-tag>
610
- <a-tag>{{ editForm.harness }}</a-tag>
611
- </a-space>
612
- </a-form-item>
613
- <a-form-item label="场景系统提示词(systemPromptPrefix)">
614
- <a-textarea
615
- v-model:value="editForm.systemPrompt"
616
- :rows="6"
617
- :placeholder="$t('agentLanesView.k10ti81i015')"
618
- />
619
- <template #extra>
620
- <span class="ws-hint">{{ $t('agentLanesView.k1rw1rd2031') }}</span>
621
- </template>
622
- </a-form-item>
623
- </a-form>
624
- </a-modal>
625
- <!-- harness 原生终端(omp rpc-ui 镜像 · 每成员独立会话 · HITL 控制) -->
626
- <OmpTerminalPanel
627
- v-model:open="terminalOpen"
628
- :agent-id="terminalAgentId"
629
- :channel-id="channelId"
630
- :pid="null"
631
- :subtitle="terminalSubtitle"
632
- />
633
- </div>
634
- </template>
635
-
636
- <style scoped>
637
- .lanes-wrap {
638
- display: flex;
639
- flex: 1 1 auto;
640
- flex-direction: column;
641
- width: 100%;
642
- max-width: 100%;
643
- min-width: 0;
644
- height: 100%;
645
- min-height: 0;
646
- overflow: hidden;
647
- background: var(--paper); /* 灰画布:气泡白卡在此浮出(Slack 声部分层) */
648
- }
649
- .toolbar {
650
- display: flex;
651
- flex: 0 0 auto;
652
- gap: 12px;
653
- min-width: 0;
654
- align-items: center;
655
- justify-content: space-between;
656
- padding: 8px 14px 6px;
657
- background: var(--paper-raised);
658
- border-bottom: 1px solid var(--line);
659
- }
660
- .team-summary {
661
- display: flex;
662
- flex: 1 1 auto;
663
- gap: 7px;
664
- min-width: 0;
665
- align-items: baseline;
666
- }
667
- .ts-label {
668
- flex: 0 0 auto;
669
- font-size: 11px;
670
- letter-spacing: 0.08em;
671
- color: var(--ink-faint);
672
- }
673
- .ts-count {
674
- flex: 0 0 auto;
675
- font-size: 15px;
676
- font-weight: 700;
677
- line-height: 1;
678
- color: var(--ink);
679
- }
680
- .ts-unit {
681
- flex: 0 0 auto;
682
- font-size: 11px;
683
- color: var(--ink-faint);
684
- }
685
- .ts-detail {
686
- flex: 0 1 auto;
687
- min-width: 0;
688
- overflow: hidden;
689
- font-size: 11px;
690
- font-family: var(--font-mono);
691
- color: var(--ink-faint);
692
- text-overflow: ellipsis;
693
- white-space: nowrap;
694
- }
695
- .lanes {
696
- overscroll-behavior: contain;
697
- display: flex;
698
- flex: 1 1 auto;
699
- gap: 10px;
700
- width: 100%;
701
- min-width: 0;
702
- min-height: 0;
703
- padding: 8px 12px 12px;
704
- overflow-x: auto;
705
- overflow-y: hidden;
706
- }
707
- .lane {
708
- display: flex;
709
- flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
710
- flex-direction: column;
711
- min-width: 240px;
712
- background: var(--paper-raised);
713
- border: 1px solid var(--line);
714
- border-radius: var(--radius-panel);
715
- box-shadow: var(--shadow-card);
716
- container-type: inline-size; /* 泳道自身为容器:窄列时内部自适应 */
717
- }
718
- /* 双层头部:第一行身份(头像/名/徽标),第二行状态摘要 + 操作簇 ——
719
- 单行方案在 320px 列内固定元素 ~360px 必然挤压遮挡,分层后各行均有余量 */
720
- .lane-head {
721
- display: flex;
722
- flex: 0 0 auto;
723
- flex-direction: column;
724
- gap: 8px;
725
- padding: 10px 12px 9px;
726
- font-size: 13px;
727
- border-bottom: 1px solid var(--line);
728
- }
729
- .head-top {
730
- display: flex;
731
- gap: 7px;
732
- min-width: 0;
733
- align-items: center;
734
- }
735
- /* 身份头像章:稳定身份色 + 白首字母;右下状态 pip(busy 呼吸 / stopped 红 / idle 静灰) */
736
- .lane-ava {
737
- position: relative;
738
- display: inline-flex;
739
- flex: 0 0 auto;
740
- align-items: center;
741
- justify-content: center;
742
- width: 26px;
743
- height: 26px;
744
- font-size: 11px;
745
- font-weight: 600;
746
- color: var(--on-av);
747
- border-radius: var(--radius-panel-sm);
748
- }
749
- .lane-ava > span:first-child {
750
- display: inline-flex;
751
- align-items: center;
752
- justify-content: center;
753
- width: 100%;
754
- height: 100%;
755
- background: var(--av, var(--av-fallback));
756
- border-radius: var(--radius-panel-sm);
757
- }
758
- .lane-state {
759
- position: absolute;
760
- right: -2px;
761
- bottom: -2px;
762
- width: 8px;
763
- height: 8px;
764
- background: var(--ink-fainter);
765
- border: 1.5px solid var(--paper-raised);
766
- border-radius: 50%;
767
- }
768
- .lane-state.busy {
769
- background: var(--tone-live-dot);
770
- animation: lane-breathe 1.9s ease-in-out infinite;
771
- }
772
- .lane-state.stopped { background: var(--tone-danger-dot); }
773
- @keyframes lane-breathe {
774
- 0%, 100% { opacity: 0.55; }
775
- 50% { opacity: 1; }
776
- }
777
- @media (prefers-reduced-motion: reduce) {
778
- .lane-state.busy { animation: none; opacity: 0.9; }
779
- }
780
- .lane-name {
781
- flex: 1 1 auto;
782
- min-width: 0;
783
- overflow: hidden;
784
- font-size: 13.5px;
785
- font-weight: 700;
786
- text-overflow: ellipsis;
787
- white-space: nowrap;
788
- }
789
- /* 中性小徽标:场景/角色/终端 —— 发丝线 chip 或墨色填充,不再叠 antd 多色 tag */
790
- .lane-chip {
791
- flex: 0 0 auto;
792
- padding: 0 6px;
793
- font-size: 9.5px;
794
- letter-spacing: 0.04em;
795
- line-height: 15px;
796
- color: var(--ink-faint);
797
- background: transparent;
798
- border: 1px solid var(--line-strong);
799
- border-radius: var(--radius-pill);
800
- }
801
- .lane-role {
802
- flex: 0 0 auto;
803
- padding: 0 7px;
804
- font-size: 9.5px;
805
- letter-spacing: 0.05em;
806
- line-height: 16px;
807
- text-transform: uppercase;
808
- border-radius: var(--radius-pill);
809
- }
810
- .lane-role.lead {
811
- color: var(--on-accent);
812
- background: var(--accent);
813
- }
814
- .lane-role.worker {
815
- color: var(--ink-soft);
816
- border: 1px solid var(--line-strong);
817
- }
818
- .term-badge {
819
- display: inline-flex;
820
- gap: 4px;
821
- flex: 0 0 auto;
822
- align-items: center;
823
- padding: 0 6px;
824
- font-family: var(--font-mono);
825
- font-size: 10px;
826
- line-height: 15px;
827
- color: var(--ink-faint);
828
- border: 1px solid var(--line-strong);
829
- border-radius: var(--radius-pill);
830
- }
831
- .term-badge .term-dot {
832
- width: 5px;
833
- height: 5px;
834
- border-radius: 50%;
835
- }
836
- .head-sub {
837
- display: flex;
838
- gap: 8px;
839
- min-width: 0;
840
- align-items: center;
841
- justify-content: space-between;
842
- }
843
- .lane-meta {
844
- flex: 1 1 auto;
845
- min-width: 0;
846
- overflow: hidden;
847
- font-size: 10.5px;
848
- font-family: var(--font-mono);
849
- color: var(--ink-faint);
850
- text-overflow: ellipsis;
851
- white-space: nowrap;
852
- }
853
- /* 执行中进度:busy 时展示当前任务标题 + 进度 %(leader 对 worker 推进的实时可见性) */
854
- .lane-progress {
855
- display: inline-flex;
856
- align-items: center;
857
- padding: 0 5px;
858
- margin-left: 4px;
859
- color: var(--tone-info-dot);
860
- background: color-mix(in srgb, var(--tone-info-dot) 10%, transparent);
861
- border-radius: var(--radius-pill);
862
- }
863
- /* 操作簇:统一浅底胶囊分组,常驻可见(hover 提亮);hairline 分隔破坏性操作 */
864
- .lane-actions {
865
- display: flex;
866
- flex: 0 0 auto;
867
- gap: 5px;
868
- align-items: center;
869
- padding: 2px;
870
- background: var(--hover-tint);
871
- border-radius: var(--radius-chip);
872
- }
873
- .lane-actions .ant-btn {
874
- font-size: 12px;
875
- opacity: 0.78;
876
- transition: opacity 0.15s ease;
877
- }
878
- .lane-head:hover .lane-actions .ant-btn { opacity: 1; }
879
- .actions-divider {
880
- flex: 0 0 auto;
881
- width: 1px;
882
- height: 14px;
883
- margin-inline: 2px;
884
- background: color-mix(in srgb, currentColor 16%, transparent);
885
- }
886
- .lane-term { padding-inline: 7px; }
887
- .lane-edit,
888
- .lane-stop,
889
- .lane-remove { padding-inline: 5px; }
890
- .term-label {
891
- display: none;
892
- margin-inline-start: 5px;
893
- }
894
- /* 窄泳道渐进披露(<300px 场景徽标让位;<260px 终端徽标让位;操作簇永不隐藏) */
895
- @container (min-width: 380px) {
896
- .term-label { display: inline; }
897
- }
898
- @container (max-width: 300px) {
899
- .lane-chip { display: none; }
900
- }
901
- @container (max-width: 260px) {
902
- .term-badge { display: none; }
903
- }
904
- .lane-body {
905
- overscroll-behavior: contain;
906
- flex: 1 1 auto;
907
- min-height: 0;
908
- padding: 8px 4px 16px;
909
- overflow-y: auto;
910
- background: var(--paper); /* 灰画布:消息气泡白卡浮出 */
911
- }
912
- .empty {
913
- display: flex;
914
- flex: 1 1 auto;
915
- flex-direction: column;
916
- gap: 6px;
917
- align-items: center;
918
- justify-content: center;
919
- padding: 32px 12px;
920
- color: var(--ink-faint);
921
- font-size: 12px;
922
- text-align: center;
923
- }
924
- .empty-icon {
925
- font-size: 22px;
926
- color: var(--ink-faint);
927
- }
928
- .empty-title {
929
- margin: 0;
930
- font-size: 13px;
931
- color: var(--ink-soft);
932
- }
933
- .empty-hint {
934
- margin: 0;
935
- font-size: 11px;
936
- color: var(--ink-faint);
937
- }
938
- .member-form { margin-top: 8px; }
939
- .mode-switch { margin-top: 4px; }
940
- .mode-hint {
941
- padding: 6px 8px;
942
- font-size: 11px;
943
- opacity: 0.6;
944
- background: color-mix(in srgb, currentColor 5%, transparent);
945
- border-radius: var(--radius-chip);
946
- }
947
-
948
- /* ── 窄屏(≤1023):泳道从"并排仪表"改为"一次一泳道"的横向卡片流 ──
949
- 桌面 min-width 240px 的泳道在 390px 下并排 = 每列都被压到极限;
950
- 改为 88% 宽 + scroll-snap:一屏一路信号,横扫切换成员。 */
951
- @media (max-width: 1023.98px) {
952
- .toolbar {
953
- flex-wrap: wrap;
954
- gap: 8px;
955
- padding: 8px 10px 6px;
956
- }
957
-
958
- .ts-label,
959
- .ts-unit,
960
- .ts-detail,
961
- .lane-meta,
962
- .empty-hint {
963
- font-size: 11.5px;
964
- }
965
-
966
- .lanes {
967
- padding: 8px;
968
- scroll-snap-type: x mandatory;
969
- -webkit-overflow-scrolling: touch;
970
- }
971
-
972
- .lane {
973
- flex: 0 0 88%;
974
- min-width: 0;
975
- scroll-snap-align: center;
976
- }
977
-
978
- .lane-name {
979
- font-size: 14px;
980
- }
981
-
982
- .lane-chip,
983
- .lane-role,
984
- .term-badge {
985
- font-size: 11.5px;
986
- line-height: 18px;
987
- }
988
-
989
- .lane-actions .ant-btn {
990
- min-width: 40px;
991
- min-height: 40px;
992
- }
993
-
994
- .empty-title {
995
- font-size: 13px;
996
- }
997
- }
998
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Agent lanes 视图(P1):每个 agent 一列并排流(相邻面板范式,2-4 agent 最佳)。
4
+ * 列头:状态徽标 + 队列上下文;列内:该 agent 的事件卡片(独立滚动)。
5
+ * 团队成员管理(P3):添加成员(name/harness/role)/ 移除成员;lead 执行中自主管理的
6
+ * 成员变更(agent.member 事件)同样在此实时呈现——实体列表是唯一状态源。
7
+ *
8
+ * harness 终端控制(rpc-ui HITL):每个 omp 成员 lane 可打开原生终端抽屉
9
+ * (xterm 实时 TUI 渲染 + steer/follow_up 注入 + ask 对话框应答)。
10
+ * 团队编排(任务指派/调度)仍由 channel 系统负责,终端是 harness 层的直接
11
+ * 人类控制通道,二者互不干扰。omp lazy spawn:进程随首个任务启动,未启动时
12
+ * 终端按钮仍可用(抽屉内等待 + 自动接入)。
13
+ */
14
+ import { message } from 'ant-design-vue'
15
+ import { useStorage } from '@vueuse/core'
16
+ import { useEntitiesStore } from '@/app/stores/workshop/entities'
17
+ import { useWorkshopApi, type TerminalSessionDto } from '@/app/composables/workshop/useWorkshopApi'
18
+ import { agentHueColor } from '@/app/composables/workshop/useEventBlocks'
19
+ import LaneBlocks from '@/app/components/workshop/lanes/LaneBlocks.vue'
20
+ import OmpTerminalPanel from '@/app/components/workshop/terminal/OmpTerminalPanel.vue'
21
+
22
+ const { t } = useI18n()
23
+
24
+ const props = defineProps<{ channelId: string }>()
25
+ const entities = useEntitiesStore()
26
+ const api = useWorkshopApi()
27
+
28
+ const agents = computed(() => entities.agents[props.channelId] ?? [])
29
+
30
+ // ===== 泳道列宽拖拽调节(PaneSplitter;按 agentId 持久化,双击复位默认宽) =====
31
+ const LANE_W_DEFAULT = 320
32
+ const laneWidths = useStorage<Record<string, number>>('aw.harness.laneW', {})
33
+ const laneWidth = (id: string): number => laneWidths.value[id] ?? LANE_W_DEFAULT
34
+ const resizeLane = (id: string, d: number): void => {
35
+ laneWidths.value = { ...laneWidths.value, [id]: Math.min(720, Math.max(240, laneWidth(id) + d)) }
36
+ }
37
+ const resetLane = (id: string): void => {
38
+ const next = { ...laneWidths.value }
39
+ Reflect.deleteProperty(next, id)
40
+ laneWidths.value = next
41
+ }
42
+
43
+ /** 泳道身份:头像章 + 稳定身份色(与聊天头像/提及卡同一哈希色相源) */
44
+ const laneInitial = (name: string): string => name.trim().charAt(0).toUpperCase() || '?'
45
+ const laneHue = (id: string): string => agentHueColor(id)
46
+
47
+ /** harness 终端徽章色(antd 语义 → 本设计 tone 点;仅作指示,文字仍说明状态) */
48
+ const TERM_DOT: Record<string, string> = {
49
+ processing: 'var(--tone-live-dot)',
50
+ warning: 'var(--tone-warning-dot)',
51
+ success: 'var(--tone-success-dot)',
52
+ }
53
+
54
+ // ===== 成员管理(用户侧 REST;状态回流以 WS agent.member 事件为准) =====
55
+ const memberModalOpen = ref(false)
56
+ const memberSubmitting = ref(false)
57
+ /** 添加模式:从零创建 / 从模板克隆 / 部署编组(批量) */
58
+ const addMode = ref<'create' | 'template' | 'team'>('create')
59
+ const memberForm = reactive({
60
+ name: '',
61
+ harness: 'omp' as 'omp' | 'mock' | 'claude',
62
+ role: 'worker' as 'lead' | 'worker',
63
+ systemPrompt: '',
64
+ })
65
+ /** 模板克隆 / 编组部署选项(弹窗打开时懒加载) */
66
+ const templates = ref<Array<{ id: string, name: string, harness: string, enabled: number }>>([])
67
+ const teams = ref<Array<{ id: string, name: string, memberCount: number, hasLead: boolean }>>([])
68
+ const selectedTemplateId = ref<string>('')
69
+ const selectedTeamId = ref<string>('')
70
+
71
+ const loadCatalog = async (): Promise<void> => {
72
+ try {
73
+ const [tplRes, teamRes] = await Promise.all([api.listTemplates(), api.listTeams()])
74
+ templates.value = (tplRes.data ?? []).map(t => ({ id: t.id, name: t.name, harness: t.harness, enabled: t.enabled }))
75
+ teams.value = (teamRes.data ?? []).map(t => ({
76
+ id: t.id,
77
+ name: t.name,
78
+ memberCount: t.members.length,
79
+ hasLead: t.members.some(m => m.role === 'lead'),
80
+ }))
81
+ }
82
+ catch { /* 目录拉取失败:对应模式显示空并提示刷新 */ }
83
+ }
84
+
85
+ // ===== 编辑成员(改名 / 改场景提示词 / 启停)=====
86
+ const editModalOpen = ref(false)
87
+ const editSubmitting = ref(false)
88
+ const editForm = reactive({ agentId: '', name: '', role: 'worker' as 'lead' | 'worker', harness: '', systemPrompt: '' })
89
+ const openEditMember = (a: { agentId: string, name: string, role: string, harness: string, config?: Record<string, unknown> }): void => {
90
+ editForm.agentId = a.agentId
91
+ editForm.name = a.name
92
+ editForm.role = a.role === 'lead' ? 'lead' : 'worker'
93
+ editForm.harness = a.harness
94
+ editForm.systemPrompt = typeof a.config?.systemPromptPrefix === 'string' ? a.config.systemPromptPrefix : ''
95
+ editModalOpen.value = true
96
+ }
97
+ const submitEditMember = async (): Promise<void> => {
98
+ const name = editForm.name.trim()
99
+ if (!name) {
100
+ message.warning(t('agentLanesView.ky6jqt4032'))
101
+ return
102
+ }
103
+ editSubmitting.value = true
104
+ try {
105
+ await api.updateChannelAgent(props.channelId, editForm.agentId, {
106
+ name,
107
+ config: {
108
+ // 保留既有 config,仅更新 systemPromptPrefix(编辑弹窗只暴露该字段)
109
+ ...(entities.agents[props.channelId]?.find(a => a.agentId === editForm.agentId)?.config ?? {}),
110
+ systemPromptPrefix: editForm.systemPrompt.trim(),
111
+ },
112
+ reason: t('agentLanesView.kfvflle033'),
113
+ })
114
+ message.success(t('agentLanesView.k21xjz2044', { p0: name }))
115
+ editModalOpen.value = false
116
+ }
117
+ catch (err) {
118
+ message.error(t('agentLanesView.k3jmrw1045', { p0: apiErrorMessage(err) }))
119
+ }
120
+ finally {
121
+ editSubmitting.value = false
122
+ }
123
+ }
124
+
125
+ const openMemberModal = (): void => {
126
+ memberForm.name = ''
127
+ memberForm.harness = 'omp'
128
+ memberForm.role = 'worker'
129
+ memberForm.systemPrompt = ''
130
+ addMode.value = 'create'
131
+ selectedTemplateId.value = ''
132
+ selectedTeamId.value = ''
133
+ memberModalOpen.value = true
134
+ void loadCatalog()
135
+ }
136
+
137
+ const submitMember = async (): Promise<void> => {
138
+ if (addMode.value === 'create') {
139
+ const name = memberForm.name.trim()
140
+ if (!name) {
141
+ message.warning(t('agentLanesView.ky6jqt4032'))
142
+ return
143
+ }
144
+ memberSubmitting.value = true
145
+ try {
146
+ await api.addChannelAgent(props.channelId, {
147
+ name,
148
+ harness: memberForm.harness,
149
+ role: memberForm.role,
150
+ config: memberForm.systemPrompt.trim()
151
+ ? { systemPromptPrefix: memberForm.systemPrompt.trim() }
152
+ : undefined,
153
+ })
154
+ message.success(t('agentLanesView.k1g5ykr3046', { p0: name }))
155
+ memberModalOpen.value = false
156
+ }
157
+ catch (err) {
158
+ message.error(t('agentLanesView.k1j97j74047', { p0: apiErrorMessage(err) }))
159
+ }
160
+ finally {
161
+ memberSubmitting.value = false
162
+ }
163
+ return
164
+ }
165
+ if (addMode.value === 'template') {
166
+ if (!selectedTemplateId.value) {
167
+ message.warning(t('agentLanesView.k13xo8sz034'))
168
+ return
169
+ }
170
+ memberSubmitting.value = true
171
+ try {
172
+ const tpl = templates.value.find(t => t.id === selectedTemplateId.value)
173
+ await api.addChannelAgent(props.channelId, {
174
+ agentId: selectedTemplateId.value,
175
+ role: memberForm.role,
176
+ config: memberForm.systemPrompt.trim()
177
+ ? { systemPromptPrefix: memberForm.systemPrompt.trim() }
178
+ : undefined,
179
+ })
180
+ message.success(t('agentLanesView.kk3uwzo048', { p0: memberForm.role, p1: tpl?.name ?? '' }))
181
+ memberModalOpen.value = false
182
+ }
183
+ catch (err) {
184
+ const text = apiErrorMessage(err)
185
+ message.error(t('agentLanesView.cloneFail', { p0: text.includes('LEAD_EXISTS') ? t('agentLanesView.leadExistsShort') : `: ${text}` }))
186
+ }
187
+ finally {
188
+ memberSubmitting.value = false
189
+ }
190
+ return
191
+ }
192
+ // 部署编组:批量克隆全部成员模板(lead 冲突由服务端 409 拒绝)
193
+ if (!selectedTeamId.value) {
194
+ message.warning(t('agentLanesView.k1tjd5ab035'))
195
+ return
196
+ }
197
+ memberSubmitting.value = true
198
+ try {
199
+ const team = teams.value.find(t => t.id === selectedTeamId.value)
200
+ await api.deployTeam(selectedTeamId.value, props.channelId)
201
+ message.success(t('agentLanesView.k1gslqf9050', { p0: team?.name ?? '', p1: team?.memberCount ?? 0 }))
202
+ memberModalOpen.value = false
203
+ }
204
+ catch (err) {
205
+ const text = apiErrorMessage(err)
206
+ message.error(t('agentLanesView.deployFail', { p0: text.includes('LEAD_EXISTS') ? t('agentLanesView.leadExistsChannel') : `: ${text}` }))
207
+ }
208
+ finally {
209
+ memberSubmitting.value = false
210
+ }
211
+ }
212
+
213
+ const removing = ref<string | null>(null)
214
+ const removeMember = async (agentId: string, name: string): Promise<void> => {
215
+ removing.value = agentId
216
+ try {
217
+ await api.removeChannelAgent(props.channelId, agentId)
218
+ message.success(t('agentLanesView.k1n3o03a052', { p0: name }))
219
+ }
220
+ catch (err) {
221
+ message.error(t('agentLanesView.k13dsp10053', { p0: apiErrorMessage(err) }))
222
+ }
223
+ finally {
224
+ removing.value = null
225
+ }
226
+ }
227
+
228
+ // ===== HITL:独立中断成员运行时(worker/lead 均可;成员保留,下次任务自动重装配) =====
229
+ const stopping = ref<string | null>(null)
230
+ const stopMember = async (agentId: string, name: string): Promise<void> => {
231
+ stopping.value = agentId
232
+ try {
233
+ await api.stopChannelAgent(props.channelId, agentId)
234
+ message.success(t('agentLanesView.kcpbv47054', { p0: name }))
235
+ }
236
+ catch (err) {
237
+ message.error(t('agentLanesView.k139ec5j055', { p0: apiErrorMessage(err) }))
238
+ }
239
+ finally {
240
+ stopping.value = null
241
+ }
242
+ }
243
+
244
+ // ===== harness 终端控制(rpc-ui HITL;每成员独立 omp 会话) =====
245
+ const terminals = ref<TerminalSessionDto[]>([])
246
+ /** agentId → 存活终端会话(lane 头徽标 + 终端按钮态) */
247
+ const terminalOf = computed(() => {
248
+ const map = new Map<string, TerminalSessionDto>()
249
+ for (const t of terminals.value) {
250
+ if (!t.alive || !t.agentId) continue
251
+ map.set(t.agentId, t)
252
+ }
253
+ return map
254
+ })
255
+ const termBadge = (t: TerminalSessionDto | undefined): { text: string, color: string } | null => {
256
+ if (!t) return null
257
+ if (t.streaming) return { text: 'streaming', color: 'processing' }
258
+ if (t.running) return { text: 'turn', color: 'warning' }
259
+ return { text: 'idle', color: 'success' }
260
+ }
261
+
262
+ let terminalsTimer: ReturnType<typeof setInterval> | null = null
263
+ const loadTerminals = async (): Promise<void> => {
264
+ try {
265
+ const res = await api.listChannelTerminals(props.channelId)
266
+ terminals.value = res.data ?? []
267
+ }
268
+ catch { /* 轮询失败静默(下次恢复) */ }
269
+ }
270
+
271
+ const terminalOpen = ref(false)
272
+ const terminalAgentId = ref<string | null>(null)
273
+ const terminalSubtitle = ref('')
274
+ const openTerminal = (a: { agentId: string, name: string, role: string }): void => {
275
+ terminalAgentId.value = a.agentId
276
+ terminalSubtitle.value = `${a.name} · ${a.role}`
277
+ terminalOpen.value = true
278
+ }
279
+
280
+ onMounted(() => {
281
+ void loadTerminals()
282
+ terminalsTimer = setInterval(() => void loadTerminals(), 5000)
283
+ })
284
+ onBeforeUnmount(() => {
285
+ if (terminalsTimer) clearInterval(terminalsTimer)
286
+ })
287
+ </script>
288
+
289
+ <template>
290
+ <div class="lanes-wrap">
291
+ <div class="toolbar">
292
+ <div class="team-summary">
293
+ <span class="ts-label">{{ $t('agentLanesView.k3y5ja016') }}</span>
294
+ <span class="ts-count">{{ agents.length }}</span>
295
+ <span class="ts-unit">{{ $t('agentLanesView.k3ll6sa017') }}</span>
296
+ <span class="ts-detail">lead {{ agents.filter(a => a.role === 'lead').length }} · worker {{ agents.filter(a => a.role === 'worker').length }}</span>
297
+ </div>
298
+ <a-button
299
+ size="small"
300
+ type="primary"
301
+ ghost
302
+ @click="openMemberModal"
303
+ >
304
+ <span class="i-tabler-user-plus" />
305
+ <span>{{ $t('agentLanesView.k1fmutgo018') }}</span>
306
+ </a-button>
307
+ </div>
308
+ <div class="lanes">
309
+ <div
310
+ v-if="agents.length === 0"
311
+ class="empty"
312
+ >
313
+ <!-- 快照未到 → 同步中;真无成员 → 空态指引(诚实区分,不永挂"等待") -->
314
+ <template v-if="!entities.channels[channelId]">
315
+ <span class="i-tabler-refresh empty-icon" />
316
+ <p class="empty-title">
317
+ {{ $t('agentLanesView.k1woev5v019') }}
318
+ </p>
319
+ </template>
320
+ <template v-else>
321
+ <span class="i-tabler-users-group empty-icon" />
322
+ <p class="empty-title">
323
+ {{ $t('agentLanesView.k122h4pc020') }}
324
+ </p>
325
+ <p class="empty-hint">
326
+ {{ $t('agentLanesView.k7cr2h8021') }}
327
+ </p>
328
+ </template>
329
+ </div>
330
+ <template
331
+ v-for="a in agents"
332
+ :key="a.agentId"
333
+ >
334
+ <div
335
+ class="lane"
336
+ :style="{ flexBasis: `${laneWidth(a.agentId)}px` }"
337
+ >
338
+ <div class="lane-head">
339
+ <!-- 第一行:身份(头像章 + 名称 + 中性徽标);名称为弹性吸收项,任意宽度截断不遮挡 -->
340
+ <div class="head-top">
341
+ <span class="lane-ava">
342
+ <span :style="{ '--av': laneHue(a.agentId) }">{{ laneInitial(a.name) }}</span>
343
+ <span
344
+ class="lane-state"
345
+ :class="a.state"
346
+ />
347
+ </span>
348
+ <span
349
+ class="lane-name"
350
+ :title="a.name"
351
+ >{{ a.name }}</span>
352
+ <span
353
+ v-if="a.config?.systemPromptPrefix"
354
+ class="lane-chip"
355
+ :title="$t('agentLanesView.k107s4am001')"
356
+ >
357
+ {{ $t('agentLanesView.k3xycu022') }}
358
+ </span>
359
+ <span
360
+ class="lane-role"
361
+ :class="a.role"
362
+ >
363
+ {{ a.role }}
364
+ </span>
365
+ <span
366
+ v-if="termBadge(terminalOf.get(a.agentId))"
367
+ class="term-badge"
368
+ :title="$t('agentLanesView.k1wn2vmt036', { p0: terminalOf.get(a.agentId)?.pid })"
369
+ >
370
+ <span
371
+ class="term-dot"
372
+ :style="{ background: TERM_DOT[termBadge(terminalOf.get(a.agentId))!.color] ?? 'var(--tone-neutral-dot)' }"
373
+ />
374
+ {{ termBadge(terminalOf.get(a.agentId))!.text }}
375
+ </span>
376
+ </div>
377
+ <!-- 第二行:状态摘要 + 操作簇(常驻可见;hairline 分隔破坏性操作) -->
378
+ <div class="head-sub">
379
+ <span class="lane-meta">
380
+ <template v-if="a.state === 'busy' && a.currentTaskTitle">
381
+ {{ a.currentTaskTitle }}
382
+ <span
383
+ v-if="a.currentTaskProgress != null"
384
+ class="lane-progress"
385
+ >{{ a.currentTaskProgress }}%</span>
386
+ </template>
387
+ <template v-else>{{ a.state }} · Q{{ a.queued ?? 0 }}</template>
388
+ </span>
389
+ <div class="lane-actions">
390
+ <a-button
391
+ v-if="a.harness === 'omp'"
392
+ size="small"
393
+ type="primary"
394
+ ghost
395
+ class="lane-term"
396
+ :title="$t('agentLanesView.k1884q17002')"
397
+ @click="openTerminal(a)"
398
+ >
399
+ <span class="i-tabler-terminal-2" />
400
+ <span class="term-label">{{ $t('agentLanesView.k4588s023') }}</span>
401
+ </a-button>
402
+ <a-button
403
+ size="small"
404
+ type="text"
405
+ class="lane-edit"
406
+ :title="$t('agentLanesView.k1pgd3tf003')"
407
+ @click="openEditMember(a)"
408
+ >
409
+ <span class="i-tabler-edit" />
410
+ </a-button>
411
+ <span class="actions-divider" />
412
+ <a-popconfirm
413
+ :title="$t('agentLanesView.k1l029kf037', { p0: a.name })"
414
+ :ok-text="$t('common.stop')"
415
+ :cancel-text="$t('common.cancel')"
416
+ @confirm="stopMember(a.agentId, a.name)"
417
+ >
418
+ <a-button
419
+ size="small"
420
+ type="text"
421
+ class="lane-stop"
422
+ :loading="stopping === a.agentId"
423
+ :title="$t('agentLanesView.hitlStopTitle')"
424
+ >
425
+ <span class="i-tabler-player-stop" />
426
+ </a-button>
427
+ </a-popconfirm>
428
+ <a-popconfirm
429
+ :title="$t('agentLanesView.kt3n27m038', { p0: a.name })"
430
+ :ok-text="$t('common.remove')"
431
+ :cancel-text="$t('common.cancel')"
432
+ @confirm="removeMember(a.agentId, a.name)"
433
+ >
434
+ <a-button
435
+ size="small"
436
+ type="text"
437
+ danger
438
+ class="lane-remove"
439
+ :loading="removing === a.agentId"
440
+ >
441
+ <span class="i-tabler-x" />
442
+ </a-button>
443
+ </a-popconfirm>
444
+ </div>
445
+ </div>
446
+ </div>
447
+ <div class="lane-body">
448
+ <!-- 列体:同类型连续事件聚合为块组件(实时/历史同一路径,无重复消费;
449
+ 宽度随泳道拖拽自适应,EventBlock 26px+1fr 网格自收缩) -->
450
+ <LaneBlocks
451
+ :channel-id="channelId"
452
+ :agent-id="a.agentId"
453
+ />
454
+ </div>
455
+ </div>
456
+ <!-- 泳道分隔条:每列右侧都挂(含最右列);统一调节"其左侧泳道"的宽度,
457
+ 尾条即最右泳道的右缘调节柄(双击复位;键盘 ←→ 微调) -->
458
+ <workshop-pane-splitter
459
+ :label="$t('agentLanesView.k7xt1y6039', { p0: a.name })"
460
+ @resize="d => resizeLane(a.agentId, d)"
461
+ @reset="resetLane(a.agentId)"
462
+ />
463
+ </template>
464
+ </div>
465
+
466
+ <!-- 添加成员弹窗(三模式:从零创建 / 模板克隆 / 编组部署) -->
467
+ <a-modal
468
+ v-model:open="memberModalOpen"
469
+ :title="$t('agentLanesView.k17kcn55004')"
470
+ :confirm-loading="memberSubmitting"
471
+ :ok-text="$t('common.add')"
472
+ :cancel-text="$t('common.cancel')"
473
+ @ok="submitMember"
474
+ >
475
+ <a-radio-group
476
+ v-model:value="addMode"
477
+ class="mode-switch"
478
+ >
479
+ <a-radio-button value="create">
480
+ {{ $t('agentLanesView.k1b7cwvy024') }}
481
+ </a-radio-button>
482
+ <a-radio-button value="template">
483
+ {{ $t('agentLanesView.k1ndey1w025') }}
484
+ </a-radio-button>
485
+ <a-radio-button value="team">
486
+ {{ $t('agentLanesView.k1l5qyux026') }}
487
+ </a-radio-button>
488
+ </a-radio-group>
489
+
490
+ <!-- 模式一:从零创建 -->
491
+ <a-form
492
+ v-if="addMode === 'create'"
493
+ layout="vertical"
494
+ class="member-form"
495
+ >
496
+ <a-form-item :label="$t('agentLanesView.k3nufdm005')">
497
+ <a-input
498
+ v-model:value="memberForm.name"
499
+ :placeholder="$t('agentLanesView.namePh')"
500
+ @press-enter="submitMember"
501
+ />
502
+ </a-form-item>
503
+ <a-form-item label="harness">
504
+ <a-radio-group v-model:value="memberForm.harness">
505
+ <a-radio value="omp">
506
+ {{ $t('agentLanesView.ompFull') }}
507
+ </a-radio>
508
+ <a-radio value="mock">
509
+ {{ $t('agentLanesView.kqg6783027') }}
510
+ </a-radio>
511
+ <a-radio value="claude">
512
+ claude
513
+ </a-radio>
514
+ </a-radio-group>
515
+ </a-form-item>
516
+ <a-form-item :label="$t('agentLanesView.k479op006')">
517
+ <a-radio-group v-model:value="memberForm.role">
518
+ <a-radio value="worker">
519
+ worker
520
+ </a-radio>
521
+ <a-radio value="lead">
522
+ {{ $t('agentLanesView.k1weovo028') }}
523
+ </a-radio>
524
+ </a-radio-group>
525
+ </a-form-item>
526
+ <a-form-item :label="$t('agentLanesView.kaogfp007')">
527
+ <a-textarea
528
+ v-model:value="memberForm.systemPrompt"
529
+ :rows="3"
530
+ :placeholder="$t('agentLanesView.kbqn6w5008')"
531
+ />
532
+ </a-form-item>
533
+ </a-form>
534
+
535
+ <!-- 模式二:从已有模板克隆 -->
536
+ <a-form
537
+ v-else-if="addMode === 'template'"
538
+ layout="vertical"
539
+ class="member-form"
540
+ >
541
+ <a-form-item :label="$t('agentLanesView.tplCloneLabel')">
542
+ <a-select
543
+ v-model:value="selectedTemplateId"
544
+ :placeholder="$t('agentLanesView.kung925009')"
545
+ :options="templates.map(t => ({ value: t.id, label: `${t.name}(${t.harness})${t.enabled === 0 ? $t('agentLanesView.tplDisabled') : ''}` }))"
546
+ />
547
+ </a-form-item>
548
+ <a-form-item :label="$t('agentLanesView.k1bl78fu010')">
549
+ <a-radio-group v-model:value="memberForm.role">
550
+ <a-radio value="worker">
551
+ worker
552
+ </a-radio>
553
+ <a-radio value="lead">
554
+ {{ $t('agentLanesView.k1weovo028') }}
555
+ </a-radio>
556
+ </a-radio-group>
557
+ </a-form-item>
558
+ <a-form-item :label="$t('agentLanesView.k1vhvbz7011')">
559
+ <a-textarea
560
+ v-model:value="memberForm.systemPrompt"
561
+ :rows="3"
562
+ :placeholder="$t('agentLanesView.k7ft6ig012')"
563
+ />
564
+ </a-form-item>
565
+ <div class="mode-hint">
566
+ {{ $t('agentLanesView.knybb6r029') }}
567
+ </div>
568
+ </a-form>
569
+
570
+ <!-- 模式三:部署 AgentTeam(批量) -->
571
+ <a-form
572
+ v-else
573
+ layout="vertical"
574
+ class="member-form"
575
+ >
576
+ <a-form-item :label="$t('agentLanesView.k29om9b013')">
577
+ <a-select
578
+ v-model:value="selectedTeamId"
579
+ :placeholder="$t('agentLanesView.kur1otz014')"
580
+ :options="teams.map(t => ({ value: t.id, label: $t('agentLanesView.k1qcmxyu040', { p0: t.name, p1: t.memberCount, p2: t.hasLead ? $t('agentLanesView.withLead') : '' }) }))"
581
+ />
582
+ </a-form-item>
583
+ <div class="mode-hint">
584
+ {{ $t('agentLanesView.k1qqnq5030') }}
585
+ </div>
586
+ </a-form>
587
+ </a-modal>
588
+
589
+ <!-- 编辑成员(名 / 场景提示词) -->
590
+ <a-modal
591
+ v-model:open="editModalOpen"
592
+ :title="$t('agentLanesView.k19j5rho041', { p0: editForm.name || '' })"
593
+ :confirm-loading="editSubmitting"
594
+ :ok-text="$t('common.save')"
595
+ :cancel-text="$t('common.cancel')"
596
+ @ok="submitEditMember"
597
+ >
598
+ <a-form
599
+ layout="vertical"
600
+ class="member-form"
601
+ >
602
+ <a-form-item :label="$t('agentLanesView.k3nufdm005')">
603
+ <a-input v-model:value="editForm.name" />
604
+ </a-form-item>
605
+ <a-form-item :label="$t('agentLanesView.roleHarnessLabel')">
606
+ <a-space>
607
+ <a-tag :color="editForm.role === 'lead' ? 'purple' : 'blue'">
608
+ {{ editForm.role }}
609
+ </a-tag>
610
+ <a-tag>{{ editForm.harness }}</a-tag>
611
+ </a-space>
612
+ </a-form-item>
613
+ <a-form-item :label="$t('agentLanesView.syspromptLabel')">
614
+ <a-textarea
615
+ v-model:value="editForm.systemPrompt"
616
+ :rows="6"
617
+ :placeholder="$t('agentLanesView.k10ti81i015')"
618
+ />
619
+ <template #extra>
620
+ <span class="ws-hint">{{ $t('agentLanesView.k1rw1rd2031') }}</span>
621
+ </template>
622
+ </a-form-item>
623
+ </a-form>
624
+ </a-modal>
625
+ <!-- harness 原生终端(omp rpc-ui 镜像 · 每成员独立会话 · HITL 控制) -->
626
+ <OmpTerminalPanel
627
+ v-model:open="terminalOpen"
628
+ :agent-id="terminalAgentId"
629
+ :channel-id="channelId"
630
+ :pid="null"
631
+ :subtitle="terminalSubtitle"
632
+ />
633
+ </div>
634
+ </template>
635
+
636
+ <style scoped>
637
+ .lanes-wrap {
638
+ display: flex;
639
+ flex: 1 1 auto;
640
+ flex-direction: column;
641
+ width: 100%;
642
+ max-width: 100%;
643
+ min-width: 0;
644
+ height: 100%;
645
+ min-height: 0;
646
+ overflow: hidden;
647
+ background: var(--paper); /* 灰画布:气泡白卡在此浮出(Slack 声部分层) */
648
+ }
649
+ .toolbar {
650
+ display: flex;
651
+ flex: 0 0 auto;
652
+ gap: 12px;
653
+ min-width: 0;
654
+ align-items: center;
655
+ justify-content: space-between;
656
+ padding: 8px 14px 6px;
657
+ background: var(--paper-raised);
658
+ border-bottom: 1px solid var(--line);
659
+ }
660
+ .team-summary {
661
+ display: flex;
662
+ flex: 1 1 auto;
663
+ gap: 7px;
664
+ min-width: 0;
665
+ align-items: baseline;
666
+ }
667
+ .ts-label {
668
+ flex: 0 0 auto;
669
+ font-size: 11px;
670
+ letter-spacing: 0.08em;
671
+ color: var(--ink-faint);
672
+ }
673
+ .ts-count {
674
+ flex: 0 0 auto;
675
+ font-size: 15px;
676
+ font-weight: 700;
677
+ line-height: 1;
678
+ color: var(--ink);
679
+ }
680
+ .ts-unit {
681
+ flex: 0 0 auto;
682
+ font-size: 11px;
683
+ color: var(--ink-faint);
684
+ }
685
+ .ts-detail {
686
+ flex: 0 1 auto;
687
+ min-width: 0;
688
+ overflow: hidden;
689
+ font-size: 11px;
690
+ font-family: var(--font-mono);
691
+ color: var(--ink-faint);
692
+ text-overflow: ellipsis;
693
+ white-space: nowrap;
694
+ }
695
+ .lanes {
696
+ overscroll-behavior: contain;
697
+ display: flex;
698
+ flex: 1 1 auto;
699
+ gap: 10px;
700
+ width: 100%;
701
+ min-width: 0;
702
+ min-height: 0;
703
+ padding: 8px 12px 12px;
704
+ overflow-x: auto;
705
+ overflow-y: hidden;
706
+ }
707
+ .lane {
708
+ display: flex;
709
+ flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
710
+ flex-direction: column;
711
+ min-width: 240px;
712
+ background: var(--paper-raised);
713
+ border: 1px solid var(--line);
714
+ border-radius: var(--radius-panel);
715
+ box-shadow: var(--shadow-card);
716
+ container-type: inline-size; /* 泳道自身为容器:窄列时内部自适应 */
717
+ }
718
+ /* 双层头部:第一行身份(头像/名/徽标),第二行状态摘要 + 操作簇 ——
719
+ 单行方案在 320px 列内固定元素 ~360px 必然挤压遮挡,分层后各行均有余量 */
720
+ .lane-head {
721
+ display: flex;
722
+ flex: 0 0 auto;
723
+ flex-direction: column;
724
+ gap: 8px;
725
+ padding: 10px 12px 9px;
726
+ font-size: 13px;
727
+ border-bottom: 1px solid var(--line);
728
+ }
729
+ .head-top {
730
+ display: flex;
731
+ gap: 7px;
732
+ min-width: 0;
733
+ align-items: center;
734
+ }
735
+ /* 身份头像章:稳定身份色 + 白首字母;右下状态 pip(busy 呼吸 / stopped 红 / idle 静灰) */
736
+ .lane-ava {
737
+ position: relative;
738
+ display: inline-flex;
739
+ flex: 0 0 auto;
740
+ align-items: center;
741
+ justify-content: center;
742
+ width: 26px;
743
+ height: 26px;
744
+ font-size: 11px;
745
+ font-weight: 600;
746
+ color: var(--on-av);
747
+ border-radius: var(--radius-panel-sm);
748
+ }
749
+ .lane-ava > span:first-child {
750
+ display: inline-flex;
751
+ align-items: center;
752
+ justify-content: center;
753
+ width: 100%;
754
+ height: 100%;
755
+ background: var(--av, var(--av-fallback));
756
+ border-radius: var(--radius-panel-sm);
757
+ }
758
+ .lane-state {
759
+ position: absolute;
760
+ right: -2px;
761
+ bottom: -2px;
762
+ width: 8px;
763
+ height: 8px;
764
+ background: var(--ink-fainter);
765
+ border: 1.5px solid var(--paper-raised);
766
+ border-radius: 50%;
767
+ }
768
+ .lane-state.busy {
769
+ background: var(--tone-live-dot);
770
+ animation: lane-breathe 1.9s ease-in-out infinite;
771
+ }
772
+ .lane-state.stopped { background: var(--tone-danger-dot); }
773
+ @keyframes lane-breathe {
774
+ 0%, 100% { opacity: 0.55; }
775
+ 50% { opacity: 1; }
776
+ }
777
+ @media (prefers-reduced-motion: reduce) {
778
+ .lane-state.busy { animation: none; opacity: 0.9; }
779
+ }
780
+ .lane-name {
781
+ flex: 1 1 auto;
782
+ min-width: 0;
783
+ overflow: hidden;
784
+ font-size: 13.5px;
785
+ font-weight: 700;
786
+ text-overflow: ellipsis;
787
+ white-space: nowrap;
788
+ }
789
+ /* 中性小徽标:场景/角色/终端 —— 发丝线 chip 或墨色填充,不再叠 antd 多色 tag */
790
+ .lane-chip {
791
+ flex: 0 0 auto;
792
+ padding: 0 6px;
793
+ font-size: 9.5px;
794
+ letter-spacing: 0.04em;
795
+ line-height: 15px;
796
+ color: var(--ink-faint);
797
+ background: transparent;
798
+ border: 1px solid var(--line-strong);
799
+ border-radius: var(--radius-pill);
800
+ }
801
+ .lane-role {
802
+ flex: 0 0 auto;
803
+ padding: 0 7px;
804
+ font-size: 9.5px;
805
+ letter-spacing: 0.05em;
806
+ line-height: 16px;
807
+ text-transform: uppercase;
808
+ border-radius: var(--radius-pill);
809
+ }
810
+ .lane-role.lead {
811
+ color: var(--on-accent);
812
+ background: var(--accent);
813
+ }
814
+ .lane-role.worker {
815
+ color: var(--ink-soft);
816
+ border: 1px solid var(--line-strong);
817
+ }
818
+ .term-badge {
819
+ display: inline-flex;
820
+ gap: 4px;
821
+ flex: 0 0 auto;
822
+ align-items: center;
823
+ padding: 0 6px;
824
+ font-family: var(--font-mono);
825
+ font-size: 10px;
826
+ line-height: 15px;
827
+ color: var(--ink-faint);
828
+ border: 1px solid var(--line-strong);
829
+ border-radius: var(--radius-pill);
830
+ }
831
+ .term-badge .term-dot {
832
+ width: 5px;
833
+ height: 5px;
834
+ border-radius: 50%;
835
+ }
836
+ .head-sub {
837
+ display: flex;
838
+ gap: 8px;
839
+ min-width: 0;
840
+ align-items: center;
841
+ justify-content: space-between;
842
+ }
843
+ .lane-meta {
844
+ flex: 1 1 auto;
845
+ min-width: 0;
846
+ overflow: hidden;
847
+ font-size: 10.5px;
848
+ font-family: var(--font-mono);
849
+ color: var(--ink-faint);
850
+ text-overflow: ellipsis;
851
+ white-space: nowrap;
852
+ }
853
+ /* 执行中进度:busy 时展示当前任务标题 + 进度 %(leader 对 worker 推进的实时可见性) */
854
+ .lane-progress {
855
+ display: inline-flex;
856
+ align-items: center;
857
+ padding: 0 5px;
858
+ margin-left: 4px;
859
+ color: var(--tone-info-dot);
860
+ background: color-mix(in srgb, var(--tone-info-dot) 10%, transparent);
861
+ border-radius: var(--radius-pill);
862
+ }
863
+ /* 操作簇:统一浅底胶囊分组,常驻可见(hover 提亮);hairline 分隔破坏性操作 */
864
+ .lane-actions {
865
+ display: flex;
866
+ flex: 0 0 auto;
867
+ gap: 5px;
868
+ align-items: center;
869
+ padding: 2px;
870
+ background: var(--hover-tint);
871
+ border-radius: var(--radius-chip);
872
+ }
873
+ .lane-actions .ant-btn {
874
+ font-size: 12px;
875
+ opacity: 0.78;
876
+ transition: opacity 0.15s ease;
877
+ }
878
+ .lane-head:hover .lane-actions .ant-btn { opacity: 1; }
879
+ .actions-divider {
880
+ flex: 0 0 auto;
881
+ width: 1px;
882
+ height: 14px;
883
+ margin-inline: 2px;
884
+ background: color-mix(in srgb, currentColor 16%, transparent);
885
+ }
886
+ .lane-term { padding-inline: 7px; }
887
+ .lane-edit,
888
+ .lane-stop,
889
+ .lane-remove { padding-inline: 5px; }
890
+ .term-label {
891
+ display: none;
892
+ margin-inline-start: 5px;
893
+ }
894
+ /* 窄泳道渐进披露(<300px 场景徽标让位;<260px 终端徽标让位;操作簇永不隐藏) */
895
+ @container (min-width: 380px) {
896
+ .term-label { display: inline; }
897
+ }
898
+ @container (max-width: 300px) {
899
+ .lane-chip { display: none; }
900
+ }
901
+ @container (max-width: 260px) {
902
+ .term-badge { display: none; }
903
+ }
904
+ .lane-body {
905
+ overscroll-behavior: contain;
906
+ flex: 1 1 auto;
907
+ min-height: 0;
908
+ padding: 8px 4px 16px;
909
+ overflow-y: auto;
910
+ background: var(--paper); /* 灰画布:消息气泡白卡浮出 */
911
+ }
912
+ .empty {
913
+ display: flex;
914
+ flex: 1 1 auto;
915
+ flex-direction: column;
916
+ gap: 6px;
917
+ align-items: center;
918
+ justify-content: center;
919
+ padding: 32px 12px;
920
+ color: var(--ink-faint);
921
+ font-size: 12px;
922
+ text-align: center;
923
+ }
924
+ .empty-icon {
925
+ font-size: 22px;
926
+ color: var(--ink-faint);
927
+ }
928
+ .empty-title {
929
+ margin: 0;
930
+ font-size: 13px;
931
+ color: var(--ink-soft);
932
+ }
933
+ .empty-hint {
934
+ margin: 0;
935
+ font-size: 11px;
936
+ color: var(--ink-faint);
937
+ }
938
+ .member-form { margin-top: 8px; }
939
+ .mode-switch { margin-top: 4px; }
940
+ .mode-hint {
941
+ padding: 6px 8px;
942
+ font-size: 11px;
943
+ opacity: 0.6;
944
+ background: color-mix(in srgb, currentColor 5%, transparent);
945
+ border-radius: var(--radius-chip);
946
+ }
947
+
948
+ /* ── 窄屏(≤1023):泳道从"并排仪表"改为"一次一泳道"的横向卡片流 ──
949
+ 桌面 min-width 240px 的泳道在 390px 下并排 = 每列都被压到极限;
950
+ 改为 88% 宽 + scroll-snap:一屏一路信号,横扫切换成员。 */
951
+ @media (max-width: 1023.98px) {
952
+ .toolbar {
953
+ flex-wrap: wrap;
954
+ gap: 8px;
955
+ padding: 8px 10px 6px;
956
+ }
957
+
958
+ .ts-label,
959
+ .ts-unit,
960
+ .ts-detail,
961
+ .lane-meta,
962
+ .empty-hint {
963
+ font-size: 11.5px;
964
+ }
965
+
966
+ .lanes {
967
+ padding: 8px;
968
+ scroll-snap-type: x mandatory;
969
+ -webkit-overflow-scrolling: touch;
970
+ }
971
+
972
+ .lane {
973
+ flex: 0 0 88%;
974
+ min-width: 0;
975
+ scroll-snap-align: center;
976
+ }
977
+
978
+ .lane-name {
979
+ font-size: 14px;
980
+ }
981
+
982
+ .lane-chip,
983
+ .lane-role,
984
+ .term-badge {
985
+ font-size: 11.5px;
986
+ line-height: 18px;
987
+ }
988
+
989
+ .lane-actions .ant-btn {
990
+ min-width: 40px;
991
+ min-height: 40px;
992
+ }
993
+
994
+ .empty-title {
995
+ font-size: 13px;
996
+ }
997
+ }
998
+ </style>