agentworkshop 0.7.52 → 0.7.53

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 (459) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +70 -1
  2. package/.output/.AgentWorkShop/prompts/host-tools.json +70 -1
  3. package/.output/.AgentWorkShop/schema.json +2 -0
  4. package/.output/nitro.json +1 -1
  5. package/.output/public/_nuxt/{D0ldVwl-.js → 77DRSnQv.js} +1 -1
  6. package/.output/public/_nuxt/8oRmGaAV.js +1 -0
  7. package/.output/public/_nuxt/{BPou3U7b.js → BLnLjzka.js} +1 -1
  8. package/.output/public/_nuxt/{CFQyYMEn2.js → BSdJTnk12.js} +1 -1
  9. package/.output/public/_nuxt/{C4lagVdN2.js → BUD1CNvB2.js} +1 -1
  10. package/.output/public/_nuxt/{CTvh8v4T.js → BXR3tgEk.js} +1 -1
  11. package/.output/public/_nuxt/{Cbeifv55.js → BhlRInVa.js} +1 -1
  12. package/.output/public/_nuxt/{mNnuUoSy.js → BkfA3BbD.js} +1 -1
  13. package/.output/public/_nuxt/{ChGuLxwW2.js → BpcQDwKM2.js} +1 -1
  14. package/.output/public/_nuxt/{DQMbdg4t.js → BwlGxRxA.js} +1 -1
  15. package/.output/public/_nuxt/{Bt2ZL5332.js → C4sQKuhk2.js} +1 -1
  16. package/.output/public/_nuxt/{CkPxL4UX2.js → CBUIODly2.js} +1 -1
  17. package/.output/public/_nuxt/CR-ILcRu2.js +1 -0
  18. package/.output/public/_nuxt/CZLnEnbE.js +14 -0
  19. package/.output/public/_nuxt/{qPsch_ny2.js → Chxx-DIE2.js} +1 -1
  20. package/.output/public/_nuxt/{BiUhxH3t2.js → CjwxhpwR2.js} +1 -1
  21. package/.output/public/_nuxt/{B9cGf8mg.js → CvGYm7w8.js} +1 -1
  22. package/.output/public/_nuxt/{B9BmiUil.js → DAGuPgXa.js} +1 -1
  23. package/.output/public/_nuxt/{DefDtlKK.js → DE6H1HdJ.js} +1 -1
  24. package/.output/public/_nuxt/{D_Ce9Fon2.js → DFhyhDGa2.js} +1 -1
  25. package/.output/public/_nuxt/{BXx9AxdF2.js → DSqMyLCV2.js} +1 -1
  26. package/.output/public/_nuxt/{B8zP7kaj2.js → DV5joWTn2.js} +1 -1
  27. package/.output/public/_nuxt/{Da90gveK.js → DVOR2pWg.js} +1 -1
  28. package/.output/public/_nuxt/DWsgRq2c.js +1 -0
  29. package/.output/public/_nuxt/{mgWHSRc8.js → DZDi8inD.js} +1 -1
  30. package/.output/public/_nuxt/{xQX5r6tW.js → Dep0Hc9w.js} +1 -1
  31. package/.output/public/_nuxt/{evt5VGDh.js → DffgG8vT.js} +1 -1
  32. package/.output/public/_nuxt/{B_opxrHJ.js → DlCktWDT.js} +1 -1
  33. package/.output/public/_nuxt/{BVzw5a_0.js → DmzmgYHb.js} +1 -1
  34. package/.output/public/_nuxt/PluginSlot.CGH0vb95.css +1 -0
  35. package/.output/public/_nuxt/{ty5W5QwO.js → WmMwIS4y.js} +1 -1
  36. package/.output/public/_nuxt/{_id_.DamiFT0Q.css → _id_.rdGx-UDx.css} +1 -1
  37. package/.output/public/_nuxt/{BgEDwFMf.js → _kUzq9VF.js} +1 -1
  38. package/.output/public/_nuxt/_wsId_.XjmieZaJ.css +1 -0
  39. package/.output/public/_nuxt/{C00Rd6Cg.js → aV6YPjg0.js} +1 -1
  40. package/.output/public/_nuxt/builds/latest.json +1 -1
  41. package/.output/public/_nuxt/builds/meta/5ccc21c2-aba0-4b63-9a13-410e0569e345.json +1 -0
  42. package/.output/public/_nuxt/{default.CXb_zoN6.css → default.CyLpQD1C.css} +1 -1
  43. package/.output/public/_nuxt/o9rJf00k.js +1 -0
  44. package/.output/public/_nuxt/sl1EEoCr2.js +21 -0
  45. package/.output/public/robots.txt +3 -0
  46. package/.output/server/chunks/_/binding-authz.mjs +1 -1
  47. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  48. package/.output/server/chunks/_/entity.mjs +1 -1
  49. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  50. package/.output/server/chunks/_/nitro.mjs +1945 -1420
  51. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  52. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  53. package/.output/server/chunks/build/{PluginSlot-D2WUZgbp.mjs → PluginSlot-CT4YaNrB.mjs} +5 -5
  54. package/.output/server/chunks/build/{PluginSlot-D2WUZgbp.mjs.map → PluginSlot-CT4YaNrB.mjs.map} +1 -1
  55. package/.output/server/chunks/build/TownView-fP9BiALv.mjs +1 -1
  56. package/.output/server/chunks/build/{_id_-BUpQSlzo.mjs → _id_-CK-QFrDV.mjs} +55 -51
  57. package/.output/server/chunks/build/_id_-CK-QFrDV.mjs.map +1 -0
  58. package/.output/server/chunks/build/{_wsId_-C4L9fVHC.mjs → _wsId_-DdqXzycE.mjs} +320 -202
  59. package/.output/server/chunks/build/{_wsId_-C4L9fVHC.mjs.map → _wsId_-DdqXzycE.mjs.map} +1 -1
  60. package/.output/server/chunks/build/{agents-ydYu-Ol9.mjs → agents-DqICy4AL.mjs} +6 -6
  61. package/.output/server/chunks/build/{agents-ydYu-Ol9.mjs.map → agents-DqICy4AL.mjs.map} +1 -1
  62. package/.output/server/chunks/build/{channel-templates-DP1ndA5K.mjs → channel-templates-gdCLajKy.mjs} +2 -2
  63. package/.output/server/chunks/build/{channel-templates-DP1ndA5K.mjs.map → channel-templates-gdCLajKy.mjs.map} +1 -1
  64. package/.output/server/chunks/build/daq-N69AOtgw.mjs +1 -1
  65. package/.output/server/chunks/build/dcw-CN9d3Rs7.mjs +1 -1
  66. package/.output/server/chunks/build/{default-DCf9tn1i.mjs → default-CXUe4HL7.mjs} +65 -62
  67. package/.output/server/chunks/build/{default-DCf9tn1i.mjs.map → default-CXUe4HL7.mjs.map} +1 -1
  68. package/.output/server/chunks/build/{pages-BzmrmLL_.mjs → pages-CRMeXrxG.mjs} +3 -3
  69. package/.output/server/chunks/build/{pages-BzmrmLL_.mjs.map → pages-CRMeXrxG.mjs.map} +1 -1
  70. package/.output/server/chunks/build/{plugins-DSka7-vS.mjs → plugins-NAS9MQ5r.mjs} +2 -2
  71. package/.output/server/chunks/build/{plugins-DSka7-vS.mjs.map → plugins-NAS9MQ5r.mjs.map} +1 -1
  72. package/.output/server/chunks/build/{schedules-BYR-lA1k.mjs → schedules-D8vYHgQ5.mjs} +2 -2
  73. package/.output/server/chunks/build/{schedules-BYR-lA1k.mjs.map → schedules-D8vYHgQ5.mjs.map} +1 -1
  74. package/.output/server/chunks/build/{settings-UyZ78w2e.mjs → settings-B7HA-sdb.mjs} +3 -3
  75. package/.output/server/chunks/build/{settings-UyZ78w2e.mjs.map → settings-B7HA-sdb.mjs.map} +1 -1
  76. package/.output/server/chunks/build/{teams-D-Y5mzU7.mjs → teams-wM-MdLv9.mjs} +8 -8
  77. package/.output/server/chunks/build/{teams-D-Y5mzU7.mjs.map → teams-wM-MdLv9.mjs.map} +1 -1
  78. package/.output/server/chunks/build/types-D6J7slEa.mjs +1 -1
  79. package/.output/server/chunks/build/{useAgentHarnessOptions-CRyQYYY0.mjs → useAgentHarnessOptions-BnFCh23W.mjs} +2 -2
  80. package/.output/server/chunks/build/{useAgentHarnessOptions-CRyQYYY0.mjs.map → useAgentHarnessOptions-BnFCh23W.mjs.map} +1 -1
  81. package/.output/server/chunks/build/{useAgentTemplateActions-CaRsOBmB.mjs → useAgentTemplateActions-DsBRUQni.mjs} +2 -2
  82. package/.output/server/chunks/build/{useAgentTemplateActions-CaRsOBmB.mjs.map → useAgentTemplateActions-DsBRUQni.mjs.map} +1 -1
  83. package/.output/server/chunks/build/{useAgentTemplateForm-ChiIuPYr.mjs → useAgentTemplateForm-DzQ_t2Ue.mjs} +2 -2
  84. package/.output/server/chunks/build/{useAgentTemplateForm-ChiIuPYr.mjs.map → useAgentTemplateForm-DzQ_t2Ue.mjs.map} +1 -1
  85. package/.output/server/chunks/build/{useAgentTemplatesCatalog-Bf5hk6Jf.mjs → useAgentTemplatesCatalog-Q7LgG1Af.mjs} +2 -2
  86. package/.output/server/chunks/build/{useAgentTemplatesCatalog-Bf5hk6Jf.mjs.map → useAgentTemplatesCatalog-Q7LgG1Af.mjs.map} +1 -1
  87. package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs +1 -1
  88. package/.output/server/chunks/build/useDaqDetailNode-D_vhG0jF.mjs +1 -1
  89. package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs +1 -1
  90. package/.output/server/chunks/build/useDaqFilters-BUQtZ82a.mjs +1 -1
  91. package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs +1 -1
  92. package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs +1 -1
  93. package/.output/server/chunks/build/{useDashboardData-CNOY4Bi3.mjs → useDashboardData-DWJ6vHjm.mjs} +2 -2
  94. package/.output/server/chunks/build/{useDashboardData-CNOY4Bi3.mjs.map → useDashboardData-DWJ6vHjm.mjs.map} +1 -1
  95. package/.output/server/chunks/build/useDcwAddNode-C1HDP3yG.mjs +1 -1
  96. package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs +1 -1
  97. package/.output/server/chunks/build/{useTeamActions-C51ciLb0.mjs → useTeamActions-BOLQkYxg.mjs} +2 -2
  98. package/.output/server/chunks/build/{useTeamActions-C51ciLb0.mjs.map → useTeamActions-BOLQkYxg.mjs.map} +1 -1
  99. package/.output/server/chunks/build/{useTeamAddMemberDialog-DthpCMP8.mjs → useTeamAddMemberDialog-BCgrLPxI.mjs} +2 -2
  100. package/.output/server/chunks/build/{useTeamAddMemberDialog-DthpCMP8.mjs.map → useTeamAddMemberDialog-BCgrLPxI.mjs.map} +1 -1
  101. package/.output/server/chunks/build/{useTeamCreateDialog-Dc2KTwyg.mjs → useTeamCreateDialog-CJk40LqS.mjs} +2 -2
  102. package/.output/server/chunks/build/{useTeamCreateDialog-Dc2KTwyg.mjs.map → useTeamCreateDialog-CJk40LqS.mjs.map} +1 -1
  103. package/.output/server/chunks/build/{useTeamDeployDialog-CXpvPnJD.mjs → useTeamDeployDialog-49hdUXQP.mjs} +2 -2
  104. package/.output/server/chunks/build/{useTeamDeployDialog-CXpvPnJD.mjs.map → useTeamDeployDialog-49hdUXQP.mjs.map} +1 -1
  105. package/.output/server/chunks/build/{useTeamPluginDialog-DTs-7SSm.mjs → useTeamPluginDialog-Dh-QOoiU.mjs} +2 -2
  106. package/.output/server/chunks/build/{useTeamPluginDialog-DTs-7SSm.mjs.map → useTeamPluginDialog-Dh-QOoiU.mjs.map} +1 -1
  107. package/.output/server/chunks/build/{useTeamsCatalog-CcpetYyW.mjs → useTeamsCatalog-HBSyKt5P.mjs} +2 -2
  108. package/.output/server/chunks/build/{useTeamsCatalog-CcpetYyW.mjs.map → useTeamsCatalog-HBSyKt5P.mjs.map} +1 -1
  109. package/.output/server/chunks/build/{useWorkshopApi-CO3ophqt.mjs → useWorkshopApi-C9sTYu1F.mjs} +9 -1
  110. package/.output/server/chunks/build/useWorkshopApi-C9sTYu1F.mjs.map +1 -0
  111. package/.output/server/chunks/build/useWorkshopWs-Bm80bLn7.mjs.map +1 -1
  112. package/.output/server/chunks/routes/api/index.get.mjs +1 -1
  113. package/.output/server/chunks/routes/api/index.post.mjs +14 -3
  114. package/.output/server/chunks/routes/api/index.post.mjs.map +1 -1
  115. package/.output/server/chunks/routes/api/mcp/workshop.mjs +43 -14
  116. package/.output/server/chunks/routes/api/mcp/workshop.mjs.map +1 -1
  117. package/.output/server/chunks/routes/api/metrics.mjs +2 -0
  118. package/.output/server/chunks/routes/api/metrics.mjs.map +1 -1
  119. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +1 -1
  120. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs.map +1 -1
  121. package/.output/server/chunks/routes/api/system/backup/list.get.mjs +47 -0
  122. package/.output/server/chunks/routes/api/system/backup/list.get.mjs.map +1 -0
  123. package/.output/server/chunks/routes/api/system/backup/run.post.mjs +35 -0
  124. package/.output/server/chunks/routes/api/system/backup/run.post.mjs.map +1 -0
  125. package/.output/server/chunks/routes/api/system/config-groups/_id_.delete.mjs +1 -1
  126. package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs +1 -1
  127. package/.output/server/chunks/routes/api/system/index.get.mjs +1 -1
  128. package/.output/server/chunks/routes/api/system/index.patch.mjs +15 -4
  129. package/.output/server/chunks/routes/api/system/index.patch.mjs.map +1 -1
  130. package/.output/server/chunks/routes/api/system/index.post.mjs +1 -1
  131. package/.output/server/chunks/routes/api/system/index2.get.mjs +1 -1
  132. package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs +1 -1
  133. package/.output/server/chunks/routes/api/system/monitor/terminate.post.mjs +1 -1
  134. package/.output/server/chunks/routes/api/system/settings/events.get.mjs +1 -1
  135. package/.output/server/chunks/routes/api/system/settings/reset.post.mjs +15 -3
  136. package/.output/server/chunks/routes/api/system/settings/reset.post.mjs.map +1 -1
  137. package/.output/server/chunks/routes/api/system/timezone.get.mjs +1 -1
  138. package/.output/server/chunks/routes/api/users/_id_.delete.mjs +14 -3
  139. package/.output/server/chunks/routes/api/users/_id_.delete.mjs.map +1 -1
  140. package/.output/server/chunks/routes/api/users/_id_.get.mjs +1 -1
  141. package/.output/server/chunks/routes/api/users/_id_.put.mjs +14 -3
  142. package/.output/server/chunks/routes/api/users/_id_.put.mjs.map +1 -1
  143. package/.output/server/chunks/routes/api/users/index.get.mjs +1 -1
  144. package/.output/server/chunks/routes/api/users/index.post.mjs +1 -1
  145. package/.output/server/chunks/routes/api/users/login.post.mjs +3 -2
  146. package/.output/server/chunks/routes/api/users/login.post.mjs.map +1 -1
  147. package/.output/server/chunks/routes/api/users/logout.post.mjs +1 -1
  148. package/.output/server/chunks/routes/api/users/me.get.mjs +1 -1
  149. package/.output/server/chunks/routes/api/users/register.post.mjs +4 -1
  150. package/.output/server/chunks/routes/api/users/register.post.mjs.map +1 -1
  151. package/.output/server/chunks/routes/api/users/setup-status.get.mjs +1 -1
  152. package/.output/server/chunks/routes/api/users/tokens/_id/token.get.mjs +1 -1
  153. package/.output/server/chunks/routes/api/users/tokens/_id_.delete.mjs +1 -1
  154. package/.output/server/chunks/routes/api/users/tokens/_id_.patch.mjs +1 -1
  155. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.get.mjs +1 -1
  156. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.post.mjs +19 -15
  157. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.post.mjs.map +1 -1
  158. package/.output/server/chunks/routes/api/workshop/a2a/send.post.mjs +1 -1
  159. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +1 -1
  160. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +1 -1
  161. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +1 -1
  162. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/grant.post.mjs +1 -1
  163. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/revoke.post.mjs +1 -1
  164. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +1 -1
  165. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +1 -1
  166. package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +1 -1
  167. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +1 -1
  168. package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +1 -1
  169. package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +1 -1
  170. package/.output/server/chunks/routes/api/workshop/agents/subscribe.post.mjs +1 -1
  171. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +1 -1
  172. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +1 -1
  173. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +1 -1
  174. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +1 -1
  175. package/.output/server/chunks/routes/api/workshop/aml/entities/prune.post.mjs +1 -1
  176. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +1 -1
  177. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +1 -1
  178. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +1 -1
  179. package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +1 -1
  180. package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +1 -1
  181. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +1 -1
  182. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +1 -1
  183. package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs +1 -1
  184. package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +1 -1
  185. package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +1 -1
  186. package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +1 -1
  187. package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs +1 -1
  188. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +1 -1
  189. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +1 -1
  190. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +1 -1
  191. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +1 -1
  192. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +1 -1
  193. package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +1 -1
  194. package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +1 -1
  195. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +1 -1
  196. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +1 -1
  197. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs +1 -1
  198. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs +1 -1
  199. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs +1 -1
  200. package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs +1 -1
  201. package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +1 -1
  202. package/.output/server/chunks/routes/api/workshop/assets/index.get.mjs +1 -1
  203. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +1 -1
  204. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +1 -1
  205. package/.output/server/chunks/routes/api/workshop/audit.get.mjs +1 -1
  206. package/.output/server/chunks/routes/api/workshop/caller.mjs +1 -1
  207. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +8 -3
  208. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs.map +1 -1
  209. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs +1 -1
  210. package/.output/server/chunks/routes/api/workshop/channel-templates/from-channel.post.mjs +1 -1
  211. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +1 -1
  212. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.post.mjs +1 -1
  213. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/search.post.mjs +1 -1
  214. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs +1 -1
  215. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +1 -1
  216. package/.output/server/chunks/routes/api/workshop/channels/_id/events.get.mjs +1 -1
  217. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +1 -1
  218. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +1 -1
  219. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.post.mjs +1 -1
  220. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +1 -1
  221. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +1 -1
  222. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +1 -1
  223. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +1 -1
  224. package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs +37 -0
  225. package/.output/server/chunks/routes/api/workshop/channels/_id/line.put.mjs.map +1 -0
  226. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +1 -1
  227. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs +1 -1
  228. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs +1 -1
  229. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +1 -1
  230. package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs +1 -1
  231. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +4 -4
  232. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +1 -1
  233. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +1 -1
  234. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +1 -1
  235. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +1 -1
  236. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +1 -1
  237. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +1 -1
  238. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +1 -1
  239. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +1 -1
  240. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +1 -1
  241. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +1 -1
  242. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +1 -1
  243. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +1 -1
  244. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +1 -1
  245. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +1 -1
  246. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +1 -1
  247. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +1 -1
  248. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +1 -1
  249. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +1 -1
  250. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +1 -1
  251. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +1 -1
  252. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +1 -1
  253. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +1 -1
  254. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +1 -1
  255. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +1 -1
  256. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +1 -1
  257. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +1 -1
  258. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +1 -1
  259. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +1 -1
  260. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +1 -1
  261. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +1 -1
  262. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +1 -1
  263. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +1 -1
  264. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +1 -1
  265. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +1 -1
  266. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +1 -1
  267. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  268. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +1 -1
  269. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +1 -1
  270. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +6 -1
  271. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs.map +1 -1
  272. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  273. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  274. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +1 -1
  275. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +1 -1
  276. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +1 -1
  277. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +1 -1
  278. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +1 -1
  279. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +1 -1
  280. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +1 -1
  281. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +1 -1
  282. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  283. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  284. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +1 -1
  285. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +1 -1
  286. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +1 -1
  287. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +1 -1
  288. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +3 -3
  289. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs.map +1 -1
  290. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +1 -1
  291. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +1 -1
  292. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +1 -1
  293. package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +1 -1
  294. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +1 -1
  295. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +1 -1
  296. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +1 -1
  297. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  298. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  299. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +1 -1
  300. package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +1 -1
  301. package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +1 -1
  302. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +1 -1
  303. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +1 -1
  304. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +14 -2
  305. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs.map +1 -1
  306. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  307. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  308. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  309. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  310. package/.output/server/chunks/routes/api/workshop/index.post.mjs +1 -1
  311. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  312. package/.output/server/chunks/routes/api/workshop/index10.get.mjs +1 -1
  313. package/.output/server/chunks/routes/api/workshop/index2.get.mjs +1 -1
  314. package/.output/server/chunks/routes/api/workshop/index2.post.mjs +1 -1
  315. package/.output/server/chunks/routes/api/workshop/index3.get.mjs +1 -1
  316. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +1 -1
  317. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +1 -1
  318. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +1 -1
  319. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +1 -1
  320. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +1 -1
  321. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +1 -1
  322. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +1 -1
  323. package/.output/server/chunks/routes/api/workshop/index7.post.mjs +1 -1
  324. package/.output/server/chunks/routes/api/workshop/index8.get.mjs +1 -1
  325. package/.output/server/chunks/routes/api/workshop/index8.post.mjs +1 -1
  326. package/.output/server/chunks/routes/api/workshop/index9.get.mjs +3 -3
  327. package/.output/server/chunks/routes/api/workshop/index9.get.mjs.map +1 -1
  328. package/.output/server/chunks/routes/api/workshop/index9.post.mjs +1 -1
  329. package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs +1 -1
  330. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  331. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  332. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  333. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  334. package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs +1 -1
  335. package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs +1 -1
  336. package/.output/server/chunks/routes/api/workshop/tasks/_id/complete.post.mjs +1 -1
  337. package/.output/server/chunks/routes/api/workshop/tasks/_id/dispatch.post.mjs +1 -1
  338. package/.output/server/chunks/routes/api/workshop/tasks/_id/report.post.mjs +1 -1
  339. package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs +1 -1
  340. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +1 -1
  341. package/.output/server/chunks/routes/api/workshop/teams/_id/index.post.mjs +1 -1
  342. package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +1 -1
  343. package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +1 -1
  344. package/.output/server/chunks/routes/api/workshop/teams/_id_.patch.mjs +1 -1
  345. package/.output/server/chunks/routes/api/workshop/users/register.post.mjs +4 -1
  346. package/.output/server/chunks/routes/api/workshop/users/register.post.mjs.map +1 -1
  347. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +2 -2
  348. package/.output/server/chunks/routes/renderer.mjs +2 -2
  349. package/.output/server/chunks/virtual/entry.mjs +340 -52
  350. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  351. package/.output/server/package.json +1 -1
  352. package/README-zh.md +7 -3
  353. package/README.md +11 -5
  354. package/app/components/app-header/HitlBell.vue +9 -9
  355. package/app/components/dcw/DcwAddNodeModal.vue +3 -3
  356. package/app/components/dcw/DcwNodesTable.vue +4 -4
  357. package/app/components/dcw/DcwRecipeEditModal.vue +2 -2
  358. package/app/components/workshop/ChatMemberPanel.vue +39 -38
  359. package/app/components/workshop/ChatTimeline.vue +33 -24
  360. package/app/components/workshop/Composer.vue +7 -7
  361. package/app/components/workshop/NotificationCenter.vue +22 -13
  362. package/app/components/workshop/PluginSlot.vue +1 -1
  363. package/app/components/workshop/TaskInspectorDrawer.vue +5 -5
  364. package/app/components/workshop/blocks/ClusterTool.vue +1 -1
  365. package/app/components/workshop/composer/ComposerToolbar.vue +2 -2
  366. package/app/components/workshop/composer/MentionMenu.vue +1 -1
  367. package/app/components/workshop/composer/ReplyChip.vue +2 -2
  368. package/app/components/workshop/composer/StatusChip.vue +7 -7
  369. package/app/components/workshop/console/LineOperationsPanel.vue +15 -14
  370. package/app/components/workshop/lanes/LaneHeader.vue +5 -3
  371. package/app/components/workshop/session-list/ChannelSettingsModal.vue +43 -1
  372. package/app/composables/useCodeCopy.ts +16 -2
  373. package/app/composables/workshop/useWorkshopApi.ts +7 -0
  374. package/app/stores/workshop/notifications.ts +43 -17
  375. package/docs/cli.en.md +1 -1
  376. package/i18n/dicts/_en.json +1551 -1557
  377. package/i18n/dicts/_en2.json +300 -300
  378. package/i18n/dicts/_en4.json +114 -115
  379. package/i18n/dicts/agentLanesView.json +61 -57
  380. package/i18n/dicts/appHeader.json +17 -7
  381. package/i18n/dicts/channelSessionList.json +51 -48
  382. package/i18n/dicts/chat.json +79 -0
  383. package/i18n/dicts/clusterTool.json +4 -3
  384. package/i18n/dicts/composer.json +38 -38
  385. package/i18n/dicts/dcwDetail.json +219 -209
  386. package/i18n/dicts/lineOps.json +14 -0
  387. package/i18n/dicts/multiChannelView.json +13 -13
  388. package/i18n/dicts/notifications.json +13 -0
  389. package/i18n/dicts/taskInspectorDrawer.json +25 -19
  390. package/i18n/locales/en.ts +152 -8
  391. package/i18n/locales/zh-CN.ts +152 -8
  392. package/package.json +1 -1
  393. package/public/robots.txt +3 -0
  394. package/server/api/mcp/workshop.ts +8 -1
  395. package/server/api/metrics.ts +4 -1
  396. package/server/api/plugins/[name]/[...path].ts +4 -3
  397. package/server/api/system/backup/list.get.ts +32 -0
  398. package/server/api/system/backup/run.post.ts +21 -0
  399. package/server/api/system/settings/index.patch.ts +12 -2
  400. package/server/api/system/settings/reset.post.ts +11 -2
  401. package/server/api/users/[id].delete.ts +9 -2
  402. package/server/api/users/[id].put.ts +9 -2
  403. package/server/api/users/index.post.ts +9 -2
  404. package/server/api/users/login.post.ts +3 -3
  405. package/server/api/users/register.post.ts +11 -1
  406. package/server/api/workshop/a2a/[agentId]/rpc/index.post.ts +23 -13
  407. package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +6 -0
  408. package/server/api/workshop/channels/[id]/line.put.ts +30 -0
  409. package/server/api/workshop/dcw/line/query.get.ts +6 -0
  410. package/server/api/workshop/dcw/recipes/[id]/revert.post.ts +3 -2
  411. package/server/api/workshop/fs/dirs.get.ts +19 -1
  412. package/server/api/workshop/ops-logs/index.get.ts +3 -2
  413. package/server/api/workshop/users/register.post.ts +7 -0
  414. package/server/mcp/workshop-server.ts +36 -13
  415. package/server/middleware/00-security-headers.ts +38 -0
  416. package/server/middleware/01-rate-guard.ts +101 -0
  417. package/server/plugins/backup.ts +56 -1
  418. package/server/plugins-builtin/diag-bridge/host/plugin.mjs +18 -11
  419. package/server/repositories/user.repository.ts +10 -5
  420. package/server/services/user.service.ts +24 -1
  421. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +1 -1
  422. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +4 -2
  423. package/server/services/workshop/agents/industrial/index.ts +1 -1
  424. package/server/services/workshop/agents/industrial/ops-tools.ts +168 -26
  425. package/server/services/workshop/agents/tool-classes.ts +2 -0
  426. package/server/services/workshop/aml/dataset-builder.ts +4 -2
  427. package/server/services/workshop/aml/job-orchestrator/process.ts +1 -0
  428. package/server/services/workshop/aml/job-orchestrator/shared.ts +2 -0
  429. package/server/services/workshop/aml/job-orchestrator/start-job.ts +5 -2
  430. package/server/services/workshop/db/channel.repo.ts +11 -1
  431. package/server/services/workshop/db/database/migrations.ts +2 -0
  432. package/server/services/workshop/db/database/rows.ts +2 -0
  433. package/server/services/workshop/db/database/seed.ts +19 -9
  434. package/server/services/workshop/dcw/dcw-controller/recipes.ts +60 -4
  435. package/server/services/workshop/dcw/dcw-controller/write.ts +35 -6
  436. package/server/services/workshop/dcw/dcw-recipe.repo.ts +4 -3
  437. package/server/services/workshop/dcw/param-map.repo.ts +3 -0
  438. package/server/services/workshop/plugins/host/init.mjs +9 -1
  439. package/server/services/workshop/plugins/host/load.mjs +2 -2
  440. package/server/services/workshop/runtime/manager/admin-channel.ts +27 -1
  441. package/server/services/workshop/runtime/manager/channel-templates.ts +5 -4
  442. package/server/services/workshop/runtime/manager/runtime-wiring.ts +17 -1
  443. package/shared/config/schema.json +2 -0
  444. package/.output/public/_nuxt/4wrEQ3np2.js +0 -21
  445. package/.output/public/_nuxt/CGQvF-Gg.js +0 -14
  446. package/.output/public/_nuxt/CcGuXiOa.js +0 -1
  447. package/.output/public/_nuxt/D0SNhlnt.js +0 -1
  448. package/.output/public/_nuxt/DnH9SRMW2.js +0 -1
  449. package/.output/public/_nuxt/ON2x-t6n.js +0 -1
  450. package/.output/public/_nuxt/PluginSlot.DmjNbrcX.css +0 -1
  451. package/.output/public/_nuxt/_wsId_.DkrQkT6r.css +0 -1
  452. package/.output/public/_nuxt/builds/meta/c64014ca-3595-4b7a-a5ca-055687c83ba2.json +0 -1
  453. package/.output/server/chunks/build/_id_-BUpQSlzo.mjs.map +0 -1
  454. package/.output/server/chunks/build/useWorkshopApi-CO3ophqt.mjs.map +0 -1
  455. package/scripts/.chk-tok +0 -1
  456. package/scripts/.retrain-body.json +0 -1
  457. package/scripts/.retrain3-body.json +0 -1
  458. package/scripts/.rt-tok +0 -1
  459. package/scripts/.rt3-tok +0 -1
