agentworkshop 0.7.49 → 0.7.51

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 (887) hide show
  1. package/.AgentWorkShop/prompts/host-tools.json +409 -6
  2. package/.output/.AgentWorkShop/aml-python/__pycache__/aml_eval.cpython-312.pyc +0 -0
  3. package/.output/.AgentWorkShop/aml-python/__pycache__/amlkit.cpython-312.pyc +0 -0
  4. package/.output/.AgentWorkShop/aml-python/__pycache__/train-example.cpython-312.pyc +0 -0
  5. package/.output/.AgentWorkShop/aml-python/__pycache__/train-hybrid-example.cpython-312.pyc +0 -0
  6. package/.output/.AgentWorkShop/aml-python/aml_eval.py +39 -6
  7. package/.output/.AgentWorkShop/aml-python/amlkit.py +222 -3
  8. package/.output/.AgentWorkShop/aml-python/train-hybrid-example.py +278 -32
  9. package/.output/.AgentWorkShop/prompts/host-tools.json +409 -6
  10. package/.output/.AgentWorkShop/schema.json +135 -133
  11. package/.output/nitro.json +1 -1
  12. package/.output/public/_nuxt/2_DkYoqD2.js +1 -0
  13. package/.output/public/_nuxt/8FXd9As6.js +1 -0
  14. package/.output/public/_nuxt/{d-w5jR8R.js → B3zB6K6m.js} +1 -1
  15. package/.output/public/_nuxt/{OXtV_ukR.js → B4ujjkUI.js} +1 -1
  16. package/.output/public/_nuxt/B9BmiUil.js +2 -0
  17. package/.output/public/_nuxt/B9QzrmgE.js +1 -0
  18. package/.output/public/_nuxt/BE7BrEw6.js +1 -0
  19. package/.output/public/_nuxt/{Cu8c3oMN2.js → BFZIll912.js} +1 -1
  20. package/.output/public/_nuxt/{ChtjzEpK2.js → BGAaKBeo2.js} +1 -1
  21. package/.output/public/_nuxt/BOmDHp5L.js +14 -0
  22. package/.output/public/_nuxt/BPou3U7b.js +1 -0
  23. package/.output/public/_nuxt/{CiTQY_n32.js → BXx9AxdF2.js} +1 -1
  24. package/.output/public/_nuxt/Bfe2qOYs.js +1 -0
  25. package/.output/public/_nuxt/BgEDwFMf.js +1 -0
  26. package/.output/public/_nuxt/BgnhB8r5.js +1 -0
  27. package/.output/public/_nuxt/Bhe953zV2.js +1 -0
  28. package/.output/public/_nuxt/{BITZvQBK.js → Bpkm4W5P.js} +1 -1
  29. package/.output/public/_nuxt/C4lagVdN2.js +1 -0
  30. package/.output/public/_nuxt/C5cf3T4d.js +1 -0
  31. package/.output/public/_nuxt/CBIoheY42.js +1 -0
  32. package/.output/public/_nuxt/{CN1CDAwe.js → CFQSjB1h.js} +1 -1
  33. package/.output/public/_nuxt/CFQyYMEn2.js +1 -0
  34. package/.output/public/_nuxt/CHrErv1E.js +1 -0
  35. package/.output/public/_nuxt/{fxWyOzyp2.js → CI2yXRr42.js} +1 -1
  36. package/.output/public/_nuxt/CIduggmC.js +1 -0
  37. package/.output/public/_nuxt/CU-mhG0-.js +1 -0
  38. package/.output/public/_nuxt/CXzfXC5M.js +1 -0
  39. package/.output/public/_nuxt/{H6pPjo0l.js → C_sjV3Ee.js} +1 -1
  40. package/.output/public/_nuxt/Ccps4cxo.js +1 -0
  41. package/.output/public/_nuxt/{CPmNRTBk2.js → CdIsmU5a2.js} +10 -10
  42. package/.output/public/_nuxt/{BS9P_Ies2.js → ChGuLxwW2.js} +1 -1
  43. package/.output/public/_nuxt/{Ck5oMvUk.js → Cn6ihm8j.js} +1 -1
  44. package/.output/public/_nuxt/{CWzp2R6G.js → D-Xyb3DA.js} +1 -1
  45. package/.output/public/_nuxt/D0SNhlnt.js +1 -0
  46. package/.output/public/_nuxt/D0ldVwl-.js +1 -0
  47. package/.output/public/_nuxt/DClNt1uS.js +1 -0
  48. package/.output/public/_nuxt/{CTVfcnjU.js → DQMbdg4t.js} +2 -2
  49. package/.output/public/_nuxt/{DHSDk8Bq2.js → DTxfFIvL2.js} +1 -1
  50. package/.output/public/_nuxt/DVKXGuMz2.js +1 -0
  51. package/.output/public/_nuxt/{CLySL7mW.js → Da0h_6VA.js} +1 -1
  52. package/.output/public/_nuxt/{PixDfCSm.js → DefDtlKK.js} +1 -1
  53. package/.output/public/_nuxt/DjefmmJu.js +1 -0
  54. package/.output/public/_nuxt/DnH9SRMW2.js +1 -0
  55. package/.output/public/_nuxt/{BmiMC_BC.js → DppqsiPC.js} +1 -1
  56. package/.output/public/_nuxt/Drq9iy6V.js +1 -0
  57. package/.output/public/_nuxt/DsGf2IV82.js +1 -0
  58. package/.output/public/_nuxt/{DoKpLEqc.js → DzfVS8N72.js} +1 -1
  59. package/.output/public/_nuxt/{DRpFsrno.js → I1cI6sPJ2.js} +1 -1
  60. package/.output/public/_nuxt/IU-UGkCJ2.js +1 -0
  61. package/.output/public/_nuxt/KXjvpIk2.js +1 -0
  62. package/.output/public/_nuxt/NJn94js_.js +1 -0
  63. package/.output/public/_nuxt/ON2x-t6n.js +1 -0
  64. package/.output/public/_nuxt/S799kNo8.js +1 -0
  65. package/.output/public/_nuxt/{uI9Ki_XZ.js → S9rLP7_v.js} +1 -1
  66. package/.output/public/_nuxt/UN3nSFjj2.js +1 -0
  67. package/.output/public/_nuxt/WVTVkqef.js +1 -0
  68. package/.output/public/_nuxt/{C_VAXV7I2.js → Yv9YItEX2.js} +1 -1
  69. package/.output/public/_nuxt/_id_.DamiFT0Q.css +1 -0
  70. package/.output/public/_nuxt/{_id_.BTrFUIHX.css → _id_.DpRe1PRh.css} +1 -1
  71. package/.output/public/_nuxt/_wsId_.DkrQkT6r.css +1 -0
  72. package/.output/public/_nuxt/aml.-czpKIs8.css +1 -0
  73. package/.output/public/_nuxt/asmJ99nX2.js +47 -0
  74. package/.output/public/_nuxt/builds/latest.json +1 -1
  75. package/.output/public/_nuxt/builds/meta/912eb5bb-670e-4cc8-b21d-7fc097723466.json +1 -0
  76. package/.output/public/_nuxt/{default.uao9U50c.css → default.CXb_zoN6.css} +1 -1
  77. package/.output/public/_nuxt/{entry.6DEjPWvR.css → entry.JdEG3KJg.css} +1 -1
  78. package/.output/public/_nuxt/jRTz0hjN2.js +1 -0
  79. package/.output/public/_nuxt/{DLPuRMeG.js → k5K6Jj6g.js} +1 -1
  80. package/.output/public/_nuxt/operations.B8X9uWK6.css +1 -0
  81. package/.output/public/_nuxt/{settings.Bo5EYkLI.css → settings.O26Dw1iT.css} +1 -1
  82. package/.output/public/_nuxt/{BACBBVdw.js → tKxYgsf-.js} +1 -1
  83. package/.output/public/_nuxt/{Cm_PTn9t2.js → vXrNItas2.js} +1 -1
  84. package/.output/public/_nuxt/{Cr518Gh02.js → zsRclU9w2.js} +1 -1
  85. package/.output/server/chunks/_/LeftOutlined.mjs +1 -1
  86. package/.output/server/chunks/_/SearchOutlined.mjs +1 -1
  87. package/.output/server/chunks/_/binding-authz.mjs +1 -1
  88. package/.output/server/chunks/_/character-asset.repo.mjs +1 -1
  89. package/.output/server/chunks/_/entity.mjs +1 -1
  90. package/.output/server/chunks/_/host-bindings.mjs +1 -1
  91. package/.output/server/chunks/_/nitro.mjs +26245 -21135
  92. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  93. package/.output/server/chunks/_/scene-layout.repo.mjs +1 -1
  94. package/.output/server/chunks/build/Checkbox-4EdGAAl_.mjs +1 -1
  95. package/.output/server/chunks/build/FormItemContext-4ORSzhWv.mjs +1 -1
  96. package/.output/server/chunks/build/{OmpTerminalPanel-EQHrOJbw.mjs → OmpTerminalPanel-mk31gVzA.mjs} +3 -3
  97. package/.output/server/chunks/build/{OmpTerminalPanel-EQHrOJbw.mjs.map → OmpTerminalPanel-mk31gVzA.mjs.map} +1 -1
  98. package/.output/server/chunks/build/{PluginSlot-BP_qedpQ.mjs → PluginSlot-D2WUZgbp.mjs} +2 -2
  99. package/.output/server/chunks/build/{PluginSlot-BP_qedpQ.mjs.map → PluginSlot-D2WUZgbp.mjs.map} +1 -1
  100. package/.output/server/chunks/build/{TownView-BAR8CPIZ.mjs → TownView-fP9BiALv.mjs} +6 -6
  101. package/.output/server/chunks/build/{TownView-BAR8CPIZ.mjs.map → TownView-fP9BiALv.mjs.map} +1 -1
  102. package/.output/server/chunks/build/{_id_-DgMpjM3_.mjs → _id_-BUpQSlzo.mjs} +142 -106
  103. package/.output/server/chunks/build/_id_-BUpQSlzo.mjs.map +1 -0
  104. package/.output/server/chunks/build/{_id_-Dv3BQbYV.mjs → _id_-CDlur81q.mjs} +25 -24
  105. package/.output/server/chunks/build/_id_-CDlur81q.mjs.map +1 -0
  106. package/.output/server/chunks/build/{_wsId_-BKJvFmyf.mjs → _wsId_-DPy2BXRd.mjs} +547 -158
  107. package/.output/server/chunks/build/_wsId_-DPy2BXRd.mjs.map +1 -0
  108. package/.output/server/chunks/build/{agents-B2GLPmn2.mjs → agents-CDLcboCv.mjs} +12 -12
  109. package/.output/server/chunks/build/{agents-B2GLPmn2.mjs.map → agents-CDLcboCv.mjs.map} +1 -1
  110. package/.output/server/chunks/build/{aml-DdWicH3t.mjs → aml-DsgtDLWF.mjs} +28 -27
  111. package/.output/server/chunks/build/aml-DsgtDLWF.mjs.map +1 -0
  112. package/.output/server/chunks/build/api-BY6ji7RC.mjs +6 -6
  113. package/.output/server/chunks/build/card-CUSocE9p.mjs +1 -1
  114. package/.output/server/chunks/build/{channel-templates-RPOtkgRf.mjs → channel-templates-DjEsj0_v.mjs} +8 -8
  115. package/.output/server/chunks/build/{channel-templates-RPOtkgRf.mjs.map → channel-templates-DjEsj0_v.mjs.map} +1 -1
  116. package/.output/server/chunks/build/checkbox-C4AKj81e.mjs +1 -1
  117. package/.output/server/chunks/build/constants-HPHJvjne.mjs +6 -6
  118. package/.output/server/chunks/build/{daq-CPtsh0e7.mjs → daq-N69AOtgw.mjs} +12 -11
  119. package/.output/server/chunks/build/daq-N69AOtgw.mjs.map +1 -0
  120. package/.output/server/chunks/build/{dcw-ry5Gyw6Y.mjs → dcw-CN9d3Rs7.mjs} +10 -10
  121. package/.output/server/chunks/build/{dcw-ry5Gyw6Y.mjs.map → dcw-CN9d3Rs7.mjs.map} +1 -1
  122. package/.output/server/chunks/build/{default-DxOE1eB0.mjs → default-DCf9tn1i.mjs} +38 -30
  123. package/.output/server/chunks/build/{default-DxOE1eB0.mjs.map → default-DCf9tn1i.mjs.map} +1 -1
  124. package/.output/server/chunks/build/descriptions-CFaUq4y4.mjs +1 -1
  125. package/.output/server/chunks/build/drawer-Cfh9_zft.mjs +1 -1
  126. package/.output/server/chunks/build/dropdown-KdKWCS5K.mjs +1 -1
  127. package/.output/server/chunks/build/form-uSlNqIAV.mjs +1 -1
  128. package/.output/server/chunks/build/input-Cr_KFj4T.mjs +1 -1
  129. package/.output/server/chunks/build/input-number-BRVnxeiA.mjs +1 -1
  130. package/.output/server/chunks/build/{logs-CeaiO3y9.mjs → logs-BcAjU1Ps.mjs} +12 -11
  131. package/.output/server/chunks/build/logs-BcAjU1Ps.mjs.map +1 -0
  132. package/.output/server/chunks/build/menu-BgkRSSx7.mjs +1 -1
  133. package/.output/server/chunks/build/{monitor-DnuXEIi5.mjs → monitor-DX_K0WMq.mjs} +13 -13
  134. package/.output/server/chunks/build/{monitor-DnuXEIi5.mjs.map → monitor-DX_K0WMq.mjs.map} +1 -1
  135. package/.output/server/chunks/build/operations-OFHYXwh4.mjs +282 -0
  136. package/.output/server/chunks/build/operations-OFHYXwh4.mjs.map +1 -0
  137. package/.output/server/chunks/build/{pages-Bn90ijKD.mjs → pages-rZKH72eC.mjs} +10 -11
  138. package/.output/server/chunks/build/pages-rZKH72eC.mjs.map +1 -0
  139. package/.output/server/chunks/build/{permissions-DL70W1YD.mjs → permissions-BvmodvsO.mjs} +8 -8
  140. package/.output/server/chunks/build/{permissions-DL70W1YD.mjs.map → permissions-BvmodvsO.mjs.map} +1 -1
  141. package/.output/server/chunks/build/{plugins-DrsZo--K.mjs → plugins-DSka7-vS.mjs} +9 -9
  142. package/.output/server/chunks/build/{plugins-DrsZo--K.mjs.map → plugins-DSka7-vS.mjs.map} +1 -1
  143. package/.output/server/chunks/build/popconfirm-Ox-_2V8A.mjs +1 -1
  144. package/.output/server/chunks/build/{schedules-VLzNcjnW.mjs → schedules-lo4bfgVt.mjs} +8 -8
  145. package/.output/server/chunks/build/{schedules-VLzNcjnW.mjs.map → schedules-lo4bfgVt.mjs.map} +1 -1
  146. package/.output/server/chunks/build/segmented-C61yLHvo.mjs +1 -1
  147. package/.output/server/chunks/build/select-rdBWytVK.mjs +1 -1
  148. package/.output/server/chunks/build/{settings-DVAUVMsX.mjs → settings-CAJWvul5.mjs} +32 -13
  149. package/.output/server/chunks/build/settings-CAJWvul5.mjs.map +1 -0
  150. package/.output/server/chunks/build/space-B2uhHJG8.mjs +1 -1
  151. package/.output/server/chunks/build/spin-C2cuvGth.mjs +1 -1
  152. package/.output/server/chunks/build/switch-DZAUIbIN.mjs +1 -1
  153. package/.output/server/chunks/build/table-DOFAvY-v.mjs +1 -1
  154. package/.output/server/chunks/build/tabs-DTeIuZeP.mjs +1 -1
  155. package/.output/server/chunks/build/tag-CHLGvNM6.mjs +1 -1
  156. package/.output/server/chunks/build/{teams-Mc07PYO8.mjs → teams-DBBvIToa.mjs} +14 -14
  157. package/.output/server/chunks/build/{teams-Mc07PYO8.mjs.map → teams-DBBvIToa.mjs.map} +1 -1
  158. package/.output/server/chunks/build/{tokens-DiwD5qni.mjs → tokens-CloWk4x_.mjs} +12 -12
  159. package/.output/server/chunks/build/{tokens-DiwD5qni.mjs.map → tokens-CloWk4x_.mjs.map} +1 -1
  160. package/.output/server/chunks/build/{town-D8VmTCmN.mjs → town-BMlctE4m.mjs} +12 -12
  161. package/.output/server/chunks/build/{town-D8VmTCmN.mjs.map → town-BMlctE4m.mjs.map} +1 -1
  162. package/.output/server/chunks/build/types-D6J7slEa.mjs +1 -1
  163. package/.output/server/chunks/build/{useAgentHarnessOptions-CPn5dzKe.mjs → useAgentHarnessOptions-D6SJ_uUr.mjs} +7 -7
  164. package/.output/server/chunks/build/{useAgentHarnessOptions-CPn5dzKe.mjs.map → useAgentHarnessOptions-D6SJ_uUr.mjs.map} +1 -1
  165. package/.output/server/chunks/build/{useAgentTemplateActions-DQWzVnZ0.mjs → useAgentTemplateActions-CdG4g_PF.mjs} +7 -7
  166. package/.output/server/chunks/build/{useAgentTemplateActions-DQWzVnZ0.mjs.map → useAgentTemplateActions-CdG4g_PF.mjs.map} +1 -1
  167. package/.output/server/chunks/build/useAgentTemplateDialogState-CZzpu5og.mjs +6 -6
  168. package/.output/server/chunks/build/{useAgentTemplateForm-vqyiRPtt.mjs → useAgentTemplateForm-a5y-N5sB.mjs} +7 -7
  169. package/.output/server/chunks/build/{useAgentTemplateForm-vqyiRPtt.mjs.map → useAgentTemplateForm-a5y-N5sB.mjs.map} +1 -1
  170. package/.output/server/chunks/build/{useAgentTemplatesCatalog-DcQbsOik.mjs → useAgentTemplatesCatalog-pBVcUuN3.mjs} +7 -7
  171. package/.output/server/chunks/build/{useAgentTemplatesCatalog-DcQbsOik.mjs.map → useAgentTemplatesCatalog-pBVcUuN3.mjs.map} +1 -1
  172. package/.output/server/chunks/build/useAmlDatasets-BM0lZu40.mjs +6 -6
  173. package/.output/server/chunks/build/{useAmlEntities-BjXWvFd_.mjs → useAmlEntities-BDQ8TmLL.mjs} +7 -7
  174. package/.output/server/chunks/build/{useAmlEntities-BjXWvFd_.mjs.map → useAmlEntities-BDQ8TmLL.mjs.map} +1 -1
  175. package/.output/server/chunks/build/{useAmlEnv-BB7p6x_w.mjs → useAmlEnv-DgTVwpqi.mjs} +7 -7
  176. package/.output/server/chunks/build/{useAmlEnv-BB7p6x_w.mjs.map → useAmlEnv-DgTVwpqi.mjs.map} +1 -1
  177. package/.output/server/chunks/build/useAmlExperiments-BtyW32PO.mjs +6 -6
  178. package/.output/server/chunks/build/{useAmlFormat-WrKuRfw2.mjs → useAmlFormat-C2sl3MGB.mjs} +7 -7
  179. package/.output/server/chunks/build/{useAmlFormat-WrKuRfw2.mjs.map → useAmlFormat-C2sl3MGB.mjs.map} +1 -1
  180. package/.output/server/chunks/build/{useAmlJobs-DBuLvWMF.mjs → useAmlJobs-fk-H5Rp5.mjs} +10 -9
  181. package/.output/server/chunks/build/useAmlJobs-fk-H5Rp5.mjs.map +1 -0
  182. package/.output/server/chunks/build/{useAmlModels-VurmkBYe.mjs → useAmlModels-hgyVHoAq.mjs} +7 -7
  183. package/.output/server/chunks/build/{useAmlModels-VurmkBYe.mjs.map → useAmlModels-hgyVHoAq.mjs.map} +1 -1
  184. package/.output/server/chunks/build/{useAmlNotes-BUdj3X6Y.mjs → useAmlNotes-BjbYNADm.mjs} +7 -7
  185. package/.output/server/chunks/build/{useAmlNotes-BUdj3X6Y.mjs.map → useAmlNotes-BjbYNADm.mjs.map} +1 -1
  186. package/.output/server/chunks/build/{useAmlPredict-DFy-Do10.mjs → useAmlPredict-U24-SZNA.mjs} +7 -7
  187. package/.output/server/chunks/build/{useAmlPredict-DFy-Do10.mjs.map → useAmlPredict-U24-SZNA.mjs.map} +1 -1
  188. package/.output/server/chunks/build/useDaqAddNode-kW7uTSMQ.mjs +7 -7
  189. package/.output/server/chunks/build/useDaqDetailBinding-cpBz4hSL.mjs +6 -6
  190. package/.output/server/chunks/build/useDaqDetailFrames-BZGtKKXW.mjs +6 -6
  191. package/.output/server/chunks/build/{useDaqDetailHistory-C4_hVeqZ.mjs → useDaqDetailHistory-DtSTfyMi.mjs} +7 -7
  192. package/.output/server/chunks/build/{useDaqDetailHistory-C4_hVeqZ.mjs.map → useDaqDetailHistory-DtSTfyMi.mjs.map} +1 -1
  193. package/.output/server/chunks/build/{useDaqDetailNode-Bwid_iA8.mjs → useDaqDetailNode-D_vhG0jF.mjs} +8 -8
  194. package/.output/server/chunks/build/{useDaqDetailNode-Bwid_iA8.mjs.map → useDaqDetailNode-D_vhG0jF.mjs.map} +1 -1
  195. package/.output/server/chunks/build/useDaqDetailParams-JfqHvTxr.mjs +7 -7
  196. package/.output/server/chunks/build/{useDaqFilters-C13836p-.mjs → useDaqFilters-BUQtZ82a.mjs} +8 -8
  197. package/.output/server/chunks/build/{useDaqFilters-C13836p-.mjs.map → useDaqFilters-BUQtZ82a.mjs.map} +1 -1
  198. package/.output/server/chunks/build/useDaqNodeWindow-1DWj2H5G.mjs +6 -6
  199. package/.output/server/chunks/build/useDaqOptimizations-Ct-TSXxy.mjs +6 -6
  200. package/.output/server/chunks/build/useDaqStream-wOxZcMDA.mjs +1 -1
  201. package/.output/server/chunks/build/useDaqTemplates-BqQRdrIm.mjs +7 -7
  202. package/.output/server/chunks/build/{useDashboardCharts-DZysNDUX.mjs → useDashboardCharts-D45083s_.mjs} +7 -7
  203. package/.output/server/chunks/build/{useDashboardCharts-DZysNDUX.mjs.map → useDashboardCharts-D45083s_.mjs.map} +1 -1
  204. package/.output/server/chunks/build/{useDashboardData-Ny_wtbo6.mjs → useDashboardData-Do_Vsepa.mjs} +8 -8
  205. package/.output/server/chunks/build/{useDashboardData-Ny_wtbo6.mjs.map → useDashboardData-Do_Vsepa.mjs.map} +1 -1
  206. package/.output/server/chunks/build/{useDcwAddNode-C8nE7geh.mjs → useDcwAddNode-C1HDP3yG.mjs} +12 -9
  207. package/.output/server/chunks/build/useDcwAddNode-C1HDP3yG.mjs.map +1 -0
  208. package/.output/server/chunks/build/useDcwCards-ETEsHvVy.mjs +6 -6
  209. package/.output/server/chunks/build/{useDcwDetailScope-BNrpDzbO.mjs → useDcwDetailScope-QTr1G6Oe.mjs} +7 -7
  210. package/.output/server/chunks/build/{useDcwDetailScope-BNrpDzbO.mjs.map → useDcwDetailScope-QTr1G6Oe.mjs.map} +1 -1
  211. package/.output/server/chunks/build/{useDcwDetailTemplates-IcKAJ37Y.mjs → useDcwDetailTemplates-Djt-0Vse.mjs} +7 -7
  212. package/.output/server/chunks/build/{useDcwDetailTemplates-IcKAJ37Y.mjs.map → useDcwDetailTemplates-Djt-0Vse.mjs.map} +1 -1
  213. package/.output/server/chunks/build/useDcwFilter-FfVlDdyS.mjs +6 -6
  214. package/.output/server/chunks/build/useDcwLedger-BLoMmcu1.mjs +6 -6
  215. package/.output/server/chunks/build/useDcwLineMutations-BMC5BTWU.mjs +6 -6
  216. package/.output/server/chunks/build/{useDcwLineRun-Bpxk4mse.mjs → useDcwLineRun-74gN_s6T.mjs} +7 -7
  217. package/.output/server/chunks/build/{useDcwLineRun-Bpxk4mse.mjs.map → useDcwLineRun-74gN_s6T.mjs.map} +1 -1
  218. package/.output/server/chunks/build/useDcwProducts-D-zrMaeV.mjs +6 -6
  219. package/.output/server/chunks/build/{useDcwQuery-1lYHOaU6.mjs → useDcwQuery-7tX4QYmv.mjs} +7 -7
  220. package/.output/server/chunks/build/{useDcwQuery-1lYHOaU6.mjs.map → useDcwQuery-7tX4QYmv.mjs.map} +1 -1
  221. package/.output/server/chunks/build/useDcwQuickActions-C-tgcXIr.mjs +6 -6
  222. package/.output/server/chunks/build/{useDcwRecipes-BzGOymqz.mjs → useDcwRecipes-FVk5gDLV.mjs} +23 -15
  223. package/.output/server/chunks/build/useDcwRecipes-FVk5gDLV.mjs.map +1 -0
  224. package/.output/server/chunks/build/useDcwStream-D04kCV3J.mjs +1 -1
  225. package/.output/server/chunks/build/{useDcwTemplates-Dsjw9u6U.mjs → useDcwTemplates-LDZffy4u.mjs} +7 -7
  226. package/.output/server/chunks/build/{useDcwTemplates-Dsjw9u6U.mjs.map → useDcwTemplates-LDZffy4u.mjs.map} +1 -1
  227. package/.output/server/chunks/build/{useDcwWrites-3RxwN36I.mjs → useDcwWrites-B22mwliK.mjs} +25 -8
  228. package/.output/server/chunks/build/useDcwWrites-B22mwliK.mjs.map +1 -0
  229. package/.output/server/chunks/build/useLocalTime-DZ2RAtVs.mjs +49 -0
  230. package/.output/server/chunks/build/useLocalTime-DZ2RAtVs.mjs.map +1 -0
  231. package/.output/server/chunks/build/{useLogsFormat-BlwLpyzy.mjs → useLogsFormat-B-o4MHy7.mjs} +7 -7
  232. package/.output/server/chunks/build/{useLogsFormat-BlwLpyzy.mjs.map → useLogsFormat-B-o4MHy7.mjs.map} +1 -1
  233. package/.output/server/chunks/build/{useLogsManual-Deev2Cjk.mjs → useLogsManual-COfCyUtj.mjs} +7 -7
  234. package/.output/server/chunks/build/{useLogsManual-Deev2Cjk.mjs.map → useLogsManual-COfCyUtj.mjs.map} +1 -1
  235. package/.output/server/chunks/build/useLogsQuery-CRAm2hmS.mjs +6 -6
  236. package/.output/server/chunks/build/useLogsScope-ClNijVj4.mjs +6 -6
  237. package/.output/server/chunks/build/{useMonitorColumns-ylxrUNTK.mjs → useMonitorColumns-DhYvkLDq.mjs} +7 -7
  238. package/.output/server/chunks/build/{useMonitorColumns-ylxrUNTK.mjs.map → useMonitorColumns-DhYvkLDq.mjs.map} +1 -1
  239. package/.output/server/chunks/build/{useMonitorData-CEpKXoD4.mjs → useMonitorData-guVjMDtR.mjs} +9 -8
  240. package/.output/server/chunks/build/useMonitorData-guVjMDtR.mjs.map +1 -0
  241. package/.output/server/chunks/build/{useMonitorFormat-_7zLapf3.mjs → useMonitorFormat-C7lhZe4g.mjs} +13 -12
  242. package/.output/server/chunks/build/useMonitorFormat-C7lhZe4g.mjs.map +1 -0
  243. package/.output/server/chunks/build/useMonitorTerminal-Dj4QwRNW.mjs +6 -6
  244. package/.output/server/chunks/build/useOperationsFeed-CeXeHqUK.mjs +191 -0
  245. package/.output/server/chunks/build/useOperationsFeed-CeXeHqUK.mjs.map +1 -0
  246. package/.output/server/chunks/build/{useTeamActions-D6sxdpkP.mjs → useTeamActions-C4n-taBk.mjs} +7 -7
  247. package/.output/server/chunks/build/{useTeamActions-D6sxdpkP.mjs.map → useTeamActions-C4n-taBk.mjs.map} +1 -1
  248. package/.output/server/chunks/build/{useTeamAddMemberDialog-Q-ntQ6Vo.mjs → useTeamAddMemberDialog-Cp_z3nMa.mjs} +7 -7
  249. package/.output/server/chunks/build/{useTeamAddMemberDialog-Q-ntQ6Vo.mjs.map → useTeamAddMemberDialog-Cp_z3nMa.mjs.map} +1 -1
  250. package/.output/server/chunks/build/{useTeamCreateDialog-D7OMfDTM.mjs → useTeamCreateDialog-C8LKUoe2.mjs} +7 -7
  251. package/.output/server/chunks/build/{useTeamCreateDialog-D7OMfDTM.mjs.map → useTeamCreateDialog-C8LKUoe2.mjs.map} +1 -1
  252. package/.output/server/chunks/build/{useTeamDeployDialog-vBRqjiut.mjs → useTeamDeployDialog-wdBVsNWY.mjs} +7 -7
  253. package/.output/server/chunks/build/{useTeamDeployDialog-vBRqjiut.mjs.map → useTeamDeployDialog-wdBVsNWY.mjs.map} +1 -1
  254. package/.output/server/chunks/build/useTeamDialogState-CFRUQxAf.mjs +6 -6
  255. package/.output/server/chunks/build/{useTeamPluginDialog-B8kxewpZ.mjs → useTeamPluginDialog-CIk8G8nI.mjs} +7 -7
  256. package/.output/server/chunks/build/{useTeamPluginDialog-B8kxewpZ.mjs.map → useTeamPluginDialog-CIk8G8nI.mjs.map} +1 -1
  257. package/.output/server/chunks/build/{useTeamsCatalog-xRhk7ezA.mjs → useTeamsCatalog-D6TULzmS.mjs} +7 -7
  258. package/.output/server/chunks/build/{useTeamsCatalog-xRhk7ezA.mjs.map → useTeamsCatalog-D6TULzmS.mjs.map} +1 -1
  259. package/.output/server/chunks/build/useTokenClipboard-CwQpoFeP.mjs +6 -6
  260. package/.output/server/chunks/build/{useTokenCreate-DpDcF1Vx.mjs → useTokenCreate-DkYvEQUD.mjs} +7 -7
  261. package/.output/server/chunks/build/{useTokenCreate-DpDcF1Vx.mjs.map → useTokenCreate-DkYvEQUD.mjs.map} +1 -1
  262. package/.output/server/chunks/build/{useTokenRename-BRejFrtc.mjs → useTokenRename-DOCP4aeI.mjs} +7 -7
  263. package/.output/server/chunks/build/{useTokenRename-BRejFrtc.mjs.map → useTokenRename-DOCP4aeI.mjs.map} +1 -1
  264. package/.output/server/chunks/build/{useTokenReveal-DER2afAS.mjs → useTokenReveal-CybwwflV.mjs} +7 -7
  265. package/.output/server/chunks/build/{useTokenReveal-DER2afAS.mjs.map → useTokenReveal-CybwwflV.mjs.map} +1 -1
  266. package/.output/server/chunks/build/{useTokensList-DHt65IOY.mjs → useTokensList-BLiMvrc4.mjs} +7 -7
  267. package/.output/server/chunks/build/{useTokensList-DHt65IOY.mjs.map → useTokensList-BLiMvrc4.mjs.map} +1 -1
  268. package/.output/server/chunks/build/{useWorkbenchAuth-B2j-zILO.mjs → useWorkbenchAuth-DqjBX-6w.mjs} +7 -7
  269. package/.output/server/chunks/build/{useWorkbenchAuth-B2j-zILO.mjs.map → useWorkbenchAuth-DqjBX-6w.mjs.map} +1 -1
  270. package/.output/server/chunks/build/{useWorkbenchWorkspaces-B_2mOxN6.mjs → useWorkbenchWorkspaces-DgiSCzsX.mjs} +10 -9
  271. package/.output/server/chunks/build/useWorkbenchWorkspaces-DgiSCzsX.mjs.map +1 -0
  272. package/.output/server/chunks/build/{useWorkshopWs-B-Es2Xg3.mjs → useWorkshopWs-Bm80bLn7.mjs} +8 -7
  273. package/.output/server/chunks/build/useWorkshopWs-Bm80bLn7.mjs.map +1 -0
  274. package/.output/server/chunks/build/{useWorkspaceAuth-tQ1ORWXo.mjs → useWorkspaceAuth-Dlvj9csD.mjs} +8 -8
  275. package/.output/server/chunks/build/{useWorkspaceAuth-tQ1ORWXo.mjs.map → useWorkspaceAuth-Dlvj9csD.mjs.map} +1 -1
  276. package/.output/server/chunks/build/{useWorkspaceChannels-BwB6TAWY.mjs → useWorkspaceChannels-D61Al2dr.mjs} +11 -10
  277. package/.output/server/chunks/build/useWorkspaceChannels-D61Al2dr.mjs.map +1 -0
  278. package/.output/server/chunks/build/{useWorkspacePanels-CuAmDpT5.mjs → useWorkspacePanels-CTwREmq0.mjs} +8 -8
  279. package/.output/server/chunks/build/{useWorkspacePanels-CuAmDpT5.mjs.map → useWorkspacePanels-CTwREmq0.mjs.map} +1 -1
  280. package/.output/server/chunks/build/{useWorkspaceShell-CcQ4JlqO.mjs → useWorkspaceShell-BCypjjjE.mjs} +7 -7
  281. package/.output/server/chunks/build/{useWorkspaceShell-CcQ4JlqO.mjs.map → useWorkspaceShell-BCypjjjE.mjs.map} +1 -1
  282. package/.output/server/chunks/build/{users-De2HygNt.mjs → users-CCf5PgRs.mjs} +8 -8
  283. package/.output/server/chunks/build/{users-De2HygNt.mjs.map → users-CCf5PgRs.mjs.map} +1 -1
  284. package/.output/server/chunks/build/{workshop-CmgpTwyf.mjs → workshop-FQ4YzooB.mjs} +13 -12
  285. package/.output/server/chunks/build/workshop-FQ4YzooB.mjs.map +1 -0
  286. package/.output/server/chunks/build/{workspaces-DVIEk1Bl.mjs → workspaces-CmW0cpOt.mjs} +1 -1
  287. package/.output/server/chunks/build/{workspaces-DVIEk1Bl.mjs.map → workspaces-CmW0cpOt.mjs.map} +1 -1
  288. package/.output/server/chunks/routes/api/health.get.mjs +6 -6
  289. package/.output/server/chunks/routes/api/index.get.mjs +7 -7
  290. package/.output/server/chunks/routes/api/index.post.mjs +7 -7
  291. package/.output/server/chunks/routes/api/mcp/workshop.mjs +6 -6
  292. package/.output/server/chunks/routes/api/metrics.mjs +6 -6
  293. package/.output/server/chunks/routes/api/plugins/_name/_...path_.mjs +6 -6
  294. package/.output/server/chunks/routes/api/plugins/client/_name_.get.mjs +6 -6
  295. package/.output/server/chunks/routes/api/plugins/i18n.get.mjs +6 -6
  296. package/.output/server/chunks/routes/api/plugins/manifest.get.mjs +6 -6
  297. package/.output/server/chunks/routes/api/system/config-groups/_id_.delete.mjs +6 -6
  298. package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs +6 -6
  299. package/.output/server/chunks/routes/api/system/config.get.mjs +6 -6
  300. package/.output/server/chunks/routes/api/system/index.get.mjs +6 -6
  301. package/.output/server/chunks/routes/api/system/index.patch.mjs +6 -6
  302. package/.output/server/chunks/routes/api/system/index.post.mjs +6 -6
  303. package/.output/server/chunks/routes/api/system/index2.get.mjs +6 -6
  304. package/.output/server/chunks/routes/api/system/monitor/terminal/ws.mjs +6 -6
  305. package/.output/server/chunks/routes/api/system/monitor/terminate.post.mjs +6 -6
  306. package/.output/server/chunks/routes/api/system/monitor.get.mjs +6 -6
  307. package/.output/server/chunks/routes/api/system/settings/events.get.mjs +6 -6
  308. package/.output/server/chunks/routes/api/system/settings/reset.post.mjs +6 -6
  309. package/.output/server/chunks/routes/api/system/timezone.get.mjs +36 -0
  310. package/.output/server/chunks/routes/api/system/timezone.get.mjs.map +1 -0
  311. package/.output/server/chunks/routes/api/users/_id_.delete.mjs +7 -7
  312. package/.output/server/chunks/routes/api/users/_id_.get.mjs +7 -7
  313. package/.output/server/chunks/routes/api/users/_id_.put.mjs +7 -7
  314. package/.output/server/chunks/routes/api/users/index.get.mjs +7 -7
  315. package/.output/server/chunks/routes/api/users/index.post.mjs +7 -7
  316. package/.output/server/chunks/routes/api/users/login.post.mjs +7 -7
  317. package/.output/server/chunks/routes/api/users/logout.post.mjs +7 -7
  318. package/.output/server/chunks/routes/api/users/me.get.mjs +7 -7
  319. package/.output/server/chunks/routes/api/users/register.post.mjs +7 -7
  320. package/.output/server/chunks/routes/api/users/setup-status.get.mjs +7 -7
  321. package/.output/server/chunks/routes/api/users/tokens/_id/token.get.mjs +7 -7
  322. package/.output/server/chunks/routes/api/users/tokens/_id_.delete.mjs +7 -7
  323. package/.output/server/chunks/routes/api/users/tokens/_id_.patch.mjs +7 -7
  324. package/.output/server/chunks/routes/api/workshop/_...path_.mjs +6 -6
  325. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.get.mjs +7 -7
  326. package/.output/server/chunks/routes/api/workshop/a2a/_agentId/index.post.mjs +5 -5
  327. package/.output/server/chunks/routes/api/workshop/a2a/send.post.mjs +6 -6
  328. package/.output/server/chunks/routes/api/workshop/agent-tools/approvals/_id/decide.post.mjs +7 -7
  329. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.delete.mjs +7 -7
  330. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs +10 -7
  331. package/.output/server/chunks/routes/api/workshop/agent-tools/bindings/_id_.patch.mjs.map +1 -1
  332. package/.output/server/chunks/routes/api/workshop/agent-tools/index.get.mjs +7 -7
  333. package/.output/server/chunks/routes/api/workshop/agent-tools/index.post.mjs +7 -7
  334. package/.output/server/chunks/routes/api/workshop/agent-tools/index2.get.mjs +7 -7
  335. package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +7 -7
  336. package/.output/server/chunks/routes/api/workshop/agent-tools/list.get.mjs +7 -7
  337. package/.output/server/chunks/routes/api/workshop/agents/_id_.delete.mjs +6 -6
  338. package/.output/server/chunks/routes/api/workshop/agents/_id_.get.mjs +6 -6
  339. package/.output/server/chunks/routes/api/workshop/agents/_id_.patch.mjs +6 -6
  340. package/.output/server/chunks/routes/api/workshop/agents/subscribe.post.mjs +6 -6
  341. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id/preview.get.mjs +6 -6
  342. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.delete.mjs +7 -7
  343. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +7 -7
  344. package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.patch.mjs +7 -7
  345. package/.output/server/chunks/routes/api/workshop/aml/entities/prune.post.mjs +7 -7
  346. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs +7 -8
  347. package/.output/server/chunks/routes/api/workshop/aml/env/recheck.post.mjs.map +1 -1
  348. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs +7 -8
  349. package/.output/server/chunks/routes/api/workshop/aml/env/uv.post.mjs.map +1 -1
  350. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs +7 -8
  351. package/.output/server/chunks/routes/api/workshop/aml/env/venv.post.mjs.map +1 -1
  352. package/.output/server/chunks/routes/api/workshop/aml/index.get.mjs +7 -7
  353. package/.output/server/chunks/routes/api/workshop/aml/index.post.mjs +7 -7
  354. package/.output/server/chunks/routes/api/workshop/aml/index2.get.mjs +6 -6
  355. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs +20 -7
  356. package/.output/server/chunks/routes/api/workshop/aml/index2.post.mjs.map +1 -1
  357. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs +7 -8
  358. package/.output/server/chunks/routes/api/workshop/aml/index3.get.mjs.map +1 -1
  359. package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs +49 -0
  360. package/.output/server/chunks/routes/api/workshop/aml/index3.post.mjs.map +1 -0
  361. package/.output/server/chunks/routes/api/workshop/aml/index4.get.mjs +7 -7
  362. package/.output/server/chunks/routes/api/workshop/aml/index5.get.mjs +7 -7
  363. package/.output/server/chunks/routes/api/workshop/aml/index6.get.mjs +7 -7
  364. package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs +28 -0
  365. package/.output/server/chunks/routes/api/workshop/aml/index7.get.mjs.map +1 -0
  366. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/cancel.post.mjs +7 -7
  367. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/logs.get.mjs +7 -7
  368. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id/retry.post.mjs +7 -7
  369. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.delete.mjs +7 -7
  370. package/.output/server/chunks/routes/api/workshop/aml/jobs/_id_.get.mjs +7 -7
  371. package/.output/server/chunks/routes/api/workshop/aml/models/_id/predict.post.mjs +7 -7
  372. package/.output/server/chunks/routes/api/workshop/aml/models/_id/promote.post.mjs +7 -7
  373. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.delete.mjs +7 -7
  374. package/.output/server/chunks/routes/api/workshop/aml/models/_id_.patch.mjs +7 -7
  375. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs +35 -0
  376. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id/train.post.mjs.map +1 -0
  377. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs +35 -0
  378. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.delete.mjs.map +1 -0
  379. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs +44 -0
  380. package/.output/server/chunks/routes/api/workshop/aml/training-plans/_id_.patch.mjs.map +1 -0
  381. package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs +46 -0
  382. package/.output/server/chunks/routes/api/workshop/aml/twin/providers.get.mjs.map +1 -0
  383. package/.output/server/chunks/routes/api/workshop/approvals/_id/decide.post.mjs +7 -7
  384. package/.output/server/chunks/routes/api/workshop/assets/character/_id_.delete.mjs +5 -5
  385. package/.output/server/chunks/routes/api/workshop/assets/devices/_id_.delete.mjs +5 -5
  386. package/.output/server/chunks/routes/api/workshop/assets/index.get.mjs +6 -6
  387. package/.output/server/chunks/routes/api/workshop/assets/index.post.mjs +6 -6
  388. package/.output/server/chunks/routes/api/workshop/assets/index2.get.mjs +5 -5
  389. package/.output/server/chunks/routes/api/workshop/assets/index2.post.mjs +6 -6
  390. package/.output/server/chunks/routes/api/workshop/audit.get.mjs +6 -6
  391. package/.output/server/chunks/routes/api/workshop/caller.mjs +7 -7
  392. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +16 -10
  393. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs.map +1 -1
  394. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.delete.mjs +6 -6
  395. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.get.mjs +6 -6
  396. package/.output/server/chunks/routes/api/workshop/channel-templates/_id_.patch.mjs +6 -6
  397. package/.output/server/chunks/routes/api/workshop/channel-templates/from-channel.post.mjs +6 -6
  398. package/.output/server/chunks/routes/api/workshop/channels/_id/activate.post.mjs +7 -7
  399. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.get.mjs +6 -6
  400. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/index.post.mjs +6 -6
  401. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/_memoryId_.delete.mjs +6 -6
  402. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/memories/search.post.mjs +6 -6
  403. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/model.patch.mjs +6 -6
  404. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/position.patch.mjs +6 -6
  405. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/range.patch.mjs +6 -6
  406. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId/stop.post.mjs +6 -6
  407. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.delete.mjs +6 -6
  408. package/.output/server/chunks/routes/api/workshop/channels/_id/agents/_agentId_.patch.mjs +6 -6
  409. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.get.mjs +6 -6
  410. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/index.post.mjs +6 -6
  411. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/messages/_messageId/read.post.mjs +6 -6
  412. package/.output/server/chunks/routes/api/workshop/channels/_id/chat/permissions.get.mjs +6 -6
  413. package/.output/server/chunks/routes/api/workshop/channels/_id/events.get.mjs +6 -6
  414. package/.output/server/chunks/routes/api/workshop/channels/_id/index.get.mjs +6 -6
  415. package/.output/server/chunks/routes/api/workshop/channels/_id/index.post.mjs +6 -6
  416. package/.output/server/chunks/routes/api/workshop/channels/_id/index.put.mjs +7 -7
  417. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.get.mjs +6 -6
  418. package/.output/server/chunks/routes/api/workshop/channels/_id/index2.post.mjs +6 -6
  419. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.get.mjs +6 -6
  420. package/.output/server/chunks/routes/api/workshop/channels/_id/index3.post.mjs +6 -6
  421. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.get.mjs +6 -6
  422. package/.output/server/chunks/routes/api/workshop/channels/_id/index4.post.mjs +6 -6
  423. package/.output/server/chunks/routes/api/workshop/channels/_id/index5.get.mjs +7 -7
  424. package/.output/server/chunks/routes/api/workshop/channels/_id/index6.get.mjs +6 -6
  425. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId/approve.post.mjs +6 -6
  426. package/.output/server/chunks/routes/api/workshop/channels/_id/members/_userId_.delete.mjs +6 -6
  427. package/.output/server/chunks/routes/api/workshop/channels/_id/members/join.post.mjs +6 -6
  428. package/.output/server/chunks/routes/api/workshop/channels/_id/members/leave.post.mjs +6 -6
  429. package/.output/server/chunks/routes/api/workshop/channels/_id/memories/_memoryId_.delete.mjs +6 -6
  430. package/.output/server/chunks/routes/api/workshop/channels/_id/queue.get.mjs +6 -6
  431. package/.output/server/chunks/routes/api/workshop/channels/_id/terminals.get.mjs +7 -7
  432. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs +51 -0
  433. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.get.mjs.map +1 -0
  434. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs +71 -0
  435. package/.output/server/chunks/routes/api/workshop/channels/_id/twin-profile.patch.mjs.map +1 -0
  436. package/.output/server/chunks/routes/api/workshop/channels/_id_.delete.mjs +6 -6
  437. package/.output/server/chunks/routes/api/workshop/channels/index.get.mjs +7 -7
  438. package/.output/server/chunks/routes/api/workshop/channels/index.patch.mjs +6 -6
  439. package/.output/server/chunks/routes/api/workshop/channels.get.mjs +6 -6
  440. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +9 -9
  441. package/.output/server/chunks/routes/api/workshop/daq/_id/bind.post.mjs +7 -7
  442. package/.output/server/chunks/routes/api/workshop/daq/_id/bindings.put.mjs +7 -7
  443. package/.output/server/chunks/routes/api/workshop/daq/_id/frames/content.get.mjs +7 -7
  444. package/.output/server/chunks/routes/api/workshop/daq/_id/frames.get.mjs +7 -7
  445. package/.output/server/chunks/routes/api/workshop/daq/_id/samples.get.mjs +7 -7
  446. package/.output/server/chunks/routes/api/workshop/daq/_id/test.post.mjs +7 -7
  447. package/.output/server/chunks/routes/api/workshop/daq/_id_.delete.mjs +7 -7
  448. package/.output/server/chunks/routes/api/workshop/daq/_id_.patch.mjs +7 -7
  449. package/.output/server/chunks/routes/api/workshop/daq/alarms/_id/ack.post.mjs +7 -7
  450. package/.output/server/chunks/routes/api/workshop/daq/alarms.get.mjs +7 -7
  451. package/.output/server/chunks/routes/api/workshop/daq/controller.post.mjs +7 -7
  452. package/.output/server/chunks/routes/api/workshop/daq/index.post.mjs +7 -7
  453. package/.output/server/chunks/routes/api/workshop/daq/infra/reconnect.post.mjs +7 -7
  454. package/.output/server/chunks/routes/api/workshop/daq/infra.get.mjs +7 -7
  455. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs +7 -7
  456. package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs +7 -7
  457. package/.output/server/chunks/routes/api/workshop/daq/test-driver.post.mjs +7 -7
  458. package/.output/server/chunks/routes/api/workshop/dcw/_id/bind.post.mjs +7 -7
  459. package/.output/server/chunks/routes/api/workshop/dcw/_id/bindings.put.mjs +7 -7
  460. package/.output/server/chunks/routes/api/workshop/dcw/_id/param-ledger.get.mjs +7 -7
  461. package/.output/server/chunks/routes/api/workshop/dcw/_id/read.post.mjs +7 -7
  462. package/.output/server/chunks/routes/api/workshop/dcw/_id/test.post.mjs +7 -7
  463. package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +7 -7
  464. package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +7 -7
  465. package/.output/server/chunks/routes/api/workshop/dcw/_id_.patch.mjs +7 -7
  466. package/.output/server/chunks/routes/api/workshop/dcw/controller.post.mjs +7 -7
  467. package/.output/server/chunks/routes/api/workshop/dcw/index.get.mjs +7 -7
  468. package/.output/server/chunks/routes/api/workshop/dcw/index.post.mjs +7 -7
  469. package/.output/server/chunks/routes/api/workshop/dcw/index2.get.mjs +7 -7
  470. package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +7 -7
  471. package/.output/server/chunks/routes/api/workshop/dcw/index3.get.mjs +7 -7
  472. package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +7 -7
  473. package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +7 -7
  474. package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +7 -7
  475. package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +7 -7
  476. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +7 -7
  477. package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +7 -7
  478. package/.output/server/chunks/routes/api/workshop/dcw/journal/node/_nodeId/rollback.post.mjs +7 -7
  479. package/.output/server/chunks/routes/api/workshop/dcw/line/query.get.mjs +7 -7
  480. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +7 -7
  481. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +7 -7
  482. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.delete.mjs +7 -7
  483. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs +7 -7
  484. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/judge.post.mjs +7 -7
  485. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/rollback.post.mjs +7 -7
  486. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id/series.get.mjs +7 -7
  487. package/.output/server/chunks/routes/api/workshop/dcw/optimizations/_id_.get.mjs +7 -7
  488. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +7 -7
  489. package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +7 -7
  490. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +7 -7
  491. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +7 -7
  492. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.delete.mjs +7 -7
  493. package/.output/server/chunks/routes/api/workshop/dcw/products/_id_.patch.mjs +7 -7
  494. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/apply.post.mjs +7 -7
  495. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/mark-good.post.mjs +7 -7
  496. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/revert.post.mjs +7 -7
  497. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/rollback-good.post.mjs +7 -7
  498. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id/versions.get.mjs +7 -7
  499. package/.output/server/chunks/routes/api/workshop/dcw/recipes/_id_.delete.mjs +7 -7
  500. package/.output/server/chunks/routes/api/workshop/dcw/recipes/index.patch.mjs +7 -7
  501. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/close.post.mjs +7 -7
  502. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/data.get.mjs +7 -7
  503. package/.output/server/chunks/routes/api/workshop/dcw/runs/_id/rollback.post.mjs +7 -7
  504. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +7 -7
  505. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +7 -7
  506. package/.output/server/chunks/routes/api/workshop/dcw/test-driver.post.mjs +7 -7
  507. package/.output/server/chunks/routes/api/workshop/device-twins/_id/control.post.mjs +7 -7
  508. package/.output/server/chunks/routes/api/workshop/device-twins/_id/telemetry.post.mjs +7 -7
  509. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.delete.mjs +7 -7
  510. package/.output/server/chunks/routes/api/workshop/device-twins/_id_.patch.mjs +7 -7
  511. package/.output/server/chunks/routes/api/workshop/fs/dirs.get.mjs +5 -5
  512. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +7 -7
  513. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +7 -7
  514. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +7 -7
  515. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +7 -7
  516. package/.output/server/chunks/routes/api/workshop/index.get.mjs +6 -6
  517. package/.output/server/chunks/routes/api/workshop/index.post.mjs +6 -6
  518. package/.output/server/chunks/routes/api/workshop/index.put.mjs +7 -7
  519. package/.output/server/chunks/routes/api/workshop/index10.get.mjs +7 -7
  520. package/.output/server/chunks/routes/api/workshop/index11.get.mjs +6 -6
  521. package/.output/server/chunks/routes/api/workshop/index12.get.mjs +6 -6
  522. package/.output/server/chunks/routes/api/workshop/index13.get.mjs +6 -6
  523. package/.output/server/chunks/routes/api/workshop/index14.get.mjs +6 -6
  524. package/.output/server/chunks/routes/api/workshop/index2.get.mjs +7 -8
  525. package/.output/server/chunks/routes/api/workshop/index2.get.mjs.map +1 -1
  526. package/.output/server/chunks/routes/api/workshop/index2.post.mjs +6 -6
  527. package/.output/server/chunks/routes/api/workshop/index3.get.mjs +6 -6
  528. package/.output/server/chunks/routes/api/workshop/index3.post.mjs +7 -7
  529. package/.output/server/chunks/routes/api/workshop/index4.get.mjs +6 -6
  530. package/.output/server/chunks/routes/api/workshop/index4.post.mjs +7 -7
  531. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +7 -7
  532. package/.output/server/chunks/routes/api/workshop/index5.post.mjs +7 -7
  533. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +7 -7
  534. package/.output/server/chunks/routes/api/workshop/index6.post.mjs +7 -7
  535. package/.output/server/chunks/routes/api/workshop/index7.get.mjs +7 -7
  536. package/.output/server/chunks/routes/api/workshop/index7.post.mjs +6 -6
  537. package/.output/server/chunks/routes/api/workshop/index8.get.mjs +6 -6
  538. package/.output/server/chunks/routes/api/workshop/index8.post.mjs +6 -6
  539. package/.output/server/chunks/routes/api/workshop/index9.get.mjs +6 -6
  540. package/.output/server/chunks/routes/api/workshop/index9.post.mjs +6 -6
  541. package/.output/server/chunks/routes/api/workshop/mailbox/all.get.mjs +6 -6
  542. package/.output/server/chunks/routes/api/workshop/mailbox.get.mjs +6 -6
  543. package/.output/server/chunks/routes/api/workshop/memories/maintenance.post.mjs +6 -6
  544. package/.output/server/chunks/routes/api/workshop/notifications/read.post.mjs +6 -6
  545. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +7 -7
  546. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +7 -7
  547. package/.output/server/chunks/routes/api/workshop/plugins/index.get.mjs +6 -6
  548. package/.output/server/chunks/routes/api/workshop/runtime.get.mjs +6 -6
  549. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +7 -7
  550. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +7 -7
  551. package/.output/server/chunks/routes/api/workshop/scene/layouts.get.mjs +6 -6
  552. package/.output/server/chunks/routes/api/workshop/schedules/_id/run.post.mjs +6 -6
  553. package/.output/server/chunks/routes/api/workshop/schedules/_id/runs.get.mjs +6 -6
  554. package/.output/server/chunks/routes/api/workshop/schedules/_id_.delete.mjs +6 -6
  555. package/.output/server/chunks/routes/api/workshop/schedules/_id_.get.mjs +6 -6
  556. package/.output/server/chunks/routes/api/workshop/schedules/_id_.patch.mjs +6 -6
  557. package/.output/server/chunks/routes/api/workshop/tasks/_id/complete.post.mjs +6 -6
  558. package/.output/server/chunks/routes/api/workshop/tasks/_id/dispatch.post.mjs +6 -6
  559. package/.output/server/chunks/routes/api/workshop/tasks/_id/index.post.mjs +6 -6
  560. package/.output/server/chunks/routes/api/workshop/tasks/_id/reassign.post.mjs +6 -6
  561. package/.output/server/chunks/routes/api/workshop/tasks/_id/report.post.mjs +6 -6
  562. package/.output/server/chunks/routes/api/workshop/tasks/_id/retry.post.mjs +6 -6
  563. package/.output/server/chunks/routes/api/workshop/tasks/_id/update.post.mjs +6 -6
  564. package/.output/server/chunks/routes/api/workshop/tasks/_id_.get.mjs +6 -6
  565. package/.output/server/chunks/routes/api/workshop/tasks.get.mjs +6 -6
  566. package/.output/server/chunks/routes/api/workshop/teams/_id/deploy.post.mjs +6 -6
  567. package/.output/server/chunks/routes/api/workshop/teams/_id/index.get.mjs +6 -6
  568. package/.output/server/chunks/routes/api/workshop/teams/_id/index.post.mjs +6 -6
  569. package/.output/server/chunks/routes/api/workshop/teams/_id/index.put.mjs +7 -7
  570. package/.output/server/chunks/routes/api/workshop/teams/_id/index2.get.mjs +7 -7
  571. package/.output/server/chunks/routes/api/workshop/teams/_id/members/_agentId_.delete.mjs +6 -6
  572. package/.output/server/chunks/routes/api/workshop/teams/_id_.delete.mjs +6 -6
  573. package/.output/server/chunks/routes/api/workshop/teams/_id_.get.mjs +6 -6
  574. package/.output/server/chunks/routes/api/workshop/teams/_id_.patch.mjs +6 -6
  575. package/.output/server/chunks/routes/api/workshop/users/me.get.mjs +6 -6
  576. package/.output/server/chunks/routes/api/workshop/users/register.post.mjs +6 -6
  577. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +7 -7
  578. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.delete.mjs +6 -6
  579. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channels/_channelId_.post.mjs +6 -6
  580. package/.output/server/chunks/routes/api/workshop/workspaces/index.delete.mjs +6 -6
  581. package/.output/server/chunks/routes/renderer.mjs +2 -2
  582. package/.output/server/chunks/virtual/entry.mjs +645 -525
  583. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  584. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  585. package/.output/server/index.mjs +6 -6
  586. package/.output/server/package.json +1 -1
  587. package/README-zh.md +829 -813
  588. package/README.md +839 -823
  589. package/app/components/AppSidebar.vue +1 -0
  590. package/app/components/aml/AmlModelsTable.vue +180 -173
  591. package/app/components/daq/DaqDetailFramesTile.vue +140 -139
  592. package/app/components/daq/DaqDetailHistoryCard.vue +129 -128
  593. package/app/components/dcw/DcwAddNodeModal.vue +342 -328
  594. package/app/components/dcw/DcwNodesTable.vue +290 -253
  595. package/app/components/dcw/DcwQueryCard.vue +252 -251
  596. package/app/components/dcw/DcwRecipeEditModal.vue +297 -285
  597. package/app/components/dcw/DcwRecipePanel.vue +371 -367
  598. package/app/components/operations/OperationsFilterCard.vue +164 -0
  599. package/app/components/operations/OperationsPageHead.vue +47 -0
  600. package/app/components/operations/OperationsTimeline.vue +215 -0
  601. package/app/components/settings/RuntimeFieldRow.vue +207 -189
  602. package/app/components/workshop/ChatTimeline.vue +596 -569
  603. package/app/components/workshop/EventBlock.vue +224 -214
  604. package/app/components/workshop/blocks/ClusterTool.vue +20 -2
  605. package/app/components/workshop/console/LineOperationsPanel.vue +179 -0
  606. package/app/components/workshop/console/RightPane.vue +6 -0
  607. package/app/composables/workshop/event-blocks/constants.ts +48 -2
  608. package/app/composables/workshop/operation-events.ts +151 -0
  609. package/app/composables/workshop/useAmlStream.ts +137 -136
  610. package/app/composables/workshop/useLocalTime.ts +81 -37
  611. package/app/config/schema.ts +3 -0
  612. package/app/pages/aml/styles.css +422 -420
  613. package/app/pages/aml/types.ts +195 -189
  614. package/app/pages/dcw/[id].vue +300 -297
  615. package/app/pages/dcw/composables/useDcwAddNode.ts +124 -121
  616. package/app/pages/dcw/composables/useDcwRecipes.ts +242 -239
  617. package/app/pages/dcw/composables/useDcwWrites.ts +100 -81
  618. package/app/pages/monitor/composables/useMonitorData.ts +125 -124
  619. package/app/pages/monitor/composables/useMonitorFormat.ts +41 -39
  620. package/app/pages/operations/composables/useOperationsFeed.ts +137 -0
  621. package/app/pages/operations.vue +61 -0
  622. package/app/stores/runtime-config.ts +1 -1
  623. package/app/stores/workshop/chat.ts +434 -434
  624. package/app/stores/workshop/notifications.ts +288 -288
  625. package/app/utils/dcw-recipe-form.ts +26 -0
  626. package/cli/aw.mjs +3 -2
  627. package/cli/commands/mcp.mjs +154 -0
  628. package/cli/core/context.mjs +5 -0
  629. package/config.yml +180 -176
  630. package/docs/aml.md +112 -111
  631. package/docs/cli.en.md +447 -445
  632. package/docs/cli.md +412 -410
  633. package/docs/mcp.en.md +27 -0
  634. package/docs/mcp.md +108 -0
  635. package/docs/plugins.en.md +621 -567
  636. package/docs/plugins.md +610 -560
  637. package/docs/sdk.en.md +964 -922
  638. package/docs/sdk.md +900 -861
  639. package/i18n/dicts/aml.json +207 -205
  640. package/i18n/locales/en.ts +37 -0
  641. package/i18n/locales/zh-CN.ts +37 -0
  642. package/mcp/aw-mcp-server.mjs +796 -0
  643. package/mcp/discovery.mjs +227 -0
  644. package/nuxt.config.ts +2 -0
  645. package/package.json +181 -172
  646. package/scripts/.chk-tok +1 -0
  647. package/scripts/.retrain-body.json +1 -0
  648. package/scripts/.retrain3-body.json +1 -0
  649. package/scripts/.rt-tok +1 -0
  650. package/scripts/.rt3-tok +1 -0
  651. package/scripts/_audit/.tmp/audit-data/dcw-params.json +15 -0
  652. package/scripts/_audit/.tmp/audit-data/dcws.json +30 -0
  653. package/scripts/_audit/aw-mcp-live-e2e.mjs +293 -0
  654. package/scripts/_audit/dcw-controller-e2e.ts +25 -18
  655. package/scripts/_audit/dcw-rollback-ledger.ts +4 -1
  656. package/scripts/_audit/feature-audit-e2e.mjs +339 -0
  657. package/scripts/_audit/full-e2e-adopt.mjs +238 -0
  658. package/scripts/_audit/full-e2e-close-loop.mjs +134 -0
  659. package/scripts/_audit/full-e2e-retrain.mjs +320 -0
  660. package/scripts/_audit/full-e2e-supplement.mjs +192 -0
  661. package/scripts/_audit/full-e2e-ui-final.mjs +111 -0
  662. package/scripts/_audit/full-e2e-ui-mount.mjs +111 -0
  663. package/scripts/_audit/full-feature-e2e-20260927.mjs +627 -0
  664. package/scripts/_audit/kb-diag-complete.mjs +261 -0
  665. package/scripts/_audit/kb-diag-complete2.mjs +271 -0
  666. package/scripts/_audit/kb-diag-loop-e2e.mjs +357 -0
  667. package/scripts/_audit/kb-diag-loop-mock.mjs +416 -0
  668. package/scripts/_audit/kb-diag-loop-v2.mjs +314 -0
  669. package/scripts/_audit/recipe-save-e2e.mjs +365 -0
  670. package/scripts/_audit/run.mjs +21 -0
  671. package/scripts/_audit/runtime-dir-e2e.mjs +393 -0
  672. package/scripts/_audit/skills-guided-e2e.mjs +316 -0
  673. package/scripts/_audit/stability-soak.mjs +252 -0
  674. package/scripts/_aw0748-failclosed-probe.mjs +1 -1
  675. package/scripts/_aw0748-mock-closedloop.mjs +12 -0
  676. package/scripts/_aw0748-mock-dispatch-diag.mjs +66 -0
  677. package/scripts/_dbg-configroot-audit.mjs +86 -84
  678. package/scripts/_dbg-daq-intervals-func.mjs +115 -94
  679. package/scripts/_dbg-optloop-ui-shots.mjs +52 -0
  680. package/scripts/_dbg-param-map-e2e.ts +7 -1
  681. package/scripts/_dbg-perms-e2e.mjs +11 -1
  682. package/scripts/_dbg-protocol-matrix.mjs +3 -1
  683. package/scripts/_dbg-render-regression.mjs +67 -25
  684. package/scripts/_lib-aml-python-run.mjs +17 -0
  685. package/scripts/_lint-reflow.mjs +105 -0
  686. package/scripts/_probe-model-channel-link.cjs +25 -0
  687. package/scripts/acceptance-aml-hybrid-e2e.ts +396 -0
  688. package/scripts/acceptance-aml-hybrid-twin.ts +3 -3
  689. package/scripts/acceptance-aml-model-identity.ts +137 -0
  690. package/scripts/dev-guard.mjs +4 -2
  691. package/scripts/dev-protocol-simulators.mjs +3 -2
  692. package/scripts/e2e-aml-decoupled.mjs +162 -0
  693. package/scripts/e2e-closedloop/scenarios-accounts.mjs +2 -0
  694. package/scripts/e2e-closedloop/scenarios-authz.mjs +12 -6
  695. package/scripts/e2e-closedloop/scenarios-daq-dcw.mjs +4 -1
  696. package/scripts/e2e-timezone-settings.mjs +57 -0
  697. package/scripts/live-aml-mockomp.mjs +304 -0
  698. package/scripts/live-aml-optloop-resume.mjs +322 -0
  699. package/scripts/live-aml-optloop.mjs +545 -0
  700. package/scripts/live-aml-stage9.mjs +169 -0
  701. package/scripts/live-aml-two-goals.mjs +315 -0
  702. package/scripts/live-optloop-two-modes.mjs +857 -0
  703. package/scripts/start.mjs +3 -1
  704. package/scripts/test-aw-skills.mjs +102 -0
  705. package/scripts/test-twin-plugin-sdk.mjs +30 -0
  706. package/sdk/context.mjs +305 -0
  707. package/sdk/index.d.mts +149 -0
  708. package/sdk/index.mjs +5 -2
  709. package/server/api/system/timezone.get.ts +20 -0
  710. package/server/api/workshop/agent-tools/bindings/[id].patch.ts +28 -23
  711. package/server/api/workshop/aml/jobs/index.post.ts +74 -44
  712. package/server/api/workshop/aml/training-plans/[id]/train.post.ts +18 -0
  713. package/server/api/workshop/aml/training-plans/[id].delete.ts +18 -0
  714. package/server/api/workshop/aml/training-plans/[id].patch.ts +30 -0
  715. package/server/api/workshop/aml/training-plans/index.get.ts +9 -0
  716. package/server/api/workshop/aml/training-plans/index.post.ts +43 -0
  717. package/server/api/workshop/aml/twin/providers.get.ts +33 -0
  718. package/server/api/workshop/channel-templates/[id]/instantiate.post.ts +8 -2
  719. package/server/api/workshop/channels/[id]/twin-profile.get.ts +45 -0
  720. package/server/api/workshop/channels/[id]/twin-profile.patch.ts +72 -0
  721. package/server/plugins/00-local-time.ts +13 -10
  722. package/server/plugins/aml-twin-worker.ts +32 -0
  723. package/server/plugins/aw-plugins.ts +16 -0
  724. package/server/plugins/workshop.ts +21 -0
  725. package/server/plugins-builtin/twin-injection-default/index.mjs +23 -0
  726. package/server/services/system-config/helpers.ts +42 -41
  727. package/server/services/system-config/recompute.ts +68 -66
  728. package/server/services/workshop/agents/host-tool-bridge/catalog.ts +50 -18
  729. package/server/services/workshop/agents/host-tool-bridge/dispatch.ts +51 -5
  730. package/server/services/workshop/agents/host-tool-bridge/tools/industrial.ts +19 -6
  731. package/server/services/workshop/agents/industrial/aml-dataset-tools.ts +276 -276
  732. package/server/services/workshop/agents/industrial/aml-job-tools.ts +549 -363
  733. package/server/services/workshop/agents/industrial/audit-query.ts +30 -0
  734. package/server/services/workshop/agents/industrial/dcw-judge-tools.ts +9 -13
  735. package/server/services/workshop/agents/industrial/dcw-tools.ts +208 -213
  736. package/server/services/workshop/agents/industrial/index.ts +3 -2
  737. package/server/services/workshop/agents/industrial/manual-approval.ts +45 -0
  738. package/server/services/workshop/agents/industrial/ops-tools.ts +370 -382
  739. package/server/services/workshop/agents/industrial/optimization-tools.ts +148 -0
  740. package/server/services/workshop/agents/industrial/param-tools.ts +169 -172
  741. package/server/services/workshop/agents/industrial/twin-tools.ts +618 -61
  742. package/server/services/workshop/agents/industrial-context.ts +4 -2
  743. package/server/services/workshop/agents/node-bindings.repo.ts +35 -2
  744. package/server/services/workshop/agents/tool-classes.ts +28 -0
  745. package/server/services/workshop/aml/aml.repo.ts +412 -386
  746. package/server/services/workshop/aml/job-orchestrator/stages.ts +108 -96
  747. package/server/services/workshop/aml/job-orchestrator/start-job.ts +29 -5
  748. package/server/services/workshop/aml/job-orchestrator/submit.ts +36 -1
  749. package/server/services/workshop/aml/model-registry.ts +265 -175
  750. package/server/services/workshop/aml/predictor.ts +336 -262
  751. package/server/services/workshop/aml/python/__pycache__/aml_eval.cpython-312.pyc +0 -0
  752. package/server/services/workshop/aml/python/__pycache__/amlkit.cpython-312.pyc +0 -0
  753. package/server/services/workshop/aml/python/__pycache__/train-example.cpython-312.pyc +0 -0
  754. package/server/services/workshop/aml/python/__pycache__/train-hybrid-example.cpython-312.pyc +0 -0
  755. package/server/services/workshop/aml/python/aml_eval.py +39 -6
  756. package/server/services/workshop/aml/python/amlkit.py +222 -3
  757. package/server/services/workshop/aml/python/train-hybrid-example.py +278 -32
  758. package/server/services/workshop/aml/runtime.ts +4 -3
  759. package/server/services/workshop/aml/twin/acceptance.ts +26 -0
  760. package/server/services/workshop/aml/twin/binding-snapshot.ts +603 -0
  761. package/server/services/workshop/aml/twin/calibration-worker.ts +150 -0
  762. package/server/services/workshop/aml/twin/channel-profile.ts +80 -7
  763. package/server/services/workshop/aml/twin/contracts.ts +3 -0
  764. package/server/services/workshop/aml/twin/declarative-provider.ts +108 -0
  765. package/server/services/workshop/aml/twin/feature-flags.ts +63 -0
  766. package/server/services/workshop/aml/twin/hybrid-provider.ts +86 -0
  767. package/server/services/workshop/aml/twin/model-backed-provider.ts +332 -0
  768. package/server/services/workshop/aml/twin/physics-spec-draft.ts +185 -0
  769. package/server/services/workshop/aml/twin/physics-spec.ts +338 -0
  770. package/server/services/workshop/aml/twin/prompt-composer.ts +143 -0
  771. package/server/services/workshop/aml/twin/provider-contracts.ts +98 -34
  772. package/server/services/workshop/aml/twin/provider-health.ts +69 -8
  773. package/server/services/workshop/aml/twin/provider-loader.ts +76 -45
  774. package/server/services/workshop/aml/twin/provider-registry.ts +670 -49
  775. package/server/services/workshop/aml/twin/repo.ts +63 -4
  776. package/server/services/workshop/aml/twin/scene-builder.ts +118 -0
  777. package/server/services/workshop/aml/twin/scene-lifecycle.ts +60 -0
  778. package/server/services/workshop/aml/twin/snapshot-service.ts +395 -22
  779. package/server/services/workshop/aml/twin/training-plans.ts +260 -0
  780. package/server/services/workshop/aml/twin/trial-service.ts +97 -13
  781. package/server/services/workshop/aml/twin/write-guard.ts +20 -0
  782. package/server/services/workshop/daq/daq-controller/state.ts +8 -5
  783. package/server/services/workshop/daq/drivers/registry.ts +116 -144
  784. package/server/services/workshop/db/database/migrations.ts +40 -0
  785. package/server/services/workshop/db/database/open.ts +4 -1
  786. package/server/services/workshop/db/database/schema.ts +68 -1
  787. package/server/services/workshop/db/database/seed.ts +22 -0
  788. package/server/services/workshop/db/readonly.ts +30 -0
  789. package/server/services/workshop/dcw/dcw-controller/crud.ts +162 -152
  790. package/server/services/workshop/dcw/dcw-controller/helpers.ts +61 -57
  791. package/server/services/workshop/dcw/dcw-controller/types.ts +61 -57
  792. package/server/services/workshop/dcw/dcw-controller/write-audit.ts +49 -0
  793. package/server/services/workshop/dcw/dcw-controller/write.ts +137 -147
  794. package/server/services/workshop/dcw/dcw-node.ts +18 -2
  795. package/server/services/workshop/dcw/dcw-recipe.repo.ts +5 -0
  796. package/server/services/workshop/dcw/dcw-runtime.ts +9 -3
  797. package/server/services/workshop/dcw/drivers/index.ts +2 -1
  798. package/server/services/workshop/dcw/drivers/modbus-rtu.ts +85 -93
  799. package/server/services/workshop/dcw/drivers/modbus.ts +131 -139
  800. package/server/services/workshop/dcw/drivers/opcua.ts +103 -110
  801. package/server/services/workshop/dcw/drivers/registry.ts +25 -45
  802. package/server/services/workshop/dcw/drivers/shared.ts +24 -6
  803. package/server/services/workshop/dcw/param-limits.ts +193 -142
  804. package/server/services/workshop/dcw/param-map.repo.ts +318 -305
  805. package/server/services/workshop/plugin-driver-registry.ts +108 -0
  806. package/server/services/workshop/runtime/manager/admin-channel.ts +13 -0
  807. package/server/services/workshop/runtime/manager/channel-templates.ts +29 -2
  808. package/server/services/workshop/runtime/manager/runtime-wiring.ts +8 -0
  809. package/server/services/workshop/runtime/permission-scope.ts +79 -81
  810. package/server/services/workshop/runtime/schedule-runtime.ts +33 -6
  811. package/server/utils/errors.ts +8 -0
  812. package/shared/config/engine.mjs +5 -0
  813. package/shared/config/home.mjs +15 -20
  814. package/shared/config/schema.json +135 -133
  815. package/shared/dcw-protocol/line.ts +133 -131
  816. package/shared/dcw-protocol/recipe.ts +311 -301
  817. package/shared/local-time.mjs +116 -36
  818. package/.output/public/_nuxt/-hB4kEmt.js +0 -1
  819. package/.output/public/_nuxt/74x7BOWv2.js +0 -1
  820. package/.output/public/_nuxt/9c5GOgbu.js +0 -1
  821. package/.output/public/_nuxt/BHwG4yNs.js +0 -1
  822. package/.output/public/_nuxt/BIOq3xqe2.js +0 -1
  823. package/.output/public/_nuxt/BL_dbd2v2.js +0 -1
  824. package/.output/public/_nuxt/BNnnYEYh2.js +0 -1
  825. package/.output/public/_nuxt/BxYTM0hb.js +0 -1
  826. package/.output/public/_nuxt/C3twEwTh2.js +0 -1
  827. package/.output/public/_nuxt/CGZVhYJ4.js +0 -1
  828. package/.output/public/_nuxt/CJRDL4EG.js +0 -1
  829. package/.output/public/_nuxt/CKQPwwZa2.js +0 -1
  830. package/.output/public/_nuxt/CeWnOF3O.js +0 -1
  831. package/.output/public/_nuxt/ChoLwR-s.js +0 -1
  832. package/.output/public/_nuxt/DGw8juu_.js +0 -2
  833. package/.output/public/_nuxt/DJdDFnjG2.js +0 -1
  834. package/.output/public/_nuxt/DXIaadqv.js +0 -1
  835. package/.output/public/_nuxt/D_7AzAWe.js +0 -1
  836. package/.output/public/_nuxt/DdvQ7LRI.js +0 -14
  837. package/.output/public/_nuxt/DeYsUzhr.js +0 -1
  838. package/.output/public/_nuxt/DkLCjrCk2.js +0 -47
  839. package/.output/public/_nuxt/DpqA8D8_.js +0 -1
  840. package/.output/public/_nuxt/DqtgGFEc.js +0 -1
  841. package/.output/public/_nuxt/Dy3oXKdn2.js +0 -1
  842. package/.output/public/_nuxt/PLGqdko0.js +0 -1
  843. package/.output/public/_nuxt/U8oQxruK.js +0 -1
  844. package/.output/public/_nuxt/WleoCtUX.js +0 -1
  845. package/.output/public/_nuxt/ZvXovq7N2.js +0 -1
  846. package/.output/public/_nuxt/_id_.Bossmhv1.css +0 -1
  847. package/.output/public/_nuxt/_wsId_.D-u-dbP9.css +0 -1
  848. package/.output/public/_nuxt/aml.Bi5X6_oi.css +0 -1
  849. package/.output/public/_nuxt/builds/meta/0530cee1-fff5-4025-a6f4-5708b6534608.json +0 -1
  850. package/.output/public/_nuxt/cDcDztgb2.js +0 -1
  851. package/.output/public/_nuxt/e7BBCLxr.js +0 -1
  852. package/.output/public/_nuxt/hkMGMTqt.js +0 -1
  853. package/.output/public/_nuxt/ikAUnxx3.js +0 -1
  854. package/.output/public/_nuxt/y9kob70X.js +0 -1
  855. package/.output/server/chunks/_/env-manager.mjs +0 -162
  856. package/.output/server/chunks/_/env-manager.mjs.map +0 -1
  857. package/.output/server/chunks/build/_id_-DgMpjM3_.mjs.map +0 -1
  858. package/.output/server/chunks/build/_id_-Dv3BQbYV.mjs.map +0 -1
  859. package/.output/server/chunks/build/_wsId_-BKJvFmyf.mjs.map +0 -1
  860. package/.output/server/chunks/build/aml-DdWicH3t.mjs.map +0 -1
  861. package/.output/server/chunks/build/client-only-qNA2OVV_.mjs +0 -43
  862. package/.output/server/chunks/build/client-only-qNA2OVV_.mjs.map +0 -1
  863. package/.output/server/chunks/build/daq-CPtsh0e7.mjs.map +0 -1
  864. package/.output/server/chunks/build/logs-CeaiO3y9.mjs.map +0 -1
  865. package/.output/server/chunks/build/pages-Bn90ijKD.mjs.map +0 -1
  866. package/.output/server/chunks/build/settings-DVAUVMsX.mjs.map +0 -1
  867. package/.output/server/chunks/build/useAmlJobs-DBuLvWMF.mjs.map +0 -1
  868. package/.output/server/chunks/build/useDcwAddNode-C8nE7geh.mjs.map +0 -1
  869. package/.output/server/chunks/build/useDcwRecipes-BzGOymqz.mjs.map +0 -1
  870. package/.output/server/chunks/build/useDcwWrites-3RxwN36I.mjs.map +0 -1
  871. package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs +0 -35
  872. package/.output/server/chunks/build/useLocalTime-9a4B-aHv.mjs.map +0 -1
  873. package/.output/server/chunks/build/useMonitorData-CEpKXoD4.mjs.map +0 -1
  874. package/.output/server/chunks/build/useMonitorFormat-_7zLapf3.mjs.map +0 -1
  875. package/.output/server/chunks/build/useWorkbenchWorkspaces-B_2mOxN6.mjs.map +0 -1
  876. package/.output/server/chunks/build/useWorkshopWs-B-Es2Xg3.mjs.map +0 -1
  877. package/.output/server/chunks/build/useWorkspaceChannels-BwB6TAWY.mjs.map +0 -1
  878. package/.output/server/chunks/build/workshop-CmgpTwyf.mjs.map +0 -1
  879. /package/.output/public/_nuxt/{B2x8SKRO.js → B2x8SKRO2.js} +0 -0
  880. /package/.output/public/_nuxt/{BzvHUfRR2.js → BzvHUfRR.js} +0 -0
  881. /package/.output/public/_nuxt/{DPUXbTg92.js → DPUXbTg9.js} +0 -0
  882. /package/.output/public/_nuxt/{DdNujZN5.js → DdNujZN52.js} +0 -0
  883. /package/.output/public/_nuxt/{DlUQa0wn2.js → DlUQa0wn.js} +0 -0
  884. /package/.output/public/_nuxt/{Du9v6qGB.js → Du9v6qGB2.js} +0 -0
  885. /package/.output/public/_nuxt/{TOWR4WpO.js → TOWR4WpO2.js} +0 -0
  886. /package/.output/public/_nuxt/{cBI0JqsV2.js → cBI0JqsV.js} +0 -0
  887. /package/.output/public/_nuxt/{zh-S_7rN.js → zh-S_7rN2.js} +0 -0
