agentworkshop 0.7.39 → 0.7.40

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 (373) 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/BOt2SP4z.js +1 -0
  7. package/.output/public/_nuxt/BP-NyKVG.js +18 -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/{C_OLzD09.js → C4oW1HsE.js} +1 -1
  12. package/.output/public/_nuxt/CFxtLwjp.js +1 -0
  13. package/.output/public/_nuxt/{Cec0Ua3F.js → CS50LB_l.js} +1 -1
  14. package/.output/public/_nuxt/C_iYD-EB.js +1 -0
  15. package/.output/public/_nuxt/{DAe0h2MB.js → ClILjvWH.js} +1 -1
  16. package/.output/public/_nuxt/CssgYAAL2.js +1 -0
  17. package/.output/public/_nuxt/{CY9LbPCI.js → CzOCSGLe.js} +1 -1
  18. package/.output/public/_nuxt/D8qvkfyY.js +1 -0
  19. package/.output/public/_nuxt/DCn7QZ5N.js +2 -0
  20. package/.output/public/_nuxt/DGIuvyRN2.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/DZshyNLa.js +1 -0
  26. package/.output/public/_nuxt/{45zuyAso.js → D_OTvQza.js} +1 -1
  27. package/.output/public/_nuxt/DcsCcxp4.js +1 -0
  28. package/.output/public/_nuxt/{DUrecWPx.js → Dihnimlk.js} +2 -2
  29. package/.output/public/_nuxt/Dqw1lpr-.js +1 -0
  30. package/.output/public/_nuxt/{CUDwzgLV.js → Dr9v6C0f.js} +9 -9
  31. package/.output/public/_nuxt/{8NMOXZOV.js → Du9ZJtnj.js} +1 -1
  32. package/.output/public/_nuxt/TownView.f-YOdXRs.css +1 -0
  33. package/.output/public/_nuxt/_id_.CM4bIgKY.css +1 -0
  34. package/.output/public/_nuxt/_id_.bNV69Dtf.css +1 -0
  35. package/.output/public/_nuxt/_wsId_.B4WiqkIS.css +1 -0
  36. package/.output/public/_nuxt/agents.ZD0rVdHE.css +1 -0
  37. package/.output/public/_nuxt/aml.BN8ZEW4h.css +1 -0
  38. package/.output/public/_nuxt/builds/latest.json +1 -1
  39. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +1 -0
  40. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +1 -0
  41. package/.output/public/_nuxt/daq.B0D3dPAc.css +1 -0
  42. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +1 -0
  43. package/.output/public/_nuxt/default.J32SgqFY.css +1 -0
  44. package/.output/public/_nuxt/entry.bdm0tv6M.css +1 -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 +2225 -1445
  61. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  62. package/.output/server/chunks/build/{TownView-jVazYOjN.mjs → TownView-BuLAsmpR.mjs} +288 -206
  63. package/.output/server/chunks/build/TownView-BuLAsmpR.mjs.map +1 -0
  64. package/.output/server/chunks/build/{_id_-CfVosNg3.mjs → _id_-BU1wfOID.mjs} +138 -138
  65. package/.output/server/chunks/build/{_id_-CfVosNg3.mjs.map → _id_-BU1wfOID.mjs.map} +1 -1
  66. package/.output/server/chunks/build/{_id_-Hwr4rZpr.mjs → _id_-BYsEua57.mjs} +33 -32
  67. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +1 -0
  68. package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs → _wsId_-BxlDcJHg.mjs} +293 -259
  69. package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs.map → _wsId_-BxlDcJHg.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-cgyjKmJu.mjs} +188 -122
  78. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +1 -0
  79. package/.output/server/chunks/build/{dcw-6vEYVeje.mjs → dcw-CoTxBlAZ.mjs} +45 -45
  80. package/.output/server/chunks/build/{dcw-6vEYVeje.mjs.map → dcw-CoTxBlAZ.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-BSOeZwwI.mjs} +34 -34
  84. package/.output/server/chunks/build/{logs-B-xhuqln.mjs.map → logs-BSOeZwwI.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-DFxjn9Xa.mjs} +33 -17
  88. package/.output/server/chunks/build/pages-DFxjn9Xa.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-qMPql9Se.mjs} +36 -10
  100. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +1 -0
  101. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs +1 -1
  102. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs +1 -1
  103. package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs +49 -0
  104. package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs.map +1 -0
  105. package/.output/server/chunks/build/{users-fohSmF-J.mjs → users-Cfd5-l2-.mjs} +6 -6
  106. package/.output/server/chunks/build/{users-fohSmF-J.mjs.map → users-Cfd5-l2-.mjs.map} +1 -1
  107. package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs → workshop-BL5bhfs0.mjs} +15 -15
  108. package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs.map → workshop-BL5bhfs0.mjs.map} +1 -1
  109. package/.output/server/chunks/build/{workspaces-BuydxM-3.mjs → workspaces-Bgz5Ekwp.mjs} +18 -3
  110. package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs.map +1 -0
  111. package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs.map +1 -1
  112. package/.output/server/chunks/routes/api/system/index.patch.mjs.map +1 -1
  113. package/.output/server/chunks/routes/api/system/index.post.mjs.map +1 -1
  114. package/.output/server/chunks/routes/api/system/settings/events.get.mjs.map +1 -1
  115. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +6 -2
  116. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs.map +1 -1
  117. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +2 -1
  118. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs.map +1 -1
  119. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  121. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs.map +1 -1
  122. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs.map +1 -1
  123. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs.map +1 -1
  124. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +3 -2
  125. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs.map +1 -1
  126. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +2 -1
  127. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs.map +1 -1
  128. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +8 -6
  129. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs.map +1 -1
  130. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +5 -5
  131. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs.map +1 -1
  132. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +14 -4
  133. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs.map +1 -1
  134. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +4 -4
  135. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs.map +1 -1
  136. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +4 -3
  137. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs.map +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +31 -0
  139. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs.map +1 -0
  140. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +28 -0
  141. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs.map +1 -0
  142. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs.map +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +32 -0
  144. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs.map +1 -0
  145. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +34 -0
  146. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs.map +1 -0
  147. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +33 -0
  148. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs.map +1 -0
  149. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +34 -0
  150. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs.map +1 -0
  151. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  152. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  153. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  154. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +2 -0
  159. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  160. package/.output/server/chunks/routes/api/workshop/index9.get.mjs +3 -1
  161. package/.output/server/chunks/routes/api/workshop/index9.get.mjs.map +1 -1
  162. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  167. package/.output/server/chunks/routes/renderer.mjs +3 -3
  168. package/.output/server/chunks/virtual/entry.mjs +51 -25
  169. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  170. package/.output/server/package.json +1 -1
  171. package/README-zh.md +199 -35
  172. package/README.md +739 -591
  173. package/app/app.vue +9 -3
  174. package/app/assets/css/main.css +517 -7
  175. package/app/components/AppHeader.vue +98 -7
  176. package/app/components/AppSidebar.vue +117 -11
  177. package/app/components/AwChart.vue +2 -1
  178. package/app/components/workshop/AgentInspectorDrawer.vue +6 -6
  179. package/app/components/workshop/AgentLanesView.vue +52 -1
  180. package/app/components/workshop/AssetLibrary.vue +4 -2
  181. package/app/components/workshop/ChannelSessionList.vue +4 -2
  182. package/app/components/workshop/Composer.vue +96 -0
  183. package/app/components/workshop/DaqNodeRow.vue +19 -0
  184. package/app/components/workshop/DaqTemplateIcon.vue +33 -0
  185. package/app/components/workshop/DeviceTwinPanel.vue +37 -35
  186. package/app/components/workshop/EventBlock.vue +3 -3
  187. package/app/components/workshop/InspectorPanel.vue +47 -11
  188. package/app/components/workshop/LiveAgentBar.vue +34 -0
  189. package/app/components/workshop/TaskBoardView.vue +83 -33
  190. package/app/components/workshop/TranscriptTimeline.vue +56 -0
  191. package/app/components/workshop/blocks/ClusterError.vue +1 -1
  192. package/app/components/workshop/blocks/ClusterRoute.vue +3 -1
  193. package/app/components/workshop/blocks/ClusterStatus.vue +2 -2
  194. package/app/components/workshop/blocks/ClusterStream.vue +8 -2
  195. package/app/components/workshop/town/ModelPreview3D.vue +1 -1
  196. package/app/components/workshop/town/TownView.vue +507 -82
  197. package/app/composables/useResponsive.ts +88 -0
  198. package/app/composables/useRouteMeta.ts +3 -0
  199. package/app/composables/workshop/useDcwStream.ts +1 -1
  200. package/app/composables/workshop/useVisibleInterval.ts +2 -2
  201. package/app/composables/workshop/useWorkshopApi.ts +1 -1
  202. package/app/layouts/default.vue +46 -2
  203. package/app/pages/aml/index.vue +712 -616
  204. package/app/pages/daq/[id].vue +3 -2
  205. package/app/pages/daq/index.vue +209 -9
  206. package/app/pages/dcw/[id].vue +109 -28
  207. package/app/pages/dcw/index.vue +83 -23
  208. package/app/pages/index.vue +35 -1
  209. package/app/pages/logs/index.vue +147 -13
  210. package/app/pages/monitor.vue +56 -12
  211. package/app/pages/permissions/index.vue +34 -0
  212. package/app/pages/plugins/index.vue +35 -4
  213. package/app/pages/settings.vue +90 -21
  214. package/app/pages/tokens.vue +23 -0
  215. package/app/pages/town.vue +50 -5
  216. package/app/pages/users.vue +23 -0
  217. package/app/pages/workshop/agents.vue +92 -2
  218. package/app/pages/workshop/channel-templates.vue +53 -0
  219. package/app/pages/workshop/index.vue +14 -0
  220. package/app/pages/workshop/teams.vue +66 -6
  221. package/app/pages/workshop/w/[wsId].vue +202 -14
  222. package/app/plugins/aw-plugins.client.ts +13 -8
  223. package/app/stores/app.ts +33 -1
  224. package/app/stores/workshop/user.ts +1 -1
  225. package/app/stores/workshop/workspaces.ts +18 -2
  226. package/app/utils/api-error.ts +1 -1
  227. package/cli/commands/build.mjs +8 -1
  228. package/docker-compose.yml +6 -0
  229. package/i18n/locales/en.ts +2 -0
  230. package/i18n/locales/zh-CN.ts +2 -0
  231. package/nuxt.config.ts +31 -0
  232. package/package.json +9 -1
  233. package/scripts/_dbg-param-map-e2e.ts +242 -0
  234. package/scripts/_dbg-param-map-rest-smoke.mjs +73 -0
  235. package/scripts/_dbg-param-real-scenario.mjs +153 -0
  236. package/scripts/api-live-e2e.mjs +40 -8
  237. package/scripts/capture-walkthrough.mjs +232 -0
  238. package/scripts/render-walkthrough-figure.mjs +46 -0
  239. package/scripts/seed-walkthrough-fixture.mjs +143 -0
  240. package/scripts/test-plugin-hardening.mjs +8 -0
  241. package/scripts/ui/admin-shots.mjs +46 -0
  242. package/scripts/ui/audit-layout.mjs +301 -0
  243. package/scripts/ui/bump-microfont.mjs +39 -0
  244. package/scripts/ui/lib.mjs +198 -0
  245. package/scripts/ui/linkcheck2.mjs +37 -0
  246. package/scripts/ui/live-render-check.mjs +72 -0
  247. package/scripts/ui/md-lint.mjs +80 -0
  248. package/scripts/ui/perf-audit.mjs +132 -0
  249. package/scripts/ui/readme-assets.mjs +30 -0
  250. package/scripts/ui/readme-shots.mjs +42 -0
  251. package/scripts/ui/record.mjs +225 -0
  252. package/scripts/ui/seed-admin.mjs +68 -0
  253. package/scripts/ui/seed-demo.mjs +83 -0
  254. package/scripts/ui/shot-hero.mjs +25 -0
  255. package/scripts/ui/shots.mjs +75 -0
  256. package/scripts/ui/sim-ui-shot.mjs +16 -0
  257. package/scripts/ui/verify-nav.mjs +51 -0
  258. package/scripts/ui/verify-responsive.mjs +285 -0
  259. package/server/api/system/config-groups/[id].patch.ts +1 -1
  260. package/server/api/system/config-groups/index.post.ts +1 -1
  261. package/server/api/system/settings/events.get.ts +1 -1
  262. package/server/api/system/settings/index.patch.ts +1 -1
  263. package/server/api/workshop/agent-tools/invoke.post.ts +16 -2
  264. package/server/api/workshop/aml/datasets/[id].get.ts +8 -1
  265. package/server/api/workshop/daq/infra/reconnect.post.ts +1 -1
  266. package/server/api/workshop/daq/templates/[key].delete.ts +1 -0
  267. package/server/api/workshop/daq/templates/[key].patch.ts +1 -0
  268. package/server/api/workshop/daq/templates/index.post.ts +1 -0
  269. package/server/api/workshop/dcw/[id]/write.post.ts +10 -2
  270. package/server/api/workshop/dcw/[id].delete.ts +5 -2
  271. package/server/api/workshop/dcw/index.get.ts +2 -0
  272. package/server/api/workshop/dcw/lines/[id].patch.ts +1 -1
  273. package/server/api/workshop/dcw/params/[id]/read.post.ts +17 -0
  274. package/server/api/workshop/dcw/params/[id]/write.post.ts +21 -0
  275. package/server/api/workshop/dcw/params/[id].delete.ts +19 -0
  276. package/server/api/workshop/dcw/params/[id].patch.ts +23 -0
  277. package/server/api/workshop/dcw/params/index.get.ts +27 -0
  278. package/server/api/workshop/dcw/params/index.post.ts +25 -0
  279. package/server/api/workshop/permissions/index.get.ts +3 -1
  280. package/server/plugins/aw-plugins.ts +14 -3
  281. package/server/plugins/dev-stability-guard.ts +3 -1
  282. package/server/plugins/system-config.ts +6 -1
  283. package/server/services/system-config.ts +28 -9
  284. package/server/services/workshop/agents/adapters/one-shot-cli-agent.ts +2 -1
  285. package/server/services/workshop/agents/agent-interface.ts +8 -6
  286. package/server/services/workshop/agents/base-agent.ts +18 -4
  287. package/server/services/workshop/agents/claude-agent.ts +7 -3
  288. package/server/services/workshop/agents/codex-agent.ts +3 -3
  289. package/server/services/workshop/agents/dsh-agent.ts +3 -3
  290. package/server/services/workshop/agents/goose-agent.ts +0 -1
  291. package/server/services/workshop/agents/harness-terminal.ts +14 -1
  292. package/server/services/workshop/agents/hermes-agent.ts +4 -2
  293. package/server/services/workshop/agents/host-tool-bridge.ts +5 -1
  294. package/server/services/workshop/agents/industrial-context.ts +4 -1
  295. package/server/services/workshop/agents/industrial-tools.ts +164 -6
  296. package/server/services/workshop/agents/omp-agent.ts +15 -9
  297. package/server/services/workshop/agents/opencode-agent.ts +47 -21
  298. package/server/services/workshop/agents/qwen-agent.ts +10 -5
  299. package/server/services/workshop/aml/entity.ts +3 -1
  300. package/server/services/workshop/aml/python-runtime.ts +3 -2
  301. package/server/services/workshop/daq/bus/mqtt.adapter.ts +8 -4
  302. package/server/services/workshop/daq/daq-controller.ts +18 -3
  303. package/server/services/workshop/daq/daq-runtime.ts +5 -3
  304. package/server/services/workshop/daq/drivers.ts +63 -8
  305. package/server/services/workshop/daq/host-ports.ts +3 -2
  306. package/server/services/workshop/daq/storage/timescale.adapter.ts +3 -1
  307. package/server/services/workshop/db/channel.repo.ts +4 -0
  308. package/server/services/workshop/db/ops.repo.ts +5 -2
  309. package/server/services/workshop/db/retention.ts +8 -3
  310. package/server/services/workshop/dcw/dcw-controller.ts +143 -16
  311. package/server/services/workshop/dcw/dcw-product.repo.ts +38 -1
  312. package/server/services/workshop/dcw/dcw-runtime.ts +3 -2
  313. package/server/services/workshop/dcw/drivers.ts +5 -2
  314. package/server/services/workshop/dcw/param-conversion.ts +78 -0
  315. package/server/services/workshop/dcw/param-limits.ts +142 -0
  316. package/server/services/workshop/dcw/param-map.repo.ts +305 -0
  317. package/server/services/workshop/dcw/recipe-rollback-manager.ts +13 -7
  318. package/server/services/workshop/runtime/agent-runtime.ts +4 -0
  319. package/server/services/workshop/runtime/manager.ts +9 -2
  320. package/server/services/workshop/settings.ts +4 -2
  321. package/server/types/pg.d.ts +49 -0
  322. package/server/utils/stability-severity.ts +13 -2
  323. package/shared/config/groups.mjs +22 -1
  324. package/shared/config/home.d.mts +28 -0
  325. package/shared/dcw-protocol.ts +136 -0
  326. package/uno.config.ts +12 -1
  327. package/.output/public/_nuxt/BFkgkW8u.js +0 -4
  328. package/.output/public/_nuxt/C5gUkRwn.js +0 -1
  329. package/.output/public/_nuxt/CM12FuB42.js +0 -1
  330. package/.output/public/_nuxt/CZiw7sEV.js +0 -1
  331. package/.output/public/_nuxt/Cc_9VzD42.js +0 -1
  332. package/.output/public/_nuxt/CdPzkOMV.js +0 -1
  333. package/.output/public/_nuxt/CmsOtOAA2.js +0 -1
  334. package/.output/public/_nuxt/D1YDxZpZ.js +0 -1
  335. package/.output/public/_nuxt/D20Yamr4.js +0 -1
  336. package/.output/public/_nuxt/DmqknNvV.js +0 -18
  337. package/.output/public/_nuxt/DnUuIR83.js +0 -1
  338. package/.output/public/_nuxt/S_fVQwJK.js +0 -1
  339. package/.output/public/_nuxt/TownView.0oLiQgTC.css +0 -1
  340. package/.output/public/_nuxt/_id_.D-SYuyZb.css +0 -1
  341. package/.output/public/_nuxt/_id_.hma_BU6T.css +0 -1
  342. package/.output/public/_nuxt/_wsId_.B12v3PhY.css +0 -1
  343. package/.output/public/_nuxt/agents.BV70ioCv.css +0 -1
  344. package/.output/public/_nuxt/ahgEJu2R.js +0 -1
  345. package/.output/public/_nuxt/aml.COEKNNqK.css +0 -1
  346. package/.output/public/_nuxt/builds/meta/4457d63f-0866-4cea-b320-04a321f24795.json +0 -1
  347. package/.output/public/_nuxt/channel-templates.C-M5nKcC.css +0 -1
  348. package/.output/public/_nuxt/daq.B3265ZDn.css +0 -1
  349. package/.output/public/_nuxt/dcw.DLYZqEhT.css +0 -1
  350. package/.output/public/_nuxt/default.BXI_fZUi.css +0 -1
  351. package/.output/public/_nuxt/entry.BB-Eeh-J.css +0 -1
  352. package/.output/public/_nuxt/logs.CqFyMrJw.css +0 -1
  353. package/.output/public/_nuxt/monitor.B--1dPzs.css +0 -1
  354. package/.output/public/_nuxt/pages.ooEzrMm9.css +0 -1
  355. package/.output/public/_nuxt/permissions.B1pjGChZ.css +0 -1
  356. package/.output/public/_nuxt/plugins.5e5chZ67.css +0 -1
  357. package/.output/public/_nuxt/pz2JDAM7.js +0 -2
  358. package/.output/public/_nuxt/settings.Dm3M0RLy.css +0 -1
  359. package/.output/public/_nuxt/teams.BC75EGnR.css +0 -1
  360. package/.output/public/_nuxt/tokens.xRb1fSPU.css +0 -1
  361. package/.output/public/_nuxt/users.CXnP2E08.css +0 -1
  362. package/.output/public/_nuxt/workshop.Jk5HxWPE.css +0 -1
  363. package/.output/server/chunks/build/TownView-jVazYOjN.mjs.map +0 -1
  364. package/.output/server/chunks/build/_id_-Hwr4rZpr.mjs.map +0 -1
  365. package/.output/server/chunks/build/agents-Bvin73QY.mjs.map +0 -1
  366. package/.output/server/chunks/build/daq-C03Uu5UE.mjs.map +0 -1
  367. package/.output/server/chunks/build/pages-BO7yZsXA.mjs.map +0 -1
  368. package/.output/server/chunks/build/settings-DoQ2Oq3P.mjs.map +0 -1
  369. package/.output/server/chunks/build/teams-2kz3CusY.mjs.map +0 -1
  370. package/.output/server/chunks/build/town-BOl6hs9B.mjs.map +0 -1
  371. package/.output/server/chunks/build/workspaces-BuydxM-3.mjs.map +0 -1
  372. /package/.output/public/_nuxt/{36oeS8_R2.js → 36oeS8_R.js} +0 -0
  373. /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,198 @@
