agentworkshop 0.7.71 → 0.8.0

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 (358) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +332 -0
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +332 -0
  3. package/.output/nitro.json +1 -1
  4. package/.output/public/_nuxt/BH8RvpVy.js +1 -0
  5. package/.output/public/_nuxt/Byf4IxU2.js +14 -0
  6. package/.output/public/_nuxt/CxVhG0Xx.js +1 -0
  7. package/.output/public/_nuxt/D0mzKkZr.js +1 -0
  8. package/.output/public/_nuxt/DIFkTkdS.js +1 -0
  9. package/.output/public/_nuxt/DgrK8UCQ.js +0 -0
  10. package/.output/public/_nuxt/builds/latest.json +1 -1
  11. package/.output/public/_nuxt/builds/meta/7a79a7e4-d691-4cf5-ae3a-3b01814479fb.json +1 -0
  12. package/.output/public/_nuxt/{default.BzOcvmSE.css → default.BEXGWZFQ.css} +1 -1
  13. package/.output/public/_nuxt/{entry.CL2i0F8o.css → entry.C7koBPom.css} +1 -1
  14. package/.output/public/_nuxt/ontology.QMGeh66l.css +1 -0
  15. package/.output/server/chunks/_/access.mjs +1 -1
  16. package/.output/server/chunks/_/binding-authz.mjs +1 -1
  17. package/.output/server/chunks/_/catalog-view.mjs +1 -1
  18. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  19. package/.output/server/chunks/_/chart-repo.mjs +1 -1
  20. package/.output/server/chunks/_/dashboard-repo.mjs +1 -1
  21. package/.output/server/chunks/_/data-export.mjs +6 -6
  22. package/.output/server/chunks/_/data-query.mjs +3 -3
  23. package/.output/server/chunks/_/eda-run.repo.mjs +2 -2
  24. package/.output/server/chunks/_/entity.mjs +1 -1
  25. package/.output/server/chunks/_/features.mjs +5 -5
  26. package/.output/server/chunks/_/golden.mjs +3 -3
  27. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  28. package/.output/server/chunks/_/index.mjs +1 -1
  29. package/.output/server/chunks/_/mes-console.mjs +1 -1
  30. package/.output/server/chunks/_/nitro.mjs +5362 -2240
  31. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  32. package/.output/server/chunks/_/ocap.mjs +1 -1
  33. package/.output/server/chunks/_/rest-auth.mjs +1 -1
  34. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  35. package/.output/server/chunks/_/spc.mjs +2 -2
  36. package/.output/server/chunks/build/TownView-BWMDbosY.mjs +1 -1
  37. package/.output/server/chunks/build/api-BEW_ebY_.mjs +64 -0
  38. package/.output/server/chunks/build/api-BEW_ebY_.mjs.map +1 -0
  39. package/.output/server/chunks/build/daq-CdxcfY_G.mjs +1 -1
  40. package/.output/server/chunks/build/dcw-BlDQtS4q.mjs +1 -1
  41. package/.output/server/chunks/build/{default-BeB88U7y.mjs → default-CJkOcmrm.mjs} +14 -8
  42. package/.output/server/chunks/build/{default-BeB88U7y.mjs.map → default-CJkOcmrm.mjs.map} +1 -1
  43. package/.output/server/chunks/build/ontology-C3HS5pMo.mjs +977 -0
  44. package/.output/server/chunks/build/ontology-C3HS5pMo.mjs.map +1 -0
  45. package/.output/server/chunks/build/types-D6J7slEa.mjs +6 -2
  46. package/.output/server/chunks/build/useDaqAddNode-D_Rkju0f.mjs +1 -1
  47. package/.output/server/chunks/build/useDaqDetailNode-DeOsDxK5.mjs +1 -1
  48. package/.output/server/chunks/build/useDaqDetailParams-Cm9a9r5G.mjs +1 -1
  49. package/.output/server/chunks/build/useDaqFilters-CctmO03p.mjs +1 -1
  50. package/.output/server/chunks/build/useDaqStream-B9ZBVQrI.mjs +1 -1
  51. package/.output/server/chunks/build/useDaqTemplates-CK37sMfi.mjs +1 -1
  52. package/.output/server/chunks/build/useDcwAddNode-DACVcEHs.mjs +1 -1
  53. package/.output/server/chunks/build/useDcwStream-Dir5ufAU.mjs +1 -1
  54. package/.output/server/chunks/routes/api/mcp/workshop.mjs +1 -1
  55. package/.output/server/chunks/routes/api/system/backup/run.post.mjs +2 -2
  56. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +17 -4
  57. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs.map +1 -1
  58. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +1 -1
  59. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +1 -1
  60. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/grant.post.mjs +1 -1
  61. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/revoke.post.mjs +1 -1
  62. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +1 -1
  63. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +1 -1
  64. package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +1 -1
  65. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +1 -1
  66. package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +1 -1
  67. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
  68. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +1 -1
  69. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +1 -1
  70. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +1 -1
  71. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +1 -1
  72. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +1 -1
  73. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +1 -1
  74. package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +1 -1
  75. package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +1 -1
  76. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +1 -1
  77. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +1 -1
  78. package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs +1 -1
  79. package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +1 -1
  80. package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +1 -1
  81. package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +1 -1
  82. package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs +1 -1
  83. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +1 -1
  84. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +1 -1
  85. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +1 -1
  86. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +1 -1
  87. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +1 -1
  88. package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +1 -1
  89. package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +1 -1
  90. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +1 -1
  91. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +1 -1
  92. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs +1 -1
  93. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs +1 -1
  95. package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/analysis/_id_.delete.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/analysis/_id_.get.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/analysis/_id_.patch.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +2 -2
  103. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.put.mjs +1 -1
  108. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
  109. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
  110. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +1 -1
  111. package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/channels/_id/tools/reset.post.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs +1 -1
  116. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs +1 -1
  117. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
  118. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +7 -7
  119. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
  120. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
  121. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
  122. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
  123. package/.output/server/chunks/routes/api/workshop/daq/_id/ingest.post.mjs +2 -2
  124. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  125. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
  126. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
  127. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
  128. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
  129. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +1 -1
  130. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
  131. package/.output/server/chunks/routes/api/workshop/daq/export/_id_.get.mjs +1 -1
  132. package/.output/server/chunks/routes/api/workshop/daq/index2.post.mjs +1 -1
  133. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  134. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  135. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  136. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  137. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  138. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  139. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  140. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  141. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  142. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  143. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  144. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  145. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  146. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  147. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  148. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  149. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  150. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  151. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  152. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  153. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
  154. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  157. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +1 -1
  158. package/.output/server/chunks/routes/api/workshop/dcw/index7.get.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/dcw/mes-artifacts/file.get.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/dcw/mes-hook-test.post.mjs +1 -1
  167. package/.output/server/chunks/routes/api/workshop/dcw/mes-test-read.post.mjs +1 -1
  168. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  175. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  176. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  177. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  178. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  179. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  180. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +1 -1
  181. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  182. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +1 -1
  183. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
  184. package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +1 -1
  185. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  186. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  187. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  188. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  189. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  190. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  191. package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +1 -1
  192. package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +1 -1
  193. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  194. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
  195. package/.output/server/chunks/routes/api/workshop/eda/commonality.get.mjs +1 -1
  196. package/.output/server/chunks/routes/api/workshop/eda/features/run.post.mjs +1 -1
  197. package/.output/server/chunks/routes/api/workshop/eda/index3.get.mjs +1 -1
  198. package/.output/server/chunks/routes/api/workshop/eda/index6.get.mjs +1 -1
  199. package/.output/server/chunks/routes/api/workshop/eda/index6.post.mjs +1 -1
  200. package/.output/server/chunks/routes/api/workshop/eda/runs/_id/labels.patch.mjs +1 -1
  201. package/.output/server/chunks/routes/api/workshop/eda/runs.get.mjs +1 -1
  202. package/.output/server/chunks/routes/api/workshop/eda/vm/predict.post.mjs +1 -1
  203. package/.output/server/chunks/routes/api/workshop/exp/confirmations/_id/decide.post.mjs +1 -1
  204. package/.output/server/chunks/routes/api/workshop/exp/index.get.mjs +1 -1
  205. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +1 -1
  206. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  207. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  208. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  209. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  210. package/.output/server/chunks/routes/api/workshop/index.mjs +62 -0
  211. package/.output/server/chunks/routes/api/workshop/index.mjs.map +1 -0
  212. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  213. package/.output/server/chunks/routes/api/workshop/index10.get.mjs +1 -1
  214. package/.output/server/chunks/routes/api/workshop/index11.get.mjs +1 -1
  215. package/.output/server/chunks/routes/api/workshop/index12.get.mjs +1 -1
  216. package/.output/server/chunks/routes/api/workshop/index2.get.mjs +1 -1
  217. package/.output/server/chunks/routes/api/workshop/index3.get.mjs +1 -1
  218. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
  219. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  220. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  221. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
  222. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  223. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
  224. package/.output/server/chunks/routes/api/workshop/index8.get.mjs +1 -1
  225. package/.output/server/chunks/routes/api/workshop/mes/datasets.get.mjs +1 -1
  226. package/.output/server/chunks/routes/api/workshop/mes/preview.post.mjs +1 -1
  227. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs +54 -0
  228. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/compile.mjs.map +1 -0
  229. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs +51 -0
  230. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/evidence.mjs.map +1 -0
  231. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs +67 -0
  232. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/publish.mjs.map +1 -0
  233. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs +49 -0
  234. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval-decision.mjs.map +1 -0
  235. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs +35 -0
  236. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/approval.mjs.map +1 -0
  237. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs +49 -0
  238. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/rules/_ruleId/transition.mjs.map +1 -0
  239. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs +35 -0
  240. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId/validate.mjs.map +1 -0
  241. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs +70 -0
  242. package/.output/server/chunks/routes/api/workshop/ontology/drafts/_draftId_.mjs.map +1 -0
  243. package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs +64 -0
  244. package/.output/server/chunks/routes/api/workshop/ontology/export/_ontologyId_.mjs.map +1 -0
  245. package/.output/server/chunks/routes/api/workshop/ontology/index.mjs +44 -0
  246. package/.output/server/chunks/routes/api/workshop/ontology/index.mjs.map +1 -0
  247. package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs +36 -0
  248. package/.output/server/chunks/routes/api/workshop/ontology/scene.mjs.map +1 -0
  249. package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs +38 -0
  250. package/.output/server/chunks/routes/api/workshop/ontology/traces/_ontologyId_.mjs.map +1 -0
  251. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs +40 -0
  252. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId/compare.mjs.map +1 -0
  253. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs +63 -0
  254. package/.output/server/chunks/routes/api/workshop/ontology/versions/_ontologyId_.mjs.map +1 -0
  255. package/.output/server/chunks/routes/api/workshop/optimization/capabilities.get.mjs +1 -1
  256. package/.output/server/chunks/routes/api/workshop/optimization/index.get.mjs +1 -1
  257. package/.output/server/chunks/routes/api/workshop/optimization/index.mjs +1 -1
  258. package/.output/server/chunks/routes/api/workshop/optimization/index2.mjs +1 -1
  259. package/.output/server/chunks/routes/api/workshop/optimization/profiles/_id/publish.post.mjs +1 -1
  260. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/crosscheck.post.mjs +1 -1
  261. package/.output/server/chunks/routes/api/workshop/optimization/recommendations/_id/dispatch.post.mjs +1 -1
  262. package/.output/server/chunks/routes/api/workshop/optimization/solve.post.mjs +1 -1
  263. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  264. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  265. package/.output/server/chunks/routes/api/workshop/process-model/datasets.post.mjs +1 -1
  266. package/.output/server/chunks/routes/api/workshop/process-model/eligibility.mjs +1 -1
  267. package/.output/server/chunks/routes/api/workshop/process-model/experiments.mjs +1 -1
  268. package/.output/server/chunks/routes/api/workshop/process-model/index.mjs +1 -1
  269. package/.output/server/chunks/routes/api/workshop/process-model/specs/_id/publish.post.mjs +1 -1
  270. package/.output/server/chunks/routes/api/workshop/process-model/train.post.mjs +1 -1
  271. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  272. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  273. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +1 -1
  274. package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +1 -1
  275. package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +1 -1
  276. package/.output/server/chunks/routes/api/workshop/tools/_name_.get.mjs +1 -1
  277. package/.output/server/chunks/routes/api/workshop/tools/sync.post.mjs +1 -1
  278. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +2 -2
  279. package/.output/server/chunks/routes/renderer.mjs +2 -2
  280. package/.output/server/chunks/virtual/entry.mjs +234 -3
  281. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  282. package/.output/server/package.json +1 -1
  283. package/README-zh.md +27 -6
  284. package/README.md +25 -4
  285. package/app/components/AppSidebar.vue +1 -0
  286. package/app/pages/ontology/api.ts +18 -0
  287. package/app/pages/ontology/index.vue +779 -0
  288. package/app/pages/ontology/types.ts +122 -0
  289. package/config.yml +1 -1
  290. package/docs/cli.en.md +2 -2
  291. package/docs/cli.md +2 -2
  292. package/docs/plugins.en.md +1 -1
  293. package/docs/plugins.md +1 -1
  294. package/docs/sdk.en.md +1 -1
  295. package/docs/sdk.md +1 -1
  296. package/i18n/locales/en.ts +58 -0
  297. package/i18n/locales/zh-CN.ts +58 -0
  298. package/mcp/aw-mcp-server.mjs +35 -1
  299. package/package.json +1 -1
  300. package/scripts/docs-shots.mjs +36 -0
  301. package/scripts/e2e-model-loop-live.mjs +731 -0
  302. package/scripts/e2e-ontology-live.mjs +774 -0
  303. package/scripts/e2e-ontology.mjs +328 -0
  304. package/scripts/ontology-idd-rag-e2e.mjs +449 -0
  305. package/server/api/workshop/agent-tools/approvals/[id]/decide.post.ts +16 -0
  306. package/server/api/workshop/ontology/drafts/[draftId]/compile.ts +39 -0
  307. package/server/api/workshop/ontology/drafts/[draftId]/evidence.ts +33 -0
  308. package/server/api/workshop/ontology/drafts/[draftId]/publish.ts +44 -0
  309. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval-decision.ts +28 -0
  310. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/approval.ts +17 -0
  311. package/server/api/workshop/ontology/drafts/[draftId]/rules/[ruleId]/transition.ts +32 -0
  312. package/server/api/workshop/ontology/drafts/[draftId]/validate.ts +16 -0
  313. package/server/api/workshop/ontology/drafts/[draftId].ts +52 -0
  314. package/server/api/workshop/ontology/drafts/index.ts +20 -0
  315. package/server/api/workshop/ontology/export/[ontologyId].ts +50 -0
  316. package/server/api/workshop/ontology/index.ts +38 -0
  317. package/server/api/workshop/ontology/scene.ts +21 -0
  318. package/server/api/workshop/ontology/traces/[ontologyId].ts +19 -0
  319. package/server/api/workshop/ontology/versions/[ontologyId]/compare.ts +21 -0
  320. package/server/api/workshop/ontology/versions/[ontologyId].ts +42 -0
  321. package/server/services/workshop/agents/hitl-decision/dispatcher.ts +15 -0
  322. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +13 -1
  323. package/server/services/workshop/agents/industrial/grant-guard.ts +59 -0
  324. package/server/services/workshop/agents/industrial/index.ts +38 -32
  325. package/server/services/workshop/agents/industrial/ontology-tools.ts +320 -0
  326. package/server/services/workshop/agents/industrial/optimizer-tools.ts +79 -0
  327. package/server/services/workshop/agents/industrial/tool-verify.ts +32 -2
  328. package/server/services/workshop/agents/tool-reference-spec.ts +1 -1
  329. package/server/services/workshop/aml/twin/physics-spec.ts +6 -22
  330. package/server/services/workshop/daq/storage/index.ts +6 -5
  331. package/server/services/workshop/daq/storage/sqlite.adapter.ts +25 -13
  332. package/server/services/workshop/db/database/open.ts +2 -0
  333. package/server/services/workshop/db/database/schema.ts +155 -1
  334. package/server/services/workshop/ontology/compiler.ts +474 -0
  335. package/server/services/workshop/ontology/contracts.ts +441 -0
  336. package/server/services/workshop/ontology/guards.ts +139 -0
  337. package/server/services/workshop/ontology/index.ts +10 -0
  338. package/server/services/workshop/ontology/ontology.service.ts +892 -0
  339. package/server/services/workshop/ontology/repo.ts +449 -0
  340. package/server/services/workshop/ontology/semantic-binding.service.ts +226 -0
  341. package/server/services/workshop/ontology/validator.ts +218 -0
  342. package/server/services/workshop/optimization/binding.repo.ts +7 -0
  343. package/server/services/workshop/optimization/contracts.ts +14 -0
  344. package/server/services/workshop/optimization/dispatch.service.ts +16 -0
  345. package/server/services/workshop/optimization/profile-adopt.service.ts +256 -0
  346. package/server/services/workshop/optimization/profile.repo.ts +19 -3
  347. package/server/services/workshop/optimization/python/__pycache__/problems.cpython-312.pyc +0 -0
  348. package/shared/dimensions.ts +124 -0
  349. package/.output/public/_nuxt/B7s2qeyT.js +0 -14
  350. package/.output/public/_nuxt/LHIav-Kv.js +0 -1
  351. package/.output/public/_nuxt/RuMLNeQE.js +0 -1
  352. package/.output/public/_nuxt/builds/meta/0ae8cb0f-6ce5-4096-9692-9b1fc197b30d.json +0 -1
  353. /package/.output/public/_nuxt/{62J4vljm2.js → 62J4vljm.js} +0 -0
  354. /package/.output/public/_nuxt/{BO0BJx3w.js → BO0BJx3w2.js} +0 -0
  355. /package/.output/public/_nuxt/{C-MY-JQE2.js → C-MY-JQE.js} +0 -0
  356. /package/.output/public/_nuxt/{D5kCimyw.js → D5kCimyw2.js} +0 -0
  357. /package/.output/public/_nuxt/{DGrrucbG2.js → DGrrucbG.js} +0 -0
  358. /package/.output/public/_nuxt/{Y4sMSPrS.js → Y4sMSPrS2.js} +0 -0
