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
@@ -32,7 +32,7 @@ import { extractJsonArray, peerPrompt, supervisePrompt, systemManual, toolArgsPr
32
32
  import { getHitlRegistry } from './hitl-registry'
33
33
  import { harnessSettings } from '../settings'
34
34
  import { spawnLineProcess } from './adapters/line-spawn'
35
- import { BaseAgentImpl } from './base-agent'
35
+ import { BaseAgentImpl, type BaseAgentConfigView } from './base-agent'
36
36
  import { resolveBridgePath, resolvePlatformBaseUrl } from './harness-env'
37
37
 
38
38
  const log = createLogger('workshop.opencode')
@@ -152,7 +152,7 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
152
152
  return 'opencode'
153
153
  }
154
154
 
155
- protected configRecord(): Record<string, unknown> {
155
+ protected configRecord(): BaseAgentConfigView {
156
156
  return this.config
157
157
  }
158
158
 
@@ -222,7 +222,7 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
222
222
  }
223
223
 
224
224
  /** HITL 应答(codex/opencode/dsh 统一入口;本 impl 处理 opencode-permission) */
225
- async respondHitl(kind: string, id: string, outcome: {
225
+ override async respondHitl(kind: string, id: string, outcome: {
226
226
  confirmed?: boolean
227
227
  cancelled?: boolean
228
228
  value?: string
@@ -247,7 +247,9 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
247
247
  await this.api('POST', `/question/${encodeURIComponent(id)}/reply`, { answer: outcome.value ?? outcome.comment ?? '' })
248
248
  }
249
249
  }
250
- if (pending.timer) clearTimeout(p.timer)
250
+ // 原为 clearTimeout(p.timer):p 在本作用域不存在(ReferenceError,且仅当计时器非空时触发),
251
+ // 该待办条目在此处就是 pending(与 dsh/codex/hermes 的同名分支写法一致)。
252
+ if (pending.timer) clearTimeout(pending.timer)
251
253
  this.pendingHitl.delete(id)
252
254
  getHitlRegistry().resolve(kind, id, cancelled ? 'cancelled' : 'answered')
253
255
  }
@@ -273,9 +275,9 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
273
275
  }
274
276
  }
275
277
 
276
- private supervising = false
278
+ // supervising 由 BaseAgentImpl 持有(protected):同名私有声明会与基类形成"两个私有声明"(TS2415)。
277
279
 
278
- async supervise(snapshot: SupervisionSnapshot, ctx: AgentRunContext, opts?: { signal?: AbortSignal }): Promise<SupervisionDecision[]> {
280
+ override async supervise(snapshot: SupervisionSnapshot, ctx: AgentRunContext, opts?: { signal?: AbortSignal }): Promise<SupervisionDecision[]> {
279
281
  // 引擎/会话不可用 → 本轮监督降级为空(调度器回退规则引擎);绝不让引擎故障
280
282
  // 以 unhandledRejection 形态逃逸(dev-stability-guard 会据此杀掉整个服务端)
281
283
  try {
@@ -296,16 +298,22 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
296
298
  memory: ctx.memory,
297
299
  })
298
300
  const timeoutMs = this.config.superviseTimeoutMs ?? 150_000
299
- return await this.runTurn(prompt, undefined, {
300
- timeoutMs,
301
- signal: opts?.signal,
302
- onDone: async () => {
303
- this.supervising = false
304
- },
305
- beforeStart: () => {
306
- this.supervising = true
307
- },
308
- }).then((events) => {
301
+ // runTurn 是 async generator(返回 AsyncGenerator,不是 Promise),必须以异步迭代消费完整个回合。
302
+ // (原实现写 .then(...)/.catch(...):运行时是 "then is not a function" 的 TypeError,supervise 整条路径实际不可用。)
303
+ try {
304
+ const events: AgentEvent[] = []
305
+ for await (const e of this.runTurn(prompt, undefined, {
306
+ timeoutMs,
307
+ signal: opts?.signal,
308
+ onDone: async () => {
309
+ this.supervising = false
310
+ },
311
+ beforeStart: () => {
312
+ this.supervising = true
313
+ },
314
+ })) {
315
+ events.push(e)
316
+ }
309
317
  // 从事件流提取最终文本 → JSON 决策兜底(工具直执行路径返回空)
310
318
  let text = ''
311
319
  for (const e of events) {
@@ -315,10 +323,21 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
315
323
  }
316
324
  const parsed = extractJsonArray(text)
317
325
  return parsed && parsed.length > 0 ? parsed as SupervisionDecision[] : []
318
- }).catch(() => {
326
+ }
327
+ catch {
319
328
  this.supervising = false
320
329
  return []
321
- })
330
+ }
331
+ }
332
+
333
+ /** supervise 用:收齐整个回合的事件(基类抽象成员;本类 supervise 自行消费并需 beforeStart/onDone 钩子) */
334
+ protected async collectTurnEvents(prompt: string, timeoutMs: number, signal?: AbortSignal): Promise<AgentEvent[]> {
335
+ const events: AgentEvent[] = []
336
+ for await (const e of this.runTurn(prompt, undefined, { timeoutMs, signal })) {
337
+ events.push(e)
338
+ if (e.kind === 'error') break
339
+ }
340
+ return events
322
341
  }
323
342
 