1
+ /**
2
+ * UI 视觉走查公共库 —— 真实浏览器(puppeteer-core + 本机 Chrome)驱动的
3
+ * 截图 / 自适应审计 / 录屏(GIF)统一入口。
4
+ *
5
+ * 为什么集中在一处:此前 20+ 个 _dbg-*.mjs 各写一遍 launch/login/screenshot,
6
+ * 浏览器路径、登录方式、等待策略各不一致,改一处要改二十处。这里收敛为
7
+ * 一个可复用对象(huashu-design「可复用优先」+ 项目 hygiene 要求)。
8
+ */
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import puppeteer from 'puppeteer-core'
12
+
13
+ export const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3021'
14
+ export const CHROME = process.env.AW_CHROME
15
+ ?? 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
16
+
17
+ export const VISUAL_USER = {
18
+ name: 'visual',
19
+ email: 'visual@awshop.local',
20
+ password: 'Visual2026',
21
+ }
22
+
23
+ export const sleep = ms => new Promise(r => setTimeout(r, ms))
24
+
25
+ /** 站点全部页面(与 app/pages 一一对应) —— 自适应审计矩阵的行 */
26
+ export const ROUTES = [
27
+ { path: '/', name: 'dashboard', title: '仪表盘' },
28
+ { path: '/workshop', name: 'workshop', title: 'Agent 工作台' },
29
+ { path: '/workshop/agents', name: 'agents', title: 'Agent 管理' },
30
+ { path: '/workshop/teams', name: 'teams', title: '团队/频道' },
31
+ { path: '/workshop/channel-templates', name: 'templates', title: '频道模板' },
32
+ { path: '/monitor', name: 'monitor', title: '实时监控 & HITL' },
33
+ { path: '/daq', name: 'daq', title: '数采中心' },
34
+ { path: '/dcw', name: 'dcw', title: '产线作业' },
35
+ { path: '/aml', name: 'aml', title: '自动建模实验室' },
36
+ { path: '/town', name: 'town', title: '数字孪生' },
37
+ { path: '/logs', name: 'logs', title: '审计日志' },
38
+ { path: '/permissions', name: 'permissions', title: '产线权限' },
39
+ { path: '/plugins', name: 'plugins', title: '插件' },
40
+ { path: '/settings', name: 'settings', title: '设置' },
41
+ { path: '/tokens', name: 'tokens', title: 'API Token' },
42
+ { path: '/users', name: 'users', title: '用户' },
43
+ ]
44
+
45
+ /** 自适应验收视口矩阵:从 iPhone SE 到超宽屏,含横屏与平板 */
46
+ export const VIEWPORTS = [
47
+ { name: 'xs-320', width: 320, height: 720, label: 'iPhone SE(竖)' },
48
+ { name: 'sm-390', width: 390, height: 844, label: 'iPhone 14(竖)' },
49
+ { name: 'mobile-land-844', width: 844, height: 390, label: '手机横屏' },
50
+ { name: 'tablet-768', width: 768, height: 1024, label: 'iPad(竖)' },
51
+ { name: 'laptop-1280', width: 1280, height: 800, label: '笔记本' },
52
+ { name: 'desktop-1440', width: 1440, height: 900, label: '桌面' },
53
+ { name: 'wide-1920', width: 1920, height: 1080, label: '宽屏' },
54
+ ]
55
+
56
+ /** REST 调用(JSON envelope 解包) */
57
+ export async function api(method, endpoint, { body, token } = {}) {
58
+ const headers = { 'content-type': 'application/json' }
59
+ if (token) headers.authorization = `Bearer ${token}`
60
+ const res = await fetch(`${BASE}${endpoint}`, {
61
+ method,
62
+ headers,
63
+ body: body === undefined ? undefined : JSON.stringify(body),
64
+ })
65
+ const text = await res.text()
66
+ try {
67
+ return JSON.parse(text)
68
+ }
69
+ catch {
70
+ return { code: -1, message: `HTTP ${res.status}: ${text.slice(0, 200)}`, data: null }
71
+ }
72
+ }
73
+
74
+ /** 确保存在一个可登录的视觉走查账号(admin 角色,便于看到全部页面真实数据) */
75
+ export async function ensureVisualUser() {
76
+ let login = await api('POST', '/api/users/login', {
77
+ body: { email: VISUAL_USER.email, password: VISUAL_USER.password },
78
+ })
79
+ if (login.code === 0 && login.data?.token) return login.data.token
80
+
81
+ const reg = await api('POST', '/api/users/register', {
82
+ body: { name: VISUAL_USER.name, email: VISUAL_USER.email, password: VISUAL_USER.password },
83
+ })
84
+ if (reg.code === 0 && reg.data?.token) return reg.data.token
85
+
86
+ // 已存在但密码不同 → 直接改库(仅测试种子用途)
87
+ const { DatabaseSync } = await import('node:sqlite')
88
+ const db = new DatabaseSync('.AgentWorkShop/data/users.sqlite')
89
+ db.prepare('update users set role=\'admin\', status=\'active\' where email=?').run(VISUAL_USER.email)
90
+ db.close()
91
+ login = await api('POST', '/api/users/login', {
92
+ body: { email: VISUAL_USER.email, password: VISUAL_USER.password },
93
+ })
94
+ if (login.code === 0 && login.data?.token) return login.data.token
95
+ throw new Error(`无法取得视觉走查账号 token: ${login.message}`)
96
+ }
97
+
98
+ /** 把账号提升为 admin(测试种子;生产数据不受影响,只改这一行) */
99
+ export async function promoteToAdmin() {
100
+ const { DatabaseSync } = await import('node:sqlite')
101
+ const db = new DatabaseSync('.AgentWorkShop/data/users.sqlite')
102
+ db.prepare('update users set role=\'admin\' where email=?').run(VISUAL_USER.email)
103
+ db.close()
104
+ }
105
+
106
+ export async function launch({ width = 1440, height = 900 } = {}) {
107
+ return puppeteer.launch({
108
+ executablePath: CHROME,
109
+ headless: true,
110
+ args: [
111
+ '--no-sandbox',
112
+ '--disable-dev-shm-usage',
113
+ '--hide-scrollbars',
114
+ '--force-color-profile=srgb',
115
+ '--font-render-hinting=none',
116
+ ],
117
+ defaultViewport: { width, height, deviceScaleFactor: 2 },
118
+ })
119
+ }
120
+
121
+ /**
122
+ * 打开一个已登录页面。
123
+ * 主题必须同时写 cookie(SSR 首帧)与 localStorage(pinia 持久化),
124
+ * 只写其一会导致首帧闪色 + antd cssinjs 不重注入(历史踩坑)。
125
+ */
126
+ export async function openPage(browser, {
127
+ token, dark = true, width = 1440, height = 900, deviceScaleFactor = 2, touch,
128
+ } = {}) {
129
+ const page = await browser.newPage()
130
+ // 窄视口默认按**触摸设备**仿真:不这么做,Chromium 报告 pointer:fine →
131
+ // 全局层 @media (pointer: coarse) 的 40px 触摸目标规则根本不生效,
132
+ // 审计会把"桌面鼠标语境下的紧凑按钮"误判成"手机上点不中"(实测踩过)。
133
+ const isTouch = touch ?? width <= 900
134
+ await page.setViewport({
135
+ width, height, deviceScaleFactor, isMobile: isTouch, hasTouch: isTouch,
136
+ })
137
+ if (token) {
138
+ await page.setCookie({ name: 'token', value: token, domain: '127.0.0.1', path: '/' })
139
+ }
140
+ await page.setCookie({
141
+ name: 'aw-theme', value: dark ? 'dark' : 'light', domain: '127.0.0.1', path: '/',
142
+ })
143
+ await page.evaluateOnNewDocument((isDark) => {
144
+ try {
145
+ localStorage.setItem('app', JSON.stringify({
146
+ isDark, sidebarCollapsed: false, accent: null, themeTouched: true,
147
+ }))
148
+ }
149
+ catch { /* 页面跳转瞬间可能失败,跳过该帧 */ }
150
+ }, dark)
151
+ return page
152
+ }
153
+
154
+ /**
155
+ * 导航到某页并**等到客户端真正水合完成**。
156
+ *
157
+ * 为什么不能只等 domcontentloaded + sleep:dev 模式下首个请求要现场编译客户端
158
+ * 包(实测冷启动 >9s),此时页面只是 SSR 快照 —— 主题、断点、图表全都没接管。
159
+ * 拿这种快照做视觉验收,会把"还没水合"误判成"设计有问题"(踩过一次)。
160
+ *
161
+ * 水合判据用 <html data-vp-tier>:它由 useResponsive 在客户端 watchEffect 里写入,
162
+ * 出现即证明客户端 setup 已跑、响应式判据已生效。
163
+ */
164
+ export async function gotoReady(page, path, { wait = 2500, timeout = 90000 } = {}) {
165
+ await page.goto(BASE + path, { waitUntil: 'domcontentloaded', timeout: 60000 })
166
+ await page.waitForFunction(
167
+ () => document.documentElement.dataset.vpTier !== undefined,
168
+ { timeout, polling: 200 },
169
+ )
170
+ await sleep(wait)
171
+ return page
172
+ }
173
+
174
+ /** 逐帧录屏器:采集 PNG 序列后交给 ffmpeg 合成 GIF(palette 优化) */
175
+ export function createRecorder(page, { dir, fps = 12, maxFrames = 400 }) {
176
+ fs.mkdirSync(dir, { recursive: true })
177
+ for (const f of fs.readdirSync(dir)) fs.rmSync(path.join(dir, f), { force: true })
178
+ let n = 0
179
+ let stopped = false
180
+ const tick = async () => {
181
+ if (stopped || n >= maxFrames) return
182
+ const i = n++
183
+ try {
184
+ await page.screenshot({ path: path.join(dir, `f${String(i).padStart(5, '0')}.png`), type: 'png' })
185
+ }
186
+ catch { /* 页面跳转瞬间可能失败,跳过该帧 */ }
187
+ }
188
+ const timer = setInterval(tick, Math.round(1000 / fps))
189
+ return {
190
+ dir,
191
+ frames: () => n,
192
+ async stop() {
193
+ stopped = true
194
+ clearInterval(timer)
195
+ },
196
+ tick,
197
+ }
198
+ }
@@ -0,0 +1,37 @@
1
+ const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3021'
2
+ const api = async (m, p, body, token) => {
3
+ const h = { 'content-type': 'application/json' }
4
+ if (token) h.authorization = 'Bearer ' + token
5
+ const r = await fetch(BASE + p, { method: m, headers: h, body: body ? JSON.stringify(body) : undefined })
6
+ const t = await r.text()
7
+ try {
8
+ return JSON.parse(t)
9
+ }
10
+ catch {
11
+ return { code: -1, message: 'HTTP ' + r.status }
12
+ }
13
+ }
14
+ const token = (await api('POST', '/api/users/login', { email: 'admin@awshop.local', password: 'admin123' })).data.token
15
+ const nodes = (await api('GET', '/api/workshop/daq', null, token)).data.nodes
16
+ const simPorts = [16040, 15041, 5840, 18830, 4010]
17
+ const sim = nodes.filter(n => simPorts.some(p => JSON.stringify(n).includes(String(p))))
18
+ const good = sim.filter(n => n.state === 'ok' || n.state === 'warn')
19
+ console.log('在线且有值的模拟器节点:', good.length, '/', sim.length)
20
+ const seen = new Set()
21
+ for (const n of good) {
22
+ const key = n.driver
23
+ if (seen.has(key) && seen.size > 4) continue
24
+ seen.add(key)
25
+ const c = n.config ?? {}
26
+ console.log(' ' + String(n.name).padEnd(22) + String(n.driver).padEnd(12) + 'state=' + String(n.state).padEnd(6) + 'val=' + String(n.value).padStart(10) + ' ' + String(n.unit ?? '').padEnd(6) + '| ' + JSON.stringify({ host: c.host, port: c.port, unitId: c.unitId, register: c.register, dataType: c.dataType, topic: c.topic, nodeId: c.nodeId, url: c.url }).slice(0, 150))
27
+ }
28
+ console.log('')
29
+ console.log('按驱动看最后更新时间(判断是否还在采):')
30
+ const byDrv = {}
31
+ for (const n of good) {
32
+ byDrv[n.driver] = byDrv[n.driver] ?? { n: 0, last: '' }
33
+ byDrv[n.driver].n++
34
+ const t = n.lastSampleAt ?? n.updatedAt ?? ''
35
+ if (t > byDrv[n.driver].last) byDrv[n.driver].last = t
36
+ }
37
+ for (const [k, v] of Object.entries(byDrv)) console.log(' ' + k.padEnd(14) + ' 在线 ' + String(v.n).padStart(4) + ' 最近采样 ' + v.last)
@@ -0,0 +1,72 @@
1
+ /**
2
+ * 验证"PLC 模拟数据 → 主项目前端"是真·实时渲染(而不是打开时拍一张快照)。
3
+ *
4
+ * 做法:同一个页面停留 15 秒,取两次 DOM 文本做逐字符 diff。
5
+ * 只在有值变化时才算通过 —— 这是"实时"唯一诚实的判据。
6
+ *
7
+ * 用法: node scripts/ui/live-render-check.mjs [--route daq] [--secs 15]
8
+ */
9
+ import { launch, openPage, gotoReady, sleep } from './lib.mjs'
10
+
11
+ const argv = process.argv.slice(2)
12
+ const arg = (k, d) => {
13
+ const i = argv.indexOf('--' + k)
14
+ return i >= 0 ? argv[i + 1] : d
15
+ }
16
+ // 路由要归一成以 / 开头:BASE + 'daq' 会拼出 http://127.0.0.1:3021daq(非法 URL)
17
+ const ROUTE = '/' + arg('route', 'daq').replace(/^\/+/, '')
18
+ const SECS = Number(arg('secs', 15))
19
+ const FILTER = arg('filter', '-cfA')
20
+ const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3021'
21
+
22
+ const login = await (await fetch(BASE + '/api/users/login', {
23
+ method: 'POST',
24
+ headers: { 'content-type': 'application/json' },
25
+ body: JSON.stringify({ email: 'admin@awshop.local', password: 'admin123' }),
26
+ })).json()
27
+ const token = login.data.token
28
+ const browser = await launch()
29
+ const page = await openPage(browser, { token, dark: true, width: 1440, height: 900, deviceScaleFactor: 1 })
30
+ await gotoReady(page, ROUTE, { wait: 8000 })
31
+
32
+ if (FILTER) {
33
+ await page.evaluate((f) => {
34
+ const input = document.querySelector('input[placeholder*="节点名称"]')
35
+ || Array.from(document.querySelectorAll('input')).find(i => /搜索|search/i.test(i.placeholder || ''))
36
+ if (!input) return
37
+ const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set
38
+ set.call(input, f)
39
+ input.dispatchEvent(new Event('input', { bubbles: true }))
40
+ }, FILTER)
41
+ await sleep(3000)
42
+ }
43
+
44
+ // 滚到节点表,保证读的是表内容
45
+ await page.evaluate(() => document.querySelector('.nodes-table')?.scrollIntoView({ block: 'start' }))
46
+ await sleep(1500)
47
+
48
+ const grab = () => page.evaluate(() => {
49
+ const t = document.querySelector('.nodes-table') || document.body
50
+ return t.innerText.replace(/\s+/g, ' ')
51
+ })
52
+ const a = await grab()
53
+ await sleep(SECS * 1000)
54
+ const c = await grab()
55
+
56
+ let diff = 0
57
+ const spots = []
58
+ for (let i = 0; i < Math.max(a.length, c.length); i++) {
59
+ if (a[i] !== c[i]) {
60
+ diff++
61
+ if (spots.length < 8) spots.push(i)
62
+ }
63
+ }
64
+ console.log('路由:', ROUTE, ' 过滤:', FILTER || '(无)', ' 观察窗口:', SECS + 's')
65
+ console.log('文本长度:', a.length, '->', c.length, ' 变化字符数:', diff)
66
+ console.log('判据:', diff > 0 ? '✔ 实时渲染(值在变)' : '✘ 静止(只是快照)')
67
+ console.log('')
68
+ for (const i of spots.slice(0, 6)) {
69
+ console.log(' ' + a.slice(Math.max(0, i - 24), i + 24).trim() + ' => ' + c.slice(Math.max(0, i - 24), i + 24).trim())
70
+ }
71
+ await page.screenshot({ path: `.e2e-shots/admin/live-${ROUTE.replace(/\//g, '_')}.png` })
72
+ await browser.close()