agentworkshop 0.7.41 → 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.
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/B8uy9iRW.js +1 -0
- package/.output/public/_nuxt/BGTYYQH-.js +1 -0
- package/.output/public/_nuxt/BUmHEJ77.js +2 -0
- package/.output/public/_nuxt/BY7YrV_D.js +1 -0
- package/.output/public/_nuxt/Bqcgz95s.js +18 -0
- package/.output/public/_nuxt/C7UlqPeD.js +1 -0
- package/.output/public/_nuxt/CTmDn-YP.js +1 -0
- package/.output/public/_nuxt/Cs8pVeVG.js +1 -0
- package/.output/public/_nuxt/DKySmNPI.js +14 -0
- package/.output/public/_nuxt/Di0NY_ht.js +1 -0
- package/.output/public/_nuxt/Du9v6qGB.js +1 -0
- package/.output/public/_nuxt/{DGbd-UiG.js → EHlEMRii.js} +1 -1
- package/.output/public/_nuxt/Eke1fs-U.js +1 -0
- package/.output/public/_nuxt/{DeJykBFf.js → H0BcqZtT.js} +1 -1
- package/.output/public/_nuxt/{C4oW1HsE.js → HSYEkkce.js} +1 -1
- package/.output/public/_nuxt/{D_OTvQza.js → J9oU00D1.js} +1 -1
- package/.output/public/_nuxt/OmpTerminalPanel.BxR5-lvi.css +1 -0
- package/.output/public/_nuxt/SSyAPaHc.js +1 -0
- package/.output/public/_nuxt/TownView.li4EMQxK.css +1 -0
- package/.output/public/_nuxt/VdLk4jLE2.js +1 -0
- package/.output/public/_nuxt/_id_.BhsXoEuk.css +1 -0
- package/.output/public/_nuxt/_id_.DQyj-WKN.css +1 -0
- package/.output/public/_nuxt/{_wsId_.B4WiqkIS.css → _wsId_.CTx-BlfF.css} +1 -1
- package/.output/public/_nuxt/aShsv_rG.js +19 -0
- package/.output/public/_nuxt/{yF0aYMF1.js → aesgXKSr.js} +3 -3
- package/.output/public/_nuxt/agents.B21nUEUX.css +1 -0
- package/.output/public/_nuxt/aml.ekTjxFkf.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/b5506ef0-f48e-4f14-a6c0-d65e505d0b09.json +1 -0
- package/.output/public/_nuxt/channel-templates.DplM1pJ3.css +1 -0
- package/.output/public/_nuxt/daq.BdVmoIfe.css +1 -0
- package/.output/public/_nuxt/dcw.DCgyzpWV.css +1 -0
- package/.output/public/_nuxt/{default.J32SgqFY.css → default.Bh3L2HU8.css} +1 -1
- package/.output/public/_nuxt/lHAxdSsj.js +1 -0
- package/.output/public/_nuxt/pages.j9rzRocr.css +1 -0
- package/.output/public/_nuxt/plugins.DhiZbHU8.css +1 -0
- package/.output/public/_nuxt/settings.BRx9rIDg.css +1 -0
- package/.output/public/_nuxt/teams.BLlOPB_Z.css +1 -0
- package/.output/public/_nuxt/tokens.De8xzszn.css +1 -0
- package/.output/public/_nuxt/workshop.D1jFNkrn.css +1 -0
- package/.output/server/chunks/_/nitro.mjs +748 -733
- package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs → OmpTerminalPanel-CVvxpsF7.mjs} +15 -15
- package/.output/server/chunks/build/{OmpTerminalPanel-Cu_s4i0A.mjs.map → OmpTerminalPanel-CVvxpsF7.mjs.map} +1 -1
- package/.output/server/chunks/build/{TownView--otsnr_7.mjs → TownView-CJ6IsDCc.mjs} +226 -225
- package/.output/server/chunks/build/{TownView--otsnr_7.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-Brv-JSvr.mjs → _id_-CVD_H0ir.mjs} +143 -142
- package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-D9pl1OZy.mjs → _id_-CtxMb3qp.mjs} +41 -41
- package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs → _wsId_-DpY3T-md.mjs} +514 -444
- package/.output/server/chunks/build/{_wsId_-BASfDf0s.mjs.map → _wsId_-DpY3T-md.mjs.map} +1 -1
- package/.output/server/chunks/build/{agents-CLQqJF2y.mjs → agents-DsCkuXDV.mjs} +49 -46
- package/.output/server/chunks/build/agents-DsCkuXDV.mjs.map +1 -0
- package/.output/server/chunks/build/{aml-_7FdFL2p.mjs → aml-CT0xKGKI.mjs} +122 -105
- package/.output/server/chunks/build/aml-CT0xKGKI.mjs.map +1 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs +117 -0
- package/.output/server/chunks/build/builtin-catalog-edpDcgfL.mjs.map +1 -0
- package/.output/server/chunks/build/{channel-templates-p6SZNjlM.mjs → channel-templates-Dla9FbYa.mjs} +49 -37
- package/.output/server/chunks/build/channel-templates-Dla9FbYa.mjs.map +1 -0
- package/.output/server/chunks/build/{daq-DkMvD8sg.mjs → daq-DWWSKGAV.mjs} +89 -88
- package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-C1jgVJkO.mjs → dcw-CT8k17_E.mjs} +44 -43
- package/.output/server/chunks/build/dcw-CT8k17_E.mjs.map +1 -0
- package/.output/server/chunks/build/{default-BE4r0FZN.mjs → default-D3525UFX.mjs} +16 -15
- package/.output/server/chunks/build/{default-BE4r0FZN.mjs.map → default-D3525UFX.mjs.map} +1 -1
- package/.output/server/chunks/build/{monitor-C0okQaXz.mjs → monitor-Dh2Kvaq5.mjs} +2 -2
- package/.output/server/chunks/build/{monitor-C0okQaXz.mjs.map → monitor-Dh2Kvaq5.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-B0bUWnC6.mjs → pages-BHcSBM2D.mjs} +24 -18
- package/.output/server/chunks/build/pages-BHcSBM2D.mjs.map +1 -0
- package/.output/server/chunks/build/{plugins-lGlUr1lj.mjs → plugins-BNgIi61W.mjs} +23 -17
- package/.output/server/chunks/build/plugins-BNgIi61W.mjs.map +1 -0
- package/.output/server/chunks/build/{settings-Ddd1qx2Z.mjs → settings-ipYBDWdn.mjs} +45 -44
- package/.output/server/chunks/build/settings-ipYBDWdn.mjs.map +1 -0
- package/.output/server/chunks/build/{teams-MEDLZdMn.mjs → teams-5lx-CwFa.mjs} +47 -44
- package/.output/server/chunks/build/teams-5lx-CwFa.mjs.map +1 -0
- package/.output/server/chunks/build/{tokens-C7fTM5C2.mjs → tokens-q-IDT8Io.mjs} +73 -53
- package/.output/server/chunks/build/tokens-q-IDT8Io.mjs.map +1 -0
- package/.output/server/chunks/build/{town-CoQdblbZ.mjs → town-QJWB5wng.mjs} +3 -2
- package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
- package/.output/server/chunks/build/{workshop-BL5bhfs0.mjs → workshop-BIlCj_ZP.mjs} +45 -45
- package/.output/server/chunks/build/workshop-BIlCj_ZP.mjs.map +1 -0
- package/.output/server/chunks/routes/renderer.mjs +1 -1
- package/.output/server/chunks/virtual/entry.mjs +637 -86
- package/.output/server/chunks/virtual/entry.mjs.map +1 -1
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +13 -3
- package/README.md +13 -3
- package/app/components/AppHeader.vue +961 -959
- package/app/components/workshop/A2aRpcDebugger.vue +237 -237
- package/app/components/workshop/AgentInspectorDrawer.vue +424 -424
- package/app/components/workshop/AgentLanesView.vue +998 -998
- package/app/components/workshop/ArtifactCard.vue +120 -120
- package/app/components/workshop/AssetLibrary.vue +452 -452
- package/app/components/workshop/ChannelSessionList.vue +812 -804
- package/app/components/workshop/CommandPalette.vue +269 -269
- package/app/components/workshop/Composer.vue +827 -827
- package/app/components/workshop/DeviceTwinPanel.vue +643 -643
- package/app/components/workshop/EventBlock.vue +602 -602
- package/app/components/workshop/FileSelectorModal.vue +188 -188
- package/app/components/workshop/InspectorPanel.vue +301 -301
- package/app/components/workshop/MemoryPanel.vue +364 -364
- package/app/components/workshop/MultiChannelView.vue +189 -189
- package/app/components/workshop/TaskBoardView.vue +686 -686
- package/app/components/workshop/TaskInspectorDrawer.vue +390 -390
- package/app/components/workshop/TranscriptTimeline.vue +601 -601
- package/app/components/workshop/blocks/ClusterArtifact.vue +61 -61
- package/app/components/workshop/blocks/ClusterError.vue +85 -85
- package/app/components/workshop/blocks/ClusterLife.vue +87 -87
- package/app/components/workshop/blocks/ClusterMember.vue +95 -93
- package/app/components/workshop/blocks/ClusterMemory.vue +84 -84
- package/app/components/workshop/blocks/ClusterOther.vue +67 -67
- package/app/components/workshop/blocks/ClusterStatus.vue +149 -147
- package/app/components/workshop/blocks/ClusterTool.vue +158 -158
- package/app/components/workshop/terminal/OmpTerminalPanel.vue +1011 -1011
- package/app/components/workshop/town/TownView.vue +56 -56
- package/app/middleware/locale-cookie.global.ts +22 -0
- package/app/pages/aml/index.vue +24 -2
- package/app/pages/daq/[id].vue +11 -11
- package/app/pages/daq/index.vue +2316 -2316
- package/app/pages/dcw/[id].vue +18 -19
- package/app/pages/dcw/index.vue +3 -3
- package/app/pages/index.vue +987 -981
- package/app/pages/plugins/index.vue +317 -311
- package/app/pages/settings.vue +1564 -1562
- package/app/pages/tokens.vue +15 -15
- package/app/pages/workshop/agents.vue +612 -609
- package/app/pages/workshop/channel-templates.vue +18 -11
- package/app/pages/workshop/index.vue +668 -668
- package/app/pages/workshop/teams.vue +743 -740
- package/app/pages/workshop/w/[wsId].vue +704 -704
- package/app/plugins/locale-restore.client.ts +16 -1
- package/app/utils/builtin-catalog.ts +104 -0
- package/config.yml +170 -170
- package/docs/full-test-plan.md +2 -3
- package/i18n/locales/en.ts +293 -35
- package/i18n/locales/zh-CN.ts +265 -7
- package/package.json +2 -2
- package/scripts/_dbg-perms-e2e.mjs +132 -109
- package/scripts/_demo-agentteam-capture.mjs +278 -0
- package/scripts/_e2e-ui-functional.mjs +371 -0
- package/scripts/_i18n-en-cjk-scan.mjs +28 -0
- package/scripts/_i18n-en-verify.mjs +107 -0
- package/scripts/_i18n-extra-checks.mjs +53 -0
- package/scripts/_i18n-scan-hardcoded.mjs +180 -0
- package/scripts/_i18n-verify-keys.mjs +50 -0
- package/scripts/_loop-biax-campaign.mjs +15 -9
- package/scripts/_sweep-button-contrast.mjs +87 -0
- package/scripts/ui/record-demo.mjs +557 -0
- package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
- package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
- package/server/services/workshop/daq/drivers.ts +11 -0
- package/.output/public/_nuxt/88GOowgO2.js +0 -1
- package/.output/public/_nuxt/BN16aRdQ.js +0 -1
- package/.output/public/_nuxt/BWVhZntO.js +0 -1
- package/.output/public/_nuxt/BXxH0UQE.js +0 -1
- package/.output/public/_nuxt/BsROmY89.js +0 -1
- package/.output/public/_nuxt/C0ZrS1KL.js +0 -19
- package/.output/public/_nuxt/C4s_niy-.js +0 -14
- package/.output/public/_nuxt/D6mbFA-a.js +0 -1
- package/.output/public/_nuxt/DS8qj8EL.js +0 -1
- package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
- package/.output/public/_nuxt/Dap5PmTL.js +0 -2
- package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
- package/.output/public/_nuxt/DvLxILQP.js +0 -1
- package/.output/public/_nuxt/OmpTerminalPanel.B_Pl5Ynm.css +0 -1
- package/.output/public/_nuxt/TownView.f-YOdXRs.css +0 -1
- package/.output/public/_nuxt/_id_.D9ymtHB5.css +0 -1
- package/.output/public/_nuxt/_id_.jf-_8E_c.css +0 -1
- package/.output/public/_nuxt/agents.ZD0rVdHE.css +0 -1
- package/.output/public/_nuxt/aml.BN8ZEW4h.css +0 -1
- package/.output/public/_nuxt/builds/meta/78a295bb-2072-48a4-8b46-6d545fdae743.json +0 -1
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
- package/.output/public/_nuxt/daq.DjSgY94l.css +0 -1
- package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
- package/.output/public/_nuxt/eqq3wWQF.js +0 -18
- package/.output/public/_nuxt/pages.D7aSDlUI.css +0 -1
- package/.output/public/_nuxt/plugins.sb4nDGqr.css +0 -1
- package/.output/public/_nuxt/qr6R6Ha1.js +0 -1
- package/.output/public/_nuxt/settings.BTgR-NiE.css +0 -1
- package/.output/public/_nuxt/teams.CxnxKVVs.css +0 -1
- package/.output/public/_nuxt/tokens.BFY7qv4f.css +0 -1
- package/.output/public/_nuxt/workshop.CR_QzeAN.css +0 -1
- package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +0 -1
- package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +0 -1
- package/.output/server/chunks/build/aml-_7FdFL2p.mjs.map +0 -1
- package/.output/server/chunks/build/channel-templates-p6SZNjlM.mjs.map +0 -1
- package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +0 -1
- package/.output/server/chunks/build/dcw-C1jgVJkO.mjs.map +0 -1
- package/.output/server/chunks/build/pages-B0bUWnC6.mjs.map +0 -1
- package/.output/server/chunks/build/plugins-lGlUr1lj.mjs.map +0 -1
- package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +0 -1
- package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +0 -1
- package/.output/server/chunks/build/tokens-C7fTM5C2.mjs.map +0 -1
- package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +0 -1
- package/.output/server/chunks/build/workshop-BL5bhfs0.mjs.map +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.continue.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.json +0 -1
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/054bb4bc4e4217a2ab0b0c6b663747214a9af464f5568ea60e784704e340bb0f.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/0554197c3d2bac203a1f5c48a5d52495d1336db1bd7aa4f4c19875810363e684.source +0 -153
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/05c203d12a954fffba942e1d626e3f5fec05b18a5aef2bac003b7884a4b77406.source +0 -431
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/145a6e6ba811af35aed31e0cc3211f53706a14980fa069ee9e3c1591139f96f0.source +0 -180
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/1ae0fb281f97899c025e368ca73f41349b01fb5f85b19e08d7ebccff49095ed5.source +0 -112
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/20ee9967aa0541c516db7575bdd4a1a745939bc7016bd10d7635903113edb8ec.source +0 -227
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/233751843aeba9d4c5263f275b8102de00443c06f286918cbd23e0b6729e01aa.source +0 -131
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/263c2e05b425e5c322e60c6475279f963bab3a873eb521b21df7fec935a46597.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/2ac5eaac28eaaee2178c4b32014df8e732f9dd432a285a872f5b7f31c1395fa4.source +0 -157
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/33a0db0d2d95d2c11d0720090458e5fba1425376f5ceb5b422d1d3471be3226b.source +0 -348
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/359e474ae83db35e4615d3728de6bf694b64d4dce9b536730a3b6518f9c0494a.source +0 -296
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/37a4c3c31c4b70f6b719c53d1e9c4fcba603f31adfdd11bb6ef9f0d094923c56.source +0 -285
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/461feca1c14a695eb8b0f17a6f95fe97eeceabc0510faaea4ba6d25bde147d8d.source +0 -217
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/4cfadbda8591f29d5e1e230dc000e701b756e850441b3900fb596a4d2f6e98c2.source +0 -301
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/50ca2810cf0b248c96948994c0c404531bbad6e553166b6816b3b03bfa0f75d2.source +0 -151
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/52d9e8cb591a9ae86e238c3620a610cd386717bee222c0f37b25b2ac7e523dc4.source +0 -135
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/6c87daafa4c006560a954636a97333f6123153c9d34d96203f7ee3a14e556357.source +0 -156
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/77bc47854b577900621cacf1697f81fa2bc005af129d37614a97a3441c08f99f.source +0 -72
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/7bbb789d3a527817118cf8399ed1861d6a962ade10878fd07223435d333a7089.source +0 -469
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/86b7c1daafc8b60ba9982dde5bf1bcc74265709483c176e71f23ec6fc267451f.source +0 -259
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/8df66d62bd41984d0970e8236882080c5085ac937d07a29779463d6a03b37c76.source +0 -97
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/9962f2d1b3a85137bd40cc835725600b39f89ee02a27c06b2e0402e1eafde115.source +0 -277
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a1090ea066b792cb201739f4994e37bf7c12f52e805239a1eab87f18a537f266.source +0 -171
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/a5241091650134d0919897e086a606a3846c8b2b54f8b4c84a8696c145ac3e9e.source +0 -425
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/af19fa4c6e34d5540028be4b17600161283af3a2a2dfc61a3fdc0834436a0c14.source +0 -99
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3713d78e2b2f93905ff6be8d58a8d0a2f42dea322eb67c987fda22ff6a084d5.source +0 -325
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/b3849943c3aff102d1af815631fc843a2a52c17646a4b04696ece323e483bbec.source +0 -332
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/bac735a3803a6650b7f1eb8f587e6dc5c68ea055000c3c99a11789438b78e837.source +0 -43
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d32c24d61eb8b33c73e5a8fc7e4dc7ef437aed238ee4371c2b00617e6104090a.source +0 -154
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/d5170dc9a9879fda04fdf91d8a110d4efb6787ee38dda596d4da899883d36a72.source +0 -531
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/dc59bcd7bb397be63ba2f2c3fec23f3782dcb17d2d75a9981eeced761f33eb32.source +0 -439
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/eb5ff8cf66b92aec7bf6726781d8df74faa2b030c3aaac17d6b00c1fc04c9b32.source +0 -323
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f10615a6fae18436e70d1201b2d44fdae3174a3aa7bb4db97dea1b3ed291d721.source +0 -433
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f36d3e271668bc1260c2823542d22dbf1d0f7041ff16b1ac175bce2256e582ca.source +0 -280
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/f94449380af09f31ac352152007146a75cc80bae8d6411ffe57b2c56f933b0ff.source +0 -117
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/fe4dfd81178fd6b648335f6405d9ede71319aebde796008e50db21f8adf44c99.source +0 -160
- package/scripts/.mimosa/hook-state/sess_aeaecb58-8833-4b83-b93c-050467d21e03.mtxwtxme-61028-1f4519fbc4.baseline/ffb4e3aa548cfdadabaa44ab599f9b6a3ca6288176a2cc7a4b2fc2a12b5fb848.source +0 -101
- package/scripts/.mimosa/hook-status/sess_aeaecb58-8833-4b83-b93c-050467d21e03-d944d71bb3.json +0 -14
- package/scripts/_dbg-audit-all-pages.mjs +0 -43
- package/scripts/_dbg-daq-frames-e2e.mjs +0 -118
- package/scripts/_dbg-daq-rowstyle-verify.mjs +0 -112
- package/scripts/_dbg-hitl-timeout-e2e.mjs +0 -93
- package/scripts/_dbg-live-line-e2e.mjs +0 -279
- package/scripts/_dbg-minimal-omp.mjs +0 -77
- package/scripts/_dbg-mode-nav.mjs +0 -173
- package/scripts/_dbg-real-scenario-timeline.log +0 -55
- package/scripts/_dbg-team-repro.mjs +0 -76
- package/scripts/_dbg-town-agents-probe.mjs +0 -60
- package/scripts/_dbg-visual-acceptance.mjs +0 -63
- package/scripts/_townview_style.css +0 -1185
- package/scripts/_townview_template.html +0 -1215
- package/server/data/agent-node-bindings.json +0 -554
- package/server/data/daq-templates.json +0 -16
- package/server/data/daqs.json +0 -7464
- package/server/data/dcw-lines.json +0 -1787
- package/server/data/dcw-products.json +0 -1626
- package/server/data/dcw-recipes.json +0 -4672
- package/server/data/dcw-rollback.json +0 -20754
- package/server/data/dcw-runs.json +0 -6354
- package/server/data/dcw-templates.json +0 -146
- package/server/data/dcw-writes.json +0 -15650
- package/server/data/dcws.json +0 -9033
- package/server/data/device-twins.json +0 -133
- package/server/data/line-runs.json +0 -322
- 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
|
-
})
|