324
343
  protected async* workerTurn(request: AgentRunRequest, ctx: AgentRunContext): AsyncGenerator<AgentEvent, void, unknown> {
@@ -800,10 +819,17 @@ export class OpenCodeAgentImpl extends BaseAgentImpl implements AgentInterface {
800
819
  }
801
820
  const createWith = async (extra: Record<string, unknown> | undefined): Promise<Record<string, unknown>> =>
802
821
  this.api('POST', '/session', { ...body, ...(extra ?? {}) })
803
- const created = await (this.config.permission ?? DEFAULT_PERMISSION)
822
+ // 历史实现写作 `await (cond) ? a : b`:await 只作用于条件,整个三元表达式的结果是**未 await** 的
823
+ // Promise,故 created?.id 恒为 undefined → sessionId 恒为 String(undefined ?? '') === '' →
824
+ // 必然落到下面的 2.5s 重试循环(真正被使用的会话来自重试那次 createWith(undefined))。
825
+ // 此处逐字保留该运行时语义:同样的 await 让出一次微任务、同样的请求照发、首次结果同样丢弃。
826
+ // 直白的修法是给整个三元表达式加括号 await(会用上带 permission 的首次会话、省掉 2.5s 重试),
827
+ // 但那会改变 opencode 会话的权限策略与启动时序,故本次不改 —— 见报告"未修的缺陷"一节。
828
+ const firstAttempt = await (this.config.permission ?? DEFAULT_PERMISSION)
829
+ void (firstAttempt
804
830
  ? createWith({ permission: this.config.permission ?? DEFAULT_PERMISSION }).catch(() => createWith(undefined))
805
- : createWith(undefined)
806
- this.sessionId = String(created?.id ?? '')
831
+ : createWith(undefined))
832
+ this.sessionId = ''
807
833
  if (!this.sessionId) {
808
834
  // 瞬态失败(实例刚起/内部分配竞态/权限规则不兼容)→ 无附加字段重试,最多 3 次
809
835
  for (let i = 0; i < 3 && !this.sessionId; i++) {
@@ -27,7 +27,7 @@ import type {
27
27
  } from './agent-interface'
28
28
  import type { AgentContextStats } from '../types/task'
29
29
  import { markHarnessProcessExit, registerHarnessProcess, bindHarnessProcess, killHarnessProcess } from './harness-process'
30
- import { peerPrompt, systemManual, toolArgsPreview, workerPrompt } from './prompt-builder'
30
+ import { extractJsonArray, peerPrompt, supervisePrompt, systemManual, toolArgsPreview, workerPrompt } from './prompt-builder'
31
31
  import { getHitlRegistry } from './hitl-registry'
32
32
  import { harnessSettings } from '../settings'
33
33
  import { StdioJsonRpcClient, type JsonRpcRequestIncoming } from './adapters/stdio-jsonrpc'
@@ -111,7 +111,9 @@ export interface QwenAgentConfig {
111
111
  export class QwenAgentImpl extends BaseAgentImpl {
112
112
  private readonly config: QwenAgentConfig
113
113
 
114
- private agentRole: 'lead' | 'worker' = 'worker'
114
+ // agentRole 由 BaseAgentImpl 持有(protected);同名私有声明会遮蔽基类 identity 角色。
115
+ // 仅收窄可见性声明,运行时代码不变。
116
+ protected override agentRole: 'lead' | 'worker' = 'worker'
115
117
  private client: StdioJsonRpcClient | null = null
116
118
  private clientStarting: Promise<void> | null = null
117
119
  private turnActive = false
@@ -177,7 +179,7 @@ export class QwenAgentImpl extends BaseAgentImpl {
177
179
  }
178
180
 
179
181
  /** HITL 应答:requestToolCallConfirmation → outcome allow/reject/cancel(fail-closed) */
180
- async respondHitl(kind: string, id: string, outcome: {
182
+ override async respondHitl(kind: string, id: string, outcome: {
181
183
  confirmed?: boolean
182
184
  cancelled?: boolean
183
185
  }): Promise<void> {
@@ -202,7 +204,7 @@ export class QwenAgentImpl extends BaseAgentImpl {
202
204
 
203
205
  // ===== run / supervise =====
204
206
 
205
- async supervise(snapshot: import('./agent-interface').SupervisionSnapshot, ctx: AgentRunContext, opts?: { signal?: AbortSignal }): Promise<import('./agent-interface').SupervisionDecision[]> {
207
+ override async supervise(snapshot: import('./agent-interface').SupervisionSnapshot, ctx: AgentRunContext, opts?: { signal?: AbortSignal }): Promise<import('./agent-interface').SupervisionDecision[]> {
206
208
  await this.ensureClient(ctx)
207
209
  if (!this.client || !this.sessionStarted) return []
208
210
  if (this.supervising) return []
@@ -216,7 +218,10 @@ export class QwenAgentImpl extends BaseAgentImpl {
216
218
  })
217
219
  this.supervising = true
218
220
  try {
219
- const events = await this.collectTurn(prompt, this.config.superviseTimeoutMs ?? 150_000, opts?.signal)
221
+ // 原为 this.collectTurn(...):该类唯一存在的整回合收集实现是 collectTurnEvents
222
+ // (与基类 supervise 调用的同名抽象成员同签名)。原调用在运行时抛 TypeError 并被
223
+ // 本方法的 catch 吞掉 → supervise 恒返回 [],详见报告。
224
+ const events = await this.collectTurnEvents(prompt, this.config.superviseTimeoutMs ?? 150_000, opts?.signal)
220
225
  let text = ''
221
226
  for (const e of events) {
222
227
  if (e.kind === 'artifact') {
@@ -72,7 +72,9 @@ export function inventory(): AmlInventory {
72
72
  kind: EntityIssue['kind'],
73
73
  rows: Array<{ id: string, path?: string }>,
74
74
  baseDir: string,
75
- contract: string[],
75
+ // 非空契约列表:三个调用点都传字面量,且下面按"多选一"语义读 contract[0] 作展示路径,
76
+ // 用非空元组表达"至少一项"这一既有前提(纯类型,运行时零变化)
77
+ contract: readonly [string, ...string[]],
76
78
  ): number => {
77
79
  const ids = new Set(rows.map(r => r.id))
78
80
  for (const r of rows) {
@@ -362,7 +362,8 @@ export async function installUv(rt: AmlRuntime, onLog?: ProgressFn): Promise<{ o
362
362
  const hit = findUvInDir(rt.toolsDir)
363
363
  if (hit) {
364
364
  const v = await runCapture(hit, ['--version'], 15_000)
365
- const version = (v.out || v.err).trim().split('\n')[0]
365
+ // 运行时 `''.split('\n')[0] === ''`,首行恒存在;?? '' 只是 noUncheckedIndexedAccess 下的写法
366
+ const version = (v.out || v.err).trim().split('\n')[0] ?? ''
366
367
  writeUvRecord(rt, { uvPath: hit, version, installedAt: new Date().toISOString(), installDir: rt.toolsDir, method: 'official-script' })
367
368
  resetProbeCache()
368
369
  emit(`安装成功:${version} @ ${hit}`)
@@ -390,7 +391,7 @@ export async function installUv(rt: AmlRuntime, onLog?: ProgressFn): Promise<{ o
390
391
  }
391
392
  catch { /* Windows 无 x 位 */ }
392
393
  const v = await runCapture(hit, ['--version'], 15_000)
393
- const version = (v.out || v.err).trim().split('\n')[0]
394
+ const version = (v.out || v.err).trim().split('\n')[0] ?? ''
394
395
  writeUvRecord(rt, { uvPath: hit, version, installedAt: new Date().toISOString(), installDir: rt.toolsDir, method: 'pip-target' })
395
396
  resetProbeCache()
396
397
  emit(`安装成功:${version} @ ${hit}`)
@@ -25,6 +25,10 @@ const TOPIC_WILDCARD = `${TOPIC_ROOT}/+/sample`
25
25
  /** 断连离线缓冲上限(满丢最旧;补发帧按消费侧乱序防御自然去重) */
26
26
  const OFFLINE_CAP = 2000
27
27
 
28
+ /** MQTT QoS 协议取值(0/1/2;shared/config/schema.json 的 daq.mqtt.qos 声明 min 0 / max 2)。
29
+ * settings 面(settings.ts)把该配置读成 number,这里按协议收窄 —— 运行时原值透传,不做钳制/兜底。 */
30
+ type MqttQos = 0 | 1 | 2
31
+
28
32
  export class MqttQueueAdapter implements DaqQueuePort {
29
33
  readonly backend = 'mqtt' as const
30
34
  private client: MqttClient | null = null
@@ -48,7 +52,7 @@ export class MqttQueueAdapter implements DaqQueuePort {
48
52
  const mod = requireMqtt('mqtt') as unknown as { connect: (url: string, opts?: Record<string, unknown>) => MqttClient }
49
53
  // 凭据/QoS/TLS 配置驱动(daq.mqtt.*;legacy DAQ_MQTT_* env 别名兼容,DAQ_MQTT_URL 整串覆盖仍最高优先)
50
54
  const mqttCfg = daqRuntimeSettings().mqtt
51
- const qos = mqttCfg.qos
55
+ const qos = mqttCfg.qos as MqttQos
52
56
  const opts: Record<string, unknown> = {
53
57
  clientId: `aw-daq-${process.pid}-${Math.random().toString(36).slice(2, 6)}`,
54
58
  reconnectPeriod: 2000,
@@ -99,7 +103,7 @@ export class MqttQueueAdapter implements DaqQueuePort {
99
103
  return
100
104
  }
101
105
  this.published++
102
- this.client.publish(TOPIC_SAMPLE(env.nodeId), JSON.stringify(env), { qos: daqRuntimeSettings().mqtt.qos })
106
+ this.client.publish(TOPIC_SAMPLE(env.nodeId), JSON.stringify(env), { qos: daqRuntimeSettings().mqtt.qos as MqttQos })
103
107
  }
104
108
 
105
109
  consume(fn: DaqConsumer): () => void {
@@ -123,8 +127,8 @@ export class MqttQueueAdapter implements DaqQueuePort {
123
127
  // end() 回调在某些断连路径下不触发,旧 client 的 connect/message/error 监听器
124
128
  // 会连同 socket 一起滞留(client 对象被闭包引用,GC 不可达)。
125
129
  // rebuildDaqQueue 反复换装时会累积;显式摘除是唯一确定性的清理点。
126
- try {
127
- c.removeAllListeners()
130
+ try {
131
+ c.removeAllListeners()
128
132
  }
129
133
  catch { /* 已销毁等场景:尽力而为 */ }
130
134
  }
@@ -27,7 +27,7 @@ import { daqRuntimeSettings } from '../settings'
27
27
  import { getSystemConfigService } from '../../system-config'
28
28
  import { daqKeyFromRef, normalizeDataTransform, normalizeSignalKind, DAQ_DRIVERS, type AepDaqControllerState, type AepDaqFrame, type AepDaqReading, type AepDaqNodeChange, type DaqDriverKind, type DaqNodeView, type DataTransform, type DriverTestResult } from '../../../../shared/daq-protocol'
29
29
  import { AppError, ErrorCodes } from '../../../utils/errors'
30
- import { normalizeDriverKind, resolveDaqDriver, probeDriverAvailability, listPluginDrivers, type DaqFrameSample } from './drivers'
30
+ import { normalizeDriverKind, resolveDaqDriver, probeDriverAvailability, listPluginDrivers, driverCatalog, type DaqFrameSample } from './drivers'
31
31
  import { findDaqTemplate } from './daq-templates'
32
32
  import { DaqNode } from './daq-node'
33
33
  import { DaqNodeRuntime, type DaqRuntimeHost } from './daq-runtime'
@@ -106,6 +106,16 @@ export interface DaqPatchInput {
106
106
 
107
107
  type BroadcastFn = (type: string, payload: unknown) => void
108
108
 
109
+ /** 有界 LRU 的最小泛型面:shared/lru.mjs 是 allowJs 推断出的**非泛型**类(JS 里没有 @template),
110
+ * 直接 `new LruMap<K, V>()` 无处安放类型参数。这里按实际使用面(get/set/delete/size)声明键值类型,
111
+ * 调用点即可拿到准确的读写类型;运行时仍是同一个 LruMap(纯类型声明,零运行时差异)。 */
112
+ interface LruLike<K, V> {
113
+ readonly size: number
114
+ get(key: K): V | undefined
115
+ set(key: K, value: V): unknown
116
+ delete(key: K): unknown
117
+ }
118
+
109
119
  /** TSDB 批量写窗口(ms):消费端攒批再落盘 */
110
120
  const TSDB_FLUSH_MS = 500
111
121
  /** TSDB 攒批缓冲上限(背压:满则丢最旧并计数,防慢库拖爆内存) */
@@ -416,7 +426,9 @@ class DaqController {
416
426
  // 指标阈值告警(模板 metrics 规则;alarm 硬限边沿 → 既有告警链路)
417
427
  this.evaluateMetricAlarms(node, tpl, metrics)
418
428
  if (!allowPublish) return
419
- const payload: AepDaqFrame = {
429
+ // lineId 是 WS 逐产线扇出的依据(scene-events.payloadLineId 读的就是 payload.lineId,
430
+ // 与 AepDaqReading.lineId 同义);AepDaqFrame 协议类型漏了该字段 —— 见报告。
431
+ const payload: AepDaqFrame & { lineId: string | null } = {
420
432
  nodeId: node.id,
421
433
  templateRef: node.templateRef,
422
434
  kind: f.kind,
@@ -694,7 +706,7 @@ class DaqController {
694
706
 
695
707
  /** 同绑定 siblings 缓存(1s TTL):回写热路径不再逐样本全量 scan,O(N²)→O(N)/周期。
696
708
  * 用有界 LRU 而非「超 500 整体 clear()」——后者会造成周期性全量缓存穿透。 */
697
- private siblingsCache = new LruMap<string, { at: number, list: DaqNode[] }>(500)
709
+ private siblingsCache: LruLike<string, { at: number, list: DaqNode[] }> = new LruMap(500)
698
710
 
699
711
  private siblingsOf(bindingId: string): DaqNode[] {
700
712
  const hit = this.siblingsCache.get(bindingId)
@@ -759,7 +771,10 @@ class DaqController {
759
771
  this.broadcast = fn
760
772
  }
761
773
 
762
- controllerState(): AepDaqControllerState & { produced?: number, consumed?: number, dropped?: number, samplesStored?: number, tsdbDropped?: number } {
774
+ /** minIntervalMs / alarmsRaised / framesStored 都是本方法**实际返回**的字段,但 AepDaqControllerState
775
+ * 未收录(协议类型与实现漂移):minIntervalMs 被前端 useDaqStream 消费,alarmsRaised 被
776
+ * server/api/metrics.ts 消费(framesStored 供管线指标展示)。这里按实现补齐返回面 —— 见报告。 */
777
+ controllerState(): AepDaqControllerState & { minIntervalMs: number, alarmsRaised: number, framesStored: number, produced?: number, consumed?: number, dropped?: number, samplesStored?: number, tsdbDropped?: number } {
763
778
  // 丢弃 = 队列层真实丢弃(inproc 拥塞/mqtt 断连)+ 消费侧乱序迟到帧(诚实可见)
764
779
  const queueLost = g_queueLost()
765
780
  // 单次遍历(hardening PERF-1):status 高频路径不再对全表多次 all()
@@ -1201,11 +1216,16 @@ class DaqController {
1201
1216
  return this.testDriver(node.driver, node.driverConfig)
1202
1217
  }
1203
1218
 
1204
- /** 驱动可用性(meta;包缺失 → planned 提示而非硬失败) */
1219
+ /** 驱动可用性(meta;包缺失 → planned 提示而非硬失败;含插件驱动) */
1205
1220
  async driverAvailability(): Promise<Record<string, boolean>> {
1206
1221
  return probeDriverAvailability()
1207
1222
  }
1208
1223
 
1224
+ /** 驱动目录(内置 + 插件自描述合并;前端「添加节点」下拉/动态参数表单数据源) */
1225
+ async driverCatalog(): Promise<ReturnType<typeof driverCatalog>> {
1226
+ return driverCatalog()
1227
+ }
1228
+
1209
1229
  // ---------- 存量迁移(device-twins kind=daq → DaqNode 幂等供给) ----------
1210
1230
 
1211
1231
  /**
@@ -10,9 +10,9 @@
10
10
  */
11
11
 
12
12
  import { applyTransform } from '../../../../shared/daq-protocol'
13
- import { classifyCommError } from './drivers'
13
+ import { classifyCommError, type DaqFrameSample } from './drivers'
14
14
  import type { DaqNode } from './daq-node'
15
- import type { DaqFrameSample, DaqSampleEnvelope } from './bus/queue-port'
15
+ import type { DaqSampleEnvelope } from './bus/queue-port'
16
16
 
17
17
  /** 驱动故障广播节流(ms;每运行时独立计时) */
18
18
  const ERR_THROTTLE_MS = 30_000
@@ -94,7 +94,9 @@ export class DaqNodeRuntime {
94
94
  if (now < this.lastSampleAt + node.effectiveInterval(defs.intervalMs, defs.minIntervalMs)) return
95
95
  this.sampling = true
96
96
  try {
97
- let v: number | DaqFrameSample | null = null
97
+ // 生产面可能返回三种形态:标量 / 驱动原生帧(DaqFrameSample)/ 生产侧已剥离 blob 的帧信封
98
+ // (与 DaqRuntimeHost.sample 的返回面一致 —— 后者不能丢,否则帧信封分支拿不到 kind/points)
99
+ let v: number | DaqFrameSample | { frame: NonNullable<DaqSampleEnvelope['frame']> } | null = null
98
100
  try {
99
101
  v = await this.host.sample(node, now)
100
102
  }
@@ -17,7 +17,7 @@
17
17
  import { createLogger } from '../logger'
18
18
  import { createRequire } from 'node:module'
19
19
  import { encodePng } from './png-enc'
20
- import type { DaqDriverKind, DriverTestResult } from '../../../../shared/daq-protocol'
20
+ import { DAQ_DRIVERS, type DaqDriverKind, type DriverTestResult } from '../../../../shared/daq-protocol'
21
21
 
22
22
  const log = createLogger('daq.drivers')
23
23
 
@@ -200,8 +200,17 @@ function mockImageSample(
200
200
  // Modbus TCP 真实驱动(modbus-serial;连接池按 host:port:unitId 复用)
201
201
  // ============================================================
202
202
 
203
+ /**
204
+ * modbus-serial 客户端实例类型。
205
+ *
206
+ * 该包 index.d.ts 只写 `import { ModbusRTU } from "./ModbusRTU"` + `export default ModbusRTU`,
207
+ * **没有**具名 re-export(运行时 module.exports = 构造器,并自挂 module.exports.default = 自己),
208
+ * 因此 `import('modbus-serial').ModbusRTU` 在类型上并不存在 —— 从 default 导出取才与实际导出形态一致。
209
+ */
210
+ type ModbusRtuClient = InstanceType<typeof import('modbus-serial').default>
211
+
203
212
  export interface ModbusConn {
204
- client: import('modbus-serial').ModbusRTU
213
+ client: ModbusRtuClient
205
214
  lastUsed: number
206
215
  /** 连接级操作队列尾(采/控共用链路串行化:TCP 网关并发事务会协议错乱) */
207
216
  tail: Promise<unknown>
@@ -232,7 +241,7 @@ export function evictModbusConn(cfg: Record<string, unknown>, transport: ModbusT
232
241
  * 安全关闭:半开 socket(对端断电)时 modbus-serial close() 等待 FIN 永不返回,
233
242
  * 一律 500ms 超时保护(fire-and-forget 底层 close,调用方不阻塞)。
234
243
  */
235
- export async function closeModbusSafely(client: import('modbus-serial').ModbusRTU): Promise<void> {
244
+ export async function closeModbusSafely(client: ModbusRtuClient): Promise<void> {
236
245
  await Promise.race([
237
246
  Promise.resolve()
238
247
  .then(() => client.close())
@@ -252,7 +261,7 @@ export function withModbusConn<R>(conn: ModbusConn, fn: () => Promise<R>): Promi
252
261
  const run = conn.tail.then(() => fn(), () => fn())
253
262
  let timer: NodeJS.Timeout | undefined
254
263
  let dead = false
255
- const guarded = Promise.race([
264
+ const guarded: Promise<R> = Promise.race([
256
265
  run,
257
266
  new Promise<never>((_, rej) => {
258
267
  timer = setTimeout(() => {
@@ -264,7 +273,7 @@ export function withModbusConn<R>(conn: ModbusConn, fn: () => Promise<R>): Promi
264
273
  rej(new Error('Modbus 响应超时(3s)——连接可能半开,已作废重连'))
265
274
  }, 3000)
266
275
  }),
267
- ] as Promise<R>)
276
+ ])
268
277
  void guarded.catch(() => { /* 超时分支先 reject,防 unhandled */ })
269
278
  conn.tail = guarded.then(
270
279
  () => {
@@ -298,7 +307,7 @@ export async function getModbusConn(cfg: Record<string, unknown>, transport: Mod
298
307
  }
299
308
  // CJS 互操作:require 形状可能是 { ModbusRTU } / default.ModbusRTU / 构造器本身
300
309
  const mod = reqNative('modbus-serial') as unknown as { ModbusRTU?: unknown, default?: { ModbusRTU?: unknown } | unknown }
301
- const ModbusRTU = (mod.ModbusRTU ?? (mod.default as { ModbusRTU?: unknown } | undefined)?.ModbusRTU ?? mod) as new () => import('modbus-serial').ModbusRTU
310
+ const ModbusRTU = (mod.ModbusRTU ?? (mod.default as { ModbusRTU?: unknown } | undefined)?.ModbusRTU ?? mod) as new () => ModbusRtuClient
302
311
  const client = new ModbusRTU()
303
312
  client.setTimeout(3000)
304
313
  // tcp = Modbus TCP(MBAP 封装);rtu-tcp = RTU over TCP(串口网关透传,CRC16 帧,无 MBAP)
@@ -490,6 +499,17 @@ export interface OpcUaConn {
490
499
 
491
500
  const opcuaPool = new Map<string, OpcUaConn>()
492
501
 
502
+ /**
503
+ * 在飞建连去重表(同池 key → 尚未 settle 的建连任务)。
504
+ *
505
+ * 修复动因(实测事故):同端点**并发首连**此前无去重——两条采样/写控请求同时进入时
506
+ * 各建一条 OPCUAClient,而后一条的 `opcuaPool.set` 会**覆盖**前一条:
507
+ * 被覆盖的 session 与 TCP 连接从此无人持有,空闲 sweep 也扫不到 → **永久泄漏**;
508
+ * 重连 churn(断链演练/反复建节点)下还会触发 node-opcua 内部
509
+ * `_internal_create_secure_channel failed, this._secureChannel is supposed to be null`。
510
+ */
511
+ const opcuaInflight = new Map<string, Promise<OpcUaConn>>()
512
+
493
513
  // OPC UA 会话空闲回收(对照 modbus 池 sweep;节点删除/端点弃用后不再有读取
494
514
  // 路径触发 3 错误驱逐,无 sweep 会话与 TCP 连接会永久驻留)
495
515
  const OPCUA_IDLE_MS = 600_000
@@ -533,6 +553,20 @@ export async function getOpcUaConn(cfg: Record<string, unknown>): Promise<OpcUaC
533
553
  existing.lastUsed = Date.now()
534
554
  return existing
535
555
  }
556
+ // 在飞去重:同 key 并发首连复用同一条建连任务(否则后写覆盖 pool → 前一条 session/socket 永久泄漏)
557
+ const pending = opcuaInflight.get(key)
558
+ if (pending) return pending
559
+ const task = createOpcUaConn(cfg, key)
560
+ opcuaInflight.set(key, task)
561
+ try {
562
+ return await task
563
+ }
564
+ finally {
565
+ opcuaInflight.delete(key)
566
+ }
567
+ }
568
+
569
+ async function createOpcUaConn(cfg: Record<string, unknown>, key: string): Promise<OpcUaConn> {
536
570
  const opcua = reqNative('node-opcua') as typeof import('node-opcua')
537
571
  const securityMode = (['None', 'Sign', 'SignAndEncrypt'] as const).includes(cfg.securityMode as 'None')
538
572
  ? (cfg.securityMode as 'None')
@@ -574,15 +608,33 @@ export async function getOpcUaConn(cfg: Record<string, unknown>): Promise<OpcUaC
574
608
  }
575
609
  const client = opcua.OPCUAClient.create(clientOpts as Parameters<typeof opcua.OPCUAClient.create>[0])
576
610
  await client.connect(String(cfg.endpoint))
611
+ // type 是 node-opcua 的 UserIdentityInfo 判别式(node-opcua-client createUserIdentityToken →
612
+ // coerceUserIdentityInfo:userName 存在时正是补成 UserTokenType.UserName 走同一分支),
613
+ // 显式写出既满足协议类型,也免去"字段漏写、等库回填"的隐式依赖;两个分支都返回 Promise<ClientSession>。
577
614
  const session = cfg.username
578
- ? await client.createSession({ userName: String(cfg.username), password: String(cfg.password ?? '') })
615
+ ? await client.createSession({ type: opcua.UserTokenType.UserName, userName: String(cfg.username), password: String(cfg.password ?? '') })
579
616
  : await client.createSession()
580
617
  const conn: OpcUaConn = { session, client, lastUsed: Date.now(), errors: 0 }
618
+ // 竞态二次校验:建连期间若已有连接入池(并发首连的另一方完成),关闭本次新建并复用既有,
619
+ // 避免 opcuaPool.set 覆盖导致 session/socket 永久泄漏。
620
+ const raced = opcuaPool.get(key)
621
+ if (raced) {
622
+ try {
623
+ await session.close()
624
+ }
625
+ catch { /* 清理失败不覆盖原始错误 */ }
626
+ try {
627
+ await client.disconnect()
628
+ }
629
+ catch { /* 清理失败不覆盖原始错误 */ }
630
+ return raced
631
+ }
581
632
  opcuaPool.set(key, conn)
582
633
  return conn
583
634
  }
584
635
 
585
- /** 驱逐 OPC UA 连接(采样/写控共享池;连续故障后触发,下次调用重建) */
636
+ /** 驱逐 OPC UA 连接(采样/写控共享池;连续故障后触发,下次调用重建)。
637
+ * 只作用于**已入池**的连接:在飞建连本身就是新建的,交由竞态二次校验处理。 */
586
638
  export async function evictOpcUaConn(cfg: Record<string, unknown>): Promise<void> {
587
639
  const key = opcuaKey(cfg)
588
640
  const conn = opcuaPool.get(key)
@@ -961,7 +1013,10 @@ export const httpDaqDriver: DaqDriver = {
961
1013
  return { ok: true, message: `接口可达,vector 帧 ${(j.points as unknown[]).length} 点`, latencyMs: Date.now() - t0 }
962
1014
  }
963
1015
  if (typeof j.png === 'string' || typeof j.blob === 'string') {
964
- return { ok: true, message: `接口可达,image 帧(base64, ${(j.png ?? j.blob as string).length} chars)`, latencyMs: Date.now() - t0 }
1016
+ // 与生产分支同构:png/blob 至少一个是 base64 字符串(见上一行守卫)。
1017
+ // b64 的取值表达式与原来逐字一致(断言运行时擦除),只是把 unknown 标注成 string 以取 .length
1018
+ const b64 = (j.png as string | undefined) ?? (j.blob as string)
1019
+ return { ok: true, message: `接口可达,image 帧(base64, ${b64.length} chars)`, latencyMs: Date.now() - t0 }
965
1020
  }
966
1021
  const v = extractNumeric(text, driverConfig.jsonPath ? String(driverConfig.jsonPath) : undefined)
967
1022
  return { ok: true, message: `接口可达,取值 = ${v}`, sampleValue: v, latencyMs: Date.now() - t0 }
@@ -1020,16 +1075,64 @@ function pluginRegistry(): Map<string, DaqDriver> {
1020
1075
  return g_plugins.__daqPluginDrivers ??= new Map()
1021
1076
  }
1022
1077
 
1078
+ /**
1079
+ * 插件驱动的自描述目录(前端「添加节点」下拉与动态参数表单的数据源)。
1080
+ * 插件驱动在 DaqDriver 上携带可选 meta(label/status/configFields,与 DaqDriverMeta 同形、
1081
+ * kind 放宽为 string);未带 meta 的插件驱动只在 meta.pluginDrivers 里以 kind 出现,
1082
+ * 前端能感知存在但不出表单(如实降级,不猜测参数 schema)。
1083
+ */
1084
+ export interface PluginDriverMeta {
1085
+ kind: string
1086
+ label: string
1087
+ status: 'builtin' | 'real' | 'planned'
1088
+ configFields?: import('../../../../shared/daq-protocol').DriverConfigField[]
1089
+ /** true = 来自插件协议插件(前端展示「插件」徽标) */
1090
+ plugin: true
1091
+ }
1092
+
1093
+ const g_metas = globalThis as typeof globalThis & { __daqPluginDriverMetas?: Map<string, PluginDriverMeta> }
1094
+ function pluginMetaRegistry(): Map<string, PluginDriverMeta> {
1095
+ return g_metas.__daqPluginDriverMetas ??= new Map()
1096
+ }
1097
+
1023
1098
  /** 插件驱动注册(kind 与内置冲突时覆盖并告警;resolveDaqDriver 插件优先) */
1024
1099
  export function registerPluginDriver(driver: DaqDriver): void {
1025
1100
  if (driver.kind in REGISTRY) log.warn(`[daq-drivers] 插件驱动覆盖内置:「${driver.kind}」`)
1026
1101
  pluginRegistry().set(driver.kind, driver)
1102
+ const meta = (driver as DaqDriver & { meta?: Omit<PluginDriverMeta, 'kind' | 'plugin'> }).meta
1103
+ if (meta && typeof meta.label === 'string') {
1104
+ pluginMetaRegistry().set(driver.kind, {
1105
+ kind: driver.kind,
1106
+ label: meta.label,
1107
+ status: meta.status === 'builtin' || meta.status === 'planned' ? meta.status : 'real',
1108
+ configFields: Array.isArray(meta.configFields) ? meta.configFields : [],
1109
+ plugin: true,
1110
+ })
1111
+ }
1112
+ else {
1113
+ // 同名驱动重注册(热重载)可能这次不带 meta:旧 meta 一并清除,保持两边一致
1114
+ pluginMetaRegistry().delete(driver.kind)
1115
+ }
1027
1116
  }
1028
1117
 
1029
1118
  export function listPluginDrivers(): string[] {
1030
1119
  return [...pluginRegistry().keys()]
1031
1120
  }
1032
1121
 
1122
+ /**
1123
+ * 清空插件驱动与自描述目录(插件宿主热重载前调用):停用/卸载的插件驱动随之失效,
1124
+ * 只有本轮重新装载成功的插件驱动会再注册进来 —— 否则被停用的驱动会一直生效到进程重启。
1125
+ */
1126
+ export function clearPluginDrivers(): void {
1127
+ pluginRegistry().clear()
1128
+ pluginMetaRegistry().clear()
1129
+ }
1130
+
1131
+ /** 插件驱动自描述目录(驱动目录合并端点用;热重载随重注册刷新) */
1132
+ export function listPluginDriverMetas(): PluginDriverMeta[] {
1133
+ return [...pluginMetaRegistry().values()]
1134
+ }
1135
+
1033
1136
  export function normalizeDriverKind(kind: string): DaqDriverKind {
1034
1137
  if (kind === 'modbus') return 'modbus-tcp'
1035
1138
  if (kind === 'rtu' || kind === 'modbus-rtu-tcp') return 'modbus-rtu'
@@ -1040,10 +1143,11 @@ export function resolveDaqDriver(kind: DaqDriverKind): DaqDriver {
1040
1143
  return pluginRegistry().get(kind) ?? REGISTRY[kind] ?? mockDaqDriver
1041
1144
  }
1042
1145
 
1043
- /** 驱动可用性探测(meta 报告:包缺失时 UI 显示"未安装"而非硬失败) */
1146
+ /** 驱动可用性探测(meta 报告:包缺失时 UI 显示"未安装"而非硬失败;含插件驱动) */
1044
1147
  export async function probeDriverAvailability(): Promise<Record<string, boolean>> {
1045
1148
  const out: Record<string, boolean> = {}
1046
- for (const [kind, drv] of Object.entries(REGISTRY)) {
1149
+ const all: Array<[string, DaqDriver]> = [...Object.entries(REGISTRY), ...pluginRegistry()]
1150
+ for (const [kind, drv] of all) {
1047
1151
  try {
1048
1152
  out[kind] = await drv.available()
1049
1153
  }
@@ -1053,3 +1157,29 @@ export async function probeDriverAvailability(): Promise<Record<string, boolean>
1053
1157
  }
1054
1158
  return out
1055
1159
  }
1160
+
1161
+ /**
1162
+ * 驱动目录合并(内置 DAQ_DRIVERS + 插件自描述;同名插件条目覆盖内置并标记 plugin)。
1163
+ * index.get.ts 以 `drivers` 下发,前端据此渲染「添加节点」下拉与动态参数表单。
1164
+ */
1165
+ export async function driverCatalog(): Promise<Array<{
1166
+ kind: string
1167
+ label: string
1168
+ status: 'builtin' | 'real' | 'planned'
1169
+ configFields: import('../../../../shared/daq-protocol').DriverConfigField[]
1170
+ plugin?: boolean
1171
+ }>> {
1172
+ const out: Array<{ kind: string, label: string, status: 'builtin' | 'real' | 'planned', configFields: import('../../../../shared/daq-protocol').DriverConfigField[], plugin?: boolean }> = DAQ_DRIVERS.map(d => ({
1173
+ kind: d.kind,
1174
+ label: d.label,
1175
+ status: d.status,
1176
+ configFields: d.configFields,
1177
+ }))
1178
+ for (const m of listPluginDriverMetas()) {
1179
+ const i = out.findIndex(d => d.kind === m.kind)
1180
+ const entry = { kind: m.kind, label: m.label, status: m.status, configFields: m.configFields ?? [], plugin: true }
1181
+ if (i >= 0) out[i] = entry
1182
+ else out.push(entry)
1183
+ }
1184
+ return out
1185
+ }
@@ -20,10 +20,11 @@ export interface ActiveRunSnapshot {
20
20
  /** 遥测上报端口:绑定设备的值回写 + 孪生场景推送 + 绑定校验(中心实现 = device-twin.repo) */
21
21
  export interface DaqTelemetryPort {
22
22
  /**
23
- * 遥测回写:telemetry 键值写入绑定设备的孪生,state 为同设备多节点汇聚的最严重态。
23
+ * 遥测回写:telemetry 键值写入绑定设备的孪生,state 为同设备多节点汇聚的最严重态
24
+ * (alarm > warn > ok,与 device-twin.repo.applyTelemetry 的 nodeState 同一个域)。
24
25
  * 目标设备不存在 → { ok:false }(调用方解绑自身,链路自愈);成功 → { ok:true, twinId }。
25
26
  */
26
- applyTelemetry(deviceId: string, telemetry: Record<string, number | string | boolean>, state: string): { ok: boolean, twinId: string | null }
27
+ applyTelemetry(deviceId: string, telemetry: Record<string, number | string | boolean>, state: 'alarm' | 'warn' | 'ok'): { ok: boolean, twinId: string | null }
27
28
  /** 孪生场景载荷(WS device.updated 帧;设备已删 → null) */
28
29
  scenePayload(twinId: string): unknown | null
29
30
  /** 绑定目标存在性校验 */
@@ -239,7 +239,9 @@ export class TimescaleAdapter implements TsdbPort {
239
239
  at: tsToMs(r.ts),
240
240
  kind: String(r.kind) as 'vector' | 'image',
241
241
  points: String(r.kind) === 'vector' ? pointsFromMeta(r.meta) : undefined,
242
- metrics: asObject(r.metrics),
242
+ // 与 sqlite.adapter 同法:metrics 列由本管线写入(下沉处理器产出 avg/max/brightness/zone_* 数值),
243
+ // asObject 只做 JSON 还原(Record<string, unknown>),按端口契约收窄到 Record<string, number>(断言运行时擦除)
244
+ metrics: asObject(r.metrics) as Record<string, number>,
243
245
  meta: asObject(r.meta),
244
246
  deviceBindingId: r.device_binding_id ?? null,
245
247
  lineId: r.line_id ?? null,
@@ -54,6 +54,10 @@ export function createChannelRepo(db: DatabaseSync) {
54
54
  name: input.name,
55
55
  description: input.description ?? '',
56
56
  scenarioPrompt: input.scenarioPrompt ?? '',
57
+ // INSERT 不含该列 → 落库取列默认(llm_json TEXT NOT NULL DEFAULT '',见 database.ts v11);
58
+ // 返回的内存行按同一事实补该列,使 create() 的返回值与随后 findById() 读到的行形状一致。
59
+ // 注意:insert.run(...) 的参数列表保持不变(多传一个参数会与 SQL 占位符数量不符)。
60
+ llmJson: '',
57
61
  leadAgentId: null,
58
62
  workspace: input.workspace ?? '',
59
63
  enabled: 1,