@@ -53,7 +53,41 @@ async function backupOne(src: string, target: string): Promise<void> {
53
53
  }
54
54
  }
55
55
 
56
- async function backupOnce(dataDir: string): Promise<void> {
56
+ /** 数据目录内的 JSON 仓储/状态文件(kv、参数面、节点绑定、插件 kv 等)+ 上级配置根的 config.yml/runtime-settings.json */
57
+ async function backupFlatFiles(dataDir: string, backupDir: string, stamp: string): Promise<void> {
58
+ const bundleDir = resolve(backupDir, `files-${stamp}`)
59
+ mkdirSync(bundleDir, { recursive: true })
60
+ const targets: Array<{ src: string, name: string }> = []
61
+ // dataDir 下所有 .json(逐文件拷贝,目录型子树由各自仓储自轮转,不整树递归)
62
+ try {
63
+ for (const f of readdirSync(dataDir, { withFileTypes: true })) {
64
+ if (f.isFile() && f.name.endsWith('.json')) targets.push({ src: resolve(dataDir, f.name), name: f.name })
65
+ }
66
+ }
67
+ catch { /* 数据目录不可读时跳过 JSON 面 */ }
68
+ // 配置根(config.yml + runtime-settings.json)——重建实例的"怎么做"半边
69
+ try {
70
+ const homeMod = await import('@/shared/config/home.mjs')
71
+ const rm = homeMod.resolveRunMode({ cwd: process.cwd(), env: process.env })
72
+ for (const cfg of ['config.yml', 'runtime-settings.json']) {
73
+ const p = resolve(rm.configRoot, cfg)
74
+ if (existsSync(p)) targets.push({ src: p, name: cfg })
75
+ }
76
+ }
77
+ catch { /* 模式解析失败只降级 JSON 面 */ }
78
+ for (const t of targets) {
79
+ try {
80
+ const tmp = resolve(bundleDir, `${t.name}.tmp`)
81
+ await copyFileAsync(t.src, tmp)
82
+ renameSync(tmp, resolve(bundleDir, t.name))
83
+ }
84
+ catch (err) {
85
+ console.error(`[backup] ${t.name} 快照失败:`, err instanceof Error ? err.message : err)
86
+ }
87
+ }
88
+ }
89
+
90
+ export async function backupOnce(dataDir: string): Promise<void> {
57
91
  const backupDir = resolve(dataDir, 'backups')
58
92
  mkdirSync(backupDir, { recursive: true })
59
93
  const stamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)
@@ -69,6 +103,8 @@ async function backupOnce(dataDir: string): Promise<void> {
69
103
  console.error(`[backup] ${file} 快照失败:`, err instanceof Error ? err.message : err)
70
104
  }
71
105
  }
