agentworkshop 0.7.40 → 0.7.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (327) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -0,0 +1,418 @@
1
+ /**
2
+ * 回归测试 —— serial-bridge 串口协议插件(纯协议层 + RTU 事务客户端 + 假端口驱动契约 + 插件入口契约)。
3
+ * 运行: node scripts/test-serial-bridge.mjs
4
+ * 退出码: 0 全通过 / 1 有失败
5
+ *
6
+ * 无需真实串口硬件:RTU 客户端(rtu.mjs)以假 SerialPort(内存从站应答)驱动,
7
+ * 驱动工厂(drivers.mjs)以注入 opener 构建,ASCII 行用假行端口;插件入口用桩 ctx
8
+ * 验证注册契约与 API 路由。
9
+ */
10
+ import { Buffer } from 'node:buffer'
11
+ import {
12
+ crc16Modbus,
13
+ buildRtuFrame,
14
+ parseRtuFrame,
15
+ decodeRegisters,
16
+ encodeWords,
17
+ engToRaw,
18
+ rawToEng,
19
+ extractLineValue,
20
+ normalizeSerialConfig,
21
+ registerOffset,
22
+ } from '../server/plugins-builtin/serial-bridge/lib.mjs'
23
+ import { createSerialDrivers } from '../server/plugins-builtin/serial-bridge/drivers.mjs'
24
+ import { createRtuClient } from '../server/plugins-builtin/serial-bridge/rtu.mjs'
25
+ import serialBridge from '../server/plugins-builtin/serial-bridge/index.mjs'
26
+
27
+ let pass = 0
28
+ let fail = 0
29
+ const check = (name, ok, detail = '') => {
30
+ if (ok) {
31
+ pass++
32
+ console.log(` PASS ${name}`)
33
+ return
34
+ }
35
+ fail++
36
+ console.log(` FAIL ${name}${detail ? ' — ' + detail : ''}`)
37
+ }
38
+ const approx = (a, b, eps = 1e-4) => Math.abs(a - b) <= eps
39
+
40
+ console.log('\n━━━ 1. 纯协议层(lib.mjs) ━━━')
41
+ {
42
+ // Modbus CRC16 已知向量:01 03 00 00 00 02 → C4 0B(Modbus 官方工具可复算)
43
+ const crc = crc16Modbus(Buffer.from([0x01, 0x03, 0x00, 0x00, 0x00, 0x02]))
44
+ check('CRC16 已知向量 01 03 00 00 00 02 → 0xC40B', crc === 0x0BC4, `got 0x${crc.toString(16)}`)
45
+
46
+ const frame = buildRtuFrame(1, 3, [0x00, 0x00, 0x00, 0x02])
47
+ check('RTU 帧构造(CRC lo/hi 序)', frame.length === 8 && frame[6] === 0xC4 && frame[7] === 0x0B, frame.toString('hex'))
48
+
49
+ const parsed = parseRtuFrame(frame)
50
+ check('RTU 帧解析(addr/func/data)', parsed.addr === 1 && parsed.func === 3 && parsed.data.length === 4)
51
+ const bad = Buffer.from(frame)
52
+ bad[2] ^= 0xFF
53
+ check('RTU 帧篡改 → crc 错误', parseRtuFrame(bad).error === 'crc')
54
+ check('RTU 短帧 → short', parseRtuFrame(Buffer.from([1, 2])).error === 'short')
55
+
56
+ // 寄存器编解码:float32 123.456 全字节序 roundtrip(int16 零点用带符号验证)
57
+ for (const byteOrder of ['big', 'little', 'wordSwap']) {
58
+ const words = encodeWords(123.456, 'float32', byteOrder)
59
+ const back = decodeRegisters(words, 'float32', byteOrder)
60
+ check(`float32 ${byteOrder} 编解码互逆`, approx(words.length === 2 ? back : back, 123.456), `got ${back}`)
61
+ }
62
+ const i16 = decodeRegisters([0xFFFE], 'int16', 'big')
63
+ check('int16 带符号解码 0xFFFE → -2', i16 === -2, `got ${i16}`)
64
+ const u32 = decodeRegisters(encodeWords(3000000123, 'uint32', 'big'), 'uint32', 'big')
65
+ check('uint32 3000000123 roundtrip', u32 === 3000000123, `got ${u32}`)
66
+
67
+ // 与内置 modbus 驱动同规约:40001 → 偏移 0,30001 → 0
68
+ check('registerOffset 40001→0 / 30001→0', registerOffset(40001, 'holding') === 0 && registerOffset(30001, 'input') === 0)
69
+
70
+ // 工程量↔原始值线性映射(0~27648 ↔ 0~100)
71
+ const cfg = { rawMin: 0, rawMax: 27648, engMin: 0, engMax: 100 }
72
+ const raw = engToRaw(50, cfg)
73
+ check('engToRaw 50 → 13824', approx(raw, 13824, 0.01), `got ${raw}`)
74
+ check('rawToEng 互逆', approx(rawToEng(raw, cfg), 50, 1e-6))
75
+ check('未配置量程直传', engToRaw(7.5, {}) === 7.5)
76
+
77
+ // ASCII 行取值
78
+ check('裸数字行', extractLineValue('25.3\n', '') === 25.3)
79
+ check('JSON 无路径 → null(需配置 jsonPath,取值路径语义显式)', extractLineValue('{"temp": 21.5}', '') === null)
80
+ check('JSON jsonPath', extractLineValue('{"data":{"temp":21.5}}', 'data.temp') === 21.5)
81
+ check('JSON 数组下标路径', extractLineValue('{"arr":[1,2,9.5]}', 'arr.2') === 9.5)
82
+ check('垃圾行 → null(跳帧)', extractLineValue('ERROR OVERHEAT', '') === null)
83
+ check('空行 → null', extractLineValue(' \n', '') === null)
84
+
85
+ // 配置规范化
86
+ const n1 = normalizeSerialConfig({ path: 'COM3', register: 40001 })
87
+ check('缺省补齐(mode/baud/type/byteOrder)', n1.errors.length === 0 && n1.cfg.mode === 'modbus-rtu' && n1.cfg.baudRate === 9600 && n1.cfg.dataType === 'float32' && n1.cfg.byteOrder === 'big')
88
+ const n2 = normalizeSerialConfig({ mode: 'ascii-line' })
89
+ check('ascii-line 不要求 register,仍要求 path', n2.errors.length === 1 && /path/.test(n2.errors[0]))
90
+ const n3 = normalizeSerialConfig({ path: 'COM3', register: 1, mode: 'nope' })
91
+ check('非法 mode 报错且回退 modbus-rtu', n3.cfg.mode === 'modbus-rtu' && n3.errors.some(e => /mode/.test(e)))
92
+ const n4 = normalizeSerialConfig({ path: 'COM3', register: 40001, scale: 0 })
93
+ check('scale=0 拒绝(除零保护)', n4.cfg.scale === 1)
94
+ }
95
+
96
+ console.log('\n━━━ 2. RTU 事务客户端(rtu.mjs,假 SerialPort 从站) ━━━')
97
+ {
98
+ /** 内存 RTU 从站:以 EventEmitter 风格的假 SerialPort,写请求即回应答帧 */
99
+ function makeFakeSerialSlave({ unitId = 7, holding = new Map(), corrupt = false, silent = false, badAddr = false } = {}) {
100
+ const listeners = { data: [], error: [], close: [], open: [] }
101
+ const port = {
102
+ isOpen: false,
103
+ on(ev, fn) { listeners[ev]?.push(fn) },
104
+ once(ev, fn) { listeners[ev]?.push(fn) },
105
+ write(buf, cb) {
106
+ setImmediate(() => cb?.())
107
+ if (silent) return
108
+ const req = Buffer.from(buf)
109
+ const func = req[1]
110
+ const respAddr = badAddr ? unitId + 1 : unitId
111
+ let resp
112
+ if (func === 3 || func === 4) {
113
+ const offset = req.readUInt16BE(2)
114
+ const count = req.readUInt16BE(4)
115
+ const data = Buffer.alloc(count * 2)
116
+ for (let i = 0; i < count; i++) data.writeUInt16BE(holding.get(offset + i) ?? 0, i * 2)
117
+ resp = Buffer.concat([Buffer.from([respAddr, func, count * 2]), data])
118
+ }
119
+ else if (func === 6) {
120
+ resp = Buffer.from(req.subarray(0, 6))
121
+ }
122
+ else if (func === 16) {
123
+ resp = Buffer.from([respAddr, func, req[2], req[3], req[4], req[5]])
124
+ }
125
+ else {
126
+ resp = Buffer.from([respAddr, func | 0x80, 1])
127
+ }
128
+ if (corrupt) resp[resp.length - 1] ^= 0xFF
129
+ const c = Buffer.alloc(2)
130
+ c.writeUInt16LE(crc16Modbus(resp))
131
+ const frame = Buffer.concat([resp, c])
132
+ if (corrupt) frame[frame.length - 1] ^= 0xFF
133
+ setImmediate(() => listeners.data.forEach(fn => fn(frame)))
134
+ },
135
+ close(cb) {
136
+ port.isOpen = false
137
+ listeners.close.forEach(fn => fn())
138
+ setImmediate(() => cb?.())
139
+ },
140
+ }
141
+ // autoOpen 语义:构造即异步发 open 事件(与真实 serialport 一致)
142
+ setImmediate(() => {
143
+ port.isOpen = true
144
+ listeners.open.forEach(fn => fn())
145
+ })
146
+ return port
147
+ }
148
+
149
+ const regs = new Map([[0, 0x41D4], [1, 0x0000]]) // float32 big 26.5
150
+ /** rtu.mjs 需要的是「类」(open() 内 new SerialPort(opts));构造器返回实例对象 */
151
+ const mkClass = (opts = {}) => function FakeSerialPortClass() {
152
+ return makeFakeSerialSlave({ holding: regs, ...opts })
153
+ }
154
+ const mk = (opts = {}) => createRtuClient(mkClass(opts), { path: 'FAKE', baudRate: 9600, dataBits: 8, stopBits: 1, parity: 'none', unitId: 7, timeoutMs: 400 })
155
+
156
+ const client = mk()
157
+ await client.open()
158
+ check('RTU open 后 isOpen', client.isOpen === true)
159
+
160
+ const r1 = await client.readHoldingRegisters(0, 2)
161
+ check('RTU FC03 读保持寄存器(float32 字序)', r1.data.length === 2 && r1.data[0] === 0x41D4 && r1.data[1] === 0x0000, JSON.stringify(r1))
162
+
163
+ const w1 = await client.writeRegister(5, 0xABCD)
164
+ check('RTU FC06 写单寄存器(回显)', w1.value === 0xABCD, JSON.stringify(w1))
165
+
166
+ const w2 = await client.writeRegisters(10, [1, 2, 3])
167
+ check('RTU FC16 写多寄存器', w2.length === 3, JSON.stringify(w2))
168
+ await client.close()
169
+ check('RTU close 后 isOpen=false', client.isOpen === false)
170
+
171
+ // 异常帧:专用假从站对一切请求回 func|0x80 + code=2(带合法 CRC)
172
+ function ExceptionPortClass() {
173
+ const __l = { data: [], error: [], close: [], open: [] }
174
+ const port = {
175
+ isOpen: false,
176
+ on(ev, fn) { __l[ev].push(fn) },
177
+ once(ev, fn) { __l[ev].push(fn) },
178
+ write(buf, cb) {
179
+ setImmediate(() => cb?.())
180
+ const body = Buffer.from([7, Buffer.from(buf)[1] | 0x80, 2])
181
+ const c = Buffer.alloc(2)
182
+ c.writeUInt16LE(crc16Modbus(body))
183
+ setImmediate(() => __l.data.forEach(fn => fn(Buffer.concat([body, c]))))
184
+ },
185
+ close(cb) { setImmediate(() => cb?.()) },
186
+ }
187
+ setImmediate(() => {
188
+ port.isOpen = true
189
+ __l.open.forEach(fn => fn())
190
+ })
191
+ return port
192
+ }
193
+ const exReal = createRtuClient(ExceptionPortClass, { path: 'FAKE', baudRate: 9600, dataBits: 8, stopBits: 1, parity: 'none', unitId: 7, timeoutMs: 400 })
194
+ await exReal.open()
195
+ let exErr = null
196
+ await exReal.readHoldingRegisters(0, 1).catch(e => (exErr = e.message))
197
+ check('RTU 异常帧 → exception code 错误', exErr?.includes('exception code=2'), exErr)
198
+
199
+ const crcClient = mk({ corrupt: true })
200
+ await crcClient.open()
201
+ let crcErr = null
202
+ await crcClient.readHoldingRegisters(0, 1).catch(e => (crcErr = e.message))
203
+ check('RTU CRC 篡改 → CRC 错误', crcErr?.includes('CRC'), crcErr)
204
+
205
+ const addrClient = mk({ badAddr: true })
206
+ await addrClient.open()
207
+ let addrErr = null
208
+ await addrClient.readHoldingRegisters(0, 1).catch(e => (addrErr = e.message))
209
+ check('RTU 从站地址不匹配 → 报错', addrErr?.includes('从站地址不匹配'), addrErr)
210
+
211
+ const silentClient = mk({ silent: true })
212
+ await silentClient.open()
213
+ const t0 = Date.now()
214
+ let timeoutErr = null
215
+ await silentClient.readHoldingRegisters(0, 1).catch(e => (timeoutErr = e.message))
216
+ check('RTU 无应答 → 硬超时(250ms)', timeoutErr?.includes('超时') && Date.now() - t0 < 1500, timeoutErr)
217
+ }
218
+
219
+ console.log('\n━━━ 3. 驱动契约(假端口,免硬件) ━━━')
220
+ {
221
+ // 内存 Modbus 从站:保持寄存器阵列 + 写镜像
222
+ function makeFakeRtu() {
223
+ const holding = new Map()
224
+ const input = new Map()
225
+ const client = {
226
+ isOpen: true,
227
+ closed: false,
228
+ setID() {},
229
+ on() {},
230
+ async readHoldingRegisters(offset, length) {
231
+ const out = []
232
+ for (let i = 0; i < length; i++) out.push(holding.get(offset + i) ?? 0)
233
+ return { data: out }
234
+ },
235
+ async readInputRegisters(offset, length) {
236
+ const out = []
237
+ for (let i = 0; i < length; i++) out.push(input.get(offset + i) ?? 0)
238
+ return { data: out }
239
+ },
240
+ async writeRegister(offset, value) { holding.set(offset, value & 0xFFFF) },
241
+ async writeRegisters(offset, words) { words.forEach((w, i) => holding.set(offset + i, w & 0xFFFF)) },
242
+ async close() {
243
+ client.closed = true
244
+ client.isOpen = false
245
+ },
246
+ }
247
+ client.__holding = holding
248
+ client.__input = input
249
+ return client
250
+ }
251
+
252
+ // float32 26.5(0x41D40000)按 big 端写入保持寄存器 0
253
+ const seedWords = (v) => {
254
+ const b = Buffer.alloc(4)
255
+ b.writeFloatBE(v, 0)
256
+ return [(b[0] << 8) | b[1], (b[2] << 8) | b[3]]
257
+ }
258
+
259
+ const fake = makeFakeRtu()
260
+ const w = seedWords(26.5)
261
+ fake.__holding.set(0, w[0])
262
+ fake.__holding.set(1, w[1])
263
+
264
+ const linePort = {
265
+ aliveRet: true,
266
+ alive() { return this.aliveRet },
267
+ latestRet: { text: '25.3', at: Date.now() },
268
+ latest() { return this.latestRet },
269
+ written: [],
270
+ async writeLine(line) { this.written.push(line) },
271
+ async close() {},
272
+ }
273
+
274
+ const factory = createSerialDrivers({
275
+ openRtu: async () => fake,
276
+ openLine: async () => linePort,
277
+ isAvailable: () => true,
278
+ log: { warn: () => {} },
279
+ })
280
+
281
+ const cfgRtu = { mode: 'modbus-rtu', path: 'COM9', baudRate: 19200, register: 40001, dataType: 'float32', byteOrder: 'big', scale: 1 }
282
+ const v1 = await factory.readDriver.sample({ driverConfig: cfgRtu })
283
+ check('sample: RTU float32 读数', approx(v1, 26.5), `got ${v1}`)
284
+
285
+ const cfgRtuScaled = { ...cfgRtu, scale: 0.1 }
286
+ const v2 = await factory.readDriver.sample({ driverConfig: cfgRtuScaled })
287
+ check('sample: 缩放系数生效(26.5×0.1)', approx(v2, 2.65), `got ${v2}`)
288
+
289
+ const cfgInput = { ...cfgRtu, registerType: 'input', register: 30001 }
290
+ fake.__input.set(0, 100)
291
+ const v3 = await factory.readDriver.sample({ driverConfig: { ...cfgInput, dataType: 'uint16' } })
292
+ check('sample: 输入寄存器(3x)FC04', v3 === 100, `got ${v3}`)
293
+
294
+ const cfgAscii = { mode: 'ascii-line', path: 'COM10', baudRate: 9600 }
295
+ const v4 = await factory.readDriver.sample({ driverConfig: cfgAscii })
296
+ check('sample: ASCII 行取值', v4 === 25.3, `got ${v4}`)
297
+
298
+ linePort.latestRet = { text: '{"data":{"t":-3.5}}', at: Date.now() }
299
+ const v5 = await factory.readDriver.sample({ driverConfig: { ...cfgAscii, jsonPath: 'data.t' } })
300
+ check('sample: ASCII JSON 路径 + 负值', v5 === -3.5, `got ${v5}`)
301
+
302
+ linePort.latestRet = { text: '99', at: Date.now() - 60_000 }
303
+ const v6 = await factory.readDriver.sample({ driverConfig: { ...cfgAscii, staleMs: 30000 } })
304
+ check('sample: 保鲜窗外 → null 跳帧', v6 === null, `got ${v6}`)
305
+
306
+ // 写 + 回读校验(0~27648 ↔ 0~100 映射)
307
+ const cfgWrite = { mode: 'modbus-rtu', path: 'COM9', baudRate: 19200, register: 40001, dataType: 'uint16', byteOrder: 'big', rawMin: 0, rawMax: 27648, engMin: 0, engMax: 100 }
308
+ const wr1 = await factory.writeDriver.write({ eng: 50, tolerance: 0.5, domain: { min: 0, max: 100 }, driverConfig: cfgWrite })
309
+ check('write: 写+回读一致 ACK', wr1.ok === true && approx(wr1.readback, 50, 0.5), JSON.stringify(wr1))
310
+
311
+ // 从站写入失真(写入值 ≠ 回读值,如从站寄存器故障)→ 回读不一致 → 拒绝 ACK
312
+ const corruptRtu = makeFakeRtu()
313
+ corruptRtu.writeRegister = async function (offset, value) {
314
+ this.__holding.set(offset, (value + 1000) & 0xFFFF)
315
+ }
316
+ corruptRtu.writeRegisters = async function (offset, words) {
317
+ words.forEach((w, i) => this.__holding.set(offset + i, (w + 1000) & 0xFFFF))
318
+ }
319
+ const corruptFactory = createSerialDrivers({
320
+ openRtu: async () => corruptRtu,
321
+ openLine: async () => linePort,
322
+ isAvailable: () => true,
323
+ log: { warn: () => {} },
324
+ })
325
+ const wr2 = await corruptFactory.writeDriver.write({ eng: 50, tolerance: 0.5, domain: { min: 0, max: 100 }, driverConfig: cfgWrite })
326
+ check('write: 回读不一致 → 拒绝', wr2.ok === false && /不一致/.test(wr2.message), JSON.stringify(wr2))
327
+ await corruptFactory.closeAll()
328
+
329
+ // ASCII 写模板下行
330
+ const wr3 = await factory.writeDriver.write({ eng: 42.5, tolerance: 0.5, domain: { min: 0, max: 100 }, driverConfig: { mode: 'ascii-line', path: 'COM10', writeTemplate: 'SET {v}' } })
331
+ check('write: ASCII 模板下行(无回读诚实标注)', wr3.ok === true && linePort.written[0] === 'SET 42.5' && wr3.readback === null, JSON.stringify({ wr3, written: linePort.written }))
332
+
333
+ // 连接测试
334
+ const t1 = await factory.readDriver.test(cfgRtu)
335
+ check('test: 通过并带回读数', t1.ok === true && t1.sampleValue != null, JSON.stringify(t1))
336
+ const t2 = await factory.readDriver.test({ mode: 'modbus-rtu', path: '' })
337
+ check('test: 缺 path 给可读错误', t2.ok === false && /path/.test(t2.message))
338
+ const t3 = await factory.writeDriver.test(cfgWrite)
339
+ check('test: 写驱动测试不触发真实写', t3.ok === true, JSON.stringify(t3))
340
+
341
+ // meta 自描述(前端动态表单数据源)
342
+ check('读驱动 meta.configFields 非空', Array.isArray(factory.readDriver.meta?.configFields) && factory.readDriver.meta.configFields.length >= 10)
343
+ check('写驱动 meta.configFields 含量程映射', factory.writeDriver.meta.configFields.some(f => f.key === 'rawMax'))
344
+
345
+ // 串行队列:前一事务阻塞时后续排队(契约:同一物理链路不并发)
346
+ let busy = 0
347
+ let maxBusy = 0
348
+ const slowFactory = createSerialDrivers({
349
+ openRtu: async () => ({
350
+ isOpen: true,
351
+ setID() {},
352
+ on() {},
353
+ async readHoldingRegisters() {
354
+ busy++
355
+ maxBusy = Math.max(maxBusy, busy)
356
+ await new Promise(r => setTimeout(r, 20))
357
+ busy--
358
+ return { data: [1, 2] }
359
+ },
360
+ async close() {},
361
+ }),
362
+ openLine: async () => linePort,
363
+ isAvailable: () => true,
364
+ log: { warn: () => {} },
365
+ })
366
+ const cfgSlow = { mode: 'modbus-rtu', path: 'COMX', register: 40001 }
367
+ await Promise.all([
368
+ slowFactory.readDriver.sample({ driverConfig: cfgSlow }),
369
+ slowFactory.readDriver.sample({ driverConfig: cfgSlow }),
370
+ slowFactory.readDriver.sample({ driverConfig: cfgSlow }),
371
+ ])
372
+ check('同链路事务串行化(无并发读)', maxBusy === 1, `maxBusy=${maxBusy}`)
373
+
374
+ await factory.closeAll()
375
+ check('closeAll 关闭串口连接', fake.closed === true)
376
+ }
377
+
378
+ console.log('\n━━━ 3. 插件入口契约(index.mjs + 桩 ctx) ━━━')
379
+ {
380
+ const registered = { daqDrivers: [], dcwDrivers: [], routes: new Map(), tools: [], disposed: [] }
381
+ const ctx = {
382
+ name: 'serial-bridge',
383
+ logger: { info: () => {}, warn: m => console.warn(' [log]', m), error: () => {} },
384
+ daq: {
385
+ registerDriver: d => registered.daqDrivers.push(d),
386
+ nodes: async () => [{ id: 'n1', name: '炉温', driver: 'serial', enabled: true, lineId: 'L1', driverConfig: { path: 'COM3' } }],
387
+ },
388
+ dcw: { registerWriteDriver: d => registered.dcwDrivers.push(d) },
389
+ route: (method, path, handler) => registered.routes.set(`${method} ${path}`, handler),
390
+ omp: { registerTool: t => registered.tools.push(t) },
391
+ onDispose: fn => registered.disposed.push(fn),
392
+ http: {},
393
+ }
394
+ await serialBridge.setup(ctx)
395
+
396
+ check('数采读驱动已注册(kind=serial)', registered.daqDrivers.length === 1 && registered.daqDrivers[0].kind === 'serial')
397
+ check('写控驱动已注册(kind=serial)', registered.dcwDrivers.length === 1 && registered.dcwDrivers[0].kind === 'serial')
398
+ check('驱动 available 探测可用(serialport 已安装)', await registered.daqDrivers[0].available() === true)
399
+ check('API 路由已注册(health/ports/probe)', ['GET /health', 'GET /ports', 'POST /probe'].every(k => registered.routes.has(k)))
400
+ check('omp 工具已注册(serial_ports)', registered.tools.length === 1 && registered.tools[0].name === 'serial_ports')
401
+ check('dispose 已登记(串口回收)', registered.disposed.length >= 1)
402
+
403
+ const health = await registered.routes.get('GET /health')()
404
+ check('GET /health:可用性 + 串口节点识别', health.available === true && health.readNodes.length === 1 && health.readNodes[0].path === 'COM3', JSON.stringify(health))
405
+ const ports = await registered.routes.get('GET /ports')()
406
+ check('GET /ports:系统串口枚举(免硬件)', ports.success === true && Array.isArray(ports.ports), JSON.stringify(ports).slice(0, 120))
407
+ const probe1 = await registered.routes.get('POST /probe')({ awBody: { mode: 'modbus-rtu' } })
408
+ check('POST /probe:缺参数给可读错误', probe1.success === false && /path/i.test(probe1.error))
409
+ const probe2 = await registered.routes.get('POST /probe')({ awBody: { mode: 'ascii-line', path: 'COM_NOT_EXIST_42', timeoutMs: 800 } })
410
+ check('POST /probe:不存在的串口给可行动诊断', probe2.success === true && probe2.ok === false && /串口/.test(probe2.message), JSON.stringify(probe2))
411
+
412
+ const tool = registered.tools[0]
413
+ const r = await tool.handler({})
414
+ check('serial_ports 工具返回诊断文本', r.text.includes('可用性') && r.text.includes('炉温'), r.text)
415
+ }
416
+
417
+ console.log(`\n━━━ 结果: ${pass} 通过 / ${fail} 失败 ━━━`)
418
+ process.exit(fail ? 1 : 0)