agentworkshop 0.7.40 → 0.7.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -1,959 +1,961 @@
1
- <script setup lang="ts">
2
- import { message } from 'ant-design-vue'
3
- import type { MenuProps, SelectProps } from 'ant-design-vue'
4
- import type { AepHitlItem } from '#shared/workshop-protocol'
5
- import { useUserStore } from '@/app/stores/workshop/user'
6
- import { useWsConnectionStore } from '@/app/stores/workshop/connection'
7
- import { useHitlStore } from '@/app/stores/workshop/hitl'
8
- import { useWorkshopWs } from '@/app/composables/workshop/useWorkshopWs'
9
- import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
10
-
11
- const { t, locale, locales, setLocale } = useI18n()
12
- const store = useAppStore()
13
- const route = useRoute()
14
- const trail = useRouteTrailStore()
15
- const userStore = useUserStore()
16
- const { metaFor } = useRouteMeta()
17
-
18
- // ── HITL 全局待办(omp ask 对话框 + dcw 审批统一徽标;页头保底建连 ——
19
- // hitl 全员直推帧只达已连 peer,不建连的页面收不到提醒;快照兜底刷新前待办) ──
20
- const hitl = useHitlStore()
21
- const wsSession = useWorkshopWs()
22
- const ensureHitlLive = () => {
23
- if (userStore.isLoggedIn) wsSession.ensureConnected()
24
- }
25
- watch(() => userStore.token, (t2) => {
26
- if (t2) {
27
- ensureHitlLive()
28
- void hitl.loadSnapshot()
29
- }
30
- else hitl.clear()
31
- }, { immediate: true })
32
- const HITL_ANSWERABLE = new Set(['codex-approval', 'opencode-permission', 'dsh-permission'])
33
- const hitlKindLabel = (kind: AepHitlItem['kind']) => {
34
- switch (kind) {
35
- case 'omp-dialog': return t('appHeader.hitlKindOmp')
36
- case 'dcw-approval': return t('appHeader.hitlKindDcw')
37
- case 'codex-approval': return t('appHeader.hitlKindCodex')
38
- case 'opencode-permission': return t('appHeader.hitlKindOpencode')
39
- case 'dsh-permission': return t('appHeader.hitlKindDsh')
40
- default: return kind
41
- }
42
- }
43
- const hitlGo = (item: AepHitlItem) => {
44
- navigateTo({ path: '/monitor', query: { agentId: item.agentId, channelId: item.channelId } })
45
- }
46
- // 引擎审批(codex/opencode/dsh)页头内联应答:批准/拒绝直接回传引擎,无需进终端
47
- const hitlApi = useWorkshopApi()
48
- const hitlAnswering = ref<string | null>(null)
49
- const hitlAnswer = async (item: AepHitlItem, confirmed: boolean): Promise<void> => {
50
- hitlAnswering.value = `${item.kind}:${item.id}`
51
- try {
52
- await hitlApi.respondHitl({ kind: item.kind, id: item.id, confirmed, cancelled: !confirmed })
53
- await hitl.loadSnapshot()
54
- }
55
- catch (e) {
56
- message.error(apiErrorMessage(e))
57
- }
58
- finally {
59
- hitlAnswering.value = null
60
- }
61
- }
62
-
63
- /* 语言选择器在窄屏显示"短名":完整语言名(简体中文=56px @14px)配上
64
- * antd 给箭头预留的 24px 内距,92px 的选择器只剩 46px 文本位 ——
65
- * 实测被截成「简 体…」,这是每页都出现的破相。
66
- * 窄屏用 2 字标签,桌面保留完整语言名。 */
67
- const localeOptions = computed(() =>
68
- (locales.value as Array<{ code: string, name: string }>).map(l => ({
69
- label: isMobile.value ? l.code.split('-')[0]!.toUpperCase() : l.name,
70
- value: l.code,
71
- })),
72
- )
73
-
74
- const switchLocale: SelectProps['onChange'] = (value) => {
75
- if (value != null) {
76
- // 持久化 + 强刷:setup 期求值的词条(脚本常量)只有重载才能整体切换
77
- localStorage.setItem('aw.locale', String(value))
78
- setLocale(String(value) as 'zh-CN' | 'en')
79
- window.location.reload()
80
- }
81
- }
82
-
83
- /* ── 响应式:窄屏顶栏不是"缩小版顶栏",而是换一套信息优先级 ─────────────
84
- * 桌面:航迹页签是主角(多页并行来回切)。
85
- * 窄屏:页签轨与右侧功能簇争抢横向空间 —— 实测 390px 下页签被右侧图标
86
- * 逐个压过去,最后连用户铭牌都被推出视口(顶栏 overflow:hidden 直接裁掉)。
87
- * 所以窄屏撤掉页签轨,用**当前页标题**替代:窄屏用户要的是"我在哪",
88
- * 不是"我刚才还开过哪 5 个页面"。 */
89
- const { isDrawer, isMobile } = useResponsive()
90
-
91
- const navToggleLabel = computed(() =>
92
- isDrawer.value
93
- ? (store.mobileNavOpen ? t('header.closeNav') : t('header.openNav'))
94
- : (store.sidebarCollapsed ? t('header.expand') : t('header.collapse')))
95
-
96
- const toggleNav = () => {
97
- if (isDrawer.value) store.toggleMobileNav()
98
- else store.toggleSidebar()
99
- }
100
-
101
- const currentTitle = computed(() => metaFor(route.path).title)
102
-
103
- // ── 航迹导航:路由变化 → 记录航点;切换时进度线扫过 ──
104
- const hydrated = ref(false)
105
- const plotting = ref(false)
106
- let plotTimer: ReturnType<typeof setTimeout> | null = null
107
-
108
- watch(() => route.path, () => {
109
- trail.visit(route.path)
110
- plotting.value = false
111
- requestAnimationFrame(() => {
112
- plotting.value = true
113
- if (plotTimer) clearTimeout(plotTimer)
114
- plotTimer = setTimeout(() => {
115
- plotting.value = false
116
- }, 620)
117
- })
118
- }, { immediate: false })
119
-
120
- onMounted(() => {
121
- hydrated.value = true
122
- trail.visit(route.path)
123
- })
124
-
125
- onBeforeUnmount(() => {
126
- if (plotTimer) clearTimeout(plotTimer)
127
- })
128
-
129
- const go = (path: string) => {
130
- if (path !== route.path) {
131
- navigateTo(path)
132
- }
133
- }
134
-
135
- /**
136
- * 关闭航点标签页:
137
- * - 非当前页 → 仅从航迹移除;
138
- * - 当前页 → 先选好跳转目标(优先左侧相邻航点,其次右侧,兜底仪表盘)再移除并跳转,
139
- * 避免关闭后停留在一个已无标签的路由上(watcher 会在跳转后 visit 目标页,不会回补被关页)。
140
- */
141
- const closeWaypoint = (path: string) => {
142
- const idx = trail.remove(path)
143
- if (path !== route.path) return
144
- const rest = trail.waypoints
145
- const target = rest[Math.min(Math.max(idx - 1, 0), Math.max(rest.length - 1, 0))]?.path ?? '/'
146
- navigateTo(target)
147
- }
148
-
149
- const isFullscreen = ref(false)
150
-
151
- function toggleFullscreen() {
152
- if (!document.fullscreenElement) {
153
- document.documentElement.requestFullscreen()
154
- isFullscreen.value = true
155
- }
156
- else {
157
- document.exitFullscreen()
158
- isFullscreen.value = false
159
- }
160
- }
161
-
162
- // 用户铭牌:真实身份(workshop 用户系统;未登录 = 访客)
163
- const userInitial = computed(() => (userStore.user?.name ?? '?').trim().charAt(0).toUpperCase())
164
- const userName = computed(() => userStore.user?.name ?? t('header.guest'))
165
- const userRole = computed(() => (userStore.isLoggedIn ? userStore.user?.role ?? 'user' : 'anonymous'))
166
-
167
- // ── 实时连接状态点(全局 WS 单例的诚实在线指示;断连不假装在线) ──
168
- const conn = useWsConnectionStore()
169
- const wsVisible = computed(() => conn.state !== 'closed' || conn.lastDataAt > 0)
170
- const wsClass = computed(() => {
171
- if (conn.state === 'open') return conn.pendingReplay ? 'syncing' : 'live'
172
- if (conn.state === 'connecting') return 'syncing'
173
- return 'down'
174
- })
175
- const wsLabel = computed(() => {
176
- if (conn.state === 'open') return conn.pendingReplay ? t('appHeader.kwsdot0002') : t('appHeader.kwsdot0001')
177
- if (conn.state === 'connecting') return t('appHeader.kwsdot0003')
178
- return t('appHeader.kwsdot0004')
179
- })
180
-
181
- interface AvatarMenuEntry {
182
- key: string
183
- label: string
184
- icon?: string
185
- danger?: boolean
186
- divider?: boolean
187
- }
188
-
189
- const avatarItems = computed<AvatarMenuEntry[]>(() => {
190
- const items: AvatarMenuEntry[] = [
191
- { key: 'tokens', icon: 'i-tabler-key', label: t('menu.tokens') },
192
- { key: 'settings', icon: 'i-tabler-adjustments', label: t('menu.settings') },
193
- ]
194
- if (userStore.isLoggedIn) {
195
- items.push({ key: 'd-logout', label: '', divider: true })
196
- items.push({ key: 'logout', icon: 'i-tabler-logout', label: t('header.logout'), danger: true })
197
- }
198
- return items
199
- })
200
-
201
- const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
202
- if (key === 'settings') {
203
- navigateTo('/settings')
204
- }
205
- else if (key === 'tokens') {
206
- navigateTo('/tokens')
207
- }
208
- else if (key === 'logout') {
209
- await userStore.logout()
210
- navigateTo('/workshop')
211
- }
212
- }
213
- </script>
214
-
215
- <template>
216
- <a-layout-header
217
- class="app-header app-header-glass"
218
- >
219
- <!-- 左侧:折叠 + 航迹标绘轨 -->
220
- <div class="header-left">
221
- <button
222
- class="collapse-btn"
223
- :aria-label="navToggleLabel"
224
- :aria-expanded="isDrawer ? store.mobileNavOpen : !store.sidebarCollapsed"
225
- @click="toggleNav()"
226
- >
227
- <span class="i-tabler-menu-2" />
228
- </button>
229
-
230
- <!-- 窄屏:当前页标题(替代被撤掉的页签轨) -->
231
- <span class="mobile-title">{{ currentTitle }}</span>
232
-
233
- <span class="rail-mark i-tabler-route" />
234
-
235
- <nav
236
- class="trail"
237
- :aria-label="t('header.trail')"
238
- >
239
- <transition-group
240
- name="stamp"
241
- tag="div"
242
- class="trail-row"
243
- >
244
- <template v-if="hydrated">
245
- <template
246
- v-for="(w, i) in trail.waypoints"
247
- :key="w.path"
248
- >
249
- <span
250
- v-if="i > 0"
251
- :key="`link-${w.path}`"
252
- class="trail-link"
253
- aria-hidden="true"
254
- />
255
- <div
256
- class="trail-node"
257
- :class="{ active: w.path === route.path }"
258
- role="button"
259
- tabindex="0"
260
- :title="metaFor(w.path).title"
261
- @click="go(w.path)"
262
- @keydown.enter.prevent="go(w.path)"
263
- >
264
- <span
265
- class="node-icon"
266
- :class="metaFor(w.path).icon"
267
- />
268
- <span class="node-title">{{ metaFor(w.path).title }}</span>
269
- <button
270
- class="node-close"
271
- :aria-label="$t('appHeader.kzwjv99001', { p0: metaFor(w.path).title })"
272
- :title="$t('appHeader.kzwjv99001', { p0: metaFor(w.path).title })"
273
- @click.stop="closeWaypoint(w.path)"
274
- >
275
- <span class="i-tabler-x" />
276
- </button>
277
- </div>
278
- </template>
279
- </template>
280
- </transition-group>
281
- </nav>
282
- </div>
283
-
284
- <!-- 右侧:功能集群 -->
285
- <div class="header-right">
286
- <!-- HITL 待办铃标(有待办才出现;点击下拉 → 进入对应 agent 终端处理) -->
287
- <ClientOnly>
288
- <a-dropdown
289
- v-if="hitl.count > 0"
290
- placement="bottomRight"
291
- >
292
- <button
293
- class="icon-btn hitl-bell"
294
- :title="t('appHeader.hitlBadge')"
295
- >
296
- <span class="i-tabler-bell-ringing" />
297
- <span class="hitl-count">{{ hitl.count }}</span>
298
- </button>
299
- <template #overlay>
300
- <div class="hitl-menu">
301
- <div class="hitl-menu-title">
302
- {{ t('appHeader.hitlBadge') }} · {{ hitl.count }}
303
- </div>
304
- <button
305
- v-for="item in hitl.items"
306
- :key="`${item.kind}:${item.id}`"
307
- class="hitl-item"
308
- @click="hitlGo(item)"
309
- >
310
- <span class="hitl-item-top">
311
- <span class="hitl-item-agent">{{ item.agentName }}</span>
312
- <span class="hitl-item-kind">{{ hitlKindLabel(item.kind) }}</span>
313
- </span>
314
- <span class="hitl-item-title">{{ item.title }}</span>
315
- <span
316
- v-if="HITL_ANSWERABLE.has(item.kind)"
317
- class="hitl-item-actions"
318
- @click.stop
319
- >
320
- <a-button
321
- size="small"
322
- type="primary"
323
- :loading="hitlAnswering === `${item.kind}:${item.id}`"
324
- @click="hitlAnswer(item, true)"
325
- >
326
- {{ t('appHeader.hitlApprove') }}
327
- </a-button>
328
- <a-button
329
- size="small"
330
- danger
331
- :disabled="hitlAnswering === `${item.kind}:${item.id}`"
332
- @click="hitlAnswer(item, false)"
333
- >
334
- {{ t('appHeader.hitlReject') }}
335
- </a-button>
336
- </span>
337
- </button>
338
- </div>
339
- </template>
340
- </a-dropdown>
341
- </ClientOnly>
342
- <!-- 实时连接状态点(WS 会话全局单例;未用过 WS 的会话不显示) -->
343
- <span
344
- v-if="hydrated && wsVisible"
345
- class="ws-dot"
346
- :class="wsClass"
347
- :title="wsLabel"
348
- />
349
- <a-tooltip :title="t('header.fullscreen')">
350
- <button
351
- class="icon-btn hdr-fullscreen"
352
- @click="toggleFullscreen"
353
- >
354
- <span
355
- class="i-tabler-arrows-maximize"
356
- :class="{ hidden: isFullscreen }"
357
- />
358
- <span
359
- class="i-tabler-arrows-minimize"
360
- :class="{ hidden: !isFullscreen }"
361
- />
362
- </button>
363
- </a-tooltip>
364
-
365
- <a-select
366
- id="hdr-locale"
367
- :value="locale"
368
- size="middle"
369
- :options="localeOptions"
370
- class="lang-select"
371
- @change="switchLocale"
372
- />
373
-
374
- <a-tooltip :title="store.isDark ? t('common.light') : t('common.dark')">
375
- <button
376
- class="icon-btn"
377
- @click="store.toggleDark()"
378
- >
379
- <span
380
- class="i-tabler-sun-high"
381
- :class="{ hidden: store.isDark }"
382
- />
383
- <span
384
- class="i-tabler-moon-stars"
385
- :class="{ hidden: !store.isDark }"
386
- />
387
- </button>
388
- </a-tooltip>
389
-
390
- <a-dropdown>
391
- <div class="user-chip">
392
- <!-- 用户身份(session 异步解析)仅客户端可知:SSR 渲染中性占位,挂载后填充
393
- —— 消除 hydration mismatch(unhead dispose 噪音的根因) -->
394
- <ClientOnly>
395
- <span class="user-initial aw-avatar">{{ userInitial }}</span>
396
- <span class="user-meta">
397
- <span class="user-name">{{ userName }}</span>
398
- <span class="user-role">{{ userRole }}</span>
399
- </span>
400
- <template #fallback>
401
- <span class="user-initial aw-avatar">·</span>
402
- <span class="user-meta">
403
- <span class="user-name">·</span>
404
- <span class="user-role">·</span>
405
- </span>
406
- </template>
407
- </ClientOnly>
408
- </div>
409
- <template #overlay>
410
- <a-menu @click="onAvatarMenu">
411
- <template
412
- v-for="item in avatarItems"
413
- :key="item.key"
414
- >
415
- <a-menu-divider v-if="item.divider" />
416
- <a-menu-item
417
- v-else
418
- :key="item.key"
419
- :danger="item.danger"
420
- >
421
- <span
422
- :class="item.icon"
423
- class="mr-2"
424
- />
425
- {{ item.label }}
426
- </a-menu-item>
427
- </template>
428
- </a-menu>
429
- </template>
430
- </a-dropdown>
431
- </div>
432
-
433
- <!-- 绘图仪进度线:路由切换时自左向右扫过 -->
434
- <span
435
- class="plotter-line"
436
- :class="{ run: plotting }"
437
- aria-hidden="true"
438
- />
439
- </a-layout-header>
440
- </template>
441
-
442
- <style scoped>
443
- .app-header {
444
- position: relative;
445
- display: flex;
446
- align-items: center;
447
- justify-content: space-between;
448
- height: var(--app-header-h, 56px);
449
- padding: 0 20px 0 10px;
450
- overflow: hidden;
451
- border-bottom: 1px solid var(--divider-hair);
452
- transition: background 0.3s ease, border-color 0.3s ease;
453
- }
454
-
455
- /* 顶栏玻璃:透出极光画布;下缘发丝线 + 微暗渐变保对比
456
- * v4:与侧栏同属壳层材质(同一观察窗),底部内阴影替代原先的顶部渐变 —— 玻璃的"厚度"来自遮蔽而非压暗 */
457
- .app-header-glass {
458
- position: sticky;
459
- top: 0;
460
- z-index: 30;
461
- background: var(--mat-chrome-bg);
462
- backdrop-filter: var(--vibrancy-chrome);
463
- border-bottom: 1px solid var(--glass-line);
464
- box-shadow: var(--glass-specular);
465
- }
466
-
467
- .header-left,
468
- .header-right {
469
- display: flex;
470
- align-items: center;
471
- gap: 10px;
472
- min-width: 0;
473
- }
474
-
475
- /* ── 航迹标绘轨 ── */
476
- .rail-mark {
477
- flex: 0 0 auto;
478
- font-size: 15px;
479
- color: var(--ink-faint);
480
- }
481
-
482
- .trail {
483
- flex: 0 1 auto;
484
- min-width: 0;
485
- overflow-x: auto;
486
- overflow-y: hidden;
487
- scrollbar-width: none;
488
- -webkit-mask-image: linear-gradient(90deg, transparent 0, black 8px, black calc(100% - 18px), transparent 100%);
489
- mask-image: linear-gradient(90deg, transparent 0, black 8px, black calc(100% - 18px), transparent 100%);
490
- }
491
-
492
- .trail::-webkit-scrollbar {
493
- display: none;
494
- }
495
-
496
- .trail-row {
497
- display: flex;
498
- align-items: center;
499
- gap: 0;
500
- padding: 3px 4px;
501
- white-space: nowrap;
502
- }
503
-
504
- /* 航点间细线:同色系 hairline */
505
- .trail-link {
506
- flex: 0 0 auto;
507
- width: 14px;
508
- height: 1px;
509
- background: var(--line-strong);
510
- }
511
-
512
- /* 航点:chip 圆角描边,悬停抬亮,当前页墨色填充(ink pill) */
513
- .trail-node {
514
- position: relative;
515
- display: inline-flex;
516
- flex: 0 0 auto;
517
- gap: 6px;
518
- align-items: center;
519
- height: 32px;
520
- padding: 0 12px;
521
- font-family: var(--font-body);
522
- font-size: 12px;
523
- font-weight: 500;
524
- letter-spacing: 0.01em;
525
- color: var(--app-text, var(--ink-soft));
526
- cursor: pointer;
527
- background: var(--paper-deep);
528
- border: 1px solid var(--line);
529
- border-radius: var(--radius-pill);
530
- transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
531
- }
532
-
533
- .trail-node:hover {
534
- color: var(--ink);
535
- background: var(--paper-tint);
536
- border-color: var(--line-strong);
537
- }
538
-
539
- .trail-node:active {
540
- transform: scale(0.98);
541
- }
542
-
543
- .trail-node:focus-visible {
544
- outline: 2px solid var(--accent);
545
- outline-offset: 2px;
546
- }
547
-
548
- .trail-node.active {
549
- color: var(--on-accent);
550
- background: var(--accent);
551
- border-color: var(--accent);
552
- }
553
-
554
- .node-icon {
555
- font-size: 13px;
556
- }
557
-
558
- .node-title {
559
- max-width: 132px;
560
- overflow: hidden;
561
- letter-spacing: 0.02em;
562
- text-overflow: ellipsis;
563
- white-space: nowrap;
564
- transition: max-width 0.18s var(--ease-out-quart), margin 0.18s var(--ease-out-quart), opacity 0.18s var(--ease-out-quart);
565
- }
566
-
567
- /* 标签关闭钮:槽位常驻(零布局抖动),悬停标签时图标淡入展开(浏览器页签交互);
568
- * 当前页签(朱砂底)用白色保证对比 */
569
- .node-close {
570
- display: inline-flex;
571
- flex: 0 0 auto;
572
- align-items: center;
573
- justify-content: center;
574
- width: 16px;
575
- height: 16px;
576
- margin-left: 4px;
577
- font-size: 12px;
578
- color: var(--ink-faint);
579
- cursor: pointer;
580
- background: transparent;
581
- border: none;
582
- border-radius: var(--radius-chip);
583
- opacity: 0;
584
- transform: scale(0.6);
585
- transition:
586
- opacity 0.18s var(--ease-out-quart),
587
- transform 0.18s var(--ease-out-quart),
588
- color var(--transition-fast),
589
- background var(--transition-fast);
590
- }
591
-
592
- .trail-node:hover .node-close,
593
- .trail-node:focus-within .node-close {
594
- opacity: 1;
595
- transform: none;
596
- }
597
-
598
- .node-close:hover {
599
- color: var(--ink);
600
- background: var(--hover-tint);
601
- }
602
-
603
- .node-close:active {
604
- transform: scale(0.9);
605
- }
606
-
607
- .trail-node.active .node-close {
608
- color: color-mix(in srgb, var(--on-accent) 85%, transparent);
609
- }
610
-
611
- .trail-node.active .node-close:hover {
612
- color: var(--on-accent);
613
- background: color-mix(in srgb, var(--on-accent) 18%, transparent);
614
- }
615
-
616
- .node-close:focus-visible {
617
- outline: 2px solid var(--accent);
618
- outline-offset: 1px;
619
- }
620
-
621
- /* 航点进场:轻抬升淡入 */
622
- .stamp-enter-active {
623
- transition: opacity 0.22s var(--ease-out-quart), transform 0.22s var(--ease-out-quart);
624
- }
625
-
626
- .stamp-enter-from {
627
- opacity: 0;
628
- transform: translateY(4px);
629
- }
630
-
631
- .stamp-leave-active {
632
- position: absolute;
633
- transition: opacity 0.14s ease;
634
- }
635
-
636
- .stamp-leave-to {
637
- opacity: 0;
638
- }
639
-
640
- .stamp-move {
641
- transition: transform 0.22s ease;
642
- }
643
-
644
- /* 路由切换进度线:墨色细扫过(替换原天青→蜜桃粉彩渐变,色锁:温灰下仅墨色动线) */
645
- .plotter-line {
646
- position: absolute;
647
- bottom: -1px;
648
- left: 0;
649
- width: 100%;
650
- height: 2px;
651
- background: linear-gradient(90deg, transparent, var(--ink-fainter) 30%, var(--accent));
652
- opacity: 0;
653
- pointer-events: none;
654
- transform: scaleX(0);
655
- transform-origin: left center;
656
- }
657
-
658
- .plotter-line.run {
659
- animation: plot-sweep 0.6s cubic-bezier(0.3, 0.8, 0.4, 1) forwards;
660
- }
661
-
662
- @keyframes plot-sweep {
663
- 0% {
664
- transform: scaleX(0);
665
- opacity: 0.9;
666
- }
667
-
668
- 70% {
669
- opacity: 0.9;
670
- }
671
-
672
- 100% {
673
- transform: scaleX(1);
674
- opacity: 0;
675
- }
676
- }
677
-
678
- /* 幽灵图标钮:无描边,悬停浮 surface(open-tag tp-close 声部) */
679
- .collapse-btn,
680
- .icon-btn {
681
- display: inline-flex;
682
- flex: 0 0 auto;
683
- align-items: center;
684
- justify-content: center;
685
- width: 34px;
686
- height: 34px;
687
- font-size: 16px;
688
- color: var(--app-text, var(--ink-soft));
689
- cursor: pointer;
690
- background: transparent;
691
- border: 0;
692
- border-radius: var(--radius-panel-sm);
693
- transition:
694
- background var(--transition-fast),
695
- color var(--transition-fast),
696
- transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
697
- }
698
-
699
- .collapse-btn:hover,
700
- .icon-btn:hover {
701
- color: var(--ink);
702
- background: var(--paper-deep);
703
- }
704
-
705
- .collapse-btn:active,
706
- .icon-btn:active {
707
- transform: scale(0.94);
708
- }
709
-
710
- .lang-select {
711
- width: 118px;
712
- }
713
-
714
- /* 语言选择器:令牌驱动(主题失配时 antd 兜底色会露白,这里显式钉死) */
715
- .lang-select :deep(.ant-select-selector) {
716
- color: var(--app-text, var(--ink));
717
- background: transparent !important;
718
- border-color: var(--app-border, var(--line)) !important;
719
- }
720
-
721
- .lang-select :deep(.ant-select-arrow) {
722
- color: var(--app-text-secondary, var(--ink-faint));
723
- }
724
-
725
- /* 操作者铭牌:头像 + 双行 */
726
- .user-chip {
727
- display: flex;
728
- align-items: center;
729
- gap: 10px;
730
- height: 42px;
731
- margin-left: 4px;
732
- padding: 0 12px 0 8px;
733
- cursor: pointer;
734
- border: 1px solid transparent;
735
- border-radius: var(--radius-pill);
736
- transition: border-color 0.16s ease, background 0.16s ease;
737
- }
738
-
739
- .user-chip:hover {
740
- border-color: var(--line);
741
- background: var(--hover-tint);
742
- }
743
-
744
- .user-initial {
745
- width: 28px;
746
- height: 28px;
747
- font-size: 12px;
748
- }
749
-
750
- .user-meta {
751
- display: flex;
752
- flex-direction: column;
753
- line-height: 1.15;
754
- }
755
-
756
- .user-name {
757
- font-size: 12.5px;
758
- font-weight: 600;
759
- color: var(--app-text, var(--ink));
760
- letter-spacing: 0.01em;
761
- }
762
-
763
- .user-role {
764
- font-family: var(--font-mono);
765
- /* 8.5px 是"看得见读不了"的下限之外(实测常驻被审计标红);
766
- 角色是身份信息,不是装饰刻度,抬到 9.5px 仍保持铭牌声部 */
767
- font-size: 9.5px;
768
- letter-spacing: 0.14em;
769
- text-transform: uppercase;
770
- color: var(--app-text-secondary, var(--ink-faint));
771
- }
772
-
773
- /* 移动端当前页标题:桌面不存在(那里有页签轨) */
774
- .mobile-title {
775
- display: none;
776
- min-width: 0;
777
- overflow: hidden;
778
- font-size: 14px;
779
- font-weight: 600;
780
- color: var(--app-text, var(--ink));
781
- text-overflow: ellipsis;
782
- white-space: nowrap;
783
- }
784
-
785
- /* ══ 顶栏响应式:按信息优先级逐级卸载,而不是让它们互相挤压 ═══════════════
786
- * 卸载顺序(从最可省到最不可省):页签轨 → 全屏钮 → 用户双行铭牌 → 语言文字。
787
- * 留下的核心是:HITL 铃标(有待办必须看得见)、连接状态点、主题、语言、身份。 */
788
- @media (max-width: 899px) {
789
- .rail-mark,
790
- .trail {
791
- display: none;
792
- }
793
-
794
- .mobile-title {
795
- display: block;
796
- margin-left: 2px;
797
- }
798
-
799
- .app-header {
800
- padding: 0 12px 0 8px;
801
- }
802
- }
803
-
804
- @media (max-width: 639px) {
805
- .hdr-fullscreen {
806
- display: none;
807
- }
808
-
809
- .user-chip {
810
- height: 38px;
811
- padding: 0 6px;
812
- }
813
-
814
- .user-meta {
815
- display: none;
816
- }
817
-
818
- /* 窄屏标签已换成 2 字符(ZH / EN),选择器只需容纳标签 + 箭头 + 内距 */
819
- .lang-select {
820
- width: 76px;
821
- }
822
-
823
- .lang-select :deep(.ant-select-selection-item) {
824
- padding-inline-end: 16px;
825
- font-size: 12.5px;
826
- }
827
-
828
- .header-left,
829
- .header-right {
830
- gap: 4px;
831
- }
832
- }
833
-
834
- .hidden {
835
- display: none;
836
- }
837
-
838
- /* 实时连接状态点(诚实在线:open=绿/syncing=琥珀呼吸/closed=红;reduced-motion 全局收敛) */
839
- .ws-dot {
840
- flex: none;
841
- width: 8px;
842
- height: 8px;
843
- margin: 0 2px;
844
- border-radius: 50%;
845
- background: var(--tone-success-dot);
846
- box-shadow: 0 0 6px color-mix(in srgb, var(--tone-success-dot) 55%, transparent);
847
- }
848
- .ws-dot.syncing {
849
- background: var(--tone-warning-dot);
850
- box-shadow: 0 0 6px color-mix(in srgb, var(--tone-warning-dot) 55%, transparent);
851
- animation: ws-pulse 1.2s ease-in-out infinite;
852
- }
853
- .ws-dot.down {
854
- background: var(--tone-danger-dot);
855
- box-shadow: 0 0 6px color-mix(in srgb, var(--tone-danger-dot) 55%, transparent);
856
- }
857
- @keyframes ws-pulse {
858
- 50% { opacity: 0.45; }
859
- }
860
-
861
- /* ── HITL 待办铃标(琥珀警示;角标数字极简,无装饰堆砌) ── */
862
- .hitl-bell {
863
- position: relative;
864
- color: var(--tone-warning-dot, #d4a017);
865
- }
866
- .hitl-count {
867
- position: absolute;
868
- top: 2px;
869
- right: 2px;
870
- min-width: 15px;
871
- height: 15px;
872
- padding: 0 4px;
873
- font-family: var(--font-mono);
874
- font-size: 9.5px;
875
- font-weight: 700;
876
- line-height: 15px;
877
- color: var(--on-accent, #fff);
878
- text-align: center;
879
- background: var(--tone-danger-dot, #e05252);
880
- border-radius: 8px;
881
- }
882
- .hitl-menu {
883
- min-width: 260px;
884
- max-width: 340px;
885
- padding: 6px;
886
- background: var(--paper, #fff);
887
- border: 1px solid var(--line);
888
- border-radius: var(--radius-panel-sm, 10px);
889
- box-shadow: 0 10px 32px rgba(0, 0, 0, 0.14);
890
- }
891
- .hitl-menu-title {
892
- padding: 6px 10px;
893
- font-size: 11px;
894
- font-weight: 600;
895
- letter-spacing: 0.08em;
896
- color: var(--ink-faint);
897
- border-bottom: 1px solid var(--line);
898
- }
899
- .hitl-item {
900
- display: flex;
901
- flex-direction: column;
902
- gap: 3px;
903
- width: 100%;
904
- margin-top: 4px;
905
- padding: 8px 10px;
906
- text-align: left;
907
- cursor: pointer;
908
- background: transparent;
909
- border: 1px solid transparent;
910
- border-radius: var(--radius-panel-sm, 8px);
911
- transition: background var(--transition-fast), border-color var(--transition-fast);
912
- }
913
- .hitl-item:hover {
914
- background: var(--paper-deep);
915
- border-color: var(--line);
916
- }
917
- .hitl-item-top {
918
- display: flex;
919
- gap: 8px;
920
- align-items: baseline;
921
- justify-content: space-between;
922
- }
923
- .hitl-item-agent {
924
- font-size: 12.5px;
925
- font-weight: 600;
926
- color: var(--ink);
927
- }
928
- .hitl-item-kind {
929
- font-family: var(--font-mono);
930
- font-size: 9.5px;
931
- letter-spacing: 0.1em;
932
- color: var(--tone-warning-dot, #d4a017);
933
- }
934
- .hitl-item-title {
935
- overflow: hidden;
936
- font-size: 12px;
937
- color: var(--ink-soft);
938
- text-overflow: ellipsis;
939
- white-space: nowrap;
940
- }
941
- .hitl-item-actions {
942
- display: flex;
943
- gap: 6px;
944
- margin-top: 6px;
945
- }
946
-
947
- @media (prefers-reduced-motion: reduce) {
948
- .plotter-line.run {
949
- animation: none;
950
- opacity: 0;
951
- }
952
-
953
- .stamp-enter-active,
954
- .stamp-leave-active,
955
- .stamp-move {
956
- transition: none;
957
- }
958
- }
959
- </style>
1
+ <script setup lang="ts">
2
+ import { message } from 'ant-design-vue'
3
+ import type { MenuProps, SelectProps } from 'ant-design-vue'
4
+ import type { AepHitlItem } from '#shared/workshop-protocol'
5
+ import { useUserStore } from '@/app/stores/workshop/user'
6
+ import { useWsConnectionStore } from '@/app/stores/workshop/connection'
7
+ import { useHitlStore } from '@/app/stores/workshop/hitl'
8
+ import { useWorkshopWs } from '@/app/composables/workshop/useWorkshopWs'
9
+ import { useWorkshopApi } from '@/app/composables/workshop/useWorkshopApi'
10
+
11
+ const { t, locale, locales, setLocale } = useI18n()
12
+ const store = useAppStore()
13
+ const route = useRoute()
14
+ const trail = useRouteTrailStore()
15
+ const userStore = useUserStore()
16
+ const { metaFor } = useRouteMeta()
17
+
18
+ // ── HITL 全局待办(omp ask 对话框 + dcw 审批统一徽标;页头保底建连 ——
19
+ // hitl 全员直推帧只达已连 peer,不建连的页面收不到提醒;快照兜底刷新前待办) ──
20
+ const hitl = useHitlStore()
21
+ const wsSession = useWorkshopWs()
22
+ const ensureHitlLive = () => {
23
+ if (userStore.isLoggedIn) wsSession.ensureConnected()
24
+ }
25
+ watch(() => userStore.token, (t2) => {
26
+ if (t2) {
27
+ ensureHitlLive()
28
+ void hitl.loadSnapshot()
29
+ }
30
+ else hitl.clear()
31
+ }, { immediate: true })
32
+ const HITL_ANSWERABLE = new Set(['codex-approval', 'opencode-permission', 'dsh-permission'])
33
+ const hitlKindLabel = (kind: AepHitlItem['kind']) => {
34
+ switch (kind) {
35
+ case 'omp-dialog': return t('appHeader.hitlKindOmp')
36
+ case 'dcw-approval': return t('appHeader.hitlKindDcw')
37
+ case 'codex-approval': return t('appHeader.hitlKindCodex')
38
+ case 'opencode-permission': return t('appHeader.hitlKindOpencode')
39
+ case 'dsh-permission': return t('appHeader.hitlKindDsh')
40
+ default: return kind
41
+ }
42
+ }
43
+ const hitlGo = (item: AepHitlItem) => {
44
+ navigateTo({ path: '/monitor', query: { agentId: item.agentId, channelId: item.channelId } })
45
+ }
46
+ // 引擎审批(codex/opencode/dsh)页头内联应答:批准/拒绝直接回传引擎,无需进终端
47
+ const hitlApi = useWorkshopApi()
48
+ const hitlAnswering = ref<string | null>(null)
49
+ const hitlAnswer = async (item: AepHitlItem, confirmed: boolean): Promise<void> => {
50
+ hitlAnswering.value = `${item.kind}:${item.id}`
51
+ try {
52
+ await hitlApi.respondHitl({ kind: item.kind, id: item.id, confirmed, cancelled: !confirmed })
53
+ await hitl.loadSnapshot()
54
+ }
55
+ catch (e) {
56
+ message.error(apiErrorMessage(e))
57
+ }
58
+ finally {
59
+ hitlAnswering.value = null
60
+ }
61
+ }
62
+
63
+ /* 语言选择器在窄屏显示"短名":完整语言名(简体中文=56px @14px)配上
64
+ * antd 给箭头预留的 24px 内距,92px 的选择器只剩 46px 文本位 ——
65
+ * 实测被截成「简 体…」,这是每页都出现的破相。
66
+ * 窄屏用 2 字标签,桌面保留完整语言名。 */
67
+ const localeOptions = computed(() =>
68
+ (locales.value as Array<{ code: string, name: string }>).map(l => ({
69
+ label: isMobile.value ? l.code.split('-')[0]!.toUpperCase() : l.name,
70
+ value: l.code,
71
+ })),
72
+ )
73
+
74
+ const switchLocale: SelectProps['onChange'] = (value) => {
75
+ if (value != null) {
76
+ // 持久化 + 强刷:setup 期求值的词条(脚本常量)只有重载才能整体切换
77
+ // cookie 参与 SSR 首帧(locale-cookie.global.ts);localStorage 保留为插件回退
78
+ useCookie('aw.locale', { maxAge: 60 * 60 * 24 * 365 }).value = String(value)
79
+ localStorage.setItem('aw.locale', String(value))
80
+ setLocale(String(value) as 'zh-CN' | 'en')
81
+ window.location.reload()
82
+ }
83
+ }
84
+
85
+ /* ── 响应式:窄屏顶栏不是"缩小版顶栏",而是换一套信息优先级 ─────────────
86
+ * 桌面:航迹页签是主角(多页并行来回切)。
87
+ * 窄屏:页签轨与右侧功能簇争抢横向空间 —— 实测 390px 下页签被右侧图标
88
+ * 逐个压过去,最后连用户铭牌都被推出视口(顶栏 overflow:hidden 直接裁掉)。
89
+ * 所以窄屏撤掉页签轨,用**当前页标题**替代:窄屏用户要的是"我在哪",
90
+ * 不是"我刚才还开过哪 5 个页面"。 */
91
+ const { isDrawer, isMobile } = useResponsive()
92
+
93
+ const navToggleLabel = computed(() =>
94
+ isDrawer.value
95
+ ? (store.mobileNavOpen ? t('header.closeNav') : t('header.openNav'))
96
+ : (store.sidebarCollapsed ? t('header.expand') : t('header.collapse')))
97
+
98
+ const toggleNav = () => {
99
+ if (isDrawer.value) store.toggleMobileNav()
100
+ else store.toggleSidebar()
101
+ }
102
+
103
+ const currentTitle = computed(() => metaFor(route.path).title)
104
+
105
+ // ── 航迹导航:路由变化 → 记录航点;切换时进度线扫过 ──
106
+ const hydrated = ref(false)
107
+ const plotting = ref(false)
108
+ let plotTimer: ReturnType<typeof setTimeout> | null = null
109
+
110
+ watch(() => route.path, () => {
111
+ trail.visit(route.path)
112
+ plotting.value = false
113
+ requestAnimationFrame(() => {
114
+ plotting.value = true
115
+ if (plotTimer) clearTimeout(plotTimer)
116
+ plotTimer = setTimeout(() => {
117
+ plotting.value = false
118
+ }, 620)
119
+ })
120
+ }, { immediate: false })
121
+
122
+ onMounted(() => {
123
+ hydrated.value = true
124
+ trail.visit(route.path)
125
+ })
126
+
127
+ onBeforeUnmount(() => {
128
+ if (plotTimer) clearTimeout(plotTimer)
129
+ })
130
+
131
+ const go = (path: string) => {
132
+ if (path !== route.path) {
133
+ navigateTo(path)
134
+ }
135
+ }
136
+
137
+ /**
138
+ * 关闭航点标签页:
139
+ * - 非当前页 → 仅从航迹移除;
140
+ * - 当前页 → 先选好跳转目标(优先左侧相邻航点,其次右侧,兜底仪表盘)再移除并跳转,
141
+ * 避免关闭后停留在一个已无标签的路由上(watcher 会在跳转后 visit 目标页,不会回补被关页)。
142
+ */
143
+ const closeWaypoint = (path: string) => {
144
+ const idx = trail.remove(path)
145
+ if (path !== route.path) return
146
+ const rest = trail.waypoints
147
+ const target = rest[Math.min(Math.max(idx - 1, 0), Math.max(rest.length - 1, 0))]?.path ?? '/'
148
+ navigateTo(target)
149
+ }
150
+
151
+ const isFullscreen = ref(false)
152
+
153
+ function toggleFullscreen() {
154
+ if (!document.fullscreenElement) {
155
+ document.documentElement.requestFullscreen()
156
+ isFullscreen.value = true
157
+ }
158
+ else {
159
+ document.exitFullscreen()
160
+ isFullscreen.value = false
161
+ }
162
+ }
163
+
164
+ // 用户铭牌:真实身份(workshop 用户系统;未登录 = 访客)
165
+ const userInitial = computed(() => (userStore.user?.name ?? '?').trim().charAt(0).toUpperCase())
166
+ const userName = computed(() => userStore.user?.name ?? t('header.guest'))
167
+ const userRole = computed(() => (userStore.isLoggedIn ? userStore.user?.role ?? 'user' : 'anonymous'))
168
+
169
+ // ── 实时连接状态点(全局 WS 单例的诚实在线指示;断连不假装在线) ──
170
+ const conn = useWsConnectionStore()
171
+ const wsVisible = computed(() => conn.state !== 'closed' || conn.lastDataAt > 0)
172
+ const wsClass = computed(() => {
173
+ if (conn.state === 'open') return conn.pendingReplay ? 'syncing' : 'live'
174
+ if (conn.state === 'connecting') return 'syncing'
175
+ return 'down'
176
+ })
177
+ const wsLabel = computed(() => {
178
+ if (conn.state === 'open') return conn.pendingReplay ? t('appHeader.kwsdot0002') : t('appHeader.kwsdot0001')
179
+ if (conn.state === 'connecting') return t('appHeader.kwsdot0003')
180
+ return t('appHeader.kwsdot0004')
181
+ })
182
+
183
+ interface AvatarMenuEntry {
184
+ key: string
185
+ label: string
186
+ icon?: string
187
+ danger?: boolean
188
+ divider?: boolean
189
+ }
190
+
191
+ const avatarItems = computed<AvatarMenuEntry[]>(() => {
192
+ const items: AvatarMenuEntry[] = [
193
+ { key: 'tokens', icon: 'i-tabler-key', label: t('menu.tokens') },
194
+ { key: 'settings', icon: 'i-tabler-adjustments', label: t('menu.settings') },
195
+ ]
196
+ if (userStore.isLoggedIn) {
197
+ items.push({ key: 'd-logout', label: '', divider: true })
198
+ items.push({ key: 'logout', icon: 'i-tabler-logout', label: t('header.logout'), danger: true })
199
+ }
200
+ return items
201
+ })
202
+
203
+ const onAvatarMenu: MenuProps['onClick'] = async ({ key }) => {
204
+ if (key === 'settings') {
205
+ navigateTo('/settings')
206
+ }
207
+ else if (key === 'tokens') {
208
+ navigateTo('/tokens')
209
+ }
210
+ else if (key === 'logout') {
211
+ await userStore.logout()
212
+ navigateTo('/workshop')
213
+ }
214
+ }
215
+ </script>
216
+
217
+ <template>
218
+ <a-layout-header
219
+ class="app-header app-header-glass"
220
+ >
221
+ <!-- 左侧:折叠 + 航迹标绘轨 -->
222
+ <div class="header-left">
223
+ <button
224
+ class="collapse-btn"
225
+ :aria-label="navToggleLabel"
226
+ :aria-expanded="isDrawer ? store.mobileNavOpen : !store.sidebarCollapsed"
227
+ @click="toggleNav()"
228
+ >
229
+ <span class="i-tabler-menu-2" />
230
+ </button>
231
+
232
+ <!-- 窄屏:当前页标题(替代被撤掉的页签轨) -->
233
+ <span class="mobile-title">{{ currentTitle }}</span>
234
+
235
+ <span class="rail-mark i-tabler-route" />
236
+
237
+ <nav
238
+ class="trail"
239
+ :aria-label="t('header.trail')"
240
+ >
241
+ <transition-group
242
+ name="stamp"
243
+ tag="div"
244
+ class="trail-row"
245
+ >
246
+ <template v-if="hydrated">
247
+ <template
248
+ v-for="(w, i) in trail.waypoints"
249
+ :key="w.path"
250
+ >
251
+ <span
252
+ v-if="i > 0"
253
+ :key="`link-${w.path}`"
254
+ class="trail-link"
255
+ aria-hidden="true"
256
+ />
257
+ <div
258
+ class="trail-node"
259
+ :class="{ active: w.path === route.path }"
260
+ role="button"
261
+ tabindex="0"
262
+ :title="metaFor(w.path).title"
263
+ @click="go(w.path)"
264
+ @keydown.enter.prevent="go(w.path)"
265
+ >
266
+ <span
267
+ class="node-icon"
268
+ :class="metaFor(w.path).icon"
269
+ />
270
+ <span class="node-title">{{ metaFor(w.path).title }}</span>
271
+ <button
272
+ class="node-close"
273
+ :aria-label="$t('appHeader.kzwjv99001', { p0: metaFor(w.path).title })"
274
+ :title="$t('appHeader.kzwjv99001', { p0: metaFor(w.path).title })"
275
+ @click.stop="closeWaypoint(w.path)"
276
+ >
277
+ <span class="i-tabler-x" />
278
+ </button>
279
+ </div>
280
+ </template>
281
+ </template>
282
+ </transition-group>
283
+ </nav>
284
+ </div>
285
+
286
+ <!-- 右侧:功能集群 -->
287
+ <div class="header-right">
288
+ <!-- HITL 待办铃标(有待办才出现;点击下拉 → 进入对应 agent 终端处理) -->
289
+ <ClientOnly>
290
+ <a-dropdown
291
+ v-if="hitl.count > 0"
292
+ placement="bottomRight"
293
+ >
294
+ <button
295
+ class="icon-btn hitl-bell"
296
+ :title="t('appHeader.hitlBadge')"
297
+ >
298
+ <span class="i-tabler-bell-ringing" />
299
+ <span class="hitl-count">{{ hitl.count }}</span>
300
+ </button>
301
+ <template #overlay>
302
+ <div class="hitl-menu">
303
+ <div class="hitl-menu-title">
304
+ {{ t('appHeader.hitlBadge') }} · {{ hitl.count }}
305
+ </div>
306
+ <button
307
+ v-for="item in hitl.items"
308
+ :key="`${item.kind}:${item.id}`"
309
+ class="hitl-item"
310
+ @click="hitlGo(item)"
311
+ >
312
+ <span class="hitl-item-top">
313
+ <span class="hitl-item-agent">{{ item.agentName }}</span>
314
+ <span class="hitl-item-kind">{{ hitlKindLabel(item.kind) }}</span>
315
+ </span>
316
+ <span class="hitl-item-title">{{ item.title }}</span>
317
+ <span
318
+ v-if="HITL_ANSWERABLE.has(item.kind)"
319
+ class="hitl-item-actions"
320
+ @click.stop
321
+ >
322
+ <a-button
323
+ size="small"
324
+ type="primary"
325
+ :loading="hitlAnswering === `${item.kind}:${item.id}`"
326
+ @click="hitlAnswer(item, true)"
327
+ >
328
+ {{ t('appHeader.hitlApprove') }}
329
+ </a-button>
330
+ <a-button
331
+ size="small"
332
+ danger
333
+ :disabled="hitlAnswering === `${item.kind}:${item.id}`"
334
+ @click="hitlAnswer(item, false)"
335
+ >
336
+ {{ t('appHeader.hitlReject') }}
337
+ </a-button>
338
+ </span>
339
+ </button>
340
+ </div>
341
+ </template>
342
+ </a-dropdown>
343
+ </ClientOnly>
344
+ <!-- 实时连接状态点(WS 会话全局单例;未用过 WS 的会话不显示) -->
345
+ <span
346
+ v-if="hydrated && wsVisible"
347
+ class="ws-dot"
348
+ :class="wsClass"
349
+ :title="wsLabel"
350
+ />
351
+ <a-tooltip :title="t('header.fullscreen')">
352
+ <button
353
+ class="icon-btn hdr-fullscreen"
354
+ @click="toggleFullscreen"
355
+ >
356
+ <span
357
+ class="i-tabler-arrows-maximize"
358
+ :class="{ hidden: isFullscreen }"
359
+ />
360
+ <span
361
+ class="i-tabler-arrows-minimize"
362
+ :class="{ hidden: !isFullscreen }"
363
+ />
364
+ </button>
365
+ </a-tooltip>
366
+
367
+ <a-select
368
+ id="hdr-locale"
369
+ :value="locale"
370
+ size="middle"
371
+ :options="localeOptions"
372
+ class="lang-select"
373
+ @change="switchLocale"
374
+ />
375
+
376
+ <a-tooltip :title="store.isDark ? t('common.light') : t('common.dark')">
377
+ <button
378
+ class="icon-btn"
379
+ @click="store.toggleDark()"
380
+ >
381
+ <span
382
+ class="i-tabler-sun-high"
383
+ :class="{ hidden: store.isDark }"
384
+ />
385
+ <span
386
+ class="i-tabler-moon-stars"
387
+ :class="{ hidden: !store.isDark }"
388
+ />
389
+ </button>
390
+ </a-tooltip>
391
+
392
+ <a-dropdown>
393
+ <div class="user-chip">
394
+ <!-- 用户身份(session 异步解析)仅客户端可知:SSR 渲染中性占位,挂载后填充
395
+ —— 消除 hydration mismatch(unhead dispose 噪音的根因) -->
396
+ <ClientOnly>
397
+ <span class="user-initial aw-avatar">{{ userInitial }}</span>
398
+ <span class="user-meta">
399
+ <span class="user-name">{{ userName }}</span>
400
+ <span class="user-role">{{ userRole }}</span>
401
+ </span>
402
+ <template #fallback>
403
+ <span class="user-initial aw-avatar">·</span>
404
+ <span class="user-meta">
405
+ <span class="user-name">·</span>
406
+ <span class="user-role">·</span>
407
+ </span>
408
+ </template>
409
+ </ClientOnly>
410
+ </div>
411
+ <template #overlay>
412
+ <a-menu @click="onAvatarMenu">
413
+ <template
414
+ v-for="item in avatarItems"
415
+ :key="item.key"
416
+ >
417
+ <a-menu-divider v-if="item.divider" />
418
+ <a-menu-item
419
+ v-else
420
+ :key="item.key"
421
+ :danger="item.danger"
422
+ >
423
+ <span
424
+ :class="item.icon"
425
+ class="mr-2"
426
+ />
427
+ {{ item.label }}
428
+ </a-menu-item>
429
+ </template>
430
+ </a-menu>
431
+ </template>
432
+ </a-dropdown>
433
+ </div>
434
+
435
+ <!-- 绘图仪进度线:路由切换时自左向右扫过 -->
436
+ <span
437
+ class="plotter-line"
438
+ :class="{ run: plotting }"
439
+ aria-hidden="true"
440
+ />
441
+ </a-layout-header>
442
+ </template>
443
+
444
+ <style scoped>
445
+ .app-header {
446
+ position: relative;
447
+ display: flex;
448
+ align-items: center;
449
+ justify-content: space-between;
450
+ height: var(--app-header-h, 56px);
451
+ padding: 0 20px 0 10px;
452
+ overflow: hidden;
453
+ border-bottom: 1px solid var(--divider-hair);
454
+ transition: background 0.3s ease, border-color 0.3s ease;
455
+ }
456
+
457
+ /* 顶栏玻璃:透出极光画布;下缘发丝线 + 微暗渐变保对比
458
+ * v4:与侧栏同属壳层材质(同一观察窗),底部内阴影替代原先的顶部渐变 —— 玻璃的"厚度"来自遮蔽而非压暗 */
459
+ .app-header-glass {
460
+ position: sticky;
461
+ top: 0;
462
+ z-index: 30;
463
+ background: var(--mat-chrome-bg);
464
+ backdrop-filter: var(--vibrancy-chrome);
465
+ border-bottom: 1px solid var(--glass-line);
466
+ box-shadow: var(--glass-specular);
467
+ }
468
+
469
+ .header-left,
470
+ .header-right {
471
+ display: flex;
472
+ align-items: center;
473
+ gap: 10px;
474
+ min-width: 0;
475
+ }
476
+
477
+ /* ── 航迹标绘轨 ── */
478
+ .rail-mark {
479
+ flex: 0 0 auto;
480
+ font-size: 15px;
481
+ color: var(--ink-faint);
482
+ }
483
+
484
+ .trail {
485
+ flex: 0 1 auto;
486
+ min-width: 0;
487
+ overflow-x: auto;
488
+ overflow-y: hidden;
489
+ scrollbar-width: none;
490
+ -webkit-mask-image: linear-gradient(90deg, transparent 0, black 8px, black calc(100% - 18px), transparent 100%);
491
+ mask-image: linear-gradient(90deg, transparent 0, black 8px, black calc(100% - 18px), transparent 100%);
492
+ }
493
+
494
+ .trail::-webkit-scrollbar {
495
+ display: none;
496
+ }
497
+
498
+ .trail-row {
499
+ display: flex;
500
+ align-items: center;
501
+ gap: 0;
502
+ padding: 3px 4px;
503
+ white-space: nowrap;
504
+ }
505
+
506
+ /* 航点间细线:同色系 hairline */
507
+ .trail-link {
508
+ flex: 0 0 auto;
509
+ width: 14px;
510
+ height: 1px;
511
+ background: var(--line-strong);
512
+ }
513
+
514
+ /* 航点:chip 圆角描边,悬停抬亮,当前页墨色填充(ink pill) */
515
+ .trail-node {
516
+ position: relative;
517
+ display: inline-flex;
518
+ flex: 0 0 auto;
519
+ gap: 6px;
520
+ align-items: center;
521
+ height: 32px;
522
+ padding: 0 12px;
523
+ font-family: var(--font-body);
524
+ font-size: 12px;
525
+ font-weight: 500;
526
+ letter-spacing: 0.01em;
527
+ color: var(--app-text, var(--ink-soft));
528
+ cursor: pointer;
529
+ background: var(--paper-deep);
530
+ border: 1px solid var(--line);
531
+ border-radius: var(--radius-pill);
532
+ transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast);
533
+ }
534
+
535
+ .trail-node:hover {
536
+ color: var(--ink);
537
+ background: var(--paper-tint);
538
+ border-color: var(--line-strong);
539
+ }
540
+
541
+ .trail-node:active {
542
+ transform: scale(0.98);
543
+ }
544
+
545
+ .trail-node:focus-visible {
546
+ outline: 2px solid var(--accent);
547
+ outline-offset: 2px;
548
+ }
549
+
550
+ .trail-node.active {
551
+ color: var(--on-accent);
552
+ background: var(--accent);
553
+ border-color: var(--accent);
554
+ }
555
+
556
+ .node-icon {
557
+ font-size: 13px;
558
+ }
559
+
560
+ .node-title {
561
+ max-width: 132px;
562
+ overflow: hidden;
563
+ letter-spacing: 0.02em;
564
+ text-overflow: ellipsis;
565
+ white-space: nowrap;
566
+ transition: max-width 0.18s var(--ease-out-quart), margin 0.18s var(--ease-out-quart), opacity 0.18s var(--ease-out-quart);
567
+ }
568
+
569
+ /* 标签关闭钮:槽位常驻(零布局抖动),悬停标签时图标淡入展开(浏览器页签交互);
570
+ * 当前页签(朱砂底)用白色保证对比 */
571
+ .node-close {
572
+ display: inline-flex;
573
+ flex: 0 0 auto;
574
+ align-items: center;
575
+ justify-content: center;
576
+ width: 16px;
577
+ height: 16px;
578
+ margin-left: 4px;
579
+ font-size: 12px;
580
+ color: var(--ink-faint);
581
+ cursor: pointer;
582
+ background: transparent;
583
+ border: none;
584
+ border-radius: var(--radius-chip);
585
+ opacity: 0;
586
+ transform: scale(0.6);
587
+ transition:
588
+ opacity 0.18s var(--ease-out-quart),
589
+ transform 0.18s var(--ease-out-quart),
590
+ color var(--transition-fast),
591
+ background var(--transition-fast);
592
+ }
593
+
594
+ .trail-node:hover .node-close,
595
+ .trail-node:focus-within .node-close {
596
+ opacity: 1;
597
+ transform: none;
598
+ }
599
+
600
+ .node-close:hover {
601
+ color: var(--ink);
602
+ background: var(--hover-tint);
603
+ }
604
+
605
+ .node-close:active {
606
+ transform: scale(0.9);
607
+ }
608
+
609
+ .trail-node.active .node-close {
610
+ color: color-mix(in srgb, var(--on-accent) 85%, transparent);
611
+ }
612
+
613
+ .trail-node.active .node-close:hover {
614
+ color: var(--on-accent);
615
+ background: color-mix(in srgb, var(--on-accent) 18%, transparent);
616
+ }
617
+
618
+ .node-close:focus-visible {
619
+ outline: 2px solid var(--accent);
620
+ outline-offset: 1px;
621
+ }
622
+
623
+ /* 航点进场:轻抬升淡入 */
624
+ .stamp-enter-active {
625
+ transition: opacity 0.22s var(--ease-out-quart), transform 0.22s var(--ease-out-quart);
626
+ }
627
+
628
+ .stamp-enter-from {
629
+ opacity: 0;
630
+ transform: translateY(4px);
631
+ }
632
+
633
+ .stamp-leave-active {
634
+ position: absolute;
635
+ transition: opacity 0.14s ease;
636
+ }
637
+
638
+ .stamp-leave-to {
639
+ opacity: 0;
640
+ }
641
+
642
+ .stamp-move {
643
+ transition: transform 0.22s ease;
644
+ }
645
+
646
+ /* 路由切换进度线:墨色细扫过(替换原天青→蜜桃粉彩渐变,色锁:温灰下仅墨色动线) */
647
+ .plotter-line {
648
+ position: absolute;
649
+ bottom: -1px;
650
+ left: 0;
651
+ width: 100%;
652
+ height: 2px;
653
+ background: linear-gradient(90deg, transparent, var(--ink-fainter) 30%, var(--accent));
654
+ opacity: 0;
655
+ pointer-events: none;
656
+ transform: scaleX(0);
657
+ transform-origin: left center;
658
+ }
659
+
660
+ .plotter-line.run {
661
+ animation: plot-sweep 0.6s cubic-bezier(0.3, 0.8, 0.4, 1) forwards;
662
+ }
663
+
664
+ @keyframes plot-sweep {
665
+ 0% {
666
+ transform: scaleX(0);
667
+ opacity: 0.9;
668
+ }
669
+
670
+ 70% {
671
+ opacity: 0.9;
672
+ }
673
+
674
+ 100% {
675
+ transform: scaleX(1);
676
+ opacity: 0;
677
+ }
678
+ }
679
+
680
+ /* 幽灵图标钮:无描边,悬停浮 surface(open-tag tp-close 声部) */
681
+ .collapse-btn,
682
+ .icon-btn {
683
+ display: inline-flex;
684
+ flex: 0 0 auto;
685
+ align-items: center;
686
+ justify-content: center;
687
+ width: 34px;
688
+ height: 34px;
689
+ font-size: 16px;
690
+ color: var(--app-text, var(--ink-soft));
691
+ cursor: pointer;
692
+ background: transparent;
693
+ border: 0;
694
+ border-radius: var(--radius-panel-sm);
695
+ transition:
696
+ background var(--transition-fast),
697
+ color var(--transition-fast),
698
+ transform 160ms cubic-bezier(0.22, 1, 0.36, 1);
699
+ }
700
+
701
+ .collapse-btn:hover,
702
+ .icon-btn:hover {
703
+ color: var(--ink);
704
+ background: var(--paper-deep);
705
+ }
706
+
707
+ .collapse-btn:active,
708
+ .icon-btn:active {
709
+ transform: scale(0.94);
710
+ }
711
+
712
+ .lang-select {
713
+ width: 118px;
714
+ }
715
+
716
+ /* 语言选择器:令牌驱动(主题失配时 antd 兜底色会露白,这里显式钉死) */
717
+ .lang-select :deep(.ant-select-selector) {
718
+ color: var(--app-text, var(--ink));
719
+ background: transparent !important;
720
+ border-color: var(--app-border, var(--line)) !important;
721
+ }
722
+
723
+ .lang-select :deep(.ant-select-arrow) {
724
+ color: var(--app-text-secondary, var(--ink-faint));
725
+ }
726
+
727
+ /* 操作者铭牌:头像 + 双行 */
728
+ .user-chip {
729
+ display: flex;
730
+ align-items: center;
731
+ gap: 10px;
732
+ height: 42px;
733
+ margin-left: 4px;
734
+ padding: 0 12px 0 8px;
735
+ cursor: pointer;
736
+ border: 1px solid transparent;
737
+ border-radius: var(--radius-pill);
738
+ transition: border-color 0.16s ease, background 0.16s ease;
739
+ }
740
+
741
+ .user-chip:hover {
742
+ border-color: var(--line);
743
+ background: var(--hover-tint);
744
+ }
745
+
746
+ .user-initial {
747
+ width: 28px;
748
+ height: 28px;
749
+ font-size: 12px;
750
+ }
751
+
752
+ .user-meta {
753
+ display: flex;
754
+ flex-direction: column;
755
+ line-height: 1.15;
756
+ }
757
+
758
+ .user-name {
759
+ font-size: 12.5px;
760
+ font-weight: 600;
761
+ color: var(--app-text, var(--ink));
762
+ letter-spacing: 0.01em;
763
+ }
764
+
765
+ .user-role {
766
+ font-family: var(--font-mono);
767
+ /* 8.5px 是"看得见读不了"的下限之外(实测常驻被审计标红);
768
+ 角色是身份信息,不是装饰刻度,抬到 9.5px 仍保持铭牌声部 */
769
+ font-size: 9.5px;
770
+ letter-spacing: 0.14em;
771
+ text-transform: uppercase;
772
+ color: var(--app-text-secondary, var(--ink-faint));
773
+ }
774
+
775
+ /* 移动端当前页标题:桌面不存在(那里有页签轨) */
776
+ .mobile-title {
777
+ display: none;
778
+ min-width: 0;
779
+ overflow: hidden;
780
+ font-size: 14px;
781
+ font-weight: 600;
782
+ color: var(--app-text, var(--ink));
783
+ text-overflow: ellipsis;
784
+ white-space: nowrap;
785
+ }
786
+
787
+ /* ══ 顶栏响应式:按信息优先级逐级卸载,而不是让它们互相挤压 ═══════════════
788
+ * 卸载顺序(从最可省到最不可省):页签轨 → 全屏钮 → 用户双行铭牌 → 语言文字。
789
+ * 留下的核心是:HITL 铃标(有待办必须看得见)、连接状态点、主题、语言、身份。 */
790
+ @media (max-width: 899px) {
791
+ .rail-mark,
792
+ .trail {
793
+ display: none;
794
+ }
795
+
796
+ .mobile-title {
797
+ display: block;
798
+ margin-left: 2px;
799
+ }
800
+
801
+ .app-header {
802
+ padding: 0 12px 0 8px;
803
+ }
804
+ }
805
+
806
+ @media (max-width: 639px) {
807
+ .hdr-fullscreen {
808
+ display: none;
809
+ }
810
+
811
+ .user-chip {
812
+ height: 38px;
813
+ padding: 0 6px;
814
+ }
815
+
816
+ .user-meta {
817
+ display: none;
818
+ }
819
+
820
+ /* 窄屏标签已换成 2 字符(ZH / EN),选择器只需容纳标签 + 箭头 + 内距 */
821
+ .lang-select {
822
+ width: 76px;
823
+ }
824
+
825
+ .lang-select :deep(.ant-select-selection-item) {
826
+ padding-inline-end: 16px;
827
+ font-size: 12.5px;
828
+ }
829
+
830
+ .header-left,
831
+ .header-right {
832
+ gap: 4px;
833
+ }
834
+ }
835
+
836
+ .hidden {
837
+ display: none;
838
+ }
839
+
840
+ /* 实时连接状态点(诚实在线:open=绿/syncing=琥珀呼吸/closed=红;reduced-motion 全局收敛) */
841
+ .ws-dot {
842
+ flex: none;
843
+ width: 8px;
844
+ height: 8px;
845
+ margin: 0 2px;
846
+ border-radius: 50%;
847
+ background: var(--tone-success-dot);
848
+ box-shadow: 0 0 6px color-mix(in srgb, var(--tone-success-dot) 55%, transparent);
849
+ }
850
+ .ws-dot.syncing {
851
+ background: var(--tone-warning-dot);
852
+ box-shadow: 0 0 6px color-mix(in srgb, var(--tone-warning-dot) 55%, transparent);
853
+ animation: ws-pulse 1.2s ease-in-out infinite;
854
+ }
855
+ .ws-dot.down {
856
+ background: var(--tone-danger-dot);
857
+ box-shadow: 0 0 6px color-mix(in srgb, var(--tone-danger-dot) 55%, transparent);
858
+ }
859
+ @keyframes ws-pulse {
860
+ 50% { opacity: 0.45; }
861
+ }
862
+
863
+ /* ── HITL 待办铃标(琥珀警示;角标数字极简,无装饰堆砌) ── */
864
+ .hitl-bell {
865
+ position: relative;
866
+ color: var(--tone-warning-dot, #d4a017);
867
+ }
868
+ .hitl-count {
869
+ position: absolute;
870
+ top: 2px;
871
+ right: 2px;
872
+ min-width: 15px;
873
+ height: 15px;
874
+ padding: 0 4px;
875
+ font-family: var(--font-mono);
876
+ font-size: 9.5px;
877
+ font-weight: 700;
878
+ line-height: 15px;
879
+ color: var(--on-accent, #fff);
880
+ text-align: center;
881
+ background: var(--tone-danger-dot, #e05252);
882
+ border-radius: 8px;
883
+ }
884
+ .hitl-menu {
885
+ min-width: 260px;
886
+ max-width: 340px;
887
+ padding: 6px;
888
+ background: var(--paper, #fff);
889
+ border: 1px solid var(--line);
890
+ border-radius: var(--radius-panel-sm, 10px);
891
+ box-shadow: 0 10px 32px rgba(0, 0, 0, 0.14);
892
+ }
893
+ .hitl-menu-title {
894
+ padding: 6px 10px;
895
+ font-size: 11px;
896
+ font-weight: 600;
897
+ letter-spacing: 0.08em;
898
+ color: var(--ink-faint);
899
+ border-bottom: 1px solid var(--line);
900
+ }
901
+ .hitl-item {
902
+ display: flex;
903
+ flex-direction: column;
904
+ gap: 3px;
905
+ width: 100%;
906
+ margin-top: 4px;
907
+ padding: 8px 10px;
908
+ text-align: left;
909
+ cursor: pointer;
910
+ background: transparent;
911
+ border: 1px solid transparent;
912
+ border-radius: var(--radius-panel-sm, 8px);
913
+ transition: background var(--transition-fast), border-color var(--transition-fast);
914
+ }
915
+ .hitl-item:hover {
916
+ background: var(--paper-deep);
917
+ border-color: var(--line);
918
+ }
919
+ .hitl-item-top {
920
+ display: flex;
921
+ gap: 8px;
922
+ align-items: baseline;
923
+ justify-content: space-between;
924
+ }
925
+ .hitl-item-agent {
926
+ font-size: 12.5px;
927
+ font-weight: 600;
928
+ color: var(--ink);
929
+ }
930
+ .hitl-item-kind {
931
+ font-family: var(--font-mono);
932
+ font-size: 9.5px;
933
+ letter-spacing: 0.1em;
934
+ color: var(--tone-warning-dot, #d4a017);
935
+ }
936
+ .hitl-item-title {
937
+ overflow: hidden;
938
+ font-size: 12px;
939
+ color: var(--ink-soft);
940
+ text-overflow: ellipsis;
941
+ white-space: nowrap;
942
+ }
943
+ .hitl-item-actions {
944
+ display: flex;
945
+ gap: 6px;
946
+ margin-top: 6px;
947
+ }
948
+
949
+ @media (prefers-reduced-motion: reduce) {
950
+ .plotter-line.run {
951
+ animation: none;
952
+ opacity: 0;
953
+ }
954
+
955
+ .stamp-enter-active,
956
+ .stamp-leave-active,
957
+ .stamp-move {
958
+ transition: none;
959
+ }
960
+ }
961
+ </style>