@@ -1,567 +1,621 @@
1
- # AgentWorkShop plugin development guide (full)
2
-
3
- > A plugin is a self-contained folder: an `index.mjs` entry (required), an optional
4
- > `client.mjs` (browser enhancement) and an optional `i18n.json` (message bundles).
5
- > One folder enhances both the **server** (routes / tools / DAQ / events) and the
6
- > **browser** (panel components / i18n / settings UI). Drop it in and it loads;
7
- > toggle it and the host hot-reloads. This page is the single authoritative reference;
8
- > the paged docs under `docs/site/plugins/` share the same source of truth.
9
- >
10
- > Applies to: plugin system v2 has been available since **v0.7.29** (frontend component
11
- > injection / plugin settings / plugin i18n / runtime services surface); this document
12
- > describes **v0.7.49**. `ctx.sdkVersion` is currently `0.3.0`.
13
-
14
- ## 1. Folder layout & quick start
15
-
16
- ```bash
17
- aw plugin create my-plugin # scaffolds into <repo>/.AgentWorkShop/plugins/my-plugin (project scope, default)
18
- aw plugin create my-plugin --global # or user scope ~/.AgentWorkShop/plugins/my-plugin (same as -g)
19
- aw plugin list # list all three scopes with enable state
20
- aw plugin disable my-plugin # disable (writes the state file; the server self-applies)
21
- aw plugin enable my-plugin # re-enable
22
- ```
23
-
24
- `aw plugin create` defaults to the **project** scope; `--global` / `-g` writes to the **user** scope; `--project` is the explicit spelling (equal to the default, kept for intuition); `--force` / `-f` overwrites an existing folder. Names are validated against `^[a-z][a-z0-9-]{1,31}$` (lowercase letter first, 2–32 chars). The command writes three files:
25
-
26
- ```
27
- <repo>/.AgentWorkShop/plugins/my-plugin/
28
- ├── index.mjs # server entry (required): export default { name, setup(ctx), … }
29
- ├── client.mjs # browser enhancement (optional, and must be declared as client:'./client.mjs')
30
- └── README.md
31
- ```
32
-
33
- The plugin folder may also contain `i18n.json` (message bundles, see §5). **Plugin KV does not live here** — it lives at `<configRoot>/data/plugins/<plugin>/kv.json` (see §6).
34
-
35
- ### Three scopes and precedence
36
-
37
- The host scans `builtin` → `project` → `user` and keeps the **first** match per name:
38
-
39
- | Scope | Entry path | Notes |
40
- |---|---|---|
41
- | `builtin` | `<packageRoot>/server/plugins-builtin/<name>/index.mjs` | shipped with the package, always present; the current release ships `diag-bridge` and `rag-bridge` |
42
- | `project` | `<repo>/.AgentWorkShop/plugins/<name>/index.mjs` | travels with the checkout, can be versioned in git |
43
- | `user` | `$AW_HOME/plugins/<name>/index.mjs` (default `~/.AgentWorkShop/plugins`) | user-level, shared across projects |
44
-
45
- Precedence is **builtin > project > user** (the host scans first-wins and de-duplicates through a `seen` set). Mind the direction: the CLI command registry (`cli/core/registry.mjs`) is **last-wins**, i.e. project > user > builtin — the two are opposite and must not be inferred from each other. `aw plugin list` and `aw plugin enable|disable` use this same order, so a toggle always hits the plugin you see.
46
-
47
- ### Enable/disable state file
48
-
49
- Enable state lives in **`<home>/plugins-state.json`**, where `home = $AW_HOME || ~/.AgentWorkShop`. In a source checkout this is **not** the config root:
50
-
51
- - `configRoot = <repo>/.AgentWorkShop` — data and plugin KV live here;
52
- - `home = ~/.AgentWorkShop` — the enable state lives here.
53
-
54
- For a global (home-mode) install the two coincide, which is why older text calling it "the config root" looked harmless — but in a checkout it points at the wrong path. File shape:
55
-
56
- ```json
57
- { "version": 1, "updatedAt": "2026-09-03T00:00:00.000Z", "disabled": ["my-plugin"] }
58
- ```
59
-
60
- `aw plugin enable|disable`, the web plugin page and the host all read and write the same file.
61
-
62
- ## 2. Plugin contract (`index.mjs`)
63
-
64
- `index.mjs` exports a **plain object** (zero import dependencies — the host injects `ctx`):
65
-
66
- ```js
67
- export default {
68
- name: 'my-plugin', // required, globally unique (missing / non-string fails the load)
69
- version: '1.1.0', // optional, shown in the manifest
70
- description: '…', // optional
71
- auth: 'user', // optional: none (default) | user | admin | agent-or-user
72
- client: './client.mjs', // optional: browser entry (must be a relative path string)
73
- settings: [ /* optional: setting declarations, see §4 */ ],
74
- configGroups: [ /* optional: config group declarations, see §4 */ ],
75
- routes: [ // optional: declarative routes (registered after setup returns)
76
- { method: 'GET', path: '/health', handler: () => ({ ok: true }) },
77
- ],
78
- async setup(ctx) { /* server lifecycle, see §3 */ },
79
- }
80
- ```
81
-
82
- The host shape-checks the module at load time (`validatePluginModule`): `name` must be a non-empty string; `setup`, if present, must be a function; `client`, if present, must be a string; `settings`, if present, must be an array. Any violation fails the load, is recorded in `failures` and logged — **error isolation never takes down the main service**.
83
-
84
- Registration order matters: **`await setup(ctx)` runs first, then `routes[]` are registered**. That way a throwing `setup` cannot leave a ghost route behind (a route that is live while the host has no record of the plugin).
85
-
86
- ## 3. Enable, disable and hot reload
87
-
88
- | Phase | Server | Browser |
89
- |---|---|---|
90
- | Load | nitro plugin `server/plugins/aw-plugins.ts` → three-scope scan → dynamic import (`?t=` busts the cache) → shape check → `setup(ctx)` → register `routes[]` | loader (`app/plugins/aw-plugins.client.ts`) fetches `GET /api/plugins/manifest` → fetches `GET /api/plugins/i18n` → dynamic-imports `/api/plugins/client/<name>` → `setup(ctx)` |
91
- | Inject | routes / tools / drivers / templates / processors take effect immediately; setting descriptors merge into `SystemConfigService` | panels inject into page slots through `ctx.ui.registerPanel`; `client:init` is emitted |
92
- | Run | `daq:sample` / `daq:frame` / `dcw:write` / `line:start\\|stop` / `config:changed` / `event:<type>` / `*` | `event:<type>` (same source as WS) / `page:change` / `i18n:changed` |
93
- | Reload | state-file change → full dispose (each `await`ed) → unbind hooks → drop routes → reload → emit `plugins:reloaded` | WS `plugins.reloaded` syncs instantly, plus a 15 s polling fallback → disabled plugins get `ctx.dispose()` |
94
- | Shutdown | per-plugin dispose queue (including a synchronous KV flush) → emit `server:close` | the page's `pagehide` triggers `ctx.dispose()` |
95
-
96
- Facts about hot reload:
97
-
98
- - **The trigger is the state file**: the host `fs.watch`es the directory holding `plugins-state.json`, with a **10 s mtime poll** as a fallback (Windows can drop events for atomic `rename` writes). `aw plugin enable|disable`, the web toggle, or simply touching the file all trigger a full reload **within about 1 s**.
99
- - **Code edits need no process restart**: the reload imports `entry + '?t=' + Date.now()`, which defeats the ESM module cache, so plugin code changes take effect on the next reload. The host has **no separate plugin-directory watcher** — after editing code, touch `plugins-state.json` (or toggle enable/disable once) to make it take effect immediately.
100
- - **Core code still needs a restart**: changes under `server/` and `shared/` are outside plugin hot reload.
101
- - **Race protection**: if the state file changes again while a reload is in flight (a disable→enable burst), the debounced callback hits the in-flight guard; when the load finishes the host compares the disabled-set snapshot and re-runs once if it drifted, so toggle events are never lost.
102
- - **Browser side**: a newly enabled plugin injects within 15 s at the latest (instantly when the WS channel is up); a disabled plugin is unloaded immediately together with all of its panels.
103
-
104
- ## 4. Plugin settings and config groups
105
-
106
- ### Declarative fields (manifest `settings[]`)
107
-
108
- ```js
109
- settings: [
110
- { key: 'base_url', type: 'string', default: 'http://127.0.0.1:8770',
111
- labelKey: 'plugin.my-plugin.settings.base_url', // i18n key (see §5)
112
- label: 'Service URL (fallback label)', description: 'applies live on save' },
113
- { key: 'max_turns', type: 'number', default: 0, min: 0, max: 2000, label: 'Max turns' },
114
- { key: 'auto_enabled', type: 'boolean', default: false, label: 'Automatic mode' },
115
- { key: 'mode', type: 'select', default: 'a', options: ['a', 'b'], label: 'Mode' },
116
- ],
117
- ```
118
-
119
- Hard constraints enforced by the validator (`validatePluginSettings`) — failing any one of them **skips that entry with a warning** while the rest still load:
120
-
121
- | Constraint | Rule |
122
- |---|---|
123
- | `key` | required and must match `/^[A-Za-z0-9_-]+$/` (letters, digits, `-`, `_` only) |
124
- | `type` | must be one of `'string' \| 'number' \| 'boolean' \| 'select'` |
125
- | `default` | **mandatory** — an entry without a default is skipped outright |
126
- | `select` | must supply an `options` array and `default` must be in it (compared as strings) |
127
- | `min` / `max` | honoured for `number` only (they are still written into the descriptor, but mean nothing for other types) |
128
- | `label` | falls back to `key`; `labelKey` wins when present (resolved through i18n) |
129
- | `description` | defaults to an empty string |
130
-
131
- Normalised descriptors always carry `applies: 'live'` (applies on save), and the key is force-namespaced to **`plugins.<plugin>.<key>`** — the same addressing space as global schema keys.
132
-
133
- ### Config groups (manifest `configGroups[]` + `settings[].group`)
134
-
135
- ```js
136
- configGroups: [
137
- { id: 'default', label: 'Connection', description: 'Backend URL and auth', order: 400 },
138
- { id: 'advanced', label: 'Advanced', collapsed: true },
139
- ],
140
- settings: [
141
- { key: 'base_url', type: 'string', default: 'http://127.0.0.1:8770', group: 'default', label: 'Service URL' },
142
- { key: 'timeout_ms', type: 'number', default: 8000, group: 'advanced', label: 'Timeout (ms)' },
143
- ],
144
- ```
145
-
146
- - Group ids are folded into the plugin's own namespace: `default` (or the plugin name) maps to `plugin-<plugin>`, any other declared id `x` maps to `plugin-<plugin>-x`. Two plugins may both declare `network` without overwriting each other.
147
- - `settings[].group` references the **declared id** (`default` / `advanced`), not the prefixed id.
148
- - Fields **without a `group`** land in the plugin's default group whose id is `plugin-<plugin>`; if the plugin never declared a title for that default id, the settings page shows the id itself (i.e. `plugin-<plugin>`). To get a readable title, declare `{ id: 'default', label: '…' }` as above — both shipped built-ins do exactly that.
149
- - Groups are removed automatically when the plugin is unloaded or disabled; no empty sections are left behind.
150
- - There is **no literal config group called "Plugins"**: what you see in Settings → Runtime is the plugin management list (enable switches + health check), a separate piece of UI from the plugin's own config groups.
151
-
152
- > `configGroups` is the field name the host actually reads (`host.mjs` reads `def.configGroups`). The type declaration file `sdk/index.d.mts` spells it `groups?`, which does not match the runtime — this guide and the code win.
153
-
154
- ### Runtime registration (extend conditionally inside `setup`)
155
-
156
- ```js
157
- async setup(ctx) {
158
- // Add a group and get back its real (namespaced) id
159
- const group = ctx.config.defineGroup({ id: 'rules', label: 'Automatic rules', collapsed: true })
160
- // Add a field (equivalent to manifest settings[], but registrable conditionally)
161
- ctx.config.defineField({
162
- key: 'auto_rules', type: 'string', default: '', group: 'rules', label: 'JSON rules',
163
- })
164
- ctx.logger.info(ctx.config.groups().map(g => g.id).join(','))
165
- ctx.logger.info(ctx.config.fields().map(f => f.key).join(','))
166
- // Removal (removeGroup takes the namespaced id returned by defineGroup; removeField takes the short key)
167
- if (group) ctx.config.removeGroup(group.id)
168
- ctx.config.removeField('auto_rules')
169
- }
170
- ```
171
-
172
- `defineGroup` / `defineField` enforce exactly the same validation as the declarative form (an invalid call returns `null` and logs a warning); `removeGroup` / `removeField` return a boolean telling whether anything was actually removed. These groups and fields are removed with the plugin, and re-registering never loses values that were already saved.
173
-
174
- ### Reading and persistence
175
-
176
- - Read with `ctx.config.get('plugins.my-plugin.base_url')` (every call reads the merged effective value, so a save applies live); `ctx.config.all()` returns a full snapshot.
177
- - Subscribe with `ctx.config.onChange(fn)`, which is sugar for `ctx.hooks.on('config:changed', fn)`.
178
- - Runtime overrides persist in `<configRoot>/runtime-settings.json`; `PATCH /api/system/settings` validates against the same descriptors and the settings page renders from them.
179
- - Migration: the `plugins.kb.*` / `plugins.diag.*` keys hard-coded in `shared/config/schema.json` before 0.7.28 were replaced by plugin declarations; on startup the server moves old overrides to the new keys in one pass (keeping the old key until the new one is registered, so data is never dropped).
180
-
181
- ## 5. Plugin i18n (`i18n.json`)
182
-
183
- ```json
184
- {
185
- "zh-CN": { "settings": { "base_url": "服务地址" }, "panel": { "title": "我的面板" } },
186
- "en": { "settings": { "base_url": "Service URL" }, "panel": { "title": "My panel" } }
187
- }
188
- ```
189
-
190
- - The host parses `i18n.json` at the plugin folder root during load (a broken file is ignored without blocking the load).
191
- - `GET /api/plugins/i18n` assembles the bundles of all **enabled** plugins (unauthenticated; UI copy only, **never secrets**) and returns `{ i18n: { "<plugin>": { "<locale>": { … } } }, locales: ["zh-CN", "en"] }`.
192
- - After fetching, the browser loader merges the messages into the vue-i18n namespace `plugin.<plugin>` — host components (settings `labelKey`, panel `titleKey`) resolve them directly — and `ctx.t(key)` resolves against the plugin's own message tree (`ctx.t('panel.title')` → `plugin.my-plugin.panel.title`, falling back to the key when missing).
193
- - On a locale switch the loader emits the `i18n:changed` hook; plugins receive it with `ctx.hooks.on('i18n:changed', fn)` (note: it is **not** on the `ctx.on` delivery surface).
194
- - The manifest exposes `hasI18n` / `settingsCount` / `hasClient` for the frontend.
195
-
196
- ## 6. Server `ctx` reference
197
-
198
- | Surface | Members | Notes |
199
- |---|---|---|
200
- | Identity | `ctx.name` / `ctx.scope` / `ctx.dir` / `ctx.sdkVersion` | `scope ∈ 'builtin' \| 'project' \| 'user'`; `dir` is the absolute plugin folder |
201
- | Hooks | `ctx.hooks.on / once / off / emit` | per-plugin facade; auto-unbound on hot reload, no manual `off` needed |
202
- | Logging | `ctx.logger.debug / info / warn / error` | prefixed with `[aw-plugins] [<plugin>]` |
203
- | Config | `ctx.config.get / all / onChange / defineGroup / defineField / removeGroup / removeField / groups / fields` | effective config (read-only) + change subscription + runtime group/field registration (see §4) |
204
- | Storage | `ctx.kv.get / set / all / bump` | plugin-private KV; **no `reset()`**; in-memory state is authoritative with a 200 ms debounced atomic flush |
205
- | Timers | `ctx.timer.setInterval / setTimeout` | auto-registered for disposal on shutdown and hot reload |
206
- | Cleanup | `ctx.onDispose(fn)` / `ctx.subscriptions.add(d)` | shares one host-owned dispose queue with `ctx.timer` |
207
- | Routes | `ctx.route(method, path, handler)` | mounted at `/api/plugins/<plugin><path>` (exact match); **returns a boolean** |
208
- | Platform | `ctx.api` | platform REST client (loopback origin; unwraps the envelope; call `ctx.api.setToken(token)` for authenticated endpoints) |
209
- | Network | `ctx.http.get(url, opt)` / `ctx.http.post(url, body, opt)` | outbound fetch; the guard is **protocol-only** (http/https), it does not restrict hosts; 8 s default timeout; `headers` pass through |
210
- | Events | `ctx.events.on(type, fn)` / `ctx.events.off(type, fn)` | scene-event sugar: subscribes to `event:<type>` internally (see §7) |
211
- | Paths | `ctx.paths.home / configRoot / dataDir`; `ctx.dataDir` | `ctx.dataDir` is this plugin's KV folder, `<configRoot>/data/plugins/<plugin>` |
212
- | DAQ | `ctx.daq.registerDriver / registerProcessor / registerTemplate / onFrame / onSample / query / nodes` | driver, sink-processor and node-template registration + time-series queries + node metadata snapshot |
213
- | Write control | `ctx.dcw.registerWriteDriver(driver)` | write-driver injection (symmetric to DAQ read drivers): contract `{ kind, available(), write(input), test(cfg), read?(input), meta? }`; disabled plugins lose their drivers on hot reload |
214
- | Tools | `ctx.omp.registerTool({ name, label?, description, parameters, roles?, handler })` | agent tool injection; `roles` accepts `'lead'` and `'worker'` only (see §10) |
215
- | Services | `ctx.services.names()` / `.get(name)` / `.provide(name, getter)` | runtime object surface: `get('daq' \| 'lines' \| 'channels' \| 'plugins')`; `provide` auto-prefixes `<plugin>.` |
216
- | Permissions | `ctx.permissions.lineMode / visibleLineIds / listGrants / setGrants` | line-grant queries and management |
217
-
218
- ### Communication-protocol plugins (custom interface connections)
219
-
220
- `ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` are the **protocol plug-in points**: serial
221
- links, other PLC protocols and private buses all inject here — the main pipelines (sampling loop /
222
- write gateway / frontend forms) need zero changes.
223
-
224
- ```js
225
- ctx.daq.registerDriver({
226
- kind: 'serial', // custom protocol kind (overriding a builtin logs a warning)
227
- async available() { return true }, // protocol-stack probe (missing package → UI shows "not installed")
228
- async sample({ driverConfig }) { return 25.3 }, // reading (scalar or v2 frame envelope)
229
- async test(driverConfig) { return { ok: true, message: 'connected', sampleValue: 25.3 } },
230
- meta: { // self-description (frontend "add node" dropdown + dynamic form)
231
- label: 'Serial (RS-232/485)',
232
- status: 'real',
233
- configFields: [{ key: 'path', label: 'Port path', type: 'string', required: true, placeholder: 'COM3' }],
234
- },
235
- })
236
- ```
237
-
238
- Key points:
239
- - The driver catalog is served via the `drivers` field of `GET /api/workshop/daq` (read) and
240
- `GET /api/workshop/dcw` (write); the DAQ/DCW pages render plugin protocols and their forms from it
241
- (with a ⌁ badge). Drivers registered without `meta` only appear in availability;
242
- - Hot reload clears all plugin drivers first, then re-loads: **disabled/unloaded protocol drivers
243
- take effect immediately upon removal** and never linger until restart;
244
- - Builtin example: `server/plugins-builtin/serial-bridge` (serial Modbus RTU + ASCII line protocol,
245
- with read/write drivers, `health`/`ports`/`probe` APIs, a frontend panel and the `serial_ports`
246
- agent tool) — use it as the template for new protocol plugins.
247
-
248
- ### Where KV lives
249
-
250
- `<configRoot>/data/plugins/<plugin>/kv.json` — **not** inside the plugin folder. Writes are authoritative in memory and flushed atomically (`tmp` + `rename`) after a 200 ms debounce; on shutdown and hot reload the pending debounce is cancelled and the state is flushed synchronously. `ctx.kv` has exactly four methods: `get / set / all / bump`.
251
-
252
- ```js
253
- ctx.kv.set('threshold', 180) // write (flushed after 200 ms)
254
- ctx.kv.get('threshold') // read (undefined if never written)
255
- ctx.kv.bump('alarms') // +1, returns the new value; bump('alarms', -1) decrements
256
- ctx.kv.all() // shallow copy of the whole store
257
- ```
258
-
259
- ## 7. Event reference (server)
260
-
261
- Events are dispatched through a HookBus: **listeners run serially, awaited, in registration order**, and every listener receives the **same unmodified payload object** — it is not a waterfall (only the last non-`undefined` return value is kept, and the host ignores it), and v1 has no veto or interception. A throwing listener is isolated and counted: a success resets the counter, and **8 consecutive failures remove the listener** (circuit breaker).
262
-
263
- ### Lifecycle and platform events
264
-
265
- | Event | When | Payload |
266
- |---|---|---|
267
- | `plugin:host:init` | after every plugin finished `setup` (once) | `{ plugins: string[], failures: number }` |
268
- | `plugins:reloaded` | at the end of a hot reload | `{ plugins: <manifest> }` |
269
- | `config:changed` | `runtime-settings.json` / settings page changed | `{ type, changed: string[], effective, sources }` — **there is no `at` field** |
270
- | `server:close` | on nitro close, after every per-plugin dispose ran | `{ at }` |
271
-
272
- ### Domain events
273
-
274
- | Event | When | Payload |
275
- |---|---|---|
276
- | `daq:sample` | the **publish-level** outlet after pipeline aggregation (same point and cadence as WS `daq.reading`, gated by `publishIntervalMs`) | `{ nodeId, templateRef, value, state, at, lineId }` |
277
- | `daq:frame` | after a multi-shape frame (vector/image) is recorded (metadata, metrics and preview only — no blobs) | `{ nodeId, templateRef, kind, at, lineId, preview?, metrics, thumbUrl?, state }` |
278
- | `dcw:write` | after a write-control ACK is booked (with 10 s same-value de-duplication) | `{ nodeId, name, eng, prevValue, ok, source, lineId, at }`; `source ∈ manual \| recipe \| agent \| rollback` |
279
- | `line:start` | batch window opens | `{ lineId, runId, recipeId, productName }` |
280
- | `line:stop` | batch window closes | `{ lineId, runId }` |
281
- | `event:<scene-type>` | any `broadcastSceneEvent(type, payload)`, exactly the same source as the browser WS feed | the event's own payload |
282
-
283
- ### Wildcards and scene events
284
-
285
- The server wildcard spelling is the **bare star `'*'`**, and the callback receives a `{ type, payload }` wrapper:
286
-
287
- ```js
288
- ctx.hooks.on('*', ({ type, payload }) => {
289
- ctx.logger.debug('event audit', type)
290
- void payload
291
- })
292
- ```
293
-
294
- `ctx.hooks.on('event:*', fn)` is **wrong** — it never fires. To subscribe to scene events by type, use `ctx.events`, which adds the `event:` prefix for you:
295
-
296
- ```js
297
- ctx.events.on('daq.node.changed', (p) => ctx.logger.debug('node changed', p?.op))
298
- ctx.events.on('daq.alarm.changed', (p) => ctx.logger.info('alarm state', p?.nodeId, p?.recovered))
299
- ```
300
-
301
- Common scene events: `daq.reading`, `daq.frame`, `daq.alarm`, `daq.alarm.changed`, `daq.node.changed`, `daq.controller`, `daq.template.changed`, `dcw.node.changed`, `device.created|updated|deleted`, `ops.log`, `permissions.changed`, `plugins.reloaded`, `aml.job|dataset|model`.
302
-
303
- ### Permission changes
304
-
305
- Permission changes reach the plugin bus as the scene event `permissions.changed` (its raw bus name is `event:permissions.changed`). `ctx.hooks.on('permissions:changed', …)` **never fires**; the correct form is:
306
-
307
- ```js
308
- ctx.events.on('permissions.changed', (p) => ctx.logger.info('grants changed', p?.userId))
309
- ```
310
-
311
- ### Config changes
312
-
313
- ```js
314
- ctx.hooks.on('config:changed', (p) => {
315
- ctx.logger.info('config changed', p.changed)
316
- ctx.logger.info('current theme colour', ctx.config.get('theme.primaryColor'))
317
- })
318
- // sugar: ctx.config.onChange(fn)
319
- ```
320
-
321
- ## 8. Browser `client.mjs` (`ctx` reference)
322
-
323
- The client entry must be **declared in the manifest** as `client: './client.mjs'` — merely having the file is not enough. The loader fetches the script from `/api/plugins/client/<plugin>` and calls `setup(ctx)` on the imported module.
324
-
325
- ```js
326
- export function setup(ctx) {
327
- let samples = 0
328
- const counter = ctx.el('strong', {}, ['0'])
329
-
330
- // scene event: pass the event name itself; the literal 'event:*' subscribes to all scene events
331
- ctx.on('daq.reading', () => { samples += 1; counter.textContent = String(samples) })
332
-
333
- // lifecycle hooks only come through ctx.hooks.on (they are not on the ctx.on surface)
334
- ctx.hooks.on('i18n:changed', ({ locale }) => ctx.log.info('locale switched:', locale))
335
-
336
- ctx.ui.registerPanel({
337
- slot: 'plugins.page', // slot name (see below)
338
- name: 'my-panel', // unique within the plugin
339
- titleKey: 'panel.title', // resolves plugin.my-plugin.panel.title (or use a static title)
340
- order: 10,
341
- mount(el) { // el = panel container (the slot renders the title)
342
- const box = ctx.el('div', {}, ['samples:', counter])
343
- el.append(box)
344
- const timer = setInterval(() => { counter.textContent = String(samples) }, 5000)
345
- return () => { clearInterval(timer); box.remove() } // cleanup: called when the panel unmounts
346
- },
347
- })
348
-
349
- ctx.log.info('client injected')
350
- }
351
- ```
352
-
353
- | Member | Notes |
354
- |---|---|
355
- | `ctx.name` / `ctx.sdkVersion` | plugin name / client SDK version |
356
- | `ctx.hooks` | a real HookBus: `client:init` / `page:change` / `i18n:changed` / `client:destroy` |
357
- | `ctx.on(type, fn)` | scene-event subscription (pass the event name; internally `event:<type>`); only the literal `'event:*'` is a wildcard; returns an unsubscribe function |
358
- | `ctx.fetch(path, opt)` | same-origin platform API helper (adds the cookie token, unwraps the `{code,data}` envelope, throws on non-2xx) |
359
- | `ctx.el(tag, attrs, children)` | DOM helper; `style` goes through `cssText`, `class` through `className`, `onXxx` through `addEventListener` |
360
- | `ctx.mount(target, node)` / `ctx.root()` | mount to a selector/element (falling back to `ctx.root()`) / the plugin's private mount point `#aw-plugin-<name>` |
361
- | `ctx.ui.registerPanel(entry)` / `ctx.ui.slots` | register a panel into a named slot, returns an unregister function; `slots` lists the known slot names |
362
- | `ctx.t(key, params?)` | plugin-namespaced translation (`plugin.<name>.<key>`) |
363
- | `ctx.locale` | current UI locale |
364
- | `ctx.log.info / warn / error` | console with an `[aw-plugin:<name>]` prefix |
365
- | `ctx.dispose()` | unload: emits `client:destroy` → collects subscriptions → removes `root()` (idempotent) |
366
-
367
- ### Wildcards and dead subscriptions (the common traps)
368
-
369
- | Spelling | Result |
370
- |---|---|
371
- | `ctx.on('daq.reading', fn)` | correct: subscribes to the scene event `daq.reading` |
372
- | `ctx.on('event:*', fn)` | correct: the only wildcard spelling, receives all scene events |
373
- | `ctx.on('*', fn)` | **dead subscription**: never fires |
374
- | `ctx.on('event:line.start', fn)` | **dead subscription**: it only subscribes to a scene event literally named `event:line.start`, which does not exist |
375
- | `ctx.on('daq:sample', fn)` | **dead subscription**: server hooks never cross to the browser |
376
- | `ctx.hooks.on('page:change', fn)` | correct: lifecycle hooks go through `ctx.hooks` |
377
- | `ctx.on('client:init', fn)` | **dead subscription**: lifecycle hooks are not on the `ctx.on` surface |
378
-
379
- Server hooks (`daq:sample`, `daq:frame`, `dcw:write`, `line:start|stop`) **never reach the browser**; the browser only receives scene events (through the TownBus/WS bridge). Conversely, `page:change` / `i18n:changed` exist on the browser side only.
380
-
381
- ### Panels and page slots
382
-
383
- A host page embeds `<workshop-plugin-slot slot-name="…" />` wherever it should accept injection; `ctx.ui.slots` currently holds:
384
-
385
- | Slot | Location |
386
- |---|---|
387
- | `plugins.page` | the plugin management page (the default drop point) |
388
- | `settings.plugins` | Settings → Runtime, below the plugin management area |
389
- | `dashboard.widgets` | the tail of the dashboard page |
390
-
391
- `registerPanel` returns an `unregisterPanel` function; a panel's `mount(el)` may return a cleanup function (called when the panel unmounts) and may throw — a single panel's failure is isolated by the slot and never affects the other panels or the page.
392
-
393
- ### Unload semantics
394
-
395
- - `ctx.dispose()` is triggered by the **`pagehide`** event (navigating away / entering bfcache; switching tabs does **not** trigger it). It emits `client:destroy`, collects the plugin's own subscriptions and removes `root()`.
396
- - `ctx.dispose()` does **not** unregister panels by itself — panel cleanup happens in the loader, which calls `unregisterPlugin(name)` on disable / hot reload.
397
- - Two hot channels back this up: the WS `plugins.reloaded` event (instant) plus a 15 s polling diff.
398
-
399
- ## 9. Routes and authentication
400
-
401
- `ctx.route(method, path, handler)` registers immediately and returns a **boolean**; `routes[]` in the manifest is registered only after `setup` returns. Both end up in the same route table:
402
-
403
- ```
404
- GET /api/plugins/<plugin>/health ← ctx.route('GET', '/health', handler)
405
- ← routes: [{ method: 'GET', path: '/health', handler }]
406
- ```
407
-
408
- - Matching is **exact**: paths are normalised with a leading `/`, and there are no wildcard segments or sub-route matching.
409
- - The handler receives the h3 `event`; the JSON body has already been pre-read by the platform forwarding layer and attached as **`event.awBody`** (plugins do not import h3).
410
- - The handler's return value is serialised to JSON by nitro; a throw becomes a 500 envelope (with plugin-attributed logging) and never leaks a raw stack.
411
- - **Authentication is enforced declaratively** by the `auth` field, checked **before** your handler, for `routes[]` and `ctx.route()` alike:
412
-
413
- | `auth` | Behaviour |
414
- |---|---|
415
- | `'none'` (default) | no check, the handler runs directly |
416
- | `'user'` | requires a valid user token, otherwise 401 |
417
- | `'admin'` | requires an administrator, otherwise 401 |
418
- | `'agent-or-user'` | accepts an agent identity or a user token |
419
-
420
- **Do not put authentication inside the handler**: `resolveUser(event)` is not importable from a plugin and is not on `ctx`. Declare `auth` instead.
421
-
422
- ```js
423
- ctx.route('GET', '/report', () => ({ plugin: ctx.name, at: new Date().toISOString() }))
424
-
425
- ctx.route('POST', '/threshold', (event) => {
426
- const v = Number(event.awBody?.threshold) // the host pre-read the JSON body
427
- if (!Number.isFinite(v)) return { ok: false, error: 'threshold must be a number' }
428
- ctx.kv.set('threshold', v)
429
- return { ok: true, threshold: v }
430
- })
431
- ```
432
-
433
- ## 10. Tools and team switches
434
-
435
- Tools registered with `ctx.omp.registerTool(def)` appear on every harness tool surface (omp receives them over RPC `set_host_tools`, other engines through the MCP bridge `tools/list`, pi through a tool file, mock through a direct REST invoke). A registry change is **re-pushed** to running sessions — no restart, no sub-process respawn.
436
-
437
- ```js
438
- ctx.omp.registerTool({
439
- name: 'sensor_log',
440
- label: 'Sensor calibration',
441
- description: 'Read or record sensor calibration conclusions',
442
- parameters: { type: 'object', properties: { sensor: { type: 'string' } }, required: ['sensor'] },
443
- roles: ['lead', 'worker'], // only these two literals; omitted = both roles
444
- handler: async (args, agent) => ({ text: `${agent.role}@${agent.channelId}: ${args.sensor}` }),
445
- })
446
- ```
447
-
448
- Never open a `url` / `host` injection hole in the parameter schema — plugin tool arguments come straight from the model, so any "let the model pick the target address" field becomes an SSRF surface.
449
-
450
- ### Team / channel scoped switches
451
-
452
- Beyond the global switch, every team (channel) keeps its own plugin switch set backed by the `channel_plugins` table:
453
-
454
- - **Picked at team creation**: `POST /api/workshop/teams` accepts `plugins: [{ name, enabled }]`, stored under the team id and propagated to the channel on deploy;
455
- - **Toggled at any time**: `GET|PUT /api/workshop/channels/:id/plugins` and `GET|PUT /api/workshop/teams/:id/plugins` (body `{ plugins: [{ name, enabled }] }`, full-replacement semantics, unknown or disabled plugin names are ignored);
456
- - **Semantics**: no explicit rows = unconfigured → every enabled plugin is visible to that team (backward compatible); once rows exist they filter — **a disabled plugin's tools are not injected into that team's agents and dispatch rejects them at the source**;
457
- - **UI**: the workshop teams page (creation checkboxes + team dialog).
458
-
459
- ## 11. Complete example
460
-
461
- ```js
462
- // index.mjs — server entry (zero import dependencies; the host injects ctx)
463
- export default {
464
- name: 'my-plugin',
465
- version: '1.0.0',
466
- description: 'Example: over-threshold counting + own API + config groups + agent tool',
467
- auth: 'user',
468
- client: './client.mjs',
469
- configGroups: [
470
- { id: 'default', label: 'My plugin', description: 'Basic configuration', order: 500 },
471
- { id: 'advanced', label: 'Advanced', collapsed: true },
472
- ],
473
- settings: [
474
- { key: 'threshold', type: 'number', default: 180, min: 0, max: 5000, group: 'default',
475
- label: 'Alarm threshold', description: 'applies live on save' },
476
- { key: 'endpoint', type: 'string', default: 'http://127.0.0.1:8080', group: 'advanced',
477
- label: 'External service URL' },
478
- ],
479
- routes: [
480
- { method: 'GET', path: '/health', handler: () => ({ ok: true }) },
481
- ],
482
-
483
- async setup(ctx) {
484
- ctx.logger.info(`loaded (scope=${ctx.scope}, sdk=${ctx.sdkVersion})`)
485
-
486
- const threshold = () => Number(ctx.config.get('plugins.my-plugin.threshold')) || 180
487
-
488
- // 1) publish-level DAQ samples: count over-threshold hits in plugin-private KV
489
- ctx.hooks.on('daq:sample', (s) => {
490
- if (typeof s?.value === 'number' && s.value > threshold())
491
- ctx.kv.bump(`alarm:${s.nodeId}`)
492
- })
493
-
494
- // 2) scene events (ctx.events adds the event: prefix for you)
495
- ctx.events.on('daq.alarm', (a) => ctx.logger.warn('alarm', a?.nodeId))
496
- ctx.events.on('permissions.changed', (p) => ctx.logger.info('grants changed', p?.userId))
497
-
498
- // 3) full event audit: the server wildcard is the bare '*', callback gets { type, payload }
499
- ctx.hooks.on('*', ({ type }) => ctx.logger.debug('event', type))
500
-
501
- // 4) routes[] are registered after setup; ctx.route registers now (returns a boolean)
502
- ctx.route('POST', '/threshold', (event) => {
503
- const v = Number(event.awBody?.threshold)
504
- if (!Number.isFinite(v)) return { ok: false, error: 'threshold must be a number' }
505
- ctx.kv.set('threshold', v)
506
- return { ok: true, threshold: v }
507
- })
508
-
509
- // 5) timers + cleanup: one host-owned dispose queue, run on shutdown and hot reload
510
- ctx.timer.setInterval(() => ctx.kv.set('heartbeat', new Date().toISOString()), 5000)
511
- ctx.onDispose(() => ctx.logger.info('plugin cleaned up'))
512
-
513
- // 6) agent tool (roles accepts 'lead' | 'worker' only)
514
- ctx.omp.registerTool({
515
- name: 'my_alarm_report',
516
- description: 'Read the over-threshold alarm counter recorded by this plugin',
517
- parameters: { type: 'object', properties: { nodeId: { type: 'string' } }, required: ['nodeId'] },
518
- roles: ['lead', 'worker'],
519
- handler: async (args, agent) => ({
520
- text: `${args.nodeId} alarmed ${ctx.kv.get(`alarm:${args.nodeId}`) ?? 0} times (by ${agent.role})`,
521
- }),
522
- })
523
- },
524
- }
525
- ```
526
-
527
- `client.mjs` is in §8 and `i18n.json` in §5. Which scope to install into and how to toggle are in §1.
528
-
529
- ## 12. Troubleshooting
530
-
531
- | Symptom | Cause / fix |
532
- |---|---|
533
- | Plugin code edits have no effect | the host has no plugin-directory watcher: touch `<home>/plugins-state.json`, or toggle `aw plugin enable/disable` once, to trigger a reload (the reload imports with `?t=` to defeat the cache); core `server/` and `shared/` changes still need a process restart |
534
- | Recreated a same-named plugin but routes 404 | a name previously disabled keeps its flag in `<home>/plugins-state.json`; recreating the directory does not reload it — run `aw plugin enable <name>` explicitly after recreating (verified in practice) |
535
- | Load failures are invisible | `aw plugin list` does **not** show them. Use `GET /api/workshop/plugins` (returns `{ plugins, failures, initedAt }`, **no `{code,data}` envelope**) or read `[aw-plugins] 装载失败 …` in the startup log |
536
- | Plugin route 404 | matching is exact; check the method and the leading `/` of the path, and whether the plugin is enabled (a disabled plugin's routes are dropped from the table) |
537
- | Plugin route 401 | the entry declared `auth: 'user' \| 'admin' \| 'agent-or-user'` and the request carried no valid credential; use `auth: 'none'` only for open endpoints |
538
- | Client panel never appears | check the browser console for `[aw-plugins]` warnings; confirm the manifest says `hasClient: true` (i.e. the manifest declares `client:'./client.mjs'`) and the plugin is `enabled !== false` |
539
- | A client subscription never fires | check you did not fall into the §8 traps (`ctx.on('*')`, `ctx.on('event:line.start')`, `ctx.on('daq:sample')`, or handing a lifecycle hook to `ctx.on`) |
540
- | A setting does not render | a declaration without `default` is skipped with a warning; a `select`'s `default` must be inside `options`; `key` allows letters, digits, `-` and `_` only |
541
- | A changed setting has no effect | read it as `ctx.config.get('plugins.<plugin>.<key>')` (the short key reads nothing) |
542
- | The `permissions:changed` hook never fires | that name does not exist; use `ctx.events.on('permissions.changed', fn)` |
543
- | KV data "disappeared" | KV is at `<configRoot>/data/plugins/<plugin>/kv.json`, not in the plugin folder; writes flush after a 200 ms debounce, so a hard kill has a tiny loss window |
544
- | `ctx.api` returns 401 | authenticated endpoints need `ctx.api.setToken(token)`; unauthenticated ones (manifest / ping) do not |
545
-
546
- **Trust model**: a plugin is arbitrary node code, on par with aw commands — only install and enable plugins you trust.
547
-
548
- ### Known boundaries (intentional, not defects)
549
-
550
- - **Hooks are observational**: v1 has no veto / interception / rewriting — write-control interlock integrity comes first.
551
- - **The `ctx.http` guard is protocol-only** (http/https) and does **not** restrict hosts; do your own allow-listing inside the plugin (the built-in `diag-bridge` / `rag-bridge` do exactly that).
552
- - **`ctx.t(key, params)` currently ignores `params`** (no interpolation); build strings yourself inside the plugin.
553
- - **`ctx.kv` has no `reset()`** — only `get / set / all / bump`.
554
- - **Server and browser subscription surfaces do not overlap**: server hooks never cross the bridge, and browser events never reach the server.
555
- - **A newly enabled browser plugin injects within 15 s** (instantly when the WS channel is up).
556
- - **`server:close` / `onDispose` depend on graceful shutdown signals**; a forced kill on Windows does not trigger them.
557
- - **`aw plugin list` only scans directories** — it reflects neither load failures nor runtime health.
558
- - **Protocol plugins share the host process with their native stacks**: serial-bridge's RTU framing is a self-contained implementation (single serialport native stack; modbus-serial is deliberately NOT used — its wrapper reproducibly crashed under dev mode). Known boundary: **inside the `aw dev` process, a serial transaction that ends in write → timeout → close can trigger a native V8 crash in `bindings-cpp`** (HandleScope fatal — not catchable in JS, regardless of which port is used); **production mode (`aw start`) is unaffected — the exact same sequence was verified live to return an honest timeout with the server healthy**, and open-only probes are also fine under dev. Connect real hardware during development with this in mind; for hardened deployments, run the serial gateway in its own process (see the plc-node-simulator child-process pattern).
559
-
560
- ## 13. Pre-release checklist
561
-
562
- 1. `aw plugin list` shows the plugin in all applicable scopes; a client plugin shows `+client`; toggling enable/disable makes routes, tools and panels appear and disappear together (a rapid toggle burst must not lose events).
563
- 2. `GET /api/workshop/plugins` reports an empty `failures`; `GET /api/plugins/manifest` reports `settingsCount` / `hasClient` / `hasI18n` matching the declaration.
564
- 3. The settings page shows the plugin's config group (fields without a declared group land in the `plugin-<plugin>` group); after a save, `ctx.config.get('plugins.<plugin>.<key>')` returns the new value immediately.
565
- 4. Locale switching updates panel titles and copy; the `i18n:changed` cleanup leaves no leaks.
566
- 5. The tool is visible through `GET /api/workshop/agent-tools/list`; its parameters expose no `url` / `host` injection hole; after the team switch is turned off the tool disappears and dispatch is rejected.
567
- 6. Trigger one hot reload (toggle enable/disable) and confirm `plugins:reloaded` reaches the plugin with no KV or timer leak.
1
+ # AgentWorkShop plugin development guide (full)
2
+
3
+ > A plugin is a self-contained folder: an `index.mjs` entry (required), an optional
4
+ > `client.mjs` (browser enhancement) and an optional `i18n.json` (message bundles).
5
+ > One folder enhances both the **server** (routes / tools / DAQ / events) and the
6
+ > **browser** (panel components / i18n / settings UI). Drop it in and it loads;
7
+ > toggle it and the host hot-reloads. This page is the single authoritative reference;
8
+ > the paged docs under `docs/site/plugins/` share the same source of truth.
9
+ >
10
+ > Applies to: plugin system v2 has been available since **v0.7.29** (frontend component
11
+ > injection / plugin settings / plugin i18n / runtime services surface); this document
12
+ > describes **v0.7.51**. `ctx.sdkVersion` is currently `0.3.0`.
13
+
14
+ ## 1. Folder layout & quick start
15
+
16
+ ```bash
17
+ aw plugin create my-plugin # scaffolds into <repo>/.AgentWorkShop/plugins/my-plugin (project scope, default)
18
+ aw plugin create my-plugin --global # or user scope ~/.AgentWorkShop/plugins/my-plugin (same as -g)
19
+ aw plugin list # list all three scopes with enable state
20
+ aw plugin disable my-plugin # disable (writes the state file; the server self-applies)
21
+ aw plugin enable my-plugin # re-enable
22
+ ```
23
+
24
+ `aw plugin create` defaults to the **project** scope; `--global` / `-g` writes to the **user** scope; `--project` is the explicit spelling (equal to the default, kept for intuition); `--force` / `-f` overwrites an existing folder. Names are validated against `^[a-z][a-z0-9-]{1,31}$` (lowercase letter first, 2–32 chars). The command writes three files:
25
+
26
+ ```
27
+ <repo>/.AgentWorkShop/plugins/my-plugin/
28
+ ├── index.mjs # server entry (required): export default { name, setup(ctx), … }
29
+ ├── client.mjs # browser enhancement (optional, and must be declared as client:'./client.mjs')
30
+ └── README.md
31
+ ```
32
+
33
+ The plugin folder may also contain `i18n.json` (message bundles, see §5). **Plugin KV does not live here** — it lives at `<configRoot>/data/plugins/<plugin>/kv.json` (see §6).
34
+
35
+ ### Three scopes and precedence
36
+
37
+ The host scans `builtin` → `project` → `user` and keeps the **first** match per name:
38
+
39
+ | Scope | Entry path | Notes |
40
+ |---|---|---|
41
+ | `builtin` | `<packageRoot>/server/plugins-builtin/<name>/index.mjs` | shipped with the package, always present; the current release ships `diag-bridge` and `rag-bridge` |
42
+ | `project` | `<repo>/.AgentWorkShop/plugins/<name>/index.mjs` | travels with the checkout, can be versioned in git |
43
+ | `user` | `$AW_HOME/plugins/<name>/index.mjs` (default `~/.AgentWorkShop/plugins`) | user-level, shared across projects |
44
+
45
+ Precedence is **builtin > project > user** (the host scans first-wins and de-duplicates through a `seen` set). Mind the direction: the CLI command registry (`cli/core/registry.mjs`) is **last-wins**, i.e. project > user > builtin — the two are opposite and must not be inferred from each other. `aw plugin list` and `aw plugin enable|disable` use this same order, so a toggle always hits the plugin you see.
46
+
47
+ ### Enable/disable state file
48
+
49
+ Enable state lives in **`<home>/plugins-state.json`**, where `home = $AW_HOME || ~/.AgentWorkShop`. In a source checkout this is **not** the config root:
50
+
51
+ - `configRoot = <repo>/.AgentWorkShop` — data and plugin KV live here;
52
+ - `home = ~/.AgentWorkShop` — the enable state lives here.
53
+
54
+ For a global (home-mode) install the two coincide, which is why older text calling it "the config root" looked harmless — but in a checkout it points at the wrong path. File shape:
55
+
56
+ ```json
57
+ { "version": 1, "updatedAt": "2026-09-03T00:00:00.000Z", "disabled": ["my-plugin"] }
58
+ ```
59
+
60
+ `aw plugin enable|disable`, the web plugin page and the host all read and write the same file.
61
+
62
+ ## 2. Plugin contract (`index.mjs`)
63
+
64
+ `index.mjs` exports a **plain object** (zero import dependencies — the host injects `ctx`):
65
+
66
+ ```js
67
+ export default {
68
+ name: 'my-plugin', // required, globally unique (missing / non-string fails the load)
69
+ version: '1.1.0', // optional, shown in the manifest
70
+ description: '…', // optional
71
+ auth: 'user', // optional: none (default) | user | admin | agent-or-user
72
+ client: './client.mjs', // optional: browser entry (must be a relative path string)
73
+ settings: [ /* optional: setting declarations, see §4 */ ],
74
+ configGroups: [ /* optional: config group declarations, see §4 */ ],
75
+ routes: [ // optional: declarative routes (registered after setup returns)
76
+ { method: 'GET', path: '/health', handler: () => ({ ok: true }) },
77
+ ],
78
+ async setup(ctx) { /* server lifecycle, see §3 */ },
79
+ }
80
+ ```
81
+
82
+ The host shape-checks the module at load time (`validatePluginModule`): `name` must be a non-empty string; `setup`, if present, must be a function; `client`, if present, must be a string; `settings`, if present, must be an array. Any violation fails the load, is recorded in `failures` and logged — **error isolation never takes down the main service**.
83
+
84
+ Registration order matters: **`await setup(ctx)` runs first, then `routes[]` are registered**. That way a throwing `setup` cannot leave a ghost route behind (a route that is live while the host has no record of the plugin).
85
+
86
+ ## 3. Enable, disable and hot reload
87
+
88
+ | Phase | Server | Browser |
89
+ |---|---|---|
90
+ | Load | nitro plugin `server/plugins/aw-plugins.ts` → three-scope scan → dynamic import (`?t=` busts the cache) → shape check → `setup(ctx)` → register `routes[]` | loader (`app/plugins/aw-plugins.client.ts`) fetches `GET /api/plugins/manifest` → fetches `GET /api/plugins/i18n` → dynamic-imports `/api/plugins/client/<name>` → `setup(ctx)` |
91
+ | Inject | routes / tools / drivers / templates / processors take effect immediately; setting descriptors merge into `SystemConfigService` | panels inject into page slots through `ctx.ui.registerPanel`; `client:init` is emitted |
92
+ | Run | `daq:sample` / `daq:frame` / `dcw:write` / `line:start\\|stop` / `config:changed` / `event:<type>` / `*` | `event:<type>` (same source as WS) / `page:change` / `i18n:changed` |
93
+ | Reload | state-file change → full dispose (each `await`ed) → unbind hooks → drop routes → reload → emit `plugins:reloaded` | WS `plugins.reloaded` syncs instantly, plus a 15 s polling fallback → disabled plugins get `ctx.dispose()` |
94
+ | Shutdown | per-plugin dispose queue (including a synchronous KV flush) → emit `server:close` | the page's `pagehide` triggers `ctx.dispose()` |
95
+
96
+ Facts about hot reload:
97
+
98
+ - **The trigger is the state file**: the host `fs.watch`es the directory holding `plugins-state.json`, with a **10 s mtime poll** as a fallback (Windows can drop events for atomic `rename` writes). `aw plugin enable|disable`, the web toggle, or simply touching the file all trigger a full reload **within about 1 s**.
99
+ - **Code edits need no process restart**: the reload imports `entry + '?t=' + Date.now()`, which defeats the ESM module cache, so plugin code changes take effect on the next reload. The host has **no separate plugin-directory watcher** — after editing code, touch `plugins-state.json` (or toggle enable/disable once) to make it take effect immediately.
100
+ - **Core code still needs a restart**: changes under `server/` and `shared/` are outside plugin hot reload.
101
+ - **Race protection**: if the state file changes again while a reload is in flight (a disable→enable burst), the debounced callback hits the in-flight guard; when the load finishes the host compares the disabled-set snapshot and re-runs once if it drifted, so toggle events are never lost.
102
+ - **Browser side**: a newly enabled plugin injects within 15 s at the latest (instantly when the WS channel is up); a disabled plugin is unloaded immediately together with all of its panels.
103
+
104
+ ## 4. Plugin settings and config groups
105
+
106
+ ### Declarative fields (manifest `settings[]`)
107
+
108
+ ```js
109
+ settings: [
110
+ { key: 'base_url', type: 'string', default: 'http://127.0.0.1:8770',
111
+ labelKey: 'plugin.my-plugin.settings.base_url', // i18n key (see §5)
112
+ label: 'Service URL (fallback label)', description: 'applies live on save' },
113
+ { key: 'max_turns', type: 'number', default: 0, min: 0, max: 2000, label: 'Max turns' },
114
+ { key: 'auto_enabled', type: 'boolean', default: false, label: 'Automatic mode' },
115
+ { key: 'mode', type: 'select', default: 'a', options: ['a', 'b'], label: 'Mode' },
116
+ ],
117
+ ```
118
+
119
+ Hard constraints enforced by the validator (`validatePluginSettings`) — failing any one of them **skips that entry with a warning** while the rest still load:
120
+
121
+ | Constraint | Rule |
122
+ |---|---|
123
+ | `key` | required and must match `/^[A-Za-z0-9_-]+$/` (letters, digits, `-`, `_` only) |
124
+ | `type` | must be one of `'string' \| 'number' \| 'boolean' \| 'select'` |
125
+ | `default` | **mandatory** — an entry without a default is skipped outright |
126
+ | `select` | must supply an `options` array and `default` must be in it (compared as strings) |
127
+ | `min` / `max` | honoured for `number` only (they are still written into the descriptor, but mean nothing for other types) |
128
+ | `label` | falls back to `key`; `labelKey` wins when present (resolved through i18n) |
129
+ | `description` | defaults to an empty string |
130
+
131
+ Normalised descriptors always carry `applies: 'live'` (applies on save), and the key is force-namespaced to **`plugins.<plugin>.<key>`** — the same addressing space as global schema keys.
132
+
133
+ ### Config groups (manifest `configGroups[]` + `settings[].group`)
134
+
135
+ ```js
136
+ configGroups: [
137
+ { id: 'default', label: 'Connection', description: 'Backend URL and auth', order: 400 },
138
+ { id: 'advanced', label: 'Advanced', collapsed: true },
139
+ ],
140
+ settings: [
141
+ { key: 'base_url', type: 'string', default: 'http://127.0.0.1:8770', group: 'default', label: 'Service URL' },
142
+ { key: 'timeout_ms', type: 'number', default: 8000, group: 'advanced', label: 'Timeout (ms)' },
143
+ ],
144
+ ```
145
+
146
+ - Group ids are folded into the plugin's own namespace: `default` (or the plugin name) maps to `plugin-<plugin>`, any other declared id `x` maps to `plugin-<plugin>-x`. Two plugins may both declare `network` without overwriting each other.
147
+ - `settings[].group` references the **declared id** (`default` / `advanced`), not the prefixed id.
148
+ - Fields **without a `group`** land in the plugin's default group whose id is `plugin-<plugin>`; if the plugin never declared a title for that default id, the settings page shows the id itself (i.e. `plugin-<plugin>`). To get a readable title, declare `{ id: 'default', label: '…' }` as above — both shipped built-ins do exactly that.
149
+ - Groups are removed automatically when the plugin is unloaded or disabled; no empty sections are left behind.
150
+ - There is **no literal config group called "Plugins"**: what you see in Settings → Runtime is the plugin management list (enable switches + health check), a separate piece of UI from the plugin's own config groups.
151
+
152
+ > `configGroups` is the field name the host actually reads (`host.mjs` reads `def.configGroups`). The type declaration file `sdk/index.d.mts` spells it `groups?`, which does not match the runtime — this guide and the code win.
153
+
154
+ ### Runtime registration (extend conditionally inside `setup`)
155
+
156
+ ```js
157
+ async setup(ctx) {
158
+ // Add a group and get back its real (namespaced) id
159
+ const group = ctx.config.defineGroup({ id: 'rules', label: 'Automatic rules', collapsed: true })
160
+ // Add a field (equivalent to manifest settings[], but registrable conditionally)
161
+ ctx.config.defineField({
162
+ key: 'auto_rules', type: 'string', default: '', group: 'rules', label: 'JSON rules',
163
+ })
164
+ ctx.logger.info(ctx.config.groups().map(g => g.id).join(','))
165
+ ctx.logger.info(ctx.config.fields().map(f => f.key).join(','))
166
+ // Removal (removeGroup takes the namespaced id returned by defineGroup; removeField takes the short key)
167
+ if (group) ctx.config.removeGroup(group.id)
168
+ ctx.config.removeField('auto_rules')
169
+ }
170
+ ```
171
+
172
+ `defineGroup` / `defineField` enforce exactly the same validation as the declarative form (an invalid call returns `null` and logs a warning); `removeGroup` / `removeField` return a boolean telling whether anything was actually removed. These groups and fields are removed with the plugin, and re-registering never loses values that were already saved.
173
+
174
+ ### Reading and persistence
175
+
176
+ - Read with `ctx.config.get('plugins.my-plugin.base_url')` (every call reads the merged effective value, so a save applies live); `ctx.config.all()` returns a full snapshot.
177
+ - Subscribe with `ctx.config.onChange(fn)`, which is sugar for `ctx.hooks.on('config:changed', fn)`.
178
+ - Runtime overrides persist in `<configRoot>/runtime-settings.json`; `PATCH /api/system/settings` validates against the same descriptors and the settings page renders from them.
179
+ - Migration: the `plugins.kb.*` / `plugins.diag.*` keys hard-coded in `shared/config/schema.json` before 0.7.28 were replaced by plugin declarations; on startup the server moves old overrides to the new keys in one pass (keeping the old key until the new one is registered, so data is never dropped).
180
+
181
+ ## 5. Plugin i18n (`i18n.json`)
182
+
183
+ ```json
184
+ {
185
+ "zh-CN": { "settings": { "base_url": "服务地址" }, "panel": { "title": "我的面板" } },
186
+ "en": { "settings": { "base_url": "Service URL" }, "panel": { "title": "My panel" } }
187
+ }
188
+ ```
189
+
190
+ - The host parses `i18n.json` at the plugin folder root during load (a broken file is ignored without blocking the load).
191
+ - `GET /api/plugins/i18n` assembles the bundles of all **enabled** plugins (unauthenticated; UI copy only, **never secrets**) and returns `{ i18n: { "<plugin>": { "<locale>": { … } } }, locales: ["zh-CN", "en"] }`.
192
+ - After fetching, the browser loader merges the messages into the vue-i18n namespace `plugin.<plugin>` — host components (settings `labelKey`, panel `titleKey`) resolve them directly — and `ctx.t(key)` resolves against the plugin's own message tree (`ctx.t('panel.title')` → `plugin.my-plugin.panel.title`, falling back to the key when missing).
193
+ - On a locale switch the loader emits the `i18n:changed` hook; plugins receive it with `ctx.hooks.on('i18n:changed', fn)` (note: it is **not** on the `ctx.on` delivery surface).
194
+ - The manifest exposes `hasI18n` / `settingsCount` / `hasClient` for the frontend.
195
+
196
+ ## 6. Server `ctx` reference
197
+
198
+ | Surface | Members | Notes |
199
+ |---|---|---|
200
+ | Identity | `ctx.name` / `ctx.scope` / `ctx.dir` / `ctx.sdkVersion` | `scope ∈ 'builtin' \| 'project' \| 'user'`; `dir` is the absolute plugin folder |
201
+ | Hooks | `ctx.hooks.on / once / off / emit` | per-plugin facade; auto-unbound on hot reload, no manual `off` needed |
202
+ | Logging | `ctx.logger.debug / info / warn / error` | prefixed with `[aw-plugins] [<plugin>]` |
203
+ | Config | `ctx.config.get / all / onChange / defineGroup / defineField / removeGroup / removeField / groups / fields` | effective config (read-only) + change subscription + runtime group/field registration (see §4) |
204
+ | Storage | `ctx.kv.get / set / all / bump` | plugin-private KV; **no `reset()`**; in-memory state is authoritative with a 200 ms debounced atomic flush |
205
+ | Timers | `ctx.timer.setInterval / setTimeout` | auto-registered for disposal on shutdown and hot reload |
206
+ | Cleanup | `ctx.onDispose(fn)` / `ctx.subscriptions.add(d)` | shares one host-owned dispose queue with `ctx.timer` |
207
+ | Routes | `ctx.route(method, path, handler)` | mounted at `/api/plugins/<plugin><path>` (exact match); **returns a boolean** |
208
+ | Platform | `ctx.api` | platform REST client (loopback origin; unwraps the envelope; call `ctx.api.setToken(token)` for authenticated endpoints) |
209
+ | Network | `ctx.http.get(url, opt)` / `ctx.http.post(url, body, opt)` | outbound fetch; the guard is **protocol-only** (http/https), it does not restrict hosts; 8 s default timeout; `headers` pass through |
210
+ | Events | `ctx.events.on(type, fn)` / `ctx.events.off(type, fn)` | scene-event sugar: subscribes to `event:<type>` internally (see §7) |
211
+ | Paths | `ctx.paths.home / configRoot / dataDir`; `ctx.dataDir` | `ctx.dataDir` is this plugin's KV folder, `<configRoot>/data/plugins/<plugin>` |
212
+ | DAQ | `ctx.daq.registerDriver / registerProcessor / registerTemplate / onFrame / onSample / query / nodes` | driver, sink-processor and node-template registration + time-series queries + node metadata snapshot |
213
+ | Write control | `ctx.dcw.registerWriteDriver(driver)` | write-driver injection (symmetric to DAQ read drivers): contract `{ kind, available(), write(input), test(cfg), read?(input), meta? }`; disabled plugins lose their drivers on hot reload |
214
+ | Tools | `ctx.omp.registerTool({ name, label?, description, parameters, roles?, handler })` | agent tool injection; `roles` accepts `'lead'` and `'worker'` only (see §10) |
215
+ | Services | `ctx.services.names()` / `.get(name)` / `.provide(name, getter)` | runtime object surface: `get('daq' \| 'lines' \| 'channels' \| 'plugins')`; `provide` auto-prefixes `<plugin>.` |
216
+ | Twin | `ctx.twin.registerPhysicsProvider / registerScenePack / registerObjectiveProfile / registerTrainingAdapter / registerSolverAdapter`; `listProviders / getProviderHealth / resolveProvider / validateProvider / retireProvider / isRegistryAvailable` | hybrid-twin **Provider Registry** bridge: register hot-swappable industrial physics cores / scene packs / solvers (next section) |
217
+ | Permissions | `ctx.permissions.lineMode / visibleLineIds / listGrants / setGrants` | line-grant queries and management |
218
+
219
+ ### Hybrid-twin provider plugins (`ctx.twin`)
220
+
221
+ `ctx.twin` is the plugin surface of the **hybrid-twin Provider Registry**: register an industrial scene's
222
+ physics core, scene pack and solvers. `twin_scene_read` / `twin_snapshot_create` / `twin_trial_run` /
223
+ `mpc_optimize` resolve the provider through `SceneContract.physicsProfileId` (falling back to the bundled
224
+ `twin-injection-default`), so **swapping scenes needs no tool or channel change**.
225
+
226
+ | Registration surface | Payload | Notes |
227
+ |---|---|---|
228
+ | `registerPhysicsProvider(provider)` | `PhysicsProviderManifest` + `initialize` / `step` / `simulate` / `evaluateConstraints`, optional `validateScene` / `calibrate` / `designSafeExperiment` / `estimateUncertainty` / `exportArtifacts` / `health` | the physics core (grey-box / white-box / ROM); `apiVersion` must be `twin-provider.v1` |
229
+ | `registerScenePack(pack)` | `{ sceneKind, sceneSchemaVersion, compile?, discover?, constraints?, objectives?, datasetSchema?, metadata? }` | scene pack: compiles field nodes + recipe into a `SceneContract` |
230
+ | `registerObjectiveProfile(profile)` | objectives and weights | the MPC objective surface |
231
+ | `registerTrainingAdapter(adapter)` / `registerSolverAdapter(adapter)` | training / solver adapters | external solvers (ONNX / ROM / third-party) |
232
+ | `listProviders(filter?)` · `getProviderHealth(id, version?)` · `resolveProvider(id, version?)` · `validateProvider(id, version?)` · `retireProvider(id, version?)` · `isRegistryAvailable()` | — | query, health, resolve, validate and retire |
233
+
234
+ **Manifest essentials**: `providerId` / `version` / `apiVersion` / `sceneKinds[]` / `backend`
235
+ (`typescript` | `pytorch` | `python` | `onnx` | `rom` | `external-solver`) / the four variable tables
236
+ (`stateVariables` / `controlVariables` / `disturbanceVariables` / `observationVariables?`) /
237
+ `parameterPriors` / `capabilities` (`onlineStep` / `rollout` / `calibration` / `uncertainty` / `externalSolver` / `mpc`) /
238
+ `artifactContract`.
239
+
240
+ **Generational hot swap**: the lifecycle is `DISCOVERED → VALIDATED → REGISTERED → READY → DRAINING → RETIRED`
241
+ (`FAILED` on rejection). A hot reload stages the new **generation** and drains the old one: trials and MPC hold a
242
+ lease on the old generation, so **in-flight work is never invalidated**, and `retireProvider` only unloads after
243
+ references reach zero.
244
+
245
+ **Minimal example** (the bundled injection provider uses this very contract, see
246
+ `server/plugins-builtin/twin-injection-default/index.mjs`):
247
+
248
+ ```js
249
+ export default {
250
+ name: 'my-injection-provider',
251
+ version: '1.0.0',
252
+ auth: 'none',
253
+ async setup(ctx) {
254
+ const core = await ctx.services.get('twinCore')
255
+ const provider = core.createInjectionProvider() // swap in your own physics core
256
+ ctx.twin.registerPhysicsProvider(provider)
257
+ ctx.twin.registerScenePack({
258
+ sceneKind: 'injection',
259
+ sceneSchemaVersion: '1.0.0',
260
+ compile: () => core.defaultInjectionScene('my-injection-provider'),
261
+ })
262
+ ctx.logger.info(`registered ${provider.manifest.physicsModelId}@${provider.manifest.version}`)
263
+ },
264
+ }
265
+ ```
266
+
267
+ **Where it lives**: the same plugin scopes as any other plugin (see §1) —
268
+ `~/.AgentWorkShop/plugins/<plugin>/` (user), `<repo>/.AgentWorkShop/plugins/<plugin>/` (project),
269
+ `server/plugins-builtin/<plugin>/` (shipped with the package). Disabling or hot-reloading a plugin
270
+ re-stages or drains its generation **without a server restart**.
271
+
272
+ ### Communication-protocol plugins (custom interface connections)
273
+
274
+ `ctx.daq.registerDriver` / `ctx.dcw.registerWriteDriver` are the **protocol plug-in points**: serial
275
+ links, other PLC protocols and private buses all inject here — the main pipelines (sampling loop /
276
+ write gateway / frontend forms) need zero changes.
277
+
278
+ ```js
279
+ ctx.daq.registerDriver({
280
+ kind: 'serial', // custom protocol kind (overriding a builtin logs a warning)
281
+ async available() { return true }, // protocol-stack probe (missing package → UI shows "not installed")
282
+ async sample({ driverConfig }) { return 25.3 }, // reading (scalar or v2 frame envelope)
283
+ async test(driverConfig) { return { ok: true, message: 'connected', sampleValue: 25.3 } },
284
+ meta: { // self-description (frontend "add node" dropdown + dynamic form)
285
+ label: 'Serial (RS-232/485)',
286
+ status: 'real',
287
+ configFields: [{ key: 'path', label: 'Port path', type: 'string', required: true, placeholder: 'COM3' }],
288
+ },
289
+ })
290
+ ```
291
+
292
+ Key points:
293
+ - The driver catalog is served via the `drivers` field of `GET /api/workshop/daq` (read) and
294
+ `GET /api/workshop/dcw` (write); the DAQ/DCW pages render plugin protocols and their forms from it
295
+ (with a ⌁ badge). Drivers registered without `meta` only appear in availability;
296
+ - Hot reload clears all plugin drivers first, then re-loads: **disabled/unloaded protocol drivers
297
+ take effect immediately upon removal** and never linger until restart;
298
+ - Builtin example: `server/plugins-builtin/serial-bridge` (serial Modbus RTU + ASCII line protocol,
299
+ with read/write drivers, `health`/`ports`/`probe` APIs, a frontend panel and the `serial_ports`
300
+ agent tool) — use it as the template for new protocol plugins.
301
+
302
+ ### Where KV lives
303
+
304
+ `<configRoot>/data/plugins/<plugin>/kv.json` — **not** inside the plugin folder. Writes are authoritative in memory and flushed atomically (`tmp` + `rename`) after a 200 ms debounce; on shutdown and hot reload the pending debounce is cancelled and the state is flushed synchronously. `ctx.kv` has exactly four methods: `get / set / all / bump`.
305
+
306
+ ```js
307
+ ctx.kv.set('threshold', 180) // write (flushed after 200 ms)
308
+ ctx.kv.get('threshold') // read (undefined if never written)
309
+ ctx.kv.bump('alarms') // +1, returns the new value; bump('alarms', -1) decrements
310
+ ctx.kv.all() // shallow copy of the whole store
311
+ ```
312
+
313
+ ## 7. Event reference (server)
314
+
315
+ Events are dispatched through a HookBus: **listeners run serially, awaited, in registration order**, and every listener receives the **same unmodified payload object** — it is not a waterfall (only the last non-`undefined` return value is kept, and the host ignores it), and v1 has no veto or interception. A throwing listener is isolated and counted: a success resets the counter, and **8 consecutive failures remove the listener** (circuit breaker).
316
+
317
+ ### Lifecycle and platform events
318
+
319
+ | Event | When | Payload |
320
+ |---|---|---|
321
+ | `plugin:host:init` | after every plugin finished `setup` (once) | `{ plugins: string[], failures: number }` |
322
+ | `plugins:reloaded` | at the end of a hot reload | `{ plugins: <manifest> }` |
323
+ | `config:changed` | `runtime-settings.json` / settings page changed | `{ type, changed: string[], effective, sources }` — **there is no `at` field** |
324
+ | `server:close` | on nitro close, after every per-plugin dispose ran | `{ at }` |
325
+
326
+ ### Domain events
327
+
328
+ | Event | When | Payload |
329
+ |---|---|---|
330
+ | `daq:sample` | the **publish-level** outlet after pipeline aggregation (same point and cadence as WS `daq.reading`, gated by `publishIntervalMs`) | `{ nodeId, templateRef, value, state, at, lineId }` |
331
+ | `daq:frame` | after a multi-shape frame (vector/image) is recorded (metadata, metrics and preview only — no blobs) | `{ nodeId, templateRef, kind, at, lineId, preview?, metrics, thumbUrl?, state }` |
332
+ | `dcw:write` | after a write-control ACK is booked (with 10 s same-value de-duplication) | `{ nodeId, name, eng, prevValue, ok, source, lineId, at }`; `source ∈ manual \| recipe \| agent \| rollback` |
333
+ | `line:start` | batch window opens | `{ lineId, runId, recipeId, productName }` |
334
+ | `line:stop` | batch window closes | `{ lineId, runId }` |
335
+ | `event:<scene-type>` | any `broadcastSceneEvent(type, payload)`, exactly the same source as the browser WS feed | the event's own payload |
336
+
337
+ ### Wildcards and scene events
338
+
339
+ The server wildcard spelling is the **bare star `'*'`**, and the callback receives a `{ type, payload }` wrapper:
340
+
341
+ ```js
342
+ ctx.hooks.on('*', ({ type, payload }) => {
343
+ ctx.logger.debug('event audit', type)
344
+ void payload
345
+ })
346
+ ```
347
+
348
+ `ctx.hooks.on('event:*', fn)` is **wrong** — it never fires. To subscribe to scene events by type, use `ctx.events`, which adds the `event:` prefix for you:
349
+
350
+ ```js
351
+ ctx.events.on('daq.node.changed', (p) => ctx.logger.debug('node changed', p?.op))
352
+ ctx.events.on('daq.alarm.changed', (p) => ctx.logger.info('alarm state', p?.nodeId, p?.recovered))
353
+ ```
354
+
355
+ Common scene events: `daq.reading`, `daq.frame`, `daq.alarm`, `daq.alarm.changed`, `daq.node.changed`, `daq.controller`, `daq.template.changed`, `dcw.node.changed`, `device.created|updated|deleted`, `ops.log`, `permissions.changed`, `plugins.reloaded`, `aml.job|dataset|model`.
356
+
357
+ ### Permission changes
358
+
359
+ Permission changes reach the plugin bus as the scene event `permissions.changed` (its raw bus name is `event:permissions.changed`). `ctx.hooks.on('permissions:changed', …)` **never fires**; the correct form is:
360
+
361
+ ```js
362
+ ctx.events.on('permissions.changed', (p) => ctx.logger.info('grants changed', p?.userId))
363
+ ```
364
+
365
+ ### Config changes
366
+
367
+ ```js
368
+ ctx.hooks.on('config:changed', (p) => {
369
+ ctx.logger.info('config changed', p.changed)
370
+ ctx.logger.info('current theme colour', ctx.config.get('theme.primaryColor'))
371
+ })
372
+ // sugar: ctx.config.onChange(fn)
373
+ ```
374
+
375
+ ## 8. Browser `client.mjs` (`ctx` reference)
376
+
377
+ The client entry must be **declared in the manifest** as `client: './client.mjs'` — merely having the file is not enough. The loader fetches the script from `/api/plugins/client/<plugin>` and calls `setup(ctx)` on the imported module.
378
+
379
+ ```js
380
+ export function setup(ctx) {
381
+ let samples = 0
382
+ const counter = ctx.el('strong', {}, ['0'])
383
+
384
+ // scene event: pass the event name itself; the literal 'event:*' subscribes to all scene events
385
+ ctx.on('daq.reading', () => { samples += 1; counter.textContent = String(samples) })
386
+
387
+ // lifecycle hooks only come through ctx.hooks.on (they are not on the ctx.on surface)
388
+ ctx.hooks.on('i18n:changed', ({ locale }) => ctx.log.info('locale switched:', locale))
389
+
390
+ ctx.ui.registerPanel({
391
+ slot: 'plugins.page', // slot name (see below)
392
+ name: 'my-panel', // unique within the plugin
393
+ titleKey: 'panel.title', // resolves plugin.my-plugin.panel.title (or use a static title)
394
+ order: 10,
395
+ mount(el) { // el = panel container (the slot renders the title)
396
+ const box = ctx.el('div', {}, ['samples:', counter])
397
+ el.append(box)
398
+ const timer = setInterval(() => { counter.textContent = String(samples) }, 5000)
399
+ return () => { clearInterval(timer); box.remove() } // cleanup: called when the panel unmounts
400
+ },
401
+ })
402
+
403
+ ctx.log.info('client injected')
404
+ }
405
+ ```
406
+
407
+ | Member | Notes |
408
+ |---|---|
409
+ | `ctx.name` / `ctx.sdkVersion` | plugin name / client SDK version |
410
+ | `ctx.hooks` | a real HookBus: `client:init` / `page:change` / `i18n:changed` / `client:destroy` |
411
+ | `ctx.on(type, fn)` | scene-event subscription (pass the event name; internally `event:<type>`); only the literal `'event:*'` is a wildcard; returns an unsubscribe function |
412
+ | `ctx.fetch(path, opt)` | same-origin platform API helper (adds the cookie token, unwraps the `{code,data}` envelope, throws on non-2xx) |
413
+ | `ctx.el(tag, attrs, children)` | DOM helper; `style` goes through `cssText`, `class` through `className`, `onXxx` through `addEventListener` |
414
+ | `ctx.mount(target, node)` / `ctx.root()` | mount to a selector/element (falling back to `ctx.root()`) / the plugin's private mount point `#aw-plugin-<name>` |
415
+ | `ctx.ui.registerPanel(entry)` / `ctx.ui.slots` | register a panel into a named slot, returns an unregister function; `slots` lists the known slot names |
416
+ | `ctx.t(key, params?)` | plugin-namespaced translation (`plugin.<name>.<key>`) |
417
+ | `ctx.locale` | current UI locale |
418
+ | `ctx.log.info / warn / error` | console with an `[aw-plugin:<name>]` prefix |
419
+ | `ctx.dispose()` | unload: emits `client:destroy` → collects subscriptions → removes `root()` (idempotent) |
420
+
421
+ ### Wildcards and dead subscriptions (the common traps)
422
+
423
+ | Spelling | Result |
424
+ |---|---|
425
+ | `ctx.on('daq.reading', fn)` | correct: subscribes to the scene event `daq.reading` |
426
+ | `ctx.on('event:*', fn)` | correct: the only wildcard spelling, receives all scene events |
427
+ | `ctx.on('*', fn)` | **dead subscription**: never fires |
428
+ | `ctx.on('event:line.start', fn)` | **dead subscription**: it only subscribes to a scene event literally named `event:line.start`, which does not exist |
429
+ | `ctx.on('daq:sample', fn)` | **dead subscription**: server hooks never cross to the browser |
430
+ | `ctx.hooks.on('page:change', fn)` | correct: lifecycle hooks go through `ctx.hooks` |
431
+ | `ctx.on('client:init', fn)` | **dead subscription**: lifecycle hooks are not on the `ctx.on` surface |
432
+
433
+ Server hooks (`daq:sample`, `daq:frame`, `dcw:write`, `line:start|stop`) **never reach the browser**; the browser only receives scene events (through the TownBus/WS bridge). Conversely, `page:change` / `i18n:changed` exist on the browser side only.
434
+
435
+ ### Panels and page slots
436
+
437
+ A host page embeds `<workshop-plugin-slot slot-name="…" />` wherever it should accept injection; `ctx.ui.slots` currently holds:
438
+
439
+ | Slot | Location |
440
+ |---|---|
441
+ | `plugins.page` | the plugin management page (the default drop point) |
442
+ | `settings.plugins` | Settings → Runtime, below the plugin management area |
443
+ | `dashboard.widgets` | the tail of the dashboard page |
444
+
445
+ `registerPanel` returns an `unregisterPanel` function; a panel's `mount(el)` may return a cleanup function (called when the panel unmounts) and may throw — a single panel's failure is isolated by the slot and never affects the other panels or the page.
446
+
447
+ ### Unload semantics
448
+
449
+ - `ctx.dispose()` is triggered by the **`pagehide`** event (navigating away / entering bfcache; switching tabs does **not** trigger it). It emits `client:destroy`, collects the plugin's own subscriptions and removes `root()`.
450
+ - `ctx.dispose()` does **not** unregister panels by itself — panel cleanup happens in the loader, which calls `unregisterPlugin(name)` on disable / hot reload.
451
+ - Two hot channels back this up: the WS `plugins.reloaded` event (instant) plus a 15 s polling diff.
452
+
453
+ ## 9. Routes and authentication
454
+
455
+ `ctx.route(method, path, handler)` registers immediately and returns a **boolean**; `routes[]` in the manifest is registered only after `setup` returns. Both end up in the same route table:
456
+
457
+ ```
458
+ GET /api/plugins/<plugin>/health ← ctx.route('GET', '/health', handler)
459
+ ← routes: [{ method: 'GET', path: '/health', handler }]
460
+ ```
461
+
462
+ - Matching is **exact**: paths are normalised with a leading `/`, and there are no wildcard segments or sub-route matching.
463
+ - The handler receives the h3 `event`; the JSON body has already been pre-read by the platform forwarding layer and attached as **`event.awBody`** (plugins do not import h3).
464
+ - The handler's return value is serialised to JSON by nitro; a throw becomes a 500 envelope (with plugin-attributed logging) and never leaks a raw stack.
465
+ - **Authentication is enforced declaratively** by the `auth` field, checked **before** your handler, for `routes[]` and `ctx.route()` alike:
466
+
467
+ | `auth` | Behaviour |
468
+ |---|---|
469
+ | `'none'` (default) | no check, the handler runs directly |
470
+ | `'user'` | requires a valid user token, otherwise 401 |
471
+ | `'admin'` | requires an administrator, otherwise 401 |
472
+ | `'agent-or-user'` | accepts an agent identity or a user token |
473
+
474
+ **Do not put authentication inside the handler**: `resolveUser(event)` is not importable from a plugin and is not on `ctx`. Declare `auth` instead.
475
+
476
+ ```js
477
+ ctx.route('GET', '/report', () => ({ plugin: ctx.name, at: new Date().toISOString() }))
478
+
479
+ ctx.route('POST', '/threshold', (event) => {
480
+ const v = Number(event.awBody?.threshold) // the host pre-read the JSON body
481
+ if (!Number.isFinite(v)) return { ok: false, error: 'threshold must be a number' }
482
+ ctx.kv.set('threshold', v)
483
+ return { ok: true, threshold: v }
484
+ })
485
+ ```
486
+
487
+ ## 10. Tools and team switches
488
+
489
+ Tools registered with `ctx.omp.registerTool(def)` appear on every harness tool surface (omp receives them over RPC `set_host_tools`, other engines through the MCP bridge `tools/list`, pi through a tool file, mock through a direct REST invoke). A registry change is **re-pushed** to running sessions — no restart, no sub-process respawn.
490
+
491
+ ```js
492
+ ctx.omp.registerTool({
493
+ name: 'sensor_log',
494
+ label: 'Sensor calibration',
495
+ description: 'Read or record sensor calibration conclusions',
496
+ parameters: { type: 'object', properties: { sensor: { type: 'string' } }, required: ['sensor'] },
497
+ roles: ['lead', 'worker'], // only these two literals; omitted = both roles
498
+ handler: async (args, agent) => ({ text: `${agent.role}@${agent.channelId}: ${args.sensor}` }),
499
+ })
500
+ ```
501
+
502
+ Never open a `url` / `host` injection hole in the parameter schema — plugin tool arguments come straight from the model, so any "let the model pick the target address" field becomes an SSRF surface.
503
+
504
+ ### Team / channel scoped switches
505
+
506
+ Beyond the global switch, every team (channel) keeps its own plugin switch set backed by the `channel_plugins` table:
507
+
508
+ - **Picked at team creation**: `POST /api/workshop/teams` accepts `plugins: [{ name, enabled }]`, stored under the team id and propagated to the channel on deploy;
509
+ - **Toggled at any time**: `GET|PUT /api/workshop/channels/:id/plugins` and `GET|PUT /api/workshop/teams/:id/plugins` (body `{ plugins: [{ name, enabled }] }`, full-replacement semantics, unknown or disabled plugin names are ignored);
510
+ - **Semantics**: no explicit rows = unconfigured → every enabled plugin is visible to that team (backward compatible); once rows exist they filter — **a disabled plugin's tools are not injected into that team's agents and dispatch rejects them at the source**;
511
+ - **UI**: the workshop teams page (creation checkboxes + team dialog).
512
+
513
+ ## 11. Complete example
514
+
515
+ ```js
516
+ // index.mjs — server entry (zero import dependencies; the host injects ctx)
517
+ export default {
518
+ name: 'my-plugin',
519
+ version: '1.0.0',
520
+ description: 'Example: over-threshold counting + own API + config groups + agent tool',
521
+ auth: 'user',
522
+ client: './client.mjs',
523
+ configGroups: [
524
+ { id: 'default', label: 'My plugin', description: 'Basic configuration', order: 500 },
525
+ { id: 'advanced', label: 'Advanced', collapsed: true },
526
+ ],
527
+ settings: [
528
+ { key: 'threshold', type: 'number', default: 180, min: 0, max: 5000, group: 'default',
529
+ label: 'Alarm threshold', description: 'applies live on save' },
530
+ { key: 'endpoint', type: 'string', default: 'http://127.0.0.1:8080', group: 'advanced',
531
+ label: 'External service URL' },
532
+ ],
533
+ routes: [
534
+ { method: 'GET', path: '/health', handler: () => ({ ok: true }) },
535
+ ],
536
+
537
+ async setup(ctx) {
538
+ ctx.logger.info(`loaded (scope=${ctx.scope}, sdk=${ctx.sdkVersion})`)
539
+
540
+ const threshold = () => Number(ctx.config.get('plugins.my-plugin.threshold')) || 180
541
+
542
+ // 1) publish-level DAQ samples: count over-threshold hits in plugin-private KV
543
+ ctx.hooks.on('daq:sample', (s) => {
544
+ if (typeof s?.value === 'number' && s.value > threshold())
545
+ ctx.kv.bump(`alarm:${s.nodeId}`)
546
+ })
547
+
548
+ // 2) scene events (ctx.events adds the event: prefix for you)
549
+ ctx.events.on('daq.alarm', (a) => ctx.logger.warn('alarm', a?.nodeId))
550
+ ctx.events.on('permissions.changed', (p) => ctx.logger.info('grants changed', p?.userId))
551
+
552
+ // 3) full event audit: the server wildcard is the bare '*', callback gets { type, payload }
553
+ ctx.hooks.on('*', ({ type }) => ctx.logger.debug('event', type))
554
+
555
+ // 4) routes[] are registered after setup; ctx.route registers now (returns a boolean)
556
+ ctx.route('POST', '/threshold', (event) => {
557
+ const v = Number(event.awBody?.threshold)
558
+ if (!Number.isFinite(v)) return { ok: false, error: 'threshold must be a number' }
559
+ ctx.kv.set('threshold', v)
560
+ return { ok: true, threshold: v }
561
+ })
562
+
563
+ // 5) timers + cleanup: one host-owned dispose queue, run on shutdown and hot reload
564
+ ctx.timer.setInterval(() => ctx.kv.set('heartbeat', new Date().toISOString()), 5000)
565
+ ctx.onDispose(() => ctx.logger.info('plugin cleaned up'))
566
+
567
+ // 6) agent tool (roles accepts 'lead' | 'worker' only)
568
+ ctx.omp.registerTool({
569
+ name: 'my_alarm_report',
570
+ description: 'Read the over-threshold alarm counter recorded by this plugin',
571
+ parameters: { type: 'object', properties: { nodeId: { type: 'string' } }, required: ['nodeId'] },
572
+ roles: ['lead', 'worker'],
573
+ handler: async (args, agent) => ({
574
+ text: `${args.nodeId} alarmed ${ctx.kv.get(`alarm:${args.nodeId}`) ?? 0} times (by ${agent.role})`,
575
+ }),
576
+ })
577
+ },
578
+ }
579
+ ```
580
+
581
+ `client.mjs` is in §8 and `i18n.json` in §5. Which scope to install into and how to toggle are in §1.
582
+
583
+ ## 12. Troubleshooting
584
+
585
+ | Symptom | Cause / fix |
586
+ |---|---|
587
+ | Plugin code edits have no effect | the host has no plugin-directory watcher: touch `<home>/plugins-state.json`, or toggle `aw plugin enable/disable` once, to trigger a reload (the reload imports with `?t=` to defeat the cache); core `server/` and `shared/` changes still need a process restart |
588
+ | Recreated a same-named plugin but routes 404 | a name previously disabled keeps its flag in `<home>/plugins-state.json`; recreating the directory does not reload it — run `aw plugin enable <name>` explicitly after recreating (verified in practice) |
589
+ | Load failures are invisible | `aw plugin list` does **not** show them. Use `GET /api/workshop/plugins` (returns `{ plugins, failures, initedAt }`, **no `{code,data}` envelope**) or read `[aw-plugins] 装载失败 …` in the startup log |
590
+ | Plugin route 404 | matching is exact; check the method and the leading `/` of the path, and whether the plugin is enabled (a disabled plugin's routes are dropped from the table) |
591
+ | Plugin route 401 | the entry declared `auth: 'user' \| 'admin' \| 'agent-or-user'` and the request carried no valid credential; use `auth: 'none'` only for open endpoints |
592
+ | Client panel never appears | check the browser console for `[aw-plugins]` warnings; confirm the manifest says `hasClient: true` (i.e. the manifest declares `client:'./client.mjs'`) and the plugin is `enabled !== false` |
593
+ | A client subscription never fires | check you did not fall into the §8 traps (`ctx.on('*')`, `ctx.on('event:line.start')`, `ctx.on('daq:sample')`, or handing a lifecycle hook to `ctx.on`) |
594
+ | A setting does not render | a declaration without `default` is skipped with a warning; a `select`'s `default` must be inside `options`; `key` allows letters, digits, `-` and `_` only |
595
+ | A changed setting has no effect | read it as `ctx.config.get('plugins.<plugin>.<key>')` (the short key reads nothing) |
596
+ | The `permissions:changed` hook never fires | that name does not exist; use `ctx.events.on('permissions.changed', fn)` |
597
+ | KV data "disappeared" | KV is at `<configRoot>/data/plugins/<plugin>/kv.json`, not in the plugin folder; writes flush after a 200 ms debounce, so a hard kill has a tiny loss window |
598
+ | `ctx.api` returns 401 | authenticated endpoints need `ctx.api.setToken(token)`; unauthenticated ones (manifest / ping) do not |
599
+
600
+ **Trust model**: a plugin is arbitrary node code, on par with aw commands — only install and enable plugins you trust.
601
+
602
+ ### Known boundaries (intentional, not defects)
603
+
604
+ - **Hooks are observational**: v1 has no veto / interception / rewriting — write-control interlock integrity comes first.
605
+ - **The `ctx.http` guard is protocol-only** (http/https) and does **not** restrict hosts; do your own allow-listing inside the plugin (the built-in `diag-bridge` / `rag-bridge` do exactly that).
606
+ - **`ctx.t(key, params)` currently ignores `params`** (no interpolation); build strings yourself inside the plugin.
607
+ - **`ctx.kv` has no `reset()`** — only `get / set / all / bump`.
608
+ - **Server and browser subscription surfaces do not overlap**: server hooks never cross the bridge, and browser events never reach the server.
609
+ - **A newly enabled browser plugin injects within 15 s** (instantly when the WS channel is up).
610
+ - **`server:close` / `onDispose` depend on graceful shutdown signals**; a forced kill on Windows does not trigger them.
611
+ - **`aw plugin list` only scans directories** — it reflects neither load failures nor runtime health.
612
+ - **Protocol plugins share the host process with their native stacks**: serial-bridge's RTU framing is a self-contained implementation (single serialport native stack; modbus-serial is deliberately NOT used — its wrapper reproducibly crashed under dev mode). Known boundary: **inside the `aw dev` process, a serial transaction that ends in write → timeout → close can trigger a native V8 crash in `bindings-cpp`** (HandleScope fatal — not catchable in JS, regardless of which port is used); **production mode (`aw start`) is unaffected — the exact same sequence was verified live to return an honest timeout with the server healthy**, and open-only probes are also fine under dev. Connect real hardware during development with this in mind; for hardened deployments, run the serial gateway in its own process (see the plc-node-simulator child-process pattern).
613
+
614
+ ## 13. Pre-release checklist
615
+
616
+ 1. `aw plugin list` shows the plugin in all applicable scopes; a client plugin shows `+client`; toggling enable/disable makes routes, tools and panels appear and disappear together (a rapid toggle burst must not lose events).
617
+ 2. `GET /api/workshop/plugins` reports an empty `failures`; `GET /api/plugins/manifest` reports `settingsCount` / `hasClient` / `hasI18n` matching the declaration.
618
+ 3. The settings page shows the plugin's config group (fields without a declared group land in the `plugin-<plugin>` group); after a save, `ctx.config.get('plugins.<plugin>.<key>')` returns the new value immediately.
619
+ 4. Locale switching updates panel titles and copy; the `i18n:changed` cleanup leaves no leaks.
620
+ 5. The tool is visible through `GET /api/workshop/agent-tools/list`; its parameters expose no `url` / `host` injection hole; after the team switch is turned off the tool disappears and dispatch is rejected.
621
+ 6. Trigger one hot reload (toggle enable/disable) and confirm `plugins:reloaded` reaches the plugin with no KV or timer leak.