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.
- 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/{DcsCcxp4.js → D98NH8kG.js} +1 -1
- 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/{C_iYD-EB.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/{DZshyNLa.js → IREZ1eC2.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/ca4TJ3Op.js +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 +1058 -830
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- 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-BuLAsmpR.mjs → TownView-CJ6IsDCc.mjs} +229 -228
- package/.output/server/chunks/build/{TownView-BuLAsmpR.mjs.map → TownView-CJ6IsDCc.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-BU1wfOID.mjs → _id_-CVD_H0ir.mjs} +149 -147
- package/.output/server/chunks/build/_id_-CVD_H0ir.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-BYsEua57.mjs → _id_-CtxMb3qp.mjs} +47 -46
- package/.output/server/chunks/build/_id_-CtxMb3qp.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-BxlDcJHg.mjs → _wsId_-DpY3T-md.mjs} +516 -446
- package/.output/server/chunks/build/{_wsId_-BxlDcJHg.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-cgyjKmJu.mjs → daq-DWWSKGAV.mjs} +95 -93
- package/.output/server/chunks/build/daq-DWWSKGAV.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-CoTxBlAZ.mjs → dcw-CT8k17_E.mjs} +46 -45
- 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/{logs-BSOeZwwI.mjs → logs--dByNyhw.mjs} +2 -2
- package/.output/server/chunks/build/{logs-BSOeZwwI.mjs.map → logs--dByNyhw.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-DFxjn9Xa.mjs → pages-BHcSBM2D.mjs} +26 -20
- 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-qMPql9Se.mjs → town-QJWB5wng.mjs} +5 -4
- package/.output/server/chunks/build/town-QJWB5wng.mjs.map +1 -0
- package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
- package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
- package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
- package/.output/server/chunks/build/useDcwStream-UPiX-3iO.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/api/workshop/channel-templates/_id/instantiate.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/channels.post.mjs +3 -3
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/start.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/templates/_key_.patch.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses/_id/providers.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/harnesses.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/pending.get.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/hitl/respond.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/disable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/plugins/_name/enable.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.delete.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/scene/layouts/_channelId_.put.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/workspaces/_id/channel-templates/_tplId_.post.mjs +1 -1
- package/.output/server/chunks/routes/renderer.mjs +2 -2
- package/.output/server/chunks/virtual/entry.mjs +639 -88
- 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 +16 -6
- package/README.md +17 -7
- 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/composables/workshop/useDaqStream.ts +7 -4
- package/app/composables/workshop/useDcwStream.ts +8 -2
- package/app/middleware/locale-cookie.global.ts +22 -0
- package/app/pages/aml/index.vue +24 -2
- package/app/pages/daq/[id].vue +20 -16
- package/app/pages/daq/index.vue +2310 -2307
- package/app/pages/dcw/[id].vue +26 -23
- 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/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/full-test-plan.md +2 -3
- package/docs/plugins.en.md +33 -1
- package/docs/plugins.md +29 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/locales/en.ts +293 -35
- package/i18n/locales/zh-CN.ts +265 -7
- package/package.json +4 -3
- 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 +130 -0
- package/scripts/_sweep-button-contrast.mjs +87 -0
- package/scripts/test-serial-bridge.mjs +418 -0
- package/scripts/ui/e2e-interactive.mjs +328 -0
- package/scripts/ui/record-demo.mjs +557 -0
- package/scripts/ui/seed-industrial-demo.mjs +171 -0
- package/server/api/workshop/daq/index.get.ts +3 -1
- package/server/api/workshop/dcw/index.get.ts +3 -0
- package/server/plugins/aw-plugins.ts +9 -7
- package/server/plugins-builtin/diag-bridge/index.mjs +608 -661
- package/server/plugins-builtin/rag-bridge/index.mjs +57 -133
- package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
- package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
- package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
- package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
- package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
- package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
- package/server/services/workshop/daq/daq-controller.ts +7 -2
- package/server/services/workshop/daq/drivers.ts +89 -3
- package/server/services/workshop/dcw/dcw-controller.ts +5 -1
- package/server/services/workshop/dcw/drivers.ts +85 -2
- package/server/services/workshop/dcw/plugin-bridge.ts +45 -0
- package/server/services/workshop/plugins/host.mjs +30 -0
- package/shared/daq-protocol.ts +15 -0
- package/.output/public/_nuxt/88GOowgO2.js +0 -1
- package/.output/public/_nuxt/BOt2SP4z.js +0 -1
- package/.output/public/_nuxt/BP-NyKVG.js +0 -18
- package/.output/public/_nuxt/BXxH0UQE.js +0 -1
- package/.output/public/_nuxt/BsROmY89.js +0 -1
- package/.output/public/_nuxt/ClILjvWH.js +0 -1
- package/.output/public/_nuxt/CzOCSGLe.js +0 -1
- package/.output/public/_nuxt/D8qvkfyY.js +0 -1
- package/.output/public/_nuxt/DCn7QZ5N.js +0 -2
- package/.output/public/_nuxt/DS8qj8EL.js +0 -1
- package/.output/public/_nuxt/DUjW5Yq5.js +0 -1
- package/.output/public/_nuxt/Dihnimlk.js +0 -19
- package/.output/public/_nuxt/Dqw1lpr-.js +0 -1
- package/.output/public/_nuxt/Dr9v6C0f.js +0 -14
- package/.output/public/_nuxt/Du9ZJtnj.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_.CM4bIgKY.css +0 -1
- package/.output/public/_nuxt/_id_.bNV69Dtf.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/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +0 -1
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +0 -1
- package/.output/public/_nuxt/daq.B0D3dPAc.css +0 -1
- package/.output/public/_nuxt/dcw.8Y7FqCVN.css +0 -1
- 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_-BU1wfOID.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-BYsEua57.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-cgyjKmJu.mjs.map +0 -1
- package/.output/server/chunks/build/dcw-CoTxBlAZ.mjs.map +0 -1
- package/.output/server/chunks/build/pages-DFxjn9Xa.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-qMPql9Se.mjs.map +0 -1
- package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs.map +0 -1
- package/.output/server/chunks/build/useDcwStream-CBbiYnWe.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,280 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* workshop 持久化层测试(node + tsx 直跑,无浏览器)
|
|
3
|
-
*
|
|
4
|
-
* 覆盖(v2:Agent 与 Channel 分离):
|
|
5
|
-
* 1. 建表:6 张表 + 4 个索引
|
|
6
|
-
* 2. channel CRUD
|
|
7
|
-
* 3. agent 定义 CRUD(全局,无 channel 绑定)
|
|
8
|
-
* 4. channel_agents 成员 CRUD + findByToken(成员级 token)
|
|
9
|
-
* 5. message 状态机(pending→consuming→consumed + resetConsuming)+ channel 作用域
|
|
10
|
-
* 6. subscription 复合主键去重(channel 隔离)
|
|
11
|
-
* 7. task CRUD + listNonTerminal
|
|
12
|
-
*/
|
|
13
|
-
import { DatabaseSync } from 'node:sqlite'
|
|
14
|
-
import { openWorkshopDb, initWorkshopDb, parseJson } from '../server/services/workshop/db/database'
|
|
15
|
-
import { createChannelRepo } from '../server/services/workshop/db/channel.repo'
|
|
16
|
-
import { createAgentRepo } from '../server/services/workshop/db/agent.repo'
|
|
17
|
-
import { createChannelAgentRepo } from '../server/services/workshop/db/channel-agent.repo'
|
|
18
|
-
import { createMessageRepo } from '../server/services/workshop/db/message.repo'
|
|
19
|
-
import { createSubscriptionRepo } from '../server/services/workshop/db/subscription.repo'
|
|
20
|
-
import { createTaskRepo } from '../server/services/workshop/db/task.repo'
|
|
21
|
-
|
|
22
|
-
let failures = 0
|
|
23
|
-
function check(name: string, ok: boolean, detail = ''): void {
|
|
24
|
-
console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
25
|
-
if (!ok) {
|
|
26
|
-
failures += 1
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function testSchema(db: DatabaseSync): void {
|
|
31
|
-
console.log('\n--- 1. 建表与索引 ---')
|
|
32
|
-
const tables = db
|
|
33
|
-
.prepare(
|
|
34
|
-
`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN ('channels','agents','channel_agents','messages','subscriptions','tasks') ORDER BY name`,
|
|
35
|
-
)
|
|
36
|
-
.all() as { name: string }[]
|
|
37
|
-
const names = tables.map(t => t.name)
|
|
38
|
-
check('6 张表全部创建', names.length === 6, names.join(','))
|
|
39
|
-
|
|
40
|
-
const indexes = db
|
|
41
|
-
.prepare(
|
|
42
|
-
`SELECT name FROM sqlite_master WHERE type = 'index' AND name IN ('idx_channel_agents_token','idx_channel_agents_channel','idx_messages_queue','idx_tasks_channel','idx_tasks_assignee')`,
|
|
43
|
-
)
|
|
44
|
-
.all()
|
|
45
|
-
check('5 个索引全部创建', indexes.length === 5)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function testChannelCrud(db: DatabaseSync): void {
|
|
49
|
-
console.log('\n--- 2. channel CRUD ---')
|
|
50
|
-
const repo = createChannelRepo(db)
|
|
51
|
-
|
|
52
|
-
const created = repo.create({ name: '频道A' })
|
|
53
|
-
check('create 生成 id 且 description 默认空串', created.id.length > 0 && created.description === '' && created.enabled === 1)
|
|
54
|
-
check('create 支持 description', repo.create({ name: '频道B', description: '描述B' }).description === '描述B')
|
|
55
|
-
|
|
56
|
-
check('list 返回 2 条', repo.list().length === 2)
|
|
57
|
-
check('findById 命中', repo.findById(created.id)?.name === '频道A')
|
|
58
|
-
check('findById 未命中返回 undefined', repo.findById('nope') === undefined)
|
|
59
|
-
|
|
60
|
-
const updated = repo.update(created.id, { name: '频道A改', description: '新描述' })
|
|
61
|
-
check('update 修改 name/description', updated?.name === '频道A改' && updated?.description === '新描述')
|
|
62
|
-
check('update 未命中返回 undefined', repo.update('nope', { name: 'x' }) === undefined)
|
|
63
|
-
check('update 保留未提供字段', repo.findById(created.id)?.leadAgentId === null)
|
|
64
|
-
|
|
65
|
-
repo.remove(created.id)
|
|
66
|
-
check('remove 后 findById 未命中', repo.findById(created.id) === undefined)
|
|
67
|
-
check('remove 后 list 长度 1', repo.list().length === 1)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function testAgentCrud(db: DatabaseSync): void {
|
|
71
|
-
console.log('\n--- 3. agent 定义 CRUD(全局) ---')
|
|
72
|
-
const repo = createAgentRepo(db)
|
|
73
|
-
|
|
74
|
-
// v10:初始化已种入内置公共模板(owner NULL);断言按"相对基线"计数,避免与种子数据耦合
|
|
75
|
-
const baseline = repo.list().length
|
|
76
|
-
|
|
77
|
-
const lead = repo.create({ name: '主理人', harness: 'mock' })
|
|
78
|
-
check('create 生成 id', lead.id.length > 0)
|
|
79
|
-
check('create 默认 configJson {}', lead.configJson === '{}')
|
|
80
|
-
|
|
81
|
-
const worker = repo.create({ name: '工人', harness: 'claude', config: { model: 'x' } })
|
|
82
|
-
check('create config 序列化为 JSON', worker.configJson === JSON.stringify({ model: 'x' }))
|
|
83
|
-
|
|
84
|
-
check('list 返回基线+2 条', repo.list().length === baseline + 2, `n=${repo.list().length}`)
|
|
85
|
-
check('findById 命中', repo.findById(lead.id)?.name === '主理人')
|
|
86
|
-
|
|
87
|
-
const updated = repo.update(worker.id, { name: '工人改', config: { model: 'y' } })
|
|
88
|
-
check('update 修改 name/config', updated?.name === '工人改' && updated?.configJson === JSON.stringify({ model: 'y' }))
|
|
89
|
-
|
|
90
|
-
repo.remove(worker.id)
|
|
91
|
-
check('remove 后 list 基线+1', repo.list().length === baseline + 1)
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function testChannelAgentCrud(db: DatabaseSync): void {
|
|
95
|
-
console.log('\n--- 4. channel_agents 实例 CRUD(克隆)+ findByToken ---')
|
|
96
|
-
const channels = createChannelRepo(db)
|
|
97
|
-
const ch = channels.create({ name: '主频道' })
|
|
98
|
-
const agents = createAgentRepo(db)
|
|
99
|
-
const a = agents.create({ name: '主理人', harness: 'mock', config: { k: 'v' } })
|
|
100
|
-
const b = agents.create({ name: '工人', harness: 'claude' })
|
|
101
|
-
|
|
102
|
-
const repo = createChannelAgentRepo(db)
|
|
103
|
-
const mA = repo.create({ channelId: ch.id, templateId: a.id, name: a.name, harness: a.harness, config: { k: 'v' }, role: 'lead' })
|
|
104
|
-
check('create 生成独立身份 id', mA.id.length > 0 && mA.id !== a.id)
|
|
105
|
-
check('create 自动生成实例 token', mA.token.length > 0)
|
|
106
|
-
check('create 复制模板 name/harness/config', mA.name === '主理人' && mA.harness === 'mock' && mA.configJson === JSON.stringify({ k: 'v' }))
|
|
107
|
-
check('create 记录 template_id', mA.templateId === a.id)
|
|
108
|
-
|
|
109
|
-
const mB = repo.create({ channelId: ch.id, templateId: b.id, name: b.name, harness: b.harness, role: 'worker', token: 'tok-1' })
|
|
110
|
-
check('create 接受显式 token', mB.token === 'tok-1')
|
|
111
|
-
check('同模板可克隆多实例且 id 不同', mB.id !== mA.id)
|
|
112
|
-
|
|
113
|
-
check('listByChannel 返回 2 条', repo.listByChannel(ch.id).length === 2)
|
|
114
|
-
check('findById 命中实例', repo.findById(mA.id)?.role === 'lead')
|
|
115
|
-
check('findByChannelAgent 命中', repo.findByChannelAgent(ch.id, mA.id)?.role === 'lead')
|
|
116
|
-
check('findByToken 命中(实例级)', repo.findByToken('tok-1')?.id === mB.id)
|
|
117
|
-
check('findByToken 未命中返回 undefined', repo.findByToken('nope') === undefined)
|
|
118
|
-
check('listByTemplate 返回该模板全部实例', repo.listByTemplate(a.id).length === 1)
|
|
119
|
-
|
|
120
|
-
const updated = repo.update(mB.id, { role: 'lead', name: '工人改' })
|
|
121
|
-
check('update 修改 role/name', updated?.role === 'lead' && updated?.name === '工人改')
|
|
122
|
-
|
|
123
|
-
repo.remove(ch.id, mB.id)
|
|
124
|
-
check('remove 后 listByChannel 长度 1', repo.listByChannel(ch.id).length === 1)
|
|
125
|
-
check('remove 后 findByToken 未命中', repo.findByToken('tok-1') === undefined)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function testMessageStateMachine(db: DatabaseSync): void {
|
|
129
|
-
console.log('\n--- 5. message 状态机 + channel 作用域 ---')
|
|
130
|
-
const channels = createChannelRepo(db)
|
|
131
|
-
const ch = channels.create({ name: '消息频道' })
|
|
132
|
-
const repo = createMessageRepo(db)
|
|
133
|
-
|
|
134
|
-
const m1 = repo.create({ channelId: ch.id, toAgentId: 'agent-a', role: 'ROLE_AGENT', parts: [{ text: 'hi' }] })
|
|
135
|
-
const m2 = repo.create({ channelId: ch.id, toAgentId: 'agent-a', role: 'ROLE_AGENT', parts: [{ text: 'yo' }] })
|
|
136
|
-
repo.create({ channelId: ch.id, toAgentId: 'agent-b', role: 'ROLE_USER', parts: [{ text: 'other' }], metadata: { k: 1 } })
|
|
137
|
-
|
|
138
|
-
check('listPendingByChannelAgent 初始 2 条', repo.listPendingByChannelAgent(ch.id, 'agent-a').length === 2)
|
|
139
|
-
check('partsJson 存 JSON.stringify', m1.partsJson === JSON.stringify([{ text: 'hi' }]))
|
|
140
|
-
check('parseJson 读回 parts', parseJson<{ text: string }[]>(m1.partsJson, []).length === 1)
|
|
141
|
-
check('parseJson 解析失败返回默认值', parseJson('{broken', []).length === 0)
|
|
142
|
-
|
|
143
|
-
check('claim 首次认领成功', repo.claim(m1.id) === true)
|
|
144
|
-
check('claim 后 pending 减为 1', repo.listPendingByChannelAgent(ch.id, 'agent-a').length === 1)
|
|
145
|
-
check('claim 重复认领失败(唯一所有权)', repo.claim(m1.id) === false)
|
|
146
|
-
const consuming = db.prepare(`SELECT state FROM messages WHERE id = ?`).get(m1.id) as { state: string }
|
|
147
|
-
check('m1 state 变为 consuming', consuming.state === 'consuming')
|
|
148
|
-
|
|
149
|
-
repo.markConsumed(m1.id)
|
|
150
|
-
const consumed = db.prepare(`SELECT state, consumed_at FROM messages WHERE id = ?`).get(m1.id) as { state: string, consumed_at: string | null }
|
|
151
|
-
check('markConsumed 后 state=consumed 且 consumed_at 非空', consumed.state === 'consumed' && consumed.consumed_at != null)
|
|
152
|
-
|
|
153
|
-
repo.claim(m2.id)
|
|
154
|
-
repo.resetConsuming()
|
|
155
|
-
const m2After = db.prepare(`SELECT state FROM messages WHERE id = ?`).get(m2.id) as { state: string }
|
|
156
|
-
check('resetConsuming 将 consuming 重置为 pending', m2After.state === 'pending')
|
|
157
|
-
check('resetConsuming 后 pending 恢复为 1', repo.listPendingByChannelAgent(ch.id, 'agent-a').length === 1)
|
|
158
|
-
|
|
159
|
-
check('listRecentByChannel 返回 3 条', repo.listRecentByChannel(ch.id, 10).length === 3)
|
|
160
|
-
check('listRecentByChannel 尊重 limit', repo.listRecentByChannel(ch.id, 2).length === 2)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function testSubscriptionDedup(db: DatabaseSync): void {
|
|
164
|
-
console.log('\n--- 6. subscription 复合主键去重(channel 隔离) ---')
|
|
165
|
-
const channels = createChannelRepo(db)
|
|
166
|
-
const ch = channels.create({ name: '订阅频道' })
|
|
167
|
-
const agents = createAgentRepo(db)
|
|
168
|
-
const channelAgents = createChannelAgentRepo(db)
|
|
169
|
-
const tplA = agents.create({ name: 'A', harness: 'mock' })
|
|
170
|
-
const tplB = agents.create({ name: 'B', harness: 'mock' })
|
|
171
|
-
const tplC = agents.create({ name: 'C', harness: 'mock' })
|
|
172
|
-
const a = channelAgents.create({ channelId: ch.id, templateId: tplA.id, name: tplA.name, harness: tplA.harness, role: 'worker' })
|
|
173
|
-
const b = channelAgents.create({ channelId: ch.id, templateId: tplB.id, name: tplB.name, harness: tplB.harness, role: 'worker' })
|
|
174
|
-
const c = channelAgents.create({ channelId: ch.id, templateId: tplC.id, name: tplC.name, harness: tplC.harness, role: 'worker' })
|
|
175
|
-
|
|
176
|
-
const repo = createSubscriptionRepo(db)
|
|
177
|
-
repo.add(ch.id, a.id, b.id)
|
|
178
|
-
repo.add(ch.id, a.id, b.id)
|
|
179
|
-
check('重复 add 同一对自动去重', repo.listByAgent(ch.id, a.id).length === 1)
|
|
180
|
-
|
|
181
|
-
repo.add(ch.id, a.id, c.id)
|
|
182
|
-
check('listByAgent 返回 2 个目标', repo.listByAgent(ch.id, a.id).length === 2)
|
|
183
|
-
check('listByTarget(b) 返回 1 个订阅者', repo.listByTarget(ch.id, b.id).length === 1)
|
|
184
|
-
|
|
185
|
-
repo.remove(ch.id, a.id, b.id)
|
|
186
|
-
check('remove 后 listByAgent 长度 1', repo.listByAgent(ch.id, a.id).length === 1)
|
|
187
|
-
check('remove 后 listByTarget(b) 为空', repo.listByTarget(ch.id, b.id).length === 0)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function testTaskCrud(db: DatabaseSync): void {
|
|
191
|
-
console.log('\n--- 7. task CRUD + listNonTerminal ---')
|
|
192
|
-
const channels = createChannelRepo(db)
|
|
193
|
-
const ch = channels.create({ name: '任务频道' })
|
|
194
|
-
const agents = createAgentRepo(db)
|
|
195
|
-
const lead = agents.create({ name: '主理人', harness: 'mock' })
|
|
196
|
-
const worker = agents.create({ name: '工人', harness: 'mock' })
|
|
197
|
-
|
|
198
|
-
const repo = createTaskRepo(db)
|
|
199
|
-
const parent = repo.create({ channelId: ch.id, assigneeId: lead.id, creatorId: 'user-1', title: '主任务' })
|
|
200
|
-
check('create 默认 SUBMITTED/progress 0/retryCount 0', parent.state === 'SUBMITTED' && parent.progress === 0 && parent.retryCount === 0)
|
|
201
|
-
check('create artifactsJson 默认 []', parent.artifactsJson === '[]')
|
|
202
|
-
|
|
203
|
-
const child = repo.create({ channelId: ch.id, parentId: parent.id, assigneeId: worker.id, creatorId: lead.id, title: '子任务', state: 'ASSIGNED', artifacts: [{ artifactId: 'a1' }] })
|
|
204
|
-
check('create 子任务 parentId/artifacts 序列化', child.parentId === parent.id && child.artifactsJson === JSON.stringify([{ artifactId: 'a1' }]))
|
|
205
|
-
|
|
206
|
-
check('listByChannel 返回 2 条', repo.listByChannel(ch.id).length === 2)
|
|
207
|
-
check('listByAssignee(worker) 返回 1 条', repo.listByAssignee(worker.id).length === 1)
|
|
208
|
-
check('listNonTerminal 初始 2 条', repo.listNonTerminal().length === 2)
|
|
209
|
-
|
|
210
|
-
const updated = repo.update(child.id, { state: 'COMPLETED', progress: 100 })
|
|
211
|
-
check('update 修改 state/progress', updated?.state === 'COMPLETED' && updated?.progress === 100)
|
|
212
|
-
check('listNonTerminal 排除终态', repo.listNonTerminal().length === 1)
|
|
213
|
-
check('listNonTerminal 只剩父任务', repo.listNonTerminal()[0]?.id === parent.id)
|
|
214
|
-
|
|
215
|
-
check('findById 命中', repo.findById(parent.id)?.title === '主任务')
|
|
216
|
-
check('findById 未命中返回 undefined', repo.findById('nope') === undefined)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/** 旧库(v3 前)FK 缺失迁移:重建 tasks/subscriptions 外键 + 清除孤儿行 */
|
|
220
|
-
function testFkMigration(): void {
|
|
221
|
-
console.log('\n--- 8. 旧库 FK 缺失迁移 ---')
|
|
222
|
-
// 手工构造无外键的旧版 tasks/subscriptions + 孤儿行
|
|
223
|
-
const db = new DatabaseSync(':memory:')
|
|
224
|
-
db.exec(`PRAGMA foreign_keys = ON;
|
|
225
|
-
CREATE TABLE channels (id TEXT PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', lead_agent_id TEXT, workspace TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
226
|
-
CREATE TABLE agents (id TEXT PRIMARY KEY, name TEXT NOT NULL, harness TEXT NOT NULL, config_json TEXT NOT NULL DEFAULT '{}', enabled INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
227
|
-
CREATE TABLE channel_agents (id TEXT PRIMARY KEY, channel_id TEXT NOT NULL, template_id TEXT, name TEXT NOT NULL, harness TEXT NOT NULL, config_json TEXT NOT NULL DEFAULT '{}', role TEXT NOT NULL DEFAULT 'worker', token TEXT NOT NULL, enabled INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL);
|
|
228
|
-
CREATE TABLE messages (id TEXT PRIMARY KEY, channel_id TEXT NOT NULL, task_id TEXT, from_agent_id TEXT, to_agent_id TEXT, role TEXT NOT NULL, parts_json TEXT NOT NULL, metadata_json TEXT NOT NULL DEFAULT '{}', state TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL, consumed_at TEXT);
|
|
229
|
-
CREATE TABLE subscriptions (channel_id TEXT NOT NULL, agent_id TEXT NOT NULL, target_agent_id TEXT NOT NULL, created_at TEXT NOT NULL, PRIMARY KEY (channel_id, agent_id, target_agent_id));
|
|
230
|
-
CREATE TABLE tasks (id TEXT PRIMARY KEY, channel_id TEXT NOT NULL, parent_id TEXT, assignee_id TEXT NOT NULL, creator_id TEXT, title TEXT NOT NULL, description TEXT, state TEXT NOT NULL, progress INTEGER NOT NULL DEFAULT 0, retry_count INTEGER NOT NULL DEFAULT 0, artifacts_json TEXT NOT NULL DEFAULT '[]', history_json TEXT NOT NULL DEFAULT '[]', created_at TEXT NOT NULL, updated_at TEXT NOT NULL);`)
|
|
231
|
-
const now = new Date().toISOString()
|
|
232
|
-
db.prepare(`INSERT INTO channels (id, name, created_at, updated_at) VALUES ('ch1', '正常频道', ?, ?)`).run(now, now)
|
|
233
|
-
db.prepare(`INSERT INTO channel_agents (id, channel_id, name, harness, role, token, created_at, updated_at) VALUES ('ag1', 'ch1', 'a', 'mock', 'lead', 'tok1', ?, ?)`).run(now, now)
|
|
234
|
-
db.prepare(`INSERT INTO tasks (id, channel_id, assignee_id, title, state, created_at, updated_at) VALUES ('t1', 'ch1', 'ag1', '正常任务', 'COMPLETED', ?, ?)`).run(now, now)
|
|
235
|
-
// 孤儿:channel 已删,任务/订阅残留
|
|
236
|
-
db.prepare(`INSERT INTO tasks (id, channel_id, assignee_id, title, state, created_at, updated_at) VALUES ('t2', 'ghost-ch', 'ag1', '孤儿任务', 'COMPLETED', ?, ?)`).run(now, now)
|
|
237
|
-
db.prepare(`INSERT INTO subscriptions (channel_id, agent_id, target_agent_id, created_at) VALUES ('ghost-ch', 'ag1', 'ag1', ?)`).run(now)
|
|
238
|
-
|
|
239
|
-
try {
|
|
240
|
-
initWorkshopDb(db)
|
|
241
|
-
const fkTasks = db.prepare('PRAGMA foreign_key_list(tasks)').all() as Array<{ from: string, table: string, on_delete: string }>
|
|
242
|
-
const fkSubs = db.prepare('PRAGMA foreign_key_list(subscriptions)').all() as Array<{ from: string, table: string, on_delete: string }>
|
|
243
|
-
check('迁移后 tasks 带 channel 级联外键', fkTasks.some(f => f.from === 'channel_id' && f.table === 'channels' && f.on_delete === 'CASCADE'))
|
|
244
|
-
check('迁移后 subscriptions 带双级联外键', fkSubs.some(f => f.from === 'channel_id' && f.table === 'channels' && f.on_delete === 'CASCADE') && fkSubs.some(f => f.from === 'agent_id' && f.table === 'channel_agents' && f.on_delete === 'CASCADE'))
|
|
245
|
-
const t2 = db.prepare(`SELECT COUNT(*) c FROM tasks WHERE id = 't2'`).get() as { c: number }
|
|
246
|
-
const s2 = db.prepare(`SELECT COUNT(*) c FROM subscriptions WHERE channel_id = 'ghost-ch'`).get() as { c: number }
|
|
247
|
-
check('孤儿任务/订阅已清除', t2.c === 0 && s2.c === 0)
|
|
248
|
-
const t1 = db.prepare(`SELECT COUNT(*) c FROM tasks WHERE id = 't1'`).get() as { c: number }
|
|
249
|
-
check('正常数据保留', t1.c === 1)
|
|
250
|
-
// 级联删除验证
|
|
251
|
-
db.prepare(`DELETE FROM channels WHERE id = 'ch1'`).run()
|
|
252
|
-
const cascade = db.prepare(`SELECT COUNT(*) c FROM tasks WHERE channel_id = 'ch1'`).get() as { c: number }
|
|
253
|
-
check('删除 channel 级联删任务', cascade.c === 0)
|
|
254
|
-
}
|
|
255
|
-
finally {
|
|
256
|
-
db.close()
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
function main(): void {
|
|
261
|
-
const db = openWorkshopDb(':memory:')
|
|
262
|
-
try {
|
|
263
|
-
testSchema(db)
|
|
264
|
-
testChannelCrud(db)
|
|
265
|
-
testAgentCrud(db)
|
|
266
|
-
testChannelAgentCrud(db)
|
|
267
|
-
testMessageStateMachine(db)
|
|
268
|
-
testSubscriptionDedup(db)
|
|
269
|
-
testTaskCrud(db)
|
|
270
|
-
}
|
|
271
|
-
finally {
|
|
272
|
-
db.close()
|
|
273
|
-
}
|
|
274
|
-
testFkMigration()
|
|
275
|
-
|
|
276
|
-
console.log(`\n${failures === 0 ? 'ALL PASS' : `${failures} FAILED`}`)
|
|
277
|
-
process.exit(failures === 0 ? 0 : 1)
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
main()
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 记忆衰减清理测试:episodic 过期删除 / semantic+team 豁免 / 容量淘汰(高分保留)/
|
|
3
|
-
* FTS 联动清理 / 幂等 + Task 9 review 必修回归(UNIQUE 含 channel_id 的跨通道团队去重)。
|
|
4
|
-
* 运行: npx tsx scripts/test-memory-maintenance.ts
|
|
5
|
-
*/
|
|
6
|
-
import type { DatabaseSync } from 'node:sqlite'
|
|
7
|
-
import { openWorkshopDb } from '../server/services/workshop/db/database'
|
|
8
|
-
import { createMemoryRepo, TEAM_AGENT_ID } from '../server/services/workshop/db/memory.repo'
|
|
9
|
-
import { runMemoryMaintenance, segmentCJK } from '../server/services/workshop/runtime/memory'
|
|
10
|
-
import { createChannelRepo } from '../server/services/workshop/db/channel.repo'
|
|
11
|
-
import { createAgentRepo } from '../server/services/workshop/db/agent.repo'
|
|
12
|
-
import { createTeamRepo } from '../server/services/workshop/db/team.repo'
|
|
13
|
-
import { createTeamMemberRepo } from '../server/services/workshop/db/team-member.repo'
|
|
14
|
-
import { createChannelAgentRepo } from '../server/services/workshop/db/channel-agent.repo'
|
|
15
|
-
import { createMessageRepo } from '../server/services/workshop/db/message.repo'
|
|
16
|
-
import { createTaskRepo } from '../server/services/workshop/db/task.repo'
|
|
17
|
-
import { createSubscriptionRepo } from '../server/services/workshop/db/subscription.repo'
|
|
18
|
-
import { createAgentChannelManager } from '../server/services/workshop/runtime/manager'
|
|
19
|
-
|
|
20
|
-
let failures = 0
|
|
21
|
-
function check(name: string, ok: boolean, detail = ''): void {
|
|
22
|
-
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` (${detail})` : ''}`)
|
|
23
|
-
if (!ok) failures++
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const db: DatabaseSync = openWorkshopDb(':memory:')
|
|
27
|
-
const repo = createMemoryRepo(db)
|
|
28
|
-
const seg = (s: string): string => segmentCJK(s)
|
|
29
|
-
|
|
30
|
-
/** 伪造行龄:created_at/last_accessed_at 一并回拨 days 天 */
|
|
31
|
-
const age = (days: number, id: string): void => {
|
|
32
|
-
const iso = new Date(Date.now() - days * 86_400_000).toISOString()
|
|
33
|
-
db.prepare('UPDATE agent_memories SET created_at = ?, last_accessed_at = ? WHERE id = ?').run(iso, iso, id)
|
|
34
|
-
}
|
|
35
|
-
const rowId = (title: string): string => repo.listByAgent('aged', 100).find(r => r.title === title)!.id
|
|
36
|
-
|
|
37
|
-
// ═══════════ 过期删除(仅 episodic;semantic / team 豁免)═══════════
|
|
38
|
-
console.log('\n--- 过期删除(episodic-only)---')
|
|
39
|
-
repo.upsert({ channelId: 'ch1', agentId: 'aged', kind: 'episodic-task', title: '古老任务', titleFts: seg('古老任务'), content: seg('ghosttoken 古老的登录实现'), importance: 0.8, taskId: 'old-1', dedupKey: 'task:old-1' })
|
|
40
|
-
repo.upsert({ channelId: 'ch1', agentId: 'aged', kind: 'semantic', title: '古老规范', titleFts: seg('古老规范'), content: seg('人工策展永不衰减'), importance: 0.9, taskId: null, dedupKey: 'manual:old-sem' })
|
|
41
|
-
repo.upsert({ channelId: 'ch1', agentId: TEAM_AGENT_ID, kind: 'semantic', title: '古老团队守则', titleFts: seg('古老团队守则'), content: seg('团队行永不衰减'), importance: 0.9, taskId: null, dedupKey: 'team:old' })
|
|
42
|
-
age(200, rowId('古老任务'))
|
|
43
|
-
age(200, rowId('古老规范'))
|
|
44
|
-
age(200, repo.listByAgent(TEAM_AGENT_ID, 100).find(r => r.title === '古老团队守则')!.id)
|
|
45
|
-
|
|
46
|
-
// ═══════════ 容量淘汰(600 → 500,保留 effectiveScore 高者)═══════════
|
|
47
|
-
console.log('\n--- 容量淘汰(仅 episodic)---')
|
|
48
|
-
for (let i = 0; i < 600; i++) {
|
|
49
|
-
repo.upsert({
|
|
50
|
-
channelId: 'ch1', agentId: 'bulk', kind: i % 2 ? 'episodic-task' : 'episodic-peer',
|
|
51
|
-
title: `批量${i}`, titleFts: seg(`批量${i}`), content: seg(`批量记忆${i}`),
|
|
52
|
-
importance: i < 50 ? 0.9 : 0.3, taskId: `bulk-${i}`, dedupKey: `task:bulk-${i}`,
|
|
53
|
-
})
|
|
54
|
-
}
|
|
55
|
-
repo.upsert({ channelId: 'ch1', agentId: 'bulk', kind: 'semantic', title: 'bulk 策展行', titleFts: seg('bulk 策展行'), content: seg('策展行不参与淘汰'), importance: 0.5, taskId: null, dedupKey: 'manual:bulk-sem' })
|
|
56
|
-
|
|
57
|
-
const res1 = runMemoryMaintenance(repo, { expireDays: 180, cap: 500 })
|
|
58
|
-
check('过期 episodic 被删除(deletedExpired=1)', res1.deletedExpired === 1, JSON.stringify(res1))
|
|
59
|
-
check('semantic 200 天豁免保留', repo.listByAgent('aged', 100).some(r => r.title === '古老规范'))
|
|
60
|
-
check('team 200 天豁免保留', repo.listByAgent(TEAM_AGENT_ID, 100).some(r => r.title === '古老团队守则'))
|
|
61
|
-
check('删除后 FTS 不再命中', repo.search('aged', 'ghosttoken', 5).length === 0)
|
|
62
|
-
check('容量淘汰至 cap(evicted=100)', res1.evicted === 100, JSON.stringify(res1))
|
|
63
|
-
const bulkRows = repo.listByAgent('bulk', 1000)
|
|
64
|
-
check('bulk 恰好剩 500 episodic + 1 semantic', bulkRows.length === 501 && bulkRows.filter(r => r.kind.startsWith('episodic')).length === 500, `len=${bulkRows.length}`)
|
|
65
|
-
check('高分(importance=0.9)全部保留', bulkRows.filter(r => r.importance === 0.9).length === 50)
|
|
66
|
-
check('孤儿 vec 清理未启用 vec 时为 0', res1.cleanedVec === 0)
|
|
67
|
-
|
|
68
|
-
const res2 = runMemoryMaintenance(repo, { expireDays: 180, cap: 500 })
|
|
69
|
-
check('维护幂等(第二遍 deletedExpired=0 / evicted=0)', res2.deletedExpired === 0 && res2.evicted === 0, JSON.stringify(res2))
|
|
70
|
-
|
|
71
|
-
// ═══════════ Task 9 必修回归:UNIQUE(agent_id, dedup_key, channel_id)═══════════
|
|
72
|
-
console.log('\n--- 跨通道团队去重(Part A)---')
|
|
73
|
-
const stubImpl = (): never => {
|
|
74
|
-
throw new Error('impl unused')
|
|
75
|
-
}
|
|
76
|
-
const tmRepos = {
|
|
77
|
-
channels: createChannelRepo(db),
|
|
78
|
-
agents: createAgentRepo(db),
|
|
79
|
-
teams: createTeamRepo(db),
|
|
80
|
-
teamMembers: createTeamMemberRepo(db),
|
|
81
|
-
channelAgents: createChannelAgentRepo(db),
|
|
82
|
-
messages: createMessageRepo(db),
|
|
83
|
-
subscriptions: createSubscriptionRepo(db),
|
|
84
|
-
tasks: createTaskRepo(db),
|
|
85
|
-
memories: repo,
|
|
86
|
-
}
|
|
87
|
-
const teamManager = createAgentChannelManager({ repos: tmRepos, implFactory: stubImpl, db })
|
|
88
|
-
const chA = tmRepos.channels.create({ name: 'ch-a' })
|
|
89
|
-
const chB = tmRepos.channels.create({ name: 'ch-b' })
|
|
90
|
-
const leadA = tmRepos.channelAgents.create({ channelId: chA.id, templateId: null, name: 'lead-a', harness: 'mock', role: 'lead' })
|
|
91
|
-
const leadB = tmRepos.channelAgents.create({ channelId: chB.id, templateId: null, name: 'lead-b', harness: 'mock', role: 'lead' })
|
|
92
|
-
|
|
93
|
-
teamManager.addTeamMemory(chA.id, leadA.id, { title: '规范甲', content: '通道甲的部署约定', dedupKey: 'shared:policy' })
|
|
94
|
-
teamManager.addTeamMemory(chB.id, leadB.id, { title: '规范乙', content: '通道乙的部署约定', dedupKey: 'shared:policy' })
|
|
95
|
-
const teamShared = repo.listByAgentChannel(chA.id, TEAM_AGENT_ID, 10)
|
|
96
|
-
const teamAll = repo.listByAgent(TEAM_AGENT_ID, 100)
|
|
97
|
-
check('跨通道同 dedupKey 团队写各留一行(不互相覆盖)', teamAll.filter(r => r.title === '规范甲' || r.title === '规范乙').length === 2)
|
|
98
|
-
check('listByAgentChannel 只含本通道行', teamShared.length === 1 && teamShared[0].title === '规范甲')
|
|
99
|
-
check('listTeamMemories 只返回本通道行', teamManager.listTeamMemories(chA.id).every(r => r.channelId === chA.id && r.title === '规范甲'))
|
|
100
|
-
const dedupA = repo.findByAgentDedup(chA.id, TEAM_AGENT_ID, 'shared:policy')
|
|
101
|
-
const dedupB = repo.findByAgentDedup(chB.id, TEAM_AGENT_ID, 'shared:policy')
|
|
102
|
-
check('findByAgentDedup 按通道作用域(两行不同)', dedupA !== null && dedupB !== null && dedupA.rowid !== dedupB.rowid)
|
|
103
|
-
check('findByAgentDedup 无匹配通道返回 null', repo.findByAgentDedup('ch-none', TEAM_AGENT_ID, 'shared:policy') === null)
|
|
104
|
-
|
|
105
|
-
// 同通道同 dedupKey 仍幂等刷新(不新增行)
|
|
106
|
-
teamManager.addTeamMemory(chA.id, leadA.id, { title: '规范甲', content: '通道甲的部署约定v2', dedupKey: 'shared:policy' })
|
|
107
|
-
check('同通道同 dedupKey 仍幂等刷新', repo.listByAgentChannel(chA.id, TEAM_AGENT_ID, 10).length === 1)
|
|
108
|
-
|
|
109
|
-
// limit 在本通道内生效(chA 3 行 / chB 2 行,dedupKey 部分重叠)
|
|
110
|
-
teamManager.addTeamMemory(chA.id, leadA.id, { title: '守则1', content: 'x1', dedupKey: 'r:1' })
|
|
111
|
-
teamManager.addTeamMemory(chA.id, leadA.id, { title: '守则2', content: 'x2', dedupKey: 'r:2' })
|
|
112
|
-
teamManager.addTeamMemory(chB.id, leadB.id, { title: '守则1', content: 'y1', dedupKey: 'r:1' })
|
|
113
|
-
check('listTeamMemories limit 作用域为本通道行', teamManager.listTeamMemories(chA.id, 2).length === 2
|
|
114
|
-
&& teamManager.listTeamMemories(chA.id, 2).every(r => r.channelId === chA.id))
|
|
115
|
-
|
|
116
|
-
console.log(`\n${failures === 0 ? 'ALL PASS' : `${failures} FAILURE(S)`}`)
|
|
117
|
-
process.exit(failures === 0 ? 0 : 1)
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agent 多 Channel 复用测试 —— 验证「Agent 模板可复用,放入每个 Channel 克隆出独立身份 id 的实例」。
|
|
3
|
-
*
|
|
4
|
-
* 覆盖:
|
|
5
|
-
* 1. 同一模板放入两个 channel → 克隆出两个独立身份 id 的实例(id 互不相同且不同于模板)
|
|
6
|
-
* 2. role/token 按实例独立
|
|
7
|
-
* 3. 元数据复制:实例复制模板 name/harness/config
|
|
8
|
-
* 4. 独立 AgentRuntime:同一模板的两实例各装配独立运行时
|
|
9
|
-
* 5. mailbox 按 channel 隔离
|
|
10
|
-
* 6. 移除实例 ≠ 删除模板
|
|
11
|
-
* 7. 模板更新不传播到已克隆实例(复制语义),新克隆取新值
|
|
12
|
-
* 8. 删除模板后实例保留
|
|
13
|
-
*
|
|
14
|
-
* 运行: npx tsx scripts/test-agent-multi-channel.ts
|
|
15
|
-
*/
|
|
16
|
-
import type { DatabaseSync } from 'node:sqlite'
|
|
17
|
-
import { openWorkshopDb } from '../server/services/workshop/db/database'
|
|
18
|
-
import { createChannelRepo } from '../server/services/workshop/db/channel.repo'
|
|
19
|
-
import { createAgentRepo } from '../server/services/workshop/db/agent.repo'
|
|
20
|
-
import { createChannelAgentRepo } from '../server/services/workshop/db/channel-agent.repo'
|
|
21
|
-
import { createTaskRepo } from '../server/services/workshop/db/task.repo'
|
|
22
|
-
import { createMemoryRepo } from '../server/services/workshop/db/memory.repo'
|
|
23
|
-
import { createChannelEventRepo } from '../server/services/workshop/db/channel-event.repo'
|
|
24
|
-
import { createTeamRepo } from '../server/services/workshop/db/team.repo'
|
|
25
|
-
import { createTeamMemberRepo } from '../server/services/workshop/db/team-member.repo'
|
|
26
|
-
import { createMessageRepo } from '../server/services/workshop/db/message.repo'
|
|
27
|
-
import { createSubscriptionRepo } from '../server/services/workshop/db/subscription.repo'
|
|
28
|
-
import { createAgentChannelManager } from '../server/services/workshop/runtime/manager'
|
|
29
|
-
import type { AgentChannelManager } from '../server/services/workshop/runtime/manager'
|
|
30
|
-
import { createAgentImpl } from '../server/services/workshop/agents/factory'
|
|
31
|
-
|
|
32
|
-
let failures = 0
|
|
33
|
-
|
|
34
|
-
function check(name: string, ok: boolean, detail = ''): void {
|
|
35
|
-
if (ok) {
|
|
36
|
-
console.log(` PASS ${name}`)
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
failures += 1
|
|
40
|
-
console.log(` FAIL ${name}${detail ? ` — ${detail}` : ''}`)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function makeManager(db: DatabaseSync): AgentChannelManager {
|
|
45
|
-
return createAgentChannelManager({
|
|
46
|
-
repos: {
|
|
47
|
-
channels: createChannelRepo(db),
|
|
48
|
-
agents: createAgentRepo(db),
|
|
49
|
-
channelAgents: createChannelAgentRepo(db),
|
|
50
|
-
messages: createMessageRepo(db),
|
|
51
|
-
subscriptions: createSubscriptionRepo(db),
|
|
52
|
-
tasks: createTaskRepo(db),
|
|
53
|
-
memories: createMemoryRepo(db),
|
|
54
|
-
|
|
55
|
-
channelEvents: createChannelEventRepo(db),
|
|
56
|
-
|
|
57
|
-
teams: createTeamRepo(db),
|
|
58
|
-
|
|
59
|
-
teamMembers: createTeamMemberRepo(db),
|
|
60
|
-
},
|
|
61
|
-
implFactory: createAgentImpl,
|
|
62
|
-
db,
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function main(): Promise<void> {
|
|
67
|
-
console.log('━━━ Agent 模板多 Channel 复用(克隆独立身份)━━━')
|
|
68
|
-
const db = openWorkshopDb(':memory:')
|
|
69
|
-
const messagesRepo = createMessageRepo(db)
|
|
70
|
-
const manager = makeManager(db)
|
|
71
|
-
try {
|
|
72
|
-
// ---- 1. 同一模板克隆出独立身份 id ----
|
|
73
|
-
console.log('\n--- 1. 克隆独立身份 id ---')
|
|
74
|
-
const chA = await manager.createChannel({ name: 'A' })
|
|
75
|
-
const chB = await manager.createChannel({ name: 'B' })
|
|
76
|
-
const tpl = await manager.createAgent({ name: 'shared', harness: 'mock', config: { delayMs: 10 } })
|
|
77
|
-
|
|
78
|
-
const a1 = await manager.addAgentToChannel({ channelId: chA.channelId, agentId: tpl.id, role: 'lead' })
|
|
79
|
-
const b1 = await manager.addAgentToChannel({ channelId: chB.channelId, agentId: tpl.id, role: 'worker' })
|
|
80
|
-
|
|
81
|
-
check('两实例 id 互不相同', a1.id !== b1.id, `a1=${a1.id.slice(0, 8)} b1=${b1.id.slice(0, 8)}`)
|
|
82
|
-
check('实例 id 不同于模板 id', a1.id !== tpl.id && b1.id !== tpl.id)
|
|
83
|
-
check('实例复制模板 name', a1.name === 'shared' && b1.name === 'shared')
|
|
84
|
-
check('实例复制模板 harness', a1.harness === 'mock' && b1.harness === 'mock')
|
|
85
|
-
|
|
86
|
-
// ---- 2. role/token 按实例独立 ----
|
|
87
|
-
console.log('\n--- 2. role/token 按实例独立 ---')
|
|
88
|
-
check('role 按 channel 独立(A=lead)', (await manager.listChannelAgents(chA.channelId))[0]!.role === 'lead')
|
|
89
|
-
check('role 按 channel 独立(B=worker)', (await manager.listChannelAgents(chB.channelId))[0]!.role === 'worker')
|
|
90
|
-
check('实例 token 各不相同', a1.token !== b1.token)
|
|
91
|
-
check('findByToken(A) 解析到 channel A', manager.findByToken(a1.token!)?.channelId === chA.channelId)
|
|
92
|
-
check('findByToken(B) 解析到 channel B', manager.findByToken(b1.token!)?.channelId === chB.channelId)
|
|
93
|
-
|
|
94
|
-
// ---- 3. 模板详情含实例 ----
|
|
95
|
-
console.log('\n--- 3. 模板详情 ---')
|
|
96
|
-
const tplDetail = manager.getAgent(tpl.id)!
|
|
97
|
-
check('模板 instances=2', tplDetail.instances.length === 2, `n=${tplDetail.instances.length}`)
|
|
98
|
-
check('模板 instances 含两实例 id', tplDetail.instances.some(i => i.id === a1.id) && tplDetail.instances.some(i => i.id === b1.id))
|
|
99
|
-
|
|
100
|
-
// ---- 4. 独立 AgentRuntime(同一模板两实例各装配)----
|
|
101
|
-
console.log('\n--- 4. 独立 AgentRuntime ---')
|
|
102
|
-
const chC = await manager.createChannel({ name: 'C' })
|
|
103
|
-
const chD = await manager.createChannel({ name: 'D' })
|
|
104
|
-
const dup = await manager.createAgent({ name: 'dup', harness: 'mock', config: { delayMs: 10 } })
|
|
105
|
-
const c1 = await manager.addAgentToChannel({ channelId: chC.channelId, agentId: dup.id, role: 'lead' })
|
|
106
|
-
const d1 = await manager.addAgentToChannel({ channelId: chD.channelId, agentId: dup.id, role: 'lead' })
|
|
107
|
-
|
|
108
|
-
manager.ensureChannelActive(chC.channelId, { tickMs: 60_000 })
|
|
109
|
-
manager.ensureChannelActive(chD.channelId, { tickMs: 60_000 })
|
|
110
|
-
const wired = manager.runtimeStatus().wiredAgents
|
|
111
|
-
check('两 channel 各装配一个 runtime(共 2)', wired.length === 2, `n=${wired.length}`)
|
|
112
|
-
check('两个 runtime 属于不同实例 id', wired.length === 2 && wired.includes(c1.id) && wired.includes(d1.id))
|
|
113
|
-
|
|
114
|
-
await manager.unloadAgent(chC.channelId, c1.id)
|
|
115
|
-
check('卸载 C 实例不影响 D 实例', manager.runtimeStatus().wiredAgents.length === 1)
|
|
116
|
-
|
|
117
|
-
// ---- 5. mailbox channel 隔离 ----
|
|
118
|
-
console.log('\n--- 5. mailbox channel 隔离 ---')
|
|
119
|
-
// 溯源守卫(v8):消息必须携带可追溯发送人(本 channel 成员或人类 fromLabel)
|
|
120
|
-
await manager.sendImmediateMessage({ channelId: chA.channelId, toAgentId: a1.id, parts: [{ text: 'hi-A' }], fromLabel: 'e2e-test' })
|
|
121
|
-
const recentA = messagesRepo.listRecentByChannel(chA.channelId, 10)
|
|
122
|
-
const recentB = messagesRepo.listRecentByChannel(chB.channelId, 10)
|
|
123
|
-
check('消息只落在 channel A', recentA.length === 1 && recentA[0]!.toAgentId === a1.id, `n=${recentA.length}`)
|
|
124
|
-
check('channel B 无该消息', recentB.length === 0, `n=${recentB.length}`)
|
|
125
|
-
|
|
126
|
-
// ---- 6. 移除实例 ≠ 删除模板 ----
|
|
127
|
-
console.log('\n--- 6. 移除实例 vs 删除模板 ---')
|
|
128
|
-
await manager.removeAgentFromChannel(chB.channelId, b1.id)
|
|
129
|
-
check('移除 B 实例后模板仍存在', manager.getAgent(tpl.id) !== undefined)
|
|
130
|
-
check('A 实例保留', (await manager.listChannelAgents(chA.channelId)).some(a => a.id === a1.id))
|
|
131
|
-
check('B 实例已移除', (await manager.listChannelAgents(chB.channelId)).length === 0)
|
|
132
|
-
check('移除后模板 instances=1', manager.getAgent(tpl.id)?.instances.length === 1)
|
|
133
|
-
|
|
134
|
-
// ---- 7. 模板更新不传播(复制语义)----
|
|
135
|
-
console.log('\n--- 7. 模板更新不传播 ---')
|
|
136
|
-
await manager.updateAgent(tpl.id, { name: 'renamed' })
|
|
137
|
-
check('更新后已克隆实例名不变', manager.getChannelAgent(a1.id)?.name === 'shared')
|
|
138
|
-
const chE = await manager.createChannel({ name: 'E' })
|
|
139
|
-
const e1 = await manager.addAgentToChannel({ channelId: chE.channelId, agentId: tpl.id, role: 'worker' })
|
|
140
|
-
check('新克隆取新模板名', e1.name === 'renamed')
|
|
141
|
-
|
|
142
|
-
// ---- 8. 删除模板后实例保留 ----
|
|
143
|
-
console.log('\n--- 8. 删除模板后实例保留 ---')
|
|
144
|
-
await manager.removeAgent(tpl.id)
|
|
145
|
-
check('删除模板后 getAgent 为 undefined', manager.getAgent(tpl.id) === undefined)
|
|
146
|
-
check('A 实例仍存在', manager.getChannelAgent(a1.id) !== undefined)
|
|
147
|
-
}
|
|
148
|
-
finally {
|
|
149
|
-
await manager.shutdown()
|
|
150
|
-
db.close()
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
console.log(`\n${failures === 0 ? 'ALL PASS' : `${failures} FAILED`}`)
|
|
154
|
-
process.exit(failures === 0 ? 0 : 1)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
main().catch((e) => {
|
|
158
|
-
console.error(e)
|
|
159
|
-
process.exit(1)
|
|
160
|
-
})
|