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,409 @@
1
+ /**
2
+ * serial-bridge —— 驱动工厂(串口读/写驱动;连接池 + 队列化事务)。
3
+ *
4
+ * createSerialDrivers({ openRtu, openLine, isAvailable, log }) 与具体串口实现解耦:
5
+ * - openRtu(cfg) → modbus-serial 客户端(RTU over 串口;connectRTUBuffered)
6
+ * - openLine(cfg) → { writeLine, close, latest } 行协议端口(serialport + readline)
7
+ * 测试脚本注入假 opener 即可覆盖 sample/write/readback 全契约,无需真实硬件。
8
+ *
9
+ * 池语义与内置 modbus-tcp 驱动一致(同链路串行化、连续故障驱逐、空闲回收):
10
+ * - 池 key = mode|path@baud,parity,dataBits,stopBits(同一物理链路多节点复用);
11
+ * - withConn 队列:单事务硬超时(半开串口防悬挂),超时作废连接;
12
+ * - 连续 3 次故障 → 驱逐,下次采样重建(自愈)。
13
+ */
14
+ import {
15
+ classifySerialError,
16
+ decodeRegisters,
17
+ encodeWords,
18
+ engToRaw,
19
+ extractLineValue,
20
+ normalizeSerialConfig,
21
+ rawToEng,
22
+ registerOffset,
23
+ WORDS_OF,
24
+ } from './lib.mjs'
25
+
26
+ /** 空闲连接回收(10 分钟未用断开;采样周期最长 60s,足够保守) */
27
+ const IDLE_MS = 600_000
28
+ /** 采/控共用链路积压保护(与内置 modbus 驱动同阈值) */
29
+ const PENDING_SKIP = 8
30
+
31
+ // ============================================================
32
+ // 驱动目录自描述(前端动态参数表单;DriverConfigField 同形)
33
+ // ============================================================
34
+
35
+ export const SERIAL_READ_FIELDS = [
36
+ { key: 'mode', label: '协议模式', type: 'select', default: 'modbus-rtu', options: [
37
+ { value: 'modbus-rtu', label: 'Modbus RTU(RS-485/232)' },
38
+ { value: 'ascii-line', label: 'ASCII 行(每行一个数值/JSON)' },
39
+ ] },
40
+ { key: 'path', label: '串口路径(path)', type: 'string', required: true, placeholder: 'COM3', hint: 'Windows 如 COM3;Linux 如 /dev/ttyUSB0' },
41
+ { key: 'baudRate', label: '波特率', type: 'number', default: 9600, hint: '与设备一致(常见 9600/19200/115200)' },
42
+ { key: 'parity', label: '校验位', type: 'select', default: 'none', options: [
43
+ { value: 'none', label: '无(None)' },
44
+ { value: 'even', label: '偶校验(Even)' },
45
+ { value: 'odd', label: '奇校验(Odd)' },
46
+ ] },
47
+ { key: 'dataBits', label: '数据位', type: 'select', default: 8, options: [
48
+ { value: 7, label: '7' },
49
+ { value: 8, label: '8' },
50
+ ] },
51
+ { key: 'stopBits', label: '停止位', type: 'select', default: 1, options: [
52
+ { value: 1, label: '1' },
53
+ { value: 2, label: '2' },
54
+ ] },
55
+ { key: 'unitId', label: '从站地址(unitId)', type: 'number', default: 1, hint: 'Modbus RTU 从站地址 1~247' },
56
+ { key: 'register', label: '寄存器地址', type: 'number', required: true, placeholder: '40001', hint: '4xxxx=保持寄存器(FC03);3xxxx=输入寄存器(FC04)' },
57
+ { key: 'registerType', label: '寄存器区', type: 'select', default: 'holding', options: [
58
+ { value: 'holding', label: '保持寄存器(4x)' },
59
+ { value: 'input', label: '输入寄存器(3x)' },
60
+ ] },
61
+ { key: 'dataType', label: '数据类型', type: 'select', default: 'float32', options: [
62
+ { value: 'int16', label: 'int16(1 寄存器)' },
63
+ { value: 'uint16', label: 'uint16(1 寄存器)' },
64
+ { value: 'int32', label: 'int32(2 寄存器)' },
65
+ { value: 'uint32', label: 'uint32(2 寄存器)' },
66
+ { value: 'float32', label: 'float32(2 寄存器,常用)' },
67
+ ] },
68
+ { key: 'byteOrder', label: '字节序', type: 'select', default: 'big', options: [
69
+ { value: 'big', label: '大端(AB CD)' },
70
+ { value: 'little', label: '小端(CD AB)' },
71
+ { value: 'wordSwap', label: '字交换(CD AB / 交换单字)' },
72
+ ] },
73
+ { key: 'scale', label: '缩放系数', type: 'number', default: 1, hint: '原始值 × scale = 工程量(如 0.1)' },
74
+ { key: 'jsonPath', label: '取值路径(ASCII 模式)', type: 'string', placeholder: 'data.temp', hint: 'ASCII 行为 JSON 时按路径取数值;裸数字留空' },
75
+ { key: 'staleMs', label: '数据保鲜窗 ms(ASCII 模式)', type: 'number', default: 30000, hint: '超过该时长未收到新行视为停发,采样跳帧(不重复消费陈旧值)' },
76
+ ]
77
+
78
+ export const SERIAL_WRITE_FIELDS = [
79
+ { key: 'mode', label: '协议模式', type: 'select', default: 'modbus-rtu', options: [
80
+ { value: 'modbus-rtu', label: 'Modbus RTU(写保持寄存器,带回读)' },
81
+ { value: 'ascii-line', label: 'ASCII 行(发 {v} 模板行,无回读)' },
82
+ ] },
83
+ { key: 'path', label: '串口路径(path)', type: 'string', required: true, placeholder: 'COM3', hint: 'Windows 如 COM3;Linux 如 /dev/ttyUSB0' },
84
+ { key: 'baudRate', label: '波特率', type: 'number', default: 9600 },
85
+ { key: 'parity', label: '校验位', type: 'select', default: 'none', options: [
86
+ { value: 'none', label: '无(None)' },
87
+ { value: 'even', label: '偶校验(Even)' },
88
+ { value: 'odd', label: '奇校验(Odd)' },
89
+ ] },
90
+ { key: 'dataBits', label: '数据位', type: 'select', default: 8, options: [{ value: 7, label: '7' }, { value: 8, label: '8' }] },
91
+ { key: 'stopBits', label: '停止位', type: 'select', default: 1, options: [{ value: 1, label: '1' }, { value: 2, label: '2' }] },
92
+ { key: 'unitId', label: '从站地址(unitId)', type: 'number', default: 1 },
93
+ { key: 'register', label: '寄存器地址', type: 'number', required: true, placeholder: '40001', hint: '写保持寄存器(FC06/FC16)+ 同址回读校验' },
94
+ { key: 'dataType', label: '数据类型', type: 'select', default: 'float32', options: [
95
+ { value: 'int16', label: 'int16(1 寄存器,FC06)' },
96
+ { value: 'uint16', label: 'uint16(1 寄存器,FC06)' },
97
+ { value: 'int32', label: 'int32(2 寄存器,FC16)' },
98
+ { value: 'uint32', label: 'uint32(2 寄存器,FC16)' },
99
+ { value: 'float32', label: 'float32(2 寄存器,FC16)' },
100
+ ] },
101
+ { key: 'byteOrder', label: '字节序', type: 'select', default: 'big', options: [
102
+ { value: 'big', label: '大端(AB CD)' },
103
+ { value: 'little', label: '小端(CD AB)' },
104
+ { value: 'wordSwap', label: '字交换(CD AB / 交换单字)' },
105
+ ] },
106
+ { key: 'rawMin', label: '原始量程下限(可选)', type: 'number', placeholder: '0', hint: '工程量↔原始值线性映射;两值都填才启用' },
107
+ { key: 'rawMax', label: '原始量程上限(可选)', type: 'number', placeholder: '27648', hint: '如西门子 0~27648 对应 4~20mA' },
108
+ { key: 'engMin', label: '工程量程下限(可选)', type: 'number', placeholder: '0', hint: '缺省取节点工艺量程 min' },
109
+ { key: 'engMax', label: '工程量程上限(可选)', type: 'number', placeholder: '100', hint: '缺省取节点工艺量程 max' },
110
+ { key: 'writeTemplate', label: 'ASCII 写模板(可选)', type: 'string', placeholder: 'SET {v}\\n', hint: 'ascii-line 模式下发行模板;{v} 替换为数值,\\n 自动补行尾' },
111
+ ]
112
+
113
+ // ============================================================
114
+ // 连接池 + 队列化事务
115
+ // ============================================================
116
+
117
+ function connKey(cfg) {
118
+ return `${cfg.mode}|${cfg.path}@${cfg.baudRate},${cfg.parity},${cfg.dataBits},${cfg.stopBits}`
119
+ }
120
+
121
+ /**
122
+ * 创建串口驱动组(每插件装载一次;热重载 dispose 时 closeAll 回收全部串口)。
123
+ */
124
+ export function createSerialDrivers({ openRtu, openLine, isAvailable, log = console }) {
125
+ const pool = new Map()
126
+ let sweep = null
127
+
128
+ async function closeConn(conn) {
129
+ try {
130
+ if (conn.kind === 'rtu') await withTimeout(conn.client.close?.(), 500)
131
+ else await withTimeout(conn.line.close(), 500)
132
+ }
133
+ catch { /* 已断 */ }
134
+ }
135
+
136
+ function withTimeout(p, ms) {
137
+ return Promise.race([
138
+ Promise.resolve(p),
139
+ new Promise(r => setTimeout(() => r(), ms)),
140
+ ])
141
+ }
142
+
143
+ /** 获取(或建立)串口连接;半开/死连接重建 + 在飞建连去重(串口独占,并发首连
144
+ * 若各开各的,后写池者覆盖前者 → 前一条串口无人持有永久泄漏,Windows 独占打开还会
145
+ * 直接报错。同键并发复用同一条建连任务,与内置 OPC UA 驱动同方案)。 */
146
+ const inflight = new Map()
147
+ async function getConn(cfg) {
148
+ const key = connKey(cfg)
149
+ const existing = pool.get(key)
150
+ const healthy = existing
151
+ && (existing.kind === 'rtu' ? existing.client.isOpen !== false : existing.line.alive() !== false)
152
+ if (existing && healthy) {
153
+ existing.lastUsed = Date.now()
154
+ return existing
155
+ }
156
+ if (existing) {
157
+ pool.delete(key)
158
+ await closeConn(existing)
159
+ }
160
+ const pending = inflight.get(key)
161
+ if (pending) return pending
162
+ const task = (async () => (cfg.mode === 'modbus-rtu'
163
+ ? { kind: 'rtu', client: await openRtu(cfg), lastUsed: Date.now(), tail: Promise.resolve(), pending: 0, errors: 0 }
164
+ : { kind: 'line', line: await openLine(cfg), lastUsed: Date.now(), tail: Promise.resolve(), pending: 0, errors: 0 }))()
165
+ inflight.set(key, task)
166
+ try {
167
+ const conn = await task
168
+ // 竞态二次校验:建连期间若已有连接入池,关闭本次新建并复用既有
169
+ const raced = pool.get(key)
170
+ if (raced && raced !== conn) {
171
+ await closeConn(conn)
172
+ return raced
173
+ }
174
+ pool.set(key, conn)
175
+ return conn
176
+ }
177
+ finally {
178
+ inflight.delete(key)
179
+ }
180
+ }
181
+
182
+ /**
183
+ * 连接级排队执行:单事务硬超时(默认 3s)——半开串口(拔线/对端掉电)时底层
184
+ * 回调永不返回,超时后作废连接并重置队列,防止单条死链堵死同串口全部节点。
185
+ */
186
+ function withConn(conn, fn, timeoutMs = 3000) {
187
+ conn.pending++
188
+ const run = conn.tail.then(() => fn(), () => fn())
189
+ let dead = false
190
+ const guarded = Promise.race([
191
+ run,
192
+ new Promise((_, rej) => setTimeout(() => {
193
+ dead = true
194
+ rej(new Error(`串口响应超时(${timeoutMs}ms)——检查接线/波特率/从站地址`))
195
+ }, timeoutMs)),
196
+ ])
197
+ void guarded.catch(() => { /* 超时分支先 reject,防 unhandled */ })
198
+ conn.tail = guarded.then(
199
+ () => { conn.pending-- },
200
+ () => { conn.pending-- },
201
+ ).then(() => {
202
+ if (dead) {
203
+ conn.tail = Promise.resolve()
204
+ // 超时即作废:关闭并移出池,下次采样重建
205
+ for (const [k, c] of pool) if (c === conn) pool.delete(k)
206
+ closeConn(conn).catch(() => {})
207
+ }
208
+ })
209
+ return guarded
210
+ }
211
+
212
+ /** 连续故障驱逐(自愈):同链路 3 次失败后断开重建 */
213
+ function bumpErrors(conn, cfg, err) {
214
+ conn.errors++
215
+ if (conn.errors >= 3) {
216
+ log.warn(`串口链路连续故障,重建: ${connKey(cfg)}(${err?.message ?? err})`)
217
+ for (const [k, c] of pool) if (c === conn) pool.delete(k)
218
+ closeConn(conn).catch(() => {})
219
+ }
220
+ }
221
+
222
+ // 空闲回收 sweep(单例;工厂随插件热重载整体销毁)
223
+ if (!sweep) {
224
+ sweep = setInterval(() => {
225
+ const now = Date.now()
226
+ for (const [key, conn] of pool) {
227
+ if (now - conn.lastUsed > IDLE_MS && conn.pending === 0) {
228
+ pool.delete(key)
229
+ closeConn(conn).catch(() => {})
230
+ }
231
+ }
232
+ }, 120_000)
233
+ sweep.unref?.()
234
+ }
235
+
236
+ async function closeAll() {
237
+ clearInterval(sweep)
238
+ sweep = null
239
+ const entries = [...pool]
240
+ pool.clear()
241
+ for (const [, conn] of entries) await closeConn(conn)
242
+ }
243
+
244
+ function poolSnapshot() {
245
+ return [...pool.entries()].map(([key, c]) => ({
246
+ key,
247
+ kind: c.kind,
248
+ lastUsedAt: c.lastUsed,
249
+ pending: c.pending,
250
+ errors: c.errors,
251
+ }))
252
+ }
253
+
254
+ // ---------- 读路径(采样 / 测试) ----------
255
+
256
+ async function readOnce(cfg, conn) {
257
+ if (conn.kind === 'rtu') {
258
+ const area = cfg.registerType
259
+ const offset = registerOffset(cfg.register, area)
260
+ const words = WORDS_OF[cfg.dataType] ?? 2
261
+ const res = area === 'input'
262
+ ? await conn.client.readInputRegisters(offset, words)
263
+ : await conn.client.readHoldingRegisters(offset, words)
264
+ const raw = decodeRegisters(res.data ?? res.buffer ?? [], cfg.dataType, cfg.byteOrder)
265
+ return { value: raw * cfg.scale, raw }
266
+ }
267
+ // ascii-line:最新行 + 保鲜窗(停发即 null,不重复消费陈旧值)
268
+ const latest = conn.line.latest()
269
+ if (!latest || !latest.text) return { value: null, raw: null }
270
+ if (Date.now() - latest.at > cfg.staleMs) return { value: null, raw: null, stale: true }
271
+ const v = extractLineValue(latest.text, cfg.jsonPath)
272
+ return { value: v == null ? null : v * cfg.scale, raw: v }
273
+ }
274
+
275
+ const readDriver = {
276
+ kind: 'serial',
277
+ meta: { label: '串口 Serial(RS-232/485)', status: 'real', configFields: SERIAL_READ_FIELDS },
278
+ available: () => Promise.resolve(Boolean(isAvailable())),
279
+ async sample({ driverConfig }) {
280
+ const { cfg, errors } = normalizeSerialConfig(driverConfig)
281
+ if (errors.length) throw new Error(errors[0])
282
+ const conn = await getConn(cfg)
283
+ if (conn.pending > PENDING_SKIP) return null
284
+ conn.lastUsed = Date.now()
285
+ return withConn(conn, async () => {
286
+ try {
287
+ const r = await readOnce(cfg, conn)
288
+ conn.errors = 0
289
+ return r.value
290
+ }
291
+ catch (err) {
292
+ bumpErrors(conn, cfg, err)
293
+ throw new Error(classifySerialError(err), { cause: err })
294
+ }
295
+ }, cfg.timeoutMs)
296
+ },
297
+ async test(driverConfig) {
298
+ const t0 = Date.now()
299
+ const { cfg, errors } = normalizeSerialConfig(driverConfig)
300
+ if (errors.length) return { ok: false, message: errors.join(';') }
301
+ try {
302
+ const conn = await getConn(cfg)
303
+ const r = await withConn(conn, () => readOnce(cfg, conn), cfg.timeoutMs)
304
+ conn.errors = 0
305
+ if (r.value == null) {
306
+ return {
307
+ ok: true,
308
+ message: cfg.mode === 'ascii-line'
309
+ ? `串口已打开 ${cfg.path}@${cfg.baudRate}(保鲜窗内暂无新鲜数据行;设备上报后采样即开始)`
310
+ : `串口已打开 ${cfg.path}@${cfg.baudRate},读数为空`,
311
+ latencyMs: Date.now() - t0,
312
+ }
313
+ }
314
+ return {
315
+ ok: true,
316
+ message: `串口连接成功,读取 ${cfg.mode === 'modbus-rtu' ? `${cfg.register}(原始 ${r.raw})` : `行 ${String(r.raw)}`} = ${r.value}`,
317
+ sampleValue: r.value,
318
+ latencyMs: Date.now() - t0,
319
+ }
320
+ }
321
+ catch (err) {
322
+ return { ok: false, message: `串口连接失败: ${classifySerialError(err)}` }
323
+ }
324
+ },
325
+ }
326
+
327
+ // ---------- 写路径(写 + 回读校验 / 周期读) ----------
328
+
329
+ const writeDriver = {
330
+ kind: 'serial',
331
+ meta: { label: '串口 Serial 写(RS-232/485)', status: 'real', configFields: SERIAL_WRITE_FIELDS },
332
+ available: () => Promise.resolve(Boolean(isAvailable())),
333
+ async write(input) {
334
+ const { cfg, errors } = normalizeSerialConfig(input.driverConfig)
335
+ if (errors.length) return { ok: false, message: errors.join(';'), raw: null, readback: null }
336
+ if (cfg.mode === 'ascii-line') {
337
+ // ASCII 写:发模板行(无协议回读;诚实标注)
338
+ try {
339
+ const conn = await getConn(cfg)
340
+ const line = String(cfg.writeTemplate || '{v}').replace('{v}', String(input.eng))
341
+ await withConn(conn, () => conn.line.writeLine(line), cfg.timeoutMs)
342
+ return { ok: true, message: `已发送「${line.trim()}」到 ${cfg.path}(ascii-line 无回读,建议配合数采节点校验)`, raw: input.eng, readback: null }
343
+ }
344
+ catch (err) {
345
+ return { ok: false, message: `串口写入失败: ${classifySerialError(err)}`, raw: null, readback: null }
346
+ }
347
+ }
348
+ try {
349
+ const conn = await getConn(cfg)
350
+ return await withConn(conn, async () => {
351
+ const raw = engToRaw(input.eng, input.driverConfig, input.domain)
352
+ const words = encodeWords(raw, cfg.dataType, cfg.byteOrder)
353
+ const offset = registerOffset(cfg.register, 'holding')
354
+ if (words.length === 1) await conn.client.writeRegister(offset, words[0])
355
+ else await conn.client.writeRegisters(offset, words)
356
+ // 回读校验:同址读回 → 解码 → 换算回工程量 → 死区容差比较(与内置写驱动同语义)
357
+ const back = await conn.client.readHoldingRegisters(offset, words.length)
358
+ const rawBack = decodeRegisters(back.data ?? back.buffer ?? [], cfg.dataType, cfg.byteOrder)
359
+ const engBack = rawToEng(rawBack, input.driverConfig, input.domain)
360
+ const ok = Math.abs(engBack - input.eng) <= Math.max(input.tolerance, 1e-9)
361
+ conn.errors = 0
362
+ return {
363
+ ok,
364
+ message: ok
365
+ ? `写入成功:${input.eng} → raw ${Number(raw.toFixed(4))},回读 ${Number(engBack.toFixed(4))} 一致`
366
+ : `回读不一致:写入 ${input.eng},回读 ${Number(engBack.toFixed(4))}(容差 ${input.tolerance})`,
367
+ raw,
368
+ readback: engBack,
369
+ }
370
+ }, cfg.timeoutMs)
371
+ }
372
+ catch (err) {
373
+ return { ok: false, message: `串口写入失败: ${classifySerialError(err)}`, raw: null, readback: null }
374
+ }
375
+ },
376
+ async read(input) {
377
+ const { cfg, errors } = normalizeSerialConfig(input.driverConfig)
378
+ if (errors.length) return { ok: false, message: errors.join(';'), eng: null, raw: null }
379
+ try {
380
+ const conn = await getConn(cfg)
381
+ const r = await withConn(conn, () => readOnce(cfg, conn), cfg.timeoutMs)
382
+ if (r.value == null) return { ok: true, message: '串口无新鲜数据', eng: null, raw: null }
383
+ return {
384
+ ok: true,
385
+ message: cfg.mode === 'modbus-rtu' ? `读取 raw ${r.raw} = ${r.value}` : `读取行值 ${r.value}`,
386
+ eng: r.value,
387
+ raw: r.raw,
388
+ }
389
+ }
390
+ catch (err) {
391
+ return { ok: false, message: classifySerialError(err), eng: null, raw: null }
392
+ }
393
+ },
394
+ async test(driverConfig) {
395
+ const t0 = Date.now()
396
+ const { cfg, errors } = normalizeSerialConfig(driverConfig)
397
+ if (errors.length) return { ok: false, message: errors.join(';') }
398
+ if (cfg.mode === 'ascii-line' && cfg.writeTemplate === '') return { ok: false, message: 'ascii-line 模式缺少写模板 writeTemplate' }
399
+ const r = await writeDriver.read({ domain: { min: 0, max: 100 }, driverConfig })
400
+ if (!r.ok) return { ok: false, message: `串口连接失败: ${r.message}` }
401
+ return {
402
+ ok: true,
403
+ message: `串口连接成功(${Date.now() - t0}ms;${cfg.mode === 'modbus-rtu' ? '读回读链路正常' : 'ascii 行链路正常'};未执行写入测试,避免误触发真实设备动作)`,
404
+ }
405
+ },
406
+ }
407
+
408
+ return { readDriver, writeDriver, closeAll, poolSnapshot, getConn }
409
+ }
@@ -0,0 +1,62 @@
1
+ {
2
+ "zh-CN": {
3
+ "panel": {
4
+ "title": "串口通信(serial-bridge)",
5
+ "refresh": "刷新",
6
+ "available": "协议栈就绪(serialport)",
7
+ "unavailable": "serialport 未安装 —— npm i serialport 后重启服务即恢复",
8
+ "ports": "系统串口",
9
+ "noports": "未发现串口设备(插入 USB 转 485/232 后刷新)",
10
+ "path": "端口",
11
+ "manufacturer": "厂商",
12
+ "serialNumber": "序列号",
13
+ "probe": "连接探针",
14
+ "probe_hint": "按给定参数开串口读一次(不落库);通过后即可按同参数创建数采/写控节点",
15
+ "mode": "协议模式",
16
+ "baud": "波特率",
17
+ "unit": "从站",
18
+ "register": "寄存器",
19
+ "sendLine": "探测行(ASCII)",
20
+ "run_probe": "测试连接",
21
+ "probing": "测试中…",
22
+ "links": "打开的链路",
23
+ "nolinks": "暂无打开的串口链路(创建串口节点并开始采集后出现)",
24
+ "link": "链路",
25
+ "pending": "在途",
26
+ "errors": "错误",
27
+ "lastUsed": "最近使用",
28
+ "nodes": "串口数采节点",
29
+ "nonodes": "暂无串口数采节点(数采中心 → 添加节点 → 协议选「串口 Serial ⌁」)"
30
+ }
31
+ },
32
+ "en": {
33
+ "panel": {
34
+ "title": "Serial Communication (serial-bridge)",
35
+ "refresh": "Refresh",
36
+ "available": "Protocol stack ready (serialport)",
37
+ "unavailable": "serialport not installed — run npm i serialport and restart",
38
+ "ports": "System serial ports",
39
+ "noports": "No serial devices found (plug in a USB-RS485/232 adapter and refresh)",
40
+ "path": "Port",
41
+ "manufacturer": "Manufacturer",
42
+ "serialNumber": "Serial No.",
43
+ "probe": "Connection probe",
44
+ "probe_hint": "Opens the port and reads once (nothing persisted); once it passes, create DAQ/write nodes with the same parameters",
45
+ "mode": "Mode",
46
+ "baud": "Baud",
47
+ "unit": "Unit",
48
+ "register": "Register",
49
+ "sendLine": "Probe line (ASCII)",
50
+ "run_probe": "Test connection",
51
+ "probing": "Testing…",
52
+ "links": "Open links",
53
+ "nolinks": "No open serial links yet (appears once a serial node starts sampling)",
54
+ "link": "Link",
55
+ "pending": "Pending",
56
+ "errors": "Errors",
57
+ "lastUsed": "Last used",
58
+ "nodes": "Serial DAQ nodes",
59
+ "nonodes": "No serial DAQ nodes yet (DAQ console → Add node → pick \"Serial ⌁\")"
60
+ }
61
+ }
62
+ }