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
@@ -104,9 +104,12 @@ export function createAlarmEventRepo(db: DatabaseSync) {
104
104
 
105
105
  return {
106
106
  /** 按 id 取报警关联节点(产线权限判定用);不存在返回 undefined */
107
- nodeIdById(id) {
107
+ nodeIdById(id: string): string | undefined {
108
108
  const r = db.prepare('SELECT node_id FROM alarm_events WHERE id = ?').get(id)
109
- return r?.node_id
109
+ // node_id 列是 TEXT NOT NULL,写入侧恒为 string(见 database.ts 的 alarm_events 定义);
110
+ // 显式收窄后,调用方拿到的是 string|undefined,而不是 node:sqlite 的 SQLOutputValue 联合
111
+ const v = r?.node_id
112
+ return typeof v === 'string' ? v : undefined
110
113
  },
111
114
 
112
115
  /** 报警产生(同 node+metric 未确认报警幂等去重,防止高频越限刷表) */
@@ -9,11 +9,16 @@
9
9
  * 覆盖链:config.yml < runtime-settings < env(AW_<键> 或历史别名)。时间列均为 TEXT ISO。
10
10
  * channel_events 与 daq_samples 已有各自保留机制,此处不重复覆盖。
11
11
  */
12
+ import type { DatabaseSync } from 'node:sqlite'
12
13
  import { retentionSettings } from '../settings'
13
14
 
14
- type DatabaseSyncLike = {
15
- prepare: (sql: string) => { run: (...args: unknown[]) => { changes: number | bigint } }
16
- }
15
+ /**
16
+ * 库句柄契约 = node:sqlite DatabaseSync 的只读子集。
17
+ * 早先手写的鸭子类型把 run 声明为 `(...args: unknown[])`,与 StatementSync 的
18
+ * `...anonymousParameters: SQLInputValue[]` 反向不兼容(unknown 形参不接受 SQLInputValue),
19
+ * 于是**真实的 DatabaseSync 反而传不进来**。直接从权威类型取子集即消除该漂移(纯类型,运行时零变化)。
20
+ */
21
+ type DatabaseSyncLike = Pick<DatabaseSync, 'prepare'>
17
22
 
18
23
  const DAY_MS = 86_400_000
19
24
  const BATCH = 5000
@@ -11,9 +11,10 @@
11
11
 
12
12
  import { randomUUID } from 'node:crypto'
13
13
  import { applyTransform, inverseTransform, normalizeDataTransform, dcwKeyFromRef } from '../../../../shared/dcw-protocol'
14
- import type { AepDcwNodeChange, DcwDriverKind, DcwNodeView, DataTransform, LineInput, LineQueryOpts, LineQueryResult, LineRunState, LineView, ProductInput, ProductView, RecipeInput, RecipeRunView, RecipeView, DcwWriteMeta } from '../../../../shared/dcw-protocol'
14
+ import type { AepDcwNodeChange, DcwDriverKind, DcwNodeView, DcwParamInput, DcwParamView, DataTransform, LineInput, LineQueryOpts, LineQueryResult, LineRunState, LineView, ProductInput, ProductView, RecipeInput, RecipeRunView, RecipeView, DcwWriteMeta } from '../../../../shared/dcw-protocol'
15
15
  import { AppError, ErrorCodes } from '../../../utils/errors'
16
16
  import { normalizeDcwDriverKind, resolveDcwDriver } from './drivers'
17
+ import { attachDcwPluginBridge } from './plugin-bridge'
17
18
  import { findDcwTemplate } from './dcw-templates'
18
19
  import { getDeviceTwinRepo } from '../assets/device-twin.repo'
19
20
  import { DcwNode } from './dcw-node'
@@ -22,6 +23,9 @@ import { DcwNodeRuntime } from './dcw-runtime'
22
23
  import { getDcwRecipeRepo, type DcwWriteHistoryEntry } from './dcw-recipe.repo'
23
24
  import { getDcwProductRepo } from './dcw-product.repo'
24
25
  import { getDcwLineRepo } from './dcw-line.repo'
26
+ import { getDcwParamRepo } from './param-map.repo'
27
+ import { assertWithinLimits, limitsBreakdownOf } from './param-limits'
28
+ import { deriveAccess } from './param-conversion'
25
29
  import { emitDcwWrite } from '@/server/services/workshop/plugins/host.mjs'
26
30
  import { clearActiveLineRun, getActiveLineRun, getAllActiveLineRuns, setActiveLineRun } from './line-run'
27
31
  import { getRecipeRollBackManager } from './recipe-rollback-manager'
@@ -31,6 +35,9 @@ import { daqRuntimeSettings } from '../settings'
31
35
  /** 运维日志写下发防噪:同节点同值 10s 内的心跳重下发不重复入册 */
32
36
  const opsWriteMemo = new Map<string, { eng: number, at: number }>()
33
37
 
38
+ // 写驱动插件桥(模块装载即挂;插件宿主先到则注册项排队、此处接管回放)
39
+ attachDcwPluginBridge()
40
+
34
41
  /** 周期读网关默认间隔(节点 readIntervalMs=null 时生效;真实 PLC 建议按链路承载调整) */
35
42
  const DEFAULT_READ_INTERVAL_MS = 5000
36
43
 
@@ -353,6 +360,9 @@ class DcwController {
353
360
  semantics: input.semantics,
354
361
  })
355
362
  this.repo.insert(node)
363
+ // 工艺参数映射面:节点创建即自动生成同名映射(用户/Agent 的参数语义面;
364
+ // PLC 寻址细节仍封装在节点驱动配置内)。失败不阻断节点创建(可事后手工补建)。
365
+ getDcwParamRepo().ensureForNode(node)
356
366
  this.syncRuntimes()
357
367
  this.emitNodeChanged('added', node)
358
368
  return node
@@ -431,6 +441,8 @@ class DcwController {
431
441
  if (!node) throw new AppError(404, ErrorCodes.NOT_FOUND, `控制节点不存在: ${id}`)
432
442
  this.repo.remove(id)
433
443
  this.runtimes.delete(id)
444
+ // 级联清理:工艺参数映射面随执行节点删除(参数不存在悬空引用)
445
+ getDcwParamRepo().removeForNode(id)
434
446
  // 级联清理:该节点的 Agent 绑定移除,挂起中的手动审批按失效收敛
435
447
  void import('../agents/node-bindings.repo').then(({ getAgentNodeBindingRepo }) => {
436
448
  const repo = getAgentNodeBindingRepo()
@@ -537,22 +549,21 @@ class DcwController {
537
549
  }
538
550
  // 调控闭环护栏(F1/F8):Agent 互斥(open 记录他人持有)+ 回退冷却方向性
539
551
  getRecipeRollBackManager().beforeWrite(node, eng, meta)
540
- // 写联锁按产线:仅当**本节点所属产线**在跑时,叠加该批次配方的工艺窗口
541
- const run = getActiveLineRun(node.lineId)
542
- if (run && recipeRunId == null) {
543
- const recipe = getDcwRecipeRepo().byId(run.recipeId)
544
- const param = recipe?.params.find(p => p.nodeId === id)
545
- if (param) {
546
- if (param.min != null && eng < param.min) {
547
- throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `设定值 ${eng}${node.unit} 低于当前配方「${run.recipeName}」的工艺下限 ${param.min}${node.unit}(节点全局量程 ${node.min}~${node.max} 不适用于本批次)`)
548
- }
549
- if (param.max != null && eng > param.max) {
550
- throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `设定值 ${eng}${node.unit} 超出当前配方「${run.recipeName}」的工艺上限 ${param.max}${node.unit}(节点全局量程 ${node.min}~${node.max} 不适用于本批次)`)
551
- }
552
- }
553
- }
552
+ // 写入限界分层联锁(param-limits):节点安全量程(结构层,驱动内 validateEng 结构性兜底)
553
+ // ∩ 工艺参数基准限界 ∩ 活动产品限界 ∩ 活动配方工艺窗口 —— 逐层收窄取交集,
554
+ // 手动 REST / Agent 工具 / 配方下发 / 回退四路共用本咽喉点,越界一律拒绝。
555
+ // D8 基准消融:仅 AW_BENCH_MODE=1 且 meta.benchArm='no-interlock'/'ungated' 时
556
+ // 旁路软联锁层(参数/产品/配方);结构层与硬量程校验结构性不可旁路;
557
+ // 生产模式(env 缺省)恒为 full 臂。
558
+ const benchNoInterlock = process.env.AW_BENCH_MODE === '1' && (meta?.benchArm === 'no-interlock' || meta?.benchArm === 'ungated')
559
+ // 配方下发路径(recipeRunId != null)跳过配方窗口层:下发值已在配方保存时对自身窗口校验,
560
+ // 且中途应用新配方不应被旧批次配方窗口误伤;产品/参数基准层对配方下发照常约束。
561
+ assertWithinLimits(node, eng, { includeSoft: !benchNoInterlock, skipRecipe: recipeRunId != null })
562
+ // D8: no-readback / ungated 臂 → 容差置 MAX,回读差异不致败(假成功语义,供 I2 消融)
563
+ const benchNoReadback = process.env.AW_BENCH_MODE === '1' && (meta?.benchArm === 'no-readback' || meta?.benchArm === 'ungated')
564
+ const benchToleranceOverride = benchNoReadback ? Number.MAX_VALUE : undefined
554
565
  const prevValue = typeof node.value === 'number' ? node.value : null
555
- const outcome = await rt.write(eng, recipeRunId)
566
+ const outcome = await rt.write(eng, recipeRunId, benchToleranceOverride)
556
567
  // 调控闭环入册:仅成功写记锚/开记录(失败写不动账本 —— PLC 值未变更);
557
568
  // 窗口聚合异步回填(F2)
558
569
  if (outcome.ok !== false) {
@@ -618,6 +629,90 @@ class DcwController {
618
629
  return this.testDriver(node.driver, node.driverConfig)
619
630
  }
620
631
 
632
+ // ---------- 工艺参数映射面(用户/Agent 的唯一语义读写面)----------
633
+
634
+ /**
635
+ * 创建工艺参数映射。两条路径:
636
+ * - nodeId 绑定路径:映射指向既有执行节点;
637
+ * - access 接入路径:设备连接 + 寄存器 + **标准转换模式**一键建映射 —— 系统
638
+ * 按转换模式展开驱动配置并自动创建执行节点(float32 直写 / int16·int32 线性
639
+ * 标定),配置期一次性选定,运行期双向换算全自动。
640
+ */
641
+ createParamMapping(input: DcwParamInput): DcwParamView {
642
+ const access = input.access
643
+ if (!access) {
644
+ const row = getDcwParamRepo().create(input)
645
+ return this.paramById(row.id)
646
+ }
647
+ if (input.nodeId) {
648
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, 'access 与 nodeId 互斥:access 路径由系统自动创建执行节点')
649
+ }
650
+ if (!input.templateRef) {
651
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, 'templateRef 必填:工艺参数需绑定语义模板(单位/量程/语义)')
652
+ }
653
+ const lineId = String(input.lineId ?? '')
654
+ if (lineId && !getDcwLineRepo().byId(lineId)) throw new AppError(404, ErrorCodes.NOT_FOUND, `产线不存在: ${lineId}`)
655
+ const derived = deriveAccess(access)
656
+ // 执行节点:驱动配置由转换模式展开(单一事实源);标定工程量程即节点安全量程
657
+ const node = this.create({
658
+ templateRef: input.templateRef,
659
+ name: input.name,
660
+ driver: derived.driver,
661
+ driverConfig: derived.driverConfig,
662
+ min: derived.engMin,
663
+ max: derived.engMax,
664
+ lineId,
665
+ })
666
+ // this.create() 已自动生成参数面;覆盖语义面(key/名称/基准限界)并留存转换模式
667
+ const auto = getDcwParamRepo().byNode(node.id)
668
+ const row = getDcwParamRepo().update(auto!.id, {
669
+ key: input.key,
670
+ name: input.name,
671
+ min: input.min ?? null,
672
+ max: input.max ?? null,
673
+ conversion: access.conversion,
674
+ })
675
+ return this.paramById(row.id)
676
+ }
677
+
678
+ listParamViews(): DcwParamView[] {
679
+ return getDcwParamRepo().listViews()
680
+ }
681
+
682
+ paramById(id: string): DcwParamView {
683
+ const row = getDcwParamRepo().byId(id)
684
+ if (!row) throw new AppError(404, ErrorCodes.NOT_FOUND, `工艺参数不存在: ${id}`)
685
+ const view = getDcwParamRepo().viewOf(row)
686
+ if (!view) throw new AppError(404, ErrorCodes.NOT_FOUND, `工艺参数 ${id} 的执行节点已不存在(映射悬空)`)
687
+ return view
688
+ }
689
+
690
+ /** 参数寻址写命令:工程量直写,限界联锁在 write() 咽喉点统一生效 */
691
+ async writeParam(id: string, value: number, meta?: DcwWriteMeta, recipeRunId: string | null = null) {
692
+ const row = getDcwParamRepo().byId(id)
693
+ const node = row ? this.repo.byId(row.nodeId) : undefined
694
+ if (!row || !node) throw new AppError(404, ErrorCodes.NOT_FOUND, `工艺参数不存在(或执行节点已删除): ${id}`)
695
+ const outcome = await this.write(node.id, value, recipeRunId, meta)
696
+ return { ...outcome, param: getDcwParamRepo().viewOf(row) }
697
+ }
698
+
699
+ /** 参数寻址读命令(读执行节点 PLC 值,标定解码为物理量) */
700
+ async readParam(id: string) {
701
+ const row = getDcwParamRepo().byId(id)
702
+ const node = row ? this.repo.byId(row.nodeId) : undefined
703
+ if (!row || !node) throw new AppError(404, ErrorCodes.NOT_FOUND, `工艺参数不存在(或执行节点已删除): ${id}`)
704
+ const read = await this.readNow(node.id)
705
+ return { ...read, param: getDcwParamRepo().viewOf(row) }
706
+ }
707
+
708
+ /** 参数当前有效写入限界剖面(分层 + 交集;展示/工具回包共用) */
709
+ paramLimitsOf(id: string) {
710
+ const row = getDcwParamRepo().byId(id)
711
+ const node = row ? this.repo.byId(row.nodeId) : undefined
712
+ if (!row || !node) throw new AppError(404, ErrorCodes.NOT_FOUND, `工艺参数不存在(或执行节点已删除): ${id}`)
713
+ return limitsBreakdownOf(node)
714
+ }
715
+
621
716
  // ---------- Recipe 配方 ----------
622
717
 
623
718
  listRecipes() {
@@ -950,6 +1045,7 @@ class DcwController {
950
1045
  createProduct(input: ProductInput): ProductView {
951
1046
  const lineId = String(input.lineId ?? '')
952
1047
  if (lineId && !getDcwLineRepo().byId(lineId)) throw new AppError(404, ErrorCodes.NOT_FOUND, `产线不存在: ${lineId}`)
1048
+ assertProductParamLimits(lineId, input.paramLimits)
953
1049
  return getDcwProductRepo().create({ ...input, lineId })
954
1050
  }
955
1051
 
@@ -959,6 +1055,8 @@ class DcwController {
959
1055
  if (!getDcwLineRepo().byId(lid)) throw new AppError(404, ErrorCodes.NOT_FOUND, `产线不存在: ${lid}`)
960
1056
  }
961
1057
  const prev = getDcwProductRepo().byId(id)
1058
+ const targetLine = String(patch.lineId ?? prev?.lineId ?? '')
1059
+ assertProductParamLimits(targetLine, patch.paramLimits)
962
1060
  const updated = getDcwProductRepo().update(id, patch)
963
1061
  // 产品换线 → 旗下配方产线归属级联(开跑校验按 recipe.lineId)
964
1062
  if (prev && prev.lineId !== updated.lineId) {
@@ -1032,6 +1130,39 @@ class DcwController {
1032
1130
 
1033
1131
  // ---------- 单例(HMR 存活) ----------
1034
1132
 
1133
+ /**
1134
+ * 产品级工艺参数限界校验:键必须命中该产品所属产线上的工艺参数映射
1135
+ * (防静默失效的拼错键);区间须 min<=max 且为有限数字。产品未挂产线时限界无
1136
+ * 寻址语境,拒绝非空限界。
1137
+ */
1138
+ function assertProductParamLimits(lineId: string, paramLimits: ProductInput['paramLimits'] | undefined): void {
1139
+ if (paramLimits == null) return
1140
+ if (typeof paramLimits !== 'object' || Array.isArray(paramLimits)) {
1141
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, 'paramLimits 需为对象(键=工艺参数 key,值={min?,max?})')
1142
+ }
1143
+ const entries = Object.entries(paramLimits)
1144
+ if (entries.length === 0) return
1145
+ if (!lineId) {
1146
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, '产品未挂载产线,不可设定工艺参数限界(限界按产线内的工艺参数寻址)')
1147
+ }
1148
+ const lineKeys = new Set(
1149
+ getDcwParamRepo().listViews().filter(p => p.lineId === lineId).map(p => p.key),
1150
+ )
1151
+ for (const [key, range] of entries) {
1152
+ if (!lineKeys.has(key)) {
1153
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `产品限界的工艺参数键「${key}」在产线上不存在(可用的工艺参数:${[...lineKeys].join(', ') || '无 —— 先创建写控节点生成参数面'})`)
1154
+ }
1155
+ const min = range?.min == null ? null : Number(range.min)
1156
+ const max = range?.max == null ? null : Number(range.max)
1157
+ if ((range?.min != null && !Number.isFinite(min)) || (range?.max != null && !Number.isFinite(max))) {
1158
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `产品限界「${key}」的上下限需为数字`)
1159
+ }
1160
+ if (min != null && max != null && min > max) {
1161
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `产品限界「${key}」非法:min ${min} > max ${max}`)
1162
+ }
1163
+ }
1164
+ }
1165
+
1035
1166
  const g = globalThis as typeof globalThis & { __dcwController?: DcwController }
1036
1167
 
1037
1168
  export function getDcwController(): DcwController {
@@ -19,13 +19,43 @@ const DB_PATH = join(ensureDataDir(), 'dcw-products.json')
19
19
  function load(): ProductView[] {
20
20
  try {
21
21
  const parsed = loadJsonFile(DB_PATH, null)
22
- return Array.isArray(parsed) ? parsed as ProductView[] : []
22
+ return Array.isArray(parsed) ? parsed.map(normRow) as ProductView[] : []
23
23
  }
24
24
  catch {
25
25
  return []
26
26
  }
27
27
  }
28
28
 
29
+ /** 行规范化:paramLimits 键值容错(非有限数字的侧丢弃;min>max 整条丢弃) */
30
+ function normRow(p: ProductView): ProductView {
31
+ const raw = p.paramLimits
32
+ if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) return p
33
+ const out: ProductView['paramLimits'] = {}
34
+ for (const [key, range] of Object.entries(raw)) {
35
+ if (!key || range == null || typeof range !== 'object') continue
36
+ const min = range.min == null ? undefined : Number(range.min)
37
+ const max = range.max == null ? undefined : Number(range.max)
38
+ const okMin = min == null || Number.isFinite(min)
39
+ const okMax = max == null || Number.isFinite(max)
40
+ if (!okMin || !okMax) continue
41
+ if (min != null && max != null && min > max) continue
42
+ const entry: { min?: number, max?: number } = {}
43
+ if (min != null) entry.min = min
44
+ if (max != null) entry.max = max
45
+ if (Object.keys(entry).length > 0) out[key] = entry
46
+ }
47
+ return { ...p, paramLimits: out }
48
+ }
49
+
50
+ /** 载荷规范化(创建/更新共用;键级有效性由控制器按产线工艺参数校验) */
51
+ function normLimitsInput(v: unknown): Record<string, { min?: number, max?: number }> | undefined {
52
+ if (v == null) return undefined
53
+ if (typeof v !== 'object' || Array.isArray(v)) {
54
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, 'paramLimits 需为对象(键=工艺参数 key)')
55
+ }
56
+ return normRow({ paramLimits: v as ProductView['paramLimits'] } as ProductView).paramLimits
57
+ }
58
+
29
59
  class DcwProductRepo {
30
60
  private list: ProductView[] = load()
31
61
 
@@ -47,6 +77,8 @@ class DcwProductRepo {
47
77
  description: String(input.description ?? '').trim(),
48
78
  createdAt: new Date().toISOString(),
49
79
  }
80
+ const limits = normLimitsInput(input.paramLimits)
81
+ if (limits) p.paramLimits = limits
50
82
  this.list.push(p)
51
83
  this.flush()
52
84
  return p
@@ -62,6 +94,11 @@ class DcwProductRepo {
62
94
  }
63
95
  if (patch.description !== undefined) p.description = String(patch.description).trim()
64
96
  if (patch.lineId !== undefined) p.lineId = String(patch.lineId)
97
+ if (patch.paramLimits !== undefined) {
98
+ const limits = normLimitsInput(patch.paramLimits)
99
+ if (limits && Object.keys(limits).length > 0) p.paramLimits = limits
100
+ else delete p.paramLimits
101
+ }
65
102
  this.flush()
66
103
  return p
67
104
  }
