agentworkshop 0.7.39 → 0.7.41

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 (405) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +55 -0
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +55 -0
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/88GOowgO2.js +1 -0
  5. package/.output/public/_nuxt/{BNT8gYU92.js → BC9wjTJK2.js} +2 -2
  6. package/.output/public/_nuxt/{CY9LbPCI.js → BN16aRdQ.js} +1 -1
  7. package/.output/public/_nuxt/BWVhZntO.js +1 -0
  8. package/.output/public/_nuxt/{HBLJ-wei.js → BXxH0UQE.js} +1 -1
  9. package/.output/public/_nuxt/{zIGh7R49.js → BneN7fbu.js} +1 -1
  10. package/.output/public/_nuxt/BsROmY89.js +1 -0
  11. package/.output/public/_nuxt/{DUrecWPx.js → C0ZrS1KL.js} +2 -2
  12. package/.output/public/_nuxt/{C_OLzD09.js → C4oW1HsE.js} +1 -1
  13. package/.output/public/_nuxt/{CUDwzgLV.js → C4s_niy-.js} +9 -9
  14. package/.output/public/_nuxt/CFxtLwjp.js +1 -0
  15. package/.output/public/_nuxt/{Cec0Ua3F.js → CS50LB_l.js} +1 -1
  16. package/.output/public/_nuxt/CssgYAAL2.js +1 -0
  17. package/.output/public/_nuxt/D6mbFA-a.js +1 -0
  18. package/.output/public/_nuxt/D98NH8kG.js +1 -0
  19. package/.output/public/_nuxt/DGIuvyRN2.js +1 -0
  20. package/.output/public/_nuxt/DGbd-UiG.js +1 -0
  21. package/.output/public/_nuxt/{CojCTLKD.js → DGj-WEBg.js} +1 -1
  22. package/.output/public/_nuxt/{DDShH71m.js → DS8qj8EL.js} +1 -1
  23. package/.output/public/_nuxt/DTFCy6h-.js +1 -0
  24. package/.output/public/_nuxt/{DrYV5i68.js → DUjW5Yq5.js} +1 -1
  25. package/.output/public/_nuxt/{45zuyAso.js → D_OTvQza.js} +1 -1
  26. package/.output/public/_nuxt/Dap5PmTL.js +2 -0
  27. package/.output/public/_nuxt/Dqw1lpr-.js +1 -0
  28. package/.output/public/_nuxt/DvLxILQP.js +1 -0
  29. package/.output/public/_nuxt/IREZ1eC2.js +1 -0
  30. package/.output/public/_nuxt/TownView.f-YOdXRs.css +1 -0
  31. package/.output/public/_nuxt/_id_.D9ymtHB5.css +1 -0
  32. package/.output/public/_nuxt/_id_.jf-_8E_c.css +1 -0
  33. package/.output/public/_nuxt/_wsId_.B4WiqkIS.css +1 -0
  34. package/.output/public/_nuxt/agents.ZD0rVdHE.css +1 -0
  35. package/.output/public/_nuxt/aml.BN8ZEW4h.css +1 -0
  36. package/.output/public/_nuxt/builds/latest.json +1 -1
  37. package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +1 -0
  38. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  39. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +1 -0
  40. package/.output/public/_nuxt/daq.DjSgY94l.css +1 -0
  41. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +1 -0
  42. package/.output/public/_nuxt/default.J32SgqFY.css +1 -0
  43. package/.output/public/_nuxt/entry.bdm0tv6M.css +1 -0
  44. package/.output/public/_nuxt/eqq3wWQF.js +18 -0
  45. package/.output/public/_nuxt/logs.BpKzmKsh.css +1 -0
  46. package/.output/public/_nuxt/monitor.D9pe7qN6.css +1 -0
  47. package/.output/public/_nuxt/pages.D7aSDlUI.css +1 -0
  48. package/.output/public/_nuxt/permissions.DwTVlFe6.css +1 -0
  49. package/.output/public/_nuxt/plugins.sb4nDGqr.css +1 -0
  50. package/.output/public/_nuxt/{Cbq-mryP.js → qr6R6Ha1.js} +1 -1
  51. package/.output/public/_nuxt/settings.BTgR-NiE.css +1 -0
  52. package/.output/public/_nuxt/teams.CxnxKVVs.css +1 -0
  53. package/.output/public/_nuxt/tokens.BFY7qv4f.css +1 -0
  54. package/.output/public/_nuxt/{town.DKIAhKnx.css → town.BBXZmeoP.css} +1 -1
  55. package/.output/public/_nuxt/users.DLgZ1D0h.css +1 -0
  56. package/.output/public/_nuxt/workshop.CR_QzeAN.css +1 -0
  57. package/.output/public/_nuxt/{BzHEilTt.js → xh3_vYcu.js} +1 -1
  58. package/.output/public/_nuxt/yF0aYMF1.js +4 -0
  59. package/.output/server/chunks/_/entity.mjs.map +1 -1
  60. package/.output/server/chunks/_/nitro.mjs +2355 -1362
  61. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  62. package/.output/server/chunks/build/{TownView-jVazYOjN.mjs → TownView--otsnr_7.mjs} +290 -208
  63. package/.output/server/chunks/build/TownView--otsnr_7.mjs.map +1 -0
  64. package/.output/server/chunks/build/{_id_-CfVosNg3.mjs → _id_-Brv-JSvr.mjs} +142 -141
  65. package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +1 -0
  66. package/.output/server/chunks/build/{_id_-Hwr4rZpr.mjs → _id_-D9pl1OZy.mjs} +38 -36
  67. package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +1 -0
  68. package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs → _wsId_-BASfDf0s.mjs} +295 -261
  69. package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs.map → _wsId_-BASfDf0s.mjs.map} +1 -1
  70. package/.output/server/chunks/build/{agents-Bvin73QY.mjs → agents-CLQqJF2y.mjs} +21 -27
  71. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +1 -0
  72. package/.output/server/chunks/build/{aml-BCSJCdX7.mjs → aml-_7FdFL2p.mjs} +109 -109
  73. package/.output/server/chunks/build/{aml-BCSJCdX7.mjs.map → aml-_7FdFL2p.mjs.map} +1 -1
  74. package/.output/server/chunks/build/api-error-dul30BYQ.mjs.map +1 -1
  75. package/.output/server/chunks/build/{channel-templates-BTq8570n.mjs → channel-templates-p6SZNjlM.mjs} +19 -19
  76. package/.output/server/chunks/build/{channel-templates-BTq8570n.mjs.map → channel-templates-p6SZNjlM.mjs.map} +1 -1
  77. package/.output/server/chunks/build/{daq-C03Uu5UE.mjs → daq-DkMvD8sg.mjs} +193 -126
  78. package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +1 -0
  79. package/.output/server/chunks/build/{dcw-6vEYVeje.mjs → dcw-C1jgVJkO.mjs} +46 -46
  80. package/.output/server/chunks/build/{dcw-6vEYVeje.mjs.map → dcw-C1jgVJkO.mjs.map} +1 -1
  81. package/.output/server/chunks/build/{default-CZmZFpqH.mjs → default-BE4r0FZN.mjs} +152 -58
  82. package/.output/server/chunks/build/{default-CZmZFpqH.mjs.map → default-BE4r0FZN.mjs.map} +1 -1
  83. package/.output/server/chunks/build/{logs-B-xhuqln.mjs → logs--dByNyhw.mjs} +35 -35
  84. package/.output/server/chunks/build/{logs-B-xhuqln.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  85. package/.output/server/chunks/build/{monitor-CyfDt4nR.mjs → monitor-C0okQaXz.mjs} +33 -33
  86. package/.output/server/chunks/build/{monitor-CyfDt4nR.mjs.map → monitor-C0okQaXz.mjs.map} +1 -1
  87. package/.output/server/chunks/build/{pages-BO7yZsXA.mjs → pages-B0bUWnC6.mjs} +35 -19
  88. package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +1 -0
  89. package/.output/server/chunks/build/{permissions-B52oOmaO.mjs → permissions-68SsNjex.mjs} +12 -12
  90. package/.output/server/chunks/build/{permissions-B52oOmaO.mjs.map → permissions-68SsNjex.mjs.map} +1 -1
  91. package/.output/server/chunks/build/{plugins-CUWmMdyt.mjs → plugins-lGlUr1lj.mjs} +17 -17
  92. package/.output/server/chunks/build/{plugins-CUWmMdyt.mjs.map → plugins-lGlUr1lj.mjs.map} +1 -1
  93. package/.output/server/chunks/build/{settings-DoQ2Oq3P.mjs → settings-Ddd1qx2Z.mjs} +50 -47
  94. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +1 -0
  95. package/.output/server/chunks/build/{teams-2kz3CusY.mjs → teams-MEDLZdMn.mjs} +38 -34
  96. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +1 -0
  97. package/.output/server/chunks/build/{tokens-BbWLqtVz.mjs → tokens-C7fTM5C2.mjs} +14 -14
  98. package/.output/server/chunks/build/{tokens-BbWLqtVz.mjs.map → tokens-C7fTM5C2.mjs.map} +1 -1
  99. package/.output/server/chunks/build/{town-BOl6hs9B.mjs → town-CoQdblbZ.mjs} +38 -12
  100. package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +1 -0
  101. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  102. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  103. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  104. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  105. package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs +49 -0
  106. package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs.map +1 -0
  107. package/.output/server/chunks/build/{users-fohSmF-J.mjs → users-Cfd5-l2-.mjs} +6 -6
  108. package/.output/server/chunks/build/{users-fohSmF-J.mjs.map → users-Cfd5-l2-.mjs.map} +1 -1
  109. package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs → workshop-BL5bhfs0.mjs} +15 -15
  110. package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs.map → workshop-BL5bhfs0.mjs.map} +1 -1
  111. package/.output/server/chunks/build/{workspaces-BuydxM-3.mjs → workspaces-Bgz5Ekwp.mjs} +18 -3
  112. package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs.map +1 -0
  113. package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs.map +1 -1
  114. package/.output/server/chunks/routes/api/system/index.patch.mjs.map +1 -1
  115. package/.output/server/chunks/routes/api/system/index.post.mjs.map +1 -1
  116. package/.output/server/chunks/routes/api/system/settings/events.get.mjs.map +1 -1
  117. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +6 -2
  118. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs.map +1 -1
  119. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +2 -1
  120. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs.map +1 -1
  121. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  123. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs.map +1 -1
  124. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs.map +1 -1
  125. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs.map +1 -1
  126. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +3 -2
  127. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs.map +1 -1
  128. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +2 -1
  129. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs.map +1 -1
  130. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +8 -6
  131. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs.map +1 -1
  132. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +5 -5
  133. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs.map +1 -1
  134. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +14 -4
  135. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs.map +1 -1
  136. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +4 -4
  137. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs.map +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +4 -3
  139. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs.map +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +31 -0
  141. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs.map +1 -0
  142. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +28 -0
  143. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs.map +1 -0
  144. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  146. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs.map +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +32 -0
  148. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs.map +1 -0
  149. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +34 -0
  150. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs.map +1 -0
  151. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +33 -0
  152. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs.map +1 -0
  153. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +34 -0
  154. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs.map +1 -0
  155. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  163. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  164. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +5 -1
  165. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  166. package/.output/server/chunks/routes/api/workshop/index9.get.mjs +3 -1
  167. package/.output/server/chunks/routes/api/workshop/index9.get.mjs.map +1 -1
  168. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  173. package/.output/server/chunks/routes/renderer.mjs +3 -3
  174. package/.output/server/chunks/virtual/entry.mjs +51 -25
  175. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  176. package/.output/server/package.json +1 -1
  177. package/README-zh.md +203 -39
  178. package/README.md +739 -591
  179. package/app/app.vue +9 -3
  180. package/app/assets/css/main.css +517 -7
  181. package/app/components/AppHeader.vue +98 -7
  182. package/app/components/AppSidebar.vue +117 -11
  183. package/app/components/AwChart.vue +2 -1
  184. package/app/components/workshop/AgentInspectorDrawer.vue +6 -6
  185. package/app/components/workshop/AgentLanesView.vue +52 -1
  186. package/app/components/workshop/AssetLibrary.vue +4 -2
  187. package/app/components/workshop/ChannelSessionList.vue +4 -2
  188. package/app/components/workshop/Composer.vue +96 -0
  189. package/app/components/workshop/DaqNodeRow.vue +19 -0
  190. package/app/components/workshop/DaqTemplateIcon.vue +33 -0
  191. package/app/components/workshop/DeviceTwinPanel.vue +37 -35
  192. package/app/components/workshop/EventBlock.vue +3 -3
  193. package/app/components/workshop/InspectorPanel.vue +47 -11
  194. package/app/components/workshop/LiveAgentBar.vue +34 -0
  195. package/app/components/workshop/TaskBoardView.vue +83 -33
  196. package/app/components/workshop/TranscriptTimeline.vue +56 -0
  197. package/app/components/workshop/blocks/ClusterError.vue +1 -1
  198. package/app/components/workshop/blocks/ClusterRoute.vue +3 -1
  199. package/app/components/workshop/blocks/ClusterStatus.vue +2 -2
  200. package/app/components/workshop/blocks/ClusterStream.vue +8 -2
  201. package/app/components/workshop/town/ModelPreview3D.vue +1 -1
  202. package/app/components/workshop/town/TownView.vue +507 -82
  203. package/app/composables/useResponsive.ts +88 -0
  204. package/app/composables/useRouteMeta.ts +3 -0
  205. package/app/composables/workshop/useDaqStream.ts +7 -4
  206. package/app/composables/workshop/useDcwStream.ts +8 -2
  207. package/app/composables/workshop/useVisibleInterval.ts +2 -2
  208. package/app/composables/workshop/useWorkshopApi.ts +1 -1
  209. package/app/layouts/default.vue +46 -2
  210. package/app/pages/aml/index.vue +712 -616
  211. package/app/pages/daq/[id].vue +12 -7
  212. package/app/pages/daq/index.vue +216 -13
  213. package/app/pages/dcw/[id].vue +117 -32
  214. package/app/pages/dcw/index.vue +83 -23
  215. package/app/pages/index.vue +35 -1
  216. package/app/pages/logs/index.vue +147 -13
  217. package/app/pages/monitor.vue +56 -12
  218. package/app/pages/permissions/index.vue +34 -0
  219. package/app/pages/plugins/index.vue +35 -4
  220. package/app/pages/settings.vue +90 -21
  221. package/app/pages/tokens.vue +23 -0
  222. package/app/pages/town.vue +50 -5
  223. package/app/pages/users.vue +23 -0
  224. package/app/pages/workshop/agents.vue +92 -2
  225. package/app/pages/workshop/channel-templates.vue +53 -0
  226. package/app/pages/workshop/index.vue +14 -0
  227. package/app/pages/workshop/teams.vue +66 -6
  228. package/app/pages/workshop/w/[wsId].vue +202 -14
  229. package/app/plugins/aw-plugins.client.ts +13 -8
  230. package/app/stores/app.ts +33 -1
  231. package/app/stores/workshop/user.ts +1 -1
  232. package/app/stores/workshop/workspaces.ts +18 -2
  233. package/app/utils/api-error.ts +1 -1
  234. package/cli/commands/build.mjs +8 -1
  235. package/docker-compose.yml +6 -0
  236. package/docs/cli.en.md +2 -2
  237. package/docs/cli.md +2 -2
  238. package/docs/plugins.en.md +33 -1
  239. package/docs/plugins.md +29 -1
  240. package/docs/sdk.en.md +1 -1
  241. package/docs/sdk.md +1 -1
  242. package/i18n/locales/en.ts +2 -0
  243. package/i18n/locales/zh-CN.ts +2 -0
  244. package/nuxt.config.ts +31 -0
  245. package/package.json +11 -2
  246. package/scripts/_dbg-param-map-e2e.ts +242 -0
  247. package/scripts/_dbg-param-map-rest-smoke.mjs +73 -0
  248. package/scripts/_dbg-param-real-scenario.mjs +153 -0
  249. package/scripts/_loop-biax-campaign.mjs +124 -0
  250. package/scripts/api-live-e2e.mjs +40 -8
  251. package/scripts/capture-walkthrough.mjs +232 -0
  252. package/scripts/render-walkthrough-figure.mjs +46 -0
  253. package/scripts/seed-walkthrough-fixture.mjs +143 -0
  254. package/scripts/test-plugin-hardening.mjs +8 -0
  255. package/scripts/test-serial-bridge.mjs +418 -0
  256. package/scripts/ui/admin-shots.mjs +46 -0
  257. package/scripts/ui/audit-layout.mjs +301 -0
  258. package/scripts/ui/bump-microfont.mjs +39 -0
  259. package/scripts/ui/e2e-interactive.mjs +328 -0
  260. package/scripts/ui/lib.mjs +198 -0
  261. package/scripts/ui/linkcheck2.mjs +37 -0
  262. package/scripts/ui/live-render-check.mjs +72 -0
  263. package/scripts/ui/md-lint.mjs +80 -0
  264. package/scripts/ui/perf-audit.mjs +132 -0
  265. package/scripts/ui/readme-assets.mjs +30 -0
  266. package/scripts/ui/readme-shots.mjs +42 -0
  267. package/scripts/ui/record.mjs +225 -0
  268. package/scripts/ui/seed-admin.mjs +68 -0
  269. package/scripts/ui/seed-demo.mjs +83 -0
  270. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  271. package/scripts/ui/shot-hero.mjs +25 -0
  272. package/scripts/ui/shots.mjs +75 -0
  273. package/scripts/ui/sim-ui-shot.mjs +16 -0
  274. package/scripts/ui/verify-nav.mjs +51 -0
  275. package/scripts/ui/verify-responsive.mjs +285 -0
  276. package/server/api/system/config-groups/[id].patch.ts +1 -1
  277. package/server/api/system/config-groups/index.post.ts +1 -1
  278. package/server/api/system/settings/events.get.ts +1 -1
  279. package/server/api/system/settings/index.patch.ts +1 -1
  280. package/server/api/workshop/agent-tools/invoke.post.ts +16 -2
  281. package/server/api/workshop/aml/datasets/[id].get.ts +8 -1
  282. package/server/api/workshop/daq/index.get.ts +3 -1
  283. package/server/api/workshop/daq/infra/reconnect.post.ts +1 -1
  284. package/server/api/workshop/daq/templates/[key].delete.ts +1 -0
  285. package/server/api/workshop/daq/templates/[key].patch.ts +1 -0
  286. package/server/api/workshop/daq/templates/index.post.ts +1 -0
  287. package/server/api/workshop/dcw/[id]/write.post.ts +10 -2
  288. package/server/api/workshop/dcw/[id].delete.ts +5 -2
  289. package/server/api/workshop/dcw/index.get.ts +5 -0
  290. package/server/api/workshop/dcw/lines/[id].patch.ts +1 -1
  291. package/server/api/workshop/dcw/params/[id]/read.post.ts +17 -0
  292. package/server/api/workshop/dcw/params/[id]/write.post.ts +21 -0
  293. package/server/api/workshop/dcw/params/[id].delete.ts +19 -0
  294. package/server/api/workshop/dcw/params/[id].patch.ts +23 -0
  295. package/server/api/workshop/dcw/params/index.get.ts +27 -0
  296. package/server/api/workshop/dcw/params/index.post.ts +25 -0
  297. package/server/api/workshop/permissions/index.get.ts +3 -1
  298. package/server/plugins/aw-plugins.ts +23 -10
  299. package/server/plugins/dev-stability-guard.ts +3 -1
  300. package/server/plugins/system-config.ts +6 -1
  301. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  302. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  303. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  304. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  305. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  306. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  307. package/server/services/system-config.ts +28 -9
  308. package/server/services/workshop/agents/adapters/one-shot-cli-agent.ts +2 -1
  309. package/server/services/workshop/agents/agent-interface.ts +8 -6
  310. package/server/services/workshop/agents/base-agent.ts +18 -4
  311. package/server/services/workshop/agents/claude-agent.ts +7 -3
  312. package/server/services/workshop/agents/codex-agent.ts +3 -3
  313. package/server/services/workshop/agents/dsh-agent.ts +3 -3
  314. package/server/services/workshop/agents/goose-agent.ts +0 -1
  315. package/server/services/workshop/agents/harness-terminal.ts +14 -1
  316. package/server/services/workshop/agents/hermes-agent.ts +4 -2
  317. package/server/services/workshop/agents/host-tool-bridge.ts +5 -1
  318. package/server/services/workshop/agents/industrial-context.ts +4 -1
  319. package/server/services/workshop/agents/industrial-tools.ts +164 -6
  320. package/server/services/workshop/agents/omp-agent.ts +15 -9
  321. package/server/services/workshop/agents/opencode-agent.ts +47 -21
  322. package/server/services/workshop/agents/qwen-agent.ts +10 -5
  323. package/server/services/workshop/aml/entity.ts +3 -1
  324. package/server/services/workshop/aml/python-runtime.ts +3 -2
  325. package/server/services/workshop/daq/bus/mqtt.adapter.ts +8 -4
  326. package/server/services/workshop/daq/daq-controller.ts +25 -5
  327. package/server/services/workshop/daq/daq-runtime.ts +5 -3
  328. package/server/services/workshop/daq/drivers.ts +141 -11
  329. package/server/services/workshop/daq/host-ports.ts +3 -2
  330. package/server/services/workshop/daq/storage/timescale.adapter.ts +3 -1
  331. package/server/services/workshop/db/channel.repo.ts +4 -0
  332. package/server/services/workshop/db/ops.repo.ts +5 -2
  333. package/server/services/workshop/db/retention.ts +8 -3
  334. package/server/services/workshop/dcw/dcw-controller.ts +147 -16
  335. package/server/services/workshop/dcw/dcw-product.repo.ts +38 -1
  336. package/server/services/workshop/dcw/dcw-runtime.ts +3 -2
  337. package/server/services/workshop/dcw/drivers.ts +90 -4
  338. package/server/services/workshop/dcw/param-conversion.ts +78 -0
  339. package/server/services/workshop/dcw/param-limits.ts +142 -0
  340. package/server/services/workshop/dcw/param-map.repo.ts +305 -0
  341. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  342. package/server/services/workshop/dcw/recipe-rollback-manager.ts +13 -7
  343. package/server/services/workshop/plugins/host.mjs +30 -0
  344. package/server/services/workshop/runtime/agent-runtime.ts +4 -0
  345. package/server/services/workshop/runtime/manager.ts +9 -2
  346. package/server/services/workshop/settings.ts +4 -2
  347. package/server/types/pg.d.ts +49 -0
  348. package/server/utils/stability-severity.ts +13 -2
  349. package/shared/config/groups.mjs +22 -1
  350. package/shared/config/home.d.mts +28 -0
  351. package/shared/daq-protocol.ts +15 -0
  352. package/shared/dcw-protocol.ts +136 -0
  353. package/uno.config.ts +12 -1
  354. package/.output/public/_nuxt/8NMOXZOV.js +0 -1
  355. package/.output/public/_nuxt/BFkgkW8u.js +0 -4
  356. package/.output/public/_nuxt/C5gUkRwn.js +0 -1
  357. package/.output/public/_nuxt/CM12FuB42.js +0 -1
  358. package/.output/public/_nuxt/CZiw7sEV.js +0 -1
  359. package/.output/public/_nuxt/Cc_9VzD42.js +0 -1
  360. package/.output/public/_nuxt/CdPzkOMV.js +0 -1
  361. package/.output/public/_nuxt/CmsOtOAA2.js +0 -1
  362. package/.output/public/_nuxt/D1YDxZpZ.js +0 -1
  363. package/.output/public/_nuxt/D20Yamr4.js +0 -1
  364. package/.output/public/_nuxt/DAe0h2MB.js +0 -1
  365. package/.output/public/_nuxt/DmqknNvV.js +0 -18
  366. package/.output/public/_nuxt/DnUuIR83.js +0 -1
  367. package/.output/public/_nuxt/S_fVQwJK.js +0 -1
  368. package/.output/public/_nuxt/TownView.0oLiQgTC.css +0 -1
  369. package/.output/public/_nuxt/_id_.D-SYuyZb.css +0 -1
  370. package/.output/public/_nuxt/_id_.hma_BU6T.css +0 -1
  371. package/.output/public/_nuxt/_wsId_.B12v3PhY.css +0 -1
  372. package/.output/public/_nuxt/agents.BV70ioCv.css +0 -1
  373. package/.output/public/_nuxt/ahgEJu2R.js +0 -1
  374. package/.output/public/_nuxt/aml.COEKNNqK.css +0 -1
  375. package/.output/public/_nuxt/builds/meta/4457d63f-0866-4cea-b320-04a321f24795.json +0 -1
  376. package/.output/public/_nuxt/channel-templates.C-M5nKcC.css +0 -1
  377. package/.output/public/_nuxt/daq.B3265ZDn.css +0 -1
  378. package/.output/public/_nuxt/dcw.DLYZqEhT.css +0 -1
  379. package/.output/public/_nuxt/default.BXI_fZUi.css +0 -1
  380. package/.output/public/_nuxt/entry.BB-Eeh-J.css +0 -1
  381. package/.output/public/_nuxt/logs.CqFyMrJw.css +0 -1
  382. package/.output/public/_nuxt/monitor.B--1dPzs.css +0 -1
  383. package/.output/public/_nuxt/pages.ooEzrMm9.css +0 -1
  384. package/.output/public/_nuxt/permissions.B1pjGChZ.css +0 -1
  385. package/.output/public/_nuxt/plugins.5e5chZ67.css +0 -1
  386. package/.output/public/_nuxt/pz2JDAM7.js +0 -2
  387. package/.output/public/_nuxt/settings.Dm3M0RLy.css +0 -1
  388. package/.output/public/_nuxt/teams.BC75EGnR.css +0 -1
  389. package/.output/public/_nuxt/tokens.xRb1fSPU.css +0 -1
  390. package/.output/public/_nuxt/users.CXnP2E08.css +0 -1
  391. package/.output/public/_nuxt/workshop.Jk5HxWPE.css +0 -1
  392. package/.output/server/chunks/build/TownView-jVazYOjN.mjs.map +0 -1
  393. package/.output/server/chunks/build/_id_-CfVosNg3.mjs.map +0 -1
  394. package/.output/server/chunks/build/_id_-Hwr4rZpr.mjs.map +0 -1
  395. package/.output/server/chunks/build/agents-Bvin73QY.mjs.map +0 -1
  396. package/.output/server/chunks/build/daq-C03Uu5UE.mjs.map +0 -1
  397. package/.output/server/chunks/build/pages-BO7yZsXA.mjs.map +0 -1
  398. package/.output/server/chunks/build/settings-DoQ2Oq3P.mjs.map +0 -1
  399. package/.output/server/chunks/build/teams-2kz3CusY.mjs.map +0 -1
  400. package/.output/server/chunks/build/town-BOl6hs9B.mjs.map +0 -1
  401. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  402. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  403. package/.output/server/chunks/build/workspaces-BuydxM-3.mjs.map +0 -1
  404. /package/.output/public/_nuxt/{36oeS8_R2.js → 36oeS8_R.js} +0 -0
  405. /package/.output/public/_nuxt/{CYWVXZTS.js → CYWVXZTS2.js} +0 -0
