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,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()
@@ -0,0 +1,80 @@
1
+ /**
2
+ * README 结构自检:标签配平、图片/链接存在、模板残留、表格列数一致。
3
+ * GitHub 的渲染器很宽容,写坏了不报错 —— 只会在页面上难看,所以得自己查。
4
+ */
5
+ import fs from 'node:fs'
6
+ import path from 'node:path'
7
+
8
+ const files = process.argv.slice(2)
9
+ const FENCE = String.fromCharCode(96).repeat(3)
10
+ let bad = 0
11
+ const fail = (f, m) => {
12
+ bad += 1
13
+ console.log(' x ' + f + ': ' + m)
14
+ }
15
+
16
+ for (const f of files) {
17
+ const t = fs.readFileSync(f, 'utf8')
18
+ const ls = t.split(/\r?\n/)
19
+ console.log('·', f)
20
+
21
+ // 转义残留:只把'转义了不该转义的字符'当问题(表格里的 \\| 是合法的)
22
+ const esc = ls.findIndex(l => l.includes('\\' + String.fromCharCode(96)))
23
+ if (esc >= 0) fail(f, 'line ' + (esc + 1) + ' 存在反引号转义残留')
24
+
25
+ // ${...} 只在不处于围栏代码块里时才算未求值模板(shell 示例里的 ${VAR} 合法)
26
+ {
27
+ let fenced = false
28
+ let hit = -1
29
+ for (let i = 0; i < ls.length; i++) {
30
+ if (ls[i].trim().startsWith(FENCE)) {
31
+ fenced = !fenced
32
+ continue
33
+ }
34
+ if (!fenced && /\$\{[a-zA-Z_]/.test(ls[i])) {
35
+ hit = i
36
+ break
37
+ }
38
+ }
39
+ if (hit >= 0) fail(f, 'line ' + (hit + 1) + ' 存在未求值的模板占位')
40
+ }
41
+
42
+ // 标签配平
43
+ for (const tag of ['div', 'table', 'tr', 'td', 'details', 'summary', 'picture', 'sub', 'code', 'a']) {
44
+ const open = (t.match(new RegExp('<' + tag + '(\\s|>)', 'g')) || []).length
45
+ const close = (t.match(new RegExp('</' + tag + '>', 'g')) || []).length
46
+ const self = (t.match(new RegExp('<' + tag + '[^>]*/>', 'g')) || []).length
47
+ if (open - self !== close) fail(f, '<' + tag + '> 不配平: open=' + open + ' self=' + self + ' close=' + close)
48
+ }
49
+
50
+ // 本地图片 / 链接存在
51
+ for (const m of t.matchAll(/<img[^>]+src="([^"]+)"/g)) {
52
+ const src = m[1]
53
+ if (/^https?:/.test(src)) continue
54
+ const abs = path.resolve(path.dirname(f), src.replace(/^\.\//, ''))
55
+ if (!fs.existsSync(abs)) fail(f, '图片不存在: ' + src)
56
+ }
57
+ for (const m of t.matchAll(/\]\((\.\/[^)#]+)\)/g)) {
58
+ const abs = path.resolve(path.dirname(f), m[1].replace(/^\.\//, ''))
59
+ if (!fs.existsSync(abs)) fail(f, '链接目标不存在: ' + m[1])
60
+ }
61
+
62
+ // 表格列数一致(转义管道符不算分隔)
63
+ const cells = row => row.replace(/\\\|/g, '\u0000').split('|').length
64
+ let cols = null, start = 0
65
+ for (let i = 0; i <= ls.length; i++) {
66
+ const isRow = i < ls.length && /^\s*\|.*\|\s*$/.test(ls[i])
67
+ if (!isRow) {
68
+ cols = null
69
+ continue
70
+ }
71
+ const n = cells(ls[i])
72
+ if (cols === null) {
73
+ cols = n
74
+ start = i
75
+ }
76
+ else if (n !== cols) fail(f, 'line ' + (i + 1) + ' 表格列数 ' + n + ' != 表头 ' + cols + '(表从 line ' + (start + 1) + ' 起)')
77
+ }
78
+ }
79
+ console.log(bad ? '\n' + bad + ' 个问题' : '\n全部通过')
80
+ process.exit(bad ? 1 : 0)
@@ -0,0 +1,132 @@
1
+ /**
2
+ * 前端实时链路性能体检 —— 用真实浏览器量"数据刷新是否把主线程压垮"。
3
+ *
4
+ * 量的是四件事(全部来自运行时,不是估算):
5
+ * 1. Long Task:>50ms 的主线程阻塞任务数 与 总阻塞时长(TBT)
6
+ * 2. FPS:用 requestAnimationFrame 采样的真实帧率 + 掉帧比例
7
+ * 3. DOM 变更速率:每秒多少次节点/属性变更(重渲染churn 的直接代理指标)
8
+ * 4. CDP Performance 指标:ScriptDuration / LayoutDuration / RecalcStyleDuration 的增量
9
+ *
10
+ * 用法: node scripts/ui/perf-audit.mjs [--routes daq,town,dashboard] [--secs 20] [--vp 1440x900]
11
+ */
12
+ import { ensureVisualUser, launch, openPage, gotoReady, sleep, ROUTES } from './lib.mjs'
13
+
14
+ const argv = process.argv.slice(2)
15
+ const arg = (k, d) => {
16
+ const i = argv.indexOf(`--${k}`)
17
+ return i >= 0 ? argv[i + 1] : d
18
+ }
19
+ const ONLY = arg('routes', 'daq,town,dashboard,monitor')
20
+ const SECS = Number(arg('secs', 20))
21
+ const [W, H] = arg('vp', '1440x900').split('x').map(Number)
22
+
23
+ const picks = ROUTES.filter(r => ONLY.split(',').includes(r.name))
24
+ // 频道会话台(/workshop/w/<id>)不在静态 ROUTES 里,但它是事件最密的页面 ——
25
+ // 用真实 workspace id 合成一条,才能量到"Agent 事件流是否压垮主线程"。
26
+
27
+ const token = await ensureVisualUser()
28
+ // 频道会话台(/workshop/w/<id>)不在静态 ROUTES 里,但它是事件最密的页面 ——
29
+ // 用真实 workspace id 合成一条,才能量到"Agent 事件流是否压垮主线程"。
30
+ if (ONLY.includes('console')) {
31
+ const { api } = await import('./lib.mjs')
32
+ const ws = await api('GET', '/api/workshop/workspaces', { token })
33
+ const list = ws.data?.workspaces ?? ws.data ?? []
34
+ const wid = (Array.isArray(list) ? list[0] : null)?.id
35
+ if (wid) picks.push({ path: `/workshop/w/${wid}`, name: 'console', title: '频道会话台' })
36
+ else console.log('!! 没有可用工作区,跳过 console(先跑 scripts/ui/seed-demo.mjs)')
37
+ }
38
+ const browser = await launch()
39
+
40
+ const INSTRUMENT = () => {
41
+ const w = window
42
+ w.__perf = { longTasks: 0, tbt: 0, frames: 0, slowFrames: 0, mutations: 0, last: performance.now(), maxGap: 0 }
43
+ try {
44
+ new PerformanceObserver((list) => {
45
+ for (const e of list.getEntries()) {
46
+ w.__perf.longTasks++
47
+ w.__perf.tbt += Math.max(0, e.duration - 50)
48
+ }
49
+ }).observe({ entryTypes: ['longtask'] })
50
+ }
51
+ catch { /* 不支持 longtask 就只靠 FPS */ }
52
+ const mo = new MutationObserver((recs) => {
53
+ w.__perf.mutations += recs.length
54
+ })
55
+ mo.observe(document.body, { childList: true, subtree: true, attributes: true, characterData: true })
56
+ const tick = (t) => {
57
+ const p = w.__perf
58
+ const gap = t - p.last
59
+ p.last = t
60
+ if (p.frames > 0) {
61
+ if (gap > p.maxGap) p.maxGap = gap
62
+ if (gap > 33) p.slowFrames++
63
+ }
64
+ p.frames++
65
+ requestAnimationFrame(tick)
66
+ }
67
+ requestAnimationFrame(tick)
68
+ }
69
+
70
+ const rows = []
71
+ for (const r of picks) {
72
+ const page = await openPage(browser, { token, dark: true, width: W, height: H, deviceScaleFactor: 1 })
73
+ const errs = []
74
+ page.on('pageerror', e => errs.push(String(e.message).slice(0, 140)))
75
+ try {
76
+ await gotoReady(page, r.path, { wait: 4000 })
77
+ await page.evaluate(INSTRUMENT)
78
+ const client = await page.createCDPSession()
79
+ await client.send('Performance.enable')
80
+ const m0 = await client.send('Performance.getMetrics')
81
+ const t0 = Date.now()
82
+
83
+ await sleep(SECS * 1000)
84
+
85
+ const m1 = await client.send('Performance.getMetrics')
86
+ const p = await page.evaluate(() => ({ ...window.__perf }))
87
+ const metric = (name) => {
88
+ const a = m0.metrics.find(x => x.name === name)?.value ?? 0
89
+ const b = m1.metrics.find(x => x.name === name)?.value ?? 0
90
+ return b - a
91
+ }
92
+ const secs = (Date.now() - t0) / 1000
93
+ const row = {
94
+ route: r.name,
95
+ secs: +secs.toFixed(1),
96
+ fps: +(p.frames / secs).toFixed(1),
97
+ slowFramePct: +((p.slowFrames / Math.max(1, p.frames)) * 100).toFixed(1),
98
+ maxGapMs: Math.round(p.maxGap),
99
+ longTasks: p.longTasks,
100
+ tbtMs: Math.round(p.tbt),
101
+ mutPerSec: Math.round(p.mutations / secs),
102
+ scriptMs: Math.round(metric('ScriptDuration') * 1000),
103
+ layoutMs: Math.round(metric('LayoutDuration') * 1000),
104
+ styleMs: Math.round(metric('RecalcStyleDuration') * 1000),
105
+ nodes: Math.round(metric('Nodes')),
106
+ listeners: Math.round(metric('JSEventListeners')),
107
+ pageErrors: errs.length,
108
+ }
109
+ rows.push(row)
110
+ console.log(JSON.stringify(row))
111
+ }
112
+ catch (e) {
113
+ console.log(r.name, 'FAIL', String(e.message).slice(0, 120))
114
+ }
115
+ await page.close()
116
+ }
117
+ await browser.close()
118
+ console.log('\n路由 FPS 掉帧% 最大间隔 长任务 阻塞ms DOM变更/s 脚本ms 布局ms 样式ms')
119
+ for (const r of rows) {
120
+ console.log(
121
+ r.route.padEnd(12),
122
+ String(r.fps).padStart(5),
123
+ String(r.slowFramePct).padStart(6),
124
+ String(r.maxGapMs).padStart(8),
125
+ String(r.longTasks).padStart(7),
126
+ String(r.tbtMs).padStart(7),
127
+ String(r.mutPerSec).padStart(10),
128
+ String(r.scriptMs).padStart(7),
129
+ String(r.layoutMs).padStart(7),
130
+ String(r.styleMs).padStart(7),
131
+ )
132
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * README / 文档站素材一键重建。
3
+ *
4
+ * 顺序不能换:截图 → GIF → hero。hero.html 里内嵌的是同目录的 town.png / daq.png,
5
+ * 所以 hero 必须最后渲,否则首屏会用到上一轮的界面截图(实测踩过)。
6
+ *
7
+ * node scripts/ui/readme-assets.mjs [--skip-gif]
8
+ */
9
+ import fs from 'node:fs'
10
+ import { execFileSync } from 'node:child_process'
11
+
12
+ const skipGif = process.argv.includes('--skip-gif')
13
+ const run = (script, args = []) => {
14
+ console.log('\n▶ node ' + script + ' ' + args.join(' '))
15
+ execFileSync(process.execPath, [script, ...args], { stdio: 'inherit' })
16
+ }
17
+
18
+ run('scripts/ui/readme-shots.mjs')
19
+
20
+ if (!skipGif) {
21
+ for (const story of ['agents', 'daq', 'dcw', 'town', 'dashboard', 'responsive']) {
22
+ const num = { agents: '01-closedloop', daq: '02-daq', dcw: '03-dcw', town: '04-town', dashboard: '05-dashboard', responsive: '06-responsive' }[story]
23
+ // record.mjs 按剧本名输出 <story>.gif,这里落成 README 里的 fig-NN-*.gif 编号名
24
+ run('scripts/ui/record.mjs', [story, '--out', 'docs/readme-assets', '--gifw', '1000'])
25
+ fs.renameSync('docs/readme-assets/' + story + '.gif', 'docs/readme-assets/fig-' + num + '.gif')
26
+ }
27
+ }
28
+
29
+ run('scripts/ui/shot-hero.mjs')
30
+ console.log('\n✔ README 素材重建完成')
@@ -0,0 +1,42 @@
1
+ /**
2
+ * README 用真实截图(与 record.mjs 的 GIF 配套)。
3
+ * 全部 1600×1000 @2x 出图,固定暗色控制室主题 —— README 首屏是深色语境,
4
+ * 混入亮色截图会让整页忽明忽暗。
5
+ */
6
+ import path from 'node:path'
7
+ import { ensureVisualUser, launch, openPage, gotoReady } from './lib.mjs'
8
+
9
+ const OUT = 'docs/readme-assets'
10
+ const W = 1600
11
+ const H = 1000
12
+
13
+ const SHOTS = [
14
+ { file: 'town.png', route: '/town', wait: 11000 },
15
+ { file: 'daq.png', route: '/daq', wait: 8000 },
16
+ { file: 'shot-workshop.png', route: '/workshop', wait: 7000 },
17
+ { file: 'shot-monitor.png', route: '/monitor', wait: 6000 },
18
+ { file: 'shot-settings.png', route: '/settings', wait: 6000 },
19
+ { file: 'shot-plugins.png', route: '/plugins', wait: 6000 },
20
+ { file: 'shot-dcw.png', route: '/dcw', wait: 7000 },
21
+ { file: 'shot-aml.png', route: '/aml', wait: 7000 },
22
+ { file: 'shot-logs.png', route: '/logs', wait: 6000 },
23
+ ]
24
+
25
+ const token = await ensureVisualUser()
26
+ const browser = await launch()
27
+ for (const s of SHOTS) {
28
+ const page = await openPage(browser, { token, dark: true, width: W, height: H, deviceScaleFactor: 2 })
29
+ const errs = []
30
+ page.on('pageerror', e => errs.push(String(e.message).slice(0, 120)))
31
+ try {
32
+ await gotoReady(page, s.route, { wait: s.wait })
33
+ await page.screenshot({ path: path.join(OUT, s.file) })
34
+ console.log(' ok ' + s.file.padEnd(22) + s.route + (errs.length ? ' errs=' + errs.length : ''))
35
+ }
36
+ catch (e) {
37
+ console.log(' FAIL ' + s.file + ' — ' + String(e.message).slice(0, 120))
38
+ }
39
+ await page.close()
40
+ }
41
+ await browser.close()
42
+ console.log('-> ' + OUT)