agentworkshop 0.8.5 → 0.8.6

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 (349) hide show
  1. package/.AgentWorkShop/prompts/host-tools.en.json +3 -3
  2. package/.AgentWorkShop/prompts/host-tools.json +14 -4
  3. package/.output/.AgentWorkShop/prompts/host-tools.en.json +3 -3
  4. package/.output/.AgentWorkShop/prompts/host-tools.json +14 -4
  5. package/.output/nitro.json +1 -1
  6. package/.output/public/_nuxt/{U_n8pryv2.js → 7op-BMk82.js} +1 -1
  7. package/.output/public/_nuxt/{zWZ26UWi.js → B1SIgyrl.js} +1 -1
  8. package/.output/public/_nuxt/{C4tXwpSt.js → BA2lmfW4.js} +1 -1
  9. package/.output/public/_nuxt/{EJkmXP432.js → BDXQqSkl2.js} +1 -1
  10. package/.output/public/_nuxt/{CY_7u1ST2.js → BI9jj13f2.js} +1 -1
  11. package/.output/public/_nuxt/{CGf9neEK2.js → BiXqyYNw2.js} +1 -1
  12. package/.output/public/_nuxt/{D9GoOkyP2.js → BlOm8JIs2.js} +1 -1
  13. package/.output/public/_nuxt/{CQ31GiJU2.js → BoXKUOiN2.js} +1 -1
  14. package/.output/public/_nuxt/{DZzbCFY2.js → BuLoPmcf.js} +1 -1
  15. package/.output/public/_nuxt/{D9ppMCbw.js → BwOK42JQ.js} +1 -1
  16. package/.output/public/_nuxt/{CZJ5cw6r2.js → C1RMfPoB2.js} +1 -1
  17. package/.output/public/_nuxt/{CGviZq4E.js → COfo_Bg3.js} +1 -1
  18. package/.output/public/_nuxt/CTGJHZ8X2.js +1 -0
  19. package/.output/public/_nuxt/{SgsmmrsZ.js → CY2NkFvI.js} +1 -1
  20. package/.output/public/_nuxt/{B4GOTVfN.js → C_4mzQ5X.js} +1 -1
  21. package/.output/public/_nuxt/{DGmFHb7m.js → CdvMP3lv.js} +1 -1
  22. package/.output/public/_nuxt/{iqxZGXsY.js → CooauJ7D.js} +1 -1
  23. package/.output/public/_nuxt/{C3vxePED.js → D1eVi1zr.js} +1 -1
  24. package/.output/public/_nuxt/{DnuJ66Jy.js → D6PUmBb0.js} +1 -1
  25. package/.output/public/_nuxt/{BBhroEeN.js → DC8sMA36.js} +1 -1
  26. package/.output/public/_nuxt/{DOUuGOQR2.js → DDHh85c42.js} +1 -1
  27. package/.output/public/_nuxt/{Cy_HDgSl.js → DDXOfmo2.js} +2 -2
  28. package/.output/public/_nuxt/{Cr2xJ_CY.js → DMFwLxfu.js} +1 -1
  29. package/.output/public/_nuxt/{BZg_MIK42.js → DoprKJ5a2.js} +1 -1
  30. package/.output/public/_nuxt/{BzBBEE-o2.js → Dr1FVeQG2.js} +1 -1
  31. package/.output/public/_nuxt/{W0sNnJQh.js → DtnsTW7e.js} +1 -1
  32. package/.output/public/_nuxt/{gUYi1hLJ2.js → DxDqd6T42.js} +1 -1
  33. package/.output/public/_nuxt/{B0UUODeh.js → DzU3zEKX.js} +1 -1
  34. package/.output/public/_nuxt/{BkO5WdX5.js → EgO70wrE.js} +1 -1
  35. package/.output/public/_nuxt/{7OZQiw002.js → HJKu4eEV2.js} +1 -1
  36. package/.output/public/_nuxt/{ChF6qLpY2.js → K5Xen0wZ2.js} +1 -1
  37. package/.output/public/_nuxt/{CNrRZmSx2.js → Rb4o4o-82.js} +1 -1
  38. package/.output/public/_nuxt/{BqBTlfiz.js → Tf6SJSDj.js} +1 -1
  39. package/.output/public/_nuxt/{DJaGVY4v2.js → ZzOyoXex2.js} +1 -1
  40. package/.output/public/_nuxt/{PbzVdCTA2.js → azOGy_tU2.js} +1 -1
  41. package/.output/public/_nuxt/{D1thFC3z.js → be-4wyTo.js} +1 -1
  42. package/.output/public/_nuxt/builds/latest.json +1 -1
  43. package/.output/public/_nuxt/builds/meta/593b65bd-cf1d-4b7f-a993-57e939c38f36.json +1 -0
  44. package/.output/public/_nuxt/{8b9CKi6F.js → dcjPjLPI.js} +1 -1
  45. package/.output/public/_nuxt/{ae2-5ZPt.js → h-Fn7pRD.js} +1 -1
  46. package/.output/public/_nuxt/{-gy9k_TE2.js → hyLdZsOd2.js} +1 -1
  47. package/.output/public/_nuxt/{BHxG41IQ2.js → o8sYG6xx2.js} +1 -1
  48. package/.output/server/chunks/_/access.mjs +1 -1
  49. package/.output/server/chunks/_/agent-dto.mjs +26 -0
  50. package/.output/server/chunks/_/agent-dto.mjs.map +1 -0
  51. package/.output/server/chunks/_/data-export.mjs +6 -6
  52. package/.output/server/chunks/_/data-query.mjs +3 -3
  53. package/.output/server/chunks/_/eda-run.repo.mjs +2 -2
  54. package/.output/server/chunks/_/features.mjs +5 -5
  55. package/.output/server/chunks/_/golden.mjs +3 -3
  56. package/.output/server/chunks/_/harness-spawn-policy.mjs +77 -0
  57. package/.output/server/chunks/_/harness-spawn-policy.mjs.map +1 -0
  58. package/.output/server/chunks/_/index.mjs +1 -1
  59. package/.output/server/chunks/_/line-scope.mjs +32 -0
  60. package/.output/server/chunks/_/line-scope.mjs.map +1 -0
  61. package/.output/server/chunks/_/mes-console.mjs +1 -1
  62. package/.output/server/chunks/_/nitro.mjs +2429 -1726
  63. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  64. package/.output/server/chunks/_/ocap.mjs +1 -1
  65. package/.output/server/chunks/_/rest-auth.mjs +1 -1
  66. package/.output/server/chunks/_/spc.mjs +1 -1
  67. package/.output/server/chunks/build/TownView-BWMDbosY.mjs +1 -1
  68. package/.output/server/chunks/build/daq-CdxcfY_G.mjs +1 -1
  69. package/.output/server/chunks/build/dcw-BlDQtS4q.mjs +1 -1
  70. package/.output/server/chunks/build/types-D6J7slEa.mjs +1 -1
  71. package/.output/server/chunks/build/useDaqAddNode-D_Rkju0f.mjs +1 -1
  72. package/.output/server/chunks/build/useDaqDetailNode-DeOsDxK5.mjs +1 -1
  73. package/.output/server/chunks/build/useDaqDetailParams-Cm9a9r5G.mjs +1 -1
  74. package/.output/server/chunks/build/useDaqFilters-CctmO03p.mjs +1 -1
  75. package/.output/server/chunks/build/useDaqStream-B9ZBVQrI.mjs +1 -1
  76. package/.output/server/chunks/build/useDaqTemplates-CK37sMfi.mjs +1 -1
  77. package/.output/server/chunks/build/useDcwAddNode-DACVcEHs.mjs +1 -1
  78. package/.output/server/chunks/build/useDcwStream-Dir5ufAU.mjs +1 -1
  79. package/.output/server/chunks/routes/api/mcp/workshop.mjs +1 -1
  80. package/.output/server/chunks/routes/api/system/backup/run.post.mjs +2 -2
  81. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +17 -9
  82. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs.map +1 -1
  83. package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +3 -0
  84. package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs.map +1 -1
  85. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  86. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs +6 -0
  87. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs.map +1 -1
  88. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs +5 -0
  89. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs.map +1 -1
  90. package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs +5 -2
  91. package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs.map +1 -1
  92. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +7 -1
  93. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs.map +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +3 -1
  95. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs.map +1 -1
  96. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +8 -6
  97. package/.output/server/chunks/routes/api/workshop/channels.post.mjs.map +1 -1
  98. package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs +29 -49
  99. package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs.map +1 -1
  100. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +6 -1
  102. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs.map +1 -1
  103. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +4 -2
  104. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs.map +1 -1
  105. package/.output/server/chunks/routes/api/workshop/daq/index2.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +14 -5
  107. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs.map +1 -1
  108. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +7 -4
  112. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs.map +1 -1
  113. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +6 -2
  116. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs.map +1 -1
  117. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +6 -2
  118. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs.map +1 -1
  119. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  121. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +6 -1
  123. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs.map +1 -1
  124. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  125. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +7 -4
  126. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs.map +1 -1
  127. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  128. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  129. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  130. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +6 -2
  131. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs.map +1 -1
  132. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  133. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +7 -4
  134. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs.map +1 -1
  135. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  136. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  137. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +6 -2
  139. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs.map +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  141. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  142. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  144. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs +6 -2
  146. package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs.map +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/mes-hook-test.post.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/dcw/mes-test-read.post.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +6 -2
  152. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs.map +1 -1
  153. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +4 -2
  154. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs.map +1 -1
  155. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +5 -2
  156. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs.map +1 -1
  157. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +7 -3
  165. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs.map +1 -1
  166. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +8 -1
  167. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs.map +1 -1
  168. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +6 -2
  169. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs.map +1 -1
  170. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +7 -4
  175. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs.map +1 -1
  176. package/.output/server/chunks/routes/api/workshop/eda/commonality.get.mjs +1 -1
  177. package/.output/server/chunks/routes/api/workshop/eda/vm/predict.post.mjs +1 -1
  178. package/.output/server/chunks/routes/api/workshop/exp/confirmations/_id/decide.post.mjs +1 -1
  179. package/.output/server/chunks/routes/api/workshop/exp/index.get.mjs +1 -1
  180. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
  181. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  182. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  183. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  184. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  185. package/.output/server/chunks/routes/api/workshop/index.mjs +1 -1
  186. package/.output/server/chunks/routes/api/workshop/index.post.mjs +2 -0
  187. package/.output/server/chunks/routes/api/workshop/index.post.mjs.map +1 -1
  188. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  189. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +8 -3
  190. package/.output/server/chunks/routes/api/workshop/index3.post.mjs.map +1 -1
  191. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  192. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
  193. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
  194. package/.output/server/chunks/routes/api/workshop/index7.post.mjs +1 -1
  195. package/.output/server/chunks/routes/api/workshop/mes/datasets.get.mjs +1 -1
  196. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/autofix.mjs +1 -1
  197. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs +1 -1
  198. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs +1 -1
  199. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs +1 -1
  200. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs +1 -1
  201. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs +1 -1
  202. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs +1 -1
  203. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/subgraph.mjs +1 -1
  204. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs +1 -1
  205. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs +1 -1
  206. package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs +1 -1
  207. package/.output/server/chunks/routes/api/workshop/ontology/index.mjs +1 -1
  208. package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs +1 -1
  209. package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs +1 -1
  210. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs +1 -1
  211. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs +1 -1
  212. package/.output/server/chunks/routes/api/workshop/optimization/capabilities.get.mjs +1 -1
  213. package/.output/server/chunks/routes/api/workshop/optimization/index.get.mjs +1 -1
  214. package/.output/server/chunks/routes/api/workshop/optimization/index.mjs +1 -1
  215. package/.output/server/chunks/routes/api/workshop/optimization/index2.mjs +1 -1
  216. package/.output/server/chunks/routes/api/workshop/optimization/profiles/_id/publish.post.mjs +1 -1
  217. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/crosscheck.post.mjs +1 -1
  218. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/dispatch.post.mjs +1 -1
  219. package/.output/server/chunks/routes/api/workshop/optimization/solve.post.mjs +1 -1
  220. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  221. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  222. package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
  223. package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
  224. package/.output/server/chunks/routes/api/workshop/process-model/index.mjs +1 -1
  225. package/.output/server/chunks/routes/api/workshop/process-model/specs/_id/publish.post.mjs +1 -1
  226. package/.output/server/chunks/routes/api/workshop/process-model/train.post.mjs +1 -1
  227. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  228. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  229. package/.output/server/chunks/routes/api/workshop/tools/sync.post.mjs +1 -1
  230. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  231. package/.output/server/chunks/routes/renderer.mjs +2 -2
  232. package/.output/server/chunks/virtual/entry.mjs +1 -1
  233. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  234. package/.output/server/package.json +1 -1
  235. package/README-zh.md +4 -2
  236. package/README.md +9 -2
  237. package/config.yml +1 -1
  238. package/docker-compose.yml +4 -2
  239. package/docs/cli.en.md +2 -2
  240. package/docs/cli.md +2 -2
  241. package/docs/plugins.en.md +1 -1
  242. package/docs/plugins.md +1 -1
  243. package/docs/sdk.en.md +1 -1
  244. package/docs/sdk.md +1 -1
  245. package/package.json +1 -1
  246. package/scripts/_audit/_live-mqtt-e2e-20261028.mjs +219 -0
  247. package/scripts/_audit/prompt-injection-probe.mts +76 -0
  248. package/scripts/api-live-e2e.mjs +9 -3
  249. package/scripts/e2e-multiexpert-remediation-real.mjs +453 -0
  250. package/scripts/e2e-node-binding-3mode-real.mjs +697 -0
  251. package/scripts/e2e-node-binding-authz.mjs +332 -0
  252. package/scripts/security-acceptance.mjs +218 -0
  253. package/server/api/workshop/agent-tools/bindings/index.post.ts +33 -17
  254. package/server/api/workshop/agents/[id].patch.ts +3 -0
  255. package/server/api/workshop/agents/index.post.ts +3 -0
  256. package/server/api/workshop/channel-templates/[id].patch.ts +5 -0
  257. package/server/api/workshop/channel-templates/index.post.ts +6 -0
  258. package/server/api/workshop/channels/[id]/agents/[agentId].patch.ts +5 -0
  259. package/server/api/workshop/channels/[id]/agents/index.get.ts +8 -2
  260. package/server/api/workshop/channels/[id]/agents/index.post.ts +7 -0
  261. package/server/api/workshop/channels/[id]/index.get.ts +10 -4
  262. package/server/api/workshop/channels.post.ts +3 -0
  263. package/server/api/workshop/daq/[id]/ingest.post.ts +26 -22
  264. package/server/api/workshop/daq/[id].patch.ts +8 -0
  265. package/server/api/workshop/daq/alarms.get.ts +18 -16
  266. package/server/api/workshop/daq/infra/reconnect.post.ts +21 -5
  267. package/server/api/workshop/daq/test-driver.post.ts +10 -3
  268. package/server/api/workshop/dcw/[id]/param-ledger.get.ts +7 -1
  269. package/server/api/workshop/dcw/[id]/read.post.ts +6 -1
  270. package/server/api/workshop/dcw/[id].patch.ts +49 -40
  271. package/server/api/workshop/dcw/journal/index.get.ts +7 -3
  272. package/server/api/workshop/dcw/mes-artifacts/file.get.ts +7 -1
  273. package/server/api/workshop/dcw/mes-artifacts/index.get.ts +7 -1
  274. package/server/api/workshop/dcw/optimizations/[id]/series.get.ts +6 -1
  275. package/server/api/workshop/dcw/optimizations/[id].get.ts +3 -1
  276. package/server/api/workshop/dcw/optimizations/index.get.ts +6 -3
  277. package/server/api/workshop/dcw/params/[id]/read.post.ts +22 -17
  278. package/server/api/workshop/dcw/recipes/[id]/versions.get.ts +20 -14
  279. package/server/api/workshop/dcw/recipes/[id].delete.ts +33 -25
  280. package/server/api/workshop/dcw/recipes/index.get.ts +17 -12
  281. package/server/api/workshop/dcw/runs/[id]/close.post.ts +21 -15
  282. package/server/api/workshop/dcw/test-driver.post.ts +21 -16
  283. package/server/api/workshop/ws/hub.ts +2 -1
  284. package/server/plugins/01-instance-guard.ts +82 -0
  285. package/server/plugins/daq.ts +40 -8
  286. package/server/plugins/workshop.ts +12 -0
  287. package/server/services/workshop/agents/adapters/line-spawn.ts +24 -1
  288. package/server/services/workshop/agents/agent-dto.ts +54 -0
  289. package/server/services/workshop/agents/harness-spawn-policy.ts +114 -0
  290. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +22 -0
  291. package/server/services/workshop/agents/host-tool-bridge/index.ts +1 -1
  292. package/server/services/workshop/agents/host-tool-bridge/per-agent.ts +17 -3
  293. package/server/services/workshop/agents/host-tool-bridge/tools/tasks.ts +1 -1
  294. package/server/services/workshop/agents/host-tool-bridge/tools/team-delegation.ts +5 -1
  295. package/server/services/workshop/agents/industrial/dcw-tools.ts +6 -2
  296. package/server/services/workshop/agents/industrial/ops-tools.ts +54 -12
  297. package/server/services/workshop/agents/industrial/recipe-gate.ts +68 -15
  298. package/server/services/workshop/agents/industrial/tool-verify-legacy-specs.ts +2 -2
  299. package/server/services/workshop/agents/industrial/tool-verify.ts +31 -2
  300. package/server/services/workshop/agents/node-bindings.repo.ts +4 -3
  301. package/server/services/workshop/agents/node-delegation.ts +49 -4
  302. package/server/services/workshop/agents/tool-approvals.ts +23 -0
  303. package/server/services/workshop/agents/tool-reference-spec.ts +3 -0
  304. package/server/services/workshop/daq/bus/mqtt.adapter.ts +12 -0
  305. package/server/services/workshop/daq/daq-controller/crud.ts +5 -3
  306. package/server/services/workshop/daq/daq-controller/ingest.ts +23 -0
  307. package/server/services/workshop/daq/daq-controller/push.ts +24 -3
  308. package/server/services/workshop/daq/daq-controller/runtime-control.ts +6 -0
  309. package/server/services/workshop/daq/daq-controller/sweep-alarms.ts +3 -1
  310. package/server/services/workshop/daq/daq-controller/tsdb-twin.ts +21 -1
  311. package/server/services/workshop/daq/daq-node.repo.ts +10 -7
  312. package/server/services/workshop/daq/daq-node.ts +2 -0
  313. package/server/services/workshop/daq/daq-runtime.ts +10 -2
  314. package/server/services/workshop/daq/drivers/modbus-tcp.ts +8 -5
  315. package/server/services/workshop/daq/drivers/registry.ts +5 -4
  316. package/server/services/workshop/daq/infra.ts +13 -0
  317. package/server/services/workshop/daq/ingest-policy.ts +24 -1
  318. package/server/services/workshop/daq/storage/timescale.adapter.ts +42 -3
  319. package/server/services/workshop/db/database/migrations.ts +83 -68
  320. package/server/services/workshop/dcw/dcw-controller/crud.ts +27 -2
  321. package/server/services/workshop/dcw/dcw-controller/lines.ts +14 -3
  322. package/server/services/workshop/dcw/dcw-controller/recipes.ts +63 -5
  323. package/server/services/workshop/dcw/dcw-recipe.repo.ts +10 -1
  324. package/server/services/workshop/dcw/dcw-runtime.ts +37 -14
  325. package/server/services/workshop/dcw/drivers/shared.ts +62 -6
  326. package/server/services/workshop/dcw/recipe-op-anchor.ts +28 -0
  327. package/server/services/workshop/dcw/recipe-rollback-manager/rollback.ts +204 -190
  328. package/server/services/workshop/dcw/recipe-rollback-manager/sweep.ts +97 -77
  329. package/server/services/workshop/line-scope.ts +80 -0
  330. package/server/services/workshop/optimization/dispatch.service.ts +18 -2
  331. package/server/services/workshop/permissions.ts +2 -0
  332. package/server/services/workshop/runtime/agent-runtime/message.ts +17 -0
  333. package/server/services/workshop/runtime/agent-runtime/state.ts +9 -3
  334. package/server/services/workshop/runtime/agent-runtime/types.ts +2 -1
  335. package/server/services/workshop/runtime/channel-runtime.ts +3 -1
  336. package/server/services/workshop/runtime/mailbox.ts +6 -3
  337. package/server/services/workshop/runtime/manager/a2a.ts +4 -2
  338. package/server/services/workshop/runtime/manager/admin-channel.ts +1 -1
  339. package/server/services/workshop/runtime/manager/chat.ts +3 -1
  340. package/server/services/workshop/runtime/manager/host-tools.ts +1 -1
  341. package/server/services/workshop/runtime/manager/runtime-wiring.ts +31 -19
  342. package/server/services/workshop/runtime/manager/state.ts +43 -1
  343. package/server/services/workshop/runtime/permission-scope.ts +21 -0
  344. package/server/services/workshop/runtime/scheduler-loop/tick.ts +16 -4
  345. package/server/services/workshop/scene-events.ts +19 -0
  346. package/shared/daq-protocol.ts +24 -5
  347. package/shared/dcw-protocol/catalog.ts +4 -4
  348. package/.output/public/_nuxt/CCI-fwcS2.js +0 -1
  349. package/.output/public/_nuxt/builds/meta/5cd11284-05bf-4fcd-9a69-56a01c9f56dd.json +0 -1