@@ -64,7 +64,7 @@ export class DcwNodeRuntime {
64
64
  * 用户/Recipe 设定值(核心写命令入口)。
65
65
  * 工艺安全量程硬校验(越界拒绝)+ 在飞互斥(同一通道同时只允许一个写事务)。
66
66
  */
67
- async write(eng: number, recipeRunId: string | null = null): Promise<DcwWriteOutcome> {
67
+ async write(eng: number, recipeRunId: string | null = null, toleranceOverride?: number): Promise<DcwWriteOutcome> {
68
68
  const node = this.node
69
69
  const invalid = node.validateEng(eng)
70
70
  if (invalid) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, invalid)
@@ -75,7 +75,8 @@ export class DcwNodeRuntime {
75
75
  node.state = 'writing'
76
76
  node.lastWriteAt = new Date().toISOString()
77
77
  try {
78
- return await this.host.executeWrite(node, eng, writeTolerance(node), recipeRunId)
78
+ // D8 基准消融:no-readback/ungated 臂由 write() 传入 MAX 容差 → 回读差异不再致败(假成功语义)
79
+ return await this.host.executeWrite(node, eng, toleranceOverride ?? writeTolerance(node), recipeRunId)
79
80
  }
80
81
  catch (err) {
81
82
  // 执行链抛错(异常不流向 outcome):状态机必须离开 writing,否则通道永久 409
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import { createRequire } from 'node:module'
11
11
  import type { DcwDriverKind } from '../../../../shared/dcw-protocol'
12
+ import { DCW_DRIVERS } from '../../../../shared/dcw-protocol'
12
13
  import { AppError } from '../../../utils/errors'
13
14
  import { classifyCommError, closeModbusSafely, decodeRegisters, evictModbusConn, evictOpcUaConn, getModbusConn, getOpcUaConn, modbusKey, registerOffset, withModbusConn } from '../daq/drivers'
14
15
 
@@ -524,7 +525,10 @@ export const mqttDcwDriver: DcwWriteDriver = {
524
525
  // 失败/超时路径同样关闭连接(此前只在成功后 end,超时会泄漏 client +
525
526
  // broker session),已关闭的 client 重复 end 幂等安全
526
527
  await new Promise<void>((resolve) => {
527
- client.end(false, {}, resolve)
528
+ // mqtt 的 DoneCallback 是 Node 风格 (error?: Error) => void;直接把 Promise 的 resolve
529
+ // 传进去会把 error 当成"完成值"兑现(错误被吞)。这里显式包一层,保持原语义:
530
+ // 回调一触发即完成,且与原来一样**不**因该 error 而 reject。
531
+ client.end(false, {}, () => resolve())
528
532
  })
529
533
  }
530
534
  return {
@@ -552,7 +556,7 @@ export const mqttDcwDriver: DcwWriteDriver = {
552
556
  })
553
557
  c.once('connect', () => resolve(c))
554
558
  c.once('error', err => reject(new Error(err.message)))
555
- }).then(c => new Promise<void>(r => c.end(false, {}, r)))
559
+ }).then(c => new Promise<void>(r => c.end(false, {}, () => r())))
556
560
  return { ok: true, message: `Broker 连接成功(${String(driverConfig.host)}:${Number(driverConfig.port ?? 1883)});未执行发布,避免误触发真实设备动作` }