@@ -0,0 +1,731 @@
1
+ #!/usr/bin/env node
2
+ /* eslint-disable @stylistic/max-statements-per-line */
3
+ /**
4
+ * 模型层闭环真机 E2E(2026-10-24)—— 三层模型的「创建→使用→迭代进化」全链。
5
+ *
6
+ * 目标:验证 Agent 经工具面真正控制/迭代模型(不止参数优化),人类作为管理者
7
+ * 查看/修正,Agent 在持续迭代中把新模型反馈进求解器(模型进化闭环)。
8
+ *
9
+ * M0 hybrid_twin 全功能频道(训练面+孪生面+优化器面同面注入)+ Agent 节点绑定 + 过程 spec
10
+ * M1 本体层:起草→批准→发布 v1;管理者修正规则窗→v2(人类按意愿修正模型)
11
+ * M2 孪生层(虚拟模型创建,全经 Agent 工具):场景发现→SceneContract 编译→冻结(人批准)
12
+ * →PhysicsSpec 骨架→校验→编译候选→频道谱系重链→真采快照→虚拟试验(recommendation-only)
13
+ * M3 模型进化第 1 轮:训练 A → Profile v1(A)+发布+绑定 → 基线求解 → 训练 B →
14
+ * optimizer_profile_propose(工具)→ HITL 批准 → 发布 v2+自动重绑 → 新模型 solve optimal
15
+ * M4 模型进化第 2 轮:训练 C → 再采纳 → v3+重绑 → solve optimal(持续进化证据+零设备写入)
16
+ * M5 管理者面 REST:本体版本/四段 diff、Profile 版本、孪生谱系、审计三态留痕
17
+ * M6 前端渲染走查(管理者查看:/optimizer /aml /ontology)
18
+ *
19
+ * 运行:node scripts/e2e-model-loop-live.mjs
20
+ * 证据:docs/audit/evidence/2026-10-24-model-loop-live/{report.json,*.png}
21
+ */
22
+ import { spawn } from 'node:child_process'
23
+ import { mkdirSync, writeFileSync, existsSync, symlinkSync, openSync, readFileSync } from 'node:fs'
24
+ import { join, resolve, dirname } from 'node:path'
25
+ import { fileURLToPath } from 'node:url'
26
+ import { tmpdir, homedir } from 'node:os'
27
+ import { DatabaseSync } from 'node:sqlite'
28
+
29
+ const REPO = resolve(dirname(fileURLToPath(import.meta.url)), '..')
30
+ const ROOT = join(tmpdir(), `aw-model-loop-live-${Date.now()}`)
31
+ const HOME = join(ROOT, 'home')
32
+ const EVID = resolve(process.env.AW_E2E_EVIDENCE_DIR || join(REPO, 'docs', 'audit', 'evidence', '2026-10-24-model-loop-live'))
33
+ mkdirSync(HOME, { recursive: true })
34
+ mkdirSync(EVID, { recursive: true })
35
+
36
+ const TAG = `mlp${Date.now().toString(36).slice(-4)}`
37
+ const BEAT = 5000
38
+ const H = 24
39
+ const F = 6
40
+ const N_STEPS = 160
41
+ const N_RUNS = 6
42
+ const DELAY_STEPS = 2
43
+
44
+ process.env.AW_MODE = 'home'
45
+ process.env.AW_HOME = HOME
46
+ process.env.AW_AML_DIR = join(HOME, 'aml')
47
+ process.env.NO_PROXY = '127.0.0.1,localhost'
48
+
49
+ mkdirSync(join(HOME, 'aml'), { recursive: true })
50
+ const realVenv = join(homedir(), '.AgentWorkShop', 'aml', '.venv')
51
+ let venvLinked = false
52
+ if (existsSync(join(realVenv, 'Scripts', 'python.exe'))) {
53
+ symlinkSync(realVenv, join(HOME, 'aml', '.venv'), 'junction')
54
+ venvLinked = true
55
+ }
56
+
57
+ const results = []
58
+ function check(id, name, cond, detail = '') {
59
+ const pass = Boolean(cond)
60
+ results.push({ id, name, pass, detail: String(detail).slice(0, 600) })
61
+ console.log(`${pass ? '✔' : '✘'} [${id}] ${name}${detail ? ` — ${String(detail).slice(0, 200)}` : ''}`)
62
+ return pass
63
+ }
64
+ const sleep = ms => new Promise(r => setTimeout(r, ms))
65
+ function killTree(pid) {
66
+ try {
67
+ spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' })
68
+ }
69
+ catch { /* 进程已退 */ }
70
+ }
71
+
72
+ const { getDcwLineRepo } = await import('../server/services/workshop/dcw/dcw-line.repo')
73
+ const { getDcwProductRepo } = await import('../server/services/workshop/dcw/dcw-product.repo')
74
+ const { getDcwRecipeRepo } = await import('../server/services/workshop/dcw/dcw-recipe.repo')
75
+ const { getDcwNodeRepo } = await import('../server/services/workshop/dcw/dcw-node.repo')
76
+ const { DcwNode } = await import('../server/services/workshop/dcw/dcw-node')
77
+ const { getDaqNodeRepo } = await import('../server/services/workshop/daq/daq-node.repo')
78
+ const { DaqNode } = await import('../server/services/workshop/daq/daq-node')
79
+ const { SqliteTimeSeriesAdapter } = await import('../server/services/workshop/daq/storage/sqlite.adapter')
80
+ const { getContextSegmentRepo, recipeContentHash } = await import('../server/services/workshop/process-model/context-segments.repo')
81
+ const { openWorkshopDb } = await import('../server/services/workshop/db/database/open')
82
+ const { configureAmlRuntime } = await import('../server/services/workshop/aml/runtime')
83
+ const { bindOpsRepos } = await import('../server/services/workshop/ops/ops')
84
+ const { createAuditRepo } = await import('../server/services/workshop/db/ops.repo')
85
+
86
+ const seedDb = openWorkshopDb(join(HOME, 'data', 'workshop.sqlite'))
87
+ configureAmlRuntime(seedDb)
88
+ try {
89
+ bindOpsRepos({ audit: createAuditRepo(seedDb) })
90
+ }
91
+ catch { /* 重复装配无害 */ }
92
+
93
+ const line = getDcwLineRepo().create({ name: `模型闭环验收线-${TAG}`, simulated: true })
94
+ const product = getDcwProductRepo().create({ name: `模型闭环验收产品-${TAG}`, lineId: line.id })
95
+ getDcwNodeRepo().insert(new DcwNode({
96
+ id: `dcw-${TAG}-press`, templateRef: 'dcw-temp-sp', driver: 'mock',
97
+ name: `熔体压力控制-${TAG}`, lineId: line.id, min: 0, max: 100, unit: 'bar',
98
+ }))
99
+ getDcwNodeRepo().insert(new DcwNode({
100
+ id: `dcw-${TAG}-speed`, templateRef: 'dcw-temp-sp', driver: 'mock',
101
+ name: `线速度控制-${TAG}`, lineId: line.id, min: 0, max: 50, unit: 'm/min',
102
+ }))
103
+ const DCW_PRESS = `dcw-${TAG}-press`
104
+ const DCW_SPEED = `dcw-${TAG}-speed`
105
+ for (const id of [DCW_PRESS, DCW_SPEED]) {
106
+ const node = getDcwNodeRepo().byId(id)
107
+ if (node) {
108
+ node.readValue = id === DCW_PRESS ? 50 : 10
109
+ getDcwNodeRepo().flushNow()
110
+ }
111
+ }
112
+ getDaqNodeRepo().insert(DaqNode.fromRow({
113
+ id: `dn-${TAG}-mv`, templateRef: 'tpl-loop-mv', name: `熔体压力-${TAG}`, unit: 'bar',
114
+ min: 0, max: 100, lineId: line.id, driver: 'mock',
115
+ }))
116
+ getDaqNodeRepo().insert(DaqNode.fromRow({
117
+ id: `dn-${TAG}-cv`, templateRef: 'tpl-loop-cv', name: `膜厚度-${TAG}`, unit: 'μm',
118
+ min: 0, max: 200, lineId: line.id, driver: 'mock',
119
+ }))
120
+ getDaqNodeRepo().insert(DaqNode.fromRow({
121
+ id: `dn-${TAG}-temp`, templateRef: 'tpl-loop-temp', name: `温区温度-${TAG}`, unit: '℃',
122
+ min: 0, max: 400, lineId: line.id, driver: 'mock',
123
+ }))
124
+ const MV_ID = `dn-${TAG}-mv`
125
+ const CV_ID = `dn-${TAG}-cv`
126
+ const TEMP_ID = `dn-${TAG}-temp`
127
+ const recipe = getDcwRecipeRepo().create({
128
+ name: `模型闭环验收配方-${TAG}`, productId: product.id,
129
+ params: [
130
+ { nodeId: DCW_PRESS, value: 50, min: 0, max: 100 },
131
+ { nodeId: DCW_SPEED, value: 10, min: 0, max: 50 },
132
+ ],
133
+ daqWindows: [{ nodeId: MV_ID, min: 0, max: 100 }],
134
+ })
135
+
136
+ const now0 = Date.now()
137
+ const runStartBase = now0 - 3 * 3600_000
138
+ const sampleRows = []
139
+ const segRepo = getContextSegmentRepo()
140
+ const seedHash = recipeContentHash(recipe)
141
+ for (let r = 0; r < N_RUNS; r++) {
142
+ const start = runStartBase + r * (N_STEPS * BEAT + 120_000)
143
+ const end = start + (N_STEPS - 1) * BEAT
144
+ const run = getDcwRecipeRepo().createRun(recipe)
145
+ run.startedAt = new Date(start).toISOString()
146
+ run.endedAt = new Date(end).toISOString()
147
+ getDcwRecipeRepo().updateRun(run)
148
+ segRepo.insert({ lineId: line.id, runId: run.id, fromTs: start, toTs: end, recipeId: recipe.id, recipeVersion: recipe.version ?? 1, recipeContentHash: seedHash, conditionId: 'c-live', source: 'line_run', confirmed: true })
149
+ let y = 52
150
+ const uq = []
151
+ for (let k = 0; k < N_STEPS; k++) {
152
+ const u = 50 + 20 * Math.sin((k + r * 11) / 9) + 6 * Math.sin((k + r * 3) / 4)
153
+ uq.push(u)
154
+ const delayed = uq[Math.max(0, uq.length - 1 - DELAY_STEPS)]
155
+ y = 0.3 * y + 0.7 * delayed + 8 + 0.02 * Math.sin((k + r) / 3)
156
+ const ts = start + k * BEAT
157
+ const tags = { lineId: line.id, productId: product.id, recipeId: recipe.id, runId: run.id }
158
+ sampleRows.push({ nodeId: MV_ID, tsMs: ts, value: u, state: 'ok', ...tags })
159
+ sampleRows.push({ nodeId: CV_ID, tsMs: ts, value: y, state: 'ok', ...tags })
160
+ sampleRows.push({ nodeId: TEMP_ID, tsMs: ts, value: 190 + 8 * Math.sin((k + r) / 6), state: 'ok', ...tags })
161
+ }
162
+ }
163
+ const seedTsdb = new SqliteTimeSeriesAdapter()
164
+ await seedTsdb.init()
165
+ await seedTsdb.writeSamples(sampleRows)
166
+ await seedTsdb.close()
167
+ console.log(`seed: line=${line.id} recipe=${recipe.id} runs=${N_RUNS} samples=${sampleRows.length}`)
168
+ console.log(`venv: ${venvLinked ? 'junction ok' : '未复用(训练将失败,先检查)'}`)
169
+ if (!venvLinked) console.warn('⚠ 未找到真实 python venv,训练轮会失败')
170
+
171
+ function insertSamples(rows) {
172
+ if (!rows.length) return
173
+ const db = new DatabaseSync(join(HOME, 'data', 'daq-timeseries.sqlite'))
174
+ db.exec('PRAGMA busy_timeout=8000')
175
+ const ins = db.prepare(`INSERT OR IGNORE INTO daq_samples
176
+ (node_id, ts_ms, value, state, line_id, product_id, recipe_id, run_id, ingested_at)
177
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`)
178
+ db.exec('BEGIN')
179
+ for (const r of rows) ins.run(r.nodeId, r.tsMs, r.value, r.state ?? 'ok', r.lineId ?? null, r.productId ?? null, r.recipeId ?? null, r.runId ?? null, r.ingestedAt ?? Date.now())
180
+ db.exec('COMMIT')
181
+ db.close()
182
+ }
183
+ function writeFreshTail() {
184
+ // context 窗口 = H*beat + maxDelay + 5*beat;尾巴必须覆盖整窗,否则网格 NaN
185
+ const now = Date.now()
186
+ const rows = []
187
+ const span = Math.ceil(H + DELAY_STEPS + 6)
188
+ for (let k = span; k >= 1; k--) {
189
+ const ts = now - k * BEAT
190
+ const tags = { lineId: line.id, productId: product.id, recipeId: recipe.id }
191
+ const mv = 50 + 6 * Math.sin(k / 5)
192
+ rows.push({ nodeId: MV_ID, tsMs: ts, value: mv, state: 'ok', ...tags })
193
+ rows.push({ nodeId: CV_ID, tsMs: ts, value: 50 + 0.7 * (mv - 50) + 0.05 * Math.sin(k / 3), state: 'ok', ...tags })
194
+ rows.push({ nodeId: TEMP_ID, tsMs: ts, value: 195, state: 'ok', ...tags })
195
+ }
196
+ insertSamples(rows)
197
+ }
198
+
199
+ // ════ 启动隔离实例 ════
200
+ // 强制 SQLite 时序(宿主 Timescale 在线时插件会切走,看不到预置样本);MQTT 指向关闭端口。
201
+ const { saveSettings } = await import('../shared/config/engine.mjs')
202
+ saveSettings({
203
+ 'daq.timescale.host': '127.0.0.1',
204
+ 'daq.timescale.port': 6543,
205
+ 'daq.mqtt.host': '127.0.0.1',
206
+ 'daq.mqtt.port': 18830,
207
+ 'daq.startInfrastructure': 'never',
208
+ }, join(HOME, 'runtime-settings.json'))
209
+ let port = 0
210
+ for (const p of [3997, 3996, 3995, 3994, 3993]) {
211
+ let occupied = false
212
+ try {
213
+ await fetch(`http://127.0.0.1:${p}/api/health`, { signal: AbortSignal.timeout(800) })
214
+ occupied = true
215
+ }
216
+ catch { /* 空闲 */ }
217
+ if (!occupied) {
218
+ const { execSync } = await import('node:child_process')
219
+ try {
220
+ const out = execSync(`netstat -ano | findstr :${p} | findstr LISTENING`, { shell: 'cmd.exe' }).toString()
221
+ const pidMatch = out.match(/\s(\d+)\s*$/m)
222
+ if (pidMatch) { spawn('taskkill', ['/PID', pidMatch[1], '/T', '/F'], { stdio: 'ignore' }); await sleep(1500) }
223
+ }
224
+ catch { /* 无监听 */ }
225
+ port = p
226
+ break
227
+ }
228
+ await sleep(300)
229
+ }
230
+ if (!port) throw new Error('无空闲端口')
231
+ const BASE = `http://127.0.0.1:${port}`
232
+ console.log(`\n=== boot ${BASE} ===`)
233
+ const logFd = openSync(join(ROOT, 'dev.log'), 'a')
234
+ const child = spawn(process.execPath, [join(REPO, 'bin', 'aw.mjs'), 'dev', '--port', String(port)], {
235
+ cwd: REPO,
236
+ env: { ...process.env, AW_RATE_LIMIT_OFF: '1', NUXT_SESSION_PASSWORD: 'ModelLoopLive#2026-e2e-session', NO_PROXY: '127.0.0.1,localhost' },
237
+ stdio: ['ignore', logFd, logFd],
238
+ windowsHide: true,
239
+ })
240
+
241
+ async function api(method, path, body, token, timeoutMs = 180_000) {
242
+ const res = await fetch(BASE + path, {
243
+ method,
244
+ headers: { 'content-type': 'application/json', ...(token ? { authorization: `Bearer ${token}` } : {}) },
245
+ body: body === undefined ? undefined : JSON.stringify(body),
246
+ signal: AbortSignal.timeout(timeoutMs),
247
+ })
248
+ let json = null
249
+ try {
250
+ json = await res.json()
251
+ }
252
+ catch { /* 非 JSON */ }
253
+ return { status: res.status, ...(json ?? {}) }
254
+ }
255
+ const data = r => r?.data
256
+ const errCode = r => r?.code ?? r?.data?.code ?? ''
257
+
258
+ let healthy = false
259
+ for (let i = 0; i < 120; i++) {
260
+ await sleep(1500)
261
+ try {
262
+ const h = await api('GET', '/api/health', undefined, undefined, 3000)
263
+ if (h?.data?.status === 'ok' && h?.data?.workshop === 'ready') { healthy = true; break }
264
+ }
265
+ catch { /* 未就绪 */ }
266
+ }
267
+ check('BOOT', '隔离实例启动 + workshop ready', healthy, `port=${port}`)
268
+ if (!healthy) { killTree(child.pid); throw new Error('实例未就绪:' + join(ROOT, 'dev.log')) }
269
+
270
+ const email = `mlp-${TAG}@awshop.local`
271
+ let reg = null
272
+ for (let attempt = 0; attempt < 3 && !data(reg ?? {})?.token; attempt++) {
273
+ try {
274
+ reg = await api('POST', '/api/users/register', { name: `模型闭环验收-${TAG}`, email, password: 'ModelLoop#2026' })
275
+ }
276
+ catch (err) {
277
+ console.log(`register attempt ${attempt + 1} failed: ${String(err).slice(0, 120)}`)
278
+ await sleep(3000)
279
+ }
280
+ }
281
+ const token = data(reg ?? {})?.token
282
+ check('BOOT', '管理员注册(首注册=admin)', Boolean(token), `status=${reg.status}`)
283
+ if (!token) { killTree(child.pid); throw new Error('注册失败') }
284
+
285
+ // ════ M0 hybrid_twin 全功能频道 + 绑定三面 ════
286
+ console.log('\n=== M0 频道与三面绑定 ===')
287
+ const ONT = `ont-${TAG}`
288
+ const SPEC = `pms-${TAG}`
289
+ const profileId = `optp-${TAG}`
290
+ const SCENE_ID = `scn-${TAG}`
291
+ let channelIdOuter
292
+ let leadAgentIdOuter
293
+ let workspaceIdOuter
294
+ let bindingId
295
+ let trainedAId
296
+ let trainedBId
297
+ let trainedCId
298
+ let trainedAHash
299
+ let trainedBHash
300
+ let trainedCHash
301
+ let spec
302
+ {
303
+ const inst = await api('POST', '/api/workshop/channel-templates/chtpl-hybrid-twin-mpc-default/instantiate', { name: `模型闭环频道-${TAG}`, bindLineId: line.id }, token)
304
+ const instData = data(inst) ?? {}
305
+ workspaceIdOuter = data(await api('POST', '/api/workshop/workspaces', { name: `模型闭环工作区-${TAG}` }, token))?.id
306
+ channelIdOuter = instData.channelId
307
+ leadAgentIdOuter = instData.leadAgentId ?? (instData.agents ?? []).find(a => a.role === 'lead')?.id
308
+ if (workspaceIdOuter && channelIdOuter) await api('POST', `/api/workshop/workspaces/${encodeURIComponent(workspaceIdOuter)}/channels/${encodeURIComponent(channelIdOuter)}`, undefined, token)
309
+ check('M0.1', 'hybrid_twin 频道实例化(全功能档位)+workspace', Boolean(workspaceIdOuter && channelIdOuter && leadAgentIdOuter), `inst=${inst.status} ch=${channelIdOuter} lead=${leadAgentIdOuter}`)
310
+
311
+ // 工具面断言(进程内同判:scopeHostToolsForRole 与线上装配同一函数)
312
+ try {
313
+ const { scopeHostToolsForRole } = await import('../server/services/workshop/agents/host-tool-bridge/catalog')
314
+ const scope = scopeHostToolsForRole('lead', channelIdOuter)
315
+ const names = new Set(scope.tools.map(t => t.name))
316
+ const need = ['twin_scene_discover', 'twin_scene_compile', 'twin_scene_freeze', 'twin_physics_spec_draft', 'twin_physics_spec_validate', 'twin_physics_spec_compile', 'twin_snapshot_create', 'twin_trial_run', 'aml_dataset_build', 'aml_job_submit', 'optimizer_solve', 'optimizer_profile_propose', 'ontology_draft_create']
317
+ const missing = need.filter(n => !names.has(n))
318
+ check('M0.2', 'hybrid 档位工具面:训练+孪生+优化器+本体四族同面注入', missing.length === 0, `missing=${missing.join(',')} total=${names.size}`)
319
+ }
320
+ catch (err) {
321
+ check('M0.2', 'hybrid 档位工具面(进程内装配)', false, `err=${String(err).slice(0, 160)}`)
322
+ }
323
+
324
+ // Agent 节点绑定:daq 读面(MV/CV/TEMP)+ 配方面(参数写面 → 场景控制面)
325
+ const bindNode = async (nodeId, kind) => api('POST', '/api/workshop/agent-tools/bindings', { agentId: leadAgentIdOuter, nodeId, kind }, token)
326
+ const b1 = await bindNode(MV_ID, 'daq')
327
+ const b2 = await bindNode(CV_ID, 'daq')
328
+ const b3 = await bindNode(TEMP_ID, 'daq')
329
+ const b4 = await bindNode(recipe.id, 'recipe')
330
+ check('M0.3', 'Agent 节点绑定:daq×3 + recipe(参数面)', [b1, b2, b3, b4].every(x => x.status === 200), `daq=${[b1.status, b2.status, b3.status]} recipe=${b4.status}/${errCode(b4)}`)
331
+
332
+ // 过程 spec 先登记发布(Profile 的 processSpecRef 谱系前置;Profile 本体在 M3 训练出模型后创建)
333
+ spec = {
334
+ schemaVersion: 2, id: SPEC, version: 1,
335
+ lineId: line.id, productId: product.id, recipeId: recipe.id, recipeVersion: recipe.version ?? 1,
336
+ conditions: [{ id: 'c1', match: [{ field: 'recipeId', op: 'eq', value: recipe.id }] }],
337
+ variables: [
338
+ { id: 'mv1', nodeId: MV_ID, role: 'mv', field: 'value', unit: 'bar', scale: 1, offset: 0, delayMs: DELAY_STEPS * BEAT, min: 0, max: 100, maxStep: 20, controlNodeId: DCW_PRESS },
339
+ { id: 'cv1', nodeId: CV_ID, role: 'cv', field: 'value', unit: 'μm', scale: 1, offset: 0, min: 0, max: 200 },
340
+ ],
341
+ timing: { beatMs: BEAT, historySteps: H, horizonSteps: F, fixedDt: true },
342
+ trainer: { ref: 'linear_arx@1', config: { arOrder: 1, inputOrder: 1, ridgeLambda: 1e-6, standardize: true } },
343
+ datasetPolicy: { split: { valRatio: 0.4, testRatio: 0.4, seed: 42 }, cleaning: { hampelK: 0 } },
344
+ gatePolicyRef: 'process-model.gates@1',
345
+ modelInterface: { id: 'linear_state_space', version: 1, inputSemantics: 'setpoint', mode: 'state_space' },
346
+ effectiveDomain: { mv: { mv1: { min: 0, max: 100, maxStep: 20 } }, cv: { cv1: { min: 0, max: 200 } } },
347
+ purpose: 'mpc_surrogate',
348
+ }
349
+ const specCreated = await api('POST', '/api/workshop/process-model/specs', spec, token)
350
+ const specPub = await api('POST', `/api/workshop/process-model/specs/${encodeURIComponent(SPEC)}/publish?version=1`, undefined, token)
351
+ check('M0.4', '过程 spec 登记+发布(求解器谱系前置)', specCreated.status === 200 && specPub.status === 200, `create=${specCreated.status}/${errCode(specCreated)} pub=${specPub.status}`)
352
+ }
353
+
354
+ // ════ M1 本体层:v1 发布 → 管理者修正 → v2 ════
355
+ console.log('\n=== M1 本体层(管理者修正路径) ===')
356
+ let draftId
357
+ const ontologyDoc = (overrides = {}) => ({
358
+ schemaVersion: 1,
359
+ name: `模型闭环验收-${TAG}`,
360
+ lineId: line.id,
361
+ entities: [
362
+ { key: 'pv.melt_pressure', kind: 'process_variable', name: '熔体压力', description: '', unit: 'bar', dimension: 'pressure', role: 'control', attrs: { dataType: 'number' } },
363
+ { key: 'cv.thickness', kind: 'quality_variable', name: '平均膜厚', description: '', unit: 'μm', dimension: 'length', role: 'target', attrs: { dataType: 'number' } },
364
+ { key: 'st.line_speed', kind: 'process_variable', name: '线速度', description: '', unit: 'm/min', dimension: '', role: 'control', attrs: { dataType: 'number' } },
365
+ { key: 'st.zone_temp', kind: 'process_variable', name: '温区温度', description: '', unit: '℃', dimension: 'temperature', role: 'state', attrs: { dataType: 'number' } },
366
+ ],
367
+ relations: [
368
+ {
369
+ id: 'rel-1', kind: 'influences', srcKey: 'pv.melt_pressure', dstKey: 'cv.thickness', status: 'validated',
370
+ evidence: [{ id: 'ev-rel', level: 'validated_control', source: { type: 'optimizer_run', runId: 'run-live', problemIrHash: 'ir-live', modelArtifactHash: 'mdl-live' }, sourceRefHash: 'h', createdAt: new Date().toISOString() }],
371
+ properties: { sign: 'negative', gainPerUnit: -0.4, lagMs: 2 * DELAY_STEPS * BEAT, evidenceTier: 'validated_control', variabilityChecked: true },
372
+ },
373
+ ],
374
+ rules: [
375
+ {
376
+ id: 'R-BOUND', key: 'thickness-window', kind: 'bound',
377
+ dsl: { type: 'bound', variableKey: 'cv.thickness', min: 45, max: 55, unit: 'μm' },
378
+ constraintLevel: 'hard', severity: 'error', status: 'proposed',
379
+ evidence: [{ id: 'ev-bound', level: 'validated_control', source: { type: 'optimizer_run', runId: 'run-b', problemIrHash: 'ir', modelArtifactHash: 'mdl' }, sourceRefHash: 'h', createdAt: new Date().toISOString() }],
380
+ description: '膜厚工艺窗(μm)', scope: { lineId: line.id, recipeId: recipe.id, conditionId: '' },
381
+ },
382
+ {
383
+ id: 'R-GUARD', key: 'cold-zone-no-speedup', kind: 'state_condition',
384
+ dsl: { type: 'state_condition', when: { variableKey: 'st.zone_temp', op: 'lt', value: 180 }, forbid: { kind: 'positive_increment', variableKey: 'st.line_speed' }, message: '温区未达 180℃,禁止提速' },
385
+ constraintLevel: 'hard', severity: 'error', status: 'proposed',
386
+ evidence: [{ id: 'ev-guard', level: 'validated_control', source: { type: 'optimizer_run', runId: 'run-g', problemIrHash: 'ir', modelArtifactHash: 'mdl' }, sourceRefHash: 'h', createdAt: new Date().toISOString() }],
387
+ description: '冷区禁提速', scope: { lineId: line.id, recipeId: recipe.id, conditionId: '' },
388
+ },
389
+ ],
390
+ bindings: [
391
+ { id: 'b-press', entityKey: 'pv.melt_pressure', platformKind: 'dcw_node', platformId: DCW_PRESS, targetSub: '', scope: `line:${line.id}`, direction: 'write', unitDeclared: 'bar', unitTarget: 'bar', scale: 1, offset: 0 },
392
+ { id: 'b-speed', entityKey: 'st.line_speed', platformKind: 'dcw_node', platformId: DCW_SPEED, targetSub: '', scope: `line:${line.id}`, direction: 'write', unitDeclared: 'm/min', unitTarget: 'm/min', scale: 1, offset: 0 },
393
+ { id: 'b-cv', entityKey: 'cv.thickness', platformKind: 'daq_node', platformId: CV_ID, targetSub: '', scope: `line:${line.id}`, direction: 'read', unitDeclared: 'μm', unitTarget: 'μm', scale: 1, offset: 0 },
394
+ { id: 'b-temp', entityKey: 'st.zone_temp', platformKind: 'daq_node', platformId: TEMP_ID, targetSub: '', scope: `line:${line.id}`, direction: 'read', unitDeclared: '℃', unitTarget: '℃', scale: 1, offset: 0 },
395
+ ],
396
+ note: 'model-loop live e2e',
397
+ ...overrides,
398
+ })
399
+ async function approveRule(draft, ruleId) {
400
+ await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draft)}/rules/${ruleId}/transition`, { to: 'reviewed' }, token)
401
+ await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draft)}/rules/${ruleId}/transition`, { to: 'validated' }, token)
402
+ const ap = await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draft)}/rules/${ruleId}/approval`, {}, token)
403
+ const apId = data(ap)?.approvalId ?? ''
404
+ await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draft)}/rules/${ruleId}/approval-decision`, { approvalId: apId, approved: true }, token)
405
+ return api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draft)}/rules/${ruleId}/transition`, { to: 'approved_for_control', approvalId: apId }, token)
406
+ }
407
+ {
408
+ const created = await api('POST', '/api/workshop/ontology', { ontologyId: ONT, lineId: line.id, name: `模型闭环验收-${TAG}` }, token)
409
+ draftId = data(created)?.draft?.id ?? ''
410
+ const saved = await api('PATCH', `/api/workshop/ontology/drafts/${encodeURIComponent(draftId)}`, { doc: ontologyDoc(), expectedRev: 1 }, token)
411
+ check('M1.1', '本体起草(活体校验+绑定通过)', created.status === 200 && saved.status === 200 && data(saved)?.validation?.ok === true, `saved=${saved.status} issues=${JSON.stringify((data(saved)?.validation?.issues ?? []).filter(i => i.severity === 'error').slice(0, 2))}`)
412
+ await approveRule(draftId, 'R-BOUND')
413
+ await approveRule(draftId, 'R-GUARD')
414
+ const pub1 = await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draftId)}/publish`, {}, token)
415
+ check('M1.2', '本体 v1 发布(contentHash+packHash)', pub1.status === 200 && data(pub1)?.version?.version === 1, `${pub1.status}/${errCode(pub1)}`)
416
+
417
+ // 管理者修正:复用草稿(发布不改 rev,草稿仍可编辑;重复发布=新版本不可变语义),
418
+ // 把膜厚窗从 [45,55] 收到 [46,54],重走生命周期后发布 v2
419
+ const detail = data(await api('GET', `/api/workshop/ontology/drafts/${encodeURIComponent(draftId)}`, undefined, token))
420
+ const curRev = detail?.draft?.rev ?? 2
421
+ const doc2 = ontologyDoc()
422
+ doc2.rules[0].dsl.min = 46
423
+ doc2.rules[0].dsl.max = 54
424
+ doc2.rules[0].description = '膜厚工艺窗(管理者修正:收窄到 46-54μm)'
425
+ const saved2 = await api('PATCH', `/api/workshop/ontology/drafts/${encodeURIComponent(draftId)}`, { doc: doc2, expectedRev: curRev }, token)
426
+ await approveRule(draftId, 'R-BOUND')
427
+ await approveRule(draftId, 'R-GUARD')
428
+ const pub2 = await api('POST', `/api/workshop/ontology/drafts/${encodeURIComponent(draftId)}/publish`, {}, token)
429
+ check('M1.3', '管理者修正规则窗 → v2 发布', pub2.status === 200 && data(pub2)?.version?.version === 2, `saved=${saved2.status}/${errCode(saved2)} pub=${pub2.status}/${errCode(pub2)}`)
430
+ const cmp = await api('GET', `/api/workshop/ontology/versions/${encodeURIComponent(ONT)}/compare?from=1&to=2`, undefined, token)
431
+ const changed = data(cmp)?.rules?.changed ?? []
432
+ check('M1.4', '四段 diff 反映管理者意志(R-BOUND changed)', cmp.status === 200 && changed.some(c => String(typeof c === 'string' ? c : (c.id ?? c.ruleId ?? '')).includes('R-BOUND')), JSON.stringify(changed).slice(0, 200))
433
+ }
434
+
435
+ // ════ M2 孪生层:Agent 工具创建虚拟模型全链 ════
436
+ console.log('\n=== M2 孪生层(Agent 工具创建虚拟模型) ===')
437
+ async function invokeTool(tool, args, timeoutMs = 180_000) {
438
+ return api('POST', '/api/workshop/agent-tools/invoke', { agentId: leadAgentIdOuter, tool, args }, token, timeoutMs)
439
+ }
440
+ const toolText = r => String(r?.data?.text ?? r?.data?.result?.text ?? r?.text ?? r?.message ?? JSON.stringify(r ?? {}).slice(0, 300))
441
+ const toolErr = r => Boolean(r?.isError ?? r?.data?.isError)
442
+ const artifactPathOf = (text) => {
443
+ const m = String(text).match(/artifact:\s*(\S+)/)
444
+ return m ? m[1] : ''
445
+ }
446
+ {
447
+ const disc = await invokeTool('twin_scene_discover', {})
448
+ check('M2.1', '工具 twin_scene_discover:从真实绑定发现场景', disc.status === 200 && !toolErr(disc) && toolText(disc).includes('nodeCount'), toolText(disc).slice(0, 160))
449
+
450
+ const comp = await invokeTool('twin_scene_compile', { scene_id: SCENE_ID, scene_version: '1.0.0', line_id: line.id, product_id: product.id, recipe_id: recipe.id, prompt: '膜厚闭环验收场景' })
451
+ const compText = toolText(comp)
452
+ check('M2.2', '工具 twin_scene_compile:SceneContract 草稿(controls+artifact)', comp.status === 200 && !toolErr(comp) && compText.includes('SceneContract Draft'), compText.slice(0, 200))
453
+
454
+ const { createTwinRepo } = await import('../server/services/workshop/aml/twin/repo')
455
+ const twinRepo = createTwinRepo(seedDb)
456
+ const sceneRow = twinRepo.getScene(SCENE_ID, '1.0.0')
457
+ const sceneJson = sceneRow ? JSON.parse(sceneRow.contractJson) : null
458
+ check('M2.3', '场景草稿持久化(控制面+目标面齐备,谱系可复用)', Boolean(sceneJson) && (sceneJson.controls ?? []).length >= 2 && (sceneJson.observations ?? []).some(v => v.role === 'target'), `controls=${sceneJson?.controls?.length} states=${sceneJson?.states?.length} obs=${sceneJson?.observations?.length}(targets=${(sceneJson?.observations ?? []).filter(v => v.role === 'target').length})`)
459
+
460
+ const frz = await invokeTool('twin_scene_freeze', { scene_id: SCENE_ID, scene_version: '1.0.0', approved_by: email, confirmation: 'USER_CONFIRMED_SCENE_CONTRACT' })
461
+ check('M2.4', '工具 twin_scene_freeze:人批准冻结(approvedBy=管理者)', frz.status === 200 && !toolErr(frz) && toolText(frz).includes('"status": "frozen"'), toolText(frz).slice(0, 200))
462
+ const noConfirm = await invokeTool('twin_scene_freeze', { scene_id: `${SCENE_ID}-x`, scene_version: '1.0.0' })
463
+ check('M2.5', '冻结治理红线:缺确认令牌 → 拒绝', toolErr(noConfirm) || toolText(noConfirm).includes('冻结被拒绝'), toolText(noConfirm).slice(0, 120))
464
+
465
+ const frozenRow = twinRepo.getScene(SCENE_ID, '1.0.0')
466
+ const frozenScene = JSON.parse(frozenRow.contractJson)
467
+
468
+ const draft = await invokeTool('twin_physics_spec_draft', { scene_json: frozenScene, dt_sec: BEAT / 1000 })
469
+ const draftArtifact = artifactPathOf(toolText(draft))
470
+ const physSpec = draftArtifact && existsSync(draftArtifact) ? JSON.parse(readFileSync(draftArtifact, 'utf8')) : null
471
+ check('M2.6', '工具 twin_physics_spec_draft:虚拟模型骨架(状态+参数可校准)', draft.status === 200 && !toolErr(draft) && Boolean(physSpec) && (physSpec.states ?? []).length >= 1 && (physSpec.parameters ?? []).length >= 1, toolText(draft).slice(0, 200))
472
+
473
+ const val = await invokeTool('twin_physics_spec_validate', { physics_spec: physSpec })
474
+ const valData = data(val) ?? {}
475
+ const valBody = valData.apiVersion ? valData : (val.data ?? {})
476
+ check('M2.7', '工具 twin_physics_spec_validate:valid=true', val.status === 200 && !toolErr(val) && (valBody.valid === true || String(toolText(val)).includes('"valid": true')), String(toolText(val)).slice(0, 160))
477
+
478
+ const compiled = await invokeTool('twin_physics_spec_compile', { physics_spec: physSpec, scene_json: frozenScene })
479
+ const compBody = data(compiled) ?? {}
480
+ check('M2.8', '工具 twin_physics_spec_compile:候选 Provider(candidate_not_registered)', compiled.status === 200 && !toolErr(compiled) && (compBody.status === 'candidate_not_registered' || String(toolText(compiled)).includes('candidate_not_registered')), String(toolText(compiled)).slice(0, 160))
481
+
482
+ // 频道谱系重链到真实冻结场景(训练通道冻结、优化通道复用的平台语义;走平台自有服务)。
483
+ // providerId 保留已注册的 injection-greybox-v1:声明式 Provider 仅为 candidate(工具明确
484
+ // 不直接注册投产),物理 rollout 由注入 greybox 承担;其观测 id 与本场景不映射 →
485
+ // 试验约束 fail-closed —— 正是「未注册 provider 不许投产」的治理语义,一并断言。
486
+ const { setHybridChannelProfile, getChannelTwinProfile } = await import('../server/services/workshop/aml/twin/channel-profile')
487
+ const prevProfile = getChannelTwinProfile(channelIdOuter)
488
+ setHybridChannelProfile({
489
+ ...prevProfile,
490
+ sceneId: SCENE_ID,
491
+ sceneVersion: '1.0.0',
492
+ sceneContract: frozenScene,
493
+ scenePackId: SCENE_ID,
494
+ providerId: 'injection-greybox-v1',
495
+ providerVersion: undefined,
496
+ providerHash: undefined,
497
+ createdBy: 'e2e',
498
+ })
499
+ const profAfter = data(await api('GET', `/api/workshop/channels/${encodeURIComponent(channelIdOuter)}/twin-profile`, undefined, token))
500
+ check('M2.9', '频道场景谱系重链(REST 可读)', profAfter?.profile?.sceneId === SCENE_ID && profAfter?.profile?.profile === 'hybrid_twin', `sceneId=${profAfter?.profile?.sceneId}`)
501
+
502
+ writeFreshTail()
503
+ const snap = await invokeTool('twin_snapshot_create', { channel_id: channelIdOuter, auto_daq: true, phase: 'exploration' }, 120_000)
504
+ const snapId = (toolText(snap).match(/snapshot_id:\s*(\S+)/) ?? [])[1] ?? ''
505
+ check('M2.10', '工具 twin_snapshot_create:服务端真采快照(auto_daq,fresh)', snap.status === 200 && !toolErr(snap) && Boolean(snapId) && toolText(snap).includes('fresh: true'), toolText(snap).slice(0, 200))
506
+
507
+ const targetVars = (frozenScene.observations ?? []).filter(v => v.role === 'target')
508
+ const targetId = targetVars[0]?.id ?? (frozenScene.observations ?? [])[0]?.id ?? 'cv'
509
+ const controlIds = (frozenScene.controls ?? []).map(c => c.id)
510
+ const candidate = {}
511
+ if (controlIds[0]) candidate[controlIds[0]] = 60
512
+ const objective = {
513
+ schemaVersion: 1, createdAt: new Date().toISOString(), createdBy: leadAgentIdOuter, objectiveId: 'loop-e2e',
514
+ targets: { [targetId]: 50 }, weights: { [targetId]: 1 }, controlCosts: {}, horizonSteps: 4, trustRegion: {},
515
+ }
516
+ const trial = await invokeTool('twin_trial_run', { snapshot_id: snapId, objective, candidate_controls: [candidate] }, 120_000)
517
+ const trialText = toolText(trial)
518
+ check('M2.11', '工具 twin_trial_run:虚拟试验完成(模型驱动 rollout)', trial.status === 200 && !toolErr(trial) && trialText.includes('VirtualTrial 已完成'), trialText.slice(0, 220))
519
+ check('M2.12', '虚拟试验治理语义:candidateExecuted=false + 约束 fail-closed(候选 provider 未注册不投产)', trialText.includes('candidateExecuted=false') && trialText.includes('constraints_passed: false'), trialText.split('\n').filter(Boolean).slice(0, 4).join(' | '))
520
+ }
521
+
522
+ // ════ M3/M4 模型进化两轮(训练 → 工具采纳 → HITL → 发布重绑 → 新模型求解) ════
523
+ console.log('\n=== M3 模型进化第 1 轮 ===')
524
+ async function trainOnce(modelName) {
525
+ let ds = await api('POST', '/api/workshop/process-model/datasets', spec, token)
526
+ for (const backoff of [3000, 6000]) {
527
+ if (ds.status === 200) break
528
+ writeFreshTail()
529
+ await sleep(backoff)
530
+ ds = await api('POST', '/api/workshop/process-model/datasets', spec, token)
531
+ }
532
+ const datasetId = data(ds)?.datasetId
533
+ // 优化资格报告(solve 前置:模型须经资格评估,缺报告 → solve 422 不具备优化资格)
534
+ const elig = await api('POST', '/api/workshop/process-model/eligibility', { specId: SPEC, specVersion: 1, datasetId, scope: 'simulation' }, token)
535
+ const eligOk = data(elig)?.report?.optimizationEligible === true
536
+ const train = await api('POST', '/api/workshop/process-model/train', { spec, datasetId, modelName }, token)
537
+ const jobId = data(train)?.job?.id
538
+ let job = null
539
+ const terminal = new Set(['done', 'failed', 'cancelled', 'timeout', 'interrupted'])
540
+ for (let i = 0; i < 240; i++) {
541
+ const jr = await api('GET', `/api/workshop/aml/jobs/${encodeURIComponent(jobId)}`, undefined, token)
542
+ job = data(jr)?.job
543
+ if (job && terminal.has(job.status)) break
544
+ await sleep(500)
545
+ }
546
+ const models = await api('GET', `/api/workshop/aml/models?recipeId=${encodeURIComponent(recipe.id)}`, undefined, token)
547
+ const model = (data(models)?.models ?? []).find(m => m.id === (job?.modelId ?? ''))
548
+ return { datasetId, rowCount: data(ds)?.rowCount ?? 0, jobId, job, modelId: job?.modelId ?? '', artifactHash: model?.artifactHash ?? '', eligOk }
549
+ }
550
+ async function adoptRound(modelId, reason) {
551
+ const adoptInvoke = invokeTool('optimizer_profile_propose', { model_id: modelId, reason }, 300_000)
552
+ let card = null
553
+ for (let i = 0; i < 60; i++) {
554
+ await sleep(500)
555
+ try {
556
+ const pending = await api('GET', '/api/workshop/agent-tools/approvals', undefined, token)
557
+ card = (data(pending)?.approvals ?? []).find(a => String(a.nodeId).startsWith('optimizer-profile:') && a.agentId === leadAgentIdOuter)
558
+ if (card) break
559
+ }
560
+ catch { /* 未就绪 */ }
561
+ }
562
+ if (!card) {
563
+ const adoptText = await adoptInvoke.catch(err => ({ text: `invoke rejected: ${String(err).slice(0, 120)}` }))
564
+ return { card: null, adoptText, dec: { status: 0 } }
565
+ }
566
+ const dec = await api('POST', `/api/workshop/agent-tools/approvals/${encodeURIComponent(card.id)}/decide`, { approved: true, choice: 0, comment: '模型进化闭环验收批准' }, token)
567
+ let adoptText
568
+ try {
569
+ adoptText = await adoptInvoke
570
+ }
571
+ catch (err) {
572
+ adoptText = { text: `invoke rejected: ${String(err).slice(0, 160)}` }
573
+ }
574
+ return { card, adoptText, dec }
575
+ }
576
+ let round1RowCount
577
+ {
578
+ // ── 第 1 轮:训练 A → Profile v1(A) → 发布 → 绑定 → 基线求解 ──
579
+ const ra = await trainOnce(`模型闭环A-${TAG}`)
580
+ trainedAId = ra.modelId
581
+ trainedAHash = ra.artifactHash
582
+ round1RowCount = ra.rowCount
583
+ check('M3.1', '第 1 轮训练 done + 模型登记 + 优化资格(artifactHash)', ra.job?.status === 'done' && ra.eligOk && trainedAId && trainedAHash.length === 64, `status=${ra.job?.status} model=${trainedAId} rows=${ra.rowCount} err=${String(ra.job?.error ?? '').slice(0, 100)}`)
584
+
585
+ const profileBody = {
586
+ schemaVersion: 1, id: profileId, version: 1,
587
+ modelRef: { modelId: trainedAId, artifactKind: 'linear_state_space@1', artifactHash: trainedAHash, modelInterface: 'linear_state_space', stage: 'candidate' },
588
+ problemTemplateRef: 'linear_mpc_quadratic@1',
589
+ problemConfig: { linear_mpc_quadratic: { cvWeights: { cv1: 1 }, duWeights: { mv1: 0.1 }, terminalWeight: 2, targets: { cv1: 50 } } },
590
+ solverRef: 'osqp_mpc@1', solverVersion: '1',
591
+ processSpecRef: `${SPEC}@1`, conditionIds: [], status: 'draft',
592
+ }
593
+ const profCreated = await api('POST', '/api/workshop/optimization/profiles', profileBody, token)
594
+ const pub1 = await api('POST', `/api/workshop/optimization/profiles/${encodeURIComponent(profileId)}/publish?version=1`, undefined, token)
595
+ check('M3.2', 'Profile v1 创建(模型A 工件回填)+发布', profCreated.status === 200 && pub1.status === 200, `create=${profCreated.status}/${errCode(profCreated)} pub=${pub1.status}/${errCode(pub1)}`)
596
+ const bind = await api('POST', '/api/workshop/optimization/bindings', {
597
+ workspaceId: workspaceIdOuter, channelId: channelIdOuter, agentInstanceId: leadAgentIdOuter, profileId, profileVersion: 1, mode: 'recommendation',
598
+ }, token)
599
+ bindingId = data(bind)?.binding?.id ?? ''
600
+ check('M3.3', 'Agent↔Profile 绑定(revision=1,推荐模式)', bind.status === 200 && Boolean(bindingId), `id=${bindingId} code=${errCode(bind)}`)
601
+ writeFreshTail()
602
+ const solve0 = await api('POST', '/api/workshop/optimization/solve', { agentInstanceId: leadAgentIdOuter, bindingId }, token)
603
+ check('M3.4', '基线模型 A solve optimal', solve0.status === 200 && data(solve0)?.result?.status === 'optimal', `status=${data(solve0)?.result?.status} http=${solve0.status} msg=${String(solve0.message ?? '').slice(0, 120)}`)
604
+
605
+ // ── 第 2 轮:训练 B(反馈数据再积累)→ 工具采纳 → HITL → v2+重绑 → 新模型求解 ──
606
+ const rb = await trainOnce(`模型闭环B-${TAG}`)
607
+ trainedBId = rb.modelId
608
+ trainedBHash = rb.artifactHash
609
+ check('M3.5', '第 2 轮训练 done + 资格(数据集不缩水,工件不同)', rb.job?.status === 'done' && rb.eligOk && trainedBId && rb.rowCount >= round1RowCount && trainedBHash !== trainedAHash, `status=${rb.job?.status} rows ${round1RowCount}→${rb.rowCount} hashDiff=${trainedAHash !== trainedBHash}`)
610
+
611
+ const adopt1 = await adoptRound(trainedBId, '模型进化第 1 轮采纳:换上新训练模型 B')
612
+ check('M3.6', '采纳卡挂出(optimizer-profile:<id>@<v>)', Boolean(adopt1.card), `nodeId=${adopt1.card?.nodeId ?? 'none'}`)
613
+ check('M3.7', 'HITL 批准 → 单一咽喉发布+重绑', adopt1.dec.status === 200 && (JSON.stringify(adopt1.dec).includes('published') || JSON.stringify(adopt1.dec).includes('已发布')), JSON.stringify(adopt1.dec).slice(0, 300))
614
+ const adoptStr1 = String(adopt1.adoptText?.data?.text ?? adopt1.adoptText?.data?.result?.text ?? adopt1.adoptText?.text ?? JSON.stringify(adopt1.adoptText)).slice(0, 240)
615
+ check('M3.8', '工具回执:模型采纳完成(诚实汇报)', adoptStr1.includes('模型采纳完成'), adoptStr1)
616
+
617
+ const binds1 = (data(await api('GET', `/api/workshop/optimization/bindings?agentInstanceId=${encodeURIComponent(leadAgentIdOuter)}`, undefined, token))?.bindings ?? []).filter(b => b.profileId === profileId)
618
+ const cardV1 = Number(String(adopt1.card?.nodeId ?? '').split('@')[1])
619
+ const rebound1 = binds1.find(b => Number(b.profileVersionNumber) === cardV1)
620
+ check('M3.9', '绑定自动重绑到采纳版本(revision+1)', Boolean(rebound1) && Number(rebound1?.revision) >= 1, `versions=[${binds1.map(b => b.profileVersionNumber).join(',')}] card=v${cardV1} rev=${rebound1?.revision}`)
621
+ const profs1 = JSON.stringify(data(await api('GET', `/api/workshop/optimization/profiles?limit=50`, undefined, token)) ?? {})
622
+ check('M3.10', '已发布 Profile v2 携带新模型工件 B', profs1.includes(trainedBId), `len=${profs1.length}`)
623
+ writeFreshTail()
624
+ const solve1 = await api('POST', '/api/workshop/optimization/solve', { agentInstanceId: leadAgentIdOuter, bindingId: rebound1?.id }, token)
625
+ check('M3.11', '新模型 B solve optimal', solve1.status === 200 && data(solve1)?.result?.status === 'optimal', `status=${data(solve1)?.result?.status} http=${solve1.status} msg=${String(solve1.message ?? '').slice(0, 120)}`)
626
+ }
627
+
628
+ console.log('\n=== M4 模型进化第 2 轮(持续迭代) ===')
629
+ {
630
+ const rc = await trainOnce(`模型闭环C-${TAG}`)
631
+ trainedCId = rc.modelId
632
+ trainedCHash = rc.artifactHash
633
+ check('M4.1', '第 3 次训练 done + 资格(反馈持续积累)', rc.job?.status === 'done' && rc.eligOk && trainedCId && trainedCHash.length === 64, `status=${rc.job?.status} model=${trainedCId} rows=${rc.rowCount}`)
634
+
635
+ const adopt2 = await adoptRound(trainedCId, '模型进化第 2 轮采纳:持续迭代换新模型 C')
636
+ check('M4.2', '第 2 轮采纳卡挂出+HITL 批准', Boolean(adopt2.card) && adopt2.dec.status === 200, `nodeId=${adopt2.card?.nodeId ?? 'none'} dec=${adopt2.dec.status}`)
637
+ const adoptStr2 = String(adopt2.adoptText?.data?.text ?? adopt2.adoptText?.data?.result?.text ?? adopt2.adoptText?.text ?? JSON.stringify(adopt2.adoptText)).slice(0, 240)
638
+ check('M4.3', '第 2 轮工具回执:采纳完成', adoptStr2.includes('模型采纳完成'), adoptStr2)
639
+
640
+ const binds2 = (data(await api('GET', `/api/workshop/optimization/bindings?agentInstanceId=${encodeURIComponent(leadAgentIdOuter)}`, undefined, token))?.bindings ?? []).filter(b => b.profileId === profileId)
641
+ const cardV2 = Number(String(adopt2.card?.nodeId ?? '').split('@')[1])
642
+ const rebound2 = binds2.find(b => Number(b.profileVersionNumber) === cardV2)
643
+ check('M4.4', '持续进化:绑定再次重绑(revision 递增)', Boolean(rebound2) && Number(rebound2?.revision) >= 2, `versions=[${binds2.map(b => b.profileVersionNumber).join(',')}] card=v${cardV2} rev=${rebound2?.revision}`)
644
+ writeFreshTail()
645
+ const solve2 = await api('POST', '/api/workshop/optimization/solve', { agentInstanceId: leadAgentIdOuter, bindingId: rebound2?.id }, token)
646
+ check('M4.5', '第 2 轮新模型 C solve optimal', solve2.status === 200 && data(solve2)?.result?.status === 'optimal', `status=${data(solve2)?.result?.status} http=${solve2.status}`)
647
+
648
+ const writesPath = join(HOME, 'data', 'dcw-writes.json')
649
+ const writesLen = existsSync(writesPath) ? readFileSync(writesPath, 'utf8').length : 0
650
+ check('M4.6', '全闭环零 DCW 写入(三轮训练+两轮采纳+求解)', writesLen === 0, `dcw-writes.json ${writesLen ? '存在' : '不存在'}`)
651
+ }
652
+
653
+ // ════ M5 管理者面 REST ════
654
+ console.log('\n=== M5 管理者面 ===')
655
+ {
656
+ const versions = await api('GET', `/api/workshop/ontology/versions/${encodeURIComponent(ONT)}`, undefined, token)
657
+ check('M5.1', '本体版本台账(v1+v2,管理者可查)', versions.status === 200 && (data(versions)?.versions ?? []).length >= 2, `n=${(data(versions)?.versions ?? []).length}`)
658
+
659
+ const profs = await api('GET', `/api/workshop/optimization/profiles?limit=50`, undefined, token)
660
+ const profStr = JSON.stringify(data(profs) ?? {})
661
+ check('M5.2', 'Profile 版本台账含三轮模型工件(A/B/C 全可查)', profs.status === 200 && [trainedAId, trainedBId, trainedCId].every(id => profStr.includes(id)), `len=${profStr.length}`)
662
+
663
+ const twinProf = await api('GET', `/api/workshop/channels/${encodeURIComponent(channelIdOuter)}/twin-profile`, undefined, token)
664
+ check('M5.3', '孪生谱系台账(场景/冻结态可查)', twinProf.status === 200 && data(twinProf)?.profile?.sceneId === SCENE_ID, `sceneId=${data(twinProf)?.profile?.sceneId}`)
665
+
666
+ const audit = await api('GET', '/api/workshop/audit?limit=500', undefined, token)
667
+ const entries = data(audit)?.entries ?? []
668
+ const adoptEntries = entries.filter(e => String(e.action).startsWith('optimizer.profile.adopt'))
669
+ const kinds = new Set(adoptEntries.map(e => e.actorKind))
670
+ check('M5.4', '审计三态留痕:AI 提议 + 人批准(adopt_propose+adopt)', adoptEntries.length >= 2 && kinds.has('agent'), `n=${adoptEntries.length} actions=${adoptEntries.map(e => e.action).join(',')} kinds=${[...kinds].join(',')}`)
671
+
672
+ const ontAudit = entries.filter(e => String(e.action).includes('ontology'))
673
+ check('M5.5', '本体审计留痕(起草/发布/修正)', ontAudit.length >= 1, `n=${ontAudit.length} sample=${ontAudit[0]?.action ?? '-'}`)
674
+ }
675
+
676
+ // ════ M6 前端渲染走查(管理者查看) ════
677
+ console.log('\n=== M6 前端渲染 ===')
678
+ process.env.AW_BASE = BASE
679
+ {
680
+ const { launch, openPage, gotoReady } = await import('./ui/lib.mjs')
681
+ const browser = await launch({ width: 1600, height: 1000 })
682
+ const page = await openPage(browser, { token, width: 1600, height: 1000, deviceScaleFactor: 1 })
683
+ const pageErrors = []
684
+ page.on('pageerror', e => pageErrors.push(String(e.message).slice(0, 200)))
685
+ const shot = name => page.screenshot({ path: join(EVID, `${name}.png`), fullPage: true })
686
+ try {
687
+ const targets = [['/optimizer', 'optimizer'], ['/aml', 'aml'], ['/ontology', 'ontology']]
688
+ let allOk = true
689
+ const detail = []
690
+ for (const [path, name] of targets) {
691
+ const errsBefore = pageErrors.length
692
+ await gotoReady(page, path, { wait: 2500 })
693
+ await sleep(800)
694
+ const bodyLen = await page.evaluate(() => (document.body.textContent ?? '').length)
695
+ const ok = pageErrors.length === errsBefore && bodyLen > 400
696
+ detail.push(`${name}:len=${bodyLen},errs=${pageErrors.length - errsBefore}`)
697
+ if (!ok) allOk = false
698
+ await shot(`page-${name}`)
699
+ }
700
+ check('M6.1', '管理者三台(/optimizer /aml /ontology)渲染无错', allOk, detail.join(' '))
701
+ check('M6.2', '全程无未捕获 pageerror', pageErrors.length === 0 || pageErrors.every(e => e.includes('dispose')), pageErrors.slice(0, 2).join(';'))
702
+ }
703
+ finally {
704
+ await browser.close().catch(() => {})
705
+ }
706
+ }
707
+
708
+ // ════ 报告 ════
709
+ const pass = results.filter(r => r.pass).length
710
+ const report = {
711
+ script: 'scripts/e2e-model-loop-live.mjs',
712
+ generatedAt: new Date().toISOString(),
713
+ base: BASE,
714
+ home: HOME,
715
+ ontology: ONT,
716
+ profileId,
717
+ sceneId: SCENE_ID,
718
+ lineId: line.id,
719
+ modelA: { id: trainedAId, artifactHash: trainedAHash },
720
+ modelB: { id: trainedBId, artifactHash: trainedBHash },
721
+ modelC: { id: trainedCId, artifactHash: trainedCHash },
722
+ total: results.length,
723
+ pass,
724
+ fail: results.length - pass,
725
+ results,
726
+ }
727
+ writeFileSync(join(EVID, 'report.json'), JSON.stringify(report, null, 2))
728
+ console.log(`\nMODEL-LOOP LIVE E2E: ${pass}/${results.length} passed, ${results.length - pass} failed`)
729
+ console.log(`evidence: ${EVID}`)
730
+ killTree(child.pid)
731
+ process.exit(results.some(r => !r.pass) ? 1 : 0)