agentworkshop 0.7.39 → 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/host-tools.json +55 -0
- package/.output/.AgentWorkShop/prompts/host-tools.json +55 -0
- 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/{CUDwzgLV.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 +2225 -1445
- 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-CZmZFpqH.mjs → default-BE4r0FZN.mjs} +152 -58
- package/.output/server/chunks/build/{default-CZmZFpqH.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 +51 -25
- package/.output/server/chunks/virtual/precomputed.mjs +1 -1
- package/.output/server/package.json +1 -1
- package/README-zh.md +199 -35
- 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 -7
- package/app/components/AppSidebar.vue +117 -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/i18n/locales/en.ts +2 -0
- package/i18n/locales/zh-CN.ts +2 -0
- package/nuxt.config.ts +31 -0
- package/package.json +9 -1
- 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/api-live-e2e.mjs +40 -8
- package/scripts/capture-walkthrough.mjs +232 -0
- 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/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/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/CmsOtOAA2.js +0 -1
- package/.output/public/_nuxt/D1YDxZpZ.js +0 -1
- package/.output/public/_nuxt/D20Yamr4.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/4457d63f-0866-4cea-b320-04a321f24795.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.BXI_fZUi.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/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,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)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 真实场景测试:用户/Agent 只管工艺参数(设温度多少度),PLC 细节由标准转换模式封装。
|
|
3
|
+
*
|
|
4
|
+
* 前置:隔离 AW 实例(E2E_ROOT,首注册即 admin)+ 运行中的 plc-node-simulator(E2E_SIM_API)。
|
|
5
|
+
* ①配置阶段(集成工程师,一次性):取模拟器「对接导出」→ 以标准转换模式(float32)一键建工艺参数映射
|
|
6
|
+
* ②运行阶段(用户视角):参数列表(无寄存器细节)→ 设 温度185/210 → 模拟器 API 确认真实收到 →
|
|
7
|
+
* 参数读回一致 → 越基准限界 400 → 模拟器侧手动改值 → 用户参数读到新值(读方向同样自动换算)
|
|
8
|
+
* ③收尾:模拟器信号值复原 + 产线 purge。
|
|
9
|
+
* 运行: node scripts/_dbg-param-real-scenario.mjs
|
|
10
|
+
*/
|
|
11
|
+
const ROOT = process.env.E2E_ROOT ?? 'http://127.0.0.1:3399'
|
|
12
|
+
const SIM_API = process.env.E2E_SIM_API ?? 'http://127.0.0.1:4010'
|
|
13
|
+
let failed = 0
|
|
14
|
+
const check = (name, ok, detail = '') => { console.log(` ${ok ? 'PASS' : 'FAIL'} ${name}${detail ? ` — ${detail}` : ''}`); if (!ok) failed++ }
|
|
15
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
|
16
|
+
const j = async (base, u, m = 'GET', b, h = {}) => {
|
|
17
|
+
const r = await fetch(base + u, { method: m, headers: { 'content-type': 'application/json', ...h }, body: b ? JSON.stringify(b) : undefined }).then(x => x.json())
|
|
18
|
+
if (r.error) return { ...r, _status: r.statusCode }
|
|
19
|
+
return r
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ---- 认证:AW_E2E_TOKEN > token 缓存文件(首注册即 admin,复跑复用) > 现场注册 ----
|
|
23
|
+
let token = process.env.AW_E2E_TOKEN
|
|
24
|
+
const tokenFile = process.env.AW_SCENE_TOKEN_FILE ?? '.tmp-scene-home/admin-token.txt'
|
|
25
|
+
if (!token) {
|
|
26
|
+
try {
|
|
27
|
+
token = (await import('node:fs')).readFileSync(tokenFile, 'utf8').trim() || null
|
|
28
|
+
}
|
|
29
|
+
catch { /* 无缓存 */ }
|
|
30
|
+
if (token) check('复用已缓存管理员 token', true)
|
|
31
|
+
}
|
|
32
|
+
if (!token) {
|
|
33
|
+
const reg = await j(ROOT, '/api/workshop/users/register', 'POST', { name: `scene-admin-${Date.now().toString(36)}` })
|
|
34
|
+
token = reg.data?.token
|
|
35
|
+
check('注册管理员(隔离实例,首注册即 admin)', !!token)
|
|
36
|
+
if (token) {
|
|
37
|
+
try {
|
|
38
|
+
;(await import('node:fs')).mkdirSync(tokenFile.replace(/[/\\][^/\\]+$/, ''), { recursive: true })
|
|
39
|
+
;(await import('node:fs')).writeFileSync(tokenFile, token)
|
|
40
|
+
}
|
|
41
|
+
catch { /* 缓存失败不影响本次 */ }
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const H = { authorization: `Bearer ${token}` }
|
|
45
|
+
const api = (u, m = 'GET', b) => j(ROOT, u, m, b, H)
|
|
46
|
+
const sim = (u, m = 'GET', b) => j(SIM_API, u, m, b)
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// ---- ① 配置阶段(一次性;模拟器导出 → 标准转换模式建映射) ----
|
|
50
|
+
console.log('\n--- ① 配置阶段:模拟器对接导出 → 标准转换模式一键建工艺参数映射 ---')
|
|
51
|
+
const nodes = await sim('/api/nodes')
|
|
52
|
+
const dev = (nodes.data ?? []).find(n => n.protocol === 'modbus-tcp')
|
|
53
|
+
check('发现 plc-node-simulator Modbus TCP 设备', !!dev?.id, `${dev?.id}(${dev?.name})`)
|
|
54
|
+
const exp = await sim(`/api/nodes/${dev.id}/export`)
|
|
55
|
+
const items = exp.data?.items ?? []
|
|
56
|
+
check('导出对接配置(寄存器/数据类型/字节序由模拟器给出)', items.length >= 2, items.map(i => `${i.signal}@${i.driverConfig.register}`).join(', '))
|
|
57
|
+
|
|
58
|
+
const line = (await api('/api/workshop/dcw/lines', 'POST', { name: '1号挤出线(参数映射)' })).data?.line
|
|
59
|
+
check('创建产线', !!line?.id)
|
|
60
|
+
|
|
61
|
+
// 两个工艺参数:加热区1/加热区2 温度,float32 直写标准模式,基准限界 160~190(收窄于模板量程 150~200)
|
|
62
|
+
const mkParam = async (item, key, name) => {
|
|
63
|
+
const cfg = item.driverConfig
|
|
64
|
+
const r = await api('/api/workshop/dcw/params', 'POST', {
|
|
65
|
+
key, name, templateRef: 'dcw-temp-sp', lineId: line.id,
|
|
66
|
+
min: 160, max: 190,
|
|
67
|
+
access: {
|
|
68
|
+
host: cfg.host, port: cfg.port, unitId: cfg.unitId, register: cfg.register,
|
|
69
|
+
conversion: { mode: 'float32', byteOrder: cfg.byteOrder },
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
return r.data?.param
|
|
73
|
+
}
|
|
74
|
+
const p1 = await mkParam(items[0], 'zone1_temp', '加热区1温度设定')
|
|
75
|
+
const p2 = await mkParam(items[1], 'zone2_temp', '加热区2温度设定')
|
|
76
|
+
check('工艺参数「zone1_temp」建映射成功(自动建执行节点)', !!p1?.id && !!p1.nodeId, `${p1?.id} → ${p1?.nodeId}`)
|
|
77
|
+
check('转换模式已留存(float32)', p1?.conversion?.mode === 'float32')
|
|
78
|
+
check('映射视图不含寄存器/数据类型(用户不可见)', !JSON.stringify(p1).includes('register') && !JSON.stringify(p1).includes('dataType'))
|
|
79
|
+
|
|
80
|
+
// ---- ② 运行阶段(用户只碰 key + 工程值) ----
|
|
81
|
+
console.log('\n--- ② 运行阶段:用户设定工艺参数 → 模拟器真实接收 → 参数读回 ---')
|
|
82
|
+
const list = await api('/api/workshop/dcw/params?lineId=' + line.id)
|
|
83
|
+
check('用户参数列表(2 条工艺参数)', (list.data?.params ?? []).length === 2, list.data?.params?.map(p => `${p.key}=${p.value}${p.unit}`).join(', '))
|
|
84
|
+
|
|
85
|
+
const simValueOf = async (signalName) => {
|
|
86
|
+
const ns = await sim('/api/nodes')
|
|
87
|
+
const sg = (ns.data ?? []).find(n => n.id === dev.id)?.signals?.find(s => s.name === signalName || s.id === signalName)
|
|
88
|
+
return sg?.value
|
|
89
|
+
}
|
|
90
|
+
const userSet = async (param, value) => {
|
|
91
|
+
const w = await api(`/api/workshop/dcw/params/${param.id}/write`, 'POST', { value })
|
|
92
|
+
return { ok: w.data?.outcome?.ok, msg: w.data?.outcome?.message ?? w.message }
|
|
93
|
+
}
|
|
94
|
+
const userRead = async (param) => {
|
|
95
|
+
const r = await api(`/api/workshop/dcw/params/${param.id}/read`, 'POST', {})
|
|
96
|
+
return { value: r.data?.read?.value, ok: r.data?.read?.ok, msg: r.data?.read?.message }
|
|
97
|
+
}
|
|
98
|
+
// 模拟器 SP 可能带一阶收敛,轮询直至到位
|
|
99
|
+
const waitSimValue = async (signalName, target, timeoutMs = 8000) => {
|
|
100
|
+
const t0 = Date.now()
|
|
101
|
+
for (;;) {
|
|
102
|
+
const v = await simValueOf(signalName)
|
|
103
|
+
if (v != null && Math.abs(v - target) <= 0.6) return v
|
|
104
|
+
if (Date.now() - t0 > timeoutMs) return v
|
|
105
|
+
await sleep(400)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
console.log(' · 用户设定 加热区1温度 = 185℃')
|
|
110
|
+
const w1 = await userSet(p1, 185)
|
|
111
|
+
check('用户写入 185℃ 成功(含 PLC 回读确认)', w1.ok === true, w1.msg)
|
|
112
|
+
const sv1 = await waitSimValue(items[0].signal, 185)
|
|
113
|
+
check('PLC 模拟器真实收到:zone1 SP = 185', sv1 != null && Math.abs(sv1 - 185) <= 0.6, `sim.value=${sv1}`)
|
|
114
|
+
const r1 = await userRead(p1)
|
|
115
|
+
check('用户按参数读回 = 185℃(工程量纲)', r1.ok && Math.abs((r1.value ?? 0) - 185) <= 0.6, `value=${r1.value}`)
|
|
116
|
+
|
|
117
|
+
console.log(' · 用户设定 加热区2温度 = 175℃')
|
|
118
|
+
const w2 = await userSet(p2, 175)
|
|
119
|
+
check('用户写入 175℃ 成功', w2.ok === true, w2.msg)
|
|
120
|
+
const sv2 = await waitSimValue(items[1].signal, 175)
|
|
121
|
+
check('PLC 模拟器真实收到:zone2 SP = 175', sv2 != null && Math.abs(sv2 - 175) <= 0.6, `sim.value=${sv2}`)
|
|
122
|
+
|
|
123
|
+
console.log(' · 用户越界尝试 195℃...基准限界上限是 190(节点量程 150~200 之内)')
|
|
124
|
+
const w3 = await api(`/api/workshop/dcw/params/${p2.id}/write`, 'POST', { value: 195 })
|
|
125
|
+
check('越基准限界 → 400 点名参数层(用户不可越界)', (w3._status === 400 || w3.code === 'VALIDATION_ERROR') && String(w3.message).includes('基准限界'), String(w3.message).slice(0, 80))
|
|
126
|
+
|
|
127
|
+
console.log(' · 现场侧把 zone1 就地改到 172℃(模拟读方向)')
|
|
128
|
+
// 模拟器 manual 接口按信号 id 寻址(导出的 signal 字段是中文名,先换 id)
|
|
129
|
+
const simSignals = (await sim('/api/nodes')).data?.find(n => n.id === dev.id)?.signals ?? []
|
|
130
|
+
const signalIdOf = (name) => simSignals.find(s => s.name === name || s.id === name)?.id
|
|
131
|
+
await sim(`/api/nodes/${dev.id}/signals/${signalIdOf(items[0].signal)}/manual`, 'POST', { value: 172 })
|
|
132
|
+
await sleep(600)
|
|
133
|
+
const r2 = await userRead(p1)
|
|
134
|
+
check('用户参数读到现场实际值 172℃(接收方向同样自动换算)', r2.ok && Math.abs((r2.value ?? 0) - 172) <= 0.6, `value=${r2.value}`)
|
|
135
|
+
|
|
136
|
+
// ---- ③ 收尾 ----
|
|
137
|
+
console.log('\n--- ③ 收尾:模拟器复原 + 产线清理 ---')
|
|
138
|
+
for (const item of items.slice(0, 2)) {
|
|
139
|
+
await sim(`/api/nodes/${dev.id}/signals/${signalIdOf(item.signal)}/manual`, 'POST', { value: 200 })
|
|
140
|
+
}
|
|
141
|
+
const back1 = await waitSimValue(items[0].signal, 200, 5000)
|
|
142
|
+
check('模拟器信号值复原(200)', back1 != null && Math.abs(back1 - 200) <= 0.6, `sim.value=${back1}`)
|
|
143
|
+
await api(`/api/workshop/dcw/lines/${line.id}?purge=1`, 'DELETE')
|
|
144
|
+
const after = await api('/api/workshop/dcw/params')
|
|
145
|
+
check('产线 purge 后工艺参数映射清零', !(after.data?.params ?? []).some(p => [p1.id, p2.id].includes(p.id)))
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
failed += 1
|
|
149
|
+
console.error('场景异常中断:', err)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
console.log(`\n${failed === 0 ? '✅ 真实场景全部通过' : `❌ ${failed} 项失败`}`)
|
|
153
|
+
process.exit(failed === 0 ? 0 : 1)
|
package/scripts/api-live-e2e.mjs
CHANGED
|
@@ -19,6 +19,24 @@
|
|
|
19
19
|
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3001'
|
|
20
20
|
const WS_BASE = BASE.replace(/^http/, 'ws')
|
|
21
21
|
|
|
22
|
+
// 网络层瞬断(本机代理 churn 间歇占满回环临时端口 → fetch failed/EADDRINUSE)
|
|
23
|
+
// 用退避重试吸收;HTTP 语义错误不重试。与 bench/lib 侧瞬断策略一致。
|
|
24
|
+
const rawFetch = globalThis.fetch.bind(globalThis)
|
|
25
|
+
globalThis.fetch = async (input, init = {}) => {
|
|
26
|
+
let lastErr
|
|
27
|
+
for (let attempt = 1; attempt <= 8; attempt++) {
|
|
28
|
+
try {
|
|
29
|
+
return await rawFetch(input, { ...init, signal: init.signal ?? AbortSignal.timeout(30_000) })
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
lastErr = err
|
|
33
|
+
if (init.signal?.aborted) throw err
|
|
34
|
+
if (attempt < 8) await new Promise(r => setTimeout(r, Math.min(800 * 2 ** (attempt - 1), 8000)))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
throw lastErr
|
|
38
|
+
}
|
|
39
|
+
|
|
22
40
|
let failures = 0
|
|
23
41
|
let passed = 0
|
|
24
42
|
let step = 0
|
|
@@ -109,20 +127,34 @@ async function main() {
|
|
|
109
127
|
const ch0 = await api('GET', '/api/workshop/channels')
|
|
110
128
|
check('GET /channels 恢复 channel 列表', ch0.code === 0 && Array.isArray(ch0.data), `count=${ch0.data?.length}`)
|
|
111
129
|
const persisted = (ch0.data ?? []).filter(c => c.leadAgentId)
|
|
112
|
-
|
|
130
|
+
// 该子检查依赖「上一轮遗留的 channel-with-lead」。首次运行/上轮已清理时按项目
|
|
131
|
+
// 失败语义诚实 skip(bench/PIPELINE.md:环境不具备宁可 skip 并写明原因,绝不伪造通过),
|
|
132
|
+
// 不计入 fail,也不得因缺前置数据而崩溃。
|
|
133
|
+
const hasPersisted = persisted.length >= 1
|
|
134
|
+
if (hasPersisted) {
|
|
135
|
+
check('恢复的 channel 带 lead 实例', true, `with-lead=${persisted.length}`)
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
console.log(' SKIP 恢复的 channel 带 lead 实例 — 实例无历史 channel-with-lead(首次运行或上轮已清理);跨重启持久化由独立重启演练覆盖')
|
|
139
|
+
}
|
|
113
140
|
|
|
114
141
|
const rt0 = await api('GET', '/api/workshop/runtime')
|
|
115
142
|
check('GET /runtime 形状(懒加载状态)', rt0.code === 0 && Array.isArray(rt0.data?.wiredAgents) && Array.isArray(rt0.data?.activeChannels), `wired=${rt0.data?.wiredAgents?.length} active=${rt0.data?.activeChannels?.length}`)
|
|
116
143
|
const rt0Wired = rt0.data?.wiredAgents?.length ?? 0
|
|
117
144
|
|
|
118
145
|
// 详情请求触发 ensureChannelActive(懒加载恢复)
|
|
119
|
-
const probeId = persisted[0].id
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
const probeId = hasPersisted ? persisted[0].id : null
|
|
147
|
+
if (probeId) {
|
|
148
|
+
const probe = await api('GET', `/api/workshop/channels/${probeId}`)
|
|
149
|
+
check('GET /channels/:id 详情(含成员)', probe.code === 0 && Array.isArray(probe.data?.agents) && probe.data.agents.length > 0, `agents=${probe.data?.agents?.length}`)
|
|
150
|
+
const rt1 = await api('GET', '/api/workshop/runtime')
|
|
151
|
+
check('详情请求后 lead 运行时装配(懒加载)', rt1.code === 0 && (rt1.data?.wiredAgents?.length ?? 0) >= rt0Wired, `wired=${rt1.data?.wiredAgents?.length} active=${rt1.data?.activeChannels?.length}`)
|
|
152
|
+
const persistedTasks = await api('GET', `/api/workshop/channels/${probeId}/tasks`)
|
|
153
|
+
check('GET /channels/:id/tasks 恢复历史任务', persistedTasks.code === 0, `tasks=${persistedTasks.data?.length}`)
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
console.log(' SKIP 详情/懒加载装配/历史任务 — 无持久化 channel 可探')
|
|
157
|
+
}
|
|
126
158
|
|
|
127
159
|
// ═══════════ 2. AGENT 模板 CRUD ═══════════
|
|
128
160
|
section('AGENT 模板 CRUD(全局定义)')
|