557
561
  }
558
562
  catch (err) {
@@ -634,12 +638,94 @@ const REGISTRY: Record<DcwDriverKind, DcwWriteDriver> = {
634
638
  'http': httpDcwDriver,
635
639
  }
636
640
 
641
+ /**
642
+ * 插件写驱动注册表(经 ctx.dcw.registerWriteDriver;与数采侧 registerPluginDriver 对称)。
643
+ * globalThis 防 HMR 双实例;resolveDcwDriver 插件优先,热重载同名幂等覆盖。
644
+ * 驱动可携带可选 meta(label/configFields)进写控驱动目录(dcwDriverCatalog)。
645
+ */
646
+ export interface PluginWriteDriverMeta {
647
+ kind: string
648
+ label: string
649
+ status: 'builtin' | 'real' | 'planned'
650
+ configFields?: import('../../../../shared/daq-protocol').DriverConfigField[]
651
+ plugin: true
652
+ }
653
+
654
+ const g_plugins = globalThis as typeof globalThis & { __dcwPluginDrivers?: Map<string, DcwWriteDriver> }
655
+ function pluginRegistry(): Map<string, DcwWriteDriver> {
656
+ return g_plugins.__dcwPluginDrivers ??= new Map()
657
+ }
658
+
659
+ const g_metas = globalThis as typeof globalThis & { __dcwPluginDriverMetas?: Map<string, PluginWriteDriverMeta> }
660
+ function pluginMetaRegistry(): Map<string, PluginWriteDriverMeta> {
661
+ return g_metas.__dcwPluginDriverMetas ??= new Map()
662
+ }
663
+
664
+ export function registerPluginWriteDriver(driver: DcwWriteDriver): void {
665
+ if (driver.kind in REGISTRY) console.warn(`[dcw-drivers] 插件写驱动覆盖内置:「${driver.kind}」`)
666
+ pluginRegistry().set(driver.kind, driver)
667
+ const meta = (driver as DcwWriteDriver & { meta?: Omit<PluginWriteDriverMeta, 'kind' | 'plugin'> }).meta
668
+ if (meta && typeof meta.label === 'string') {
669
+ pluginMetaRegistry().set(driver.kind, {
670
+ kind: driver.kind,
671
+ label: meta.label,
672
+ status: meta.status === 'builtin' || meta.status === 'planned' ? meta.status : 'real',
673
+ configFields: Array.isArray(meta.configFields) ? meta.configFields : [],
674
+ plugin: true,
675
+ })
676
+ }
677
+ else {
678
+ pluginMetaRegistry().delete(driver.kind)
679
+ }
680
+ }
681
+
682
+ export function listPluginWriteDrivers(): string[] {
683
+ return [...pluginRegistry().keys()]
684
+ }
685
+
686
+ /** 插件写驱动自描述目录(写控驱动目录合并端点用) */
687
+ export function listPluginWriteDriverMetas(): PluginWriteDriverMeta[] {
688
+ return [...pluginMetaRegistry().values()]
689
+ }
690
+
691
+ /** 清空插件写驱动与其自描述目录(插件宿主热重载前调用;与数采侧 clearPluginDrivers 对称) */
692
+ export function clearPluginWriteDrivers(): void {
693
+ pluginRegistry().clear()
694
+ pluginMetaRegistry().clear()
695
+ }
696
+
697
+ /**
698
+ * 写控驱动目录(内置 DCW_DRIVERS + 插件自描述合并;同名插件条目覆盖内置)。
699
+ * dcw index.get 以 `drivers` 下发,前端写控节点向导据此渲染。
700
+ */
701
+ export async function dcwDriverCatalog(): Promise<Array<{
702
+ kind: string
703
+ label: string
704
+ status: 'builtin' | 'real' | 'planned'
705
+ configFields: import('../../../../shared/daq-protocol').DriverConfigField[]
706
+ plugin?: boolean
707
+ }>> {
708
+ const out: Array<{ kind: string, label: string, status: 'builtin' | 'real' | 'planned', configFields: import('../../../../shared/daq-protocol').DriverConfigField[], plugin?: boolean }> = DCW_DRIVERS.map(d => ({
709
+ kind: d.kind,
710
+ label: d.label,
711
+ status: d.status,
712
+ configFields: d.configFields,
713
+ }))
714
+ for (const m of listPluginWriteDriverMetas()) {
715
+ const i = out.findIndex(d => d.kind === m.kind)
716
+ const entry = { kind: m.kind, label: m.label, status: m.status, configFields: m.configFields ?? [], plugin: true }
717
+ if (i >= 0) out[i] = entry
718
+ else out.push(entry)
719
+ }
720
+ return out
721
+ }
722
+
637
723
  export function normalizeDcwDriverKind(kind: string): DcwDriverKind {
638
724
  if (kind === 'modbus') return 'modbus-tcp'
639
725
  if (kind === 'rtu' || kind === 'modbus-rtu-tcp') return 'modbus-rtu'
640
- return (kind in REGISTRY ? kind : 'mock') as DcwDriverKind
726
+ return ((kind in REGISTRY || pluginRegistry().has(kind) ? kind : 'mock')) as DcwDriverKind
641
727
  }
642
728
 
643
729
  export function resolveDcwDriver(kind: DcwDriverKind): DcwWriteDriver {
644
- return REGISTRY[kind] ?? mockDcwDriver
730
+ return pluginRegistry().get(kind) ?? REGISTRY[kind] ?? mockDcwDriver
645
731
  }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * ParamConversion —— 工艺参数 → PLC 标准转换模式展开器(配置期)。
3
+ *
4
+ * 用户在映射配置期选定「标准转换模式」(float32 直写 / int16·int32 线性标定)
5
+ * 与设备连接 + 寄存器,本模块将其一次性展开为执行节点驱动配置:
6
+ * - float32 原始值 = 工程值,驱动直写 float32
7
+ * - int16-scaled eng∈[engMin,engMax] 线性映射 raw∈[rawMin,rawMax],int16 编码
8
+ * - int32-scaled 同上,int32 编码
9
+ * 运行期双向换算(下发 encode / 回读 decode)由既有驱动链完成,本模块零参与 ——
10
+ * 展开产物即单一事实源(driverConfig),转换模式本身仅作展示/审计摘要留存。
11
+ */
12
+
13
+ import type { ParamAccessSpec } from '../../../../shared/dcw-protocol'
14
+ import { AppError, ErrorCodes } from '../../../utils/errors'
15
+
16
+ /** 展开产物:执行节点创建所需的驱动字段(结构与 DCW 驱动 configFields 一致) */
17
+ export interface DerivedAccess {
18
+ driver: 'modbus-tcp' | 'modbus-rtu'
19
+ driverConfig: Record<string, string | number | boolean>
20
+ /** 节点工艺安全量程(取标定工程量程;float32 模式由调用方回退模板量程) */
21
+ engMin?: number
22
+ engMax?: number
23
+ }
24
+
25
+ /** 校验 + 展开:接入规格 → 驱动配置(非法配置在配置期即拒,不带病运行) */
26
+ export function deriveAccess(access: ParamAccessSpec): DerivedAccess {
27
+ const driver = access.driver ?? 'modbus-tcp'
28
+ const host = String(access.host ?? '').trim()
29
+ if (!host) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, 'access.host 必填:设备连接地址(PLC/网关 IP)')
30
+ const register = Number(access.register)
31
+ if (!Number.isInteger(register) || register < 40001 || register > 49999) {
32
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `access.register 须为 4xxxx 保持寄存器地址(当前: ${String(access.register)})`)
33
+ }
34
+ const conv = access.conversion
35
+ if (!conv || !['float32', 'int16-scaled', 'int32-scaled'].includes(conv?.mode)) {
36
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `access.conversion.mode 须为标准转换模式之一: float32 / int16-scaled / int32-scaled(当前: ${String(conv?.mode)})`)
37
+ }
38
+ const byteOrder = conv.byteOrder ?? 'big'
39
+ const base = {
40
+ host,
41
+ port: access.port == null ? 502 : Number(access.port),
42
+ unitId: access.unitId == null ? 1 : Number(access.unitId),
43
+ register,
44
+ }
45
+
46
+ if (conv.mode === 'float32') {
47
+ // float32 直写:未配原始量程 → 驱动 raw=eng
48
+ return {
49
+ driver,
50
+ driverConfig: { ...base, dataType: 'float32', byteOrder },
51
+ }
52
+ }
53
+
54
+ // int16/int32 线性标定:标定量程必填且 eng 跨度/raw 跨度非零
55
+ const engMin = Number(conv.engMin)
56
+ const engMax = Number(conv.engMax)
57
+ const rawMin = Number(conv.rawMin)
58
+ const rawMax = Number(conv.rawMax)
59
+ for (const [k, v] of [['engMin', engMin], ['engMax', engMax], ['rawMin', rawMin], ['rawMax', rawMax]] as const) {
60
+ if (!Number.isFinite(v)) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `转换模式 ${conv.mode} 需要完整线性标定:${k} 缺失或非数字`)
61
+ }
62
+ if (engMax === engMin) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `线性标定非法:engMax(${engMax}) 不得等于 engMin`)
63
+ if (rawMax === rawMin) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `线性标定非法:rawMax(${rawMax}) 不得等于 rawMin`)
64
+ return {
65
+ driver,
66
+ driverConfig: {
67
+ ...base,
68
+ dataType: conv.mode === 'int16-scaled' ? 'int16' : 'int32',
69
+ byteOrder,
70
+ engMin,
71
+ engMax,
72
+ rawMin,
73
+ rawMax,
74
+ },
75
+ engMin,
76
+ engMax,
77
+ }
78
+ }