agentworkshop 0.7.40 → 0.7.44

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 (327) hide show
  1. package/.output/nitro.json +1 -1
  2. package/.output/public/_nuxt/B8uy9iRW.js +1 -0
  3. package/.output/public/_nuxt/BGTYYQH-.js +1 -0
  4. package/.output/public/_nuxt/BUmHEJ77.js +2 -0
  5. package/.output/public/_nuxt/BY7YrV_D.js +1 -0
  6. package/.output/public/_nuxt/Bqcgz95s.js +18 -0
  7. package/.output/public/_nuxt/C7UlqPeD.js +1 -0
  8. package/.output/public/_nuxt/CTmDn-YP.js +1 -0
  9. package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
  10. package/.output/public/_nuxt/{DcsCcxp4.js → D98NH8kG.js} +1 -1
  11. package/.output/public/_nuxt/DKySmNPI.js +14 -0
  12. package/.output/public/_nuxt/Di0NY_ht.js +1 -0
  13. package/.output/public/_nuxt/Du9v6qGB.js +1 -0
  14. package/.output/public/_nuxt/{C_iYD-EB.js → EHlEMRii.js} +1 -1
  15. package/.output/public/_nuxt/Eke1fs-U.js +1 -0
  16. package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
  17. package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
  18. package/.output/public/_nuxt/{DZshyNLa.js → IREZ1eC2.js} +1 -1
  19. package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
  20. package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
  21. package/.output/public/_nuxt/SSyAPaHc.js +1 -0
  22. package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
  23. package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
  24. package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
  25. package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
  26. package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
  27. package/.output/public/_nuxt/aShsv_rG.js +19 -0
  28. package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
  29. package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
  30. package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
  31. package/.output/public/_nuxt/builds/latest.json +1 -1
  32. package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
  33. package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
  34. package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
  35. package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
  36. package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
  37. package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
  38. package/.output/public/_nuxt/lHAxdSsj.js +1 -0
  39. package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
  40. package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
  41. package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
  42. package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
  43. package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
  44. package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
  45. package/.output/server/chunks/_/nitro.mjs +1058 -830
  46. package/.output/server/chunks/_/nitro.mjs.map +1 -1
  47. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
  48. package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
  49. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
  50. package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
  51. package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
  52. package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
  53. package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
  54. package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
  55. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
  56. package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
  57. package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
  58. package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
  59. package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
  60. package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
  61. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
  62. package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
  63. package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
  64. package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
  65. package/.output/server/chunks/build/{daq-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
  66. package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
  67. package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
  68. package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
  69. package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
  70. package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
  71. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
  72. package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.mjs.map} +1 -1
  73. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
  74. package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
  75. package/.output/server/chunks/build/{pages-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
  76. package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
  77. package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
  78. package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
  79. package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
  80. package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
  81. package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
  82. package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
  83. package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
  84. package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
  85. package/.output/server/chunks/build/{town-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
  86. package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
  87. package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
  88. package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
  89. package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
  90. package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
  91. package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
  92. package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
  93. package/.output/server/chunks/routes/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
  94. package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
  95. package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
  96. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
  97. package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
  98. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
  99. package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
  100. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
  101. package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
  102. package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
  103. package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
  104. package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
  105. package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
  106. package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
  107. package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
  108. package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
  109. package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
  110. package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
  111. package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
  112. package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
  113. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
  114. package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
  115. package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
  116. package/.output/server/chunks/routes/renderer.mjs +2 -2
  117. package/.output/server/chunks/virtual/entry.mjs +639 -88
  118. package/.output/server/chunks/virtual/entry.mjs.map +1 -1
  119. package/.output/server/chunks/virtual/precomputed.mjs +1 -1
  120. package/.output/server/package.json +1 -1
  121. package/README-zh.md +16 -6
  122. package/README.md +17 -7
  123. package/app/components/AppHeader.vue +961 -959
  124. package/app/components/workshop/A2aRpcDebugger.vue +237 -237
  125. package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
  126. package/app/components/workshop/AgentLanesView.vue +998 -998
  127. package/app/components/workshop/ArtifactCard.vue +120 -120
  128. package/app/components/workshop/AssetLibrary.vue +452 -452
  129. package/app/components/workshop/ChannelSessionList.vue +812 -804
  130. package/app/components/workshop/CommandPalette.vue +269 -269
  131. package/app/components/workshop/Composer.vue +827 -827
  132. package/app/components/workshop/DeviceTwinPanel.vue +643 -643
  133. package/app/components/workshop/EventBlock.vue +602 -602
  134. package/app/components/workshop/FileSelectorModal.vue +188 -188
  135. package/app/components/workshop/InspectorPanel.vue +301 -301
  136. package/app/components/workshop/MemoryPanel.vue +364 -364
  137. package/app/components/workshop/MultiChannelView.vue +189 -189
  138. package/app/components/workshop/TaskBoardView.vue +686 -686
  139. package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
  140. package/app/components/workshop/TranscriptTimeline.vue +601 -601
  141. package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
  142. package/app/components/workshop/blocks/ClusterError.vue +85 -85
  143. package/app/components/workshop/blocks/ClusterLife.vue +87 -87
  144. package/app/components/workshop/blocks/ClusterMember.vue +95 -93
  145. package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
  146. package/app/components/workshop/blocks/ClusterOther.vue +67 -67
  147. package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
  148. package/app/components/workshop/blocks/ClusterTool.vue +158 -158
  149. package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
  150. package/app/components/workshop/town/TownView.vue +56 -56
  151. package/app/composables/workshop/useDaqStream.ts +7 -4
  152. package/app/composables/workshop/useDcwStream.ts +8 -2
  153. package/app/middleware/locale-cookie.global.ts +22 -0
  154. package/app/pages/aml/index.vue +24 -2
  155. package/app/pages/daq/[id].vue +20 -16
  156. package/app/pages/daq/index.vue +2310 -2307
  157. package/app/pages/dcw/[id].vue +26 -23
  158. package/app/pages/dcw/index.vue +3 -3
  159. package/app/pages/index.vue +987 -981
  160. package/app/pages/plugins/index.vue +317 -311
  161. package/app/pages/settings.vue +1564 -1562
  162. package/app/pages/tokens.vue +15 -15
  163. package/app/pages/workshop/agents.vue +612 -609
  164. package/app/pages/workshop/channel-templates.vue +18 -11
  165. package/app/pages/workshop/index.vue +668 -668
  166. package/app/pages/workshop/teams.vue +743 -740
  167. package/app/pages/workshop/w/[wsId].vue +704 -704
  168. package/app/plugins/locale-restore.client.ts +16 -1
  169. package/app/utils/builtin-catalog.ts +104 -0
  170. package/config.yml +170 -170
  171. package/docs/cli.en.md +2 -2
  172. package/docs/cli.md +2 -2
  173. package/docs/full-test-plan.md +2 -3
  174. package/docs/plugins.en.md +33 -1
  175. package/docs/plugins.md +29 -1
  176. package/docs/sdk.en.md +1 -1
  177. package/docs/sdk.md +1 -1
  178. package/i18n/locales/en.ts +293 -35
  179. package/i18n/locales/zh-CN.ts +265 -7
  180. package/package.json +4 -3
  181. package/scripts/_dbg-perms-e2e.mjs +132 -109
  182. package/scripts/_demo-agentteam-capture.mjs +278 -0
  183. package/scripts/_e2e-ui-functional.mjs +371 -0
  184. package/scripts/_i18n-en-cjk-scan.mjs +28 -0
  185. package/scripts/_i18n-en-verify.mjs +107 -0
  186. package/scripts/_i18n-extra-checks.mjs +53 -0
  187. package/scripts/_i18n-scan-hardcoded.mjs +180 -0
  188. package/scripts/_i18n-verify-keys.mjs +50 -0
  189. package/scripts/_loop-biax-campaign.mjs +130 -0
  190. package/scripts/_sweep-button-contrast.mjs +87 -0
  191. package/scripts/test-serial-bridge.mjs +418 -0
  192. package/scripts/ui/e2e-interactive.mjs +328 -0
  193. package/scripts/ui/record-demo.mjs +557 -0
  194. package/scripts/ui/seed-industrial-demo.mjs +171 -0
  195. package/server/api/workshop/daq/index.get.ts +3 -1
  196. package/server/api/workshop/dcw/index.get.ts +3 -0
  197. package/server/plugins/aw-plugins.ts +9 -7
  198. package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
  199. package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
  200. package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
  201. package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
  202. package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
  203. package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
  204. package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
  205. package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
  206. package/server/services/workshop/daq/daq-controller.ts +7 -2
  207. package/server/services/workshop/daq/drivers.ts +89 -3
  208. package/server/services/workshop/dcw/dcw-controller.ts +5 -1
  209. package/server/services/workshop/dcw/drivers.ts +85 -2
  210. package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
  211. package/server/services/workshop/plugins/host.mjs +30 -0
  212. package/shared/daq-protocol.ts +15 -0
  213. package/.output/public/_nuxt/88GOowgO2.js +0 -1
  214. package/.output/public/_nuxt/BOt2SP4z.js +0 -1
  215. package/.output/public/_nuxt/BP-NyKVG.js +0 -18
  216. package/.output/public/_nuxt/BXxH0UQE.js +0 -1
  217. package/.output/public/_nuxt/BsROmY89.js +0 -1
  218. package/.output/public/_nuxt/ClILjvWH.js +0 -1
  219. package/.output/public/_nuxt/CzOCSGLe.js +0 -1
  220. package/.output/public/_nuxt/D8qvkfyY.js +0 -1
  221. package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
  222. package/.output/public/_nuxt/DS8qj8EL.js +0 -1
  223. package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
  224. package/.output/public/_nuxt/Dihnimlk.js +0 -19
  225. package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
  226. package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
  227. package/.output/public/_nuxt/Du9ZJtnj.js +0 -1
  228. package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
  229. package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
  230. package/.output/public/_nuxt/_id_.CM4bIgKY.css +0 -1
  231. package/.output/public/_nuxt/_id_.bNV69Dtf.css +0 -1
  232. package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
  233. package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
  234. package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
  235. package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
  236. package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
  237. package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
  238. package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
  239. package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
  240. package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
  241. package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
  242. package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
  243. package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
  244. package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
  245. package/.output/server/chunks/build/_id_-BU1wfOID.mjs.map +0 -1
  246. package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +0 -1
  247. package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
  248. package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
  249. package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
  250. package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +0 -1
  251. package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
  252. package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +0 -1
  253. package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
  254. package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
  255. package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
  256. package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
  257. package/.output/server/chunks/build/town-qMPql9Se.mjs.map +0 -1
  258. package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
  259. package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs.map +0 -1
  260. package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
  261. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
  262. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
  263. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
  264. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
  265. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
  266. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
  267. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
  268. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
  269. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
  270. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
  271. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
  272. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
  273. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
  274. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
  275. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
  276. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
  277. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
  278. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
  279. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
  280. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
  281. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
  282. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
  283. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
  284. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
  285. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
  286. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
  287. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
  288. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
  289. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
  290. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
  291. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
  292. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
  293. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
  294. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
  295. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
  296. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
  297. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
  298. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
  299. package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
  300. package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
  301. package/scripts/_dbg-audit-all-pages.mjs +0 -43
  302. package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
  303. package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
  304. package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
  305. package/scripts/_dbg-live-line-e2e.mjs +0 -279
  306. package/scripts/_dbg-minimal-omp.mjs +0 -77
  307. package/scripts/_dbg-mode-nav.mjs +0 -173
  308. package/scripts/_dbg-real-scenario-timeline.log +0 -55
  309. package/scripts/_dbg-team-repro.mjs +0 -76
  310. package/scripts/_dbg-town-agents-probe.mjs +0 -60
  311. package/scripts/_dbg-visual-acceptance.mjs +0 -63
  312. package/scripts/_townview_style.css +0 -1185
  313. package/scripts/_townview_template.html +0 -1215
  314. package/server/data/agent-node-bindings.json +0 -554
  315. package/server/data/daq-templates.json +0 -16
  316. package/server/data/daqs.json +0 -7464
  317. package/server/data/dcw-lines.json +0 -1787
  318. package/server/data/dcw-products.json +0 -1626
  319. package/server/data/dcw-recipes.json +0 -4672
  320. package/server/data/dcw-rollback.json +0 -20754
  321. package/server/data/dcw-runs.json +0 -6354
  322. package/server/data/dcw-templates.json +0 -146
  323. package/server/data/dcw-writes.json +0 -15650
  324. package/server/data/dcws.json +0 -9033
  325. package/server/data/device-twins.json +0 -133
  326. package/server/data/line-runs.json +0 -322
  327. package/server/data/scene-layouts.json +0 -277
@@ -1,112 +0,0 @@
1
- /**
2
- * HITL 全局待办层单元测试(tsx 直跑):
3
- * npx tsx scripts/test-hitl-registry.ts
4
- *
5
- * 覆盖:
6
- * - hitl-registry:登记/快照/幂等/落定/per-channel 订阅/parkDeadline/resolver 补全
7
- * - harness-terminal park 语义:零订阅 park(不再秒取消)/订阅暂停/退订重新计时/
8
- * 应答落定 answered/TTL 到期落定 expired 并写 cancelled 到 omp stdin
9
- *
10
- * TTL 经 AW_SECURITY_HITL_TIMEOUT_MS=250 注入(须在首次 settings 读取前设置)。
11
- */
12
- import type { OmpRpcClient } from '../server/services/workshop/agents/adapters/omp-rpc-client'
13
- import { securityHitlTimeoutMs } from '../server/services/workshop/settings'
14
- import { getHitlRegistry } from '../server/services/workshop/agents/hitl-registry'
15
- import {
16
- attachTerminalTap,
17
- respondTerminalUi,
18
- subscribeTerminal,
19
- } from '../server/services/workshop/agents/harness-terminal'
20
-
21
- process.env.AW_SECURITY_HITL_TIMEOUT_MS = '250'
22
-
23
- let failures = 0
24
- const check = (name: string, cond: boolean, extra = '') => {
25
- console.log(`${cond ? ' ✓' : ' ✗ FAIL'} ${name}${extra ? ` (${extra})` : ''}`)
26
- if (!cond) failures++
27
- }
28
- const sleep = (ms: number) => new Promise(r => setTimeout(r, ms))
29
-
30
- console.log('[hitl] park TTL =', securityHitlTimeoutMs(), 'ms')
31
- const reg = getHitlRegistry()
32
-
33
- // ===== 1. registry 基础 =====
34
- console.log('[1] hitl-registry 基础语义')
35
- const events: Array<{ type: string, channelId?: string }> = []
36
- const unsub = reg.subscribe('ch-1', (e) => {
37
- events.push({ type: e.type, channelId: (e.payload as { channelId?: string }).channelId })
38
- })
39
- reg.register({ kind: 'omp-dialog', id: 'd1', agentId: 'a1', channelId: 'ch-1', agentName: 'lead', method: 'confirm', title: '确认下发?' })
40
- check('登记后 snapshot 可见', reg.snapshot().length === 1 && reg.snapshot()[0]!.id === 'd1')
41
- check('channel 订阅者收到 hitl.request', events.length === 1 && events[0]!.type === 'hitl.request')
42
- reg.register({ kind: 'omp-dialog', id: 'd1', agentId: 'a1', channelId: 'ch-1', title: '重复登记' })
43
- check('同 kind+id 幂等(不重复)', reg.snapshot().length === 1)
44
- reg.register({ kind: 'dcw-approval', id: 'ap-x', agentId: 'a2', channelId: 'ch-2', title: 'DCW 下发审批' })
45
- check('channelId 过滤生效', reg.snapshot('ch-2').length === 1 && reg.snapshot('ch-1').length === 1)
46
- check('find 取回单条', reg.find('dcw-approval', 'ap-x')?.title === 'DCW 下发审批')
47
- reg.setParkDeadline('omp-dialog', 'd1', '2026-01-01T00:00:00.000Z')
48
- check('setParkDeadline 更新 expiresAt', reg.find('omp-dialog', 'd1')?.expiresAt === '2026-01-01T00:00:00.000Z')
49
- check('resolve 落定并广播 hitl.resolved', reg.resolve('omp-dialog', 'd1', 'answered', 'user-1') && events[1]!.type === 'hitl.resolved')
50
- check('落定后 snapshot 清空该条', reg.snapshot('ch-1').length === 0 && reg.find('omp-dialog', 'd1') === null)
51
- check('重复 resolve 幂等返回 false', reg.resolve('omp-dialog', 'd1', 'answered') === false)
52
- unsub()
53
-
54
- // ===== 2. resolver 补全 =====
55
- console.log('[2] resolver 补全 channelId/agentName')
56
- reg.configureResolver(agentId => agentId === 'inst-9' ? { channelId: 'ch-9', agentName: '温控工' } : null)
57
- reg.register({ kind: 'dcw-approval', id: 'ap-r', agentId: 'inst-9', title: 'DCW 下发审批' })
58
- check('resolver 补全 channelId', reg.find('dcw-approval', 'ap-r')?.channelId === 'ch-9')
59
- check('resolver 补全 agentName', reg.find('dcw-approval', 'ap-r')?.agentName === '温控工')
60
- reg.resolve('dcw-approval', 'ap-r', 'cancelled')
61
-
62
- // ===== 3. harness-terminal park 语义(假 omp 客户端) =====
63
- console.log('[3] harness-terminal park 语义')
64
- const written: Array<Record<string, unknown>> = []
65
- const fakeClient = {
66
- onRawFrame(fn: (f: Record<string, unknown>) => void) {
67
- ;(fakeClient as unknown as { __emit: (f: Record<string, unknown>) => void }).__emit = fn
68
- return () => {}
69
- },
70
- async send() {},
71
- writeRaw(frame: Record<string, unknown>) { written.push(frame) },
72
- } as unknown as OmpRpcClient
73
-
74
- attachTerminalTap(fakeClient, { pid: 4242, harness: 'omp', agentId: 'agt-1', channelId: 'ch-1', name: '调度长', role: 'lead' })
75
- const emit = (f: Record<string, unknown>) => (fakeClient as unknown as { __emit: (f: Record<string, unknown>) => void }).__emit(f)
76
-
77
- emit({ type: 'extension_ui_request', id: 'dlg-1', method: 'confirm', title: '允许写入设定值?' })
78
- await sleep(20)
79
- check('零订阅到达 → park 而非秒取消(未写 cancelled 到 stdin)', !written.some(w => w.type === 'extension_ui_response'))
80
- const parked = reg.find('omp-dialog', 'dlg-1')
81
- check('登记进全局待办且带 park 截止', parked !== null && typeof parked?.expiresAt === 'string')
82
-
83
- const unsubTerm = subscribeTerminal(4242, () => {})
84
- await sleep(10)
85
- check('订阅者接入 → 计时暂停(expiresAt=null)', reg.find('omp-dialog', 'dlg-1')?.expiresAt === null)
86
-
87
- unsubTerm()
88
- await sleep(10)
89
- check('订阅者全部离开 → 重新计时(expiresAt 恢复未来时刻)', typeof reg.find('omp-dialog', 'dlg-1')?.expiresAt === 'string')
90
-
91
- respondTerminalUi(4242, { id: 'dlg-1', confirmed: true })
92
- await sleep(10)
93
- check('应答写入 omp stdin(extension_ui_response confirmed)', written.some(w => w.type === 'extension_ui_response' && w.id === 'dlg-1' && w.confirmed === true))
94
- check('应答后全局待办落定', reg.find('omp-dialog', 'dlg-1') === null)
95
-
96
- // TTL 到期 → expired
97
- emit({ type: 'extension_ui_request', id: 'dlg-2', method: 'select', title: '选择方案', options: ['A', 'B'] })
98
- await sleep(20)
99
- check('第二个对话框已 park', reg.find('omp-dialog', 'dlg-2') !== null)
100
- await sleep(400)
101
- check('park 超时 → 落定 expired 且全局待办清空', reg.find('omp-dialog', 'dlg-2') === null)
102
- check('超时向 omp stdin 写 cancelled', written.some(w => w.type === 'extension_ui_response' && w.id === 'dlg-2' && w.cancelled === true))
103
-
104
- // omp 主动撤销(method=cancel)→ 落定 cancelled
105
- emit({ type: 'extension_ui_request', id: 'dlg-3', method: 'input', title: '补充说明' })
106
- await sleep(20)
107
- emit({ type: 'extension_ui_request', method: 'cancel', targetId: 'dlg-3' })
108
- await sleep(20)
109
- check('omp 撤销对话框 → 待办落定清空', reg.find('omp-dialog', 'dlg-3') === null)
110
-
111
- console.log(failures === 0 ? '\n[hitl] 全部通过' : `\n[hitl] ${failures} 项失败`)
112
- process.exit(failures === 0 ? 0 : 1)
@@ -1,227 +0,0 @@
1
- /**
2
- * 双驱动端到端测试 — MCP(Streamable HTTP)与 REST API 同流程驱动真实 Manager。
3
- *
4
- * 覆盖:
5
- * 1. MCP 驱动: 真实 WebStandardStreamableHTTPServerTransport + MCP SDK Client 连接
6
- * → initialize → tools/list(16 工具)→ channel.create → agent.create(lead/worker, 取 token)
7
- * → task.submit → task.list → a2a.send(带 token) → task.dispatch/report/complete
8
- * → 无 token → UNAUTHORIZED
9
- * 2. REST 驱动: 同一 Manager,经 h3 app 调用 REST 端点
10
- * → 创建 channel/agent → 发任务 → Bearer token 调 mailbox/subscribe/report/complete/dispatch/tasks
11
- * → 无 token → 401;跨 channel → SCOPE_VIOLATION
12
- *
13
- * 测试环境: :memory: db + 真实 Manager + mock impl(delayMs=0),h3 toWebHandler 供 MCP client fetch 注入。
14
- */
15
- import { randomUUID } from 'node:crypto'
16
- import { toWebHandler, createApp, eventHandler } from 'h3'
17
- import { Client } from '@modelcontextprotocol/sdk/client/index.js'
18
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
19
- import { openWorkshopDb } from '../server/services/workshop/db/database'
20
- import { createChannelRepo } from '../server/services/workshop/db/channel.repo'
21
- import { createAgentRepo } from '../server/services/workshop/db/agent.repo'
22
- import { createChannelAgentRepo } from '../server/services/workshop/db/channel-agent.repo'
23
- import { createTaskRepo } from '../server/services/workshop/db/task.repo'
24
- import { createMemoryRepo } from '../server/services/workshop/db/memory.repo'
25
- import { createChannelEventRepo } from '../server/services/workshop/db/channel-event.repo'
26
- import { createTeamRepo } from '../server/services/workshop/db/team.repo'
27
- import { createTeamMemberRepo } from '../server/services/workshop/db/team-member.repo'
28
- import { createMessageRepo } from '../server/services/workshop/db/message.repo'
29
- import { createSubscriptionRepo } from '../server/services/workshop/db/subscription.repo'
30
- import { createAgentChannelManager } from '../server/services/workshop/runtime/manager'
31
- import { createAgentImpl } from '../server/services/workshop/agents/factory'
32
- import { SchedulerLoop } from '../server/services/workshop/runtime/scheduler-loop'
33
- import type { AgentChannelManager } from '../server/services/workshop/runtime/manager'
34
- import type { ChannelRuntime } from '../server/services/workshop/runtime/channel-runtime'
35
-
36
- let failures = 0
37
- function check(name: string, ok: boolean, detail = ''): void {
38
- console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
39
- if (!ok) failures += 1
40
- }
41
-
42
- const sleep = (ms: number) => new Promise<void>(r => setTimeout(r, ms))
43
-
44
- async function waitUntil(cond: () => boolean | Promise<boolean>, timeoutMs: number): Promise<boolean> {
45
- const deadline = Date.now() + timeoutMs
46
- while (Date.now() < deadline) {
47
- if (await cond()) return true
48
- await sleep(50)
49
- }
50
- return false
51
- }
52
-
53
- /** 白盒取 manager 内部 ChannelRuntime(测试装配 SchedulerLoop 用) */
54
- function channelRuntimeOf(manager: AgentChannelManager, channelId: string): ChannelRuntime {
55
- const internals = manager as unknown as { channels: Map<string, ChannelRuntime> }
56
- const cr = internals.channels.get(channelId)
57
- if (!cr) throw new Error(`channel runtime 不存在: ${channelId}`)
58
- return cr
59
- }
60
-
61
- function attachScheduler(manager: AgentChannelManager, channelId: string, tickMs: number): SchedulerLoop {
62
- // 懒加载时代:先激活 channel(装配 lead 运行时与默认循环),再换成测试配置的循环
63
- manager.ensureChannelActive(channelId)
64
- const cr = channelRuntimeOf(manager, channelId)
65
- cr.scheduler?.stop()
66
- const lead = cr.getAgents().find(a => a.role === 'lead')
67
- if (!lead) throw new Error('无 lead')
68
- const internals = manager as unknown as { agentIndex: Map<string, unknown> }
69
- const runtime = internals.agentIndex.get(lead.agentId) as { role: string } & { abortCurrent(): void }
70
- void runtime
71
- const loop = new SchedulerLoop(cr, lead as never, { tickMs })
72
- cr.scheduler = loop
73
- loop.start()
74
- return loop
75
- }
76
-
77
- interface Harness {
78
- db: ReturnType<typeof openWorkshopDb>
79
- manager: AgentChannelManager
80
- loops: SchedulerLoop[]
81
- channels: string[]
82
- fetchLike: typeof fetch
83
- }
84
-
85
- function setup(): Harness {
86
- const db = openWorkshopDb(':memory:')
87
- const repos = {
88
- channels: createChannelRepo(db),
89
- agents: createAgentRepo(db),
90
- channelAgents: createChannelAgentRepo(db),
91
- messages: createMessageRepo(db),
92
- subscriptions: createSubscriptionRepo(db),
93
- tasks: createTaskRepo(db),
94
- memories: createMemoryRepo(db),
95
-
96
- channelEvents: createChannelEventRepo(db),
97
-
98
- teams: createTeamRepo(db),
99
-
100
- teamMembers: createTeamMemberRepo(db),
101
- }
102
- const manager = createAgentChannelManager({ repos, implFactory: createAgentImpl, db })
103
-
104
- // h3 app 挂 REST 入口(与生产路由同 handler;MCP 端点单独用原生 fetch 包)
105
- const app = createApp()
106
- app.use(
107
- '/api/workshop/channels',
108
- eventHandler(async (_event) => {
109
- // 简化:直接走 manager(端点行为在 test-rest 段显式断言)
110
- return { code: 0, data: await manager.listChannels() }
111
- }),
112
- )
113
- const webHandle = toWebHandler(app)
114
- const fetchLike: typeof fetch = (input, init) => webHandle(input as Request, init)
115
- return { db, manager, loops: [], channels: [], fetchLike }
116
- }
117
-
118
- async function main(): Promise<void> {
119
- const h = setup()
120
- console.log('\n=== 1. MCP Streamable HTTP 驱动(真实 transport + SDK Client) ===')
121
-
122
- // 手动构造 MCP 端点 handler 的 web 包装(与 server/api/mcp/workshop.ts 同逻辑)
123
- const sessions = new Map<string, { handle(request: Request): Promise<Response> }>()
124
- const mcpFetch: typeof fetch = async (input, init) => {
125
- const request = new Request(input, init)
126
- const url = new URL(request.url)
127
- const sessionId = url.searchParams.get('sessionId') ?? request.headers.get('mcp-session-id')
128
- if (request.method === 'DELETE' && sessionId) {
129
- sessions.delete(sessionId)
130
- return new Response(null, { status: 204 })
131
- }
132
- let entry = sessionId ? sessions.get(sessionId) : undefined
133
- if (!entry) {
134
- const { WebStandardStreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js')
135
- const { createWorkshopMcpServer } = await import('../server/mcp/workshop-server')
136
- const newSessionId = randomUUID()
137
- const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: () => newSessionId })
138
- const server = createWorkshopMcpServer(h.manager)
139
- await server.connect(transport)
140
- entry = { handle: req => transport.handleRequest(req) }
141
- sessions.set(newSessionId, entry)
142
- }
143
- const res = await entry.handle(request)
144
- console.log(`[mcp] ${request.method} in-session=${sessionId ?? '-'} -> ${res.status} out-session=${res.headers.get('mcp-session-id') ?? '-'}`)
145
- return res
146
- }
147
-
148
- const client = new Client({ name: 'e2e-test', version: '1.0.0' })
149
- await client.connect(new StreamableHTTPClientTransport(new URL('http://localhost/api/mcp/workshop'), { fetch: mcpFetch }))
150
-
151
- const tools = await client.listTools()
152
- check('tools/list 返回 25 工具(16 基础 + 4 扩展 + 5 设备孪生)', tools.tools.length === 25, `got=${tools.tools.length}`)
153
-
154
- // channel.create + agent.create(lead/worker)→ 拿 token
155
- const ch = await client.callTool({ name: 'workshop.channel.create', arguments: { name: 'mcp-channel', leadAgent: { name: 'lead', harness: 'mock', config: { delayMs: 0 } } } })
156
- const channelId = JSON.parse((ch.content as { text: string }[])[0].text).channelId as string
157
- h.channels.push(channelId)
158
- check('MCP channel.create 返回 channelId', typeof channelId === 'string' && channelId.length > 0)
159
-
160
- const w1Tpl = await client.callTool({ name: 'workshop.agent.create', arguments: { name: 'w1', harness: 'mock', config: { delayMs: 0 } } })
161
- const tpl = JSON.parse((w1Tpl.content as { text: string }[])[0].text)
162
- check('MCP agent.create 返回模板', typeof tpl.id === 'string' && tpl.id.length > 0)
163
- const w1 = await client.callTool({ name: 'workshop.agent.add', arguments: { channelId, agentId: tpl.id, role: 'worker' } })
164
- const worker1 = JSON.parse((w1.content as { text: string }[])[0].text)
165
- check('MCP agent.add 返回实例 token', typeof worker1.token === 'string' && worker1.token.length > 0)
166
-
167
- const lead = (await h.manager.listChannelAgents(channelId)).find(a => a.role === 'lead')!
168
- // 带 lead token 的 client(task.list/a2a.send 等 Agent 作业面工具需要认证)
169
- const authedClient = new Client({ name: 'e2e-lead', version: '1.0.0' })
170
- await authedClient.connect(new StreamableHTTPClientTransport(new URL('http://localhost/api/mcp/workshop'), {
171
- fetch: mcpFetch,
172
- requestInit: { headers: { Authorization: `Bearer ${lead.token}` } },
173
- }))
174
- attachScheduler(h.manager, channelId, 10)
175
-
176
- // task.submit → 自动调度闭环(等 COMPLETED)
177
- const sub = await client.callTool({ name: 'workshop.task.submit', arguments: { channelId, title: 'MCP 驱动任务' } })
178
- const mainTask = JSON.parse((sub.content as { text: string }[])[0].text)
179
- check('MCP task.submit 创建任务', typeof mainTask.id === 'string')
180
- const done = await waitUntil(async () => {
181
- const list = await authedClient.callTool({ name: 'workshop.task.list', arguments: {} })
182
- const tasks = JSON.parse((list.content as { text: string }[])[0].text) as { id: string, state: string }[]
183
- return tasks.some(t => t.id === mainTask.id && t.state === 'COMPLETED')
184
- }, 10_000)
185
- check('MCP 驱动任务自动闭环 COMPLETED', done)
186
-
187
- // 无 token → 工具错误(MCP 工具错误以 isError 结果返回,非异常)
188
- const anon = new Client({ name: 'anon', version: '1.0.0' })
189
- await anon.connect(new StreamableHTTPClientTransport(new URL('http://localhost/api/mcp/workshop'), { fetch: mcpFetch }))
190
- const anonRes = await anon.callTool({ name: 'workshop.a2a.send', arguments: { toAgentId: lead.id, parts: [{ text: 'hi' }] } })
191
- const anonText = ((anonRes.content as { text: string }[])[0]?.text ?? '')
192
- check('MCP 无 token → UNAUTHORIZED', anonRes.isError === true && anonText.includes('UNAUTHORIZED'), anonText.slice(0, 120))
193
-
194
- console.log('\n=== 2. REST API 驱动 ===')
195
- const w1Agent = (await h.manager.listChannelAgents(channelId)).find(a => a.name === 'w1')!
196
- const token = w1Agent.token!
197
- check('REST 作业面 token 有效(findByToken)', h.manager.findByToken(token)?.id === w1Agent.id)
198
-
199
- // 跨 channel 作用域:另一 channel 的 agent 查任务 → SCOPE_VIOLATION
200
- const chB = await h.manager.createChannel({ name: 'b-channel' })
201
- h.channels.push(chB.channelId)
202
- const leadB = await h.manager.addAgentToChannel({ channelId: chB.channelId, agentId: (await h.manager.createAgent({ name: 'leadB', harness: 'mock', config: { delayMs: 0 } })).id, role: 'lead' })
203
- let scopeCode = ''
204
- try {
205
- await h.manager.getTask(chB.channelId, leadB.id, mainTask.id)
206
- }
207
- catch (e) {
208
- scopeCode = (e as { code?: string }).code ?? ''
209
- }
210
- check('REST 跨 channel 访问 → SCOPE_VIOLATION', scopeCode === 'SCOPE_VIOLATION', scopeCode)
211
-
212
- // a2a.send 投递(caller=worker token;A2AMessage 无 toAgentId 字段,以 messageId+parts 断言)
213
- const msg = await h.manager.sendA2A(channelId, w1Agent.id, { toAgentId: lead.id, parts: [{ text: 'REST 消息' }] })
214
- check('REST 作业面 a2a.send 投递', msg.messageId.length > 0 && msg.parts.length === 1 && msg.metadata?.['x-aw-target-agent'] === lead.id, JSON.stringify(msg.metadata))
215
-
216
- for (const loop of h.loops) loop.stop()
217
- for (const cid of h.channels) await h.manager.removeChannel(cid)
218
- h.db.close()
219
-
220
- console.log(`\n${failures === 0 ? 'ALL PASS' : `${failures} FAILED`}`)
221
- process.exit(failures === 0 ? 0 : 1)
222
- }
223
-
224
- main().catch((e) => {
225
- console.error('双驱动测试异常:', e)
226
- process.exit(1)
227
- })
@@ -1,131 +0,0 @@
1
- /**
2
- * Agent 数控读写真机验证(协议级):
3
- * ①真实 omp --mode rpc 子进程接受含 dcw_read/dcw_control 的 set_host_tools
4
- * ②真实 OmpRpcAgentImpl.handleHostTool 分发 → toolDcwRead/toolDcwControl 执行体:
5
- * mock 节点写读对照 / 真实 Modbus TCP(1502 从站 40023)写读 roundtrip / 越权与量程联锁。
6
- * 数据目录 AW_HOME 隔离,不触碰检出内 .AgentWorkShop。
7
- * 运行: npx tsx --tsconfig .nuxt/tsconfig.server.json scripts/_dbg-agent-dcw-rw.ts
8
- */
9
- import { spawn } from 'node:child_process'
10
- import { mkdtempSync } from 'node:fs'
11
- import { tmpdir } from 'node:os'
12
- import { join } from 'node:path'
13
-
14
- // 进程级隔离:配置根落在一次性目录(须在业务模块 import 前设置)
15
- const awHome = mkdtempSync(join(tmpdir(), 'aw-agent-dcw-rw-'))
16
- process.env.AW_MODE = 'home'
17
- process.env.AW_HOME = awHome
18
- process.env.NO_PROXY = '127.0.0.1,localhost'
19
-
20
- let failures = 0
21
- const check = (name: string, ok: boolean, detail = ''): void => {
22
- console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
23
- if (!ok) failures += 1
24
- }
25
-
26
- // ===== 环境准备(隔离配置根内):mock 节点 + 真实 Modbus 节点 + Agent 绑定 =====
27
- const { getDcwController } = await import('../server/services/workshop/dcw/dcw-controller')
28
- const { getAgentNodeBindingRepo } = await import('../server/services/workshop/agents/node-bindings.repo')
29
- const AGENT = 'agent-rw-verify'
30
- const mockNode = getDcwController().create({ templateRef: 'dcw-temp-sp', name: 'Agent验证·mock', driver: 'mock', driverConfig: { key: 'agent-rw-mock' }, readIntervalMs: 0 })
31
- const modbusNode = getDcwController().create({
32
- templateRef: 'dcw-temp-sp', name: 'Agent验证·Modbus真实', driver: 'modbus-tcp', readIntervalMs: 0,
33
- min: 0, max: 300,
34
- driverConfig: { host: '127.0.0.1', port: 1502, unitId: 1, register: 40023, dataType: 'float32', byteOrder: 'big' },
35
- })
36
- const repo = getAgentNodeBindingRepo()
37
- repo.bind(AGENT, mockNode.id, 'dcw', 'auto')
38
- repo.bind(AGENT, modbusNode.id, 'dcw', 'auto')
39
- console.log(`mock=${mockNode.id} modbus=${modbusNode.id}(40023)\n`)
40
-
41
- // ===== ① 真实 omp 子进程:set_host_tools 含 dcw_read/dcw_control → 协议接受 =====
42
- console.log('--- ① 真实 omp 子进程工具面注入 ---')
43
- const { hostToolsForRole, OmpRpcAgentImpl } = await import('../server/services/workshop/agents/omp-agent')
44
-
45
- {
46
- const tools = hostToolsForRole('worker')
47
- const child = spawn('omp', ['--mode', 'rpc'], { stdio: ['pipe', 'pipe', 'pipe'], windowsHide: true })
48
- let buf = ''
49
- let sent = false
50
- let accepted: unknown = null
51
- await new Promise<unknown>((resolve) => {
52
- const timer = setTimeout(() => resolve('timeout'), 30_000)
53
- child.stdout!.setEncoding('utf-8')
54
- child.stdout!.on('data', (chunk: string) => {
55
- buf += chunk
56
- let idx: number
57
- while ((idx = buf.indexOf('\n')) >= 0) {
58
- const line = buf.slice(0, idx)
59
- buf = buf.slice(idx + 1)
60
- if (!line.trim()) continue
61
- try {
62
- const frame = JSON.parse(line)
63
- if (frame.type === 'ready' && !sent) {
64
- sent = true
65
- child.stdin!.write(`${JSON.stringify({ id: 'req_tools', type: 'set_host_tools', tools })}\n`)
66
- }
67
- else if (frame.type === 'response' && frame.id === 'req_tools') {
68
- clearTimeout(timer)
69
- accepted = frame
70
- resolve(frame)
71
- }
72
- }
73
- catch { /* 非 JSON 行忽略 */ }
74
- }
75
- })
76
- child.on('exit', () => resolve(accepted ?? 'exit'))
77
- })
78
- child.kill()
79
- const resp = accepted as { success?: boolean, data?: { toolNames?: string[] } } | null
80
- const names = resp?.data?.toolNames ?? []
81
- check('①真实 omp 接受含 dcw_read/dcw_control 的 set_host_tools',
82
- !!resp && resp.success === true && names.includes('dcw_read') && names.includes('dcw_control'),
83
- `工具数=${names.length}`)
84
- }
85
-
86
- // ===== ② 真实分发 + 执行:handleHostTool → dcw_read / dcw_control =====
87
- console.log('\n--- ② Agent 工具真实执行(mock) ---')
88
- {
89
- const impl = new OmpRpcAgentImpl({ agentId: AGENT, name: '读写验证员', role: 'worker', channelId: 'ch-rw-v' })
90
- // 真实 server 里 workspace 由 run 上下文注入 ensureClient;工具执行体不依赖 workspace
91
- // 内函数 —— harness 注入最小桩以通过就绪门(与 server 行为一致:非空即放行)
92
- ;(impl as unknown as { workspace: unknown }).workspace = { listAgents: async () => [] }
93
- const handle = (impl as unknown as Record<string, (req: unknown) => Promise<{ text: string, isError?: boolean }>>).handleHostTool.bind(impl)
94
-
95
- const r0 = await handle({ toolName: 'dcw_read', arguments: { node_id: mockNode.id } })
96
- check('②dcw_read:mock 未写入如实报失败(鉴权通过)', r0.isError === true && /读取失败/.test(r0.text) && !/无权/.test(r0.text), r0.text.slice(0, 90))
97
-
98
- const w = await handle({ toolName: 'dcw_control', arguments: { node_id: mockNode.id, value: 175.5, hypothesis: 'Agent 真机验证写入' } })
99
- check('②Agent 下发 175.5 成功(开优化记录)', !w.isError && /下发成功/.test(w.text), w.text.replace(/\n/g, ' ').slice(0, 100))
100
- const r1 = await handle({ toolName: 'dcw_read', arguments: { node_id: mockNode.id } })
101
- check('②dcw_read 读回 ACT=175.5 且与 SET 一致', !r1.isError && /175\.5/.test(r1.text) && /一致/.test(r1.text), r1.text.replace(/\n/g, ' ').slice(0, 120))
102
-
103
- // 越权:未绑定节点读取/写入均拒
104
- const rDeny = await handle({ toolName: 'dcw_read', arguments: { node_id: 'dw-not-bound' } })
105
- const wDeny = await handle({ toolName: 'dcw_control', arguments: { node_id: 'dw-not-bound', value: 1 } })
106
- check('②未绑定节点读取/写入均被拒', rDeny.isError === true && wDeny.isError === true && /无权/.test(rDeny.text) && /无权/.test(wDeny.text))
107
-
108
- // 量程联锁:越工艺安全量程下发被拒
109
- const wRange = await handle({ toolName: 'dcw_control', arguments: { node_id: mockNode.id, value: 999 } })
110
- check('②越量程下发被拒(999 ∉ [150,200])', wRange.isError === true && /越出|量程/.test(wRange.text), wRange.text.slice(0, 80))
111
- }
112
-
113
- // ===== ③ 真实 Modbus TCP:Agent 写 → 读(真寄存器 roundtrip) =====
114
- console.log('\n--- ③ Agent 经真实 Modbus(1502 从站 40023)读写 ---')
115
- {
116
- const impl = new OmpRpcAgentImpl({ agentId: AGENT, name: '读写验证员', role: 'worker', channelId: 'ch-rw-v' })
117
- ;(impl as unknown as { workspace: unknown }).workspace = { listAgents: async () => [] }
118
- const handle = (impl as unknown as Record<string, (req: unknown) => Promise<{ text: string, isError?: boolean }>>).handleHostTool.bind(impl)
119
-
120
- const wm = await handle({ toolName: 'dcw_control', arguments: { node_id: modbusNode.id, value: 4.5, hypothesis: '真实链路验证' } })
121
- check('③Agent 经真实 Modbus 下发 4.5 成功(同址回读一致)', !wm.isError && /下发成功/.test(wm.text), wm.text.replace(/\n/g, ' ').slice(0, 110))
122
- const rm = await handle({ toolName: 'dcw_read', arguments: { node_id: modbusNode.id } })
123
- check('③Agent 从真实寄存器读回 4.5(ACT=SET)', !rm.isError && /4\.5/.test(rm.text) && /一致/.test(rm.text), rm.text.replace(/\n/g, ' ').slice(0, 130))
124
-
125
- const wm2 = await handle({ toolName: 'dcw_control', arguments: { node_id: modbusNode.id, value: 12.75 } })
126
- const rm2 = await handle({ toolName: 'dcw_read', arguments: { node_id: modbusNode.id } })
127
- check('③第二轮:写 12.75 → 读回 12.75', !wm2.isError && !rm2.isError && /12\.75/.test(rm2.text), rm2.text.replace(/\n/g, ' ').slice(0, 110))
128
- }
129
-
130
- console.log(failures === 0 ? '\nAGENT-DCW-RW ALL PASS' : `\nAGENT-DCW-RW FAILED(${failures})`)
131
- process.exit(failures === 0 ? 0 : 1)
@@ -1,160 +0,0 @@
1
- /**
2
- * GameSession 会话级集成测试 — 模拟 Agent 全链路(无浏览器,node + tsx 直跑)
3
- *
4
- * 直接构造 GameSession + 假 WS peer,驱动上行消息,断言下行指令序列:
5
- * 1. 同步 brain(ScriptBrain):connect → session.ready/agent.state;玩家贴脸 →
6
- * dialog.open;3 次 interact → advance×2 + close + 大脑收到事件转入 wait
7
- * 2. 异步 brain(MockSdkBrain, 零延迟):决议经 Promise 到达,agent.move 照常下发
8
- * 3. 大脑主动 dialog.close 动作 → 下行 dialog.close
9
- * 4. stop() 停止主循环,不再产出新指令
10
- */
11
- import { AGENT_SPAWN, type AgentAction, type AgentBrain, type AgentContext, type ServerToClient } from '../server/types/game'
12
- import { MockSdkBrain, ScriptBrain } from '../server/services/game/agent'
13
- import { GameSession } from '../server/services/game/session'
14
-
15
- let failures = 0
16
- function check(name: string, ok: boolean, detail = ''): void {
17
- console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
18
- if (!ok) {
19
- failures += 1
20
- }
21
- }
22
-
23
- /** 假 WS peer:捕获下行指令(结构兼容 h3 peer) */
24
- class FakePeer {
25
- sent: ServerToClient[] = []
26
-
27
- send(data: string | Uint8Array): void {
28
- this.sent.push(JSON.parse(typeof data === 'string' ? data : new TextDecoder().decode(data)))
29
- }
30
-
31
- close(): void {}
32
- }
33
-
34
- const sleep = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms))
35
-
36
- /** 轮询等待某条下行指令出现 */
37
- async function waitFor(peer: FakePeer, pred: (cmd: ServerToClient) => boolean, timeoutMs: number): Promise<ServerToClient | null> {
38
- const deadline = Date.now() + timeoutMs
39
- while (Date.now() < deadline) {
40
- const found = peer.sent.find(pred)
41
- if (found) {
42
- return found
43
- }
44
- await sleep(25)
45
- }
46
- return null
47
- }
48
-
49
- const count = (peer: FakePeer, type: ServerToClient['type']) => peer.sent.filter(c => c.type === type).length
50
-
51
- // ---------------------------------------------------------------- 1. 同步 brain 全链路
52
-
53
- async function testSyncBrain(): Promise<void> {
54
- const peer = new FakePeer()
55
- const session = new GameSession(new ScriptBrain())
56
- session.connect(peer)
57
-
58
- check('connect → session.ready', peer.sent.some(m => m.type === 'session.ready'))
59
- check('connect → agent.state(初始)', peer.sent.some(m => m.type === 'agent.state'))
60
-
61
- // 玩家贴脸(80px < TALK_RADIUS 96px)→ 首个 tick 应发起对话
62
- session.handleInput({
63
- type: 'player.pos',
64
- payload: { x: AGENT_SPAWN.x + 80, y: AGENT_SPAWN.y, tileX: 0, tileY: 0 },
65
- })
66
- const opened = await waitFor(peer, m => m.type === 'dialog.open', 2000)
67
- check('贴脸 → dialog.open', opened !== null, opened ? `lines=${(opened as { payload: { lines: string[] } }).payload.lines.length}` : 'timeout')
68
-
69
- // 空格推进:3 句 → advance×2 + close
70
- session.handleInput({ type: 'input.interact', payload: {} })
71
- await waitFor(peer, m => m.type === 'dialog.advance', 1000)
72
- session.handleInput({ type: 'input.interact', payload: {} })
73
- await waitFor(peer, () => count(peer, 'dialog.advance') >= 2, 1000)
74
- session.handleInput({ type: 'input.interact', payload: {} })
75
- const closed = await waitFor(peer, m => m.type === 'dialog.close', 1000)
76
- check('interact×3 → dialog.close', closed !== null)
77
- check('advance 恰好 2 次', count(peer, 'dialog.advance') === 2, `actual=${count(peer, 'dialog.advance')}`)
78
-
79
- // 大脑收到 dialog.closed 事件 → 转入 wait
80
- const waited = await waitFor(peer, m => m.type === 'agent.state' && m.payload.mode === 'wait', 2000)
81
- check('dialog.closed 事件 → 大脑转入 wait', waited !== null)
82
- session.stop()
83
- }
84
-
85
- // ---------------------------------------------------------------- 2. 异步 brain(sdk-mock)
86
-
87
- async function testAsyncBrain(): Promise<void> {
88
- const peer = new FakePeer()
89
- const session = new GameSession(new MockSdkBrain([0, 0]))
90
- session.connect(peer)
91
-
92
- const ready = await waitFor(peer, m => m.type === 'session.ready', 1000)
93
- check('async connect → session.ready', ready !== null)
94
- // 无玩家感知 → 大脑游荡,首个 move 决议须经 Promise 应用
95
- const moved = await waitFor(peer, m => m.type === 'agent.move', 2000)
96
- check('async 决议 → agent.move 下发', moved !== null, moved ? `dir=${moved.payload.dir}` : 'timeout')
97
- session.stop()
98
- }
99
-
100
- // ---------------------------------------------------------------- 3. 大脑主动关闭对话
101
-
102
- /** 逐 tick 演进的测试大脑:先开对话,下一 tick 主动关闭 */
103
- class CloseBrain implements AgentBrain {
104
- readonly name = 'close-test'
105
-
106
- private phase = 0
107
-
108
- think(_ctx: AgentContext): AgentAction[] {
109
- if (this.phase === 0) {
110
- this.phase = 1
111
- return [{ kind: 'dialog', lines: ['hi'] }]
112
- }
113
- if (this.phase === 1) {
114
- this.phase = 2
115
- return [{ kind: 'dialog.close' }]
116
- }
117
- return []
118
- }
119
- }
120
-
121
- async function testBrainClose(): Promise<void> {
122
- const peer = new FakePeer()
123
- const session = new GameSession(new CloseBrain())
124
- session.connect(peer)
125
-
126
- const opened = await waitFor(peer, m => m.type === 'dialog.open', 1000)
127
- check('大脑发起对话 → dialog.open', opened !== null)
128
- const closed = await waitFor(peer, m => m.type === 'dialog.close', 1000)
129
- check('大脑 dialog.close 动作 → 下行 dialog.close', closed !== null)
130
- session.stop()
131
- }
132
-
133
- // ---------------------------------------------------------------- 4. stop() 停止主循环
134
-
135
- async function testStop(): Promise<void> {
136
- const peer = new FakePeer()
137
- const session = new GameSession(new ScriptBrain())
138
- session.connect(peer)
139
- await waitFor(peer, m => m.type === 'agent.move', 1500)
140
- session.stop()
141
- const before = peer.sent.length
142
- await sleep(300)
143
- check('stop() 后无新指令', peer.sent.length === before, `before=${before} after=${peer.sent.length}`)
144
- }
145
-
146
- // ----------------------------------------------------------------
147
-
148
- async function main(): Promise<void> {
149
- await testSyncBrain()
150
- await testAsyncBrain()
151
- await testBrainClose()
152
- await testStop()
153
- console.log(`\n===== ${4 - failures}/4 passed =====`)
154
- process.exit(failures ? 1 : 0)
155
- }
156
-
157
- main().catch((e) => {
158
- console.error('SESSION TEST CRASH:', e)
159
- process.exit(2)
160
- })