106
+ // JSON 仓储 + 配置面快照(三库之外的可重建性半边)
107
+ await backupFlatFiles(dataDir, backupDir, stamp)
72
108
  // 轮转:每库仅保留最近 backup.keep 份(按文件名内时间戳倒序;env BACKUP_KEEP 兼容)
73
109
  const keep = Math.max(1, backupSettings().keep)
74
110
  for (const file of DB_FILES) {
@@ -83,9 +119,28 @@ async function backupOnce(dataDir: string): Promise<void> {
83
119
  catch { /* 轮转失败不致命 */ }
84
120
  }
85
121
  }
122
+ // files-<stamp>/ 目录同 keep 轮转
123
+ const fileBundles = readdirSync(backupDir)
124
+ .filter(f => f.startsWith('files-') && !f.includes('.'))
125
+ .sort()
126
+ .reverse()
127
+ for (const stale of fileBundles.slice(keep)) {
128
+ try {
129
+ rmSync(resolve(backupDir, stale), { recursive: true })
130
+ }
131
+ catch { /* 轮转失败不致命 */ }
132
+ }
86
133
  console.log('[backup] 快照完成 →', backupDir)
87
134
  // R4:记录最近一次成功备份时间,供 /api/metrics 观测
88
135
  g.__awBackupLastAt = new Date().toISOString()
