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,704 +1,704 @@
1
- <script setup lang="ts">
2
- /**
3
- * Workspace 主控台(Zcode 风格 Harness):
4
- * 顶栏(WS 状态 + seq + 视图切换)+ 左栏 Channel 会话 + 中部三视图
5
- * (时间线 / Agent lanes / 任务板)+ 右侧 Inspector + 底部 Composer +
6
- * Agent/Task 双抽屉(执行详情)。
7
- * 挂载 workspace 全部 channel 的 WS 订阅;聚焦 channel 驱动中部/右侧上下文。
8
- */
9
- import { useWorkspacesStore } from '../../../stores/workshop/workspaces'
10
- import { useEntitiesStore } from '../../../stores/workshop/entities'
11
- import { useWorkshopWs } from '../../../composables/workshop/useWorkshopWs'
12
- import { useUserStore } from '../../../stores/workshop/user'
13
- import { useStorage } from '@vueuse/core'
14
- import { useResponsive } from '../../../composables/useResponsive'
15
-
16
- const { t } = useI18n()
17
-
18
- definePageMeta({ layout: 'default' })
19
-
20
- const route = useRoute()
21
- const wsId = computed(() => String(route.params.wsId))
22
- const userStore = useUserStore()
23
- const wsStore = useWorkspacesStore()
24
-
25
- // 用户守卫 + workspace 服务端加载
26
- // SSR 阶段不判登录(会话恢复是客户端插件,服务端无登录态——同步踢回会把
27
- // 刷新/直达 URL 的已登录用户误弹回总览);客户端挂载后校验并按需加载
28
- const authReady = ref(false)
29
- onMounted(() => {
30
- if (!userStore.isLoggedIn) {
31
- navigateTo('/workshop')
32
- return
33
- }
34
- authReady.value = true
35
- if (!wsStore.loaded) wsStore.load().catch(() => {})
36
- })
37
- const entities = useEntitiesStore()
38
- const { subscribe, unsubscribe, conn } = useWorkshopWs()
39
-
40
- // 视口档位(唯一判据;SSR 期返回桌面档)——
41
- // 窄屏(≤1023)三栏仪表台只剩 ~460px 画布,必须换成「单通道示波器」形态
42
- const { isDesktop } = useResponsive()
43
-
44
- const workspace = computed(() => wsStore.workspaces.find(w => w.id === wsId.value))
45
- // 聚焦 channel:activeChannelId 须在挂载清单内(陈旧持久化/竞态下回退首频道),
46
- // 避免订阅死频道 → 快照永不到达 → 右栏/时间线长时间"空数据"假象
47
- const channelId = computed(() => {
48
- const ws = workspace.value
49
- if (!ws) return undefined
50
- const ids = ws.channelIds
51
- const active = ws.activeChannelId
52
- return active && ids.includes(active) ? active : ids[0]
53
- })
54
-
55
- // 订阅生命周期:workspace 挂载的 channel 变化 → 增量 sub/unsub
56
- watch(
57
- () => [wsId.value, workspace.value?.channelIds.join(',') ?? ''],
58
- () => {
59
- const mounted = new Set(workspace.value?.channelIds ?? [])
60
- for (const id of mounted) subscribe(id)
61
- },
62
- { immediate: true },
63
- )
64
- watch(
65
- () => workspace.value?.channelIds.join(',') ?? '',
66
- (_next, prev) => {
67
- if (prev === undefined) return
68
- const mounted = new Set(workspace.value?.channelIds ?? [])
69
- for (const prevId of prev.split(',').filter(Boolean)) {
70
- if (!mounted.has(prevId)) unsubscribe(prevId)
71
- }
72
- },
73
- )
74
- onBeforeUnmount(() => {
75
- for (const id of workspace.value?.channelIds ?? []) unsubscribe(id)
76
- })
77
-
78
- const stateColor = computed(() =>
79
- conn.state === 'open' ? 'var(--tone-success-dot)' : conn.state === 'connecting' ? 'var(--tone-warning-dot)' : 'var(--tone-danger-dot)',
80
- )
81
- const lastSeq = computed(() => (channelId.value ? conn.cursors[channelId.value] ?? 0 : 0))
82
-
83
- // 视图切换(P1 三视图 + P2 多通道同屏 + P5 RPG 小镇)
84
- // 深链:?view=lanes/board/split/town 直达指定视图(可分享/收藏)
85
- // inspector 只在窄屏出现(桌面它是右侧常驻栏),故不进深链白名单
86
- type CenterView = 'timeline' | 'lanes' | 'board' | 'split' | 'town' | 'inspector'
87
- const VIEW_KEYS: Record<string, CenterView> = {
88
- 1: 'timeline',
89
- 2: 'lanes',
90
- 3: 'board',
91
- 4: 'split',
92
- 5: 'town',
93
- }
94
- const VIEW_VALUES = new Set(['timeline', 'lanes', 'board', 'split', 'town'])
95
- const initView = route.query.view
96
- const view = ref<CenterView>(
97
- typeof initView === 'string' && VIEW_VALUES.has(initView) ? initView as CenterView : 'timeline',
98
- )
99
- const viewOptions = computed(() => {
100
- const base = [
101
- { value: 'timeline', label: t('wsView.k3otu32010') },
102
- { value: 'lanes', label: 'Agent lanes' },
103
- { value: 'board', label: t('wsView.k3ko7a8011') },
104
- { value: 'split', label: t('wsView.k3xbmo012') },
105
- { value: 'town', label: t('wsView.k1cz0pbw013') },
106
- ]
107
- // 窄屏「一次一区」:检查器不占侧栏,并入切换条(四区都由同一条承载)
108
- return narrowUI.value ? [...base, { value: 'inspector', label: '检查器' }] : base
109
- })
110
- // 数字快捷键 1-5 直切视图(非输入焦点时;控制台型键盘操作与 ⌘K 面板同一取向)
111
- const onViewKey = (ev: KeyboardEvent): void => {
112
- // 窄屏抽屉:Esc 收起(与全站侧栏抽屉一致)
113
- if (ev.key === 'Escape' && narrowUI.value && leftOpen.value) {
114
- leftOpen.value = false
115
- return
116
- }
117
- if (ev.metaKey || ev.ctrlKey || ev.altKey) return
118
- const t = ev.target as HTMLElement | null
119
- if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return
120
- const next = VIEW_KEYS[ev.key]
121
- if (!next || !channelId.value) return
122
- ev.preventDefault()
123
- view.value = next
124
- }
125
- onMounted(() => window.addEventListener('keydown', onViewKey))
126
- onBeforeUnmount(() => window.removeEventListener('keydown', onViewKey))
127
-
128
- // 侧栏折叠(现代 harness 布局:左会话栏 / 右检查器可按需收起)
129
- // 形态差异只在挂载后生效:SSR/首帧一律按桌面结构渲染,客户端接管后才翻档,
130
- // 否则服务端 5 项切换条 vs 客户端 6 项会触发水合告警
131
- const mounted = ref(false)
132
- onMounted(() => {
133
- mounted.value = true
134
- })
135
- const narrowUI = computed(() => mounted.value && !isDesktop.value)
136
-
137
- const leftOpen = ref(true)
138
- const rightOpen = ref(true)
139
- /** 窄屏:左右侧栏都不再占位(收成覆盖式抽屉 / 并入切换条) */
140
- const applyTier = (desktop: boolean): void => {
141
- leftOpen.value = desktop
142
- rightOpen.value = desktop
143
- // 检查器只是窄屏的第 4 区:回桌面后它回到右栏,中部得有自己的区(否则中部空白)
144
- if (desktop && view.value === 'inspector') view.value = 'timeline'
145
- }
146
- onMounted(() => {
147
- if (!isDesktop.value) applyTier(false)
148
- })
149
- watch(isDesktop, d => applyTier(d))
150
- // 抽屉里选中频道后自动收起(窄屏少一次手动关闭)
151
- watch(channelId, (next, prev) => {
152
- if (prev !== undefined && next !== prev && narrowUI.value) leftOpen.value = false
153
- })
154
-
155
- // 侧栏宽度拖拽调节(PaneSplitter;localStorage 持久化,双击复位到默认值)
156
- const LEFT_W_DEFAULT = 248
157
- const RIGHT_W_DEFAULT = 300
158
- const leftWidth = useStorage('aw.harness.leftW', LEFT_W_DEFAULT)
159
- const rightWidth = useStorage('aw.harness.rightW', RIGHT_W_DEFAULT)
160
- const resizeLeft = (d: number): void => {
161
- leftWidth.value = Math.min(460, Math.max(220, leftWidth.value + d))
162
- }
163
- const resizeRight = (d: number): void => {
164
- rightWidth.value = Math.min(560, Math.max(240, rightWidth.value - d))
165
- }
166
- // 初始化消毒:陈旧持久化值(超出合法范围/异常类型)夹取回默认邻域,防布局被历史脏数据撑坏
167
- resizeLeft(0)
168
- resizeRight(0)
169
-
170
- // 抽屉状态(P1)
171
- const agentDrawerOpen = ref(false)
172
- const agentDrawerId = ref<string | null>(null)
173
- const openAgent = (id: string): void => {
174
- agentDrawerId.value = id
175
- agentDrawerOpen.value = true
176
- }
177
- /** 活动条入口:跨 channel 的 busy 成员 → 先聚焦其 channel 再开抽屉 */
178
- const openAgentInChannel = (target: { channelId: string, agentId: string }): void => {
179
- wsStore.setActiveChannel(wsId.value, target.channelId)
180
- openAgent(target.agentId)
181
- }
182
- /** @提及 pill 点击入口(ClusterRoute/ClusterStream inject;时间线与 lanes 全树可用) */
183
- provide('aw:open-agent', openAgentInChannel)
184
- const taskDrawerOpen = ref(false)
185
- const taskDrawerId = ref<string | null>(null)
186
- const openTask = (id: string): void => {
187
- taskDrawerId.value = id
188
- taskDrawerOpen.value = true
189
- }
190
-
191
- // ⌘K 命令面板 + A2A 调试器(P2)
192
- const paletteOpen = ref(false)
193
- const a2aDebugOpen = ref(false)
194
- const composerBox = ref<HTMLElement | null>(null)
195
- const focusComposer = (): void => {
196
- composerBox.value?.querySelector('textarea')?.focus()
197
- }
198
-
199
- useHead({ title: () => `${workspace.value?.name ?? 'Workspace'} · AgentWorkShop` })
200
- </script>
201
-
202
- <template>
203
- <div class="harness">
204
- <!-- @提及悬停信息卡(文档级委托;时间线/lanes 全树生效) -->
205
- <workshop-mention-hover-card />
206
- <!-- 顶栏 -->
207
- <div class="topbar">
208
- <div
209
- class="left"
210
- tabindex="-1"
211
- @keydown="onViewKey"
212
- >
213
- <span class="topbar-mark i-tabler-box" />
214
- <span class="ws-name">{{ workspace?.name ?? '未知 Workspace' }}</span>
215
- <span
216
- v-if="channelId"
217
- class="chan-chip"
218
- :title="channelId"
219
- >
220
- <span class="chan-hash">#</span>{{ entities.channels[channelId]?.name ?? channelId.slice(0, 8) }}
221
- </span>
222
- <a-segmented
223
- v-if="channelId"
224
- v-model:value="view"
225
- size="small"
226
- :options="viewOptions"
227
- class="view-switch"
228
- :title="$t('wsView.kqwckjr001')"
229
- @keydown="onViewKey"
230
- />
231
- </div>
232
- <div class="right">
233
- <!-- 窄屏:频道会话列表入口(覆盖式抽屉;该页一次只看一区) -->
234
- <button
235
- v-if="narrowUI"
236
- class="pane-toggle im toggle-left-narrow"
237
- :class="{ off: !leftOpen }"
238
- title="频道会话列表"
239
- @click="leftOpen = !leftOpen"
240
- >
241
- <span class="i-tabler-list-details im-pop" />
242
- </button>
243
- <button
244
- class="pane-toggle im toggle-left-desk"
245
- :class="{ off: !leftOpen }"
246
- :title="$t('wsView.k1tsy2e4002')"
247
- @click="leftOpen = !leftOpen"
248
- >
249
- <span class="i-tabler-layout-sidebar-left-collapse im-pop" />
250
- </button>
251
- <button
252
- class="pane-toggle im toggle-right-desk"
253
- :class="{ off: !rightOpen }"
254
- :title="$t('wsView.k1tx0ppf003')"
255
- @click="rightOpen = !rightOpen"
256
- >
257
- <span class="i-tabler-layout-sidebar-right-collapse im-pop" />
258
- </button>
259
- <button
260
- class="pane-toggle im"
261
- title="A2A RPC/SSE 调试器"
262
- @click="a2aDebugOpen = true"
263
- >
264
- <span class="i-tabler-terminal-2 im-pop" />
265
- </button>
266
- <button
267
- class="pane-toggle im"
268
- :title="$t('wsView.k1cvg8sb004')"
269
- @click="paletteOpen = true"
270
- >
271
- <span class="i-tabler-command im-pop" />
272
- </button>
273
- <span
274
- class="dot"
275
- :style="{ background: stateColor }"
276
- />
277
- <span
278
- class="ws-state"
279
- :data-state="conn.state"
280
- >{{ conn.state }}</span>
281
- <span class="seq">seq {{ lastSeq }}</span>
282
- </div>
283
- </div>
284
-
285
- <!-- 主体三栏(左/右侧栏可折叠 + 拖拽调宽;分隔条 hairline 即面板边界) -->
286
- <div class="main">
287
- <!-- 窄屏抽屉遮罩:点空白收起(不盖住底部 Composer,输入不被"锁在"抽屉后) -->
288
- <div
289
- v-if="narrowUI && leftOpen"
290
- class="drawer-scrim"
291
- @click="leftOpen = false"
292
- />
293
- <div
294
- v-if="leftOpen"
295
- class="left-pane"
296
- :style="{ flexBasis: narrowUI ? 'auto' : `${leftWidth}px` }"
297
- >
298
- <div class="left-scroll">
299
- <workshop-channel-session-list :ws-id="wsId" />
300
- </div>
301
- <workshop-live-agent-bar
302
- :ws-id="wsId"
303
- @open-agent="openAgentInChannel"
304
- />
305
- </div>
306
- <workshop-pane-splitter
307
- v-if="leftOpen && !narrowUI"
308
- :label="$t('wsView.k1fkfvra005')"
309
- @resize="resizeLeft"
310
- @reset="leftWidth = LEFT_W_DEFAULT"
311
- />
312
- <div class="center-pane">
313
- <template v-if="channelId">
314
- <workshop-transcript-timeline
315
- v-if="view === 'timeline'"
316
- :channel-id="channelId"
317
- />
318
- <workshop-agent-lanes-view
319
- v-else-if="view === 'lanes'"
320
- :channel-id="channelId"
321
- />
322
- <workshop-task-board-view
323
- v-else-if="view === 'board'"
324
- :channel-id="channelId"
325
- @open-task="openTask"
326
- />
327
- <workshop-multi-channel-view
328
- v-else-if="view === 'split'"
329
- :ws-id="wsId"
330
- @open-task="openTask"
331
- />
332
- <workshop-town-view
333
- v-else-if="view === 'town'"
334
- :channel-id="channelId"
335
- />
336
- <!-- 窄屏第 4 区:检查器并入切换条(桌面仍是右侧常驻栏) -->
337
- <workshop-inspector-panel
338
- v-else-if="view === 'inspector' && narrowUI"
339
- :channel-id="channelId"
340
- @open-agent="openAgent"
341
- @open-task="openTask"
342
- />
343
- </template>
344
- <div
345
- v-else
346
- class="pane-empty"
347
- >
348
- <span class="pe-icon i-tabler-messages" />
349
- <div class="pe-title">
350
- {{ $t('wsView.klzbtyp007') }} <span class="aw-serif-accent-italic">Channel</span> {{ $t('wsView.k3zaj4008') }}
351
- </div>
352
- <div class="pe-sub">
353
- {{ $t('wsView.k1us0cxy009') }}
354
- </div>
355
- </div>
356
- </div>
357
- <workshop-pane-splitter
358
- v-if="rightOpen && !narrowUI"
359
- :label="$t('wsView.kwqb0st006')"
360
- @resize="resizeRight"
361
- @reset="rightWidth = RIGHT_W_DEFAULT"
362
- />
363
- <div
364
- v-if="rightOpen && !narrowUI"
365
- class="right-pane"
366
- :class="{ empty: wsStore.loaded && !channelId }"
367
- :style="{ flexBasis: `${rightWidth}px` }"
368
- >
369
- <workshop-inspector-panel
370
- v-if="channelId"
371
- :channel-id="channelId"
372
- @open-agent="openAgent"
373
- @open-task="openTask"
374
- />
375
- <!-- 加载中/无频道的诚实降级态(workspace 列表未返回前不误判为"空") -->
376
- <div
377
- v-else-if="!wsStore.loaded"
378
- class="pane-loading"
379
- >
380
- 正在加载 Workspace…
381
- </div>
382
- </div>
383
- </div>
384
-
385
- <!-- Composer -->
386
- <div
387
- v-if="channelId"
388
- ref="composerBox"
389
- class="composer-pane"
390
- >
391
- <workshop-composer :channel-id="channelId" />
392
-
393
- <!-- 抽屉 -->
394
- <workshop-agent-inspector-drawer
395
- v-model:open="agentDrawerOpen"
396
- :channel-id="channelId ?? ''"
397
- :agent-id="agentDrawerId"
398
- />
399
- <workshop-task-inspector-drawer
400
- v-model:open="taskDrawerOpen"
401
- :channel-id="channelId ?? ''"
402
- :task-id="taskDrawerId"
403
- />
404
- <workshop-a2a-rpc-debugger
405
- v-model:open="a2aDebugOpen"
406
- :channel-id="channelId ?? ''"
407
- />
408
-
409
- <!-- ⌘K 命令面板 -->
410
- <workshop-command-palette
411
- v-model:open="paletteOpen"
412
- :ws-id="wsId"
413
- @set-view="view = $event"
414
- @open-a2a-debug="a2aDebugOpen = true"
415
- @compose="focusComposer"
416
- />
417
- </div>
418
- </div>
419
- </template>
420
-
421
- <style scoped>
422
- .harness {
423
- display: flex;
424
- flex-direction: column;
425
- height: calc(100dvh - var(--app-header-h, 56px) - var(--app-footer-h, 46px) - 16px);
426
- min-height: 0;
427
- margin: 0;
428
- border: 1px solid var(--line);
429
- border-radius: var(--radius-shell);
430
- overflow: hidden;
431
- background: var(--paper-raised);
432
- box-shadow: var(--shadow-card);
433
- }
434
- .topbar {
435
- display: flex;
436
- align-items: center;
437
- justify-content: space-between;
438
- padding: 8px 14px;
439
- font-size: 13px;
440
- background: var(--paper-raised);
441
- border-bottom: 1px solid var(--line);
442
- }
443
- .left,
444
- .right {
445
- display: flex;
446
- gap: 8px;
447
- align-items: center;
448
- min-width: 0;
449
- }
450
- .topbar-mark {
451
- font-size: 15px;
452
- color: var(--ink-faint);
453
- }
454
- .ws-name {
455
- max-width: 260px;
456
- overflow: hidden;
457
- font-family: var(--font-display);
458
- font-weight: 400;
459
- font-size: 17px;
460
- letter-spacing: -0.01em;
461
- text-overflow: ellipsis;
462
- white-space: nowrap;
463
- }
464
- /* channel 胶囊:hairline chip + serif # */
465
- .chan-chip {
466
- display: inline-flex;
467
- gap: 3px;
468
- align-items: center;
469
- max-width: 200px;
470
- padding: 1px 10px;
471
- overflow: hidden;
472
- font-size: 12px;
473
- color: var(--ink-soft);
474
- text-overflow: ellipsis;
475
- white-space: nowrap;
476
- border: 1px solid var(--line-strong);
477
- border-radius: var(--radius-pill);
478
- }
479
- .chan-hash {
480
- font-family: var(--font-display);
481
- color: var(--ink-faint);
482
- }
483
- .view-switch { margin-left: 8px; }
484
- .dot { width: 8px; height: 8px; border-radius: 50%; }
485
- .ws-state {
486
- font-family: var(--font-mono);
487
- font-size: 10px;
488
- letter-spacing: 0.1em;
489
- text-transform: uppercase;
490
- padding: 1px 6px;
491
- border: 1px solid var(--line);
492
- border-radius: var(--radius-chip);
493
- }
494
- .ws-state[data-state='open'] { color: var(--tone-success-dot); border-color: color-mix(in srgb, var(--tone-success-dot) 45%, transparent); }
495
- .ws-state[data-state='connecting'] { color: var(--tone-warning-dot); border-color: color-mix(in srgb, var(--tone-warning-dot) 45%, transparent); }
496
- .ws-state[data-state='closed'] { color: var(--tone-danger-dot); border-color: color-mix(in srgb, var(--tone-danger-dot) 45%, transparent); }
497
- .seq {
498
- font-family: var(--font-mono);
499
- font-variant-numeric: tabular-nums;
500
- font-size: 10px;
501
- color: var(--ink-faint);
502
- }
503
-
504
- /* 侧栏折叠开关:幽灵图标钮 */
505
- .pane-toggle {
506
- display: inline-flex;
507
- align-items: center;
508
- justify-content: center;
509
- width: 26px;
510
- height: 26px;
511
- font-size: 14px;
512
- color: var(--ink-soft);
513
- cursor: pointer;
514
- background: transparent;
515
- border: 0;
516
- border-radius: var(--radius-panel-sm);
517
- transition: color var(--transition-fast), background var(--transition-fast), opacity var(--transition-fast);
518
- }
519
- .pane-toggle:hover {
520
- color: var(--ink);
521
- background: var(--paper-deep);
522
- }
523
- .pane-toggle.off {
524
- opacity: 0.4;
525
- }
526
- .pane-toggle.off:hover {
527
- opacity: 1;
528
- }
529
- .main {
530
- display: flex;
531
- flex: 1 1 auto;
532
- min-height: 0;
533
- }
534
- .left-pane {
535
- display: flex;
536
- flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
537
- flex-direction: column;
538
- min-height: 0;
539
- overflow: hidden;
540
- background: var(--paper);
541
- }
542
- .left-scroll {
543
- flex: 1 1 auto;
544
- min-height: 0;
545
- overflow: hidden auto;
546
- }
547
- .center-pane {
548
- display: flex;
549
- flex: 1 1 auto;
550
- min-width: 0;
551
- min-height: 0;
552
- max-width: 100%;
553
- overflow: hidden;
554
- background: var(--paper); /* 灰画布:消息气泡/白色面板在此浮出(Slack 式分层) */
555
- }
556
- .center-pane > * {
557
- min-width: 0;
558
- max-width: 100%;
559
- }
560
- .right-pane {
561
- flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
562
- min-height: 0;
563
- overflow: hidden;
564
- background: var(--paper);
565
- }
566
- .right-pane.empty { opacity: 0.35; }
567
- /* workspace 列表加载中的诚实降级态(不算"空",不淡化整栏) */
568
- .pane-loading {
569
- display: flex;
570
- align-items: center;
571
- justify-content: center;
572
- height: 100%;
573
- font-size: 12px;
574
- color: var(--ink-faint);
575
- }
576
- .composer-pane { flex: 0 0 auto; }
577
-
578
- /* ══════════════════════════════════════════════════════════════════════════
579
- 窄屏形态 · 单通道示波器(≤1023px) —— 一次只看一路信号
580
- ──────────────────────────────────────────────────────────────────────────
581
- 桌面是「三栏仪表台」:会话栏 248 + 画布 + 检查器 300 + 两条分隔条。
582
- 到了 ≤1023,画布只剩 ~460px(900–1023 还要再让 64px 图标轨),
583
- 硬塞的结果是三栏互相压扁。所以窄屏换一台仪器,而不是把仪表台缩小:
584
-
585
- · 中部视图(时间线 / lanes / 任务板 / 检查器)→ 由顶栏切换条承载,
586
- 一次只挂载一区,顶部切换条独占一行、可横扫;
587
- · 频道会话列表 → 覆盖式抽屉(点遮罩/Esc/选中频道收起),不再占位;
588
- · 拖拽分隔条在窄屏没有意义(没有第二栏可分配宽度)→ 隐藏;
589
- · 触摸目标 ≥40px、标签 ≥11.5px(手持 30cm 距离下 10px 只剩 6px 有效字号)。
590
-
591
- ⚠️ 断点数值与 main.css v5 / useResponsive.ts 一致(1024 = 三栏仪表台下限),
592
- 此处不再引入新魔数。桌面(≥1024)样式完全不受影响。
593
- ══════════════════════════════════════════════════════════════════════════ */
594
- @media (max-width: 1023.98px) {
595
- .harness {
596
- /* 窄屏收掉壳体外留白,把纵向像素让给内容与底部 Composer */
597
- height: calc(100dvh - var(--app-header-h, 56px) - var(--app-footer-h, 46px) - 8px);
598
- }
599
-
600
- .topbar {
601
- flex-wrap: wrap;
602
- gap: 6px 8px;
603
- padding: 8px 10px;
604
- }
605
-
606
- .topbar .left {
607
- row-gap: 8px;
608
- }
609
-
610
- .ws-name {
611
- max-width: 40vw;
612
- font-size: 15px;
613
- }
614
-
615
- .chan-chip {
616
- max-width: 34vw;
617
- font-size: 12px;
618
- }
619
-
620
- /* 切换条独占一行:五个视图名不再挤掉工作区名(channel 名仍可见) */
621
- .view-switch {
622
- flex: 1 1 100%;
623
- margin-left: 0;
624
- }
625
-
626
- .ws-state,
627
- .seq {
628
- font-size: 11.5px;
629
- }
630
-
631
- /* 触摸目标:26px 的幽灵图标钮在手持设备上点不中 */
632
- .pane-toggle {
633
- width: 40px;
634
- height: 40px;
635
- font-size: 18px;
636
- }
637
-
638
- .toggle-left-desk,
639
- .toggle-right-desk {
640
- display: none;
641
- }
642
-
643
- .main {
644
- position: relative;
645
- }
646
-
647
- .left-pane {
648
- position: absolute;
649
- top: 0;
650
- bottom: 0;
651
- left: 0;
652
- z-index: 30;
653
- width: min(86vw, 320px);
654
- border-right: 1px solid var(--line);
655
- box-shadow: var(--shadow-float);
656
- animation: pane-in 0.18s cubic-bezier(0.22, 1, 0.36, 1);
657
- }
658
-
659
- .drawer-scrim {
660
- position: absolute;
661
- inset: 0;
662
- z-index: 25;
663
- background: color-mix(in srgb, var(--ink) 34%, transparent);
664
- }
665
-
666
- /* 窄屏没有第二栏可分宽度,拖拽条只会白占 9px */
667
- .pane-splitter {
668
- display: none;
669
- }
670
-
671
- .pane-loading {
672
- font-size: 13px;
673
- }
674
- }
675
-
676
- /* 窄屏专属入口在桌面不出现(响应式形态差异不用 JS 表达,避免水合抖动) */
677
- .toggle-left-narrow {
678
- display: none;
679
- }
680
-
681
- @media (max-width: 1023.98px) {
682
- .toggle-left-narrow {
683
- display: inline-flex;
684
- }
685
- }
686
-
687
- @media (prefers-reduced-motion: reduce) {
688
- .left-pane {
689
- animation: none;
690
- }
691
- }
692
-
693
- @keyframes pane-in {
694
- from {
695
- opacity: 0.5;
696
- transform: translateX(-16px);
697
- }
698
-
699
- to {
700
- opacity: 1;
701
- transform: none;
702
- }
703
- }
704
- </style>
1
+ <script setup lang="ts">
2
+ /**
3
+ * Workspace 主控台(Zcode 风格 Harness):
4
+ * 顶栏(WS 状态 + seq + 视图切换)+ 左栏 Channel 会话 + 中部三视图
5
+ * (时间线 / Agent lanes / 任务板)+ 右侧 Inspector + 底部 Composer +
6
+ * Agent/Task 双抽屉(执行详情)。
7
+ * 挂载 workspace 全部 channel 的 WS 订阅;聚焦 channel 驱动中部/右侧上下文。
8
+ */
9
+ import { useWorkspacesStore } from '../../../stores/workshop/workspaces'
10
+ import { useEntitiesStore } from '../../../stores/workshop/entities'
11
+ import { useWorkshopWs } from '../../../composables/workshop/useWorkshopWs'
12
+ import { useUserStore } from '../../../stores/workshop/user'
13
+ import { useStorage } from '@vueuse/core'
14
+ import { useResponsive } from '../../../composables/useResponsive'
15
+
16
+ const { t } = useI18n()
17
+
18
+ definePageMeta({ layout: 'default' })
19
+
20
+ const route = useRoute()
21
+ const wsId = computed(() => String(route.params.wsId))
22
+ const userStore = useUserStore()
23
+ const wsStore = useWorkspacesStore()
24
+
25
+ // 用户守卫 + workspace 服务端加载
26
+ // SSR 阶段不判登录(会话恢复是客户端插件,服务端无登录态——同步踢回会把
27
+ // 刷新/直达 URL 的已登录用户误弹回总览);客户端挂载后校验并按需加载
28
+ const authReady = ref(false)
29
+ onMounted(() => {
30
+ if (!userStore.isLoggedIn) {
31
+ navigateTo('/workshop')
32
+ return
33
+ }
34
+ authReady.value = true
35
+ if (!wsStore.loaded) wsStore.load().catch(() => {})
36
+ })
37
+ const entities = useEntitiesStore()
38
+ const { subscribe, unsubscribe, conn } = useWorkshopWs()
39
+
40
+ // 视口档位(唯一判据;SSR 期返回桌面档)——
41
+ // 窄屏(≤1023)三栏仪表台只剩 ~460px 画布,必须换成「单通道示波器」形态
42
+ const { isDesktop } = useResponsive()
43
+
44
+ const workspace = computed(() => wsStore.workspaces.find(w => w.id === wsId.value))
45
+ // 聚焦 channel:activeChannelId 须在挂载清单内(陈旧持久化/竞态下回退首频道),
46
+ // 避免订阅死频道 → 快照永不到达 → 右栏/时间线长时间"空数据"假象
47
+ const channelId = computed(() => {
48
+ const ws = workspace.value
49
+ if (!ws) return undefined
50
+ const ids = ws.channelIds
51
+ const active = ws.activeChannelId
52
+ return active && ids.includes(active) ? active : ids[0]
53
+ })
54
+
55
+ // 订阅生命周期:workspace 挂载的 channel 变化 → 增量 sub/unsub
56
+ watch(
57
+ () => [wsId.value, workspace.value?.channelIds.join(',') ?? ''],
58
+ () => {
59
+ const mounted = new Set(workspace.value?.channelIds ?? [])
60
+ for (const id of mounted) subscribe(id)
61
+ },
62
+ { immediate: true },
63
+ )
64
+ watch(
65
+ () => workspace.value?.channelIds.join(',') ?? '',
66
+ (_next, prev) => {
67
+ if (prev === undefined) return
68
+ const mounted = new Set(workspace.value?.channelIds ?? [])
69
+ for (const prevId of prev.split(',').filter(Boolean)) {
70
+ if (!mounted.has(prevId)) unsubscribe(prevId)
71
+ }
72
+ },
73
+ )
74
+ onBeforeUnmount(() => {
75
+ for (const id of workspace.value?.channelIds ?? []) unsubscribe(id)
76
+ })
77
+
78
+ const stateColor = computed(() =>
79
+ conn.state === 'open' ? 'var(--tone-success-dot)' : conn.state === 'connecting' ? 'var(--tone-warning-dot)' : 'var(--tone-danger-dot)',
80
+ )
81
+ const lastSeq = computed(() => (channelId.value ? conn.cursors[channelId.value] ?? 0 : 0))
82
+
83
+ // 视图切换(P1 三视图 + P2 多通道同屏 + P5 RPG 小镇)
84
+ // 深链:?view=lanes/board/split/town 直达指定视图(可分享/收藏)
85
+ // inspector 只在窄屏出现(桌面它是右侧常驻栏),故不进深链白名单
86
+ type CenterView = 'timeline' | 'lanes' | 'board' | 'split' | 'town' | 'inspector'
87
+ const VIEW_KEYS: Record<string, CenterView> = {
88
+ 1: 'timeline',
89
+ 2: 'lanes',
90
+ 3: 'board',
91
+ 4: 'split',
92
+ 5: 'town',
93
+ }
94
+ const VIEW_VALUES = new Set(['timeline', 'lanes', 'board', 'split', 'town'])
95
+ const initView = route.query.view
96
+ const view = ref<CenterView>(
97
+ typeof initView === 'string' && VIEW_VALUES.has(initView) ? initView as CenterView : 'timeline',
98
+ )
99
+ const viewOptions = computed(() => {
100
+ const base = [
101
+ { value: 'timeline', label: t('wsView.k3otu32010') },
102
+ { value: 'lanes', label: 'Agent lanes' },
103
+ { value: 'board', label: t('wsView.k3ko7a8011') },
104
+ { value: 'split', label: t('wsView.k3xbmo012') },
105
+ { value: 'town', label: t('wsView.k1cz0pbw013') },
106
+ ]
107
+ // 窄屏「一次一区」:检查器不占侧栏,并入切换条(四区都由同一条承载)
108
+ return narrowUI.value ? [...base, { value: 'inspector', label: t('wsView.inspector') }] : base
109
+ })
110
+ // 数字快捷键 1-5 直切视图(非输入焦点时;控制台型键盘操作与 ⌘K 面板同一取向)
111
+ const onViewKey = (ev: KeyboardEvent): void => {
112
+ // 窄屏抽屉:Esc 收起(与全站侧栏抽屉一致)
113
+ if (ev.key === 'Escape' && narrowUI.value && leftOpen.value) {
114
+ leftOpen.value = false
115
+ return
116
+ }
117
+ if (ev.metaKey || ev.ctrlKey || ev.altKey) return
118
+ const t = ev.target as HTMLElement | null
119
+ if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return
120
+ const next = VIEW_KEYS[ev.key]
121
+ if (!next || !channelId.value) return
122
+ ev.preventDefault()
123
+ view.value = next
124
+ }
125
+ onMounted(() => window.addEventListener('keydown', onViewKey))
126
+ onBeforeUnmount(() => window.removeEventListener('keydown', onViewKey))
127
+
128
+ // 侧栏折叠(现代 harness 布局:左会话栏 / 右检查器可按需收起)
129
+ // 形态差异只在挂载后生效:SSR/首帧一律按桌面结构渲染,客户端接管后才翻档,
130
+ // 否则服务端 5 项切换条 vs 客户端 6 项会触发水合告警
131
+ const mounted = ref(false)
132
+ onMounted(() => {
133
+ mounted.value = true
134
+ })
135
+ const narrowUI = computed(() => mounted.value && !isDesktop.value)
136
+
137
+ const leftOpen = ref(true)
138
+ const rightOpen = ref(true)
139
+ /** 窄屏:左右侧栏都不再占位(收成覆盖式抽屉 / 并入切换条) */
140
+ const applyTier = (desktop: boolean): void => {
141
+ leftOpen.value = desktop
142
+ rightOpen.value = desktop
143
+ // 检查器只是窄屏的第 4 区:回桌面后它回到右栏,中部得有自己的区(否则中部空白)
144
+ if (desktop && view.value === 'inspector') view.value = 'timeline'
145
+ }
146
+ onMounted(() => {
147
+ if (!isDesktop.value) applyTier(false)
148
+ })
149
+ watch(isDesktop, d => applyTier(d))
150
+ // 抽屉里选中频道后自动收起(窄屏少一次手动关闭)
151
+ watch(channelId, (next, prev) => {
152
+ if (prev !== undefined && next !== prev && narrowUI.value) leftOpen.value = false
153
+ })
154
+
155
+ // 侧栏宽度拖拽调节(PaneSplitter;localStorage 持久化,双击复位到默认值)
156
+ const LEFT_W_DEFAULT = 248
157
+ const RIGHT_W_DEFAULT = 300
158
+ const leftWidth = useStorage('aw.harness.leftW', LEFT_W_DEFAULT)
159
+ const rightWidth = useStorage('aw.harness.rightW', RIGHT_W_DEFAULT)
160
+ const resizeLeft = (d: number): void => {
161
+ leftWidth.value = Math.min(460, Math.max(220, leftWidth.value + d))
162
+ }
163
+ const resizeRight = (d: number): void => {
164
+ rightWidth.value = Math.min(560, Math.max(240, rightWidth.value - d))
165
+ }
166
+ // 初始化消毒:陈旧持久化值(超出合法范围/异常类型)夹取回默认邻域,防布局被历史脏数据撑坏
167
+ resizeLeft(0)
168
+ resizeRight(0)
169
+
170
+ // 抽屉状态(P1)
171
+ const agentDrawerOpen = ref(false)
172
+ const agentDrawerId = ref<string | null>(null)
173
+ const openAgent = (id: string): void => {
174
+ agentDrawerId.value = id
175
+ agentDrawerOpen.value = true
176
+ }
177
+ /** 活动条入口:跨 channel 的 busy 成员 → 先聚焦其 channel 再开抽屉 */
178
+ const openAgentInChannel = (target: { channelId: string, agentId: string }): void => {
179
+ wsStore.setActiveChannel(wsId.value, target.channelId)
180
+ openAgent(target.agentId)
181
+ }
182
+ /** @提及 pill 点击入口(ClusterRoute/ClusterStream inject;时间线与 lanes 全树可用) */
183
+ provide('aw:open-agent', openAgentInChannel)
184
+ const taskDrawerOpen = ref(false)
185
+ const taskDrawerId = ref<string | null>(null)
186
+ const openTask = (id: string): void => {
187
+ taskDrawerId.value = id
188
+ taskDrawerOpen.value = true
189
+ }
190
+
191
+ // ⌘K 命令面板 + A2A 调试器(P2)
192
+ const paletteOpen = ref(false)
193
+ const a2aDebugOpen = ref(false)
194
+ const composerBox = ref<HTMLElement | null>(null)
195
+ const focusComposer = (): void => {
196
+ composerBox.value?.querySelector('textarea')?.focus()
197
+ }
198
+
199
+ useHead({ title: () => `${workspace.value?.name ?? 'Workspace'} · AgentWorkShop` })
200
+ </script>
201
+
202
+ <template>
203
+ <div class="harness">
204
+ <!-- @提及悬停信息卡(文档级委托;时间线/lanes 全树生效) -->
205
+ <workshop-mention-hover-card />
206
+ <!-- 顶栏 -->
207
+ <div class="topbar">
208
+ <div
209
+ class="left"
210
+ tabindex="-1"
211
+ @keydown="onViewKey"
212
+ >
213
+ <span class="topbar-mark i-tabler-box" />
214
+ <span class="ws-name">{{ workspace?.name ?? $t('wsView.unknownWs') }}</span>
215
+ <span
216
+ v-if="channelId"
217
+ class="chan-chip"
218
+ :title="channelId"
219
+ >
220
+ <span class="chan-hash">#</span>{{ entities.channels[channelId]?.name ?? channelId.slice(0, 8) }}
221
+ </span>
222
+ <a-segmented
223
+ v-if="channelId"
224
+ v-model:value="view"
225
+ size="small"
226
+ :options="viewOptions"
227
+ class="view-switch"
228
+ :title="$t('wsView.kqwckjr001')"
229
+ @keydown="onViewKey"
230
+ />
231
+ </div>
232
+ <div class="right">
233
+ <!-- 窄屏:频道会话列表入口(覆盖式抽屉;该页一次只看一区) -->
234
+ <button
235
+ v-if="narrowUI"
236
+ class="pane-toggle im toggle-left-narrow"
237
+ :class="{ off: !leftOpen }"
238
+ :title="$t('wsView.channelListTitle')"
239
+ @click="leftOpen = !leftOpen"
240
+ >
241
+ <span class="i-tabler-list-details im-pop" />
242
+ </button>
243
+ <button
244
+ class="pane-toggle im toggle-left-desk"
245
+ :class="{ off: !leftOpen }"
246
+ :title="$t('wsView.k1tsy2e4002')"
247
+ @click="leftOpen = !leftOpen"
248
+ >
249
+ <span class="i-tabler-layout-sidebar-left-collapse im-pop" />
250
+ </button>
251
+ <button
252
+ class="pane-toggle im toggle-right-desk"
253
+ :class="{ off: !rightOpen }"
254
+ :title="$t('wsView.k1tx0ppf003')"
255
+ @click="rightOpen = !rightOpen"
256
+ >
257
+ <span class="i-tabler-layout-sidebar-right-collapse im-pop" />
258
+ </button>
259
+ <button
260
+ class="pane-toggle im"
261
+ :title="$t('wsView.a2aTitle')"
262
+ @click="a2aDebugOpen = true"
263
+ >
264
+ <span class="i-tabler-terminal-2 im-pop" />
265
+ </button>
266
+ <button
267
+ class="pane-toggle im"
268
+ :title="$t('wsView.k1cvg8sb004')"
269
+ @click="paletteOpen = true"
270
+ >
271
+ <span class="i-tabler-command im-pop" />
272
+ </button>
273
+ <span
274
+ class="dot"
275
+ :style="{ background: stateColor }"
276
+ />
277
+ <span
278
+ class="ws-state"
279
+ :data-state="conn.state"
280
+ >{{ conn.state }}</span>
281
+ <span class="seq">seq {{ lastSeq }}</span>
282
+ </div>
283
+ </div>
284
+
285
+ <!-- 主体三栏(左/右侧栏可折叠 + 拖拽调宽;分隔条 hairline 即面板边界) -->
286
+ <div class="main">
287
+ <!-- 窄屏抽屉遮罩:点空白收起(不盖住底部 Composer,输入不被"锁在"抽屉后) -->
288
+ <div
289
+ v-if="narrowUI && leftOpen"
290
+ class="drawer-scrim"
291
+ @click="leftOpen = false"
292
+ />
293
+ <div
294
+ v-if="leftOpen"
295
+ class="left-pane"
296
+ :style="{ flexBasis: narrowUI ? 'auto' : `${leftWidth}px` }"
297
+ >
298
+ <div class="left-scroll">
299
+ <workshop-channel-session-list :ws-id="wsId" />
300
+ </div>
301
+ <workshop-live-agent-bar
302
+ :ws-id="wsId"
303
+ @open-agent="openAgentInChannel"
304
+ />
305
+ </div>
306
+ <workshop-pane-splitter
307
+ v-if="leftOpen && !narrowUI"
308
+ :label="$t('wsView.k1fkfvra005')"
309
+ @resize="resizeLeft"
310
+ @reset="leftWidth = LEFT_W_DEFAULT"
311
+ />
312
+ <div class="center-pane">
313
+ <template v-if="channelId">
314
+ <workshop-transcript-timeline
315
+ v-if="view === 'timeline'"
316
+ :channel-id="channelId"
317
+ />
318
+ <workshop-agent-lanes-view
319
+ v-else-if="view === 'lanes'"
320
+ :channel-id="channelId"
321
+ />
322
+ <workshop-task-board-view
323
+ v-else-if="view === 'board'"
324
+ :channel-id="channelId"
325
+ @open-task="openTask"
326
+ />
327
+ <workshop-multi-channel-view
328
+ v-else-if="view === 'split'"
329
+ :ws-id="wsId"
330
+ @open-task="openTask"
331
+ />
332
+ <workshop-town-view
333
+ v-else-if="view === 'town'"
334
+ :channel-id="channelId"
335
+ />
336
+ <!-- 窄屏第 4 区:检查器并入切换条(桌面仍是右侧常驻栏) -->
337
+ <workshop-inspector-panel
338
+ v-else-if="view === 'inspector' && narrowUI"
339
+ :channel-id="channelId"
340
+ @open-agent="openAgent"
341
+ @open-task="openTask"
342
+ />
343
+ </template>
344
+ <div
345
+ v-else
346
+ class="pane-empty"
347
+ >
348
+ <span class="pe-icon i-tabler-messages" />
349
+ <div class="pe-title">
350
+ {{ $t('wsView.klzbtyp007') }} <span class="aw-serif-accent-italic">Channel</span> {{ $t('wsView.k3zaj4008') }}
351
+ </div>
352
+ <div class="pe-sub">
353
+ {{ $t('wsView.k1us0cxy009') }}
354
+ </div>
355
+ </div>
356
+ </div>
357
+ <workshop-pane-splitter
358
+ v-if="rightOpen && !narrowUI"
359
+ :label="$t('wsView.kwqb0st006')"
360
+ @resize="resizeRight"
361
+ @reset="rightWidth = RIGHT_W_DEFAULT"
362
+ />
363
+ <div
364
+ v-if="rightOpen && !narrowUI"
365
+ class="right-pane"
366
+ :class="{ empty: wsStore.loaded && !channelId }"
367
+ :style="{ flexBasis: `${rightWidth}px` }"
368
+ >
369
+ <workshop-inspector-panel
370
+ v-if="channelId"
371
+ :channel-id="channelId"
372
+ @open-agent="openAgent"
373
+ @open-task="openTask"
374
+ />
375
+ <!-- 加载中/无频道的诚实降级态(workspace 列表未返回前不误判为"空") -->
376
+ <div
377
+ v-else-if="!wsStore.loaded"
378
+ class="pane-loading"
379
+ >
380
+ {{ $t('wsView.loadingWs') }}
381
+ </div>
382
+ </div>
383
+ </div>
384
+
385
+ <!-- Composer -->
386
+ <div
387
+ v-if="channelId"
388
+ ref="composerBox"
389
+ class="composer-pane"
390
+ >
391
+ <workshop-composer :channel-id="channelId" />
392
+
393
+ <!-- 抽屉 -->
394
+ <workshop-agent-inspector-drawer
395
+ v-model:open="agentDrawerOpen"
396
+ :channel-id="channelId ?? ''"
397
+ :agent-id="agentDrawerId"
398
+ />
399
+ <workshop-task-inspector-drawer
400
+ v-model:open="taskDrawerOpen"
401
+ :channel-id="channelId ?? ''"
402
+ :task-id="taskDrawerId"
403
+ />
404
+ <workshop-a2a-rpc-debugger
405
+ v-model:open="a2aDebugOpen"
406
+ :channel-id="channelId ?? ''"
407
+ />
408
+
409
+ <!-- ⌘K 命令面板 -->
410
+ <workshop-command-palette
411
+ v-model:open="paletteOpen"
412
+ :ws-id="wsId"
413
+ @set-view="view = $event"
414
+ @open-a2a-debug="a2aDebugOpen = true"
415
+ @compose="focusComposer"
416
+ />
417
+ </div>
418
+ </div>
419
+ </template>
420
+
421
+ <style scoped>
422
+ .harness {
423
+ display: flex;
424
+ flex-direction: column;
425
+ height: calc(100dvh - var(--app-header-h, 56px) - var(--app-footer-h, 46px) - 16px);
426
+ min-height: 0;
427
+ margin: 0;
428
+ border: 1px solid var(--line);
429
+ border-radius: var(--radius-shell);
430
+ overflow: hidden;
431
+ background: var(--paper-raised);
432
+ box-shadow: var(--shadow-card);
433
+ }
434
+ .topbar {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ padding: 8px 14px;
439
+ font-size: 13px;
440
+ background: var(--paper-raised);
441
+ border-bottom: 1px solid var(--line);
442
+ }
443
+ .left,
444
+ .right {
445
+ display: flex;
446
+ gap: 8px;
447
+ align-items: center;
448
+ min-width: 0;
449
+ }
450
+ .topbar-mark {
451
+ font-size: 15px;
452
+ color: var(--ink-faint);
453
+ }
454
+ .ws-name {
455
+ max-width: 260px;
456
+ overflow: hidden;
457
+ font-family: var(--font-display);
458
+ font-weight: 400;
459
+ font-size: 17px;
460
+ letter-spacing: -0.01em;
461
+ text-overflow: ellipsis;
462
+ white-space: nowrap;
463
+ }
464
+ /* channel 胶囊:hairline chip + serif # */
465
+ .chan-chip {
466
+ display: inline-flex;
467
+ gap: 3px;
468
+ align-items: center;
469
+ max-width: 200px;
470
+ padding: 1px 10px;
471
+ overflow: hidden;
472
+ font-size: 12px;
473
+ color: var(--ink-soft);
474
+ text-overflow: ellipsis;
475
+ white-space: nowrap;
476
+ border: 1px solid var(--line-strong);
477
+ border-radius: var(--radius-pill);
478
+ }
479
+ .chan-hash {
480
+ font-family: var(--font-display);
481
+ color: var(--ink-faint);
482
+ }
483
+ .view-switch { margin-left: 8px; }
484
+ .dot { width: 8px; height: 8px; border-radius: 50%; }
485
+ .ws-state {
486
+ font-family: var(--font-mono);
487
+ font-size: 10px;
488
+ letter-spacing: 0.1em;
489
+ text-transform: uppercase;
490
+ padding: 1px 6px;
491
+ border: 1px solid var(--line);
492
+ border-radius: var(--radius-chip);
493
+ }
494
+ .ws-state[data-state='open'] { color: var(--tone-success-dot); border-color: color-mix(in srgb, var(--tone-success-dot) 45%, transparent); }
495
+ .ws-state[data-state='connecting'] { color: var(--tone-warning-dot); border-color: color-mix(in srgb, var(--tone-warning-dot) 45%, transparent); }
496
+ .ws-state[data-state='closed'] { color: var(--tone-danger-dot); border-color: color-mix(in srgb, var(--tone-danger-dot) 45%, transparent); }
497
+ .seq {
498
+ font-family: var(--font-mono);
499
+ font-variant-numeric: tabular-nums;
500
+ font-size: 10px;
501
+ color: var(--ink-faint);
502
+ }
503
+
504
+ /* 侧栏折叠开关:幽灵图标钮 */
505
+ .pane-toggle {
506
+ display: inline-flex;
507
+ align-items: center;
508
+ justify-content: center;
509
+ width: 26px;
510
+ height: 26px;
511
+ font-size: 14px;
512
+ color: var(--ink-soft);
513
+ cursor: pointer;
514
+ background: transparent;
515
+ border: 0;
516
+ border-radius: var(--radius-panel-sm);
517
+ transition: color var(--transition-fast), background var(--transition-fast), opacity var(--transition-fast);
518
+ }
519
+ .pane-toggle:hover {
520
+ color: var(--ink);
521
+ background: var(--paper-deep);
522
+ }
523
+ .pane-toggle.off {
524
+ opacity: 0.4;
525
+ }
526
+ .pane-toggle.off:hover {
527
+ opacity: 1;
528
+ }
529
+ .main {
530
+ display: flex;
531
+ flex: 1 1 auto;
532
+ min-height: 0;
533
+ }
534
+ .left-pane {
535
+ display: flex;
536
+ flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
537
+ flex-direction: column;
538
+ min-height: 0;
539
+ overflow: hidden;
540
+ background: var(--paper);
541
+ }
542
+ .left-scroll {
543
+ flex: 1 1 auto;
544
+ min-height: 0;
545
+ overflow: hidden auto;
546
+ }
547
+ .center-pane {
548
+ display: flex;
549
+ flex: 1 1 auto;
550
+ min-width: 0;
551
+ min-height: 0;
552
+ max-width: 100%;
553
+ overflow: hidden;
554
+ background: var(--paper); /* 灰画布:消息气泡/白色面板在此浮出(Slack 式分层) */
555
+ }
556
+ .center-pane > * {
557
+ min-width: 0;
558
+ max-width: 100%;
559
+ }
560
+ .right-pane {
561
+ flex: 0 0 auto; /* 宽度由拖拽分隔条驱动(inline flexBasis) */
562
+ min-height: 0;
563
+ overflow: hidden;
564
+ background: var(--paper);
565
+ }
566
+ .right-pane.empty { opacity: 0.35; }
567
+ /* workspace 列表加载中的诚实降级态(不算"空",不淡化整栏) */
568
+ .pane-loading {
569
+ display: flex;
570
+ align-items: center;
571
+ justify-content: center;
572
+ height: 100%;
573
+ font-size: 12px;
574
+ color: var(--ink-faint);
575
+ }
576
+ .composer-pane { flex: 0 0 auto; }
577
+
578
+ /* ══════════════════════════════════════════════════════════════════════════
579
+ 窄屏形态 · 单通道示波器(≤1023px) —— 一次只看一路信号
580
+ ──────────────────────────────────────────────────────────────────────────
581
+ 桌面是「三栏仪表台」:会话栏 248 + 画布 + 检查器 300 + 两条分隔条。
582
+ 到了 ≤1023,画布只剩 ~460px(900–1023 还要再让 64px 图标轨),
583
+ 硬塞的结果是三栏互相压扁。所以窄屏换一台仪器,而不是把仪表台缩小:
584
+
585
+ · 中部视图(时间线 / lanes / 任务板 / 检查器)→ 由顶栏切换条承载,
586
+ 一次只挂载一区,顶部切换条独占一行、可横扫;
587
+ · 频道会话列表 → 覆盖式抽屉(点遮罩/Esc/选中频道收起),不再占位;
588
+ · 拖拽分隔条在窄屏没有意义(没有第二栏可分配宽度)→ 隐藏;
589
+ · 触摸目标 ≥40px、标签 ≥11.5px(手持 30cm 距离下 10px 只剩 6px 有效字号)。
590
+
591
+ ⚠️ 断点数值与 main.css v5 / useResponsive.ts 一致(1024 = 三栏仪表台下限),
592
+ 此处不再引入新魔数。桌面(≥1024)样式完全不受影响。
593
+ ══════════════════════════════════════════════════════════════════════════ */
594
+ @media (max-width: 1023.98px) {
595
+ .harness {
596
+ /* 窄屏收掉壳体外留白,把纵向像素让给内容与底部 Composer */
597
+ height: calc(100dvh - var(--app-header-h, 56px) - var(--app-footer-h, 46px) - 8px);
598
+ }
599
+
600
+ .topbar {
601
+ flex-wrap: wrap;
602
+ gap: 6px 8px;
603
+ padding: 8px 10px;
604
+ }
605
+
606
+ .topbar .left {
607
+ row-gap: 8px;
608
+ }
609
+
610
+ .ws-name {
611
+ max-width: 40vw;
612
+ font-size: 15px;
613
+ }
614
+
615
+ .chan-chip {
616
+ max-width: 34vw;
617
+ font-size: 12px;
618
+ }
619
+
620
+ /* 切换条独占一行:五个视图名不再挤掉工作区名(channel 名仍可见) */
621
+ .view-switch {
622
+ flex: 1 1 100%;
623
+ margin-left: 0;
624
+ }
625
+
626
+ .ws-state,
627
+ .seq {
628
+ font-size: 11.5px;
629
+ }
630
+
631
+ /* 触摸目标:26px 的幽灵图标钮在手持设备上点不中 */
632
+ .pane-toggle {
633
+ width: 40px;
634
+ height: 40px;
635
+ font-size: 18px;
636
+ }
637
+
638
+ .toggle-left-desk,
639
+ .toggle-right-desk {
640
+ display: none;
641
+ }
642
+
643
+ .main {
644
+ position: relative;
645
+ }
646
+
647
+ .left-pane {
648
+ position: absolute;
649
+ top: 0;
650
+ bottom: 0;
651
+ left: 0;
652
+ z-index: 30;
653
+ width: min(86vw, 320px);
654
+ border-right: 1px solid var(--line);
655
+ box-shadow: var(--shadow-float);
656
+ animation: pane-in 0.18s cubic-bezier(0.22, 1, 0.36, 1);
657
+ }
658
+
659
+ .drawer-scrim {
660
+ position: absolute;
661
+ inset: 0;
662
+ z-index: 25;
663
+ background: color-mix(in srgb, var(--ink) 34%, transparent);
664
+ }
665
+
666
+ /* 窄屏没有第二栏可分宽度,拖拽条只会白占 9px */
667
+ .pane-splitter {
668
+ display: none;
669
+ }
670
+
671
+ .pane-loading {
672
+ font-size: 13px;
673
+ }
674
+ }
675
+
676
+ /* 窄屏专属入口在桌面不出现(响应式形态差异不用 JS 表达,避免水合抖动) */
677
+ .toggle-left-narrow {
678
+ display: none;
679
+ }
680
+
681
+ @media (max-width: 1023.98px) {
682
+ .toggle-left-narrow {
683
+ display: inline-flex;
684
+ }
685
+ }
686
+
687
+ @media (prefers-reduced-motion: reduce) {
688
+ .left-pane {
689
+ animation: none;
690
+ }
691
+ }
692
+
693
+ @keyframes pane-in {
694
+ from {
695
+ opacity: 0.5;
696
+ transform: translateX(-16px);
697
+ }
698
+
699
+ to {
700
+ opacity: 1;
701
+ transform: none;
702
+ }
703
+ }
704
+ </style>