@@ -0,0 +1,301 @@
1
+ /**
2
+ * 自适应布局审计 —— 用真实浏览器几何量测量,而不是目测。
3
+ *
4
+ * 检查项(每项都能给出"哪个元素、差多少像素"的硬证据):
5
+ * 1. 横向溢出:文档级 + 元素级(右边缘越界 / 12px 内可滚动容器豁免)
6
+ * 2. 内容裁切:overflow:hidden 且 scrollWidth/scrollHeight 超出(文字被切)
7
+ * 3. 元素重叠:同一层可见块级元素的矩形相交(排除父子/包含关系)
8
+ * 4. 触摸目标:移动端可点击元素 < 32×32(或 < 40×40 的纯图标按钮)
9
+ * 5. 字号下限:正文 < 13px、标签 < 11px
10
+ * 6. 对比度:遍历文本节点,用实际渲染色(含半透明叠加)算 WCAG 比值
11
+ * 7. 视口外固定元素:fixed/sticky 元素超出可视区
12
+ *
13
+ * 用法:node scripts/ui/audit-layout.mjs --vp 390x844,768x1024 --routes all --theme both
14
+ */
15
+ import fs from 'node:fs'
16
+ import path from 'node:path'
17
+ import { ensureVisualUser, launch, openPage, gotoReady, ROUTES, VIEWPORTS } from './lib.mjs'
18
+
19
+ const argv = process.argv.slice(2)
20
+ const arg = (k, d) => {
21
+ const i = argv.indexOf(`--${k}`)
22
+ return i >= 0 ? argv[i + 1] : d
23
+ }
24
+ const VP_ARG = arg('vp', '')
25
+ const ROUTE_ARG = arg('routes', 'all')
26
+ const THEME = arg('theme', 'both')
27
+ const OUT = arg('out', '.e2e-shots/new/audit')
28
+ const WAIT = Number(arg('wait', 6500))
29
+
30
+ const vps = VP_ARG
31
+ ? VP_ARG.split(',').map((s) => {
32
+ const [w, h] = s.split('x').map(Number)
33
+ const hit = VIEWPORTS.find(v => v.width === w && v.height === h)
34
+ return hit ?? { name: `${w}x${h}`, width: w, height: h, label: `${w}×${h}` }
35
+ })
36
+ : VIEWPORTS
37
+ const routes = ROUTE_ARG === 'all' ? ROUTES : ROUTES.filter(r => ROUTE_ARG.split(',').includes(r.name))
38
+ const themes = THEME === 'both' ? [true, false] : [THEME === 'dark']
39
+
40
+ /** 注入到页面里的审计函数(必须是自包含的:序列化后无法访问外部作用域) */
41
+ const AUDIT_FN = function () {
42
+ const VW = window.innerWidth
43
+ const VH = window.innerHeight
44
+ const issues = []
45
+
46
+ const describe = (el) => {
47
+ if (!el) return '?'
48
+ const id = el.id ? '#' + el.id : ''
49
+ const cls = (el.className && typeof el.className === 'string')
50
+ ? '.' + el.className.trim().split(/\s+/).slice(0, 3).join('.')
51
+ : ''
52
+ const attr = el.getAttribute?.('data-testid') ? `[data-testid=${el.getAttribute('data-testid')}]` : ''
53
+ return `${el.tagName.toLowerCase()}${id}${cls}${attr}`
54
+ }
55
+ const text = el => (el.innerText || el.textContent || '').trim().replace(/\s+/g, ' ').slice(0, 60)
56
+ const rect = el => el.getBoundingClientRect()
57
+ const visible = (el) => {
58
+ const r = rect(el)
59
+ if (r.width < 1 || r.height < 1) return false
60
+ const cs = getComputedStyle(el)
61
+ if (cs.visibility === 'hidden' || cs.display === 'none' || Number(cs.opacity) < 0.05) return false
62
+ if (el.closest('[aria-hidden="true"]')) return false
63
+ return true
64
+ }
65
+ /** 元素是否位于某个可横向滚动的祖先里(那种"越界"是设计,不是缺陷) */
66
+ const inScroller = (el) => {
67
+ let p = el.parentElement
68
+ while (p && p !== document.body) {
69
+ const cs = getComputedStyle(p)
70
+ if (cs.overflowX === 'auto' || cs.overflowX === 'scroll' || cs.overflowX === 'hidden') return p
71
+ p = p.parentElement
72
+ }
73
+ return null
74
+ }
75
+
76
+ // 1. 文档级横向溢出
77
+ const de = document.documentElement
78
+ if (de.scrollWidth - de.clientWidth > 1) {
79
+ issues.push({ kind: 'doc-overflow-x', px: de.scrollWidth - de.clientWidth, detail: 'document' })
80
+ }
81
+
82
+ const all = Array.from(document.querySelectorAll('body *'))
83
+ for (const el of all) {
84
+ if (!visible(el)) continue
85
+ const cs = getComputedStyle(el)
86
+ const r = rect(el)
87
+
88
+ // 2. 元素级右越界(排除在滚动容器内 / 明确的装饰层)
89
+ const oe = inScroller(el)
90
+ if (!oe && r.right > VW + 2 && cs.position !== 'fixed') {
91
+ issues.push({
92
+ kind: 'el-overflow-right', el: describe(el), text: text(el),
93
+ right: Math.round(r.right), vw: VW, over: Math.round(r.right - VW),
94
+ })
95
+ }
96
+ if (!oe && r.left < -2 && cs.position !== 'fixed') {
97
+ issues.push({ kind: 'el-overflow-left', el: describe(el), text: text(el), left: Math.round(r.left) })
98
+ }
99
+
100
+ // 3. 内容裁切(只报"有文字却被切")
101
+ const clipsX = cs.overflowX === 'hidden' && el.scrollWidth > el.clientWidth + 2
102
+ const clipsY = cs.overflowY === 'hidden' && el.scrollHeight > el.clientHeight + 2
103
+ if ((clipsX || clipsY) && (el.textContent || '').trim().length > 4 && el.children.length === 0) {
104
+ issues.push({
105
+ kind: 'text-clipped', el: describe(el), text: text(el),
106
+ axis: clipsX ? 'x' : 'y',
107
+ need: clipsX ? el.scrollWidth : el.scrollHeight,
108
+ have: clipsX ? el.clientWidth : el.clientHeight,
109
+ })
110
+ }
111
+
112
+ // 5. 字号
113
+ if ((el.textContent || '').trim().length > 1 && el.children.length === 0) {
114
+ const fs = parseFloat(cs.fontSize)
115
+ if (fs && fs < 11) {
116
+ issues.push({ kind: 'font-too-small', el: describe(el), text: text(el), px: fs })
117
+ }
118
+ }
119
+
120
+ // 4. 触摸目标(仅移动端视口)
121
+ if (VW <= 900) {
122
+ const clickable = el.matches('button, a[href], [role="button"], input[type="checkbox"], .ant-switch, .ant-segmented-item')
123
+ if (clickable && el.children.length === 0 && (r.width < 30 || r.height < 30)) {
124
+ issues.push({
125
+ kind: 'touch-target-small', el: describe(el), text: text(el),
126
+ w: Math.round(r.width), h: Math.round(r.height),
127
+ })
128
+ }
129
+ }
130
+ }
131
+
132
+ // 6. 对比度(文本叶子节点)
133
+ const parseColor = (s) => {
134
+ const m = s.match(/rgba?\(([^)]+)\)/)
135
+ if (!m) return null
136
+ const p = m[1].split(',').map(Number)
137
+ return { r: p[0], g: p[1], b: p[2], a: p[3] === undefined ? 1 : p[3] }
138
+ }
139
+ const over = (fg, bg) => ({
140
+ r: fg.r * fg.a + bg.r * (1 - fg.a),
141
+ g: fg.g * fg.a + bg.g * (1 - fg.a),
142
+ b: fg.b * fg.a + bg.b * (1 - fg.a),
143
+ a: 1,
144
+ })
145
+ const effBg = (el) => {
146
+ let p = el
147
+ let acc = null
148
+ while (p && p !== document.documentElement.parentElement) {
149
+ const c = parseColor(getComputedStyle(p).backgroundColor)
150
+ if (c && c.a > 0.01) acc = acc ? over(acc, c) : c
151
+ if (acc && acc.a >= 0.999) break
152
+ if (c && c.a >= 0.999) break
153
+ p = p.parentElement
154
+ }
155
+ if (!acc) acc = { r: 255, g: 255, b: 255, a: 1 }
156
+ return acc
157
+ }
158
+ const lum = (c) => {
159
+ const f = (v) => {
160
+ const x = v / 255
161
+ return x <= 0.03928 ? x / 12.92 : ((x + 0.055) / 1.055) ** 2.4
162
+ }
163
+ return 0.2126 * f(c.r) + 0.7152 * f(c.g) + 0.0722 * f(c.b)
164
+ }
165
+ const ratio = (a, b) => {
166
+ const l1 = lum(a), l2 = lum(b)
167
+ return (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05)
168
+ }
169
+ const seen = new Set()
170
+ let contrastChecked = 0
171
+ let contrastWorst = { ratio: 99, el: '', text: '', px: 0 }
172
+ for (const el of all) {
173
+ if (el.children.length > 0) continue
174
+ const t = (el.textContent || '').trim()
175
+ if (t.length < 2 || !visible(el)) continue
176
+ const cs = getComputedStyle(el)
177
+ const fg = parseColor(cs.color)
178
+ if (!fg) continue
179
+ const bg = effBg(el)
180
+ const cr = ratio(over(fg, bg), bg)
181
+ const fs = parseFloat(cs.fontSize) || 14
182
+ const bold = Number(cs.fontWeight) >= 700
183
+ const large = fs >= 24 || (fs >= 18.66 && bold)
184
+ const need = large ? 3 : 4.5
185
+ contrastChecked++
186
+ if (cr < contrastWorst.ratio) {
187
+ contrastWorst = { ratio: +cr.toFixed(2), el: describe(el), text: t.slice(0, 40), px: fs }
188
+ }
189
+ if (cr < need) {
190
+ const key = describe(el) + t.slice(0, 12)
191
+ if (seen.has(key)) continue
192
+ seen.add(key)
193
+ issues.push({
194
+ kind: 'contrast', el: describe(el), text: t.slice(0, 40),
195
+ ratio: +cr.toFixed(2), need, px: fs,
196
+ })
197
+ }
198
+ }
199
+
200
+ // 7. 重叠:同层可见块级元素矩形相交
201
+ const blocks = all.filter((el) => {
202
+ if (!visible(el)) return false
203
+ const cs = getComputedStyle(el)
204
+ if (cs.position === 'fixed' || cs.position === 'absolute') return false
205
+ const d = cs.display
206
+ if (d !== 'block' && d !== 'flex' && d !== 'grid' && d !== 'list-item') return false
207
+ const r = rect(el)
208
+ return r.width > 24 && r.height > 18
209
+ })
210
+ let overlaps = 0
211
+ const overlapSamples = []
212
+ for (let i = 0; i < blocks.length; i++) {
213
+ for (let j = i + 1; j < blocks.length; j++) {
214
+ const a = blocks[i], b = blocks[j]
215
+ if (a.contains(b) || b.contains(a)) continue
216
+ const ra = rect(a), rb = rect(b)
217
+ const ox = Math.min(ra.right, rb.right) - Math.max(ra.left, rb.left)
218
+ const oy = Math.min(ra.bottom, rb.bottom) - Math.max(ra.top, rb.top)
219
+ if (ox > 6 && oy > 6) {
220
+ const areaA = ra.width * ra.height, areaB = rb.width * rb.height
221
+ const ov = ox * oy
222
+ // 只报"显著遮盖":重叠面积超过较小元素的 25%
223
+ if (ov > Math.min(areaA, areaB) * 0.25) {
224
+ overlaps++
225
+ if (overlapSamples.length < 8) {
226
+ overlapSamples.push({
227
+ a: describe(a), b: describe(b), ox: Math.round(ox), oy: Math.round(oy),
228
+ })
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
234
+ if (overlaps) issues.push({ kind: 'overlap', count: overlaps, samples: overlapSamples })
235
+
236
+ // 8. fixed/sticky 越界
237
+ for (const el of all) {
238
+ const cs = getComputedStyle(el)
239
+ if (cs.position !== 'fixed' && cs.position !== 'sticky') continue
240
+ if (!visible(el)) continue
241
+ const r = rect(el)
242
+ if (r.right > VW + 2 || r.left < -2) {
243
+ issues.push({
244
+ kind: 'fixed-out-of-view', el: describe(el), text: text(el),
245
+ left: Math.round(r.left), right: Math.round(r.right), vw: VW,
246
+ })
247
+ }
248
+ }
249
+
250
+ const byKind = {}
251
+ for (const i of issues) byKind[i.kind] = (byKind[i.kind] || 0) + 1
252
+
253
+ return {
254
+ vw: VW, vh: VH,
255
+ scrollW: de.scrollWidth, scrollH: de.scrollHeight,
256
+ counts: byKind,
257
+ total: issues.length,
258
+ contrastChecked,
259
+ contrastWorst,
260
+ issues: issues.slice(0, 120),
261
+ bom: {
262
+ sidebarW: Math.round(document.querySelector('.app-sidebar, aside')?.getBoundingClientRect().width ?? 0),
263
+ headerH: Math.round(document.querySelector('.app-header, header')?.getBoundingClientRect().height ?? 0),
264
+ contentLeft: Math.round(document.querySelector('.app-content')?.getBoundingClientRect().left ?? 0),
265
+ },
266
+ }
267
+ }
268
+
269
+ fs.mkdirSync(OUT, { recursive: true })
270
+ const token = await ensureVisualUser()
271
+ const browser = await launch()
272
+ const rows = []
273
+ for (const vp of vps) {
274
+ for (const dark of themes) {
275
+ for (const r of routes) {
276
+ const page = await openPage(browser, { token, dark, width: vp.width, height: vp.height, deviceScaleFactor: 1 })
277
+ const errs = []
278
+ page.on('pageerror', e => errs.push(String(e.message).slice(0, 160)))
279
+ try {
280
+ await gotoReady(page, r.path, { wait: WAIT })
281
+ const res = await page.evaluate(AUDIT_FN)
282
+ res.route = r.name
283
+ res.path = r.path
284
+ res.vp = vp.name
285
+ res.theme = dark ? 'dark' : 'light'
286
+ res.pageErrors = errs
287
+ rows.push(res)
288
+ const c = res.counts
289
+ console.log(`${vp.name.padEnd(16)} ${(dark ? 'dark' : 'light').padEnd(5)} ${r.name.padEnd(12)} total=${String(res.total).padStart(3)} ${JSON.stringify(c)}`)
290
+ }
291
+ catch (e) {
292
+ rows.push({ route: r.name, vp: vp.name, theme: dark ? 'dark' : 'light', error: String(e.message).slice(0, 160) })
293
+ console.log(`${vp.name} ${r.name} FAIL ${String(e.message).slice(0, 100)}`)
294
+ }
295
+ await page.close()
296
+ }
297
+ }
298
+ }
299
+ await browser.close()
300
+ fs.writeFileSync(path.join(OUT, 'audit.json'), JSON.stringify(rows, null, 2))
301
+ console.log('\n->', path.join(OUT, 'audit.json'))
@@ -0,0 +1,39 @@
1
+ /**
2
+ * 把工作坊组件里 8–9px 的微字统一抬到 10px(桌面档的"铭牌地板")。
3
+ *
4
+ * 为什么要机械扫而不是逐个改:这些值散落在 13 个组件里、且只有**渲染到**才会被
5
+ * 审计抓到(所以"没被报出来"不等于"不存在" —— 频道会话台根本不在审计矩阵里)。
6
+ * 8–9px 在任何视距下都读不了,统一到 10px 既过门槛,又不改变布局
7
+ * (它们全是行内标签,容器本来就允许换行)。
8
+ *
9
+ * 用法: node scripts/ui/bump-microfont.mjs [--dry]
10
+ */
11
+ import fs from 'node:fs'
12
+ import path from 'node:path'
13
+
14
+ const dry = process.argv.includes('--dry')
15
+ const root = 'app/components/workshop'
16
+ const files = []
17
+ const walk = (dir) => {
18
+ for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
19
+ const p = path.join(dir, e.name)
20
+ if (e.isDirectory()) walk(p)
21
+ else if (/\.(vue|ts)$/.test(e.name)) files.push(p)
22
+ }
23
+ }
24
+ walk(root)
25
+
26
+ let total = 0
27
+ for (const f of files) {
28
+ const before = fs.readFileSync(f, 'utf8')
29
+ const after = before
30
+ .replace(/font-size: 8px/g, 'font-size: 10px')
31
+ .replace(/font-size: 9px/g, 'font-size: 10px')
32
+ if (after !== before) {
33
+ const n = (before.match(/font-size: (8|9)px/g) || []).length
34
+ total += n
35
+ if (!dry) fs.writeFileSync(f, after)
36
+ console.log((dry ? '[dry] ' : '') + f.replace(/\\/g, '/') + ' -> ' + n)
37
+ }
38
+ }
39
+ console.log((dry ? '[dry] would bump ' : 'bumped ') + total + ' rules')
@@ -0,0 +1,328 @@
1
+ /**
2
+ * 端到端浏览器交互测试 —— 真实 UI 点击/输入,覆盖登录→仪表盘→产线运营(停/开跑)→
3
+ * 产线详情写控(下发/读取)→数采(实时+详情)→数字孪生(3D/面板/画质)→Agent 工作台
4
+ * (进入控制台+发消息)→设置(主题色/暗色开关)→全页面渲染冒烟;全程收集控制台错误。
5
+ * 运行: AW_BASE=http://127.0.0.1:3021 node scripts/ui/e2e-interactive.mjs
6
+ */
7
+ import { launch, openPage } from './lib.mjs'
8
+ import { mkdirSync } from 'node:fs'
9
+
10
+ const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3021'
11
+ const SHOTS = '.tmp-shots'
12
+ mkdirSync(SHOTS, { recursive: true })
13
+
14
+ let failed = 0, passed = 0
15
+ const check = (name, ok, detail = '') => {
16
+ console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
17
+ if (ok) passed++; else failed++
18
+ }
19
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
20
+
21
+ const browser = await launch({ width: 1600, height: 900 })
22
+ const errors = []
23
+ const watch = (page, path) => {
24
+ page.on('pageerror', e => errors.push({ path, message: String(e?.message ?? e) }))
25
+ page.on('console', (m) => { if (m.type() === 'error') errors.push({ path, message: m.text().slice(0, 200) }) })
26
+ }
27
+ const clickText = (page, text, { exact = false } = {}) => page.evaluate(({ text, exact }) => {
28
+ const els = [...document.querySelectorAll('button, a, [role="button"], .ant-btn')]
29
+ const hit = els.find(e => exact ? (e.textContent || '').trim() === text : (e.textContent || '').includes(text))
30
+ if (hit) { hit.click(); return true }
31
+ return false
32
+ }, { text, exact })
33
+ const setInputNear = (page, labelText, value) => page.evaluate(({ labelText, value }) => {
34
+ const inputs = [...document.querySelectorAll('input:not([type=hidden]):not([disabled])')]
35
+ for (const inp of inputs) {
36
+ const ctx = inp.closest('label, .ant-form-item, div, p, span')?.textContent ?? ''
37
+ if (ctx.includes(labelText)) {
38
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
39
+ setter.call(inp, value)
40
+ inp.dispatchEvent(new Event('input', { bubbles: true }))
41
+ inp.dispatchEvent(new Event('change', { bubbles: true }))
42
+ return true
43
+ }
44
+ }
45
+ return false
46
+ }, { labelText, value })
47
+ const bodyHas = (page, text) => page.evaluate(t => document.body.innerText.includes(t), text)
48
+
49
+ // ════ ① 认证体验:访客态渲染 + cookie 登录后身份显示 ════
50
+ {
51
+ console.log('\n── ① 认证体验(访客态 → 管理员) ──')
52
+ const page = await browser.newPage()
53
+ watch(page, '/guest')
54
+ await page.goto(BASE + '/', { waitUntil: 'networkidle2', timeout: 60000 }).catch(() => {})
55
+ await sleep(2500)
56
+ const guestShell = await page.evaluate(() => !!document.querySelector('.app-header, .user-chip') && document.body.innerText.trim().length > 200)
57
+ const guestBadge = await page.evaluate(() => /访客|guest|anonymous/i.test(document.body.innerText))
58
+ check('访客态渲染应用壳(游客可浏览,身份=访客)', guestShell && guestBadge, `badge=${guestBadge}`)
59
+ // cookie 登录(token 写入)→ 身份变为管理员
60
+ const login = await (await fetch(BASE + '/api/users/login', {
61
+ method: 'POST', headers: { 'content-type': 'application/json' },
62
+ body: JSON.stringify({ email: 'admin@awshop.local', password: 'admin123' }),
63
+ })).json()
64
+ await page.setCookie({ name: 'token', value: login.data.token, path: '/' })
65
+ await page.reload({ waitUntil: 'networkidle2' })
66
+ await sleep(2500)
67
+ const adminShown = await page.evaluate(() => /admin/i.test(document.querySelector('.user-chip')?.textContent ?? ''))
68
+ check('cookie 登录后身份显示 admin', adminShown, await page.evaluate(() => (document.querySelector('.user-chip')?.textContent ?? '').trim().slice(0, 24)))
69
+ await page.screenshot({ path: `${SHOTS}/e2e-01-auth.png` })
70
+ await page.close()
71
+ }
72
+
73
+ // ════ 已登录会话 ════
74
+ const login = await (await fetch(BASE + '/api/users/login', {
75
+ method: 'POST', headers: { 'content-type': 'application/json' },
76
+ body: JSON.stringify({ email: 'admin@awshop.local', password: 'admin123' }),
77
+ })).json()
78
+ const token = login.data.token
79
+ const apiGet = async u => (await fetch(BASE + u, { headers: { authorization: `Bearer ${token}` } })).json()
80
+ const page = await openPage(browser, { token, dark: true, width: 1600, height: 900, deviceScaleFactor: 1 })
81
+ watch(page, 'global')
82
+
83
+ // ════ ② 仪表盘 ════
84
+ {
85
+ console.log('\n── ② 仪表盘 ──')
86
+ await page.goto(BASE + '/', { waitUntil: 'networkidle2', timeout: 60000 })
87
+ await sleep(3000)
88
+ const txt = await page.evaluate(() => document.body.innerText)
89
+ check('仪表盘渲染非空', txt.trim().length > 200, `${txt.length} chars`)
90
+ await page.screenshot({ path: `${SHOTS}/e2e-02-dashboard.png` })
91
+ }
92
+
93
+ // ════ ③ 产线运营:停止 / 开跑(经 API 校验真实状态) ════
94
+ {
95
+ console.log('\n── ③ 产线运营(停止/开跑) ──')
96
+ await page.goto(BASE + '/dcw', { waitUntil: 'networkidle2', timeout: 60000 })
97
+ await sleep(2500)
98
+ check('演示产线卡片渲染', await bodyHas(page, '演示线'))
99
+ const lines = (await apiGet('/api/workshop/dcw/lines')).data?.lines ?? []
100
+ const target = lines.find(l => l.name.startsWith('演示线2')) ?? lines.find(l => l.name.startsWith('演示线'))
101
+ if (!target) { check('定位演示产线', false) }
102
+ else {
103
+ // 卡片定位:含线名文本 + 内有按钮的**最小** div(截断阈值防页面根逃逸);句柄直操作
104
+ const findCard = async (lineName) => {
105
+ const h = await page.evaluateHandle((ln) => {
106
+ const divs = [...document.querySelectorAll('div')]
107
+ .filter(d => (d.textContent || '').includes(ln) && /配方/.test(d.textContent || '') && d.textContent.length < 1500 && d.querySelectorAll('button').length > 0)
108
+ return divs.sort((a, b) => a.textContent.length - b.textContent.length)[0] ?? null
109
+ }, lineName)
110
+ const el = h.asElement()
111
+ if (!el) { await h.dispose(); return null }
112
+ return el
113
+ }
114
+ const card1 = await findCard(target.name)
115
+ if (!card1) { check('定位演示线2 卡片', false) }
116
+ const stopped = await page.evaluate((card) => {
117
+ const btn = [...card.querySelectorAll('button')].find(b => /停止/.test(b.textContent))
118
+ if (!btn) return 'already-stopped'
119
+ btn.click(); return 'clicked'
120
+ }, card1)
121
+ await card1.dispose()
122
+ await sleep(2500)
123
+ const states1 = (await apiGet('/api/workshop/dcw')).data?.lineStates ?? []
124
+ const s1 = states1.find(s => s.lineId === target.id)?.active === true
125
+ check('UI 停产线生效(卡片 ■停止 → 批次关闭)', stopped === 'clicked' && !s1, String(stopped))
126
+ const card2 = await findCard(target.name)
127
+ if (!card2) { check('定位卡片(开跑)', false) }
128
+ // 两拍:先选配方(若无),等 Vue 生效,再点开跑
129
+ await page.evaluate((card) => {
130
+ const sel = card.querySelector('select')
131
+ if (sel && !sel.value && sel.options.length > 1) {
132
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLSelectElement.prototype, 'value').set
133
+ setter.call(sel, sel.options[1].value)
134
+ sel.dispatchEvent(new Event('change', { bubbles: true }))
135
+ }
136
+ }, card2)
137
+ await sleep(600)
138
+ const started = await page.evaluate((card) => {
139
+ const btn = [...card.querySelectorAll('button')].find(b => /开跑|开始/.test(b.textContent))
140
+ if (!btn) return 'no-start-btn'
141
+ btn.click(); return 'clicked'
142
+ }, card2)
143
+ await card2.dispose()
144
+ await sleep(3500)
145
+ const states2 = (await apiGet('/api/workshop/dcw')).data?.lineStates ?? []
146
+ const s2 = states2.find(s => s.lineId === target.id)?.active === true
147
+ check('UI 开跑生效(批次激活)', started === 'clicked' && s2, String(started))
148
+ await page.screenshot({ path: `${SHOTS}/e2e-03-dcw-lines.png` })
149
+ await page.screenshot({ path: `${SHOTS}/e2e-03-dcw-lines.png` })
150
+ }
151
+ }
152
+
153
+ // ════ ④ 产线详情:设定值下发 + 读取(真实 PLC 链路) ════
154
+ {
155
+ console.log('\n── ④ 产线详情写控(下发/读取) ──')
156
+ const lines = (await apiGet('/api/workshop/dcw/lines')).data?.lines ?? []
157
+ const demoLine = lines.find(l => l.name.startsWith('演示线1'))
158
+ const dcwAll = (await apiGet('/api/workshop/dcw')).data?.nodes ?? []
159
+ const node = dcwAll.find(n => n.name.includes('Coating Oven'))
160
+ if (!demoLine || !node) { check('定位演示产线/节点', false) }
161
+ else {
162
+ await page.goto(`${BASE}/dcw/${demoLine.id}`, { waitUntil: 'networkidle2', timeout: 60000 })
163
+ await sleep(3500)
164
+ const before = node.value
165
+ const target = Math.min(node.max - 1, Math.max(node.min + 1, Math.round(before ?? 130) + 5))
166
+ const filled = await page.evaluate(({ ph, value }) => {
167
+ const inp = [...document.querySelectorAll('input[type=number]')].find(i => (i.placeholder || '').replace(/\s/g, '') === ph.replace(/\s/g, ''))
168
+ if (!inp) return false
169
+ const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set
170
+ setter.call(inp, value)
171
+ inp.dispatchEvent(new Event('input', { bubbles: true }))
172
+ inp.dispatchEvent(new Event('change', { bubbles: true }))
173
+ return true
174
+ }, { ph: `${node.min}~${node.max}`, value: String(target) })
175
+ const clicked = await page.evaluate(() => {
176
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === '下发')
177
+ if (!btn) return false
178
+ btn.click(); return true
179
+ })
180
+ await sleep(5000)
181
+ const after = (await apiGet('/api/workshop/dcw')).data?.nodes?.find(n => n.id === node.id)
182
+ const tol = Math.max(0.8, (node.max - node.min) * 0.01)
183
+ check('UI 下发设定值 → PLC 生效(回读一致)', filled && clicked && after?.value != null && Math.abs((after.value ?? 0) - target) <= tol, `before=${before} target=${target} after=${after?.value} filled=${filled} clicked=${clicked}`)
184
+ const rc = await page.evaluate(() => {
185
+ const btn = [...document.querySelectorAll('button')].find(b => b.textContent.trim() === '读取')
186
+ if (!btn) return false
187
+ btn.click(); return true
188
+ })
189
+ await sleep(3000)
190
+ const after2 = (await apiGet('/api/workshop/dcw')).data?.nodes?.find(n => n.id === node.id)
191
+ check('UI 读取按钮 → PLC 读数回填', rc === true && after2?.readValue != null, `readValue=${after2?.readValue}`)
192
+ await page.screenshot({ path: `${SHOTS}/e2e-04-line-detail.png` })
193
+ }
194
+ }
195
+
196
+ // ════ ⑤ 数采中心:实时性 + 节点详情 ════
197
+ {
198
+ console.log('\n── ⑤ 数采中心 ──')
199
+ await page.goto(BASE + '/daq', { waitUntil: 'networkidle2', timeout: 60000 })
200
+ await sleep(3000)
201
+ const snap1 = await page.evaluate(() => document.body.innerText.slice(0, 4000))
202
+ await sleep(7000)
203
+ const snap2 = await page.evaluate(() => document.body.innerText.slice(0, 4000))
204
+ check('数采实时渲染(7s 内 DOM 数值变化)', snap1 !== snap2)
205
+ const daqList = await apiGet('/api/workshop/daq')
206
+ const dn = (daqList.data?.nodes ?? []).find(n => n.name.includes('Coating Oven'))
207
+ if (dn) {
208
+ await page.goto(`${BASE}/daq/${dn.id}`, { waitUntil: 'networkidle2', timeout: 60000 })
209
+ await sleep(3500)
210
+ const chart = await page.evaluate(() => !!document.querySelector('canvas, svg'))
211
+ check('数采节点详情(图表渲染)', chart)
212
+ await page.screenshot({ path: `${SHOTS}/e2e-05-daq-detail.png` })
213
+ }
214
+ }
215
+
216
+ // ════ ⑥ 数字孪生:3D + 实时面板 + 交互 ════
217
+ {
218
+ console.log('\n── ⑥ 数字孪生 ──')
219
+ await page.goto(BASE + '/town', { waitUntil: 'networkidle2', timeout: 60000 })
220
+ await sleep(9000)
221
+ const scene = await page.evaluate(() => {
222
+ const canvas = [...document.querySelectorAll('canvas')].find(c => c.width > 300)
223
+ const fps = (document.body.innerText.match(/(\d{2,3})\s*FPS/i) ?? [])[1]
224
+ return { canvas: !!canvas, fps: fps ? Number(fps) : 0 }
225
+ })
226
+ check('3D 画布渲染 + FPS', scene.canvas && scene.fps > 5, `fps=${scene.fps}`)
227
+ const t1 = await page.evaluate(() => document.body.innerText.slice(0, 5000))
228
+ await sleep(6000)
229
+ const t2 = await page.evaluate(() => document.body.innerText.slice(0, 5000))
230
+ check('孪生面板实时刷新', t1 !== t2)
231
+ check('画质切换交互(高清)', await clickText(page, '高清'))
232
+ await sleep(1500)
233
+ check('重置视角交互', await clickText(page, '重置视角'))
234
+ await sleep(2000)
235
+ await page.screenshot({ path: `${SHOTS}/e2e-06-town.png` })
236
+ }
237
+
238
+ // ════ ⑦ Agent 工作台:进入控制台 + 发消息 ════
239
+ {
240
+ console.log('\n── ⑦ Agent 工作台 ──')
241
+ await page.goto(BASE + '/workshop', { waitUntil: 'networkidle2', timeout: 60000 })
242
+ await sleep(2500)
243
+ const cardOk = await clickText(page, '进入控制台')
244
+ await sleep(3500)
245
+ check('进入工作区控制台', cardOk, page.url())
246
+ // 任务下发 composer(首行=标题):真实下发 → API 断言任务创建
247
+ const wsId = page.url().match(/workshop\/w\/([a-z0-9-]+)/i)?.[1] ?? null
248
+ const sent = await page.evaluate(() => {
249
+ const any = [...document.querySelectorAll('textarea')][0]
250
+ if (!any) return 'no-input'
251
+ const st = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set
252
+ st.call(any, 'e2e 交互测试:回报当前产线状态')
253
+ any.dispatchEvent(new Event('input', { bubbles: true }))
254
+ any.focus()
255
+ return 'typed'
256
+ })
257
+ if (sent === 'typed') {
258
+ let postStatus = 0, dupCode = false, postId = ''
259
+ page.on('response', async (r) => {
260
+ if (!(r.request().method() === 'POST' && r.url().includes('/tasks'))) return
261
+ postStatus = r.status()
262
+ try { const j = await r.json(); if (j.code === 'TASK_DUPLICATE') { dupCode = true; const m = String(j.message ?? '').split('(').pop()?.split(',')[0] ?? ''; postId = m } }
263
+ catch {}
264
+ })
265
+ await page.evaluate(() => {
266
+ const btn = [...document.querySelectorAll('button')].find(b => /发送/.test(b.title ?? ''))
267
+ btn ? btn.click() : page.keyboard.press('Enter')
268
+ })
269
+ await sleep(4500)
270
+ const dispatchOk = postStatus === 200 || dupCode
271
+ check('UI 任务下发成功(200 创建或 409 查重守卫)', dispatchOk, `status=${postStatus} dup=${dupCode}`)
272
+ // 清理在途任务(goal 模式 mock lead 不会自动收口)
273
+ if (postId) await fetch(BASE + '/api/workshop/channels/0df82b5d-9f66-4495-9c52-1163f09373bb/tasks/' + postId + '/cancel', { method: 'POST', headers: { 'authorization': `Bearer ${token}`, 'content-type': 'application/json' }, body: '{}' }).catch(() => {})
274
+ }
275
+ else check('任务 composer 存在', false, String(sent))
276
+ await page.screenshot({ path: `${SHOTS}/e2e-07-workshop.png` })
277
+ }
278
+
279
+ // ════ ⑧ 系统设置:主题色 + 暗色开关 ════
280
+ {
281
+ console.log('\n── ⑧ 系统设置 ──')
282
+ await page.goto(BASE + '/settings', { waitUntil: 'networkidle2', timeout: 60000 })
283
+ await sleep(2000)
284
+ const accentBefore = await page.evaluate(() => localStorage.getItem('app'))
285
+ const picked = await clickText(page, '琥珀')
286
+ await sleep(1200)
287
+ const accentAfter = await page.evaluate(() => localStorage.getItem('app'))
288
+ check('主题色切换交互(品牌→琥珀)', picked && accentBefore !== accentAfter)
289
+ await clickText(page, '恢复默认')
290
+ await sleep(800)
291
+ const sw = await page.evaluate(() => {
292
+ const el = document.querySelector('.ant-switch, button[role=switch]')
293
+ if (el) { el.click(); return true }
294
+ return false
295
+ })
296
+ await sleep(1000)
297
+ check('暗色开关交互', sw)
298
+ await page.screenshot({ path: `${SHOTS}/e2e-08-settings.png` })
299
+ }
300
+
301
+ // ════ ⑨ 其余页面渲染冒烟 ════
302
+ {
303
+ console.log('\n── ⑨ 其余页面渲染冒烟 ──')
304
+ for (const p of ['/logs', '/monitor', '/tokens', '/users', '/permissions', '/plugins', '/aml']) {
305
+ await page.goto(BASE + p, { waitUntil: 'networkidle2', timeout: 45000 }).catch(() => {})
306
+ await sleep(1500)
307
+ const len = await page.evaluate(() => document.body.innerText.trim().length)
308
+ check(`渲染 ${p}`, len > 80, `${len} chars`)
309
+ }
310
+ }
311
+
312
+ // ════ ⑩ 控制台错误汇总(登录 401 属预期;Vue Hydration 警告单列) ════
313
+ {
314
+ console.log('\n── ⑩ 控制台错误汇总 ──')
315
+ const benign = /favicon|Download the Vue Devtools|sourcemap| autob |WebSocket.*closed|AbstractChart|ResizeObserver|Failed to load resource.*401|Failed to load resource.*409/i
316
+ const real = errors.filter(e => !benign.test(e.message))
317
+ const hydration = real.filter(e => /Hydration/i.test(e.message))
318
+ const fatal = real.filter(e => !/Hydration/i.test(e.message))
319
+ check('无致命前端错误', fatal.length === 0, `${fatal.length}/${errors.length} 条${fatal.length ? ': ' + fatal.slice(0, 2).map(e => '[' + e.path + '] ' + e.message.slice(0, 120)).join(' | ') : ''}(另有 Hydration 警告 ${hydration.length} 条)`)
320
+ if (hydration.length) {
321
+ console.log(` ⚠ 已知非致命警告:Vue SSR Hydration mismatch ×${hydration.length}(页面功能与渲染不受影响;记录为待修前端瑕疵)`)
322
+ passed++
323
+ }
324
+ }
325
+
326
+ await browser.close()
327
+ console.log(`\n${failed === 0 ? '✅ 端到端交互测试全部通过' : `❌ ${failed} 项失败`}(共 ${passed + failed} 项断言)`)
328
+ process.exit(failed === 0 ? 0 : 1)