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,686 +1,686 @@
1
- <script setup lang="ts">
2
- /**
3
- * 任务板(open-tag TaskBoard 交互移植,按 AgentWorkShop 权限模型收敛):
4
- * - 看板/列表双视图(localStorage 持久化);看板 = 全高等宽泳道(surface-strong),
5
- * 列表 = 按状态分组的行式列表;
6
- * - 卡片拖拽:拖到「异常/取消」列 = cancelTask(HITL 中断);FAILED/CANCELED 卡拖回
7
- * 「待启动」列 = retryTask(重试派发);执行中/等待汇总/已完成由系统流转,不可手动移入;
8
- * - 状态胶囊点击菜单:取消 / 重试 / 打开详情(与拖拽同一动作面);
9
- * - 乐观更新:本地先改 state(FLIP 视觉即时),REST 失败回滚并以 WS 事件对齐。
10
- */
11
- import { message } from 'ant-design-vue'
12
- import { useEntitiesStore, type TaskView } from '@/app/stores/workshop/entities'
13
- import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
14
-
15
- const { t } = useI18n()
16
-
17
- const props = defineProps<{ channelId: string }>()
18
- const emit = defineEmits<{ (e: 'openTask', taskId: string): void }>()
19
-
20
- const entities = useEntitiesStore()
21
- const api = useWorkshopApi()
22
- const tasks = computed(() => entities.tasks[props.channelId] ?? [])
23
- /** 实体快照是否已到达(未到时空态显示同步中,不误判"没有任务") */
24
- const synced = computed(() => entities.channels[props.channelId] !== undefined
25
- || entities.tasks[props.channelId] !== undefined)
26
-
27
- /** 乐观覆盖:taskId → 预期 state(REST 进行中;WS 确认后由实体覆盖) */
28
- const optimistic = ref(new Map<string, string>())
29
- const mergedTasks = computed<TaskView[]>(() =>
30
- tasks.value.map(t => (optimistic.value.has(t.id) ? { ...t, state: optimistic.value.get(t.id)! } : t)),
31
- )
32
-
33
- interface Column {
34
- key: string
35
- title: string
36
- states: string[]
37
- dot: string
38
- /** 允许用户手动移入该列的动作(null = 系统流转列) */
39
- moveAction: 'cancel' | 'retry' | null
40
- items: TaskView[]
41
- }
42
-
43
- const BASE_COLUMNS: Array<Omit<Column, 'items'>> = [
44
- { key: 'todo', title: t('taskBoardView.k3nf71d008'), states: ['SUBMITTED', 'ASSIGNED'], dot: 'var(--tone-neutral-dot)', moveAction: 'retry' },
45
- { key: 'doing', title: t('taskBoardView.k3o5tz9009'), states: ['WORKING'], dot: 'var(--tone-info-dot)', moveAction: null },
46
- { key: 'waiting', title: t('taskBoardView.k1hpx9p1010'), states: ['WAITING'], dot: 'var(--tone-warning-dot)', moveAction: null },
47
- { key: 'done', title: t('taskBoardView.k3n77g3011'), states: ['COMPLETED'], dot: 'var(--tone-success-dot)', moveAction: null },
48
- { key: 'bad', title: t('taskBoardView.k1veotws012'), states: ['FAILED', 'CANCELED'], dot: 'var(--tone-danger-dot)', moveAction: 'cancel' },
49
- ]
50
-
51
- const columns = computed<Column[]>(() => BASE_COLUMNS.map(col => ({
52
- ...col,
53
- items: mergedTasks.value.filter(t => col.states.includes(t.state)),
54
- })))
55
-
56
- const agentName = (id: string): string =>
57
- entities.agentById(props.channelId, id)?.name ?? id.slice(0, 6)
58
- const childCount = (id: string): number =>
59
- mergedTasks.value.filter(t => t.parentId === id).length
60
-
61
- const stateOf = (t: TaskView): string => t.state
62
-
63
- // ===== 视图切换(看板/列表,持久化) =====
64
- const layout = ref<'board' | 'list'>(
65
- (import.meta.client && localStorage.getItem('aw.tasks.boardLayout') === 'list') ? 'list' : 'board',
66
- )
67
- const setLayout = (l: 'board' | 'list') => {
68
- layout.value = l
69
- if (import.meta.client) localStorage.setItem('aw.tasks.boardLayout', l)
70
- }
71
-
72
- // ===== 拖拽(HTML5 DnD;整列作为放置目标) =====
73
- const dragId = ref<string | null>(null)
74
- const dragFrom = ref<string | null>(null)
75
- const dropOver = ref<string | null>(null)
76
-
77
- const taskById = (id: string): TaskView | undefined => mergedTasks.value.find(t => t.id === id)
78
-
79
- /** 该卡是否允许移入该列(动作存在且目标态 ≠ 当前态) */
80
- const canDrop = (taskId: string, col: Column): boolean => {
81
- if (!col.moveAction) return false
82
- const t = taskById(taskId)
83
- if (!t) return false
84
- if (col.states.includes(t.state)) return false
85
- if (col.moveAction === 'retry') return ['FAILED', 'CANCELED'].includes(t.state)
86
- return ['SUBMITTED', 'ASSIGNED', 'WORKING', 'WAITING', 'FAILED'].includes(t.state)
87
- }
88
-
89
- const onDragStart = (ev: DragEvent, t: TaskView, colKey: string) => {
90
- dragId.value = t.id
91
- dragFrom.value = colKey
92
- if (ev.dataTransfer) {
93
- ev.dataTransfer.effectAllowed = 'move'
94
- ev.dataTransfer.setData('text/plain', t.id)
95
- }
96
- }
97
- const onDragOver = (ev: DragEvent, col: Column) => {
98
- if (!dragId.value || !canDrop(dragId.value, col)) return
99
- ev.preventDefault()
100
- if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move'
101
- }
102
- const onDragLeave = (colKey: string) => {
103
- if (dropOver.value === colKey) dropOver.value = null
104
- }
105
- const onDragOverCol = (ev: DragEvent, col: Column) => {
106
- dropOver.value = col.key
107
- onDragOver(ev, col)
108
- }
109
- const onDragEnd = () => {
110
- dragId.value = null
111
- dragFrom.value = null
112
- dropOver.value = null
113
- }
114
- /** 列样式态:可放 = 蓝调高亮;不可放 = 压暗;拖出来源列 = 微降透明 */
115
- const colClasses = (col: Column): Record<string, boolean> => ({
116
- 'drop-over': dropOver.value === col.key && !!dragId.value && canDrop(dragId.value, col),
117
- 'no-drop': !!dragId.value && dropOver.value === col.key && !canDrop(dragId.value, col),
118
- 'drag-origin': dragFrom.value === col.key,
119
- })
120
- const onDrop = async (ev: DragEvent, col: Column) => {
121
- ev.preventDefault()
122
- const id = dragId.value
123
- dropOver.value = null
124
- dragId.value = null
125
- dragFrom.value = null
126
- if (!id || !canDrop(id, col)) return
127
- await applyMove(id, col.moveAction!)
128
- }
129
-
130
- // ===== 动作面(拖拽与胶囊菜单共用) =====
131
- const acting = ref(new Set<string>())
132
- const applyMove = async (taskId: string, action: 'cancel' | 'retry'): Promise<void> => {
133
- if (acting.value.has(taskId)) return
134
- const task = taskById(taskId)
135
- if (!task) return
136
- acting.value.add(taskId)
137
- const prev = task.state
138
- // 乐观预移:cancel → CANCELED;retry → ASSIGNED
139
- const next = action === 'cancel' ? 'CANCELED' : 'ASSIGNED'
140
- const opt = new Map(optimistic.value)
141
- opt.set(taskId, next)
142
- optimistic.value = opt
143
- try {
144
- await (action === 'cancel' ? api.cancelTask(taskId) : api.retryTask(taskId))
145
- message.success(action === 'cancel' ? t('taskBoardView.k189y4q013') : t('taskBoardView.kv1l1j3014'))
146
- }
147
- catch (e) {
148
- // 回滚乐观预移,弹错(WS 事件为准)
149
- const revert = new Map(optimistic.value)
150
- revert.set(taskId, prev)
151
- optimistic.value = revert
152
- const err = e as { data?: { message?: string }, message?: string }
153
- message.error(apiErrorMessage(err, t('taskBoardView.k1e7zwuc015')))
154
- }
155
- finally {
156
- acting.value.delete(taskId)
157
- // REST 完成后短暂清理乐观层,交给 WS/实体收敛
158
- setTimeout(() => {
159
- const clean = new Map(optimistic.value)
160
- clean.delete(taskId)
161
- optimistic.value = clean
162
- }, 4000)
163
- }
164
- }
165
-
166
- /** 状态胶囊菜单(动作 + 详情);fixed 定位并按视口收敛 */
167
- const menu = ref<{ task: TaskView, x: number, y: number } | null>(null)
168
- const openMenu = (ev: MouseEvent, task: TaskView) => {
169
- const x = Math.min(ev.clientX, Math.max(window.innerWidth - 156, 8))
170
- const y = Math.min(ev.clientY, Math.max(window.innerHeight - 160, 8))
171
- menu.value = { task, x, y }
172
- }
173
- const closeMenu = () => {
174
- menu.value = null
175
- }
176
- const menuActions = computed(() => {
177
- const task = menu.value?.task
178
- if (!task) return []
179
- const acts: Array<{ key: 'cancel' | 'retry' | 'detail', label: string, danger?: boolean }> = []
180
- if (['SUBMITTED', 'ASSIGNED', 'WORKING', 'WAITING', 'FAILED'].includes(task.state)) {
181
- acts.push({ key: 'cancel', label: t('taskBoardView.k1bs0t9b016'), danger: true })
182
- }
183
- if (['FAILED', 'CANCELED'].includes(task.state)) {
184
- acts.push({ key: 'retry', label: t('taskBoardView.k1lclwk6017') })
185
- }
186
- acts.push({ key: 'detail', label: t('taskBoardView.k1dx9ysj018') })
187
- return acts
188
- })
189
- const onMenuAction = async (key: 'cancel' | 'retry' | 'detail') => {
190
- const t = menu.value?.task
191
- closeMenu()
192
- if (!t) return
193
- if (key === 'detail') {
194
- emit('openTask', t.id)
195
- }
196
- else {
197
- await applyMove(t.id, key)
198
- }
199
- }
200
- </script>
201
-
202
- <template>
203
- <div class="board-shell">
204
- <!-- 工具条:视图切换 + 计数 -->
205
- <div class="toolbar">
206
- <div class="aw-seg">
207
- <button
208
- type="button"
209
- :class="{ on: layout === 'board' }"
210
- @click="setLayout('board')"
211
- >
212
- {{ $t('taskBoardView.k43pyn001') }}
213
- </button>
214
- <button
215
- type="button"
216
- :class="{ on: layout === 'list' }"
217
- @click="setLayout('list')"
218
- >
219
- {{ $t('taskBoardView.k3x7l0002') }}
220
- </button>
221
- </div>
222
- <span class="count">{{ mergedTasks.length }} {{ $t('taskBoardView.k168w1ze004') }}</span>
223
- </div>
224
-
225
- <!-- 空态(快照未到 → 同步中,不误判为空) -->
226
- <div
227
- v-if="mergedTasks.length === 0"
228
- class="pane-empty"
229
- >
230
- <span :class="synced ? 'pe-icon i-tabler-list-check' : 'pe-icon i-tabler-refresh'" />
231
- <div class="pe-title">
232
- {{ synced ? $t('taskBoardView.k3vkhhj006') : $t('taskBoardView.k1f9c17l019') }} <span class="aw-serif-accent-italic">{{ $t('taskBoardView.k3wcox003') }}</span>
233
- </div>
234
- <div class="pe-sub">
235
- {{ synced ? '在下方 Composer 提交首个任务(首行标题,支持 goal / loop / pipeline 模式)' : '任务快照对齐后自动呈现' }}
236
- </div>
237
- </div>
238
-
239
- <!-- 看板:全高等宽泳道 -->
240
- <div
241
- v-else-if="layout === 'board'"
242
- class="board"
243
- >
244
- <div
245
- v-for="col in columns"
246
- :key="col.key"
247
- class="task-col"
248
- :class="colClasses(col)"
249
- @dragover="onDragOverCol($event, col)"
250
- @dragleave="onDragLeave(col.key)"
251
- @drop="onDrop($event, col)"
252
- >
253
- <div class="sec">
254
- <span
255
- class="st-dot"
256
- :style="{ background: col.dot }"
257
- />
258
- <span class="sec-title">{{ col.title }}</span>
259
- <span class="sec-cnt">{{ col.items.length }}</span>
260
- </div>
261
- <div class="task-col-body">
262
- <div
263
- v-for="task in col.items"
264
- :key="task.id"
265
- class="card task"
266
- :class="{ dragging: dragId === task.id }"
267
- draggable="true"
268
- @dragstart="onDragStart($event, task, col.key)"
269
- @dragend="onDragEnd"
270
- @click="emit('openTask', task.id)"
271
- >
272
- <div class="tk-chan">
273
- <span>#{{ agentName(task.assigneeId) }}</span>
274
- <span
275
- v-if="childCount(task.id)"
276
- class="tk-num"
277
- >{{ $t('taskBoardView.k4b1x005') }} {{ childCount(task.id) }}</span>
278
- <span
279
- v-if="task.artifacts"
280
- class="tk-num"
281
- ><span class="i-tabler-package" /> {{ task.artifacts }}</span>
282
- </div>
283
- <div class="tk-title">
284
- {{ task.title }}
285
- </div>
286
- <div
287
- v-if="task.routeReason"
288
- class="tk-route"
289
- :title="$t('taskBoardView.k8m7hm6020', { p0: task.routeReason })"
290
- >
291
- ↳ {{ task.routeReason }}
292
- </div>
293
- <a-progress
294
- v-if="task.state === 'WORKING' && task.progress > 0"
295
- :percent="task.progress"
296
- size="small"
297
- :show-info="false"
298
- />
299
- <div class="tk-foot">
300
- <button
301
- type="button"
302
- class="st-pill-btn"
303
- @click.stop="openMenu($event, task)"
304
- >
305
- <span
306
- class="st-pill"
307
- :style="{ color: col.dot, borderColor: col.dot }"
308
- >{{ col.title }}</span>
309
- </button>
310
- </div>
311
- </div>
312
- <div
313
- v-if="col.items.length === 0"
314
- class="col-empty"
315
- >
316
- {{ col.moveAction ? $t('taskBoardView.k13pad50007') : '-' }}
317
- </div>
318
- </div>
319
- </div>
320
- </div>
321
-
322
- <!-- 列表:按状态分组 -->
323
- <div
324
- v-else
325
- class="task-list"
326
- >
327
- <template
328
- v-for="col in columns"
329
- :key="col.key"
330
- >
331
- <div
332
- v-if="col.items.length"
333
- class="list-sec sec"
334
- >
335
- <span
336
- class="st-dot"
337
- :style="{ background: col.dot }"
338
- />
339
- <span class="sec-title">{{ col.title }}</span>
340
- <span class="sec-cnt">{{ col.items.length }}</span>
341
- </div>
342
- <div
343
- v-for="task in col.items"
344
- :key="task.id"
345
- class="list-row"
346
- @click="emit('openTask', task.id)"
347
- >
348
- <span class="grow">{{ task.title }}</span>
349
- <span class="lnum">#{{ agentName(task.assigneeId) }}</span>
350
- <span class="meta">{{ task.id.slice(0, 8) }}</span>
351
- <span
352
- class="st-pill"
353
- :style="{ color: col.dot, borderColor: col.dot }"
354
- >
355
- {{ stateOf(task) }}
356
- </span>
357
- </div>
358
- </template>
359
- </div>
360
-
361
- <!-- 状态菜单(fixed 定位,随点击点) -->
362
- <template v-if="menu">
363
- <div
364
- class="menu-backdrop"
365
- @click="closeMenu"
366
- @scroll="closeMenu"
367
- />
368
- <div
369
- class="st-menu"
370
- :style="{ left: `${menu.x}px`, top: `${menu.y}px` }"
371
- >
372
- <button
373
- v-for="a in menuActions"
374
- :key="a.key"
375
- type="button"
376
- :class="{ danger: a.danger }"
377
- @click="onMenuAction(a.key)"
378
- >
379
- <span
380
- :class="a.key === 'cancel'
381
- ? 'i-tabler-circle-off'
382
- : a.key === 'retry' ? 'i-tabler-rotate' : 'i-tabler-external-link'"
383
- />
384
- {{ a.label }}
385
- </button>
386
- </div>
387
- </template>
388
- </div>
389
- </template>
390
-
391
- <style scoped>
392
- .board-shell {
393
- display: flex;
394
- flex-direction: column;
395
- flex: 1 1 auto;
396
- min-height: 0;
397
- animation: view-in 0.2s cubic-bezier(0.22, 1, 0.36, 1);
398
- }
399
- @keyframes view-in {
400
- from { opacity: 0; transform: translateY(4px); }
401
- to { opacity: 1; transform: none; }
402
- }
403
- @media (prefers-reduced-motion: reduce) {
404
- .board-shell { animation: none; }
405
- }
406
-
407
- .toolbar {
408
- display: flex;
409
- flex: 0 0 auto;
410
- gap: 12px;
411
- align-items: center;
412
- padding: 10px 16px;
413
- border-bottom: 1px solid var(--line);
414
- }
415
- .count {
416
- overflow: hidden;
417
- font-size: 11.5px;
418
- color: var(--ink-faint);
419
- text-overflow: ellipsis;
420
- white-space: nowrap;
421
- }
422
-
423
- /* 看板:全高等宽泳道(surface-strong 软底,白卡浮于其上) */
424
- .board {
425
- overscroll-behavior: contain;
426
- display: flex;
427
- flex: 1 1 auto;
428
- gap: 12px;
429
- align-items: stretch;
430
- min-height: 0;
431
- padding: 12px 14px 14px;
432
- overflow-x: auto;
433
- }
434
- .task-col {
435
- display: flex;
436
- flex: 0 0 300px;
437
- flex-direction: column;
438
- min-width: 280px;
439
- min-height: 0;
440
- padding: 2px 8px 8px;
441
- background: var(--paper-deep);
442
- border-radius: var(--radius-panel);
443
- }
444
- .task-col.drop-over {
445
- background: var(--tone-info-bg);
446
- outline: 2px dashed color-mix(in srgb, var(--tone-info-dot) 65%, transparent);
447
- outline-offset: -2px;
448
- }
449
- .task-col.no-drop { opacity: 0.6; }
450
- .task-col.drag-origin { opacity: 0.85; }
451
- .sec {
452
- display: flex;
453
- gap: 7px;
454
- align-items: center;
455
- padding: 10px 6px 8px;
456
- }
457
- .sec-title {
458
- font-size: 12px;
459
- font-weight: 600;
460
- letter-spacing: 0.06em;
461
- color: var(--ink-soft);
462
- }
463
- .sec-cnt {
464
- font-size: 11px;
465
- font-variant-numeric: tabular-nums;
466
- color: var(--ink-faint);
467
- }
468
- .st-dot {
469
- flex: none;
470
- width: 8px;
471
- height: 8px;
472
- border-radius: 50%;
473
- }
474
- .task-col-body {
475
- overscroll-behavior: contain;
476
- flex: 1 1 auto;
477
- min-height: 0;
478
- padding: 0 2px;
479
- overflow-y: auto;
480
- }
481
-
482
- /* 任务卡:白卡 + 悬停微抬(open-tag card.task) */
483
- .card.task {
484
- position: relative;
485
- padding: 10px 12px;
486
- margin-bottom: 8px;
487
- cursor: pointer;
488
- background: var(--paper-raised);
489
- border: 1px solid var(--line);
490
- border-radius: var(--radius-panel);
491
- transition: transform 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
492
- }
493
- .card.task:hover {
494
- border-color: var(--line-strong);
495
- transform: translateY(-1px);
496
- }
497
- .card.task.dragging {
498
- opacity: 0.45;
499
- cursor: grabbing;
500
- }
501
- .tk-chan {
502
- display: flex;
503
- gap: 8px;
504
- font-size: 11.5px;
505
- color: var(--ink-faint);
506
- }
507
- .tk-num {
508
- font-variant-numeric: tabular-nums;
509
- color: var(--ink-faint);
510
- }
511
- .tk-title {
512
- display: -webkit-box;
513
- overflow: hidden;
514
- margin: 3px 0 6px;
515
- font-size: 13px;
516
- font-weight: 600;
517
- line-height: 1.35;
518
- color: var(--ink);
519
- -webkit-box-orient: vertical;
520
- -webkit-line-clamp: 3;
521
- }
522
- .tk-route {
523
- overflow: hidden;
524
- margin-bottom: 4px;
525
- font-size: 11px;
526
- color: var(--ink-faint);
527
- text-overflow: ellipsis;
528
- white-space: nowrap;
529
- }
530
- .tk-foot {
531
- display: flex;
532
- justify-content: flex-end;
533
- }
534
- .col-empty {
535
- padding: 14px 8px;
536
- font-size: 11.5px;
537
- color: var(--ink-faint);
538
- text-align: center;
539
- border: 1.5px dashed var(--line-strong);
540
- border-radius: var(--radius-panel);
541
- }
542
-
543
- /* 状态胶囊:hairline pill,色调随列 */
544
- .st-pill-btn {
545
- padding: 0;
546
- cursor: pointer;
547
- background: transparent;
548
- border: 0;
549
- }
550
- .st-pill {
551
- display: inline-flex;
552
- gap: 4px;
553
- align-items: center;
554
- padding: 1px 9px;
555
- font-size: 11px;
556
- font-weight: 600;
557
- border: 1px solid var(--line-strong);
558
- border-radius: var(--radius-pill);
559
- }
560
- .st-pill-btn:hover .st-pill { background: var(--hover-tint); }
561
-
562
- /* 列表视图 */
563
- .task-list {
564
- overscroll-behavior: contain;
565
- flex: 1 1 auto;
566
- min-height: 0;
567
- padding: 10px 16px 18px;
568
- overflow-y: auto;
569
- }
570
- .list-sec { padding: 8px 2px 6px; }
571
- .list-row {
572
- display: flex;
573
- gap: 12px;
574
- align-items: center;
575
- padding: 9px 12px;
576
- margin-bottom: 4px;
577
- cursor: pointer;
578
- background: var(--paper-raised);
579
- border: 1px solid var(--line);
580
- border-radius: var(--radius-panel);
581
- transition: border-color 0.12s ease;
582
- }
583
- .list-row:hover { border-color: var(--line-strong); }
584
- .list-row .grow {
585
- flex: 1;
586
- min-width: 0;
587
- overflow: hidden;
588
- font-size: 13.5px;
589
- color: var(--ink);
590
- text-overflow: ellipsis;
591
- white-space: nowrap;
592
- }
593
- .lnum {
594
- font-size: 12px;
595
- color: var(--ink-faint);
596
- }
597
- .meta {
598
- font-family: var(--font-mono);
599
- font-size: 11px;
600
- color: var(--ink-faint);
601
- }
602
-
603
- /* 状态菜单 */
604
- .menu-backdrop {
605
- position: fixed;
606
- inset: 0;
607
- z-index: 60;
608
- }
609
- .st-menu {
610
- position: fixed;
611
- z-index: 61;
612
- min-width: 128px;
613
- padding: 4px;
614
- background: var(--paper-raised);
615
- border: 1px solid var(--line-strong);
616
- border-radius: var(--radius-panel-sm);
617
- box-shadow: var(--shadow-float);
618
- }
619
- .st-menu button {
620
- display: flex;
621
- gap: 7px;
622
- align-items: center;
623
- width: 100%;
624
- padding: 6px 9px;
625
- font-family: var(--font-body);
626
- font-size: 12.5px;
627
- color: var(--ink-soft);
628
- text-align: left;
629
- cursor: pointer;
630
- background: transparent;
631
- border: 0;
632
- border-radius: var(--radius-chip);
633
- }
634
- .st-menu button:hover { background: var(--paper-deep); color: var(--ink); }
635
- .st-menu button.danger { color: var(--tone-danger-dot); }
636
-
637
- /* ── 窄屏(≤1023):看板列从"全高等宽四列"改为"一次一列"的横向卷轴 ──
638
- 300px 定宽列在 390px 下只能露出半列 + 半列,状态语义读不出来;
639
- 改为 86% 宽 + scroll-snap:一屏一个状态列。列表视图行允许折行,不竖排。 */
640
- @media (max-width: 1023.98px) {
641
- .toolbar {
642
- flex-wrap: wrap;
643
- gap: 8px;
644
- padding: 10px;
645
- }
646
-
647
- .count,
648
- .sec-cnt,
649
- .tk-route,
650
- .meta,
651
- .st-pill {
652
- font-size: 11.5px;
653
- }
654
-
655
- .board {
656
- padding: 10px;
657
- scroll-snap-type: x mandatory;
658
- -webkit-overflow-scrolling: touch;
659
- }
660
-
661
- .task-col {
662
- flex: 0 0 86%;
663
- min-width: 0;
664
- scroll-snap-align: center;
665
- }
666
-
667
- /* 行内四项(标题/成员/ID/状态)在 370px 下必然挤:允许折行,不允许压成竖排 */
668
- .list-row {
669
- flex-wrap: wrap;
670
- row-gap: 4px;
671
- min-height: 44px;
672
- }
673
-
674
- .list-row .grow {
675
- flex: 1 1 60%;
676
- }
677
-
678
- .st-pill-btn {
679
- min-height: 40px;
680
- }
681
-
682
- .st-menu button {
683
- min-height: 44px;
684
- }
685
- }
686
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * 任务板(open-tag TaskBoard 交互移植,按 AgentWorkShop 权限模型收敛):
4
+ * - 看板/列表双视图(localStorage 持久化);看板 = 全高等宽泳道(surface-strong),
5
+ * 列表 = 按状态分组的行式列表;
6
+ * - 卡片拖拽:拖到「异常/取消」列 = cancelTask(HITL 中断);FAILED/CANCELED 卡拖回
7
+ * 「待启动」列 = retryTask(重试派发);执行中/等待汇总/已完成由系统流转,不可手动移入;
8
+ * - 状态胶囊点击菜单:取消 / 重试 / 打开详情(与拖拽同一动作面);
9
+ * - 乐观更新:本地先改 state(FLIP 视觉即时),REST 失败回滚并以 WS 事件对齐。
10
+ */
11
+ import { message } from 'ant-design-vue'
12
+ import { useEntitiesStore, type TaskView } from '@/app/stores/workshop/entities'
13
+ import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
14
+
15
+ const { t } = useI18n()
16
+
17
+ const props = defineProps<{ channelId: string }>()
18
+ const emit = defineEmits<{ (e: 'openTask', taskId: string): void }>()
19
+
20
+ const entities = useEntitiesStore()
21
+ const api = useWorkshopApi()
22
+ const tasks = computed(() => entities.tasks[props.channelId] ?? [])
23
+ /** 实体快照是否已到达(未到时空态显示同步中,不误判"没有任务") */
24
+ const synced = computed(() => entities.channels[props.channelId] !== undefined
25
+ || entities.tasks[props.channelId] !== undefined)
26
+
27
+ /** 乐观覆盖:taskId → 预期 state(REST 进行中;WS 确认后由实体覆盖) */
28
+ const optimistic = ref(new Map<string, string>())
29
+ const mergedTasks = computed<TaskView[]>(() =>
30
+ tasks.value.map(t => (optimistic.value.has(t.id) ? { ...t, state: optimistic.value.get(t.id)! } : t)),
31
+ )
32
+
33
+ interface Column {
34
+ key: string
35
+ title: string
36
+ states: string[]
37
+ dot: string
38
+ /** 允许用户手动移入该列的动作(null = 系统流转列) */
39
+ moveAction: 'cancel' | 'retry' | null
40
+ items: TaskView[]
41
+ }
42
+
43
+ const BASE_COLUMNS: Array<Omit<Column, 'items'>> = [
44
+ { key: 'todo', title: t('taskBoardView.k3nf71d008'), states: ['SUBMITTED', 'ASSIGNED'], dot: 'var(--tone-neutral-dot)', moveAction: 'retry' },
45
+ { key: 'doing', title: t('taskBoardView.k3o5tz9009'), states: ['WORKING'], dot: 'var(--tone-info-dot)', moveAction: null },
46
+ { key: 'waiting', title: t('taskBoardView.k1hpx9p1010'), states: ['WAITING'], dot: 'var(--tone-warning-dot)', moveAction: null },
47
+ { key: 'done', title: t('taskBoardView.k3n77g3011'), states: ['COMPLETED'], dot: 'var(--tone-success-dot)', moveAction: null },
48
+ { key: 'bad', title: t('taskBoardView.k1veotws012'), states: ['FAILED', 'CANCELED'], dot: 'var(--tone-danger-dot)', moveAction: 'cancel' },
49
+ ]
50
+
51
+ const columns = computed<Column[]>(() => BASE_COLUMNS.map(col => ({
52
+ ...col,
53
+ items: mergedTasks.value.filter(t => col.states.includes(t.state)),
54
+ })))
55
+
56
+ const agentName = (id: string): string =>
57
+ entities.agentById(props.channelId, id)?.name ?? id.slice(0, 6)
58
+ const childCount = (id: string): number =>
59
+ mergedTasks.value.filter(t => t.parentId === id).length
60
+
61
+ const stateOf = (t: TaskView): string => t.state
62
+
63
+ // ===== 视图切换(看板/列表,持久化) =====
64
+ const layout = ref<'board' | 'list'>(
65
+ (import.meta.client && localStorage.getItem('aw.tasks.boardLayout') === 'list') ? 'list' : 'board',
66
+ )
67
+ const setLayout = (l: 'board' | 'list') => {
68
+ layout.value = l
69
+ if (import.meta.client) localStorage.setItem('aw.tasks.boardLayout', l)
70
+ }
71
+
72
+ // ===== 拖拽(HTML5 DnD;整列作为放置目标) =====
73
+ const dragId = ref<string | null>(null)
74
+ const dragFrom = ref<string | null>(null)
75
+ const dropOver = ref<string | null>(null)
76
+
77
+ const taskById = (id: string): TaskView | undefined => mergedTasks.value.find(t => t.id === id)
78
+
79
+ /** 该卡是否允许移入该列(动作存在且目标态 ≠ 当前态) */
80
+ const canDrop = (taskId: string, col: Column): boolean => {
81
+ if (!col.moveAction) return false
82
+ const t = taskById(taskId)
83
+ if (!t) return false
84
+ if (col.states.includes(t.state)) return false
85
+ if (col.moveAction === 'retry') return ['FAILED', 'CANCELED'].includes(t.state)
86
+ return ['SUBMITTED', 'ASSIGNED', 'WORKING', 'WAITING', 'FAILED'].includes(t.state)
87
+ }
88
+
89
+ const onDragStart = (ev: DragEvent, t: TaskView, colKey: string) => {
90
+ dragId.value = t.id
91
+ dragFrom.value = colKey
92
+ if (ev.dataTransfer) {
93
+ ev.dataTransfer.effectAllowed = 'move'
94
+ ev.dataTransfer.setData('text/plain', t.id)
95
+ }
96
+ }
97
+ const onDragOver = (ev: DragEvent, col: Column) => {
98
+ if (!dragId.value || !canDrop(dragId.value, col)) return
99
+ ev.preventDefault()
100
+ if (ev.dataTransfer) ev.dataTransfer.dropEffect = 'move'
101
+ }
102
+ const onDragLeave = (colKey: string) => {
103
+ if (dropOver.value === colKey) dropOver.value = null
104
+ }
105
+ const onDragOverCol = (ev: DragEvent, col: Column) => {
106
+ dropOver.value = col.key
107
+ onDragOver(ev, col)
108
+ }
109
+ const onDragEnd = () => {
110
+ dragId.value = null
111
+ dragFrom.value = null
112
+ dropOver.value = null
113
+ }
114
+ /** 列样式态:可放 = 蓝调高亮;不可放 = 压暗;拖出来源列 = 微降透明 */
115
+ const colClasses = (col: Column): Record<string, boolean> => ({
116
+ 'drop-over': dropOver.value === col.key && !!dragId.value && canDrop(dragId.value, col),
117
+ 'no-drop': !!dragId.value && dropOver.value === col.key && !canDrop(dragId.value, col),
118
+ 'drag-origin': dragFrom.value === col.key,
119
+ })
120
+ const onDrop = async (ev: DragEvent, col: Column) => {
121
+ ev.preventDefault()
122
+ const id = dragId.value
123
+ dropOver.value = null
124
+ dragId.value = null
125
+ dragFrom.value = null
126
+ if (!id || !canDrop(id, col)) return
127
+ await applyMove(id, col.moveAction!)
128
+ }
129
+
130
+ // ===== 动作面(拖拽与胶囊菜单共用) =====
131
+ const acting = ref(new Set<string>())
132
+ const applyMove = async (taskId: string, action: 'cancel' | 'retry'): Promise<void> => {
133
+ if (acting.value.has(taskId)) return
134
+ const task = taskById(taskId)
135
+ if (!task) return
136
+ acting.value.add(taskId)
137
+ const prev = task.state
138
+ // 乐观预移:cancel → CANCELED;retry → ASSIGNED
139
+ const next = action === 'cancel' ? 'CANCELED' : 'ASSIGNED'
140
+ const opt = new Map(optimistic.value)
141
+ opt.set(taskId, next)
142
+ optimistic.value = opt
143
+ try {
144
+ await (action === 'cancel' ? api.cancelTask(taskId) : api.retryTask(taskId))
145
+ message.success(action === 'cancel' ? t('taskBoardView.k189y4q013') : t('taskBoardView.kv1l1j3014'))
146
+ }
147
+ catch (e) {
148
+ // 回滚乐观预移,弹错(WS 事件为准)
149
+ const revert = new Map(optimistic.value)
150
+ revert.set(taskId, prev)
151
+ optimistic.value = revert
152
+ const err = e as { data?: { message?: string }, message?: string }
153
+ message.error(apiErrorMessage(err, t('taskBoardView.k1e7zwuc015')))
154
+ }
155
+ finally {
156
+ acting.value.delete(taskId)
157
+ // REST 完成后短暂清理乐观层,交给 WS/实体收敛
158
+ setTimeout(() => {
159
+ const clean = new Map(optimistic.value)
160
+ clean.delete(taskId)
161
+ optimistic.value = clean
162
+ }, 4000)
163
+ }
164
+ }
165
+
166
+ /** 状态胶囊菜单(动作 + 详情);fixed 定位并按视口收敛 */
167
+ const menu = ref<{ task: TaskView, x: number, y: number } | null>(null)
168
+ const openMenu = (ev: MouseEvent, task: TaskView) => {
169
+ const x = Math.min(ev.clientX, Math.max(window.innerWidth - 156, 8))
170
+ const y = Math.min(ev.clientY, Math.max(window.innerHeight - 160, 8))
171
+ menu.value = { task, x, y }
172
+ }
173
+ const closeMenu = () => {
174
+ menu.value = null
175
+ }
176
+ const menuActions = computed(() => {
177
+ const task = menu.value?.task
178
+ if (!task) return []
179
+ const acts: Array<{ key: 'cancel' | 'retry' | 'detail', label: string, danger?: boolean }> = []
180
+ if (['SUBMITTED', 'ASSIGNED', 'WORKING', 'WAITING', 'FAILED'].includes(task.state)) {
181
+ acts.push({ key: 'cancel', label: t('taskBoardView.k1bs0t9b016'), danger: true })
182
+ }
183
+ if (['FAILED', 'CANCELED'].includes(task.state)) {
184
+ acts.push({ key: 'retry', label: t('taskBoardView.k1lclwk6017') })
185
+ }
186
+ acts.push({ key: 'detail', label: t('taskBoardView.k1dx9ysj018') })
187
+ return acts
188
+ })
189
+ const onMenuAction = async (key: 'cancel' | 'retry' | 'detail') => {
190
+ const t = menu.value?.task
191
+ closeMenu()
192
+ if (!t) return
193
+ if (key === 'detail') {
194
+ emit('openTask', t.id)
195
+ }
196
+ else {
197
+ await applyMove(t.id, key)
198
+ }
199
+ }
200
+ </script>
201
+
202
+ <template>
203
+ <div class="board-shell">
204
+ <!-- 工具条:视图切换 + 计数 -->
205
+ <div class="toolbar">
206
+ <div class="aw-seg">
207
+ <button
208
+ type="button"
209
+ :class="{ on: layout === 'board' }"
210
+ @click="setLayout('board')"
211
+ >
212
+ {{ $t('taskBoardView.k43pyn001') }}
213
+ </button>
214
+ <button
215
+ type="button"
216
+ :class="{ on: layout === 'list' }"
217
+ @click="setLayout('list')"
218
+ >
219
+ {{ $t('taskBoardView.k3x7l0002') }}
220
+ </button>
221
+ </div>
222
+ <span class="count">{{ mergedTasks.length }} {{ $t('taskBoardView.k168w1ze004') }}</span>
223
+ </div>
224
+
225
+ <!-- 空态(快照未到 → 同步中,不误判为空) -->
226
+ <div
227
+ v-if="mergedTasks.length === 0"
228
+ class="pane-empty"
229
+ >
230
+ <span :class="synced ? 'pe-icon i-tabler-list-check' : 'pe-icon i-tabler-refresh'" />
231
+ <div class="pe-title">
232
+ {{ synced ? $t('taskBoardView.k3vkhhj006') : $t('taskBoardView.k1f9c17l019') }} <span class="aw-serif-accent-italic">{{ $t('taskBoardView.k3wcox003') }}</span>
233
+ </div>
234
+ <div class="pe-sub">
235
+ {{ synced ? $t('taskBoardView.emptySubNew') : $t('taskBoardView.emptySubSync') }}
236
+ </div>
237
+ </div>
238
+
239
+ <!-- 看板:全高等宽泳道 -->
240
+ <div
241
+ v-else-if="layout === 'board'"
242
+ class="board"
243
+ >
244
+ <div
245
+ v-for="col in columns"
246
+ :key="col.key"
247
+ class="task-col"
248
+ :class="colClasses(col)"
249
+ @dragover="onDragOverCol($event, col)"
250
+ @dragleave="onDragLeave(col.key)"
251
+ @drop="onDrop($event, col)"
252
+ >
253
+ <div class="sec">
254
+ <span
255
+ class="st-dot"
256
+ :style="{ background: col.dot }"
257
+ />
258
+ <span class="sec-title">{{ col.title }}</span>
259
+ <span class="sec-cnt">{{ col.items.length }}</span>
260
+ </div>
261
+ <div class="task-col-body">
262
+ <div
263
+ v-for="task in col.items"
264
+ :key="task.id"
265
+ class="card task"
266
+ :class="{ dragging: dragId === task.id }"
267
+ draggable="true"
268
+ @dragstart="onDragStart($event, task, col.key)"
269
+ @dragend="onDragEnd"
270
+ @click="emit('openTask', task.id)"
271
+ >
272
+ <div class="tk-chan">
273
+ <span>#{{ agentName(task.assigneeId) }}</span>
274
+ <span
275
+ v-if="childCount(task.id)"
276
+ class="tk-num"
277
+ >{{ $t('taskBoardView.k4b1x005') }} {{ childCount(task.id) }}</span>
278
+ <span
279
+ v-if="task.artifacts"
280
+ class="tk-num"
281
+ ><span class="i-tabler-package" /> {{ task.artifacts }}</span>
282
+ </div>
283
+ <div class="tk-title">
284
+ {{ task.title }}
285
+ </div>
286
+ <div
287
+ v-if="task.routeReason"
288
+ class="tk-route"
289
+ :title="$t('taskBoardView.k8m7hm6020', { p0: task.routeReason })"
290
+ >
291
+ ↳ {{ task.routeReason }}
292
+ </div>
293
+ <a-progress
294
+ v-if="task.state === 'WORKING' && task.progress > 0"
295
+ :percent="task.progress"
296
+ size="small"
297
+ :show-info="false"
298
+ />
299
+ <div class="tk-foot">
300
+ <button
301
+ type="button"
302
+ class="st-pill-btn"
303
+ @click.stop="openMenu($event, task)"
304
+ >
305
+ <span
306
+ class="st-pill"
307
+ :style="{ color: col.dot, borderColor: col.dot }"
308
+ >{{ col.title }}</span>
309
+ </button>
310
+ </div>
311
+ </div>
312
+ <div
313
+ v-if="col.items.length === 0"
314
+ class="col-empty"
315
+ >
316
+ {{ col.moveAction ? $t('taskBoardView.k13pad50007') : '-' }}
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <!-- 列表:按状态分组 -->
323
+ <div
324
+ v-else
325
+ class="task-list"
326
+ >
327
+ <template
328
+ v-for="col in columns"
329
+ :key="col.key"
330
+ >
331
+ <div
332
+ v-if="col.items.length"
333
+ class="list-sec sec"
334
+ >
335
+ <span
336
+ class="st-dot"
337
+ :style="{ background: col.dot }"
338
+ />
339
+ <span class="sec-title">{{ col.title }}</span>
340
+ <span class="sec-cnt">{{ col.items.length }}</span>
341
+ </div>
342
+ <div
343
+ v-for="task in col.items"
344
+ :key="task.id"
345
+ class="list-row"
346
+ @click="emit('openTask', task.id)"
347
+ >
348
+ <span class="grow">{{ task.title }}</span>
349
+ <span class="lnum">#{{ agentName(task.assigneeId) }}</span>
350
+ <span class="meta">{{ task.id.slice(0, 8) }}</span>
351
+ <span
352
+ class="st-pill"
353
+ :style="{ color: col.dot, borderColor: col.dot }"
354
+ >
355
+ {{ stateOf(task) }}
356
+ </span>
357
+ </div>
358
+ </template>
359
+ </div>
360
+
361
+ <!-- 状态菜单(fixed 定位,随点击点) -->
362
+ <template v-if="menu">
363
+ <div
364
+ class="menu-backdrop"
365
+ @click="closeMenu"
366
+ @scroll="closeMenu"
367
+ />
368
+ <div
369
+ class="st-menu"
370
+ :style="{ left: `${menu.x}px`, top: `${menu.y}px` }"
371
+ >
372
+ <button
373
+ v-for="a in menuActions"
374
+ :key="a.key"
375
+ type="button"
376
+ :class="{ danger: a.danger }"
377
+ @click="onMenuAction(a.key)"
378
+ >
379
+ <span
380
+ :class="a.key === 'cancel'
381
+ ? 'i-tabler-circle-off'
382
+ : a.key === 'retry' ? 'i-tabler-rotate' : 'i-tabler-external-link'"
383
+ />
384
+ {{ a.label }}
385
+ </button>
386
+ </div>
387
+ </template>
388
+ </div>
389
+ </template>
390
+
391
+ <style scoped>
392
+ .board-shell {
393
+ display: flex;
394
+ flex-direction: column;
395
+ flex: 1 1 auto;
396
+ min-height: 0;
397
+ animation: view-in 0.2s cubic-bezier(0.22, 1, 0.36, 1);
398
+ }
399
+ @keyframes view-in {
400
+ from { opacity: 0; transform: translateY(4px); }
401
+ to { opacity: 1; transform: none; }
402
+ }
403
+ @media (prefers-reduced-motion: reduce) {
404
+ .board-shell { animation: none; }
405
+ }
406
+
407
+ .toolbar {
408
+ display: flex;
409
+ flex: 0 0 auto;
410
+ gap: 12px;
411
+ align-items: center;
412
+ padding: 10px 16px;
413
+ border-bottom: 1px solid var(--line);
414
+ }
415
+ .count {
416
+ overflow: hidden;
417
+ font-size: 11.5px;
418
+ color: var(--ink-faint);
419
+ text-overflow: ellipsis;
420
+ white-space: nowrap;
421
+ }
422
+
423
+ /* 看板:全高等宽泳道(surface-strong 软底,白卡浮于其上) */
424
+ .board {
425
+ overscroll-behavior: contain;
426
+ display: flex;
427
+ flex: 1 1 auto;
428
+ gap: 12px;
429
+ align-items: stretch;
430
+ min-height: 0;
431
+ padding: 12px 14px 14px;
432
+ overflow-x: auto;
433
+ }
434
+ .task-col {
435
+ display: flex;
436
+ flex: 0 0 300px;
437
+ flex-direction: column;
438
+ min-width: 280px;
439
+ min-height: 0;
440
+ padding: 2px 8px 8px;
441
+ background: var(--paper-deep);
442
+ border-radius: var(--radius-panel);
443
+ }
444
+ .task-col.drop-over {
445
+ background: var(--tone-info-bg);
446
+ outline: 2px dashed color-mix(in srgb, var(--tone-info-dot) 65%, transparent);
447
+ outline-offset: -2px;
448
+ }
449
+ .task-col.no-drop { opacity: 0.6; }
450
+ .task-col.drag-origin { opacity: 0.85; }
451
+ .sec {
452
+ display: flex;
453
+ gap: 7px;
454
+ align-items: center;
455
+ padding: 10px 6px 8px;
456
+ }
457
+ .sec-title {
458
+ font-size: 12px;
459
+ font-weight: 600;
460
+ letter-spacing: 0.06em;
461
+ color: var(--ink-soft);
462
+ }
463
+ .sec-cnt {
464
+ font-size: 11px;
465
+ font-variant-numeric: tabular-nums;
466
+ color: var(--ink-faint);
467
+ }
468
+ .st-dot {
469
+ flex: none;
470
+ width: 8px;
471
+ height: 8px;
472
+ border-radius: 50%;
473
+ }
474
+ .task-col-body {
475
+ overscroll-behavior: contain;
476
+ flex: 1 1 auto;
477
+ min-height: 0;
478
+ padding: 0 2px;
479
+ overflow-y: auto;
480
+ }
481
+
482
+ /* 任务卡:白卡 + 悬停微抬(open-tag card.task) */
483
+ .card.task {
484
+ position: relative;
485
+ padding: 10px 12px;
486
+ margin-bottom: 8px;
487
+ cursor: pointer;
488
+ background: var(--paper-raised);
489
+ border: 1px solid var(--line);
490
+ border-radius: var(--radius-panel);
491
+ transition: transform 0.12s ease, border-color 0.12s ease, opacity 0.12s ease;
492
+ }
493
+ .card.task:hover {
494
+ border-color: var(--line-strong);
495
+ transform: translateY(-1px);
496
+ }
497
+ .card.task.dragging {
498
+ opacity: 0.45;
499
+ cursor: grabbing;
500
+ }
501
+ .tk-chan {
502
+ display: flex;
503
+ gap: 8px;
504
+ font-size: 11.5px;
505
+ color: var(--ink-faint);
506
+ }
507
+ .tk-num {
508
+ font-variant-numeric: tabular-nums;
509
+ color: var(--ink-faint);
510
+ }
511
+ .tk-title {
512
+ display: -webkit-box;
513
+ overflow: hidden;
514
+ margin: 3px 0 6px;
515
+ font-size: 13px;
516
+ font-weight: 600;
517
+ line-height: 1.35;
518
+ color: var(--ink);
519
+ -webkit-box-orient: vertical;
520
+ -webkit-line-clamp: 3;
521
+ }
522
+ .tk-route {
523
+ overflow: hidden;
524
+ margin-bottom: 4px;
525
+ font-size: 11px;
526
+ color: var(--ink-faint);
527
+ text-overflow: ellipsis;
528
+ white-space: nowrap;
529
+ }
530
+ .tk-foot {
531
+ display: flex;
532
+ justify-content: flex-end;
533
+ }
534
+ .col-empty {
535
+ padding: 14px 8px;
536
+ font-size: 11.5px;
537
+ color: var(--ink-faint);
538
+ text-align: center;
539
+ border: 1.5px dashed var(--line-strong);
540
+ border-radius: var(--radius-panel);
541
+ }
542
+
543
+ /* 状态胶囊:hairline pill,色调随列 */
544
+ .st-pill-btn {
545
+ padding: 0;
546
+ cursor: pointer;
547
+ background: transparent;
548
+ border: 0;
549
+ }
550
+ .st-pill {
551
+ display: inline-flex;
552
+ gap: 4px;
553
+ align-items: center;
554
+ padding: 1px 9px;
555
+ font-size: 11px;
556
+ font-weight: 600;
557
+ border: 1px solid var(--line-strong);
558
+ border-radius: var(--radius-pill);
559
+ }
560
+ .st-pill-btn:hover .st-pill { background: var(--hover-tint); }
561
+
562
+ /* 列表视图 */
563
+ .task-list {
564
+ overscroll-behavior: contain;
565
+ flex: 1 1 auto;
566
+ min-height: 0;
567
+ padding: 10px 16px 18px;
568
+ overflow-y: auto;
569
+ }
570
+ .list-sec { padding: 8px 2px 6px; }
571
+ .list-row {
572
+ display: flex;
573
+ gap: 12px;
574
+ align-items: center;
575
+ padding: 9px 12px;
576
+ margin-bottom: 4px;
577
+ cursor: pointer;
578
+ background: var(--paper-raised);
579
+ border: 1px solid var(--line);
580
+ border-radius: var(--radius-panel);
581
+ transition: border-color 0.12s ease;
582
+ }
583
+ .list-row:hover { border-color: var(--line-strong); }
584
+ .list-row .grow {
585
+ flex: 1;
586
+ min-width: 0;
587
+ overflow: hidden;
588
+ font-size: 13.5px;
589
+ color: var(--ink);
590
+ text-overflow: ellipsis;
591
+ white-space: nowrap;
592
+ }
593
+ .lnum {
594
+ font-size: 12px;
595
+ color: var(--ink-faint);
596
+ }
597
+ .meta {
598
+ font-family: var(--font-mono);
599
+ font-size: 11px;
600
+ color: var(--ink-faint);
601
+ }
602
+
603
+ /* 状态菜单 */
604
+ .menu-backdrop {
605
+ position: fixed;
606
+ inset: 0;
607
+ z-index: 60;
608
+ }
609
+ .st-menu {
610
+ position: fixed;
611
+ z-index: 61;
612
+ min-width: 128px;
613
+ padding: 4px;
614
+ background: var(--paper-raised);
615
+ border: 1px solid var(--line-strong);
616
+ border-radius: var(--radius-panel-sm);
617
+ box-shadow: var(--shadow-float);
618
+ }
619
+ .st-menu button {
620
+ display: flex;
621
+ gap: 7px;
622
+ align-items: center;
623
+ width: 100%;
624
+ padding: 6px 9px;
625
+ font-family: var(--font-body);
626
+ font-size: 12.5px;
627
+ color: var(--ink-soft);
628
+ text-align: left;
629
+ cursor: pointer;
630
+ background: transparent;
631
+ border: 0;
632
+ border-radius: var(--radius-chip);
633
+ }
634
+ .st-menu button:hover { background: var(--paper-deep); color: var(--ink); }
635
+ .st-menu button.danger { color: var(--tone-danger-dot); }
636
+
637
+ /* ── 窄屏(≤1023):看板列从"全高等宽四列"改为"一次一列"的横向卷轴 ──
638
+ 300px 定宽列在 390px 下只能露出半列 + 半列,状态语义读不出来;
639
+ 改为 86% 宽 + scroll-snap:一屏一个状态列。列表视图行允许折行,不竖排。 */
640
+ @media (max-width: 1023.98px) {
641
+ .toolbar {
642
+ flex-wrap: wrap;
643
+ gap: 8px;
644
+ padding: 10px;
645
+ }
646
+
647
+ .count,
648
+ .sec-cnt,
649
+ .tk-route,
650
+ .meta,
651
+ .st-pill {
652
+ font-size: 11.5px;
653
+ }
654
+
655
+ .board {
656
+ padding: 10px;
657
+ scroll-snap-type: x mandatory;
658
+ -webkit-overflow-scrolling: touch;
659
+ }
660
+
661
+ .task-col {
662
+ flex: 0 0 86%;
663
+ min-width: 0;
664
+ scroll-snap-align: center;
665
+ }
666
+
667
+ /* 行内四项(标题/成员/ID/状态)在 370px 下必然挤:允许折行,不允许压成竖排 */
668
+ .list-row {
669
+ flex-wrap: wrap;
670
+ row-gap: 4px;
671
+ min-height: 44px;
672
+ }
673
+
674
+ .list-row .grow {
675
+ flex: 1 1 60%;
676
+ }
677
+
678
+ .st-pill-btn {
679
+ min-height: 40px;
680
+ }
681
+
682
+ .st-menu button {
683
+ min-height: 44px;
684
+ }
685
+ }
686
+ </style>