136
+ // 备份成败留痕(audit_log;动态导入避免插件装载期循环依赖)
137
+ void import('../services/workshop/ops/ops').then(({ recordOps }) => {
138
+ recordOps({
139
+ actor: 'system', actorName: 'backup', actorKind: 'system',
140
+ action: 'system.backup.run', kind: 'system',
141
+ summary: `数据库快照完成(${DB_FILES.length} 库 + JSON/配置)→ ${backupDir}`,
142
+ })
143
+ }).catch(() => {})
89
144
  }
90
145
 
91
146
  export default function backupPlugin() {
@@ -68,17 +68,24 @@ export default {
68
68
  if (Date.now() - last < AUTO_COOLDOWN_MS) return
69
69
  const now = Date.now()
70
70
  ctx.kv.set(`cooldown:${line}`, now) // 先占冷却,防同窗连发
71
- startDiagnosis(ctx, {
72
- line,
73
- fromMs: now - 60 * MIN,
74
- toMs: now,
75
- question: `${line} 产线该时窗数据深度根因诊断(自动触发:节点 ${s.nodeId} 采样值 ${s.value} ${rule.op === 'lt' ? '<' : '>'} ${rule.value})`,
76
- scene: `${line}_auto`,
77
- source: 'auto',
78
- }).then((r) => {
79
- if (r.ok) ctx.logger.warn(`自动诊断已发起:产线 ${line} runId=${r.runId}(节点 ${s.nodeId}=${s.value} 越限)`)
80
- else ctx.logger.warn(`自动诊断发起失败(产线 ${line}): ${r.error}`)
81
- }).catch(() => {})
71
+ // 首拍竞态:开线后首个越限样本即刻触发,快照查询早于 tsdb 首次刷盘(TSDB_FLUSH_MS=500ms)
72
+ // → 时窗查空失败。空窗失败定向重试(≤3 次、间隔 5s,总窗 ≤20s);冷却不回滚,仍只此一次派发。
73
+ const question = `${line} 产线该时窗数据深度根因诊断(自动触发:节点 ${s.nodeId} 采样值 ${s.value} ${rule.op === 'lt' ? '<' : '>'} ${rule.value})`
74
+ const attempt = (n) => {
75
+ startDiagnosis(ctx, {
76
+ line,
77
+ fromMs: now - 60 * MIN,
78
+ toMs: now,
79
+ question,
80
+ scene: `${line}_auto`,
81
+ source: 'auto',
82
+ }).then((r) => {
83
+ if (r.ok) ctx.logger.warn(`自动诊断已发起:产线 ${line} runId=${r.runId}(节点 ${s.nodeId}=${s.value} 越限)`)
84
+ else if (n < 3 && String(r.error ?? '').includes('无数采样本')) setTimeout(() => attempt(n + 1), 5000)
85
+ else ctx.logger.warn(`自动诊断发起失败(产线 ${line}): ${r.error}`)
86
+ }).catch(() => {})
87
+ }
88
+ attempt(0)
82
89
  }
83
90
  catch { /* 钩子内绝不抛 */ }
84
91
  })
@@ -139,7 +139,8 @@ function importLegacyWorkshopUsers(d: DatabaseSync): void {
139
139
  insertUser.run(row.id, row.name, email, hashPassword(randomPassword()), 'user', 'active', row.created_at ?? now())
140
140
  const hash = hashToken(row.token)
141
141
  if (!tokenExists.get(hash)) {
142
- insertToken.run(randomUUID(), row.id, 'legacy', hash, row.token, row.created_at ?? now())
142
+ // 安全基线:token_plain 不落明文(哈希即可鉴权;明文列仅为旧库形状兼容,恒 NULL)
143
+ insertToken.run(randomUUID(), row.id, 'legacy', hash, null, row.created_at ?? now())
143
144
  }
144
145
  }
145
146
  }
