agentworkshop 0.7.39 → 0.7.41
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/{CY9LbPCI.js → BN16aRdQ.js} +1 -1
- package/.output/public/_nuxt/BWVhZntO.js +1 -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/{DUrecWPx.js → C0ZrS1KL.js} +2 -2
- package/.output/public/_nuxt/{C_OLzD09.js → C4oW1HsE.js} +1 -1
- package/.output/public/_nuxt/{CUDwzgLV.js → C4s_niy-.js} +9 -9
- package/.output/public/_nuxt/CFxtLwjp.js +1 -0
- package/.output/public/_nuxt/{Cec0Ua3F.js → CS50LB_l.js} +1 -1
- package/.output/public/_nuxt/CssgYAAL2.js +1 -0
- package/.output/public/_nuxt/D6mbFA-a.js +1 -0
- package/.output/public/_nuxt/D98NH8kG.js +1 -0
- package/.output/public/_nuxt/DGIuvyRN2.js +1 -0
- package/.output/public/_nuxt/DGbd-UiG.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/{45zuyAso.js → D_OTvQza.js} +1 -1
- package/.output/public/_nuxt/Dap5PmTL.js +2 -0
- package/.output/public/_nuxt/Dqw1lpr-.js +1 -0
- package/.output/public/_nuxt/DvLxILQP.js +1 -0
- package/.output/public/_nuxt/IREZ1eC2.js +1 -0
- package/.output/public/_nuxt/TownView.f-YOdXRs.css +1 -0
- package/.output/public/_nuxt/_id_.D9ymtHB5.css +1 -0
- package/.output/public/_nuxt/_id_.jf-_8E_c.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/78a295bb-2072-48a4-8b46-6d545fdae743.json +1 -0
- package/.output/public/_nuxt/ca4TJ3Op.js +1 -0
- package/.output/public/_nuxt/channel-templates.BOtahzPi.css +1 -0
- package/.output/public/_nuxt/daq.DjSgY94l.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/eqq3wWQF.js +18 -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 +2355 -1362
- package/.output/server/chunks/_/nitro.mjs.map +1 -1
- package/.output/server/chunks/build/{TownView-jVazYOjN.mjs → TownView--otsnr_7.mjs} +290 -208
- package/.output/server/chunks/build/TownView--otsnr_7.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-CfVosNg3.mjs → _id_-Brv-JSvr.mjs} +142 -141
- package/.output/server/chunks/build/_id_-Brv-JSvr.mjs.map +1 -0
- package/.output/server/chunks/build/{_id_-Hwr4rZpr.mjs → _id_-D9pl1OZy.mjs} +38 -36
- package/.output/server/chunks/build/_id_-D9pl1OZy.mjs.map +1 -0
- package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs → _wsId_-BASfDf0s.mjs} +295 -261
- package/.output/server/chunks/build/{_wsId_-BVg3bB77.mjs.map → _wsId_-BASfDf0s.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-DkMvD8sg.mjs} +193 -126
- package/.output/server/chunks/build/daq-DkMvD8sg.mjs.map +1 -0
- package/.output/server/chunks/build/{dcw-6vEYVeje.mjs → dcw-C1jgVJkO.mjs} +46 -46
- package/.output/server/chunks/build/{dcw-6vEYVeje.mjs.map → dcw-C1jgVJkO.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--dByNyhw.mjs} +35 -35
- package/.output/server/chunks/build/{logs-B-xhuqln.mjs.map → logs--dByNyhw.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-B0bUWnC6.mjs} +35 -19
- package/.output/server/chunks/build/pages-B0bUWnC6.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-CoQdblbZ.mjs} +38 -12
- package/.output/server/chunks/build/town-CoQdblbZ.mjs.map +1 -0
- package/.output/server/chunks/build/{useDaqStream-CIBaiGAl.mjs → useDaqStream-DfCFpb3p.mjs} +9 -3
- package/.output/server/chunks/build/useDaqStream-DfCFpb3p.mjs.map +1 -0
- package/.output/server/chunks/build/{useDcwStream-CBbiYnWe.mjs → useDcwStream-UPiX-3iO.mjs} +11 -2
- package/.output/server/chunks/build/useDcwStream-UPiX-3iO.mjs.map +1 -0
- package/.output/server/chunks/build/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/start.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/lines/_id/stop.post.mjs +1 -1
- package/.output/server/chunks/routes/api/workshop/dcw/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/index5.get.mjs +3 -1
- package/.output/server/chunks/routes/api/workshop/index5.get.mjs.map +1 -1
- package/.output/server/chunks/routes/api/workshop/index6.get.mjs +5 -1
- 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 +203 -39
- 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/useDaqStream.ts +7 -4
- package/app/composables/workshop/useDcwStream.ts +8 -2
- 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 +12 -7
- package/app/pages/daq/index.vue +216 -13
- package/app/pages/dcw/[id].vue +117 -32
- 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/cli.en.md +2 -2
- package/docs/cli.md +2 -2
- package/docs/plugins.en.md +33 -1
- package/docs/plugins.md +29 -1
- package/docs/sdk.en.md +1 -1
- package/docs/sdk.md +1 -1
- package/i18n/locales/en.ts +2 -0
- package/i18n/locales/zh-CN.ts +2 -0
- package/nuxt.config.ts +31 -0
- package/package.json +11 -2
- 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/_loop-biax-campaign.mjs +124 -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/test-serial-bridge.mjs +418 -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/e2e-interactive.mjs +328 -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/seed-industrial-demo.mjs +171 -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/index.get.ts +3 -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 +5 -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 +23 -10
- package/server/plugins/dev-stability-guard.ts +3 -1
- package/server/plugins/system-config.ts +6 -1
- package/server/plugins-builtin/serial-bridge/client.mjs +210 -0
- package/server/plugins-builtin/serial-bridge/drivers.mjs +409 -0
- package/server/plugins-builtin/serial-bridge/i18n.json +62 -0
- package/server/plugins-builtin/serial-bridge/index.mjs +247 -0
- package/server/plugins-builtin/serial-bridge/lib.mjs +208 -0
- package/server/plugins-builtin/serial-bridge/rtu.mjs +168 -0
- package/server/services/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 +25 -5
- package/server/services/workshop/daq/daq-runtime.ts +5 -3
- package/server/services/workshop/daq/drivers.ts +141 -11
- 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 +147 -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 +90 -4
- 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/plugin-bridge.ts +45 -0
- package/server/services/workshop/dcw/recipe-rollback-manager.ts +13 -7
- package/server/services/workshop/plugins/host.mjs +30 -0
- 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/daq-protocol.ts +15 -0
- package/shared/dcw-protocol.ts +136 -0
- package/uno.config.ts +12 -1
- package/.output/public/_nuxt/8NMOXZOV.js +0 -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/DAe0h2MB.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_-CfVosNg3.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/useDaqStream-CIBaiGAl.mjs.map +0 -1
- package/.output/server/chunks/build/useDcwStream-CBbiYnWe.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
|
@@ -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)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 双拉任务确定性 campaign —— R11 Major-1 的实验落地:
|
|
3
|
+
* 固定模拟器状态(plantReset warm)+ 固定 commit + 固定配方,连续执行 N=30 次
|
|
4
|
+
* AgentTeam 双拉闭环任务,记录每次入带结果,给出入带率的 Wilson 95% CI。
|
|
5
|
+
* 预注册判据:入带 = |最终厚度 − 25.0| ≤ 0.7 µm;每次写后等待 ≥12s 运输滞后。
|
|
6
|
+
* 运行: AW_BASE=http://127.0.0.1:3005 NO_PROXY=127.0.0.1,localhost node scripts/_loop-biax-campaign.mjs [N]
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
9
|
+
import { join, resolve, dirname } from 'node:path'
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
const REPO = resolve(HERE, '..')
|
|
14
|
+
const N = Number(process.argv[2] ?? 30)
|
|
15
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3005'
|
|
16
|
+
const OUT = join(REPO, 'bench', 'results', 'biax-campaign')
|
|
17
|
+
const SIM = process.env.E2E_SIM_API ?? 'http://127.0.0.1:4010'
|
|
18
|
+
const fetchRetry = async (url, opts = {}, tries = 8) => {
|
|
19
|
+
let lastErr
|
|
20
|
+
for (let i = 1; i <= tries; i++) {
|
|
21
|
+
try { return await fetch(url, { ...opts, signal: AbortSignal.timeout(20000) }) }
|
|
22
|
+
catch (e) { lastErr = e; await new Promise(r => setTimeout(r, Math.min(500 * 2 ** (i - 1), 6000))) }
|
|
23
|
+
}
|
|
24
|
+
throw lastErr
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
mkdirSync(OUT, { recursive: true })
|
|
28
|
+
|
|
29
|
+
const BS = String.fromCharCode(92)
|
|
30
|
+
const sleep = (ms) => new Promise(r => setTimeout(r, ms))
|
|
31
|
+
|
|
32
|
+
const login = await (await fetch(BASE + '/api/users/login', {
|
|
33
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
34
|
+
body: JSON.stringify({ email: 'admin@awshop.local', password: 'admin123' }),
|
|
35
|
+
})).json()
|
|
36
|
+
const H = { authorization: `Bearer ${login.data.token}`, 'content-type': 'application/json' }
|
|
37
|
+
const api = {
|
|
38
|
+
call: async (method, u, b) => (await fetch(BASE + u, { method, headers: H, body: b ? JSON.stringify(b) : undefined })).json(),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const { ensureSimulator, simUp, simNodes, simExport, plantReset } = await import(pathToFileURL(join(REPO, 'bench', 'lib', 'sim.mjs')).href)
|
|
42
|
+
const { provisionBiaxLine, runBiaxMission, ensureBiaxLine } = await import(pathToFileURL(join(REPO, 'bench', 'lib', 'biax.mjs')).href)
|
|
43
|
+
const { makeApi } = await import(pathToFileURL(join(REPO, 'bench', 'lib', 'util.mjs')).href)
|
|
44
|
+
|
|
45
|
+
if (!(await simUp())) {
|
|
46
|
+
console.error('模拟器不可达:请先启动 plc-node-simulator (:4010)')
|
|
47
|
+
process.exit(1)
|
|
48
|
+
}
|
|
49
|
+
await ensureSimulator().catch(() => {})
|
|
50
|
+
|
|
51
|
+
const sfx = 'camp' + Date.now().toString(36).slice(-4)
|
|
52
|
+
// 平台夹具:channel + mock lead/worker(任务板剧本)+ opencode 绑定执行体
|
|
53
|
+
const ch = await api.call('POST', '/api/workshop/channels', { name: `biax-campaign-${sfx}`, leadAgent: { name: `clead-${sfx}`, harness: 'mock', config: { delayMs: 40 } } })
|
|
54
|
+
const channelId = ch.data?.channelId ?? ch.data?.channel?.id ?? ch.data?.id
|
|
55
|
+
const worker = await api.call('POST', '/api/workshop/agents', { name: `cworker-${sfx}`, harness: 'mock', config: { delayMs: 60 } })
|
|
56
|
+
await api.call('POST', `/api/workshop/channels/${channelId}/agents`, { agentId: worker.data?.id, role: 'worker' })
|
|
57
|
+
const agent = await api.call('POST', '/api/workshop/agents', { name: `cexec-${sfx}`, harness: 'opencode', config: {} })
|
|
58
|
+
const joinResp = await api.call('POST', `/api/workshop/channels/${channelId}/agents`, { agentId: agent.data?.id, role: 'worker' })
|
|
59
|
+
const instId = joinResp.data?.id ?? joinResp.data?.agentId
|
|
60
|
+
if (!instId) { console.error('执行体创建失败'); process.exit(1) }
|
|
61
|
+
|
|
62
|
+
const runs = []
|
|
63
|
+
const stateFile = join(OUT, 'campaign-state.json')
|
|
64
|
+
const save = () => writeFileSync(stateFile, JSON.stringify({ sfx, startedAt: new Date().toISOString(), runs }, null, 1))
|
|
65
|
+
|
|
66
|
+
console.log(`campaign start: N=${N} base=${BASE} sim=:4010`)
|
|
67
|
+
for (let k = 1; k <= N; k++) {
|
|
68
|
+
const t0 = Date.now()
|
|
69
|
+
// ① 固定模拟器状态(确定性根修:零随机漂移 + warm 标称)
|
|
70
|
+
await plantReset({ warm: true, disturbances: { heaterDecay: 1, feedDriftPerMin: 0 } })
|
|
71
|
+
await sleep(1500)
|
|
72
|
+
// ②.5 SP 归一化:崩塌任务会把 SP 写到量程外(实测 pump-sp=0 残留),起跑前统一设回
|
|
73
|
+
// 量程内 35% 位(确定性起点;任务只写铸速/快辊/轨宽三旋钮,其余 SP 仅作配方合法值)
|
|
74
|
+
{
|
|
75
|
+
const nodes = (await simNodes()).filter(n => n.id.startsWith('biax-'))
|
|
76
|
+
for (const dev of nodes) {
|
|
77
|
+
for (const sig of dev.signals ?? []) {
|
|
78
|
+
if (!/SP/i.test(sig.id) && !/SP/i.test(sig.name ?? '')) continue
|
|
79
|
+
const lo = sig.min ?? 0, hi = sig.max ?? 100
|
|
80
|
+
const v = Number(sig.value)
|
|
81
|
+
if (!Number.isFinite(v) || v < lo + 0.05 * (hi - lo)) {
|
|
82
|
+
const target = +(lo + 0.35 * (hi - lo)).toFixed(sig.decimals ?? 1)
|
|
83
|
+
await fetchRetry(SIM + '/api/nodes/' + dev.id + '/signals/' + encodeURIComponent(sig.id) + '/manual', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ value: target }) }).catch(() => {})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
await sleep(1200)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ② 每轮独立布线(探测补建幂等;节点随 purge 回收)
|
|
91
|
+
const ensured = await ensureBiaxLine()
|
|
92
|
+
if (!ensured?.verified) throw new Error('ensureBiaxLine 失败')
|
|
93
|
+
const provisioned = await provisionBiaxLine(api, { sfx: sfx + k })
|
|
94
|
+
const line = provisioned
|
|
95
|
+
// ③ 绑定执行体到三个执行节点的 DCW
|
|
96
|
+
for (const nodeId of Object.values(line.dcw)) {
|
|
97
|
+
await api.call('POST', '/api/workshop/agent-tools/bindings', { agentId: instId, nodeId, kind: 'dcw', mode: 'auto' })
|
|
98
|
+
}
|
|
99
|
+
await sleep(2000)
|
|
100
|
+
// ④ 任务(含治理写与判定)
|
|
101
|
+
const mission = await runBiaxMission(api, { instId, line, simDevices: (await simNodes()).filter(n => n.id.startsWith('biax')), sfx: sfx + k, maxRounds: 6 })
|
|
102
|
+
if (k === 1 || !mission.attained) console.log('DIAG provErrors:', JSON.stringify(provisioned.errors ?? []), '| dcw:', Object.keys(line.dcw ?? {}).length, '| daq:', Object.keys(line.daq ?? {}).length, '| ids:', JSON.stringify(line.ids ?? {}))
|
|
103
|
+
const run = {
|
|
104
|
+
k, attained: mission.attained, thickness: mission.thickness,
|
|
105
|
+
writes: mission.writes, knobs: mission.distinctNodes.size, wallS: Math.round((Date.now() - t0) / 1000),
|
|
106
|
+
}
|
|
107
|
+
runs.push(run)
|
|
108
|
+
save()
|
|
109
|
+
console.log(`[${k}/${N}] attained=${run.attained} h=${run.thickness?.toFixed(2)}µm writes=${run.writes} wall=${run.wallS}s`)
|
|
110
|
+
// ⑤ 收尾:停线防批次泄漏
|
|
111
|
+
await api.call('POST', `/api/workshop/dcw/lines/${line.ids.line}/stop`, {}).catch(() => {})
|
|
112
|
+
await api.call('DELETE', `/api/workshop/dcw/lines/${line.ids.line}?purge=1`, {}).catch(() => {})
|
|
113
|
+
await sleep(2000)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const ok = runs.filter(r => r.attained).length
|
|
117
|
+
const n = runs.length
|
|
118
|
+
const p = ok / n
|
|
119
|
+
const z = 1.96
|
|
120
|
+
const ci = n ? [p - z * Math.sqrt((p * (1 - p)) / n), p + z * Math.sqrt((p * (1 - p)) / n)] : [0, 0]
|
|
121
|
+
const summary = { n, ok, rate: +p.toFixed(3), wilson95: ci.map(c => +c.toFixed(3)), runs }
|
|
122
|
+
writeFileSync(join(OUT, 'campaign-summary.json'), JSON.stringify(summary, null, 1))
|
|
123
|
+
console.log(`\n结果: ${ok}/${n} 入带,rate=${(p * 100).toFixed(1)}%,Wilson95=[${ci[0].toFixed(3)},${ci[1].toFixed(3)}]`)
|
|
124
|
+
console.log(`产物: ${OUT}`)
|
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(全局定义)')
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical walkthrough-figure capture. High-DPI (3x), element/row-accurate crops
|
|
3
|
+
* of the four operational stages on a live AgentWorkShop instance.
|
|
4
|
+
*
|
|
5
|
+
* The shared production instance carries Chinese-named test data, so the four
|
|
6
|
+
* screenshots are taken against the small ENGLISH fixture created by
|
|
7
|
+
* scripts/seed-walkthrough-fixture.mjs (line "Cast-film line A", English DAQ
|
|
8
|
+
* nodes, an English agent team). The UI is forced to the `en` locale (persisted
|
|
9
|
+
* in localStorage under `aw.locale`, applied by app/plugins/locale-restore.client.ts)
|
|
10
|
+
* before any capture, so every UI string is English.
|
|
11
|
+
*
|
|
12
|
+
* Geometry discipline: every crop is ≤470 CSS px wide (12 px UI text must print
|
|
13
|
+
* ≥ ~6.4 pt at the ~88 mm panel width of the 181 mm figure), except panel 2 whose
|
|
14
|
+
* four table columns cannot fit narrower.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* NO_PROXY='127.0.0.1,localhost' node scripts/seed-walkthrough-fixture.mjs
|
|
18
|
+
* NO_PROXY='127.0.0.1,localhost' AW_BASE=http://127.0.0.1:3005 node scripts/capture-walkthrough.mjs
|
|
19
|
+
*/
|
|
20
|
+
import puppeteer from 'puppeteer-core'
|
|
21
|
+
import fs from 'node:fs'
|
|
22
|
+
import path from 'node:path'
|
|
23
|
+
|
|
24
|
+
const BASE = process.env.AW_BASE ?? 'http://127.0.0.1:3005'
|
|
25
|
+
const OUT = process.env.AW_OUT ?? 'paper/tii/figures/walkthrough'
|
|
26
|
+
fs.mkdirSync(OUT, { recursive: true })
|
|
27
|
+
|
|
28
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms))
|
|
29
|
+
const tok = async (e, p) => {
|
|
30
|
+
const r = await (await fetch(`${BASE}/api/users/login`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ email: e, password: p }) })).json()
|
|
31
|
+
if (!r.data?.token) throw new Error(`login failed: ${e} (${JSON.stringify(r).slice(0, 120)})`)
|
|
32
|
+
return r.data.token
|
|
33
|
+
}
|
|
34
|
+
const ADMIN = await tok('admin@awshop.local', 'admin123')
|
|
35
|
+
|
|
36
|
+
const jget = async (u) => {
|
|
37
|
+
const r = await (await fetch(`${BASE}${u}`, { headers: { Authorization: `Bearer ${ADMIN}` } })).json()
|
|
38
|
+
return r.data
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── fixture discovery (fail loudly rather than silently capturing Chinese) ──
|
|
42
|
+
const LINES = (await jget('/api/workshop/dcw/lines')).lines
|
|
43
|
+
const LINE = LINES.find(l => l.name === 'Cast-film line A')
|
|
44
|
+
if (!LINE) throw new Error('English fixture line "Cast-film line A" missing — run scripts/seed-walkthrough-fixture.mjs first')
|
|
45
|
+
const DAQ = await jget('/api/workshop/daq')
|
|
46
|
+
const modbusNode = DAQ.nodes.find(n => n.lineId === LINE.id && n.driver === 'modbus-tcp')
|
|
47
|
+
if (!modbusNode) throw new Error('fixture Modbus-TCP node missing on Cast-film line A')
|
|
48
|
+
// panel 4 uses the real judged optimization records already on the ledger of this node
|
|
49
|
+
const RECORD_LINE = 'ln-af002514'
|
|
50
|
+
const RECORD_NODE = 'dw-322b1978'
|
|
51
|
+
console.log(`fixture line=${LINE.id} modbus=${modbusNode.id} (${modbusNode.name}) records=${RECORD_NODE}`)
|
|
52
|
+
|
|
53
|
+
const browser = await puppeteer.launch({
|
|
54
|
+
executablePath: process.env.AW_CHROME ?? 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
|
55
|
+
headless: 'new',
|
|
56
|
+
args: ['--no-sandbox', '--disable-gpu', '--proxy-server=direct://', '--proxy-bypass-list=*'],
|
|
57
|
+
})
|
|
58
|
+
const page = await browser.newPage()
|
|
59
|
+
const errs = []
|
|
60
|
+
page.on('pageerror', e => errs.push(String(e).slice(0, 120)))
|
|
61
|
+
await page.setViewport({ width: 1680, height: 1600, deviceScaleFactor: 3 })
|
|
62
|
+
|
|
63
|
+
const auth = async (t) => { const c = await page.target().createCDPSession(); await c.send('Network.clearBrowserCookies'); await page.setCookie({ name: 'token', value: t, domain: '127.0.0.1', path: '/' }) }
|
|
64
|
+
const goto = async (u, w = 10000) => { await page.goto(`${BASE}${u}`, { waitUntil: 'domcontentloaded', timeout: 90_000 }); await sleep(w) }
|
|
65
|
+
|
|
66
|
+
// content-visibility:auto sections report estimated rects until rendered; force real
|
|
67
|
+
// layout so element-accurate clips do not drift. Also kill CSS transitions/animations
|
|
68
|
+
// so a mid-transition frame (e.g. the hold-interval cell double-painting its value)
|
|
69
|
+
// can never be captured.
|
|
70
|
+
const settle = async () => {
|
|
71
|
+
await page.evaluate(() => {
|
|
72
|
+
let st = document.getElementById('aw-capture-calm')
|
|
73
|
+
if (!st) {
|
|
74
|
+
st = document.createElement('style')
|
|
75
|
+
st.id = 'aw-capture-calm'
|
|
76
|
+
st.textContent = '*,*::before,*::after{transition:none!important;transition-duration:0s!important;animation:none!important;animation-duration:0s!important;animation-delay:0s!important}'
|
|
77
|
+
document.head.appendChild(st)
|
|
78
|
+
}
|
|
79
|
+
for (const e of document.querySelectorAll('*')) {
|
|
80
|
+
const cs = getComputedStyle(e)
|
|
81
|
+
if (cs.contentVisibility && cs.contentVisibility !== 'visible') e.style.contentVisibility = 'visible'
|
|
82
|
+
}
|
|
83
|
+
window.scrollTo(0, document.body.scrollHeight)
|
|
84
|
+
})
|
|
85
|
+
await sleep(700)
|
|
86
|
+
await page.evaluate(() => window.scrollTo(0, 0))
|
|
87
|
+
await sleep(300)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Grow the viewport to the full document height BEFORE measuring, so that the
|
|
91
|
+
// screenshot's captureBeyondViewport expansion cannot re-layout the page and
|
|
92
|
+
// shift elements between measurement and capture.
|
|
93
|
+
const expand = async () => {
|
|
94
|
+
const h = await page.evaluate(() => document.documentElement.scrollHeight)
|
|
95
|
+
await page.setViewport({ width: 1680, height: Math.min(Math.ceil(h) + 40, 14000), deviceScaleFactor: 3 })
|
|
96
|
+
await sleep(1200)
|
|
97
|
+
await settle()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const repaint = async () => {
|
|
101
|
+
await page.evaluate(() => new Promise(res => {
|
|
102
|
+
window.scrollTo(0, 0)
|
|
103
|
+
requestAnimationFrame(() => requestAnimationFrame(res))
|
|
104
|
+
}))
|
|
105
|
+
await sleep(120)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Capture only once the pixels stop changing: two consecutive identical captures are
|
|
109
|
+
// required (guards against mid-transition / late-commit frames like double-struck text).
|
|
110
|
+
const shot = async (n, x, y, w, h, tries = 5) => {
|
|
111
|
+
const clip = { x, y, width: w, height: h }
|
|
112
|
+
let prev = null
|
|
113
|
+
let stable = false
|
|
114
|
+
for (let i = 0; i < tries; i++) {
|
|
115
|
+
await repaint()
|
|
116
|
+
const buf = Buffer.from(await page.screenshot({ encoding: 'binary', clip, captureBeyondViewport: true }))
|
|
117
|
+
if (prev && Buffer.compare(prev, buf) === 0) { prev = buf; stable = true; break }
|
|
118
|
+
prev = buf
|
|
119
|
+
await sleep(1000)
|
|
120
|
+
}
|
|
121
|
+
fs.writeFileSync(path.join(OUT, n), prev)
|
|
122
|
+
console.log(` ✓ ${n} ${Math.round(w)}x${Math.round(h)} css @3x ${stable ? 'stable' : 'NOT STABLE (last frame written)'}`)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// ── force English UI before any capture ──────────────────────────────────
|
|
126
|
+
await auth(ADMIN)
|
|
127
|
+
await goto('/daq', 4000)
|
|
128
|
+
await page.evaluate(() => localStorage.setItem('aw.locale', 'en'))
|
|
129
|
+
await page.reload({ waitUntil: 'domcontentloaded' })
|
|
130
|
+
await sleep(6000)
|
|
131
|
+
const heads = await page.evaluate(() => [...document.querySelectorAll('.nodes-table thead th')].map(t => t.textContent.trim()))
|
|
132
|
+
if (!heads.some(h => /Sampling/i.test(h))) throw new Error(`locale not English (headers: ${JSON.stringify(heads)})`)
|
|
133
|
+
console.log(`locale : en (headers: ${heads.join(' | ')})`)
|
|
134
|
+
|
|
135
|
+
// ══ ① provision & connect — /daq nodes filtered to the English line ══════
|
|
136
|
+
console.log('\n== ① /daq ==')
|
|
137
|
+
await page.evaluate((ln) => {
|
|
138
|
+
for (const s of document.querySelectorAll('select')) {
|
|
139
|
+
if ([...s.options].some(o => o.value === ln)) { s.value = ln; s.dispatchEvent(new Event('change', { bubbles: true })); break }
|
|
140
|
+
}
|
|
141
|
+
}, LINE.id)
|
|
142
|
+
await sleep(3500)
|
|
143
|
+
await settle()
|
|
144
|
+
await expand()
|
|
145
|
+
|
|
146
|
+
const G = await page.evaluate(() => {
|
|
147
|
+
const tbl = document.querySelector('.nodes-table')
|
|
148
|
+
const rect = tbl.getBoundingClientRect()
|
|
149
|
+
const ths = [...tbl.querySelectorAll('thead th')].map(t => ({ x: Math.round(t.getBoundingClientRect().x - rect.x), text: t.textContent.trim() }))
|
|
150
|
+
const rows = [...tbl.querySelectorAll('tbody tr')].map((tr) => {
|
|
151
|
+
const q = tr.getBoundingClientRect()
|
|
152
|
+
const tds = tr.querySelectorAll('td')
|
|
153
|
+
return { y: Math.round(q.y + window.scrollY), h: q.height, name: (tr.querySelector('b')?.textContent || '').trim(), driver: (tds[6]?.textContent || '').trim() }
|
|
154
|
+
})
|
|
155
|
+
return { x: rect.x, ths, rows }
|
|
156
|
+
})
|
|
157
|
+
console.log(' rows:', G.rows.map(r => `${r.name}[${r.driver}]`).join(', '))
|
|
158
|
+
if (!G.rows.some(r => r.driver.includes('modbus'))) throw new Error('no Modbus-TCP row visible on filtered line')
|
|
159
|
+
// columns: 4 Sampling · 5 WS Publish · 6 Driver · 7 Line (indices are locale-stable).
|
|
160
|
+
// Keep the crop ≤470 css px: 12 px UI text must print ≥ ~6.4 pt at the 88 mm panel width.
|
|
161
|
+
const mbIdx = G.rows.findIndex(r => r.driver.includes('modbus'))
|
|
162
|
+
const mkIdx = mbIdx > 0 ? mbIdx - 1 : 0
|
|
163
|
+
const x0 = G.x + G.ths[4].x
|
|
164
|
+
const x1 = G.x + G.ths[8].x
|
|
165
|
+
const yS = G.rows[mkIdx].y
|
|
166
|
+
const yE = G.rows[mbIdx].y + G.rows[mbIdx].h
|
|
167
|
+
console.log(` pair: ${G.rows[mkIdx].name}[${G.rows[mkIdx].driver}] + ${G.rows[mbIdx].name}[${G.rows[mbIdx].driver}]`)
|
|
168
|
+
await shot('p1-driver.png', x0 - 2, yS, Math.min(x1 - x0 + 4, 470), yE - yS)
|
|
169
|
+
|
|
170
|
+
// ══ ② bind node → line / device — /dcw/{English line} ═══════════════════
|
|
171
|
+
console.log('\n== ② /dcw/' + LINE.id + ' ==')
|
|
172
|
+
await goto(`/dcw/${LINE.id}`, 9000)
|
|
173
|
+
await settle()
|
|
174
|
+
await expand()
|
|
175
|
+
const D = await page.evaluate(() => {
|
|
176
|
+
const t = document.querySelector('.aw-tile.table-card table')
|
|
177
|
+
const rect = t.getBoundingClientRect()
|
|
178
|
+
const ths = [...t.querySelectorAll('thead th')].map(x => ({ x: Math.round(x.getBoundingClientRect().x - rect.x), text: x.textContent.trim() }))
|
|
179
|
+
return { x: rect.x, y: rect.y + window.scrollY, h: rect.height, ths }
|
|
180
|
+
})
|
|
181
|
+
console.log(' cols:', D.ths.map(c => c.text).join(' | '))
|
|
182
|
+
// columns: 5 Setpoint · 6 Process Range · 7 Hold Interval · 8 Bound Device
|
|
183
|
+
const c0 = D.x + D.ths[5].x
|
|
184
|
+
const c1 = D.x + D.ths[9].x
|
|
185
|
+
await shot('p2-binding.png', c0 - 6, D.y, (c1 - c0) + 12, D.h)
|
|
186
|
+
|
|
187
|
+
// ══ ④ governed closed loop — ledger optimization records (real, judged) ══
|
|
188
|
+
console.log('\n== ④ /dcw/' + RECORD_LINE + ' ledger ==')
|
|
189
|
+
await goto(`/dcw/${RECORD_LINE}`, 9000)
|
|
190
|
+
await page.evaluate((nid) => {
|
|
191
|
+
const s = document.querySelector('.ledger-card select.inp')
|
|
192
|
+
if (!s) return
|
|
193
|
+
s.value = nid
|
|
194
|
+
s.dispatchEvent(new Event('change', { bubbles: true }))
|
|
195
|
+
}, RECORD_NODE)
|
|
196
|
+
await sleep(3500)
|
|
197
|
+
await settle()
|
|
198
|
+
await expand()
|
|
199
|
+
const L = await page.evaluate(() => {
|
|
200
|
+
const card = document.querySelector('.ledger-card')
|
|
201
|
+
const col = [...card.querySelectorAll('.ledger-col')].find(c => c.querySelector('li.ledger-rec')) || [...card.querySelectorAll('.ledger-col')].pop()
|
|
202
|
+
const recs = [...col.querySelectorAll('li.ledger-rec')]
|
|
203
|
+
const r = col.getBoundingClientRect()
|
|
204
|
+
const last = recs[recs.length - 1].getBoundingClientRect()
|
|
205
|
+
return { x: r.x, y: r.y + window.scrollY, w: r.width, h: last.bottom - r.top, texts: recs.map(li => li.textContent.replace(/\s+/g, ' ').trim()) }
|
|
206
|
+
})
|
|
207
|
+
console.log(' records:', JSON.stringify(L.texts))
|
|
208
|
+
// tight: header + the real records, without the empty box below them
|
|
209
|
+
await shot('p4-records.png', L.x - 5, L.y - 5, Math.min(L.w + 14, 470), L.h + 14)
|
|
210
|
+
|
|
211
|
+
// ══ ③ compose & deploy the agent team — /workshop/teams ═════════════════
|
|
212
|
+
// Desktop viewport: one card in the multi-column grid (~343 px wide), which keeps the
|
|
213
|
+
// crop ≤470 css px so member text prints ≥ ~6 pt at the 88 mm panel width.
|
|
214
|
+
console.log('\n== ③ /workshop/teams ==')
|
|
215
|
+
await page.setViewport({ width: 1680, height: 1600, deviceScaleFactor: 3 })
|
|
216
|
+
await goto('/workshop/teams', 9000)
|
|
217
|
+
await settle()
|
|
218
|
+
await expand()
|
|
219
|
+
const cards = await page.evaluate(() => [...document.querySelectorAll('.grid .card')].map(e => {
|
|
220
|
+
const r = e.getBoundingClientRect()
|
|
221
|
+
const members = e.querySelectorAll('.member')
|
|
222
|
+
const last = members[members.length - 1]?.getBoundingClientRect()
|
|
223
|
+
return { x: Math.round(r.x), y: Math.round(r.y + window.scrollY), w: Math.round(r.width), h: Math.round(r.height), memberH: last ? Math.round(last.bottom - r.top) : Math.round(r.height), t: e.textContent.replace(/\s+/g, ' ').slice(0, 40) }
|
|
224
|
+
}))
|
|
225
|
+
console.log(' cards:', JSON.stringify(cards))
|
|
226
|
+
const tgt = cards.find(c => c.t.includes('Cast-film control team'))
|
|
227
|
+
if (!tgt) throw new Error('English team card not found on /workshop/teams')
|
|
228
|
+
// crop head + member rows only (excludes the Add-Member button) to keep the figure compact
|
|
229
|
+
await shot('p3-team.png', tgt.x - 5, tgt.y - 5, Math.min(tgt.w + 10, 470), tgt.memberH + 8)
|
|
230
|
+
|
|
231
|
+
console.log('\npageerrors:', errs.length ? errs.join(' | ') : 'none')
|
|
232
|
+
await browser.close()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render the walkthrough figure HTML to (a) an exact-size PDF via headless Chrome
|
|
3
|
+
* --print-to-pdf and (b) a high-resolution PNG for inspection/archival.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node scripts/render-walkthrough-figure.mjs
|
|
6
|
+
*/
|
|
7
|
+
import { execFileSync } from 'node:child_process'
|
|
8
|
+
import fs from 'node:fs'
|
|
9
|
+
import path from 'node:path'
|
|
10
|
+
import puppeteer from 'puppeteer-core'
|
|
11
|
+
|
|
12
|
+
const CHROME = process.env.AW_CHROME ?? 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe'
|
|
13
|
+
const DIR = path.resolve('paper/tii/figures/walkthrough')
|
|
14
|
+
const SRC = path.join(DIR, 'fig-walkthrough.html')
|
|
15
|
+
if (!fs.existsSync(SRC)) throw new Error('missing ' + SRC)
|
|
16
|
+
|
|
17
|
+
// ── (a) exact-size PDF ────────────────────────────────────────────────────
|
|
18
|
+
const pdfUrl = 'file:///' + SRC.replace(/\\/g, '/')
|
|
19
|
+
const outPdf = path.resolve('paper/tii/figures/pdf/fig-walkthrough.pdf')
|
|
20
|
+
fs.mkdirSync(path.dirname(outPdf), { recursive: true })
|
|
21
|
+
execFileSync(CHROME, [
|
|
22
|
+
'--headless=new', '--disable-gpu', '--no-pdf-header-footer',
|
|
23
|
+
`--print-to-pdf=${outPdf}`, pdfUrl,
|
|
24
|
+
], { stdio: 'ignore' })
|
|
25
|
+
const info = execFileSync('pdfinfo', [outPdf], { encoding: 'utf8' })
|
|
26
|
+
console.log('PDF :', outPdf)
|
|
27
|
+
console.log(' ', info.split('\n').filter(l => /Page size|Pages/.test(l)).join(' | ').trim())
|
|
28
|
+
|
|
29
|
+
// ── (b) high-resolution PNG (overall + per-panel) ─────────────────────────
|
|
30
|
+
const browser = await puppeteer.launch({ executablePath: CHROME, headless: 'new', args: ['--no-sandbox', '--disable-gpu', '--allow-file-access-from-files'] })
|
|
31
|
+
const page = await browser.newPage()
|
|
32
|
+
// 181 mm wide at 96 dpi/css == 684 css px; height is a minimum — fullPage grows to content
|
|
33
|
+
await page.setViewport({ width: 684, height: 200, deviceScaleFactor: 8 })
|
|
34
|
+
await page.goto(pdfUrl, { waitUntil: 'networkidle0' })
|
|
35
|
+
await page.evaluate(() => document.fonts?.ready)
|
|
36
|
+
await new Promise(r => setTimeout(r, 500))
|
|
37
|
+
await page.screenshot({ path: path.join(DIR, 'fig-walkthrough.png'), fullPage: true })
|
|
38
|
+
const png = fs.statSync(path.join(DIR, 'fig-walkthrough.png'))
|
|
39
|
+
console.log('PNG :', path.join(DIR, 'fig-walkthrough.png'), `${(png.size / 1024).toFixed(0)} KB`)
|
|
40
|
+
|
|
41
|
+
// medium-res variant for quick review
|
|
42
|
+
await page.setViewport({ width: 684, height: 200, deviceScaleFactor: 2 })
|
|
43
|
+
await page.screenshot({ path: path.join(DIR, '_preview.png'), fullPage: true })
|
|
44
|
+
console.log('PREV :', path.join(DIR, '_preview.png'))
|
|
45
|
+
|
|
46
|
+
await browser.close()
|