agentworkshop 0.7.41 → 0.7.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  11. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  12. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  13. package/.output/public/_nuxt/{DGbd-UiG.js → EHlEMRii.js} +1 -1
  14. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  15. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  16. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  17. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  18. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  19. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  20. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  21. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  22. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  23. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  24. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  25. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  26. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  27. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  28. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  29. package/.output/public/_nuxt/builds/latest.json +1 -1
  30. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  31. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  32. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  33. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  34. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  35. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  36. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  37. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  38. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  39. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  40. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  41. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  42. package/.output/server/chunks/_/nitro.mjs +748 -733
  43. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  44. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  45. package/.output/server/chunks/build/{TownView--otsnr_7.mjs → TownView-CJ6IsDCc.mjs} +226 -225
  46. package/.output/server/chunks/build/{TownView--otsnr_7.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  47. package/.output/server/chunks/build/{_id_-Brv-JSvr.mjs → _id_-CVD_H0ir.mjs} +143 -142
  48. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  49. package/.output/server/chunks/build/{_id_-D9pl1OZy.mjs → _id_-CtxMb3qp.mjs} +41 -41
  50. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  51. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs → _wsId_-DpY3T-md.mjs} +514 -444
  52. package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  53. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  54. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  55. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  56. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  57. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  58. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  59. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  60. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  61. package/.output/server/chunks/build/{daq-DkMvD8sg.mjs → daq-DWWSKGAV.mjs} +89 -88
  62. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  63. package/.output/server/chunks/build/{dcw-C1jgVJkO.mjs → dcw-CT8k17_E.mjs} +44 -43
  64. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  65. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  66. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  67. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  68. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  69. package/.output/server/chunks/build/{pages-B0bUWnC6.mjs → pages-BHcSBM2D.mjs} +24 -18
  70. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  71. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  72. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  73. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  74. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  75. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  76. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  77. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  78. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  79. package/.output/server/chunks/build/{town-CoQdblbZ.mjs → town-QJWB5wng.mjs} +3 -2
  80. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  81. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  82. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  83. package/.output/server/chunks/routes/renderer.mjs +1 -1
  84. package/.output/server/chunks/virtual/entry.mjs +637 -86
  85. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  86. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  87. package/.output/server/package.json +1 -1
  88. package/README-zh.md +13 -3
  89. package/README.md +13 -3
  90. package/app/components/AppHeader.vue +961 -959
  91. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  92. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  93. package/app/components/workshop/AgentLanesView.vue +998 -998
  94. package/app/components/workshop/ArtifactCard.vue +120 -120
  95. package/app/components/workshop/AssetLibrary.vue +452 -452
  96. package/app/components/workshop/ChannelSessionList.vue +812 -804
  97. package/app/components/workshop/CommandPalette.vue +269 -269
  98. package/app/components/workshop/Composer.vue +827 -827
  99. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  100. package/app/components/workshop/EventBlock.vue +602 -602
  101. package/app/components/workshop/FileSelectorModal.vue +188 -188
  102. package/app/components/workshop/InspectorPanel.vue +301 -301
  103. package/app/components/workshop/MemoryPanel.vue +364 -364
  104. package/app/components/workshop/MultiChannelView.vue +189 -189
  105. package/app/components/workshop/TaskBoardView.vue +686 -686
  106. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  107. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  108. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  109. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  110. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  111. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  112. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  113. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  114. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  115. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  116. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  117. package/app/components/workshop/town/TownView.vue +56 -56
  118. package/app/middleware/locale-cookie.global.ts +22 -0
  119. package/app/pages/aml/index.vue +24 -2
  120. package/app/pages/daq/[id].vue +11 -11
  121. package/app/pages/daq/index.vue +2316 -2316
  122. package/app/pages/dcw/[id].vue +18 -19
  123. package/app/pages/dcw/index.vue +3 -3
  124. package/app/pages/index.vue +987 -981
  125. package/app/pages/plugins/index.vue +317 -311
  126. package/app/pages/settings.vue +1564 -1562
  127. package/app/pages/tokens.vue +15 -15
  128. package/app/pages/workshop/agents.vue +612 -609
  129. package/app/pages/workshop/channel-templates.vue +18 -11
  130. package/app/pages/workshop/index.vue +668 -668
  131. package/app/pages/workshop/teams.vue +743 -740
  132. package/app/pages/workshop/w/[wsId].vue +704 -704
  133. package/app/plugins/locale-restore.client.ts +16 -1
  134. package/app/utils/builtin-catalog.ts +104 -0
  135. package/config.yml +170 -170
  136. package/docs/full-test-plan.md +2 -3
  137. package/i18n/locales/en.ts +293 -35
  138. package/i18n/locales/zh-CN.ts +265 -7
  139. package/package.json +2 -2
  140. package/scripts/_dbg-perms-e2e.mjs +132 -109
  141. package/scripts/_demo-agentteam-capture.mjs +278 -0
  142. package/scripts/_e2e-ui-functional.mjs +371 -0
  143. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  144. package/scripts/_i18n-en-verify.mjs +107 -0
  145. package/scripts/_i18n-extra-checks.mjs +53 -0
  146. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  147. package/scripts/_i18n-verify-keys.mjs +50 -0
  148. package/scripts/_loop-biax-campaign.mjs +15 -9
  149. package/scripts/_sweep-button-contrast.mjs +87 -0
  150. package/scripts/ui/record-demo.mjs +557 -0
  151. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  152. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  153. package/server/services/workshop/daq/drivers.ts +11 -0
  154. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  155. package/.output/public/_nuxt/BN16aRdQ.js +0 -1
  156. package/.output/public/_nuxt/BWVhZntO.js +0 -1
  157. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  158. package/.output/public/_nuxt/BsROmY89.js +0 -1
  159. package/.output/public/_nuxt/C0ZrS1KL.js +0 -19
  160. package/.output/public/_nuxt/C4s_niy-.js +0 -14
  161. package/.output/public/_nuxt/D6mbFA-a.js +0 -1
  162. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  163. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  164. package/.output/public/_nuxt/Dap5PmTL.js +0 -2
  165. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  166. package/.output/public/_nuxt/DvLxILQP.js +0 -1
  167. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  168. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  169. package/.output/public/_nuxt/_id_.D9ymtHB5.css +0 -1
  170. package/.output/public/_nuxt/_id_.jf-_8E_c.css +0 -1
  171. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  172. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  173. package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +0 -1
  174. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  175. package/.output/public/_nuxt/daq.DjSgY94l.css +0 -1
  176. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  177. package/.output/public/_nuxt/eqq3wWQF.js +0 -18
  178. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  179. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  180. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  181. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  182. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  183. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  184. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  185. package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +0 -1
  186. package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +0 -1
  187. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  188. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  189. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  190. package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +0 -1
  191. package/.output/server/chunks/build/dcw-C1jgVJkO.mjs.map +0 -1
  192. package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +0 -1
  193. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  194. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  195. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  196. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  197. package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +0 -1
  198. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  199. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  200. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  201. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  202. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  203. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  204. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  205. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  206. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  207. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  208. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  209. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  210. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  211. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  212. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  213. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  214. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  215. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  216. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  217. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  218. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  219. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  220. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  221. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  222. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  223. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  224. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  225. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  226. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  227. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  228. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  229. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  230. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  231. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  232. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  233. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  234. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  235. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  236. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  237. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  238. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  239. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  240. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  241. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  242. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  243. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  244. package/scripts/_dbg-minimal-omp.mjs +0 -77
  245. package/scripts/_dbg-mode-nav.mjs +0 -173
  246. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  247. package/scripts/_dbg-team-repro.mjs +0 -76
  248. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  249. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  250. package/scripts/_townview_style.css +0 -1185
  251. package/scripts/_townview_template.html +0 -1215
  252. package/server/data/agent-node-bindings.json +0 -554
  253. package/server/data/daq-templates.json +0 -16
  254. package/server/data/daqs.json +0 -7464
  255. package/server/data/dcw-lines.json +0 -1787
  256. package/server/data/dcw-products.json +0 -1626
  257. package/server/data/dcw-recipes.json +0 -4672
  258. package/server/data/dcw-rollback.json +0 -20754
  259. package/server/data/dcw-runs.json +0 -6354
  260. package/server/data/dcw-templates.json +0 -146
  261. package/server/data/dcw-writes.json +0 -15650
  262. package/server/data/dcws.json +0 -9033
  263. package/server/data/device-twins.json +0 -133
  264. package/server/data/line-runs.json +0 -322
  265. package/server/data/scene-layouts.json +0 -277
@@ -1,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>