agentworkshop 0.7.38 → 0.7.40
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/.AgentWorkShop/prompts/aml-mission-default.md +20 -20
- package/.AgentWorkShop/prompts/aml-playbook.md +49 -49
- package/.AgentWorkShop/prompts/host-tools.json +1293 -1238
- package/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -10
- package/.output/.AgentWorkShop/prompts/aml-mission-default.md +20 -20
- package/.output/.AgentWorkShop/prompts/aml-playbook.md +49 -49
- package/.output/.AgentWorkShop/prompts/host-tools.json +1293 -1238
- package/.output/.AgentWorkShop/prompts/peer-reply-cross-channel.md +10 -10
- package/.output/nitro.json +1 -1
- package/.output/public/_nuxt/88GOowgO2.js +1 -0
- package/.output/public/_nuxt/{BNT8gYU92.js → BC9wjTJK2.js} +2 -2
- package/.output/public/_nuxt/BOt2SP4z.js +1 -0
- package/.output/public/_nuxt/BP-NyKVG.js +18 -0
- package/.output/public/_nuxt/{HBLJ-wei.js → BXxH0UQE.js} +1 -1
- package/.output/public/_nuxt/{zIGh7R49.js → BneN7fbu.js} +1 -1
- package/.output/public/_nuxt/BsROmY89.js +1 -0
- package/.output/public/_nuxt/{C_OLzD09.js → C4oW1HsE.js} +1 -1
- package/.output/public/_nuxt/CFxtLwjp.js +1 -0
- package/.output/public/_nuxt/{Cec0Ua3F.js → CS50LB_l.js} +1 -1
- package/.output/public/_nuxt/C_iYD-EB.js +1 -0
- package/.output/public/_nuxt/{DAe0h2MB.js → ClILjvWH.js} +1 -1
- package/.output/public/_nuxt/CssgYAAL2.js +1 -0
- package/.output/public/_nuxt/{CY9LbPCI.js → CzOCSGLe.js} +1 -1
- package/.output/public/_nuxt/D8qvkfyY.js +1 -0
- package/.output/public/_nuxt/DCn7QZ5N.js +2 -0
- package/.output/public/_nuxt/DGIuvyRN2.js +1 -0
- package/.output/public/_nuxt/{CojCTLKD.js → DGj-WEBg.js} +1 -1
- package/.output/public/_nuxt/{DDShH71m.js → DS8qj8EL.js} +1 -1
- package/.output/public/_nuxt/DTFCy6h-.js +1 -0
- package/.output/public/_nuxt/{DrYV5i68.js → DUjW5Yq5.js} +1 -1
- package/.output/public/_nuxt/DZshyNLa.js +1 -0
- package/.output/public/_nuxt/{45zuyAso.js → D_OTvQza.js} +1 -1
- package/.output/public/_nuxt/DcsCcxp4.js +1 -0
- package/.output/public/_nuxt/{DUrecWPx.js → Dihnimlk.js} +2 -2
- package/.output/public/_nuxt/Dqw1lpr-.js +1 -0
- package/.output/public/_nuxt/{BG8sCcwx.js → Dr9v6C0f.js} +9 -9
- package/.output/public/_nuxt/{8NMOXZOV.js → Du9ZJtnj.js} +1 -1
- package/.output/public/_nuxt/TownView.f-YOdXRs.css +1 -0
- package/.output/public/_nuxt/_id_.CM4bIgKY.css +1 -0
- package/.output/public/_nuxt/_id_.bNV69Dtf.css +1 -0
- package/.output/public/_nuxt/_wsId_.B4WiqkIS.css +1 -0
- package/.output/public/_nuxt/agents.ZD0rVdHE.css +1 -0
- package/.output/public/_nuxt/aml.BN8ZEW4h.css +1 -0
- package/.output/public/_nuxt/builds/latest.json +1 -1
- package/.output/public/_nuxt/builds/meta/f4707884-7cf4-4a31-80d2-e3d72b73745f.json +1 -0
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +1 -0
- package/.output/public/_nuxt/daq.B0D3dPAc.css +1 -0
- package/.output/public/_nuxt/dcw.8Y7FqCVN.css +1 -0
- package/.output/public/_nuxt/default.J32SgqFY.css +1 -0
- package/.output/public/_nuxt/entry.bdm0tv6M.css +1 -0
- package/.output/public/_nuxt/logs.BpKzmKsh.css +1 -0
- package/.output/public/_nuxt/monitor.D9pe7qN6.css +1 -0
- package/.output/public/_nuxt/pages.D7aSDlUI.css +1 -0
- package/.output/public/_nuxt/permissions.DwTVlFe6.css +1 -0
- package/.output/public/_nuxt/plugins.sb4nDGqr.css +1 -0
- package/.output/public/_nuxt/{Cbq-mryP.js → qr6R6Ha1.js} +1 -1
- package/.output/public/_nuxt/settings.BTgR-NiE.css +1 -0
- package/.output/public/_nuxt/teams.CxnxKVVs.css +1 -0
- package/.output/public/_nuxt/tokens.BFY7qv4f.css +1 -0
- package/.output/public/_nuxt/{town.DKIAhKnx.css → town.BBXZmeoP.css} +1 -1
- package/.output/public/_nuxt/users.DLgZ1D0h.css +1 -0
- package/.output/public/_nuxt/workshop.CR_QzeAN.css +1 -0
- package/.output/public/_nuxt/{BzHEilTt.js → xh3_vYcu.js} +1 -1
- package/.output/public/_nuxt/yF0aYMF1.js +4 -0
- package/.output/server/chunks/_/entity.mjs.map +1 -1
- package/.output/server/chunks/_/nitro.mjs +2244 -1464
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{TownView-jVazYOjN.mjs → TownView-BuLAsmpR.mjs} +288 -206
- package/.output/server/chunks/build/TownView-BuLAsmpR.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-CfVosNg3.mjs → _id_-BU1wfOID.mjs} +138 -138
- package/.output/server/chunks/build/{_id_-CfVosNg3.mjs.map → _id_-BU1wfOID.mjs.map} +1 -1
- package/.output/server/chunks/build/{_id_-Hwr4rZpr.mjs → _id_-BYsEua57.mjs} +33 -32
- package/.output/server/chunks/build/_id_-BYsEua57.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs → _wsId_-BxlDcJHg.mjs} +293 -259
- package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs.map → _wsId_-BxlDcJHg.mjs.map} +1 -1
- package/.output/server/chunks/build/{agents-Bvin73QY.mjs → agents-CLQqJF2y.mjs} +21 -27
- package/.output/server/chunks/build/agents-CLQqJF2y.mjs.map +1 -0
- package/.output/server/chunks/build/{aml-BCSJCdX7.mjs → aml-_7FdFL2p.mjs} +109 -109
- package/.output/server/chunks/build/{aml-BCSJCdX7.mjs.map → aml-_7FdFL2p.mjs.map} +1 -1
- package/.output/server/chunks/build/api-error-dul30BYQ.mjs.map +1 -1
- package/.output/server/chunks/build/{channel-templates-BTq8570n.mjs → channel-templates-p6SZNjlM.mjs} +19 -19
- package/.output/server/chunks/build/{channel-templates-BTq8570n.mjs.map → channel-templates-p6SZNjlM.mjs.map} +1 -1
- package/.output/server/chunks/build/{daq-C03Uu5UE.mjs → daq-cgyjKmJu.mjs} +188 -122
- package/.output/server/chunks/build/daq-cgyjKmJu.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-6vEYVeje.mjs → dcw-CoTxBlAZ.mjs} +45 -45
- package/.output/server/chunks/build/{dcw-6vEYVeje.mjs.map → dcw-CoTxBlAZ.mjs.map} +1 -1
- package/.output/server/chunks/build/{default-hseyOqBT.mjs → default-BE4r0FZN.mjs} +158 -79
- package/.output/server/chunks/build/{default-hseyOqBT.mjs.map → default-BE4r0FZN.mjs.map} +1 -1
- package/.output/server/chunks/build/{logs-B-xhuqln.mjs → logs-BSOeZwwI.mjs} +34 -34
- package/.output/server/chunks/build/{logs-B-xhuqln.mjs.map → logs-BSOeZwwI.mjs.map} +1 -1
- package/.output/server/chunks/build/{monitor-CyfDt4nR.mjs → monitor-C0okQaXz.mjs} +33 -33
- package/.output/server/chunks/build/{monitor-CyfDt4nR.mjs.map → monitor-C0okQaXz.mjs.map} +1 -1
- package/.output/server/chunks/build/{pages-BO7yZsXA.mjs → pages-DFxjn9Xa.mjs} +33 -17
- package/.output/server/chunks/build/pages-DFxjn9Xa.mjs.map +1 -0
- package/.output/server/chunks/build/{permissions-B52oOmaO.mjs → permissions-68SsNjex.mjs} +12 -12
- package/.output/server/chunks/build/{permissions-B52oOmaO.mjs.map → permissions-68SsNjex.mjs.map} +1 -1
- package/.output/server/chunks/build/{plugins-CUWmMdyt.mjs → plugins-lGlUr1lj.mjs} +17 -17
- package/.output/server/chunks/build/{plugins-CUWmMdyt.mjs.map → plugins-lGlUr1lj.mjs.map} +1 -1
- package/.output/server/chunks/build/{settings-DoQ2Oq3P.mjs → settings-Ddd1qx2Z.mjs} +50 -47
- package/.output/server/chunks/build/settings-Ddd1qx2Z.mjs.map +1 -0
- package/.output/server/chunks/build/{teams-2kz3CusY.mjs → teams-MEDLZdMn.mjs} +38 -34
- package/.output/server/chunks/build/teams-MEDLZdMn.mjs.map +1 -0
- package/.output/server/chunks/build/{tokens-BbWLqtVz.mjs → tokens-C7fTM5C2.mjs} +14 -14
- package/.output/server/chunks/build/{tokens-BbWLqtVz.mjs.map → tokens-C7fTM5C2.mjs.map} +1 -1
- package/.output/server/chunks/build/{town-BOl6hs9B.mjs → town-qMPql9Se.mjs} +36 -10
- package/.output/server/chunks/build/town-qMPql9Se.mjs.map +1 -0
- package/.output/server/chunks/build/useDaqStream-CIBaiGAl.mjs +1 -1
- package/.output/server/chunks/build/useDcwStream-CBbiYnWe.mjs +1 -1
- package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs +49 -0
- package/.output/server/chunks/build/useResponsive-BKDMGTHs.mjs.map +1 -0
- package/.output/server/chunks/build/{users-fohSmF-J.mjs → users-Cfd5-l2-.mjs} +6 -6
- package/.output/server/chunks/build/{users-fohSmF-J.mjs.map → users-Cfd5-l2-.mjs.map} +1 -1
- package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs → workshop-BL5bhfs0.mjs} +15 -15
- package/.output/server/chunks/build/{workshop-DX5-ldc4.mjs.map → workshop-BL5bhfs0.mjs.map} +1 -1
- package/.output/server/chunks/build/{workspaces-BuydxM-3.mjs → workspaces-Bgz5Ekwp.mjs} +18 -3
- package/.output/server/chunks/build/workspaces-Bgz5Ekwp.mjs.map +1 -0
- package/.output/server/chunks/routes/api/system/config-groups/_id_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/system/index.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/system/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/system/settings/events.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs +6 -2
- package/.output/server/chunks/routes/api/workshop/agent-tools/invoke.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs +2 -1
- package/.output/server/chunks/routes/api/workshop/aml/datasets/_id_.get.mjs.map +1 -1
- 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/daq/index.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/daq/templates/_key_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs +3 -2
- package/.output/server/chunks/routes/api/workshop/dcw/_id/write.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs +2 -1
- package/.output/server/chunks/routes/api/workshop/dcw/_id_.delete.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs +8 -6
- package/.output/server/chunks/routes/api/workshop/dcw/index2.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs +5 -5
- package/.output/server/chunks/routes/api/workshop/dcw/index3.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs +14 -4
- package/.output/server/chunks/routes/api/workshop/dcw/index4.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs +4 -4
- package/.output/server/chunks/routes/api/workshop/dcw/index4.post.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs +4 -3
- package/.output/server/chunks/routes/api/workshop/dcw/index5.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs +31 -0
- package/.output/server/chunks/routes/api/workshop/dcw/index5.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs +28 -0
- package/.output/server/chunks/routes/api/workshop/dcw/index6.get.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id_.patch.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs +32 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/read.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs +34 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id/write.post.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs +33 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.delete.mjs.map +1 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs +34 -0
- package/.output/server/chunks/routes/api/workshop/dcw/params/_id_.patch.mjs.map +1 -0
- 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/index6.get.mjs +2 -0
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index9.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index9.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 +3 -3
- package/.output/server/chunks/virtual/entry.mjs +53 -25
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +204 -40
- package/README.md +739 -591
- package/app/app.vue +9 -3
- package/app/assets/css/main.css +517 -7
- package/app/components/AppHeader.vue +98 -24
- package/app/components/AppSidebar.vue +118 -11
- package/app/components/AwChart.vue +2 -1
- package/app/components/workshop/AgentInspectorDrawer.vue +6 -6
- package/app/components/workshop/AgentLanesView.vue +52 -1
- package/app/components/workshop/AssetLibrary.vue +4 -2
- package/app/components/workshop/ChannelSessionList.vue +4 -2
- package/app/components/workshop/Composer.vue +96 -0
- package/app/components/workshop/DaqNodeRow.vue +19 -0
- package/app/components/workshop/DaqTemplateIcon.vue +33 -0
- package/app/components/workshop/DeviceTwinPanel.vue +37 -35
- package/app/components/workshop/EventBlock.vue +3 -3
- package/app/components/workshop/InspectorPanel.vue +47 -11
- package/app/components/workshop/LiveAgentBar.vue +34 -0
- package/app/components/workshop/TaskBoardView.vue +83 -33
- package/app/components/workshop/TranscriptTimeline.vue +56 -0
- package/app/components/workshop/blocks/ClusterError.vue +1 -1
- package/app/components/workshop/blocks/ClusterRoute.vue +3 -1
- package/app/components/workshop/blocks/ClusterStatus.vue +2 -2
- package/app/components/workshop/blocks/ClusterStream.vue +8 -2
- package/app/components/workshop/town/ModelPreview3D.vue +1 -1
- package/app/components/workshop/town/TownView.vue +507 -82
- package/app/composables/useResponsive.ts +88 -0
- package/app/composables/useRouteMeta.ts +3 -0
- package/app/composables/workshop/useDcwStream.ts +1 -1
- package/app/composables/workshop/useVisibleInterval.ts +2 -2
- package/app/composables/workshop/useWorkshopApi.ts +1 -1
- package/app/layouts/default.vue +46 -2
- package/app/pages/aml/index.vue +712 -616
- package/app/pages/daq/[id].vue +3 -2
- package/app/pages/daq/index.vue +209 -9
- package/app/pages/dcw/[id].vue +109 -28
- package/app/pages/dcw/index.vue +83 -23
- package/app/pages/index.vue +35 -1
- package/app/pages/logs/index.vue +147 -13
- package/app/pages/monitor.vue +56 -12
- package/app/pages/permissions/index.vue +34 -0
- package/app/pages/plugins/index.vue +35 -4
- package/app/pages/settings.vue +90 -21
- package/app/pages/tokens.vue +23 -0
- package/app/pages/town.vue +50 -5
- package/app/pages/users.vue +23 -0
- package/app/pages/workshop/agents.vue +92 -2
- package/app/pages/workshop/channel-templates.vue +53 -0
- package/app/pages/workshop/index.vue +14 -0
- package/app/pages/workshop/teams.vue +66 -6
- package/app/pages/workshop/w/[wsId].vue +202 -14
- package/app/plugins/aw-plugins.client.ts +13 -8
- package/app/stores/app.ts +33 -1
- package/app/stores/workshop/user.ts +1 -1
- package/app/stores/workshop/workspaces.ts +18 -2
- package/app/utils/api-error.ts +1 -1
- package/cli/commands/build.mjs +8 -1
- package/docker-compose.yml +6 -0
- package/docs/full-test-plan.md +4 -0
- package/docs/plugins.en.md +1 -0
- package/docs/plugins.md +1 -0
- package/i18n/locales/en.ts +3 -0
- package/i18n/locales/zh-CN.ts +3 -0
- package/nuxt.config.ts +31 -0
- package/package.json +9 -1
- package/scripts/_audit/detached-start.mjs +103 -0
- package/scripts/_audit/guard-upstream-liveness.mjs +104 -0
- package/scripts/_dbg-param-map-e2e.ts +242 -0
- package/scripts/_dbg-param-map-rest-smoke.mjs +73 -0
- package/scripts/_dbg-param-real-scenario.mjs +153 -0
- package/scripts/_dbg-perms-e2e.mjs +10 -3
- package/scripts/_dbg-protocol-matrix.mjs +453 -441
- package/scripts/_dbg-render-regression.mjs +151 -130
- package/scripts/_dbg-town-agents-probe.mjs +60 -0
- package/scripts/_dbg-visual-acceptance.mjs +63 -0
- package/scripts/api-live-e2e.mjs +40 -8
- package/scripts/capture-walkthrough.mjs +232 -0
- package/scripts/e2e-agent-team.mjs +5 -3
- package/scripts/render-walkthrough-figure.mjs +46 -0
- package/scripts/seed-walkthrough-fixture.mjs +143 -0
- package/scripts/test-plugin-hardening.mjs +8 -0
- package/scripts/three-system-e2e.mjs +6 -6
- package/scripts/ui/admin-shots.mjs +46 -0
- package/scripts/ui/audit-layout.mjs +301 -0
- package/scripts/ui/bump-microfont.mjs +39 -0
- package/scripts/ui/lib.mjs +198 -0
- package/scripts/ui/linkcheck2.mjs +37 -0
- package/scripts/ui/live-render-check.mjs +72 -0
- package/scripts/ui/md-lint.mjs +80 -0
- package/scripts/ui/perf-audit.mjs +132 -0
- package/scripts/ui/readme-assets.mjs +30 -0
- package/scripts/ui/readme-shots.mjs +42 -0
- package/scripts/ui/record.mjs +225 -0
- package/scripts/ui/seed-admin.mjs +68 -0
- package/scripts/ui/seed-demo.mjs +83 -0
- package/scripts/ui/shot-hero.mjs +25 -0
- package/scripts/ui/shots.mjs +75 -0
- package/scripts/ui/sim-ui-shot.mjs +16 -0
- package/scripts/ui/verify-nav.mjs +51 -0
- package/scripts/ui/verify-responsive.mjs +285 -0
- package/server/api/system/config-groups/[id].patch.ts +1 -1
- package/server/api/system/config-groups/index.post.ts +1 -1
- package/server/api/system/settings/events.get.ts +1 -1
- package/server/api/system/settings/index.patch.ts +1 -1
- package/server/api/workshop/agent-tools/invoke.post.ts +16 -2
- package/server/api/workshop/aml/datasets/[id].get.ts +8 -1
- package/server/api/workshop/daq/infra/reconnect.post.ts +1 -1
- package/server/api/workshop/daq/templates/[key].delete.ts +1 -0
- package/server/api/workshop/daq/templates/[key].patch.ts +1 -0
- package/server/api/workshop/daq/templates/index.post.ts +1 -0
- package/server/api/workshop/dcw/[id]/write.post.ts +10 -2
- package/server/api/workshop/dcw/[id].delete.ts +5 -2
- package/server/api/workshop/dcw/index.get.ts +2 -0
- package/server/api/workshop/dcw/lines/[id].patch.ts +1 -1
- package/server/api/workshop/dcw/params/[id]/read.post.ts +17 -0
- package/server/api/workshop/dcw/params/[id]/write.post.ts +21 -0
- package/server/api/workshop/dcw/params/[id].delete.ts +19 -0
- package/server/api/workshop/dcw/params/[id].patch.ts +23 -0
- package/server/api/workshop/dcw/params/index.get.ts +27 -0
- package/server/api/workshop/dcw/params/index.post.ts +25 -0
- package/server/api/workshop/permissions/index.get.ts +3 -1
- package/server/plugins/aw-plugins.ts +14 -3
- package/server/plugins/dev-stability-guard.ts +3 -1
- package/server/plugins/system-config.ts +6 -1
- package/server/plugins-builtin/rag-bridge/index.mjs +9 -2
- package/server/services/system-config.ts +28 -9
- package/server/services/workshop/agents/adapters/one-shot-cli-agent.ts +2 -1
- package/server/services/workshop/agents/agent-interface.ts +8 -6
- package/server/services/workshop/agents/base-agent.ts +18 -4
- package/server/services/workshop/agents/claude-agent.ts +7 -3
- package/server/services/workshop/agents/codex-agent.ts +3 -3
- package/server/services/workshop/agents/dsh-agent.ts +3 -3
- package/server/services/workshop/agents/goose-agent.ts +0 -1
- package/server/services/workshop/agents/harness-terminal.ts +14 -1
- package/server/services/workshop/agents/hermes-agent.ts +4 -2
- package/server/services/workshop/agents/host-tool-bridge.ts +5 -1
- package/server/services/workshop/agents/industrial-context.ts +4 -1
- package/server/services/workshop/agents/industrial-tools.ts +164 -6
- package/server/services/workshop/agents/omp-agent.ts +15 -9
- package/server/services/workshop/agents/opencode-agent.ts +47 -21
- package/server/services/workshop/agents/qwen-agent.ts +10 -5
- package/server/services/workshop/aml/entity.ts +3 -1
- package/server/services/workshop/aml/python-runtime.ts +3 -2
- package/server/services/workshop/daq/bus/mqtt.adapter.ts +8 -4
- package/server/services/workshop/daq/daq-controller.ts +18 -3
- package/server/services/workshop/daq/daq-runtime.ts +5 -3
- package/server/services/workshop/daq/drivers.ts +63 -8
- package/server/services/workshop/daq/host-ports.ts +3 -2
- package/server/services/workshop/daq/storage/timescale.adapter.ts +3 -1
- package/server/services/workshop/db/channel.repo.ts +4 -0
- package/server/services/workshop/db/ops.repo.ts +5 -2
- package/server/services/workshop/db/retention.ts +8 -3
- package/server/services/workshop/dcw/dcw-controller.ts +143 -16
- package/server/services/workshop/dcw/dcw-product.repo.ts +38 -1
- package/server/services/workshop/dcw/dcw-runtime.ts +3 -2
- package/server/services/workshop/dcw/drivers.ts +5 -2
- package/server/services/workshop/dcw/param-conversion.ts +78 -0
- package/server/services/workshop/dcw/param-limits.ts +142 -0
- package/server/services/workshop/dcw/param-map.repo.ts +305 -0
- package/server/services/workshop/dcw/recipe-rollback-manager.ts +13 -7
- package/server/services/workshop/runtime/agent-runtime.ts +4 -0
- package/server/services/workshop/runtime/manager.ts +9 -2
- package/server/services/workshop/settings.ts +4 -2
- package/server/types/pg.d.ts +49 -0
- package/server/utils/stability-severity.ts +13 -2
- package/shared/config/groups.mjs +22 -1
- package/shared/config/home.d.mts +28 -0
- package/shared/dcw-protocol.ts +136 -0
- package/uno.config.ts +12 -1
- package/.output/public/_nuxt/BFkgkW8u.js +0 -4
- package/.output/public/_nuxt/C5gUkRwn.js +0 -1
- package/.output/public/_nuxt/CM12FuB42.js +0 -1
- package/.output/public/_nuxt/CZiw7sEV.js +0 -1
- package/.output/public/_nuxt/Cc_9VzD42.js +0 -1
- package/.output/public/_nuxt/CdPzkOMV.js +0 -1
- package/.output/public/_nuxt/D1YDxZpZ.js +0 -1
- package/.output/public/_nuxt/D20Yamr4.js +0 -1
- package/.output/public/_nuxt/DVsjtsvZ2.js +0 -1
- package/.output/public/_nuxt/DmqknNvV.js +0 -18
- package/.output/public/_nuxt/DnUuIR83.js +0 -1
- package/.output/public/_nuxt/S_fVQwJK.js +0 -1
- package/.output/public/_nuxt/TownView.0oLiQgTC.css +0 -1
- package/.output/public/_nuxt/_id_.D-SYuyZb.css +0 -1
- package/.output/public/_nuxt/_id_.hma_BU6T.css +0 -1
- package/.output/public/_nuxt/_wsId_.B12v3PhY.css +0 -1
- package/.output/public/_nuxt/agents.BV70ioCv.css +0 -1
- package/.output/public/_nuxt/ahgEJu2R.js +0 -1
- package/.output/public/_nuxt/aml.COEKNNqK.css +0 -1
- package/.output/public/_nuxt/builds/meta/44b66249-54d0-4c8a-9057-9755a32329e2.json +0 -1
- package/.output/public/_nuxt/channel-templates.C-M5nKcC.css +0 -1
- package/.output/public/_nuxt/daq.B3265ZDn.css +0 -1
- package/.output/public/_nuxt/dcw.DLYZqEhT.css +0 -1
- package/.output/public/_nuxt/default.DE_vT238.css +0 -1
- package/.output/public/_nuxt/entry.BB-Eeh-J.css +0 -1
- package/.output/public/_nuxt/logs.CqFyMrJw.css +0 -1
- package/.output/public/_nuxt/monitor.B--1dPzs.css +0 -1
- package/.output/public/_nuxt/pages.ooEzrMm9.css +0 -1
- package/.output/public/_nuxt/permissions.B1pjGChZ.css +0 -1
- package/.output/public/_nuxt/plugins.5e5chZ67.css +0 -1
- package/.output/public/_nuxt/pz2JDAM7.js +0 -2
- package/.output/public/_nuxt/settings.Dm3M0RLy.css +0 -1
- package/.output/public/_nuxt/teams.BC75EGnR.css +0 -1
- package/.output/public/_nuxt/tokens.xRb1fSPU.css +0 -1
- package/.output/public/_nuxt/users.CXnP2E08.css +0 -1
- package/.output/public/_nuxt/workshop.Jk5HxWPE.css +0 -1
- package/.output/server/chunks/build/TownView-jVazYOjN.mjs.map +0 -1
- package/.output/server/chunks/build/_id_-Hwr4rZpr.mjs.map +0 -1
- package/.output/server/chunks/build/agents-Bvin73QY.mjs.map +0 -1
- package/.output/server/chunks/build/daq-C03Uu5UE.mjs.map +0 -1
- package/.output/server/chunks/build/pages-BO7yZsXA.mjs.map +0 -1
- package/.output/server/chunks/build/settings-DoQ2Oq3P.mjs.map +0 -1
- package/.output/server/chunks/build/teams-2kz3CusY.mjs.map +0 -1
- package/.output/server/chunks/build/town-BOl6hs9B.mjs.map +0 -1
- package/.output/server/chunks/build/workspaces-BuydxM-3.mjs.map +0 -1
- /package/.output/public/_nuxt/{36oeS8_R2.js → 36oeS8_R.js} +0 -0
- /package/.output/public/_nuxt/{CYWVXZTS.js → CYWVXZTS2.js} +0 -0
package/docs/plugins.md
CHANGED
|
@@ -497,6 +497,7 @@ export default {
|
|
|
497
497
|
| 现象 | 原因 / 处理 |
|
|
498
498
|
|---|---|
|
|
499
499
|
| 改了插件代码没生效 | 宿主没有插件目录 watcher:触碰 `<home>/plugins-state.json` 或 `aw plugin enable/disable` 往返一次触发重载(重载会带 `?t=` 破缓存);核心 `server/`、`shared/` 改动仍需重启进程 |
|
|
500
|
+
| 重建同名插件后路由 404 | 此前 disable 过的插件名在 `<home>/plugins-state.json` 里留有禁用标记,重建目录不会自动恢复装载 —— 重建后必须显式 `aw plugin enable <name>`(实测踩过) |
|
|
500
501
|
| 装载失败看不到 | `aw plugin list` **不显示**装载失败。用 `GET /api/workshop/plugins`(返回 `{ plugins, failures, initedAt }`,**没有 `{code,data}` 信封**),或看启动日志里的 `[aw-plugins] 装载失败 …` |
|
|
501
502
|
| 插件路由 404 | exact-match;检查 method 与 path 前导 `/`,以及插件是否处于启用态(停用插件的路由已从路由表移除) |
|
|
502
503
|
| 插件路由 401 | 入口声明了 `auth: 'user' \| 'admin' \| 'agent-or-user'`,请求未带有效凭据;开放接口才用 `auth: 'none'` |
|
package/i18n/locales/en.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
|
25
25
|
tokens: 'API Tokens',
|
|
26
26
|
daq: 'DAQ Console',
|
|
27
27
|
dcw: 'Line Ops',
|
|
28
|
+
aml: 'Modeling',
|
|
28
29
|
logs: 'Logs',
|
|
29
30
|
users: 'Users',
|
|
30
31
|
monitor: 'Runtime Monitor',
|
|
@@ -35,6 +36,8 @@ export default {
|
|
|
35
36
|
header: {
|
|
36
37
|
collapse: 'Collapse menu',
|
|
37
38
|
expand: 'Expand menu',
|
|
39
|
+
openNav: 'Open navigation',
|
|
40
|
+
closeNav: 'Close navigation',
|
|
38
41
|
language: 'Language',
|
|
39
42
|
theme: 'Theme',
|
|
40
43
|
fullscreen: 'Fullscreen',
|
package/i18n/locales/zh-CN.ts
CHANGED
|
@@ -25,6 +25,7 @@ export default {
|
|
|
25
25
|
tokens: 'API Token',
|
|
26
26
|
daq: '数采中心',
|
|
27
27
|
dcw: '产线运营',
|
|
28
|
+
aml: '建模',
|
|
28
29
|
logs: '日志管理',
|
|
29
30
|
users: '用户管理',
|
|
30
31
|
monitor: '运行时监控',
|
|
@@ -35,6 +36,8 @@ export default {
|
|
|
35
36
|
header: {
|
|
36
37
|
collapse: '折叠菜单',
|
|
37
38
|
expand: '展开菜单',
|
|
39
|
+
openNav: '打开导航',
|
|
40
|
+
closeNav: '关闭导航',
|
|
38
41
|
language: '语言',
|
|
39
42
|
theme: '主题',
|
|
40
43
|
fullscreen: '全屏',
|
package/nuxt.config.ts
CHANGED
|
@@ -221,6 +221,37 @@ export default defineNuxtConfig({
|
|
|
221
221
|
},
|
|
222
222
|
},
|
|
223
223
|
|
|
224
|
+
vite: {
|
|
225
|
+
build: {
|
|
226
|
+
/**
|
|
227
|
+
* rolldown 的构建期检查开关。
|
|
228
|
+
*
|
|
229
|
+
* pluginTimings 会在"插件钩子占了构建时间大头"时打一条 WARN。本项目装了
|
|
230
|
+
* unocss / antd / i18n / unplugin-icons 等一堆构建期插件,这条 WARN 每次必现 ——
|
|
231
|
+
* 它是**构建自身的耗时画像**,不是代码质量问题,也无法通过改我们的代码消除
|
|
232
|
+
* (unocss 的 renderChunk 调用 93 次、占 ~19%,这是它的工作方式)。
|
|
233
|
+
*
|
|
234
|
+
* 只关这一项。其余 checks(命名空间当函数调用、配置冲突、无用的内置特性替代等)
|
|
235
|
+
* 都是**真的可能在抓 bug** 的检查,保持开启。
|
|
236
|
+
*/
|
|
237
|
+
rolldownOptions: {
|
|
238
|
+
checks: { pluginTimings: false },
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* 客户端单块体积阈值。
|
|
242
|
+
*
|
|
243
|
+
* 默认的 500kB 对本项目不适用:three.js(数字孪生,~1.35MB)与
|
|
244
|
+
* echarts(~650kB)是重型第三方库的固有体积,而且都已经是**路由级动态导入**
|
|
245
|
+
* ——/town 才加载 three,只有真正进入孪生页的用户才付这份下载。
|
|
246
|
+
* 继续拆分的收益很小(它们本身就是单个不可再分的库)。
|
|
247
|
+
*
|
|
248
|
+
* 阈值提到 1500kB 的用意不是"把告警关掉",而是让这条告警重新变得**有意义**:
|
|
249
|
+
* 一旦某天我们自己的代码或某个新依赖把这个数字推过 1.5MB,它照样会报。
|
|
250
|
+
*/
|
|
251
|
+
chunkSizeWarningLimit: 1500,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
|
|
224
255
|
typescript: {
|
|
225
256
|
strict: true,
|
|
226
257
|
typeCheck: false,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentworkshop",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "AgentWorkShop 软件开发系统 - 基于 Nuxt 4 的配置驱动运行范式",
|
|
6
6
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
@@ -80,6 +80,13 @@
|
|
|
80
80
|
"prepare": "husky",
|
|
81
81
|
"game:test": "tsx scripts/test-agent-brain.ts && tsx scripts/test-game-session.ts",
|
|
82
82
|
"test:api-live": "node scripts/api-live-e2e.mjs",
|
|
83
|
+
"ui:seed": "node scripts/ui/seed-demo.mjs",
|
|
84
|
+
"ui:shots": "node scripts/ui/shots.mjs",
|
|
85
|
+
"ui:audit": "node scripts/ui/audit-layout.mjs",
|
|
86
|
+
"ui:verify": "node scripts/ui/verify-responsive.mjs",
|
|
87
|
+
"ui:verify-nav": "node scripts/ui/verify-nav.mjs",
|
|
88
|
+
"readme:lint": "node scripts/ui/md-lint.mjs README.md README-zh.md",
|
|
89
|
+
"readme:assets": "node scripts/ui/readme-assets.mjs",
|
|
83
90
|
"game:verify": "node scripts/verify-game.mjs",
|
|
84
91
|
"game:build-assets": "node scripts/build-player-sheet.mjs",
|
|
85
92
|
"game:build-wuwa": "node scripts/build-wuwa-assets.mjs",
|
|
@@ -146,6 +153,7 @@
|
|
|
146
153
|
"@nuxt/eslint": "^1.0.0",
|
|
147
154
|
"@types/js-yaml": "^4.0.9",
|
|
148
155
|
"@types/node": "^26.2.0",
|
|
156
|
+
"@types/pg": "^8.23.1",
|
|
149
157
|
"@types/three": "^0.185.4",
|
|
150
158
|
"eslint": "^9.17.0",
|
|
151
159
|
"husky": "^9.1.7",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 把 `aw start` 以**真分离**方式拉起(脱离 agent 作业的进程组),日志落文件。
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:agent 工具的后台作业在回合结束/被回收时会连子进程一起终止,
|
|
5
|
+
* 而 `aw start` 的 stdout 缓冲区随之丢失 —— 表现为「日志停在半路、无 fatal 行、
|
|
6
|
+
* 端口释放、退出码 1」,极易误判成服务自己崩了(实测踩过两次)。
|
|
7
|
+
*
|
|
8
|
+
* 做法:node 的 spawn + detached + stdio 重定向到文件。
|
|
9
|
+
* Windows 上还要 unref 并用 windowsHide,否则父进程退出时子进程一起走。
|
|
10
|
+
*
|
|
11
|
+
* 用法:
|
|
12
|
+
* node scripts/_audit/detached-start.mjs [--log <path>] [--cwd <dir>] [--port 3001] [--wait 45]
|
|
13
|
+
* node scripts/_audit/detached-start.mjs --status # 只看健康门与日志尾
|
|
14
|
+
* node scripts/_audit/detached-start.mjs --stop # 停掉分离实例
|
|
15
|
+
*/
|
|
16
|
+
import { spawn, spawnSync } from 'node:child_process'
|
|
17
|
+
import { closeSync, existsSync, openSync, readFileSync, statSync } from 'node:fs'
|
|
18
|
+
import { dirname, join, resolve } from 'node:path'
|
|
19
|
+
import { fileURLToPath } from 'node:url'
|
|
20
|
+
|
|
21
|
+
const arg = (k, d) => {
|
|
22
|
+
const i = process.argv.indexOf(k)
|
|
23
|
+
return i >= 0 ? process.argv[i + 1] : d
|
|
24
|
+
}
|
|
25
|
+
const repo = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..')
|
|
26
|
+
const CWD = resolve(arg('--cwd', repo))
|
|
27
|
+
const PORT = Number(arg('--port', '3001'))
|
|
28
|
+
const LOG = resolve(arg('--log', join(CWD, '..', 'aw-detached.log')))
|
|
29
|
+
const PIDFILE = LOG + '.pid'
|
|
30
|
+
const BASE = `http://127.0.0.1:${PORT}`
|
|
31
|
+
|
|
32
|
+
const health = async () => {
|
|
33
|
+
try {
|
|
34
|
+
const r = await fetch(`${BASE}/api/health`, { signal: AbortSignal.timeout(6000) })
|
|
35
|
+
const j = await r.json()
|
|
36
|
+
return j?.data?.status === 'ok' ? { ok: true, version: j.data.version, uptimeMs: j.data.uptimeMs } : { ok: false }
|
|
37
|
+
}
|
|
38
|
+
catch { return { ok: false } }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (process.argv.includes('--status')) {
|
|
42
|
+
const h = await health()
|
|
43
|
+
console.log(h.ok ? `✔ 健康 version=${h.version} uptime=${Math.round(h.uptimeMs / 1000)}s` : '✖ 不可达')
|
|
44
|
+
const pid = existsSync(PIDFILE) ? readFileSync(PIDFILE, 'utf8').trim() : '(无 pid 文件)'
|
|
45
|
+
console.log(` 分离 pid 文件: ${pid}`)
|
|
46
|
+
if (existsSync(LOG)) {
|
|
47
|
+
const size = statSync(LOG).size
|
|
48
|
+
const tail = readFileSync(LOG, 'utf8').split(/\r?\n/).slice(-6)
|
|
49
|
+
console.log(` 日志 ${LOG} (${(size / 1024).toFixed(1)} KB) 尾部:`)
|
|
50
|
+
for (const l of tail) if (l.trim()) console.log(' ' + l.slice(0, 150))
|
|
51
|
+
}
|
|
52
|
+
process.exitCode = h.ok ? 0 : 1
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
else if (process.argv.includes('--stop')) {
|
|
56
|
+
const pid = existsSync(PIDFILE) ? Number(readFileSync(PIDFILE, 'utf8').trim()) : NaN
|
|
57
|
+
if (Number.isInteger(pid)) {
|
|
58
|
+
const r = spawnSync('taskkill', ['/PID', String(pid), '/T', '/F'], { stdio: 'ignore' })
|
|
59
|
+
console.log(r.status === 0 ? `✔ 已终止分离实例 pid=${pid}` : `· taskkill 退出码 ${r.status}(进程可能已退出)`)
|
|
60
|
+
}
|
|
61
|
+
else console.log('· 无 pid 文件,改用 aw stop')
|
|
62
|
+
if (!Number.isInteger(pid)) spawnSync('aw', ['stop'], { cwd: CWD, stdio: 'inherit', shell: true })
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
else {
|
|
66
|
+
const pre = await health()
|
|
67
|
+
if (pre.ok) {
|
|
68
|
+
console.log(`· ${BASE} 已有健康实例(version=${pre.version}),不重复启动`)
|
|
69
|
+
process.exitCode = 0
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const out = openSync(LOG, 'a')
|
|
73
|
+
// 只清大小写重复的代理变量:7890 会拦 localhost
|
|
74
|
+
const env = { ...process.env, NO_PROXY: '127.0.0.1,localhost', HTTP_PROXY: '', HTTPS_PROXY: '' }
|
|
75
|
+
const child = spawn(process.execPath, [join(repo, 'bin', 'aw.mjs'), 'start', '--port', String(PORT)], {
|
|
76
|
+
cwd: CWD,
|
|
77
|
+
env,
|
|
78
|
+
detached: true,
|
|
79
|
+
windowsHide: true,
|
|
80
|
+
stdio: ['ignore', out, out],
|
|
81
|
+
})
|
|
82
|
+
closeSync(out)
|
|
83
|
+
child.unref()
|
|
84
|
+
const { writeFileSync } = await import('node:fs')
|
|
85
|
+
writeFileSync(PIDFILE, String(child.pid), 'utf8')
|
|
86
|
+
console.log(`✔ 已分离启动 pid=${child.pid} → ${BASE},日志 ${LOG}`)
|
|
87
|
+
|
|
88
|
+
const waitS = Number(arg('--wait', '45'))
|
|
89
|
+
for (let i = 0; i < waitS; i += 3) {
|
|
90
|
+
await new Promise(r => setTimeout(r, 3000))
|
|
91
|
+
const h = await health()
|
|
92
|
+
if (h.ok) {
|
|
93
|
+
console.log(`✔ 健康门通过 version=${h.version}(${(i + 3)}s)`)
|
|
94
|
+
process.exitCode = 0
|
|
95
|
+
break
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (process.exitCode === undefined) {
|
|
99
|
+
console.error(`✖ ${waitS}s 内未通过健康门,看日志:${LOG}`)
|
|
100
|
+
process.exitCode = 1
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 稳定性护栏端到端验证:外部设备不可达时,服务端**必须存活**。
|
|
3
|
+
*
|
|
4
|
+
* 复现的真实事故:一台 Modbus TCP 设备未启动(connect ECONNREFUSED 127.0.0.1:1502)
|
|
5
|
+
* 触发 unhandledRejection 走到 fatal 分支 → 整个生产实例 exit 1 →
|
|
6
|
+
* 数采/数控/数字孪生全平台被一台设备带走。
|
|
7
|
+
*
|
|
8
|
+
* 做法:
|
|
9
|
+
* 1. 建立指向**必然拒绝连接**的端口的 Modbus TCP 数采节点(默认 1502,可 --port 覆盖)
|
|
10
|
+
* 2. 等采集轮询真正发起连接(默认 25s)
|
|
11
|
+
* 3. 断言:健康门仍 200、进程未死、日志出现 upstream 分级记录
|
|
12
|
+
*
|
|
13
|
+
* 用法:node scripts/_audit/guard-upstream-liveness.mjs [--base http://127.0.0.1:3001] [--port 1502] [--wait 25] [--keep]
|
|
14
|
+
*/
|
|
15
|
+
const arg = (k, d) => {
|
|
16
|
+
const i = process.argv.indexOf(k)
|
|
17
|
+
return i >= 0 ? process.argv[i + 1] : d
|
|
18
|
+
}
|
|
19
|
+
const BASE = arg('--base', 'http://127.0.0.1:3001')
|
|
20
|
+
const DEAD_PORT = Number(arg('--port', '1502'))
|
|
21
|
+
const WAIT_S = Number(arg('--wait', '25'))
|
|
22
|
+
const KEEP = process.argv.includes('--keep')
|
|
23
|
+
const TOKEN = process.env.AW_E2E_TOKEN
|
|
24
|
+
?? (await (await fetch(`${BASE}/api/users/login`, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { 'content-type': 'application/json' },
|
|
27
|
+
body: JSON.stringify({ email: 'admin@awshop.local', password: 'admin123' }),
|
|
28
|
+
})).json()).data.token
|
|
29
|
+
|
|
30
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
31
|
+
const api = async (method, path, body) => {
|
|
32
|
+
const r = await fetch(BASE + path, {
|
|
33
|
+
method,
|
|
34
|
+
headers: { 'content-type': 'application/json', 'authorization': `Bearer ${TOKEN}` },
|
|
35
|
+
body: body === undefined ? undefined : JSON.stringify(body),
|
|
36
|
+
})
|
|
37
|
+
return r.json().catch(() => ({}))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let pass = 0
|
|
41
|
+
let fail = 0
|
|
42
|
+
const check = (name, ok, detail = '') => {
|
|
43
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
44
|
+
ok ? pass++ : fail++
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const health = async () => {
|
|
48
|
+
try {
|
|
49
|
+
const r = await fetch(`${BASE}/api/health`, { signal: AbortSignal.timeout(5000) })
|
|
50
|
+
const j = await r.json()
|
|
51
|
+
return { ok: r.ok && j?.data?.status === 'ok', version: j?.data?.version }
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
return { ok: false, error: String(e?.message ?? e) }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(`\n━━━ 稳定性护栏:外部设备不可达不应带走服务(设备端口 :${DEAD_PORT}) ━━━`)
|
|
59
|
+
const before = await health()
|
|
60
|
+
check('前置:服务健康', before.ok, `version=${before.version}`)
|
|
61
|
+
|
|
62
|
+
// 1) 建一条指向死端口的 Modbus TCP 节点。字段形状对齐既有节点:
|
|
63
|
+
// templateRef 必填(信号模板)、地址走 driverConfig.register。
|
|
64
|
+
const ts = Date.now()
|
|
65
|
+
const line = await api('POST', '/api/workshop/dcw/lines', { name: `护栏验证线-${ts}`, productId: null })
|
|
66
|
+
const lineId = line?.data?.line?.id ?? line?.data?.id
|
|
67
|
+
check('创建验证产线', !!lineId, lineId ?? JSON.stringify(line).slice(0, 120))
|
|
68
|
+
|
|
69
|
+
const node = await api('POST', '/api/workshop/daq', {
|
|
70
|
+
lineId,
|
|
71
|
+
name: `死端口节点-${ts}`,
|
|
72
|
+
templateRef: arg('--template', 'daq-temp-tc'),
|
|
73
|
+
driver: 'modbus-tcp',
|
|
74
|
+
unit: '℃',
|
|
75
|
+
intervalMs: 500,
|
|
76
|
+
enabled: true,
|
|
77
|
+
driverConfig: { host: '127.0.0.1', port: DEAD_PORT, unitId: 1, register: 40001, dataType: 'float32', byteOrder: 'big' },
|
|
78
|
+
})
|
|
79
|
+
const nodeId = node?.data?.node?.id ?? node?.data?.id
|
|
80
|
+
check('创建指向必然拒绝连接端口的数采节点', !!nodeId, nodeId ?? JSON.stringify(node).slice(0, 200))
|
|
81
|
+
|
|
82
|
+
// 2) 开跑,让轮询真正去连那个端口
|
|
83
|
+
if (lineId) await api('POST', `/api/workshop/dcw/lines/${lineId}/start`, {})
|
|
84
|
+
console.log(` · 等待 ${WAIT_S}s 让采集轮询发起连接 ...`)
|
|
85
|
+
await sleep(WAIT_S * 1000)
|
|
86
|
+
|
|
87
|
+
// 3) 核心断言:服务还活着
|
|
88
|
+
const after = await health()
|
|
89
|
+
check('设备不可达后服务仍健康(修复前此处 exit 1)', after.ok, after.ok ? `version=${after.version}` : `unreachable: ${after.error}`)
|
|
90
|
+
|
|
91
|
+
const opened = await api('GET', `/api/workshop/daq/${nodeId}/samples?from=${Date.now() - WAIT_S * 1000}&bucketMs=1000&limit=5`).catch(() => null)
|
|
92
|
+
check('节点面仍可查询(平台未被带走)', opened !== null)
|
|
93
|
+
|
|
94
|
+
// 4) 清理
|
|
95
|
+
if (!KEEP && lineId) {
|
|
96
|
+
await api('POST', `/api/workshop/dcw/lines/${lineId}/stop`, {}).catch(() => null)
|
|
97
|
+
await api('DELETE', `/api/workshop/daq/${nodeId}`).catch(() => null)
|
|
98
|
+
await api('DELETE', `/api/workshop/dcw/lines/${lineId}`).catch(() => null)
|
|
99
|
+
console.log(' · 验证用产线/节点已清理')
|
|
100
|
+
}
|
|
101
|
+
else if (KEEP) console.log(` · KEEP=1,保留现场:line=${lineId} node=${nodeId}`)
|
|
102
|
+
|
|
103
|
+
console.log(`\n━━━ 结果:${pass} PASS / ${fail} FAIL ━━━\n`)
|
|
104
|
+
process.exitCode = fail === 0 ? 0 : 1
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工艺参数映射层 E2E(真实 PLC 模拟器连通):
|
|
3
|
+
* ①节点创建自动生成工艺参数映射 + 参数面不透出 PLC 寻址细节
|
|
4
|
+
* ②真实 Modbus TCP 从站(dev-plc-simulator,一阶惯性温度回路)参数写→回读 roundtrip
|
|
5
|
+
* ③参数基准限界联锁(常驻层) ④产品限界 + 配方窗口分层联锁(产线运行期)
|
|
6
|
+
* ⑤配方下发同样受限(run.results 记失败) ⑥Agent param_control/param_read(绑定/越权/消歧)
|
|
7
|
+
* ⑦节点删除级联清理映射 ⑧运行中 plc-node-simulator(16040)导出配置连通性交叉验证
|
|
8
|
+
* 数据目录 AW_HOME 隔离,不触碰检出内 .AgentWorkShop。
|
|
9
|
+
* 运行: npx tsx --tsconfig .nuxt/tsconfig.server.json scripts/_dbg-param-map-e2e.ts
|
|
10
|
+
*/
|
|
11
|
+
import { mkdtempSync } from 'node:fs'
|
|
12
|
+
import { tmpdir } from 'node:os'
|
|
13
|
+
import { join } from 'node:path'
|
|
14
|
+
// @ts-expect-error 既有验证脚本助手(无类型声明)
|
|
15
|
+
import { startPlcSim } from './_lib-plc-sim-child.mjs'
|
|
16
|
+
|
|
17
|
+
// 进程级隔离:配置根落在一次性目录(须在业务模块 import 前设置)
|
|
18
|
+
const awHome = mkdtempSync(join(tmpdir(), 'aw-param-map-e2e-'))
|
|
19
|
+
process.env.AW_MODE = 'home'
|
|
20
|
+
process.env.AW_HOME = awHome
|
|
21
|
+
process.env.NO_PROXY = '127.0.0.1,localhost'
|
|
22
|
+
|
|
23
|
+
let failures = 0
|
|
24
|
+
const check = (name: string, ok: boolean, detail = ''): void => {
|
|
25
|
+
console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`)
|
|
26
|
+
if (!ok) failures += 1
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ===== 真实 Modbus 从站(scripts/dev-plc-simulator.mjs:40021 温度SP / 40001 温度PV,float32 大端) =====
|
|
30
|
+
const SIM_PORT = 15040
|
|
31
|
+
const sim = startPlcSim(SIM_PORT)
|
|
32
|
+
const simReady: Promise<boolean> = sim.ready
|
|
33
|
+
|
|
34
|
+
// ===== 服务层导入(隔离根内) =====
|
|
35
|
+
const { getDcwController } = await import('../server/services/workshop/dcw/dcw-controller')
|
|
36
|
+
const { getDcwParamRepo } = await import('../server/services/workshop/dcw/param-map.repo')
|
|
37
|
+
const { getDcwLineRepo } = await import('../server/services/workshop/dcw/dcw-line.repo')
|
|
38
|
+
const { getDcwProductRepo } = await import('../server/services/workshop/dcw/dcw-product.repo')
|
|
39
|
+
const { getDcwRecipeRepo } = await import('../server/services/workshop/dcw/dcw-recipe.repo')
|
|
40
|
+
const { getAgentNodeBindingRepo } = await import('../server/services/workshop/agents/node-bindings.repo')
|
|
41
|
+
const { toolParamControl, toolParamRead } = await import('../server/services/workshop/agents/industrial-tools')
|
|
42
|
+
|
|
43
|
+
const ctrl = getDcwController()
|
|
44
|
+
const paramRepo = getDcwParamRepo()
|
|
45
|
+
|
|
46
|
+
/** 捕获 AppError 的消息与状态码 */
|
|
47
|
+
async function errOf(fn: () => Promise<unknown> | unknown): Promise<{ status: number, message: string }> {
|
|
48
|
+
try {
|
|
49
|
+
await fn()
|
|
50
|
+
return { status: 0, message: '(未抛错)' }
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
const e = err as { status?: number, statusCode?: number, message?: string }
|
|
54
|
+
return { status: e.status ?? e.statusCode ?? 500, message: e.message ?? String(err) }
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
console.log('\n--- ① 节点创建 → 自动工艺参数映射(参数面语义) ---')
|
|
60
|
+
if (!await simReady) throw new Error(`Modbus 模拟器未就绪(${SIM_PORT},可能端口被占)`)
|
|
61
|
+
console.log(` 模拟器就绪 @ 127.0.0.1:${SIM_PORT}`)
|
|
62
|
+
const line = getDcwLineRepo().create({ name: '参数映射验证线' })
|
|
63
|
+
const node = ctrl.create({
|
|
64
|
+
templateRef: 'dcw-temp-sp',
|
|
65
|
+
name: 'PP验证·烘箱温度',
|
|
66
|
+
driver: 'modbus-tcp',
|
|
67
|
+
driverConfig: { host: '127.0.0.1', port: SIM_PORT, unitId: 1, register: 40021, dataType: 'float32', byteOrder: 'big' },
|
|
68
|
+
min: 0,
|
|
69
|
+
max: 300,
|
|
70
|
+
readIntervalMs: 0,
|
|
71
|
+
lineId: line.id,
|
|
72
|
+
})
|
|
73
|
+
const prow = paramRepo.byNode(node.id)
|
|
74
|
+
check('节点创建自动生成工艺参数映射', !!prow && prow.key === 'temp-sp')
|
|
75
|
+
const pview0 = paramRepo.listViews().find(p => p.nodeId === node.id)
|
|
76
|
+
check('参数视图为语义面(无 driverConfig/寄存器/数据类型/字节序)', !!pview0
|
|
77
|
+
&& !('driverConfig' in (pview0 ?? {}))
|
|
78
|
+
&& !JSON.stringify(pview0).includes('register')
|
|
79
|
+
&& !JSON.stringify(pview0).includes('float32')
|
|
80
|
+
&& !JSON.stringify(pview0).includes('byteOrder'),
|
|
81
|
+
JSON.stringify(pview0).slice(0, 120))
|
|
82
|
+
check('参数视图携带驱动类别(仅类别)与产线归属', pview0?.driver === 'modbus-tcp' && pview0?.lineId === line.id)
|
|
83
|
+
const pid = pview0!.id
|
|
84
|
+
|
|
85
|
+
console.log('\n--- ② 真实 Modbus 参数写→回读 roundtrip(工程量纲直写) ---')
|
|
86
|
+
const w1 = await ctrl.writeParam(pid, 180, { source: 'manual', actor: 'e2e', actorName: 'E2E' })
|
|
87
|
+
check('参数写入成功(180℃)', w1.ok, w1.message)
|
|
88
|
+
check('PLC 同址回读一致(标定后物理值)', w1.readback != null && Math.abs((w1.readback ?? 0) - 180) < 0.5, `readback=${w1.readback}`)
|
|
89
|
+
check('执行节点设定值回填', ctrl.byId(node.id)?.value != null && Math.abs((ctrl.byId(node.id)!.value ?? 0) - 180) < 0.5)
|
|
90
|
+
await ctrl.readParam(pid).then((r) => {
|
|
91
|
+
check('参数读取成功(PLC 当前值)', r.ok && Math.abs((r.value ?? 0) - 180) < 0.5, `value=${r.value}`)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
console.log('\n--- ③ 工艺参数基准限界(常驻层) ---')
|
|
95
|
+
paramRepo.update(pid, { min: 100, max: 250 })
|
|
96
|
+
const eHi = await errOf(() => ctrl.writeParam(pid, 260))
|
|
97
|
+
check('超出基准限界 260 → 拒绝且点名参数层', eHi.status === 400 && eHi.message.includes('基准限界'), eHi.message.slice(0, 90))
|
|
98
|
+
const eLo = await errOf(() => ctrl.writeParam(pid, 90))
|
|
99
|
+
check('低于基准限界 90 → 拒绝', eLo.status === 400 && eLo.message.includes('基准限界'))
|
|
100
|
+
const w2 = await ctrl.writeParam(pid, 200)
|
|
101
|
+
check('限界内 200 → 通过', w2.ok)
|
|
102
|
+
const eNode = await errOf(() => ctrl.write(node.id, 260))
|
|
103
|
+
check('节点面直写越参数限界 → 同样拒绝(同一咽喉点)', eNode.status === 400 && eNode.message.includes('基准限界'))
|
|
104
|
+
|
|
105
|
+
console.log('\n--- ④ 产品限界 + 配方窗口(产线运行期分层联锁) ---')
|
|
106
|
+
const eBadKey = await errOf(() => ctrl.createProduct({ name: 'PP验证产品·坏键', lineId: line.id, paramLimits: { 'no-such-param': { min: 1, max: 2 } } }))
|
|
107
|
+
check('产品限界引用不存在的参数键 → 400(防静默失效)', eBadKey.status === 400 && eBadKey.message.includes('no-such-param'))
|
|
108
|
+
const eBadRange = await errOf(() => ctrl.createProduct({ name: 'PP验证产品·坏区间', lineId: line.id, paramLimits: { 'temp-sp': { min: 200, max: 150 } } }))
|
|
109
|
+
check('产品限界 min>max → 400', eBadRange.status === 400)
|
|
110
|
+
const product = ctrl.createProduct({
|
|
111
|
+
name: 'PP验证产品',
|
|
112
|
+
lineId: line.id,
|
|
113
|
+
paramLimits: { 'temp-sp': { min: 150, max: 210 } },
|
|
114
|
+
})
|
|
115
|
+
check('产品限界设定成功(150~210)', (product.paramLimits?.['temp-sp']?.max ?? 0) === 210)
|
|
116
|
+
const recipe = ctrl.createRecipe({
|
|
117
|
+
productId: product.id,
|
|
118
|
+
name: 'PP验证配方',
|
|
119
|
+
params: [{ nodeId: node.id, value: 180, min: 160, max: 220 }],
|
|
120
|
+
})
|
|
121
|
+
const run = await ctrl.lineStart(line.id, recipe.id)
|
|
122
|
+
check('产线开跑(批次激活)', !!run.id)
|
|
123
|
+
const eProduct = await errOf(() => ctrl.writeParam(pid, 215))
|
|
124
|
+
check('215 在参数限界内但超产品上限 210 → 拒绝且点名产品层', eProduct.status === 400 && eProduct.message.includes('产品'), eProduct.message.slice(0, 90))
|
|
125
|
+
const eRecipe = await errOf(() => ctrl.writeParam(pid, 155))
|
|
126
|
+
check('155 在产品限界内但低于配方下限 160 → 拒绝且点名配方层', eRecipe.status === 400 && eRecipe.message.includes('配方'), eRecipe.message.slice(0, 90))
|
|
127
|
+
const w3 = await ctrl.writeParam(pid, 170)
|
|
128
|
+
check('170 四层交集内 → 通过', w3.ok)
|
|
129
|
+
const eNodeGate = await errOf(() => ctrl.write(node.id, 215))
|
|
130
|
+
check('节点面直写 215 → 产品层同样拦截', eNodeGate.status === 400 && eNodeGate.message.includes('产品'))
|
|
131
|
+
|
|
132
|
+
console.log('\n--- ⑤ 配方下发同样受限(run.results 记失败;产线保持运行) ---')
|
|
133
|
+
ctrl.updateRecipe(recipe.id, { params: [{ nodeId: node.id, value: 215, min: 160, max: 220 }] }, { by: 'user', actor: 'e2e', actorName: 'E2E', description: '压到产品上限外验证下发拦截' })
|
|
134
|
+
const run2 = await ctrl.applyRecipe(recipe.id)
|
|
135
|
+
const r2 = run2.results[0] as { ok: boolean, message: string }
|
|
136
|
+
check('配方参数 215 超产品上限 210 → 下发失败入档(不阻塞批次)', r2.ok === false && r2.message.includes('产品'), r2.message.slice(0, 90))
|
|
137
|
+
ctrl.updateRecipe(recipe.id, { params: [{ nodeId: node.id, value: 180, min: 160, max: 220 }] }, { by: 'user', actor: 'e2e', actorName: 'E2E', description: '回退验证值' })
|
|
138
|
+
const run3 = await ctrl.applyRecipe(recipe.id)
|
|
139
|
+
check('配方参数 180 限界内 → 下发成功', (run3.results[0] as { ok: boolean }).ok === true)
|
|
140
|
+
// 跨配方下发不误伤:配方乙自身窗口 [150,210] 值 155,低于活动批次配方甲窗口下限 160
|
|
141
|
+
// —— 配方下发路径跳过配方层(自身窗口保存时已校验),产品/参数层照常 → 应放行
|
|
142
|
+
const recipeB = ctrl.createRecipe({ productId: product.id, name: 'PP验证配方·乙', params: [{ nodeId: node.id, value: 155, min: 150, max: 210 }] })
|
|
143
|
+
const runB = await ctrl.applyRecipe(recipeB.id)
|
|
144
|
+
const rB = runB.results[0] as { ok: boolean, message: string }
|
|
145
|
+
check('运行中应用配方乙(155 在甲窗口外、乙窗口内)→ 不被旧批次窗口误伤', rB.ok === true, rB.message.slice(0, 90))
|
|
146
|
+
|
|
147
|
+
console.log('\n--- ⑥ Agent 参数面(param_control/param_read;批次运行中) ---')
|
|
148
|
+
const AGENT = 'agent-pp-verify'
|
|
149
|
+
getAgentNodeBindingRepo().bind(AGENT, node.id, 'dcw', 'auto')
|
|
150
|
+
const t1 = await toolParamControl(AGENT, { param: 'temp-sp', value: 175 })
|
|
151
|
+
check('param_control 按 key 下发成功(语义回包含参数名/有效区间)', !t1.isError && t1.text.includes('工艺参数') && t1.text.includes('有效写入区间'), t1.text.slice(0, 100))
|
|
152
|
+
const t2 = await toolParamControl(AGENT, { param: 'temp-sp', value: 215 })
|
|
153
|
+
check('param_control 超产品限界 → 拒绝(文案含产品层)', t2.isError === true && t2.text.includes('产品'), t2.text.slice(0, 100))
|
|
154
|
+
const t3 = await toolParamControl(AGENT, { param: pid, value: 168 })
|
|
155
|
+
check('param_control 按 pp-id 下发成功', !t3.isError)
|
|
156
|
+
const t4 = await toolParamRead(AGENT, { param: 'temp-sp' })
|
|
157
|
+
check('param_read 返回读数 + 限界区间', !t4.isError && t4.text.includes('PLC 读数'), t4.text.slice(0, 80))
|
|
158
|
+
const t5 = await toolParamControl('agent-nobody', { param: 'temp-sp', value: 170 })
|
|
159
|
+
check('未绑定 Agent → 拒绝', t5.isError === true)
|
|
160
|
+
// 跨产线:第二条线同模板节点,key 产线内唯一
|
|
161
|
+
const line2 = getDcwLineRepo().create({ name: '参数映射验证线·乙' })
|
|
162
|
+
const node2 = ctrl.create({ templateRef: 'dcw-speed-sp', name: 'PP验证乙·速度', driver: 'mock', driverConfig: { key: `pp-e2e-2-${Date.now()}` }, readIntervalMs: 0, lineId: line2.id })
|
|
163
|
+
check('第二产线节点自动映射(speed-sp)', paramRepo.byNode(node2.id)?.key === 'speed-sp')
|
|
164
|
+
const t6 = await toolParamControl(AGENT, { param: 'speed-sp', value: 300 })
|
|
165
|
+
check('未绑定节点的参数 → 拒绝(权限沿用节点绑定)', t6.isError === true, t6.text.slice(0, 80))
|
|
166
|
+
|
|
167
|
+
console.log('\n--- ⑦ 级联:节点删除 → 参数映射自动清理 ---')
|
|
168
|
+
ctrl.remove(node2.id)
|
|
169
|
+
check('执行节点删除后映射移除', paramRepo.byNode(node2.id) == null)
|
|
170
|
+
|
|
171
|
+
console.log('\n--- ⑧ 运行中 plc-node-simulator(16040)连通性交叉验证 ---')
|
|
172
|
+
try {
|
|
173
|
+
const res = await fetch('http://127.0.0.1:4010/api/nodes', { signal: AbortSignal.timeout(3000) }).then(r => r.json()) as { data?: Array<{ id: string, protocol: string }> }
|
|
174
|
+
const mbNode = (res.data ?? []).find(n => n.protocol === 'modbus-tcp')
|
|
175
|
+
if (mbNode) {
|
|
176
|
+
const exp = await fetch(`http://127.0.0.1:4010/api/nodes/${mbNode.id}/export`, { signal: AbortSignal.timeout(3000) }).then(r => r.json()) as { data?: { items?: Array<{ driver: string, driverConfig: Record<string, unknown> }> } }
|
|
177
|
+
const cfg = exp.data?.items?.[0]?.driverConfig
|
|
178
|
+
const test = await ctrl.testDriver('modbus-tcp', cfg as Record<string, unknown>)
|
|
179
|
+
check('五协议模拟器导出配置 → 网关连通测试通过', test.ok === true, JSON.stringify(cfg))
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
check('五协议模拟器连通性交叉验证', false, '无 modbus-tcp 设备(模拟器未启动或无预设)')
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
catch (err) {
|
|
186
|
+
check('五协议模拟器连通性交叉验证', false, `模拟器不可达:${err instanceof Error ? err.message : String(err)}`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('\n--- ⑨ 标准转换模式:线级双向换算验证(原始寄存器字核对) ---')
|
|
190
|
+
const ModbusRTU = (await import('modbus-serial')).default
|
|
191
|
+
const raw = new ModbusRTU()
|
|
192
|
+
await raw.connectTCP('127.0.0.1', { port: SIM_PORT })
|
|
193
|
+
raw.setID(1)
|
|
194
|
+
// float32 直写模式:速度SP 40023(2 寄存器,大端)
|
|
195
|
+
const f32 = ctrl.createParamMapping({
|
|
196
|
+
key: 'oven_f32', templateRef: 'dcw-speed-sp', name: '转换验证·float32', lineId: line.id,
|
|
197
|
+
access: { host: '127.0.0.1', port: SIM_PORT, unitId: 1, register: 40023, conversion: { mode: 'float32' } },
|
|
198
|
+
})
|
|
199
|
+
check('access 一键建映射:自动建节点+参数面+float32 模式留存', !!f32.nodeId && f32.conversion?.mode === 'float32', `${f32.id} → ${f32.nodeId}`)
|
|
200
|
+
check('映射视图仍不透出 driverConfig', !JSON.stringify(f32).includes('driverConfig'))
|
|
201
|
+
await ctrl.writeParam(f32.id, 320)
|
|
202
|
+
const w32 = await raw.readHoldingRegisters(40023 - 40001, 2)
|
|
203
|
+
const buf32 = Buffer.alloc(4)
|
|
204
|
+
buf32.writeUInt16BE(w32.data[0]!, 0)
|
|
205
|
+
buf32.writeUInt16BE(w32.data[1]!, 2)
|
|
206
|
+
check('float32 直写:PLC 寄存器字 = float32BE(320)', Math.abs(buf32.readFloatBE(0) - 320) < 0.01, `words=[${w32.data}]`)
|
|
207
|
+
// int16 线性标定模式:eng 0~50 ↔ raw 0~500,寄存器 40025(1 寄存器)
|
|
208
|
+
const i16 = ctrl.createParamMapping({
|
|
209
|
+
key: 'oven_i16', templateRef: 'dcw-tension-sp', name: '转换验证·int16标定', lineId: line.id,
|
|
210
|
+
access: { host: '127.0.0.1', port: SIM_PORT, unitId: 1, register: 40025, conversion: { mode: 'int16-scaled', engMin: 0, engMax: 50, rawMin: 0, rawMax: 500 } },
|
|
211
|
+
})
|
|
212
|
+
await ctrl.writeParam(i16.id, 22)
|
|
213
|
+
const w16 = await raw.readHoldingRegisters(40025 - 40001, 1)
|
|
214
|
+
check('int16 线性标定写:eng 22 → 原始字 220(0.1 分辨率)', w16.data[0] === 220, `word=${w16.data[0]}`)
|
|
215
|
+
// 读方向:原始字 300 → 工程值 30.0(接收同样自动换算)
|
|
216
|
+
await raw.writeRegister(40025 - 40001, 300)
|
|
217
|
+
const rd16 = await ctrl.readParam(i16.id)
|
|
218
|
+
check('int16 线性标定读:原始字 300 → 工程值 30', rd16.ok && Math.abs((rd16.value ?? 0) - 30) < 0.01, `value=${rd16.value}`)
|
|
219
|
+
// 非法转换模式在配置期即拒
|
|
220
|
+
const eBadConv = await errOf(() => ctrl.createParamMapping({
|
|
221
|
+
key: 'bad-conv', templateRef: 'dcw-temp-sp', lineId: line.id,
|
|
222
|
+
access: { host: '127.0.0.1', register: 40021, conversion: { mode: 'int16-scaled' } as never },
|
|
223
|
+
}))
|
|
224
|
+
check('int16 标定缺量程 → 配置期 400 拒绝', eBadConv.status === 400, eBadConv.message.slice(0, 80))
|
|
225
|
+
raw.close()
|
|
226
|
+
|
|
227
|
+
console.log('\n--- 清理 ---')
|
|
228
|
+
// removeLine 自带活动批次窗口停止;lineStop 为同步抛错方法,勿在此直调
|
|
229
|
+
await ctrl.removeLine(line.id, { purge: true })
|
|
230
|
+
await ctrl.removeLine(line2.id, { purge: true })
|
|
231
|
+
check('产线 purge 后参数映射全部清理', paramRepo.listViews().filter(p => [line.id, line2.id].includes(p.lineId)).length === 0)
|
|
232
|
+
}
|
|
233
|
+
catch (err) {
|
|
234
|
+
failures += 1
|
|
235
|
+
console.error('\nE2E 异常中断:', err)
|
|
236
|
+
}
|
|
237
|
+
finally {
|
|
238
|
+
sim.kill()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(`\n${failures === 0 ? '✅ 全部通过' : `❌ ${failures} 项失败`} (AW_HOME=${awHome})`)
|
|
242
|
+
process.exit(failures === 0 ? 0 : 1)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工艺参数映射 REST 冒烟(真实 dev server):
|
|
3
|
+
* ①params 列表(登录态 + 产线可见性过滤) ②建节点自动生成参数面(REST 视图无寄存器细节)
|
|
4
|
+
* ③param 写/读路由 ④param 基准限界 → 越界 400 ⑤产品限界落库 → 运行期产品层拦截
|
|
5
|
+
* ⑥清理(产线 purge)。mock 驱动,不依赖外部模拟器。
|
|
6
|
+
* 运行: node scripts/_dbg-param-map-rest-smoke.mjs(ROOT/E2E_USER/E2E_PASS 可覆盖)
|
|
7
|
+
*/
|
|
8
|
+
const ROOT = process.env.E2E_ROOT ?? 'http://127.0.0.1:3001'
|
|
9
|
+
let failed = 0
|
|
10
|
+
const check = (name, ok, detail = '') => { console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`); if (!ok) failed++ }
|
|
11
|
+
|
|
12
|
+
/** 认证:AW_E2E_TOKEN 直用(隔离实例首注册 token);否则 E2E_USER/E2E_PASS 登录(docs/full-test-plan.md 测试账号) */
|
|
13
|
+
let __token = process.env.AW_E2E_TOKEN
|
|
14
|
+
if (!__token) {
|
|
15
|
+
const login = await fetch(`${ROOT}/api/users/login`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email: process.env.E2E_USER ?? 'admin@awshop.local', password: process.env.E2E_PASS ?? 'admin123' }) }).then(r => r.json())
|
|
16
|
+
__token = login.data?.token
|
|
17
|
+
check('登录', !!__token, login.message ?? '')
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
check('使用 AW_E2E_TOKEN', true)
|
|
21
|
+
}
|
|
22
|
+
const H = { authorization: `Bearer ${__token}`, 'content-type': 'application/json' }
|
|
23
|
+
const j = (u, m = 'GET', b) => fetch(ROOT + u, { method: m, headers: H, body: b ? JSON.stringify(b) : undefined }).then(r => r.json())
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// ① 列表路由
|
|
27
|
+
const list = await j('/api/workshop/dcw/params?limits=1')
|
|
28
|
+
check('GET /dcw/params 路由可用(含 limits 剖面)', Array.isArray(list.data?.params) && list.data.limits != null, `params=${list.data?.params?.length}`)
|
|
29
|
+
|
|
30
|
+
// ② 建线 + 建节点(mock)→ 自动参数面
|
|
31
|
+
const line = (await j('/api/workshop/dcw/lines', 'POST', { name: `PP冒烟线-${Date.now().toString(36)}` })).data?.line
|
|
32
|
+
check('创建产线', !!line?.id)
|
|
33
|
+
const node = (await j('/api/workshop/dcw', 'POST', { templateRef: 'dcw-pressure-sp', name: 'PP冒烟·熔体压力', driver: 'mock', driverConfig: { key: `pp-rest-${Date.now()}` }, readIntervalMs: 0, lineId: line.id })).data?.node
|
|
34
|
+
check('创建执行节点(mock)', !!node?.id)
|
|
35
|
+
const after = await j('/api/workshop/dcw/params')
|
|
36
|
+
const pv = after.data.params.find(p => p.nodeId === node.id)
|
|
37
|
+
check('节点创建自动生成参数面(pressure-sp)', pv?.key === 'pressure-sp', pv?.id)
|
|
38
|
+
check('REST 参数视图不含寄存器/数据类型细节', !JSON.stringify(pv).includes('register') && !JSON.stringify(pv).includes('dataType'))
|
|
39
|
+
|
|
40
|
+
// ③④ 参数写/读 + 基准限界
|
|
41
|
+
const w1 = (await j(`/api/workshop/dcw/params/${pv.id}/write`, 'POST', { value: 0.9 })).data?.outcome
|
|
42
|
+
check('param write 0.9MPa 成功(mock 回读一致)', w1?.ok === true, w1?.message)
|
|
43
|
+
const rd = (await j(`/api/workshop/dcw/params/${pv.id}/read`, 'POST', {})).data?.read
|
|
44
|
+
check('param read 返回物理值', rd?.ok === true && Math.abs((rd.value ?? 0) - 0.9) < 0.01, `value=${rd?.value}`)
|
|
45
|
+
await j(`/api/workshop/dcw/params/${pv.id}`, 'PATCH', { min: 0.7, max: 1.1 })
|
|
46
|
+
const w2 = await j(`/api/workshop/dcw/params/${pv.id}/write`, 'POST', { value: 1.15 })
|
|
47
|
+
check('越基准限界 1.15 → 400 点名参数层', w2.code === 'VALIDATION_ERROR' && w2.message?.includes('基准限界'), w2.message?.slice(0, 80))
|
|
48
|
+
|
|
49
|
+
// ⑤ 产品限界 → 运行期产品层拦截
|
|
50
|
+
const product = (await j('/api/workshop/dcw/products', 'POST', { name: 'PP冒烟产品', lineId: line.id, paramLimits: { 'pressure-sp': { min: 0.8, max: 1.0 } } })).data?.product
|
|
51
|
+
check('产品限界落库(0.8~1.0)', product?.paramLimits?.['pressure-sp']?.max === 1.0)
|
|
52
|
+
const recipe = (await j('/api/workshop/dcw/recipes', 'POST', { productId: product.id, name: 'PP冒烟配方', params: [{ nodeId: node.id, value: 0.9, min: 0.75, max: 1.05 }] })).data?.recipe
|
|
53
|
+
const start = await j(`/api/workshop/dcw/lines/${line.id}/start`, 'POST', { recipeId: recipe.id })
|
|
54
|
+
check('产线开跑', !!start.data?.run?.id, start.message)
|
|
55
|
+
const w3 = await j(`/api/workshop/dcw/params/${pv.id}/write`, 'POST', { value: 1.05 })
|
|
56
|
+
check('1.05 超产品上限 1.0 → 400 点名产品层', w3.code === 'VALIDATION_ERROR' && w3.message?.includes('产品'), w3.message?.slice(0, 80))
|
|
57
|
+
const w4 = (await j(`/api/workshop/dcw/params/${pv.id}/write`, 'POST', { value: 0.95 })).data?.outcome
|
|
58
|
+
check('0.95 交集内 → 通过', w4?.ok === true)
|
|
59
|
+
await j(`/api/workshop/dcw/lines/${line.id}/stop`, 'POST', {})
|
|
60
|
+
|
|
61
|
+
// ⑥ 清理
|
|
62
|
+
const del = await j(`/api/workshop/dcw/lines/${line.id}?purge=1`, 'DELETE')
|
|
63
|
+
check('产线 purge 清理', del.code === 0 || del.removed != null || del.data != null, JSON.stringify(del).slice(0, 80))
|
|
64
|
+
const afterDel = await j('/api/workshop/dcw/params')
|
|
65
|
+
check('purge 后参数面同步清理', !afterDel.data.params.some(p => p.id === pv.id))
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
failed += 1
|
|
69
|
+
console.error('冒烟异常中断:', err)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`\n${failed === 0 ? '✅ REST 冒烟全部通过' : `❌ ${failed} 项失败`}`)
|
|
73
|
+
process.exit(failed === 0 ? 0 : 1)
|