@@ -235,80 +235,95 @@ export function migrateMissingForeignKeys(db: DatabaseSync): void {
235
235
  * 身份与 token 已迁移至全局用户系统(data/users.sqlite),owner 列仅存用户 id 引用,
236
236
  * 保留 FK 会使新全局用户 id 在本地 users 表无行而写入失败。
237
237
  * 表重建期间临时关闭外键,避免 DROP 触发级联;重建后以原名重新命名,引用方 FK 依旧有效。
238
+ *
239
+ * 2026-10-28 审查 D-P1-2:旧实现用**白名单 DDL** 重建 channels/agents/teams ——
240
+ * 白名单随后续版本加列而漂移(channels 缺 scenario_prompt/llm_json/visibility/
241
+ * join_policy/approval_policy/chat_enabled/version/line_id 共 8 列),带 owner FK 的
242
+ * 老库升级时整列数据被静默丢弃且不可复现。现在每条重建语句都携带**完整列清单**,
243
+ * 由 tests/migration-owner-fk.test.ts 守护:升级前后"列集 + 全部业务数据"逐列相等
244
+ * (任一列漏抄 → 列集变化 → 测试红)。
238
245
  */
239
246
  export function migrateDropOwnerFks(db: DatabaseSync): void {
240
- const rebuilds: Array<{ table: string, ddl: string }> = []
241
- if (hasForeignKey(db, 'workspaces', 'owner_user_id', 'users')) {
242
- rebuilds.push({
243
- table: 'workspaces',
244
- ddl: `CREATE TABLE workspaces_new (
245
- id TEXT PRIMARY KEY,
246
- owner_user_id TEXT NOT NULL,
247
- name TEXT NOT NULL,
248
- created_at TEXT NOT NULL
249
- );
250
- INSERT INTO workspaces_new SELECT id, owner_user_id, name, created_at FROM workspaces;`,
251
- })
252
- }
253
- if (hasForeignKey(db, 'channels', 'owner_user_id', 'users')) {
254
- rebuilds.push({
255
- table: 'channels',
256
- ddl: `CREATE TABLE channels_new (
257
- id TEXT PRIMARY KEY,
258
- name TEXT NOT NULL,
259
- description TEXT NOT NULL DEFAULT '',
260
- lead_agent_id TEXT,
261
- workspace TEXT NOT NULL DEFAULT '',
262
- enabled INTEGER NOT NULL DEFAULT 1,
263
- owner_user_id TEXT,
264
- created_at TEXT NOT NULL,
265
- updated_at TEXT NOT NULL
266
- );
267
- INSERT INTO channels_new SELECT id, name, description, lead_agent_id, workspace, enabled, owner_user_id, created_at, updated_at FROM channels;`,
268
- })
269
- }
270
- if (hasForeignKey(db, 'agents', 'owner_user_id', 'users')) {
271
- rebuilds.push({
272
- table: 'agents',
273
- ddl: `CREATE TABLE agents_new (
274
- id TEXT PRIMARY KEY,
275
- name TEXT NOT NULL,
276
- harness TEXT NOT NULL,
277
- config_json TEXT NOT NULL DEFAULT '{}',
278
- enabled INTEGER NOT NULL DEFAULT 1,
279
- visibility TEXT NOT NULL DEFAULT 'private',
280
- owner_user_id TEXT,
281
- created_at TEXT NOT NULL,
282
- updated_at TEXT NOT NULL
283
- );
284
- INSERT INTO agents_new SELECT id, name, harness, config_json, enabled, visibility, owner_user_id, created_at, updated_at FROM agents;`,
285
- })
286
- }
287
- if (hasForeignKey(db, 'teams', 'owner_user_id', 'users')) {
288
- rebuilds.push({
289
- table: 'teams',
290
- ddl: `CREATE TABLE teams_new (
291
- id TEXT PRIMARY KEY,
292
- name TEXT NOT NULL,
293
- description TEXT NOT NULL DEFAULT '',
294
- visibility TEXT NOT NULL DEFAULT 'private',
295
- owner_user_id TEXT,
296
- created_at TEXT NOT NULL,
297
- updated_at TEXT NOT NULL
298
- );
299
- INSERT INTO teams_new SELECT id, name, description, visibility, owner_user_id, created_at, updated_at FROM teams;`,
300
- })
301
- }
302
- if (rebuilds.length === 0) return
247
+ const needWorkspaces = hasForeignKey(db, 'workspaces', 'owner_user_id', 'users')
248
+ const needChannels = hasForeignKey(db, 'channels', 'owner_user_id', 'users')
249
+ const needAgents = hasForeignKey(db, 'agents', 'owner_user_id', 'users')
250
+ const needTeams = hasForeignKey(db, 'teams', 'owner_user_id', 'users')
251
+ if (!needWorkspaces && !needChannels && !needAgents && !needTeams) return
303
252
 
304
253
  db.exec('PRAGMA foreign_keys = OFF;')
305
254
  try {
306
- for (const { table, ddl } of rebuilds) {
307
- db.exec(`${ddl}
308
- DROP TABLE ${table};
309
- ALTER TABLE ${table}_new RENAME TO ${table};`)
255
+ if (needWorkspaces) {
256
+ db.prepare(`CREATE TABLE workspaces_new (
257
+ id TEXT PRIMARY KEY,
258
+ owner_user_id TEXT NOT NULL,
259
+ name TEXT NOT NULL,
260
+ created_at TEXT NOT NULL
261
+ )`).run()
262
+ db.prepare('INSERT INTO workspaces_new (id, owner_user_id, name, created_at) SELECT id, owner_user_id, name, created_at FROM workspaces').run()
263
+ db.prepare('DROP TABLE workspaces').run()
264
+ db.prepare('ALTER TABLE workspaces_new RENAME TO workspaces').run()
265
+ }
266
+ if (needChannels) {
267
+ db.prepare(`CREATE TABLE channels_new (
268
+ id TEXT PRIMARY KEY,
269
+ name TEXT NOT NULL,
270
+ description TEXT NOT NULL DEFAULT '',
271
+ scenario_prompt TEXT NOT NULL DEFAULT '',
272
+ llm_json TEXT NOT NULL DEFAULT '',
273
+ lead_agent_id TEXT,
274
+ workspace TEXT NOT NULL DEFAULT '',
275
+ enabled INTEGER NOT NULL DEFAULT 1,
276
+ created_at TEXT NOT NULL,
277
+ updated_at TEXT NOT NULL,
278
+ owner_user_id TEXT,
279
+ visibility TEXT NOT NULL DEFAULT 'private',
280
+ join_policy TEXT NOT NULL DEFAULT 'owner_approve',
281
+ approval_policy TEXT NOT NULL DEFAULT 'owner_only',
282
+ chat_enabled INTEGER NOT NULL DEFAULT 0,
283
+ version INTEGER NOT NULL DEFAULT 1,
284
+ line_id TEXT NOT NULL DEFAULT ''
285
+ )`).run()
286
+ db.prepare(`INSERT INTO channels_new (id, name, description, scenario_prompt, llm_json, lead_agent_id,
287
+ workspace, enabled, created_at, updated_at, owner_user_id, visibility,
288
+ join_policy, approval_policy, chat_enabled, version, line_id)
289
+ SELECT id, name, description, scenario_prompt, llm_json, lead_agent_id,
290
+ workspace, enabled, created_at, updated_at, owner_user_id, visibility,
291
+ join_policy, approval_policy, chat_enabled, version, line_id FROM channels`).run()
292
+ db.prepare('DROP TABLE channels').run()
293
+ db.prepare('ALTER TABLE channels_new RENAME TO channels').run()
294
+ }
295
+ if (needAgents) {
296
+ db.prepare(`CREATE TABLE agents_new (
297
+ id TEXT PRIMARY KEY,
298
+ name TEXT NOT NULL,
299
+ harness TEXT NOT NULL,
300
+ config_json TEXT NOT NULL DEFAULT '{}',
301
+ enabled INTEGER NOT NULL DEFAULT 1,
302
+ visibility TEXT NOT NULL DEFAULT 'private',
303
+ created_at TEXT NOT NULL,
304
+ updated_at TEXT NOT NULL,
305
+ owner_user_id TEXT
306
+ )`).run()
307
+ db.prepare('INSERT INTO agents_new (id, name, harness, config_json, enabled, visibility, created_at, updated_at, owner_user_id) SELECT id, name, harness, config_json, enabled, visibility, created_at, updated_at, owner_user_id FROM agents').run()
308
+ db.prepare('DROP TABLE agents').run()
309
+ db.prepare('ALTER TABLE agents_new RENAME TO agents').run()
310
+ }
311
+ if (needTeams) {
312
+ db.prepare(`CREATE TABLE teams_new (
313
+ id TEXT PRIMARY KEY,
314
+ name TEXT NOT NULL,
315
+ description TEXT NOT NULL DEFAULT '',
316
+ visibility TEXT NOT NULL DEFAULT 'private',
317
+ created_at TEXT NOT NULL,
318
+ updated_at TEXT NOT NULL,
319
+ owner_user_id TEXT
320
+ )`).run()
321
+ db.prepare('INSERT INTO teams_new (id, name, description, visibility, created_at, updated_at, owner_user_id) SELECT id, name, description, visibility, created_at, updated_at, owner_user_id FROM teams').run()
322
+ db.prepare('DROP TABLE teams').run()
323
+ db.prepare('ALTER TABLE teams_new RENAME TO teams').run()
310
324
  }
311
- db.exec(`CREATE INDEX IF NOT EXISTS idx_workspaces_owner ON workspaces(owner_user_id);`)
325
+ // 重建会连带删除该表的索引;这 4 张表在 SCHEMA_SQL 里只有一个索引,原样补回
326
+ db.prepare('CREATE INDEX IF NOT EXISTS idx_workspaces_owner ON workspaces(owner_user_id)').run()
312
327
  }
313
328
  finally {
314
329
  db.exec('PRAGMA foreign_keys = ON;')
@@ -7,10 +7,12 @@ import type { DcwCreateInput, DcwPatchInput } from './types'
7
7
  import { AppError, ErrorCodes } from '../../../../utils/errors'
8
8
  import { DcwNode } from '../dcw-node'
9
9
  import { dcwKeyFromRef, normalizeDataTransform } from '../../../../../shared/dcw-protocol'
10
+ import { stripMaskedSecrets } from '../../../../../shared/daq-protocol'
10
11
  import { findDcwTemplate } from '../dcw-templates'
11
12
  import { getDcwLineRepo } from '../dcw-line.repo'
12
13
  import { getDcwParamRepo } from '../param-map.repo'
13
14
  import { requireDcwDriverKind } from '../drivers'
15
+ import { assertDriverRawRange } from '../drivers/shared'
14
16
  import { randomUUID } from 'node:crypto'
15
17
 
16
18
  function normalizeStepLimitInput(value: unknown): number | null | undefined {
@@ -37,7 +39,7 @@ export abstract class DcwControllerCrud extends DcwControllerControl {
37
39
  templateRef: input.templateRef,
38
40
  name: input.name ?? `${tpl.name} ${String(seq).padStart(2, '0')}`,
39
41
  driver: input.driver ? requireDcwDriverKind(input.driver) : undefined,
40
- driverConfig: input.driverConfig ?? {},
42
+ driverConfig: stripMaskedSecrets(input.driverConfig ?? {}),
41
43
  transform: normalizeDataTransform(input.transform),
42
44
  enabled: input.enabled,
43
45
  holdIntervalMs: input.holdIntervalMs ?? null,
@@ -55,6 +57,13 @@ export abstract class DcwControllerCrud extends DcwControllerControl {
55
57
  writeLockSeconds: input.writeLockSeconds,
56
58
  stepLimit: normalizeStepLimitInput(input.stepLimit),
57
59
  })
60
+ // S15:Modbus 原始量程必须在配置期可被 dataType 表示(越界运行期会拒绝写,但提前拦更好)
61
+ try {
62
+ assertDriverRawRange(node.driver, node.driverConfig)
63
+ }
64
+ catch (err) {
65
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, err instanceof Error ? err.message : String(err))
66
+ }
58
67
  this.repo.insert(node)
59
68
  // 工艺参数映射面:节点创建即自动生成同名映射(用户/Agent 的参数语义面;
60
69
  // PLC 寻址细节仍封装在节点驱动配置内)。失败不阻断节点创建(可事后手工补建)。
@@ -72,20 +81,34 @@ export abstract class DcwControllerCrud extends DcwControllerControl {
72
81
  // 保写心跳若继续按旧值周期重下发,会把陈旧设定写向新目标(10-07 换绑残留事故根因①)。
73
82
  // 清值后心跳自动停驻,须新写成功才重新锚定。
74
83
  let invalidateValue = false
84
+ const prevDriver = node.driver
85
+ const prevDriverConfig = node.driverConfig
75
86
  if (patch.name !== undefined) node.name = patch.name
76
87
  if (patch.driver !== undefined) {
77
88
  node.driver = requireDcwDriverKind(patch.driver)
78
89
  invalidateValue = true
79
90
  }
80
91
  if (patch.driverConfig !== undefined) {
81
- node.driverConfig = { ...node.driverConfig, ...patch.driverConfig }
92
+ // P0-2(2026-10-28 审查):掩码回显不得覆盖真实凭据(与 DAQ 同源单点过滤)
93
+ node.driverConfig = { ...node.driverConfig, ...stripMaskedSecrets(patch.driverConfig) }
82
94
  invalidateValue = true
83
95
  }
96
+ // S15:配置期校验(失败回滚 driver/driverConfig,避免内存里留下半改状态)
97
+ try {
98
+ assertDriverRawRange(node.driver, node.driverConfig)
99
+ }
100
+ catch (err) {
101
+ node.driver = prevDriver
102
+ node.driverConfig = prevDriverConfig
103
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, err instanceof Error ? err.message : String(err))
104
+ }
84
105
  if (patch.transform !== undefined) {
85
106
  if (patch.transform.kind === 'linear' && (!Number.isFinite(Number(patch.transform.scale)) || Number(patch.transform.scale) === 0)) {
86
107
  throw new AppError(400, ErrorCodes.VALIDATION_ERROR, '标定系数 scale 必须为非零数字(物理值 = scale × PLC值 + offset)')
87
108
  }
88
109
  node.transform = normalizeDataTransform(patch.transform)
110
+ // 标定变了 → 旧 value 的物理含义变了,保写心跳必须停驻到下一次成功写
111
+ invalidateValue = true
89
112
  }
90
113
  if (patch.unit !== undefined) node.unit = patch.unit
91
114
  // decimals 必须 0..6 整数:它同时喂给 toFixed() —— 越界值会在**驱动已经写进 PLC 之后**
@@ -102,11 +125,13 @@ export abstract class DcwControllerCrud extends DcwControllerControl {
102
125
  if (patch.min !== undefined) {
103
126
  const v = Number(patch.min)
104
127
  if (!Number.isFinite(v)) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `min 必须为有限数字(当前: ${String(patch.min)})`)
128
+ if (node.min !== v) invalidateValue = true
105
129
  node.min = v
106
130
  }
107
131
  if (patch.max !== undefined) {
108
132
  const v = Number(patch.max)
109
133
  if (!Number.isFinite(v)) throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `max 必须为有限数字(当前: ${String(patch.max)})`)
134
+ if (node.max !== v) invalidateValue = true
110
135
  node.max = v
111
136
  }
112
137
  // 量程交叉校验放在两者都应用之后:min>max 会让 writeTolerance 变负、越界判定恒真
@@ -37,6 +37,10 @@ export abstract class DcwControllerLines extends DcwControllerRecipes {
37
37
  if (opts?.purge) this.remove(n.id)
38
38
  else {
39
39
  n.lineId = ''
40
+ // 2026-10-28:解绑产线必须清掉当前设定 —— 否则保写心跳会以"未分配产线"身份
41
+ // 继续把该线最后的 setpoint 写给 PLC(旧逻辑 !lineId 默认豁免产线门)。
42
+ n.value = null
43
+ this.runtimes.get(n.id)?.rearm()
40
44
  this.emitNodeChanged('updated', n)
41
45
  }
42
46
  }
@@ -78,10 +82,17 @@ export abstract class DcwControllerLines extends DcwControllerRecipes {
78
82
  }
79
83
  const line = getDcwLineRepo().byId(lineId)
80
84
  if (!line) throw new AppError(404, ErrorCodes.NOT_FOUND, `产线不存在: ${lineId}`)
81
- if (getActiveLineRun(lineId)) {
82
- throw new AppError(409, ErrorCodes.CONFLICT, `产线「${line.name}」已在运行(批次 ${getActiveLineRun(lineId)!.runId}),请先停止当前数据采集`)
83
- }
84
85
  const repo = getDcwRecipeRepo()
86
+ // 2026-10-28:注册表可能指向一个已被关闭/不存在的批次(关闭接口/多入口/崩溃恢复竞态)。
87
+ // 这种情况应清掉过期窗口后继续开跑,而不是把整条线永久卡在"已在运行"。
88
+ const current = getActiveLineRun(lineId)
89
+ if (current) {
90
+ const dbRun = repo.runById(current.runId)
91
+ if (dbRun && !dbRun.endedAt) {
92
+ throw new AppError(409, ErrorCodes.CONFLICT, `产线「${line.name}」已在运行(批次 ${current.runId}),请先停止当前数据采集`)
93
+ }
94
+ clearActiveLineRun(lineId)
95
+ }
85
96
  const recipe = repo.byId(recipeId)
86
97
  if (!recipe) throw new AppError(404, ErrorCodes.NOT_FOUND, `Recipe 不存在: ${recipeId}`)
87
98
  if (recipe.lineId !== lineId) {
@@ -7,12 +7,13 @@ import type { DcwWriteHistoryEntry } from '../dcw-recipe.repo'
7
7
  import type { RecipeInput, RecipeRunView, RecipeView, WriteVerifyOutcome } from '../../../../../shared/dcw-protocol'
8
8
  import { AppError, ErrorCodes } from '../../../../utils/errors'
9
9
  import { getDcwRecipeRepo } from '../dcw-recipe.repo'
10
- import { assertRecipeOpIntervalForExecution, recordRecipeOpAnchor } from '../recipe-op-anchor'
10
+ import { assertRecipeOpIntervalForExecution, beginRecipeOp, endRecipeOp, recordRecipeOpAnchor } from '../recipe-op-anchor'
11
11
  import { hydrateTrialLastAt, setTrialLastAt } from './gate-persist'
12
12
  import { recordOps } from '../../ops/ops'
13
13
  import { summarizeAck, verifyRecipeWrite } from './write-verify'
14
14
  import { writeTolerance } from '../dcw-runtime'
15
15
  import { notifyLineOperators } from '../../runtime/line-notify'
16
+ import { assertWithinLimits } from '../param-limits'
16
17
 
17
18
  /**
18
19
  * 试验节拍 env 解析(显式 0 = 关闭;空/非法 = 默认 5 分钟)。
@@ -111,7 +112,7 @@ export abstract class DcwControllerRecipes extends DcwControllerParams {
111
112
  * 2026-10-08 ACK 鉴定:每参数写后经 verifyRecipeWrite 补验(链路受理→独立回读
112
113
  * 证实),结果行携带 ack/verify —— 「ok」不再被当成「设备已生效」。
113
114
  */
114
- protected async writeRecipeParams(recipe: RecipeView, run: RecipeRunView): Promise<void> {
115
+ protected async writeRecipeParams(recipe: RecipeView, run: RecipeRunView, opts: { enforceRecipeWindow?: boolean } = {}): Promise<void> {
115
116
  const results: RecipeRunView['results'] = []
116
117
  for (const param of recipe.params) {
117
118
  const node = param.nodeId ? this.repo.byId(param.nodeId) : undefined
@@ -129,6 +130,24 @@ export abstract class DcwControllerRecipes extends DcwControllerParams {
129
130
  results.push({ templateRef: node.templateRef, nodeId: node.id, ok: false, message: `已跳过:节点「${node.name}」已取消绑定(${node.lineId ? '已改挂其他产线' : '已从产线移除'}),参数未下发`, value: param.value })
130
131
  continue
131
132
  }
133
+ // 2026-10-28:trial/候选覆盖值必须再过一次配方工艺窗口。
134
+ // 配方路径在 write() 里 skipRecipe:true(保存值已在保存时校验),但 trial 的
135
+ // overrides 是新值 —— 曾在 40~48 的配方窗下把 80/120 写进 PLC。
136
+ if (opts.enforceRecipeWindow) {
137
+ try {
138
+ assertWithinLimits(node, param.value, { includeSoft: true, skipRecipe: false })
139
+ }
140
+ catch (err) {
141
+ results.push({
142
+ templateRef: node.templateRef,
143
+ nodeId: node.id,
144
+ ok: false,
145
+ message: `候选值未下发(越界):${err instanceof Error ? err.message : String(err)}`,
146
+ value: param.value,
147
+ })
148
+ continue
149
+ }
150
+ }
132
151
  try {
133
152
  const outcome = await this.write(node.id, param.value, run.id, { source: 'recipe', actor: 'recipe' })
134
153
  // ACK 鉴定:命令 ok 但仅链路受理(mqtt/http-no-echo/mock)→ 独立回读补验
@@ -169,6 +188,13 @@ export abstract class DcwControllerRecipes extends DcwControllerParams {
169
188
  }
170
189
  }
171
190
 
191
+ /** 占位试验节拍(与 assertTrialCadence 同步调用,中间不得有 await;B-P1-2)。 */
192
+ private claimTrialCadence(lineId: string): void {
193
+ const at = Date.now()
194
+ this.trialLastAt.set(lineId, at)
195
+ setTrialLastAt(lineId, at) // P1-5 落盘
196
+ }
197
+
172
198
  /**
173
199
  * Recipe 手动应用 / Agent 试验(一键下发工艺参数集;不激活产线窗口)。
174
200
  * v19 trial 语义:**声明式覆盖集** —— 提供 overrides 时只写 overrides 列出的节点
@@ -197,9 +223,41 @@ export abstract class DcwControllerRecipes extends DcwControllerParams {
197
223
  if (unknown.length) {
198
224
  throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `覆盖参数不在配方内: ${unknown.map(o => o.nodeId).join(', ')}(试验只能修改配方已有参数的值;新增参数请先 recipe_update)`)
199
225
  }
200
- if (opts.trial) this.assertTrialCadence(recipe.lineId)
226
+ if (opts.trial) {
227
+ // B-P1-2(2026-10-28 审查):判定与占位必须同一次同步执行(中间无 await),
228
+ // 否则并发 trial 双双过检;写入完成前第二次会被节拍拦下。
229
+ this.assertTrialCadence(recipe.lineId)
230
+ this.claimTrialCadence(recipe.lineId)
231
+ }
201
232
  // Recipe 级下发间隔卡控(执行时兜底):仅 Agent 路径;approvalId 自豁免防审批竞态
202
- if (opts.agentDispatch) assertRecipeOpIntervalForExecution(recipe, { approvalId: opts.agentApprovalId, emergency: opts.emergency })
233
+ if (opts.agentDispatch) {
234
+ assertRecipeOpIntervalForExecution(recipe, { approvalId: opts.agentApprovalId, emergency: opts.emergency })
235
+ // 在飞占位:频控是"查-做-记"三段,首尾之间隔着整批写入(秒级)。两个并发下发
236
+ // 都会在"查"时看到旧锚。占位与判定同步完成,follow 到 finally 释放。
237
+ beginRecipeOp(recipeId, recipe.name)
238
+ }
239
+ try {
240
+ return await this.applyRecipeLocked(recipe, opts, overrides)
241
+ }
242
+ finally {
243
+ if (opts.agentDispatch) endRecipeOp(recipeId)
244
+ }
245
+ }
246
+
247
+ /** applyRecipe 的持锁段(调用方已通过节拍/频控判定并在飞占位;不得在此重复占位)。 */
248
+ private async applyRecipeLocked(
249
+ recipe: RecipeView,
250
+ opts: {
251
+ overrides?: Array<{ nodeId: string, value: number }>
252
+ trial?: boolean
253
+ agentDispatch?: boolean
254
+ agentApprovalId?: string
255
+ emergency?: boolean
256
+ },
257
+ overrides: Array<{ nodeId: string, value: number }>,
258
+ ): Promise<RecipeRunView> {
259
+ const repo = getDcwRecipeRepo()
260
+ const recipeId = recipe.id
203
261
  // 声明式覆盖集:有 overrides 只写声明的节点(未声明参数保持现状,绝不顺带整批);
204
262
  // 无 overrides(手动应用/统一回退重下发)= 整批语义
205
263
  const effective = overrides.length > 0
@@ -209,7 +267,7 @@ export abstract class DcwControllerRecipes extends DcwControllerParams {
209
267
  })
210
268
  : recipe.params
211
269
  const run = repo.createRun(recipe, effective)
212
- await this.writeRecipeParams({ ...recipe, params: effective }, run)
270
+ await this.writeRecipeParams({ ...recipe, params: effective }, run, { enforceRecipeWindow: overrides.length > 0 })
213
271
  // 运行中应用新配方版本 → 谱系拆段(A13):旧段在 now 闭合,新段记录新版本/内容 hash。
214
272
  try {
215
273
  const { getActiveLineRun, setActiveLineRun } = await import('../line-run')
@@ -357,7 +357,16 @@ class DcwRecipeRepo {
357
357
  markGood(recipeId: string, runId: string): RecipeView {
358
358
  const r = this.byId(recipeId)
359
359
  if (!r) throw new AppError(404, ErrorCodes.NOT_FOUND, `Recipe 不存在: ${recipeId}`)
360
- if (!this.runById(runId)) throw new AppError(404, ErrorCodes.NOT_FOUND, `批次不存在: ${runId}`)
360
+ const run = this.runById(runId)
361
+ if (!run) throw new AppError(404, ErrorCodes.NOT_FOUND, `批次不存在: ${runId}`)
362
+ // 2026-10-28:此前只校验 run 存在 —— 可把同线另一个配方/别的产品的批次标为"已知良好",
363
+ // 之后 to_last_good 会把那个批次的 setpoints 整批写进当前配方。
364
+ if (run.recipeId !== recipeId) {
365
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `批次 ${runId} 属于配方 ${run.recipeId},不能标记为配方 ${recipeId} 的良好批次`)
366
+ }
367
+ if (run.lineId !== r.lineId) {
368
+ throw new AppError(400, ErrorCodes.VALIDATION_ERROR, `批次 ${runId} 属于产线 ${run.lineId},与配方产线 ${r.lineId} 不一致`)
369
+ }
361
370
  r.lastGoodRunId = runId
362
371
  this.flushRecipes()
363
372
  return r
@@ -8,6 +8,7 @@
8
8
 
9
9
  import { AppError, ErrorCodes } from '../../../utils/errors'
10
10
  import type { DcwNode } from './dcw-node'
11
+ import { assertWithinLimits } from './param-limits'
11
12
 
12
13
  /** 回读死区容差:max(0.5×10^-小数位, 量程 0.5%) */
13
14
  export function writeTolerance(node: DcwNode): number {
@@ -49,6 +50,8 @@ export interface DcwReadOutcome {
49
50
  export class DcwNodeRuntime {
50
51
  private writing = false
51
52
  private lastHoldAt = 0
53
+ /** 保写被限界拒绝的最近告警时刻(节流,避免每拍刷日志) */
54
+ private holdBlockedAt = 0
52
55
  /** 周期读节拍 + 在飞互斥(读事务不与写事务互斥,但同节点读不重入) */
53
56
  private lastReadTick = 0
54
57
  private reading = false
@@ -107,20 +110,40 @@ export class DcwNodeRuntime {
107
110
  const hold = node.holdIntervalMs ?? this.host.defaults().holdIntervalMs
108
111
  // P0-1 保写产线门:绑定产线的节点仅在产线活动(有运行批次窗口)时保写,停线即挂起,
109
112
  // 开线后下一拍自然恢复(10-07 事故根因④:停线不挂起心跳,残留节点持续直写)。
110
- // lineId=''(未分配产线的独立通道)不受门控,保持独立节点用法。
111
- if (hold && hold > 0 && now - this.lastHoldAt >= hold && (!node.lineId || this.host.lineActive(node.lineId))) {
112
- this.lastHoldAt = now
113
- this.writing = true
114
- node.state = 'writing'
115
- // tick 是同步节拍(由 sweep 同步调用),这里 fire-and-forget 但**全程持锁**,
116
- // 保证与 write() 互斥;finally 里收敛状态并释放闸门。
117
- void Promise.resolve()
118
- .then(() => this.host.executeWrite(node, node.value!, writeTolerance(node), null))
119
- .catch(() => { /* 保写失败不抛出:下一拍重试 */ })
120
- .finally(() => {
121
- this.writing = false
122
- if (node.state === 'writing') node.state = 'error'
123
- })
113
+ // 2026-10-28:未分配产线的节点不再默认豁免 —— 删线(purge=false)会把 lineId 置空,
114
+ // 旧逻辑因此让已下线产线的设定值被永久重写。现在必须显式 AW_HOLD_UNASSIGNED=1 才保写。
115
+ const lineGate = node.lineId ? this.host.lineActive(node.lineId) : process.env.AW_HOLD_UNASSIGNED === '1'
116
+ if (hold && hold > 0 && now - this.lastHoldAt >= hold && lineGate) {
117
+ // 2026-10-28:保写心跳也过四层限界(含配方窗)。此前只查在飞闸门,操作员收窄量程/
118
+ // 换配方后可写被拒、心跳却按旧值持续重写 PLC。
119
+ let withinLimits = true
120
+ try {
121
+ assertWithinLimits(node, node.value!, { includeSoft: true, skipRecipe: false })
122
+ }
123
+ catch (err) {
124
+ withinLimits = false
125
+ if (now - this.holdBlockedAt > 60_000) {
126
+ this.holdBlockedAt = now
127
+ console.warn(`[dcw] 保写心跳被限界拒绝(跳过本拍):${node.name}(${node.id}) ${err instanceof Error ? err.message : String(err)}`)
128
+ }
129
+ }
130
+ if (!withinLimits) {
131
+ // 不写,但周期读继续(下方读路径与本块独立)
132
+ }
133
+ else {
134
+ this.lastHoldAt = now
135
+ this.writing = true
136
+ node.state = 'writing'
137
+ // tick 是同步节拍(由 sweep 同步调用),这里 fire-and-forget 但**全程持锁**,
138
+ // 保证与 write() 互斥;finally 里收敛状态并释放闸门。
139
+ void Promise.resolve()
140
+ .then(() => this.host.executeWrite(node, node.value!, writeTolerance(node), null))
141
+ .catch(() => { /* 保写失败不抛出:下一拍重试 */ })
142
+ .finally(() => {
143
+ this.writing = false
144
+ if (node.state === 'writing') node.state = 'error'
145
+ })
146
+ }
124
147
  }
125
148
  }
126
149
  // 周期读:被动观测,不依赖写过、不与写在飞互斥(仅同节点读串行)。
@@ -164,6 +164,8 @@ export const num = (v: unknown): number | undefined => {
164
164
  /**
165
165
  * 工程量 → 原始值:线性映射 eng ∈ [engMin, engMax] ↔ raw ∈ [rawMin, rawMax]。
166
166
  * 未提供原始量程时 raw = eng(float32 直写;int 类型仍需映射,缺省按量程直传并取整)。
167
+ * 2026-10-28(S15):换算结果必须落在 rawMin~rawMax 内 —— 此前越界值会被 encodeWords
168
+ * 静默按位回绕(int16/uint16)后写进 PLC,造成"写失败但设备已变"。
167
169
  */
168
170
  export function engToRaw(eng: number, input: DcwWriteInput): number {
169
171
  const cfg = input.driverConfig
@@ -173,7 +175,14 @@ export function engToRaw(eng: number, input: DcwWriteInput): number {
173
175
  const engMin = num(cfg.engMin) ?? input.domain.min
174
176
  const engMax = num(cfg.engMax) ?? input.domain.max
175
177
  if (engMax === engMin) return eng
176
- return rawMin + ((eng - engMin) / (engMax - engMin)) * (rawMax - rawMin)
178
+ const out = rawMin + ((eng - engMin) / (engMax - engMin)) * (rawMax - rawMin)
179
+ if (!Number.isFinite(out)) throw new Error(`工程量 ${eng} 换算原始值失败(结果非有限数)`)
180
+ const lo = Math.min(rawMin, rawMax)
181
+ const hi = Math.max(rawMin, rawMax)
182
+ if (out < lo - 1e-9 || out > hi + 1e-9) {
183
+ throw new Error(`工程量 ${eng} 换算原始值 ${Number(out.toFixed(4))} 超出驱动原始量程 ${lo}~${hi}(eng ${engMin}~${engMax});请核对节点量程与 engMin/engMax/rawMin/rawMax 配置`)
184
+ }
185
+ return out
177
186
  }
178
187
 
179
188
  /** 原始值 → 工程量(回读换算;映射参数对称) */
@@ -208,16 +217,63 @@ export function readbackAck(
208
217
  return { ok, message, raw: opts.raw, readback, ack: ok ? 'readback-verified' : 'unverified' }
209
218
  }
210
219
 
211
- /** 原始值 → 寄存器字序(数据类型 + 字节序;与数采 decodeRegisters 互逆) */
220
+ /**
221
+ * 原始值 → 寄存器字序(数据类型 + 字节序;与数采 decodeRegisters 互逆)。
222
+ * 2026-10-28(S15):整数类型先校验可表示范围,越界抛错而不是按位回绕。
223
+ */
212
224
  export function encodeWords(raw: number, dataType: string, byteOrder: string): number[] {
213
- if (dataType === 'int16' || dataType === 'uint16') return [raw & 0xFFFF]
225
+ if (dataType === 'int16' || dataType === 'uint16') {
226
+ const r = Math.round(raw)
227
+ if (!Number.isFinite(r)) throw new Error(`原始值 ${raw} 不是有限数,拒绝编码为 ${dataType}`)
228
+ if (dataType === 'int16' && (r < -32768 || r > 32767)) throw new Error(`原始值 ${r} 超出 int16 范围(-32768~32767),拒绝写入`)
229
+ if (dataType === 'uint16' && (r < 0 || r > 65535)) throw new Error(`原始值 ${r} 超出 uint16 范围(0~65535),拒绝写入`)
230
+ return [r & 0xFFFF]
231
+ }
232
+ if (dataType === 'uint32') {
233
+ const r = Math.round(raw)
234
+ if (!Number.isFinite(r) || r < 0 || r > 4294967295) throw new Error(`原始值 ${raw} 超出 uint32 范围,拒绝写入`)
235
+ }
236
+ if (dataType === 'int32') {
237
+ const r = Math.round(raw)
238
+ if (!Number.isFinite(r) || r < -2147483648 || r > 2147483647) throw new Error(`原始值 ${raw} 超出 int32 范围,拒绝写入`)
239
+ }
214
240
  const buf = Buffer.alloc(4)
215
241
  if (dataType === 'float32') buf.writeFloatBE(raw, 0)
216
- else if (dataType === 'uint32') buf.writeUInt32BE(raw, 0)
217
- else buf.writeInt32BE(raw, 0)
242
+ else if (dataType === 'uint32') buf.writeUInt32BE(Math.round(raw), 0)
243
+ else buf.writeInt32BE(Math.round(raw), 0)
218
244
  const B = [buf[0]!, buf[1]!, buf[2]!, buf[3]!]
219
245
  const word = (hi: number, lo: number) => ((hi & 0xFF) << 8) | (lo & 0xFF)
220
- if (byteOrder === 'little') return [word(B[1]!, B[0]!), word(B[3]!, B[2]!)]
246
+ // 与 daq/drivers/modbus-tcp.ts:decodeRegisters 严格互逆(2026-10-28 P0-1):
247
+ // little=DCBA → [D C][B A];wordSwap=CDAB → [C D][A B];big=ABCD → [A B][C D]
248
+ if (byteOrder === 'little') return [word(B[3]!, B[2]!), word(B[1]!, B[0]!)]
221
249
  if (byteOrder === 'wordSwap') return [word(B[2]!, B[3]!), word(B[0]!, B[1]!)]
222
250
  return [word(B[0]!, B[1]!), word(B[2]!, B[3]!)]
223
251
  }
252
+
253
+ const INT_RANGES: Record<string, [number, number]> = {
254
+ int16: [-32768, 32767],
255
+ uint16: [0, 65535],
256
+ int32: [-2147483648, 2147483647],
257
+ uint32: [0, 4294967295],
258
+ }
259
+
260
+ /**
261
+ * 配置期校验(S15):Modbus 原始量程必须能被 dataType 表示。
262
+ * 在节点创建/编辑时拒绝,避免运行期写坏 PLC 后才发现。
263
+ */
264
+ export function assertDriverRawRange(driver: string, cfg: Record<string, unknown> = {}): void {
265
+ if (!driver.startsWith('modbus')) return
266
+ const rawMin = num(cfg.rawMin)
267
+ const rawMax = num(cfg.rawMax)
268
+ if (rawMin === undefined && rawMax === undefined) return
269
+ if (rawMin === undefined || rawMax === undefined) throw new Error('rawMin 与 rawMax 必须同时提供')
270
+ if (!(rawMax > rawMin)) throw new Error('rawMin 必须小于 rawMax')
271
+ const dt = String(cfg.dataType ?? 'float32')
272
+ const range = INT_RANGES[dt]
273
+ if (range && (rawMin < range[0] || rawMax > range[1])) {
274
+ throw new Error(`原始量程 ${rawMin}~${rawMax} 超出 ${dt} 可表示范围 ${range[0]}~${range[1]}`)
275
+ }
276
+ const engMin = num(cfg.engMin)
277
+ const engMax = num(cfg.engMax)
278
+ if (engMin !== undefined && engMax !== undefined && !(engMax > engMin)) throw new Error('engMin 必须小于 engMax')
279
+ }
@@ -67,6 +67,34 @@ export function getRecipeOpAnchor(recipeId: string): RecipeOpAnchor | null {
67
67
  return store().anchors.find(a => a.recipeId === recipeId) ?? null
68
68
  }
69
69
 
70
+ /**
71
+ * 执行在飞占位(2026-10-28 审查 B-P1-2)。
72
+ *
73
+ * 频控判定是"查锚 → 写入(秒级 await)→ 落锚"三段:两个并发下发都会在"查"时
74
+ * 读到同一旧锚而双双过检,opIntervalMs/试验节拍被整体绕过。占位与判定必须在
75
+ * 同一次同步执行内完成(JS 单线程内天然原子),写入结束在 finally 释放。
76
+ * 跨进程语义仍由单进程写锁约束(与本仓库其它写路径同口径)。
77
+ */
78
+ const inflight = new Set<string>()
79
+
80
+ export function beginRecipeOp(recipeId: string, recipeName: string): void {
81
+ if (inflight.has(recipeId)) {
82
+ throw new AppError(429, ErrorCodes.WRITE_FREQUENT,
83
+ `配方「${recipeName}」已有一次下发正在执行(在飞占位),本次未获执行、产线未做任何变更。`
84
+ + `请等待该次下发返回后重试;若长时间无响应,请检查产线连接与写历史。`)
85
+ }
86
+ inflight.add(recipeId)
87
+ }
88
+
89
+ export function endRecipeOp(recipeId: string): void {
90
+ inflight.delete(recipeId)
91
+ }
92
+
93
+ /** 仅供测试/观测:该配方是否有在飞下发。 */
94
+ export function isRecipeOpInflight(recipeId: string): boolean {
95
+ return inflight.has(recipeId)
96
+ }
97
+
70
98
  /** 仅供测试:清空全部锚(内存+落盘) */
71
99
  export function resetRecipeOpAnchors(): void {
72
100
  g.__recipeOpAnchors = { anchors: [] }