@@ -241,17 +242,21 @@ function toUser(row: Record<string, unknown>): User {
241
242
  export const userRepository = {
242
243
  list({ page, pageSize, keyword }: UserListQuery): Paginated<User> {
243
244
  const d = getDb()
244
- const kw = keyword?.toLowerCase() ?? ''
245
+ // 分页边界钳制:超大/非法分页参数在 SQL 外收口(OFFSET 1e20 会打穿 sqlite 抛 500)
246
+ const safePage = Number.isSafeInteger(page) && page >= 1 ? page : 1
247
+ const safeSize = Number.isSafeInteger(pageSize) && pageSize >= 1 ? Math.min(pageSize, 200) : 20
248
+ // LIKE 通配符转义:用户关键词里的 %/_ 按字面匹配,不放大匹配面(ESCAPE 子句配套)
249
+ const kw = keyword?.toLowerCase() ? keyword.toLowerCase().replace(/[\\%_]/g, ch => `\\${ch}`) : ''
245
250
  const where = kw
246
- ? `WHERE LOWER(name) LIKE ? OR LOWER(email) LIKE ?`
251
+ ? `WHERE LOWER(name) LIKE ? ESCAPE '\\' OR LOWER(email) LIKE ? ESCAPE '\\'`
247
252
  : ''
248
253
  const params = kw ? [`%${kw}%`, `%${kw}%`] : []
249
254
  const total = (d.prepare(`SELECT COUNT(*) AS n FROM users ${where}`).get(...params) as { n: number }).n
250
255
  const rows = d.prepare(
251
256
  `SELECT id, name, email, role, status, created_at AS createdAt FROM users ${where}
252
257
  ORDER BY created_at DESC LIMIT ? OFFSET ?`,
253
- ).all(...params, pageSize, (page - 1) * pageSize) as Array<Record<string, unknown>>
254
- return { items: rows.map(toUser), total, page, pageSize }
258
+ ).all(...params, safeSize, (safePage - 1) * safeSize) as Array<Record<string, unknown>>
259
+ return { items: rows.map(toUser), total, page: safePage, pageSize: safeSize }
255
260
  },
256
261
 
257
262
  /** 全量用户(不分页;管理面授权矩阵用,避免分页截断) */
@@ -23,6 +23,12 @@ export function resolveUserByToken(token: string): UserProfile | null {
23
23
  }
24
24
 
25
25
  export const userService = {
26
+ /**
27
+ * 登录防爆破:同 IP+邮箱在 15 分钟窗口内累计失败 ≥5 次 → 锁到窗口结束(429)。
28
+ * 内存态,重启即清;多实例部署由反代层限流补齐。计数表封顶 5000 条防内存滥用。
29
+ */
30
+ _loginFails: new Map<string, { count: number, firstAt: number }>(),
31
+
26
32
  // ===== 管理面(与既有契约一致)=====
27
33
 
28
34
  list(input: UserListInput) {
@@ -104,9 +110,25 @@ export const userService = {
104
110
  },
105
111
 
106
112
  /** 登录:密码校验通过后签发新 token(每次登录生成一个可独立吊销的会话 token) */
107
- login(input: UserLogin): AuthResult {
113
+ login(input: UserLogin, opts: { ip?: string } = {}): AuthResult {
114
+ const FAIL_WINDOW_MS = 15 * 60_000
115
+ const LOCK_THRESHOLD = 5
116
+ const lockKey = `${opts.ip ?? 'local'}|${input.email.toLowerCase()}`
117
+ const fails = this._loginFails
118
+ if (fails.size > 5000) {
119
+ const oldest = fails.keys().next().value
120
+ if (oldest) fails.delete(oldest)
121
+ }
122
+ const rec = fails.get(lockKey)
123
+ // 已达阈值且窗口未过 → 锁定;偶发失败(1-4 次)不锁,窗口滑过即重置
124
+ if (rec && rec.count >= LOCK_THRESHOLD && Date.now() - rec.firstAt < FAIL_WINDOW_MS) {
125
+ const waitMin = Math.ceil((FAIL_WINDOW_MS - (Date.now() - rec.firstAt)) / 60_000)
126
+ throw new AppError(429, 'LOGIN_LOCKED', `失败次数过多,账号已临时锁定,请约 ${waitMin} 分钟后再试`)
127
+ }
108
128
  const { id, hash } = userRepository.getPasswordHash(input.email) ?? {}
109
129
  if (!id || !hash || !verifyPassword(input.password, hash)) {
130
+ if (!rec || Date.now() - rec.firstAt >= FAIL_WINDOW_MS) fails.set(lockKey, { count: 1, firstAt: Date.now() })
131
+ else rec.count += 1
110
132
  throw new AppError(401, 'UNAUTHORIZED', '邮箱或密码错误')
111
133
  }
112
134
  const user = userRepository.findById(id)
@@ -116,6 +138,7 @@ export const userService = {
116
138
  if (user.status !== 'active') {
117
139
  throw new AppError(403, 'FORBIDDEN', '账号已禁用')
118
140
  }
141
+ fails.delete(lockKey)
119
142
  const { raw } = userRepository.createToken(id, `session-${Date.now()}`)
120
143
  return { user: this.publicProfile(user), token: raw }
121
144
  },
@@ -96,7 +96,7 @@ export async function dispatchHostTool(ctx: HostToolBridgeContext, req: HostTool
96
96
  }
97
97
 
98
98
  // 所有真实写入口在统一分发层再做一次服务端 fail-closed 守卫;工具目录隐藏不是安全边界。
99
- if (['dcw_control', 'param_control', 'dcw_rollback', 'recipe_update', 'recipe_rollback'].includes(req.toolName)) {
99
+ if (['dcw_control', 'param_control', 'dcw_rollback', 'recipe_update', 'recipe_rollback', 'recipe_trial', 'recipe_apply'].includes(req.toolName)) {
100
100
  const profile = getChannelTwinProfile(identity.channelId)
101
101
  const guard = twinWriteGuard(identity.channelId, profile)
102
102
  if (!guard.allowed) return { text: `${guard.code}: ${guard.message}`, isError: true }
@@ -9,7 +9,7 @@
9
9
  * 该族**不依赖 workspace**,由 dispatch 在 workspace 门控之前分流(保持原分发顺序)。
10
10
  */
11
11
  import type { HostToolResult } from '../types'
12
- import { toolAmlDatasetBuild, toolAmlDatasetStats, toolAmlJobCancel, toolAmlJobLogs, toolAmlJobStatus, toolAmlJobSubmit, toolAmlLeaderboard, toolAmlModelFind, toolAmlModelPromote, toolAmlModelReference, toolAmlNodeCatalog, toolAmlTrainingPlanCreate, toolAmlTrainingPlanList, toolAmlTrainingPlanTrain, toolDaqFrames, toolDaqQuery, toolDcwControl, toolDcwJudge, toolDcwJournal, toolDcwRead, toolDcwRollback, toolLineContext, toolMyIndustrialNodes, toolOpsLog, toolParamControl, toolParamRead, toolRecipeLog, toolRecipeRollback, toolRecipeUpdate, toolRecipeVersions, toolOptimizationExplore, toolTwinProviderCatalog, toolTwinSceneDiscover, toolTwinSceneCompile, toolTwinSceneFreeze, toolTwinSceneRead, toolTwinPhysicsSpecDraft, toolTwinPhysicsSpecValidate, toolTwinPhysicsSpecCompile, toolTwinSnapshotCreate, toolTwinTrialRun, toolMpcOptimize, toolTwinGateEvaluate, toolTwinBayesOptimize, toolTwinCalibrationRequest } from '../../industrial'
12
+ import { toolAmlDatasetBuild, toolAmlDatasetStats, toolAmlJobCancel, toolAmlJobLogs, toolAmlJobStatus, toolAmlJobSubmit, toolAmlLeaderboard, toolAmlModelFind, toolAmlModelPromote, toolAmlModelReference, toolAmlNodeCatalog, toolAmlTrainingPlanCreate, toolAmlTrainingPlanList, toolAmlTrainingPlanTrain, toolDaqFrames, toolDaqQuery, toolDcwControl, toolDcwJudge, toolDcwJournal, toolDcwRead, toolDcwRollback, toolLineContext, toolMyIndustrialNodes, toolOpsLog, toolParamControl, toolParamRead, toolRecipeApply, toolRecipeLog, toolRecipeRollback, toolRecipeTrial, toolRecipeUpdate, toolRecipeVersions, toolOptimizationExplore, toolTwinProviderCatalog, toolTwinSceneDiscover, toolTwinSceneCompile, toolTwinSceneFreeze, toolTwinSceneRead, toolTwinPhysicsSpecDraft, toolTwinPhysicsSpecValidate, toolTwinPhysicsSpecCompile, toolTwinSnapshotCreate, toolTwinTrialRun, toolMpcOptimize, toolTwinGateEvaluate, toolTwinBayesOptimize, toolTwinCalibrationRequest } from '../../industrial'
13
13
 
14
14
  type IndustrialToolHandler = (agentId: string, args: Record<string, unknown>, channelId?: string) => Promise<HostToolResult>
15
15
 
@@ -59,7 +59,9 @@ const INDUSTRIAL_HANDLERS = new Map<string, IndustrialToolHandler>([
59
59
  ['line_context', (agentId, args) => toolLineContext(agentId, args as { line_id?: string })],
60
60
  ['recipe_versions', (agentId, args) => toolRecipeVersions(agentId, args as { recipe_id?: string, limit?: number | string })],
61
61
  ['recipe_update', (agentId, args) => toolRecipeUpdate(agentId, args as Parameters<typeof toolRecipeUpdate>[1])],
62
- ['recipe_rollback', (agentId, args) => toolRecipeRollback(agentId, args as Parameters<typeof toolRecipeRollback>[1])],
62
+ ['recipe_rollback', (agentId, args) => toolRecipeRollback(agentId, args as Parameters<typeof toolRecipeRollback>[1])],
63
+ ['recipe_trial', (agentId, args) => toolRecipeTrial(agentId, args as Parameters<typeof toolRecipeTrial>[1])],
64
+ ['recipe_apply', (agentId, args) => toolRecipeApply(agentId, args as Parameters<typeof toolRecipeApply>[1])],
63
65
  ])
64
66
 
65
67
  /** 工业工具名集合(与 INDUSTRIAL_HANDLERS 同源;dispatch 据此在 workspace 门控前分流) */
@@ -17,7 +17,7 @@ export { toolMyIndustrialNodes, toolDcwControl, toolDcwRead } from './dcw-tools'
17
17
  export { toolParamControl, toolParamRead } from './param-tools'
18
18
  export { toolDaqQuery, toolDaqFrames } from './daq-tools'
19
19
  export { toolDcwJudge, toolDcwRollback, toolDcwJournal } from './dcw-judge-tools'
20
- export { toolOpsLog, toolRecipeLog, toolLineContext, toolRecipeVersions, toolRecipeUpdate, toolRecipeRollback } from './ops-tools'
20
+ export { toolOpsLog, toolRecipeLog, toolLineContext, toolRecipeVersions, toolRecipeUpdate, toolRecipeRollback, toolRecipeTrial, toolRecipeApply } from './ops-tools'
21
21
  export { toolAmlNodeCatalog, toolAmlDatasetBuild, toolAmlDatasetStats } from './aml-dataset-tools'
22
22
  export { toolAmlJobSubmit, toolAmlJobStatus, toolAmlJobLogs, toolAmlJobCancel, toolAmlLeaderboard, toolAmlModelFind, toolAmlModelPromote, toolAmlModelReference, toolAmlTrainingPlanList, toolAmlTrainingPlanTrain, toolAmlTrainingPlanCreate } from './aml-job-tools'
23
23
 
@@ -17,10 +17,26 @@ import { fanoutQuery, windowOf } from './audit-query'
17
17
  // 运维日志 / Recipe 变更史查询(Agent 自查面:负责产线 scoped)
18
18
  // ================================================================
19
19
 
20
- /** Agent 负责范围 = 绑定节点的全集(节点 id 集 + 这些节点所属产线集);无绑定 = 无范围 */
21
- export function agentOpsScope(agentId: string): { lineIds: string[], nodeIds: Set<string> } | null {
20
+ /** v18:频道绑定产线(只读扩权)——经全局 manager 读;manager 未装配/未绑定 = null */
21
+ function channelBoundLineId(agentId: string): string | null {
22
+ try {
23
+ const g = globalThis as typeof globalThis & {
24
+ __workshopManager?: { channelLineBindingOf(id: string): string | null }
25
+ }
26
+ return g.__workshopManager?.channelLineBindingOf(agentId) ?? null
27
+ }
28
+ catch {
29
+ return null
30
+ }
31
+ }
32
+
33
+ /** Agent 负责范围 = 绑定节点的全集(节点 id 集 + 这些节点所属产线集;写权限依据)
34
+ * ∪ 频道绑定产线(boundLineIds;v18 只读扩权:日志/配方史/上下文可读,写仍走节点授权)。 */
35
+ export function agentOpsScope(agentId: string): { lineIds: string[], nodeIds: Set<string>, boundLineIds: string[] } | null {
22
36
  const bindings = getAgentNodeBindingRepo().byAgent(agentId)
23
- if (bindings.length === 0) return null
37
+ const boundLine = channelBoundLineId(agentId)
38
+ const boundLineIds = boundLine ? [boundLine] : []
39
+ if (bindings.length === 0 && boundLineIds.length === 0) return null
24
40
  const lineIds = new Set<string>()
25
41
  const nodeIds = new Set<string>()
26
42
  for (const b of bindings) {
@@ -31,7 +47,17 @@ export function agentOpsScope(agentId: string): { lineIds: string[], nodeIds: Se
31
47
  }
32
48
  catch { /* 节点刚被删等情况忽略 */ }
33
49
  }
34
- return { lineIds: [...lineIds], nodeIds }
50
+ return { lineIds: [...lineIds], nodeIds, boundLineIds }
51
+ }
52
+
53
+ /** 读工具的可见产线全集 = 节点授权线 ∪ 频道绑定线(去重) */
54
+ function readableLineIds(scope: { lineIds: string[], boundLineIds: string[] }): string[] {
55
+ return [...new Set([...scope.lineIds, ...scope.boundLineIds])]
56
+ }
57
+
58
+ /** 产线展示标注:仅频道绑定(非节点授权)的线标注只读来源 */
59
+ function lineTag(lid: string, scope: { lineIds: string[], boundLineIds: string[] }): string {
60
+ return scope.lineIds.includes(lid) ? '' : '(频道绑定,只读)'
35
61
  }
36
62
 
37
63
  /** 配方参数的节点失效态(数据一致性展示/Agent 提示共用):null=正常 */
@@ -63,19 +89,20 @@ export async function toolOpsLog(agentId: string, args: {
63
89
  const audit = getOps()?.audit
64
90
  if (!audit) return { text: '审计仓储未装配(服务未就绪),请稍后重试。', isError: true }
65
91
  const scope = agentOpsScope(agentId)
66
- if (!scope) return { text: '你尚未绑定任何工业节点,无日志可查(日志权限跟随节点绑定)。', isError: true }
92
+ if (!scope) return { text: '你尚未绑定任何工业节点,所在频道也未绑定产线,无日志可查(日志权限跟随节点绑定或频道绑线)。', isError: true }
67
93
  const nodeId = String(args.node_id ?? '').trim()
68
94
  const lineId = String(args.line_id ?? '').trim()
95
+ const readable = readableLineIds(scope)
69
96
  if (nodeId && !scope.nodeIds.has(nodeId))
70
97
  return { text: `无权查询节点 ${nodeId} 的日志(仅可查自己绑定的节点;用 my_industrial_nodes 查看绑定)。`, isError: true }
71
- if (lineId && !scope.lineIds.includes(lineId))
72
- return { text: `无权查询产线 ${lineId} 的日志(你的负责产线:${scope.lineIds.join(', ') || '(绑定节点均未分配产线)'})。`, isError: true }
98
+ if (lineId && !readable.includes(lineId))
99
+ return { text: `无权查询产线 ${lineId} 的日志(你的可读产线:${readable.join(', ') || '(无)'})。`, isError: true }
73
100
  const kind = String(args.kind ?? '').trim() || undefined
74
101
  const actorKind = String(args.actor_kind ?? '').trim() || undefined
75
102
  const mine = args.mine === true || args.mine === 'true'
76
103
  const { minutes, limit, from } = windowOf(args)
77
104
 
78
- const lines = lineId ? [lineId] : scope.lineIds
105
+ const lines = lineId ? [lineId] : readable
79
106
  let rows = fanoutQuery(audit, lines, lid => ({ lineId: lid, kind, actorKind, actor: mine ? agentId : undefined, from, limit }))
80
107
  if (nodeId) rows = rows.filter(r => r.targetId === nodeId)
81
108
  rows.sort((a, b) => String(b.at).localeCompare(String(a.at)))
@@ -86,7 +113,7 @@ export async function toolOpsLog(agentId: string, args: {
86
113
  const src = OPS_ACTOR_LABEL[String(r.actorKind)] ?? String(r.actorKind)
87
114
  return `- [${fmtAuditAt(String(r.at))}] 来源=${src} 操作者=${String(r.actorName) || String(r.actor)} | ${String(r.action)} | ${String(r.summary)}`
88
115
  })
89
- const scopeNote = lineId ? `产线 ${lineId}` : `负责产线 ${scope.lineIds.length} 条`
116
+ const scopeNote = lineId ? `产线 ${lineId}` : `可读产线 ${readable.length} 条`
90
117
  return {
91
118
  text: `运维日志(${scopeNote},近 ${minutes} 分钟,${rows.length} 条,新→旧):\n${body.join('\n')}\n\n说明:来源=Agent 的操作者格式为「Channel名/成员名」;需要节点级参数值变更史用 dcw_journal,配方下发/回退专门视图用 recipe_log。`,
92
119
  }
@@ -103,14 +130,15 @@ export async function toolRecipeLog(agentId: string, args: {
103
130
  const audit = getOps()?.audit
104
131
  if (!audit) return { text: '审计仓储未装配(服务未就绪),请稍后重试。', isError: true }
105
132
  const scope = agentOpsScope(agentId)
106
- if (!scope) return { text: '你尚未绑定任何工业节点,无 Recipe 历史可查(权限跟随节点绑定)。', isError: true }
133
+ if (!scope) return { text: '你尚未绑定任何工业节点,所在频道也未绑定产线,无 Recipe 历史可查(权限跟随节点绑定或频道绑线)。', isError: true }
107
134
  const lineId = String(args.line_id ?? '').trim()
108
- if (lineId && !scope.lineIds.includes(lineId))
109
- return { text: `无权查询产线 ${lineId} 的 Recipe 历史(你的负责产线:${scope.lineIds.join(', ') || '(无)'})。`, isError: true }
135
+ const readable = readableLineIds(scope)
136
+ if (lineId && !readable.includes(lineId))
137
+ return { text: `无权查询产线 ${lineId} 的 Recipe 历史(你的可读产线:${readable.join(', ') || '(无)'})。`, isError: true }
110
138
  const recipeId = String(args.recipe_id ?? '').trim() || undefined
111
139
  const { minutes, limit, from } = windowOf(args)
112
140
 
113
- const lines = lineId ? [lineId] : scope.lineIds
141
+ const lines = lineId ? [lineId] : readable
114
142
  const rows = fanoutQuery(audit, lines, lid => [
115
143
  { lineId: lid, kind: 'recipe', recipeId, from, limit },
116
144
  { lineId: lid, kind: 'rollback', recipeId, from, limit },
@@ -131,9 +159,8 @@ export async function toolRecipeLog(agentId: string, args: {
131
159
  : action
132
160
  return `- [${fmtAuditAt(String(r.at))}] ${tag} 来源=${src} 操作者=${String(r.actorName) || String(r.actor)} | ${String(r.summary)}${r.recipeId ? `(配方 ${String(r.recipeId).slice(0, 8)})` : ''}`
133
161
  })
134
- return {
135
- text: `Recipe 变更史(${lineId ? `产线 ${lineId}` : `负责产线 ${scope.lineIds.length} 条`},近 ${minutes} 分钟,${rows.length} 条,新→旧):\n${body.join('\n')}\n\n说明:配方下发=整批参数写命令;优化开窗=单次设定变更(含 Agent 假设);回退执行=已恢复基线值。节点级参数值逐笔历史用 dcw_journal(node_id)。`,
136
- }
162
+ const text = `Recipe 变更史(${lineId ? `产线 ${lineId}` : `可读产线 ${readable.length} 条`},近 ${minutes} 分钟,${rows.length} 条,新→旧):\n${body.join('\n')}\n\n说明:配方下发=整批参数写命令;优化开窗=单次设定变更(含 Agent 假设);回退执行=已恢复基线值。节点级参数值逐笔历史用 dcw_journal(node_id)。`
163
+ return { text }
137
164
  }
138
165
 
139
166
  // ================================================================
@@ -145,11 +172,12 @@ export async function toolRecipeLog(agentId: string, args: {
145
172
  * 配方目标 vs PLC 当前值对照、lastGood 批次。 */
146
173
  export async function toolLineContext(agentId: string, args: { line_id?: string } = {}): Promise<{ text: string, isError?: boolean }> {
147
174
  const scope = agentOpsScope(agentId)
148
- if (!scope) return { text: '你尚未绑定任何工业节点,暂无产线上下文(请先在数字孪生界面绑定节点)。' }
175
+ if (!scope) return { text: '你尚未绑定任何工业节点,所在频道也未绑定产线,暂无产线上下文(可在频道设置绑定产线获得只读上下文,或在数字孪生界面绑定节点)。' }
149
176
  const wanted = String(args.line_id ?? '').trim()
150
- const lineIds = wanted ? [wanted] : scope.lineIds
151
- if (wanted && !scope.lineIds.includes(wanted)) {
152
- return { text: `产线 ${wanted} 不在你的负责范围(你绑定节点覆盖的产线:${scope.lineIds.join(', ') || '无'})。`, isError: true }
177
+ const readable = readableLineIds(scope)
178
+ const lineIds = wanted ? [wanted] : readable
179
+ if (wanted && !readable.includes(wanted)) {
180
+ return { text: `产线 ${wanted} 不在你的可读范围(节点授权与频道绑定覆盖:${readable.join(', ') || '无'})。`, isError: true }
153
181
  }
154
182
  if (lineIds.length === 0) return { text: '你绑定的节点均未分配产线(未挂线的节点没有产线/产品/配方上下文)。' }
155
183
 
@@ -159,7 +187,7 @@ export async function toolLineContext(agentId: string, args: { line_id?: string
159
187
  const run = getActiveLineRun(lid)
160
188
  const recipe = run?.recipeId ? getDcwController().listRecipes().find(r => r.id === run.recipeId) : undefined
161
189
  const parts: string[] = []
162
- parts.push(`■ 产线 ${line?.name ?? lid}(${lid})· 状态:${run ? '运行中' : '待机/停线'}`)
190
+ parts.push(`■ 产线 ${line?.name ?? lid}(${lid})${lineTag(lid, scope)} · 状态:${run ? '运行中' : '待机/停线'}`)
163
191
  if (run) {
164
192
  parts.push(` 活动批次 ${run.runId.slice(0, 8)} · 产品「${run.productName}」(${run.productId}) · 开跑 ${run.startedAt.slice(11, 19)} · 已打标 ${run.taggedSamples} 样本`)
165
193
  }
@@ -210,7 +238,7 @@ export async function toolLineContext(agentId: string, args: { line_id?: string
210
238
  sections.push(parts.join('\n'))
211
239
  }
212
240
  return {
213
- text: `你控制的产线全景(${sections.length} 条):\n\n${sections.join('\n\n')}\n\n说明:配方目标=开跑批次冻结的工艺窗口;PLC 当前值=实时读数。把验证过的最佳参数固化到配方用 recipe_update;回退配方到稳定版本用 recipe_rollback;逐节点参数变更史用 dcw_journal;配方版本史用 recipe_versions。`,
241
+ text: `你控制的产线全景(${sections.length} 条):\n\n${sections.join('\n\n')}\n\n说明:配方目标=开跑批次冻结的工艺窗口;PLC 当前值=实时读数;(频道绑定,只读)= 该线来自频道绑定的只读授权,可看日志/配方/状态,参数下发仍需节点授权。把验证过的最佳参数固化到配方用 recipe_update;回退配方到稳定版本用 recipe_rollback;逐节点参数变更史用 dcw_journal;配方版本史用 recipe_versions。`,
214
242
  }
215
243
  }
216
244
 
@@ -221,9 +249,11 @@ export async function toolRecipeVersions(agentId: string, args: { recipe_id?: st
221
249
  if (!recipeId) return { text: 'recipe_id 必填(line_context 可看到当前配方的 id)。', isError: true }
222
250
  const recipe = getDcwController().listRecipes().find(r => r.id === recipeId)
223
251
  if (!recipe) return { text: `配方 ${recipeId} 不存在。`, isError: true }
252
+ // 只读面:节点授权线 ∪ 频道绑定线均可看版本史(回退走 toolRecipeRollback 的写门)
224
253
  const scope = agentOpsScope(agentId)
225
- if (!scope || !recipe.lineId || !scope.lineIds.includes(recipe.lineId)) {
226
- return { text: `无权查看配方 ${recipeId} 的版本史(该配方不在你负责的产线上)。`, isError: true }
254
+ const readable = scope ? readableLineIds(scope) : []
255
+ if (!recipe.lineId || !readable.includes(recipe.lineId)) {
256
+ return { text: `无权查看配方 ${recipeId} 的版本史(该配方不在你可读的产线上)。`, isError: true }
227
257
  }
228
258
  const rows = getDcwController().recipeVersions(recipeId)
229
259
  const limit = Math.min(Number(args.limit) || 20, 50)
@@ -327,16 +357,19 @@ export async function toolRecipeUpdate(agentId: string, args: {
327
357
  }
328
358
 
329
359
  /** 工具:recipe_rollback —— 回退配方参数到稳定版本(指定历史版本或已知良好批次快照)。
330
- * 生成新版本(非破坏,历史保留);不改运行中 PLC 当前值。 */
360
+ * 生成新版本(非破坏,历史保留);不改运行中 PLC 当前值。
361
+ * v19 dispatch=true = **统一回退**:定义回退 + 该版本参数整批重下发到 PLC(批次级恢复)。 */
331
362
  export async function toolRecipeRollback(agentId: string, args: {
332
363
  recipe_id?: string
333
364
  version?: number | string
334
365
  to_last_good?: boolean | string
366
+ dispatch?: boolean | string
335
367
  reason?: string
336
368
  }): Promise<{ text: string, isError?: boolean }> {
337
369
  const recipeId = String(args.recipe_id ?? '').trim()
338
370
  const reason = String(args.reason ?? '').trim()
339
371
  const toLastGood = args.to_last_good === true || args.to_last_good === 'true'
372
+ const dispatch = args.dispatch === true || args.dispatch === 'true'
340
373
  const version = Number(args.version)
341
374
  if (!recipeId) return { text: 'recipe_id 必填。', isError: true }
342
375
  if (!reason) return { text: 'reason 必填:回退必须说明原因(如优化翻车/越限),便于版本史追溯。', isError: true }
@@ -351,6 +384,20 @@ export async function toolRecipeRollback(agentId: string, args: {
351
384
  return { text: `不能回退到 v${version}(当前已是 v${recipe.version ?? 1};回退目标是更早的版本)。`, isError: true }
352
385
  }
353
386
  try {
387
+ if (dispatch) {
388
+ const { recipe: updated, run } = await getDcwController().rollbackRecipeAndDispatch(recipeId, {
389
+ version: toLastGood ? undefined : version,
390
+ toLastGood,
391
+ by: 'agent',
392
+ actorName: agentBadgeLabel(agentId),
393
+ actor: agentId,
394
+ description: reason,
395
+ })
396
+ const okN = run.results.filter(r => r.ok).length
397
+ return {
398
+ text: `统一回退完成:配方「${updated.name}」已生成 v${updated.version ?? 1},参数恢复为目标版本(${toLastGood ? '已知良好批次冻结' : `v${version}`}),并已整批重下发到 PLC(${okN}/${run.results.length} 参数成功);原因:${reason}。\n用 daq_query 复测确认恢复效果;版本史用 recipe_versions 复核。`,
399
+ }
400
+ }
354
401
  const updated = getDcwController().revertRecipe(recipeId, {
355
402
  version: toLastGood ? undefined : version,
356
403
  toLastGood,
@@ -361,10 +408,105 @@ export async function toolRecipeRollback(agentId: string, args: {
361
408
  description: reason,
362
409
  })
363
410
  return {
364
- text: `回退完成:配方「${updated.name}」已生成 v${updated.version ?? 1},参数恢复为目标版本(${toLastGood ? '已知良好批次冻结' : `v${version}`});原因:${reason}。\n运行中批次不受影响(仍按开跑冻结参数);新参数下次开跑生效。版本史用 recipe_versions 复核。`,
411
+ text: `回退完成:配方「${updated.name}」已生成 v${updated.version ?? 1},参数恢复为目标版本(${toLastGood ? '已知良好批次冻结' : `v${version}`});原因:${reason}。\n注意:仅回退了配方定义,运行中 PLC 未变动;要把恢复参数写入产线(统一回退),用 recipe_rollback 并带 dispatch=true。版本史用 recipe_versions 复核。`,
365
412
  }
366
413
  }
367
414
  catch (err) {
368
415
  return { text: `回退失败:${err instanceof Error ? err.message : String(err)}`, isError: true }
369
416
  }
370
417
  }
418
+
419
+ /** 工具:recipe_trial —— 多参数候选整批试验下发(v19 配方链路核心)。
420
+ * 候选值只覆盖列出节点(**必须是自己绑定的数控节点**);整批一次落产线,**不写配方版本** ——
421
+ * 复测判读:有进步/达标 → recipe_update 固化(同 id 版本+1);劣化 → recipe_rollback(dispatch=true) 统一回退。
422
+ * 受同线试验节拍卡控(默认 ≥5 分钟,防连发震荡);四层限界(量程∩参数∩产品)照常拦截。 */
423
+ export async function toolRecipeTrial(agentId: string, args: {
424
+ recipe_id?: string
425
+ params?: Array<{ node_id?: string, value?: number | string }>
426
+ hypothesis?: string
427
+ }): Promise<{ text: string, isError?: boolean }> {
428
+ const recipeId = String(args.recipe_id ?? '').trim()
429
+ const hypothesis = String(args.hypothesis ?? '').trim()
430
+ if (!recipeId) return { text: 'recipe_id 必填(line_context 可看到当前配方 id)。', isError: true }
431
+ if (!hypothesis) return { text: 'hypothesis 必填:试验必须声明依据(知识库条目/数据分析结论),便于审计追溯。', isError: true }
432
+ const list = (args.params ?? []).filter(p => p && String(p.node_id ?? '').trim() && Number.isFinite(Number(p.value)))
433
+ .map(p => ({ nodeId: String(p.node_id).trim(), value: Number(p.value) }))
434
+ if (list.length === 0) return { text: 'params 至少提供一条 {node_id, value}(value 必须为数字);试验的意义就是多参数一次整批改动。', isError: true }
435
+ const recipe = getDcwController().listRecipes().find(r => r.id === recipeId)
436
+ if (!recipe) return { text: `配方 ${recipeId} 不存在。`, isError: true }
437
+ const repo = getAgentNodeBindingRepo()
438
+ for (const p of list) {
439
+ const node = getDcwController().byId(p.nodeId)
440
+ if (!node) return { text: `节点 ${p.nodeId} 已删除,不能纳入试验。`, isError: true }
441
+ if (node.lineId !== recipe.lineId) return { text: `节点「${node.name}」不在配方「${recipe.name}」所在产线,不可混入试验。`, isError: true }
442
+ if (!node.enabled) return { text: `节点「${node.name}」已停用,试验被拒。`, isError: true }
443
+ if (!repo.find(agentId, p.nodeId, 'dcw')) {
444
+ return { text: `无权试验节点「${node.name}」(仅可改自己绑定的数控节点;缺授权找 lead 用 team_grant_nodes 授予)。`, isError: true }
445
+ }
446
+ }
447
+ try {
448
+ const run = await getDcwController().applyRecipe(recipeId, { overrides: list, trial: true })
449
+ const okN = run.results.filter(r => r.ok).length
450
+ // 批次级留痕:试验动作入审计(逐参数写已有 write-audit;这里是"一次试验"粒度)
451
+ try {
452
+ const { recordOps } = await import('../../ops/ops')
453
+ recordOps({
454
+ actor: agentId, actorName: agentBadgeLabel(agentId), actorKind: 'agent',
455
+ action: 'recipe.trial', kind: 'recipe', targetKind: 'recipe', targetId: recipeId, recipeId, lineId: recipe.lineId,
456
+ summary: `配方试验整批下发(${okN}/${run.results.length} 成功,批次 ${run.id.slice(0, 8)}):${list.map(p => `${p.nodeId.slice(0, 8)}→${p.value}`).join(';')} | 假设:${hypothesis}`,
457
+ })
458
+ }
459
+ catch { /* 审计失败不影响试验结果 */ }
460
+ const changed = run.results.map((r) => {
461
+ const node = r.nodeId ? getDcwController().byId(r.nodeId) : undefined
462
+ const reason = r.ok ? '' : `(${String(r.message).slice(0, 60)})`
463
+ return `${node?.name ?? r.templateRef}→${r.value}${node?.unit ?? ''}${r.ok ? '' : ` 被拒${reason}`}`
464
+ }).join(';')
465
+ return {
466
+ text: `试验已整批下发(${okN}/${run.results.length} 成功,批次 ${run.id.slice(0, 8)}):${changed}。假设:${hypothesis}。注意:本次试验**未写入配方版本** —— 等待工艺惯性后 daq_query 复测判读:有进步/达标 → recipe_update 把候选值固化进配方(同 id 版本+1);无进步/劣化 → recipe_rollback(recipe_id, dispatch=true, reason=…) 统一回退(定义回退+PLC 整批恢复)。`,
467
+ }
468
+ }
469
+ catch (err) {
470
+ return { text: `试验下发失败:${err instanceof Error ? err.message : String(err)}`, isError: true }
471
+ }
472
+ }
473
+
474
+ /** 工具:recipe_apply —— 把配方当前已固化参数整批下发到产线(改配方(recipe_update)之后用)。
475
+ * 与 recipe_trial 的区别:apply 下发的是**已固化版本**(无候选覆盖),用于"改配方 → 配方下发"链路的正式下发。 */
476
+ export async function toolRecipeApply(agentId: string, args: {
477
+ recipe_id?: string
478
+ reason?: string
479
+ }): Promise<{ text: string, isError?: boolean }> {
480
+ const recipeId = String(args.recipe_id ?? '').trim()
481
+ if (!recipeId) return { text: 'recipe_id 必填(line_context 可看到当前配方 id)。', isError: true }
482
+ const recipe = getDcwController().listRecipes().find(r => r.id === recipeId)
483
+ if (!recipe) return { text: `配方 ${recipeId} 不存在。`, isError: true }
484
+ const scope = agentOpsScope(agentId)
485
+ if (!scope || !recipe.lineId || !scope.lineIds.includes(recipe.lineId)) {
486
+ return { text: `无权下发配方 ${recipeId}(该配方不在你负责的产线上)。`, isError: true }
487
+ }
488
+ const boundInRecipe = getAgentNodeBindingRepo().byAgent(agentId)
489
+ .some(b => b.kind === 'dcw' && recipe.params.some(p => p.nodeId === b.nodeId))
490
+ if (!boundInRecipe) {
491
+ return { text: `无权下发配方 ${recipeId}(需持有该配方至少一个数控节点的授权;找 lead 用 team_grant_nodes 授予)。`, isError: true }
492
+ }
493
+ try {
494
+ const run = await getDcwController().applyRecipe(recipeId)
495
+ const okN = run.results.filter(r => r.ok).length
496
+ try {
497
+ const { recordOps } = await import('../../ops/ops')
498
+ recordOps({
499
+ actor: agentId, actorName: agentBadgeLabel(agentId), actorKind: 'agent',
500
+ action: 'recipe.apply', kind: 'recipe', targetKind: 'recipe', targetId: recipeId, recipeId, lineId: recipe.lineId,
501
+ summary: `Agent 整批下发配方 v${recipe.version ?? 1}(${okN}/${run.results.length} 参数成功,批次 ${run.id.slice(0, 8)})${args.reason ? ` | 原因:${String(args.reason).trim()}` : ''}`,
502
+ })
503
+ }
504
+ catch { /* 审计失败不影响下发结果 */ }
505
+ return {
506
+ text: `配方「${recipe.name}」v${recipe.version ?? 1} 已整批下发(${okN}/${run.results.length} 参数成功,批次 ${run.id.slice(0, 8)})。用 daq_query 复测确认工艺响应。`,
507
+ }
508
+ }
509
+ catch (err) {
510
+ return { text: `配方下发失败:${err instanceof Error ? err.message : String(err)}`, isError: true }
511
+ }
512
+ }
@@ -27,4 +27,6 @@ export const HYBRID_TWIN_DIRECT_WRITE_TOOL_NAMES = new Set([
27
27
  'dcw_rollback',
28
28
  'recipe_update',
29
29
  'recipe_rollback',
30
+ 'recipe_trial',
31
+ 'recipe_apply',
30
32
  ])
@@ -194,7 +194,9 @@ export async function buildDataset(spec: AmlDatasetSpec, by: { id: string, kind:
194
194
  continue
195
195
  }
196
196
 
197
- // 公共网格:[max(t0), min(t1)];逐节点最近邻对齐,网格点缺失计 NaN
197
+ // 公共网格:[max(t0), min(t1)];逐节点最近邻对齐,网格点缺失计 NaN。
198
+ // 全节点查空(如该 run 采样缺失/时序库丢数)时 g0/g1 保持 ±Infinity,
199
+ // 必须按丢弃处理 —— 否则 steps=Infinity 会把网格循环推到 RangeError(500)。
198
200
  let g0 = -Infinity
199
201
  let g1 = Infinity
200
202
  for (const n of order) {
@@ -206,7 +208,7 @@ export async function buildDataset(spec: AmlDatasetSpec, by: { id: string, kind:
206
208
  g0 = Math.max(g0, first.at)
207
209
  g1 = Math.min(g1, last.at)
208
210
  }
209
- const steps = g1 > g0 ? Math.floor((g1 - g0) / beat) + 1 : 0
211
+ const steps = Number.isFinite(g0) && Number.isFinite(g1) && g1 > g0 ? Math.floor((g1 - g0) / beat) + 1 : 0
210
212
  if (steps < spec.window.historySteps + spec.window.horizonSteps + 2) {
211
213
  runsDropped.push({ runId: run.id, reason: `有效网格 ${steps} 步不足一个窗口` })
212
214
  continue
@@ -96,5 +96,6 @@ export function handleProtocolLine(run: RunningJob, rawLine: string): void {
96
96
  }
97
97
  else if (evt.type === 'error' && evt.message) {
98
98
  pushLog(run, `[error] ${evt.message}`)
99
+ run.lastProtocolError = String(evt.message).slice(0, 300) // 结题失败原因优先用它(见 start-job 评估段)
99
100
  }
100
101
  }
@@ -31,6 +31,8 @@ export interface RunningJob {
31
31
  logLines: string[]
32
32
  protocolBadLines: number
33
33
  killed: boolean
34
+ /** 最近一条 ##AML error 协议消息(评估器的人读失败原因走 stdout;stderr 尾常为空) */
35
+ lastProtocolError?: string
34
36
  }
35
37
 
36
38
  export interface OrchestratorState {
@@ -140,8 +140,11 @@ export async function startJob(row: AmlJobRow): Promise<void> {
140
140
  const exit2 = await runPython(run, vpy, [evalScript, '--job', join(jobDir, 'job.json')], jobDir)
141
141
  if (run.killed) return
142
142
  if (exit2.code !== 0) {
143
- rt.repo.job.fail(row.id, 'failed', `评估失败:${lastErrLine(exit2.errTail)}(永久错误不重试)`, new Date().toISOString())
144
- experimentFail(run, `评估失败:${lastErrLine(exit2.errTail)}`)
143
+ // 失败原因优先取评估器的 ##AML error 协议消息(如「test 切分为空…」,人读可操作);
144
+ // stderr 尾兜底 —— 评估器把错误走 stdout 协议行时 errTail 常为空,只给 "exit" 无从排查。
145
+ const reason = run.lastProtocolError || lastErrLine(exit2.errTail)
146
+ rt.repo.job.fail(row.id, 'failed', `评估失败:${reason}(永久错误不重试)`, new Date().toISOString())
147
+ experimentFail(run, `评估失败:${reason}`)
145
148
  finishRun(run)
146
149
  return
147
150
  }